From 2c75fd588fb109e7603b17f58746e4b9d8504b17 Mon Sep 17 00:00:00 2001 From: marq24 Date: Sun, 18 Oct 2026 11:34:15 +0200 Subject: [PATCH 01/24] prepare for HACS -> HA Marketplace --- .github/workflows/release.yaml | 42 +++++------------- .../goecharger_api2/brand/icon.png | Bin 0 -> 15099 bytes .../goecharger_api2/brand/logo.png | Bin 0 -> 61238 bytes .../goecharger_api2/manifest.json | 2 +- hacs.json | 3 ++ 5 files changed, 16 insertions(+), 31 deletions(-) create mode 100644 custom_components/goecharger_api2/brand/icon.png create mode 100644 custom_components/goecharger_api2/brand/logo.png diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index ed7a86a..f944e2c 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -5,41 +5,23 @@ on: types: - published +permissions: + contents: write + jobs: - release: - name: Release goecharger_api2 + release_zip_file: + name: Build & Upload HACS zip release runs-on: ubuntu-latest - permissions: - contents: write steps: - # Step 1: Check out the code - - name: ⤵️ Check out code from GitHub - uses: actions/checkout@v4 - - # Step 2: (Optional, but recommended) Install yq (in case it's not present) - #- name: Install yq - # run: sudo apt-get update && sudo apt-get install -y yq - - # Step 3: Update version in manifest.json to match the release tag - #- name: 🔢 Adjust version number - # shell: bash - # run: | - # version="${{ github.event.release.tag_name }}" - # version="${version,,}" # convert to lowercase (e.g., V2024.10.1 -> v2024.10.1) - # version="${version#v}" # remove leading 'v' if present - # yq e -P -o=json \ - # -i ".version = \"${version}\"" \ - # "${{ github.workspace }}/custom_components/goecharger_api2/manifest.json" + - name: Checkout repository + uses: actions/checkout@v6 - # Step 4: Create a ZIP archive of the component directory - - name: 📦 Create zipped release package - shell: bash + - name: Build zip archive run: | cd "${{ github.workspace }}/custom_components/goecharger_api2" - zip goecharger_api2.zip -r ./ + zip -r "${{ github.workspace }}/goecharger_api2.zip" . -x "*__pycache__*" -x "*.pyc" - # Step 5: Upload ZIP as a release asset (for HACS) - - name: ⬆️ Upload zip to release - uses: softprops/action-gh-release@v2 + - name: Upload zip to GitHub Release + uses: softprops/action-gh-release@v3 with: - files: ${{ github.workspace }}/custom_components/goecharger_api2/goecharger_api2.zip \ No newline at end of file + files: ${{ github.workspace }}/goecharger_api2.zip \ No newline at end of file diff --git a/custom_components/goecharger_api2/brand/icon.png b/custom_components/goecharger_api2/brand/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..769c479c30d5f38b6cf43ebe608cc02fb47b2bd4 GIT binary patch literal 15099 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 literal 0 HcmV?d00001 diff --git a/custom_components/goecharger_api2/brand/logo.png b/custom_components/goecharger_api2/brand/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..622285f676966bf4eb65b57d54decfada770d994 GIT binary patch literal 61238 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 literal 0 HcmV?d00001 diff --git a/custom_components/goecharger_api2/manifest.json b/custom_components/goecharger_api2/manifest.json index 746795c..805e920 100644 --- a/custom_components/goecharger_api2/manifest.json +++ b/custom_components/goecharger_api2/manifest.json @@ -12,5 +12,5 @@ "iot_class": "local_push", "issue_tracker": "https://github.com/marq24/ha-goecharger-api2/issues", "requirements": ["msgpack>=1.1.2"], - "version": "2026.10.2" + "version": "2026.10.3" } diff --git a/hacs.json b/hacs.json index 5561997..bcd8d32 100644 --- a/hacs.json +++ b/hacs.json @@ -1,5 +1,8 @@ { "name": "go-e APIv2 Connect", + "zip_release": true, + "filename": "goecharger_api2.zip", + "content_in_root": false, "homeassistant": "2023.8.0", "hacs": "1.18.0", "render_readme": true From b2a96e47c7571c4c0923c40738de83711036c04b Mon Sep 17 00:00:00 2001 From: marq24 Date: Sun, 18 Oct 2026 11:41:30 +0200 Subject: [PATCH 02/24] actions/checkout@v2 -> actions/checkout@v6 --- .github/workflows/hacs.yaml | 2 +- .github/workflows/hassfest.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/hacs.yaml b/.github/workflows/hacs.yaml index 67b4bce..53fa08b 100644 --- a/.github/workflows/hacs.yaml +++ b/.github/workflows/hacs.yaml @@ -11,7 +11,7 @@ jobs: name: HACS Action runs-on: "ubuntu-latest" steps: - - uses: "actions/checkout@v2" + - uses: "actions/checkout@v6" - name: HACS Action uses: "hacs/action@main" with: diff --git a/.github/workflows/hassfest.yaml b/.github/workflows/hassfest.yaml index a9bf5c7..9b2b2f0 100644 --- a/.github/workflows/hassfest.yaml +++ b/.github/workflows/hassfest.yaml @@ -10,5 +10,5 @@ jobs: validate: runs-on: "ubuntu-latest" steps: - - uses: "actions/checkout@v2" + - uses: "actions/checkout@v6" - uses: "home-assistant/actions/hassfest@master" From fe7e80ae26dca6bf579f0d66804349275293d1bd Mon Sep 17 00:00:00 2001 From: marq24 Date: Sun, 18 Oct 2026 12:12:55 +0200 Subject: [PATCH 03/24] I must learn how to use git :-/ --- .../goecharger_api2/brand/icon@2x.png | Bin 0 -> 23353 bytes .../goecharger_api2/brand/logo@2x.png | Bin 0 -> 169086 bytes 2 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 custom_components/goecharger_api2/brand/icon@2x.png create mode 100644 custom_components/goecharger_api2/brand/logo@2x.png diff --git a/custom_components/goecharger_api2/brand/icon@2x.png b/custom_components/goecharger_api2/brand/icon@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..b39e59f8458719631e3d95070fde2b3e67321513 GIT binary patch literal 23353 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 literal 0 HcmV?d00001 diff --git a/custom_components/goecharger_api2/brand/logo@2x.png b/custom_components/goecharger_api2/brand/logo@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..4a77716c5ba3ba646d0aa7abfcf478a773c311fd GIT binary patch literal 169086 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 literal 0 HcmV?d00001 From 76b8dd8aa3e74e99d4bcffee435b8d367335edec Mon Sep 17 00:00:00 2001 From: marq24 Date: Sun, 18 Oct 2026 17:08:34 +0200 Subject: [PATCH 04/24] added icon to README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 2fd0046..c921f68 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# Home Assistant Integration 'go-eCharger & go-eController [via WebSocket & APIv2]' +# Home Assistant Integration 'go-eCharger & go-eController [via WebSocket & APIv2]' ![logo](https://github.com/marq24/ha-goecharger-api2/raw/main/logo.png) From c953777cda85a9a53762803b11e9febb7ba666ee Mon Sep 17 00:00:00 2001 From: marq24 Date: Fri, 23 Oct 2026 13:56:23 +0200 Subject: [PATCH 05/24] include resonable stuff from #173 --- custom_components/goecharger_api2/__init__.py | 10 +++++----- .../goecharger_api2/pygoecharger_ha/__init__.py | 5 ++++- custom_components/goecharger_api2/service.py | 15 +++++++++++++-- 3 files changed, 22 insertions(+), 8 deletions(-) diff --git a/custom_components/goecharger_api2/__init__.py b/custom_components/goecharger_api2/__init__.py index 3830864..4b1526c 100644 --- a/custom_components/goecharger_api2/__init__.py +++ b/custom_components/goecharger_api2/__init__.py @@ -266,7 +266,7 @@ async def check_and_write_to_16a(hass: HomeAssistant, config_entry_id: str, brid _LOGGER.info(f"reduce {a_key} to 16A -> writing {res[a_key]}") await bridge.write_value_to_key(a_key, res[a_key]) except Exception as e: - _LOGGER.error(f"16A checker: Error while forcing 16A settings:", e) + _LOGGER.error(f"16A checker: Error while forcing 16A settings: {type(e).__name__} - {e}") @staticmethod async def check_device_registry(hass: HomeAssistant): @@ -591,7 +591,7 @@ def _handle_client_connection_error(self, msg: str, exception: Exception): self._CLIENT_COMMUNICATION_ERROR_TS = time.time() self._CLIENT_COMMUNICATION_ERROR_COUNT += 1 if self._CLIENT_COMMUNICATION_ERROR_COUNT > 5: - _LOGGER.warning(f"{msg}: Too many ClientConnectionError #{self._CLIENT_COMMUNICATION_ERROR_COUNT} while fetching data:{type(exception).__name__} - {exception} - will try to restart integration.", stack_info=True) + _LOGGER.warning(f"{msg}: Too many ClientConnectionError #{self._CLIENT_COMMUNICATION_ERROR_COUNT} while fetching data: {type(exception).__name__} - {exception} - will try to restart integration.", stack_info=True) if not self._RESTART_TRIGGERED: _LOGGER.info(f"{msg}: TRIGGER RESTART...") self._RESTART_TRIGGERED = True @@ -651,10 +651,10 @@ async def async_write_key(self, key: str, value, entity: Entity = None) -> dict: except ClientConnectionError as cc_err: self._handle_client_connection_error("async_write_key()", cc_err) - raise ValueError(f"ClientConnectionError while writing {key} to wallbox: {type(cc_err.__name__)} - {cc_err}") from cc_err + raise ValueError(f"ClientConnectionError while writing {key} to wallbox: {type(cc_err).__name__} - {cc_err}") from cc_err except Exception as e: - _LOGGER.error(f"Error while writing single {key} to wallbox: {e}") - raise ValueError(f"Exception while writing {key} to wallbox: {e}") from e + _LOGGER.error(f"Error while writing single {key} to wallbox: {type(e).__name__} - {e}") + raise ValueError(f"Exception while writing {key} to wallbox: {type(e).__name__} - {e}") from e async def async_write_multiple_keys(self, attr:dict, key: str, value, entity: Entity = None) -> dict: if self._CLIENT_COMMUNICATION_ERROR_TS + CLIENT_COMMUNICATION_ERROR_DELAY_IN_SECONDS > time.time(): diff --git a/custom_components/goecharger_api2/pygoecharger_ha/__init__.py b/custom_components/goecharger_api2/pygoecharger_ha/__init__.py index 4733731..a5a1967 100644 --- a/custom_components/goecharger_api2/pygoecharger_ha/__init__.py +++ b/custom_components/goecharger_api2/pygoecharger_ha/__init__.py @@ -133,7 +133,10 @@ class GoeChargerApiV2Bridge: def __init__(self, web_session, intg_type:str, host:str|None=None, access_password:str|None=None, serial:str|None=None, token:str|None=None, lang:str = "en") -> None: + + self.host_url = None self.ws_url = None + if host is not None: # THIS IS LAN ACCESS... self.token = None @@ -574,7 +577,7 @@ async def _write_values_int(self, a_params, a_result_key, a_result_value) -> dic except aiohttp.ClientResponseError as io_exc: _LOGGER.warning(f"_write_values_int(): ClientResponseError while 'await res.json(): {io_exc}") - elif res.status == 500 and int(res.headers['Content-Length']) > 0: + elif res.status == 500 and (res.content_length or 0) > 0: try: r_json = await res.json() return {"err": r_json} diff --git a/custom_components/goecharger_api2/service.py b/custom_components/goecharger_api2/service.py index 9f4f779..f087bea 100644 --- a/custom_components/goecharger_api2/service.py +++ b/custom_components/goecharger_api2/service.py @@ -1,6 +1,8 @@ import asyncio import datetime import logging +import math + from numbers import Number from homeassistant.config_entries import ConfigEntry @@ -14,6 +16,15 @@ _COORDINATOR_PER_CONFIGENTRYID_MAP = {} +def _is_finite_number(value) -> bool: + """NaN and infinity are Numbers, but would be sent to the charger as invalid JSON.""" + if not isinstance(value, Number): + return False + try: + return math.isfinite(float(value)) + except (TypeError, ValueError, OverflowError): + return False + class GoeChargerApiV2Service(): def __init__(self, hass: HomeAssistant, config: ConfigEntry, coordinator): # pylint: disable=unused-argument """Initialize the sensor.""" @@ -46,9 +57,9 @@ async def set_pv_data(self, call: ServiceCall): ppv = call.data.get('ppv', 0) pakku = call.data.get('pakku', 0) if pgrid is not None and isinstance(pgrid, Number): - if not isinstance(ppv, Number): + if not _is_finite_number(ppv): ppv = 0 - if not isinstance(pakku, Number): + if not _is_finite_number(pakku): pakku = 0 payload = { From dbe80589642f9b35b164774ffa26e74e25ea3aaa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ale=C5=A1=20Vu=C4=8Dina?= <21204857+MrCodeLabs@users.noreply.github.com> Date: Fri, 23 Oct 2026 15:37:33 +0200 Subject: [PATCH 06/24] Target the selected charger and validate PV blueprint measurements (#171) * Target the selected charger in the PV surplus blueprint * Validate PV blueprint measurements before sending updates --- .../go-echarger-pv-surplus-data.yaml | 117 ++++++++++-- example/blueprint/automation/readme.md | 45 ++++- tests/blueprints/README.md | 20 ++ tests/blueprints/harness.py | 179 ++++++++++++++++++ tests/blueprints/requirements.txt | 3 + tests/blueprints/test_measurements.py | 171 +++++++++++++++++ tests/blueprints/test_target.py | 75 ++++++++ 7 files changed, 590 insertions(+), 20 deletions(-) create mode 100644 tests/blueprints/README.md create mode 100644 tests/blueprints/harness.py create mode 100644 tests/blueprints/requirements.txt create mode 100644 tests/blueprints/test_measurements.py create mode 100644 tests/blueprints/test_target.py diff --git a/example/blueprint/automation/go-echarger-pv-surplus-data.yaml b/example/blueprint/automation/go-echarger-pv-surplus-data.yaml index b702462..37914b8 100644 --- a/example/blueprint/automation/go-echarger-pv-surplus-data.yaml +++ b/example/blueprint/automation/go-echarger-pv-surplus-data.yaml @@ -11,6 +11,11 @@ blueprint: - Night, car connected: zero values every 30 seconds. - At sunset: one immediate zero push, even if no car is connected. + Daylight updates are skipped if any used power source is missing, non-numeric + or non-finite. Disabled battery inputs are ignored. Skipping an update does + not command the charger to stop and does not clear its previous averages. + This blueprint does not verify the age of otherwise numeric measurements. + The zero values keep the go-eCharger from working with stale `pvopt_*` averages. The charger calculates these averages from the last values it has received, no matter how old they are. Without the zero values, a car that is (re)connected at night can start @@ -31,6 +36,19 @@ blueprint: homeassistant: min_version: 2024.6.0 input: + charger_config_entry: + name: Target go-eCharger (optional) + description: > + Select the go-eCharger integration entry to receive all data, including + night and sunset zeros. Required when using multiple chargers. Leave + empty to keep the service's default charger selection. When selected, + choose explicit Car connected entities from this same integration entry; + device and area targets are not supported with an explicit charger. + default: "" + selector: + config_entry: + integration: goecharger_api2 + car_connected: name: Car connected Entity description: Entity from go-eCharger API2 integration that indicates the vehicle connection status @@ -168,7 +186,25 @@ blueprint: boolean: variables: + charger_config_entry: !input charger_config_entry car_connected_entity: !input car_connected_entity + charger_target_valid: > + {% if not charger_config_entry %} + {{ true }} + {% else %} + {% set entities = car_connected_entity.get('entity_id', []) %} + {% set entities = [entities] if entities is string else entities %} + {% set ns = namespace(valid=entities | length > 0 + and not car_connected_entity.get('device_id') + and not car_connected_entity.get('area_id') + and config_entry_attr(charger_config_entry, 'domain') == 'goecharger_api2') %} + {% for entity in entities %} + {% if config_entry_id(entity) != charger_config_entry %} + {% set ns.valid = false %} + {% endif %} + {% endfor %} + {{ ns.valid }} + {% endif %} multiplier: !input power_multiplier grid_single_entity: !input grid_single_entity grid_single_invert: !input grid_single_invert @@ -182,18 +218,46 @@ variables: battery_list: !input battery_entities battery_invert_flag: !input battery_invert + # Preserve method precedence; an incomplete selected method must not fall back. + grid_sources: > + {% if grid_single_entity %} + {{ [grid_single_entity] }} + {% elif grid_consumption or grid_feedin %} + {{ [grid_consumption, grid_feedin] }} + {% elif grid_phases | length == 3 and grid_phases | unique | list | length == 3 %} + {{ grid_phases }} + {% else %} + {{ [] }} + {% endif %} + # Capture each used state once, so validation and arithmetic use the same sample. + power_samples: > + {% set ns = namespace(samples={}) %} + {% for entity in (grid_sources + pv_list + (battery_list if battery_enable else [])) | unique %} + {% set ns.samples = dict(ns.samples, **{entity: states(entity)}) if entity else ns.samples %} + {% endfor %} + {{ ns.samples }} + measurements_valid: > + {% set ns = namespace(valid=grid_sources | length > 0 and pv_list | length > 0 + and is_number(multiplier) and multiplier | float(0) > 0) %} + {% for entity in grid_sources + pv_list + (battery_list if battery_enable else []) %} + {% if not entity or not is_number(power_samples.get(entity)) %} + {% set ns.valid = false %} + {% endif %} + {% endfor %} + {{ ns.valid }} + pgrid_value: > {% if grid_single_entity %} - {% set value = states(grid_single_entity) | float(0) %} + {% set value = power_samples.get(grid_single_entity) | float(0) %} {{ (value * -1) if grid_single_invert else value }} {% elif grid_consumption and grid_feedin %} - {% set cons_val = states(grid_consumption) | float(0) %} - {% set feed_val = states(grid_feedin) | float(0) %} + {% set cons_val = power_samples.get(grid_consumption) | float(0) %} + {% set feed_val = power_samples.get(grid_feedin) | float(0) %} {{ cons_val - feed_val }} {% elif grid_phases | length >= 3 %} {% set ns = namespace(total=0) %} {% for entity in grid_phases[:3] %} - {% set ns.total = ns.total + (states(entity) | float(0)) %} + {% set ns.total = ns.total + (power_samples.get(entity) | float(0)) %} {% endfor %} {{ (ns.total * -1) if grid_phases_invert else ns.total }} {% else %} @@ -203,7 +267,7 @@ variables: ppv_value: > {% set ns = namespace(total=0) %} {% for entity in pv_list %} - {% set ns.total = ns.total + (states(entity) | float(0)) %} + {% set ns.total = ns.total + (power_samples.get(entity) | float(0)) %} {% endfor %} {{ (ns.total * -1) if pv_invert_flag else ns.total }} @@ -211,13 +275,17 @@ variables: {% if battery_enable %} {% set ns = namespace(total=0) %} {% for entity in battery_list %} - {% set ns.total = ns.total + (states(entity) | float(0)) %} + {% set ns.total = ns.total + (power_samples.get(entity) | float(0)) %} {% endfor %} {{ (ns.total * -1) if battery_invert_flag else ns.total }} {% else %} 0 {% endif %} + pgrid_watts: '{{ (pgrid_value | float(0)) * (multiplier | float(0)) }}' + ppv_watts: '{{ (ppv_value | float(0)) * (multiplier | float(0)) }}' + pakku_watts: '{{ (pakku_value | float(0)) * (multiplier | float(0)) }}' + triggers: - trigger: time_pattern seconds: /5 @@ -227,6 +295,12 @@ triggers: id: sunset actions: + - if: + - condition: template + value_template: '{{ not charger_target_valid }}' + then: + - stop: "Select Car connected entities belonging to the target go-eCharger integration entry." + error: true - choose: # Daylight, car connected: real data every 5 seconds - conditions: @@ -239,11 +313,18 @@ actions: - condition: sun before: sunset sequence: + - condition: template + alias: "Skip daylight update when a used measurement is invalid" + value_template: > + {{ measurements_valid and is_number(pgrid_watts) + and is_number(ppv_watts) and is_number(pakku_watts) }} - action: goecharger_api2.set_pv_data - data: - pgrid: '{{ ((pgrid_value | float(0)) * (multiplier | float(1))) | round(0) | int }}' - ppv: '{{ ((ppv_value | float(0)) * (multiplier | float(1))) | round(0) | int }}' - pakku: '{{ ((pakku_value | float(0)) * (multiplier | float(1))) | round(0) | int }}' + data: > + {% set payload = dict( + pgrid=(pgrid_watts | float) | round(0) | int, + ppv=(ppv_watts | float) | round(0) | int, + pakku=(pakku_watts | float) | round(0) | int) %} + {{ dict(payload, configid=charger_config_entry) if charger_config_entry else payload }} # Night, car connected: zeros every 30 seconds (flushes stale averages) - conditions: @@ -263,10 +344,9 @@ actions: value_template: '{{ now().second % 30 < 5 }}' sequence: - action: goecharger_api2.set_pv_data - data: - pgrid: 0 - ppv: 0 - pakku: 0 + data: > + {% set payload = dict(pgrid=0, ppv=0, pakku=0) %} + {{ dict(payload, configid=charger_config_entry) if charger_config_entry else payload }} # Sunset: one immediate zero push, regardless of car connection - conditions: @@ -274,9 +354,8 @@ actions: id: sunset sequence: - action: goecharger_api2.set_pv_data - data: - pgrid: 0 - ppv: 0 - pakku: 0 + data: > + {% set payload = dict(pgrid=0, ppv=0, pakku=0) %} + {{ dict(payload, configid=charger_config_entry) if charger_config_entry else payload }} -mode: single \ No newline at end of file +mode: single diff --git a/example/blueprint/automation/readme.md b/example/blueprint/automation/readme.md index 1dcfd41..e29d3fe 100644 --- a/example/blueprint/automation/readme.md +++ b/example/blueprint/automation/readme.md @@ -34,6 +34,20 @@ This blueprint enables automatic PV surplus charging for go-eCharger wallboxes b ## Configuration +### Target go-eCharger +Select the go-eCharger integration entry when more than one charger is configured. +All three transmission paths (daylight, night and sunset) use this entry. +Create one automation per charger and select its own **Car connected** entity. + +Leaving this input empty preserves the service's existing default selection; +the blueprint omits `configid` rather than passing an empty ID. The service +uses its first registered charger, so select an explicit target for multiple chargers. + +With an explicit target, select individual Car connected entities belonging to +that same integration entry. Mixed chargers, device/area targets and a removed +or invalid entry stop the automation with an error visible in its trace. +The blueprint does not silently fall back to another charger. + ### Car Connected Select the "car connected" entity (`binary_sensor`) of your go-eCharger from the API2 integration. - Example: `binary_sensor.goe_123456_car_0` @@ -75,6 +89,35 @@ Choose **ONE** method: | Night, car connected | Zero values every 30 seconds | | At sunset | One immediate zero push, regardless of car connection | +### Invalid measurements + +Daylight updates require finite numeric states for every used grid and PV +entity and, when enabled, every selected battery entity. `unknown`, +`unavailable`, missing entities, text, NaN and infinity skip the entire update; +they are not replaced with zero or omitted from a partial sum. A real zero is +valid. Each used state is captured once for validation and calculation. +Non-finite totals or unit conversion results also skip the update. + +The existing grid method precedence remains single entity, then consumption / +feed-in, then phases. Both consumption and feed-in must be selected; phases +require exactly three distinct entities. An incomplete selected method does +not fall back to another method. Unused grid inputs are ignored. + +An empty battery selection means no battery measurement and still sends zero. +Disabling battery transmission ignores any selected battery entities and also +sends zero. An enabled, selected but unavailable battery skips the update. +At least one PV entity is required. The multiplier must be finite and positive. + +The failed condition is visible in the automation trace. Transmission resumes +on the next daylight tick with valid measurements and a connected car. Night +and sunset zeros remain independent of measurement validity. + +**This is input validation, not a charging-stop policy.** Skipping transmission +does not issue a Stop command or clear previous `pvopt_*` averages. Ensure your +installation has an appropriate independent response to loss of measurement +data. The blueprint does not check source timestamps or freshness: an old but +numeric state passes validation. No fixed age threshold is assumed. + The go-eCharger calculates its `pvopt_*` averages from the last values it has received, no matter how old they are. Without the zero values, a car that is (re)connected at night can start charging at sunrise on the basis of the previous day's surplus, even though the PV generation has not started yet. ## go-eCharger Setup @@ -105,4 +148,4 @@ The go-eCharger calculates its `pvopt_*` averages from the last values it has re ## Support For issues with the blueprint, please create an issue in this repository. -For go-eCharger integration issues, see the [main integration documentation](../../../README.md). \ No newline at end of file +For go-eCharger integration issues, see the [main integration documentation](../../../README.md). diff --git a/tests/blueprints/README.md b/tests/blueprints/README.md new file mode 100644 index 0000000..df3b404 --- /dev/null +++ b/tests/blueprints/README.md @@ -0,0 +1,20 @@ +# Blueprint regression tests + +From the repository root, in an isolated Python 3.12 environment: + +```sh +python -m pip install -r tests/blueprints/requirements.txt +python -m pytest tests/blueprints -q +``` + +Tests load the actual blueprint YAML, resolve input defaults, render its Jinja +templates and evaluate the supported action/condition tree using synthetic +states and entity/config-entry registries. Routing tests additionally execute +the unchanged upstream `set_pv_data` method extracted from `service.py`, with +two fake LAN coordinators. No hardware, credentials or network is used. + +This is not a Home Assistant runtime/schema validation or hardware test. +The state/registry helpers and action evaluator are test doubles; HA import, +selector UI and automation traces must still be checked in a disposable HA +instance before release. WAN filtering and firmware behaviour are outside +these tests. No claim is made that stopping transmission stops charging. diff --git a/tests/blueprints/harness.py b/tests/blueprints/harness.py new file mode 100644 index 0000000..7db603b --- /dev/null +++ b/tests/blueprints/harness.py @@ -0,0 +1,179 @@ +"""Execute the blueprint's actual templates and action tree with synthetic HA data. + +This is an isolated regression harness, not a Home Assistant runtime or a device +test. Registry/state helpers are doubles. Unsupported actions fail explicitly. +Template results use literal parsing as HA does for dict/bool/number results. +""" + +import ast +import logging +import math +from numbers import Number +from pathlib import Path +from types import SimpleNamespace +from unittest.mock import AsyncMock + +from jinja2 import StrictUndefined +from jinja2.sandbox import SandboxedEnvironment +import yaml + +ROOT = Path(__file__).resolve().parents[2] +BLUEPRINT = ROOT / "example/blueprint/automation/go-echarger-pv-surplus-data.yaml" + + +class Input(str): + pass + + +class Loader(yaml.SafeLoader): + pass + + +Loader.add_constructor("!input", lambda loader, node: Input(loader.construct_scalar(node))) + + +def is_number(value): + # Contract of HA is_number: numeric conversion must be finite. + try: + return math.isfinite(float(value)) + except (ValueError, TypeError, OverflowError): + return False + + +class BlueprintRun: + def __init__(self, inputs=None, states=None, target="", connected="on", + registry=None, entries=None, daylight=True, second=0, trigger="tick"): + self.document = yaml.load(BLUEPRINT.read_text(encoding="utf-8"), Loader=Loader) + self.inputs = {} + for name, value in self.document["blueprint"]["input"].items(): + for key, field in value.get("input", {name: value}).items(): + if "default" in field: + self.inputs[key] = field["default"] + self.inputs.update(car_connected_entity={"entity_id": "binary_sensor.car_a"}, + charger_config_entry=target, grid_single_entity="sensor.grid", + pv_entities=["sensor.pv"]) + self.inputs.update(inputs or {}) + self.states = {"binary_sensor.car_a": connected, "binary_sensor.car_b": connected, + "sensor.grid": "-1200", "sensor.pv": "2000"} + self.states.update(states or {}) + self.registry = registry if registry is not None else { + "binary_sensor.car_a": "entry_a", "binary_sensor.car_b": "entry_b"} + self.entries = entries if entries is not None else { + "entry_a": "goecharger_api2", "entry_b": "goecharger_api2"} + self.daylight, self.trigger = daylight, trigger + self.env = SandboxedEnvironment(undefined=StrictUndefined) + self.env.globals.update( + states=lambda entity: self.states.get(entity, "unknown"), + expand=self.expand, + config_entry_id=lambda entity: self.registry.get(entity), + config_entry_attr=lambda entry, attr: self.entries.get(entry) if attr == "domain" else None, + is_number=is_number, + now=lambda: SimpleNamespace(second=second), + ) + self.variables = {} + self.calls = [] + self.stopped = None + + def expand(self, entities): + if isinstance(entities, str): + entities = [entities] + return [SimpleNamespace(entity_id=entity, state=self.states[entity]) + for entity in entities if entity in self.states] + + def render(self, value): + if isinstance(value, Input): + return self.inputs[value] + if isinstance(value, dict): + return {key: self.render(item) for key, item in value.items()} + if isinstance(value, list): + return [self.render(item) for item in value] + if isinstance(value, str) and ("{{" in value or "{%" in value): + rendered = self.env.from_string(value).render(self.variables).strip() + try: + return ast.literal_eval(rendered) + except (ValueError, SyntaxError): + return rendered + return value + + def condition(self, condition): + kind = condition["condition"] + if kind == "template": + result = self.render(condition["value_template"]) + assert isinstance(result, bool), result + return result + if kind == "trigger": + return condition["id"] == self.trigger + if kind == "sun": + return self.daylight + if kind == "and": + return all(self.condition(item) for item in condition["conditions"]) + if kind == "not": + return not any(self.condition(item) for item in condition["conditions"]) + raise AssertionError(f"Unsupported condition: {condition}") + + def actions(self, actions): + for action in actions: + if self.stopped: + return + if "if" in action: + branch = "then" if all(self.condition(c) for c in action["if"]) else "else" + self.actions(action.get(branch, [])) + elif "choose" in action: + for choice in action["choose"]: + if all(self.condition(c) for c in choice["conditions"]): + self.actions(choice["sequence"]) + break + elif "stop" in action: + self.stopped = action["stop"] + elif "condition" in action: + if not self.condition(action): + self.stopped = action.get("alias", "condition failed") + elif "variables" in action: + for key, value in action["variables"].items(): + self.variables[key] = self.render(value) + elif action.get("action") == "goecharger_api2.set_pv_data": + payload = self.render(action["data"]) + assert isinstance(payload, dict) + self.calls.append(payload) + else: + raise AssertionError(f"Unsupported action: {action}") + + def run(self): + for key, value in self.document["variables"].items(): + self.variables[key] = self.render(value) + self.actions(self.document["actions"]) + return self.calls + + +def dispatch(payload, available=("entry_a", "entry_b")): + """Run the unmodified upstream service method with fake LAN coordinators. + + AST extraction avoids importing/starting the complete HA integration. The + production method body is used verbatim; only external types/globals are stubbed. + """ + tree = ast.parse((ROOT / "custom_components/goecharger_api2/service.py").read_text()) + service = next(node for node in tree.body if isinstance(node, ast.ClassDef) + and node.name == "GoeChargerApiV2Service") + method = next(node for node in service.body if isinstance(node, ast.AsyncFunctionDef) + and node.name == "set_pv_data") + module = ast.fix_missing_locations(ast.Module(body=[method], type_ignores=[])) + coordinators = { + entry: SimpleNamespace(name=entry, _serial="example", intg_type="charger", mode="LAN", + async_write_key=AsyncMock(return_value={})) for entry in available + } + namespace = {"ServiceCall": object, "Number": Number, "WAN": "WAN", + "INTG_TYPE": SimpleNamespace(CHARGER=SimpleNamespace(value="charger")), + "Tag": SimpleNamespace(IDS=SimpleNamespace(key="ids")), + "_LOGGER": logging.getLogger(__name__), + "_COORDINATOR_PER_CONFIGENTRYID_MAP": coordinators} + exec(compile(module, "upstream/service.py", "exec"), namespace) + # All awaited calls are immediate AsyncMocks; no event loop or sockets are needed. + coroutine = namespace["set_pv_data"](None, SimpleNamespace(data=payload, return_response=False)) + try: + coroutine.send(None) + except StopIteration: + pass + else: + coroutine.close() + raise AssertionError("Unexpected asynchronous I/O in the service double") + return {entry: obj.async_write_key.call_args_list for entry, obj in coordinators.items()} diff --git a/tests/blueprints/requirements.txt b/tests/blueprints/requirements.txt new file mode 100644 index 0000000..5b295d8 --- /dev/null +++ b/tests/blueprints/requirements.txt @@ -0,0 +1,3 @@ +Jinja2==3.1.6 +PyYAML==6.0.3 +pytest==8.4.2 diff --git a/tests/blueprints/test_measurements.py b/tests/blueprints/test_measurements.py new file mode 100644 index 0000000..1d25273 --- /dev/null +++ b/tests/blueprints/test_measurements.py @@ -0,0 +1,171 @@ +"""Input failures must not become plausible surplus or incomplete power sums.""" +from collections import Counter + +import pytest + +from harness import BlueprintRun + + +BAD_VALUES = ["unknown", "unavailable", "not a number", "", "NaN", "inf", "-inf", "1e999"] + + +@pytest.mark.parametrize("bad", BAD_VALUES) +@pytest.mark.parametrize("source", ["sensor.grid", "sensor.pv", "sensor.battery"]) +def test_bad_used_source_skips_whole_update(source, bad): + run = BlueprintRun(inputs={"battery_entities": ["sensor.battery"]}, + states={"sensor.battery": "0", source: bad}) + assert run.run() == [] + assert "measurement is invalid" in run.stopped + + +@pytest.mark.parametrize("input_name,value", [ + ("grid_single_entity", "sensor.missing"), ("pv_entities", ["sensor.missing"]), + ("battery_entities", ["sensor.missing"]), +]) +def test_missing_entity(input_name, value): + assert BlueprintRun(inputs={input_name: value}).run() == [] + + +@pytest.mark.parametrize("grid", ["0", "1200", "-1200"]) +def test_real_zero_import_and_export(grid): + payload = BlueprintRun(states={"sensor.grid": grid, "sensor.pv": "0"}).run()[0] + assert payload == {"pgrid": int(grid), "ppv": 0, "pakku": 0} + + +def dual_inputs(): + return {"grid_single_entity": None, "grid_consumption_entity": "sensor.import", + "grid_feedin_entity": "sensor.export"} + + +@pytest.mark.parametrize("missing", ["sensor.import", "sensor.export"]) +def test_dual_missing_input_cannot_become_export(missing): + states = {"sensor.import": "1500", "sensor.export": "1000", missing: "unavailable"} + assert BlueprintRun(inputs=dual_inputs(), states=states).run() == [] + + +def test_dual_valid(): + payload = BlueprintRun(inputs=dual_inputs(), states={ + "sensor.import": "1500", "sensor.export": "1000"}).run()[0] + assert payload["pgrid"] == 500 + + +@pytest.mark.parametrize("missing", ["grid_consumption_entity", "grid_feedin_entity"]) +def test_incomplete_dual_does_not_fall_back_to_phases(missing): + inputs = dict(dual_inputs(), grid_phase_entities=["sensor.l1", "sensor.l2", "sensor.l3"]) + inputs[missing] = None + assert BlueprintRun(inputs=inputs, states={ + "sensor.l1": "-1000", "sensor.l2": "-1000", "sensor.l3": "-1000"}).run() == [] + + +@pytest.mark.parametrize("missing_phase", ["sensor.l1", "sensor.l2", "sensor.l3"]) +def test_missing_phase_never_yields_partial_sum(missing_phase): + states = {"sensor.l1": "-1000", "sensor.l2": "-2000", "sensor.l3": "500", + missing_phase: "unavailable"} + run = BlueprintRun(inputs={"grid_single_entity": None, + "grid_phase_entities": ["sensor.l1", "sensor.l2", "sensor.l3"]}, states=states) + assert run.run() == [] + + +@pytest.mark.parametrize("phases", [[], ["sensor.l1"], ["sensor.l1", "sensor.l2"], + ["sensor.l1", "sensor.l2", "sensor.l3", "sensor.l4"], ["sensor.l1"] * 3]) +def test_phase_method_requires_three_distinct_entities(phases): + assert BlueprintRun(inputs={"grid_single_entity": None, "grid_phase_entities": phases}, + states={f"sensor.l{i}": "0" for i in range(1, 5)}).run() == [] + + +def test_three_phases_and_inversion(): + payload = BlueprintRun(inputs={"grid_single_entity": None, + "grid_phase_entities": ["sensor.l1", "sensor.l2", "sensor.l3"], + "grid_phases_invert": True}, states={ + "sensor.l1": "1000", "sensor.l2": "2000", "sensor.l3": "-500"}).run()[0] + assert payload["pgrid"] == -2500 + + +def test_unused_grid_inputs_do_not_block_selected_single(): + assert len(BlueprintRun(inputs={"grid_consumption_entity": "sensor.missing", + "grid_phase_entities": ["sensor.missing"]}).run()) == 1 + + +def test_failed_single_does_not_fall_back_to_healthy_dual(): + inputs = dict(dual_inputs(), grid_single_entity="sensor.missing") + assert BlueprintRun(inputs=inputs, states={"sensor.import": "0", "sensor.export": "1000"}).run() == [] + + +@pytest.mark.parametrize("enabled,entities", [(False, ["sensor.missing"]), (True, []), (False, [])]) +def test_intentionally_absent_battery(enabled, entities): + payload = BlueprintRun(inputs={"battery_enabled": enabled, "battery_entities": entities}).run()[0] + assert payload["pakku"] == 0 + + +@pytest.mark.parametrize("source_list", ["pv_entities", "battery_entities"]) +def test_multiple_sources_must_all_be_available(source_list): + assert BlueprintRun(inputs={source_list: ["sensor.first", "sensor.missing"]}, + states={"sensor.first": "1200"}).run() == [] + + +def test_empty_pv_is_invalid(): + assert BlueprintRun(inputs={"pv_entities": []}).run() == [] + + +def test_multiple_sources_inversion_and_kw_conversion(): + payload = BlueprintRun(inputs={"power_multiplier": 1000, + "grid_single_invert": True, "pv_entities": ["sensor.pv1", "sensor.pv2"], + "pv_invert": True, "battery_entities": ["sensor.b1", "sensor.b2"], + "battery_invert": True}, states={"sensor.grid": "1.25", "sensor.pv1": "-2", + "sensor.pv2": "-0.75", "sensor.b1": "0.25", "sensor.b2": "0.5"}).run()[0] + assert payload == {"pgrid": -1250, "ppv": 2750, "pakku": -750} + + +def test_rounding_after_conversion(): + payload = BlueprintRun(inputs={"power_multiplier": 1000}, states={ + "sensor.grid": "-0.1234", "sensor.pv": "0.5678"}).run()[0] + assert payload["pgrid"] == -123 + assert payload["ppv"] == 568 + + +@pytest.mark.parametrize("multiplier", [0, -1, "invalid", "NaN", "inf"]) +def test_invalid_multiplier(multiplier): + assert BlueprintRun(inputs={"power_multiplier": multiplier}).run() == [] + + +@pytest.mark.parametrize("source", ["grid", "pv", "battery"]) +def test_overflow_after_unit_conversion(source): + assert BlueprintRun(inputs={"power_multiplier": 1000, "battery_entities": ["sensor.battery"]}, + states={"sensor.battery": "0", f"sensor.{source}": "1e308"}).run() == [] + + +def test_overflow_of_sum(): + assert BlueprintRun(inputs={"pv_entities": ["sensor.first", "sensor.second"]}, + states={"sensor.first": "1e308", "sensor.second": "1e308"}).run() == [] + + +@pytest.mark.parametrize("target,entity", [("", "car_a"), ("entry_a", "car_a"), ("entry_b", "car_b")]) +@pytest.mark.parametrize("trigger,connected", [("tick", "on"), ("sunset", "off")]) +@pytest.mark.parametrize("bad", ["unavailable", "NaN", "inf"]) +def test_night_and_sunset_zeros_survive_invalid_measurements(target, entity, trigger, connected, bad): + run = BlueprintRun(target=target, daylight=False, trigger=trigger, connected=connected, + inputs={"car_connected_entity": {"entity_id": f"binary_sensor.{entity}"}}, + states={"sensor.grid": bad, "sensor.pv": bad}) + payload = run.run()[0] + assert (payload["pgrid"], payload["ppv"], payload["pakku"]) == (0, 0, 0) + assert payload.get("configid", "") == target + + +def test_recovers_on_next_valid_tick(): + assert BlueprintRun(states={"sensor.grid": "unavailable"}).run() == [] + assert BlueprintRun(states={"sensor.grid": "-500"}).run()[0]["pgrid"] == -500 + + +def test_validation_and_arithmetic_use_one_sample_per_source(): + run = BlueprintRun(inputs={"pv_entities": ["sensor.pv", "sensor.pv"]}) + reads = Counter() + + def changing_state(entity): + reads[entity] += 1 + return run.states.get(entity, "unknown") if reads[entity] == 1 else "unavailable" + + run.env.globals["states"] = changing_state + payload = run.run()[0] + assert payload["pgrid"] == -1200 + assert payload["ppv"] == 4000 # Existing sum semantics are unchanged. + assert reads == {"sensor.grid": 1, "sensor.pv": 1} diff --git a/tests/blueprints/test_target.py b/tests/blueprints/test_target.py new file mode 100644 index 0000000..8110586 --- /dev/null +++ b/tests/blueprints/test_target.py @@ -0,0 +1,75 @@ +"""Routing regressions: render actual YAML and run the upstream service method.""" +import pytest + +from harness import BlueprintRun, dispatch + + +@pytest.mark.parametrize("target,entity", [("entry_a", "car_a"), ("entry_b", "car_b"), ("", "car_a")]) +@pytest.mark.parametrize("daylight,trigger,connected", [(True, "tick", "on"), + (False, "tick", "on"), + (False, "sunset", "off")]) +def test_routes_all_paths(target, entity, daylight, trigger, connected): + run = BlueprintRun(target=target, daylight=daylight, trigger=trigger, connected=connected, + inputs={"car_connected_entity": {"entity_id": f"binary_sensor.{entity}"}}) + calls = run.run() + assert len(calls) == 1 + payload = calls[0] + if target: + assert payload["configid"] == target + else: + assert "configid" not in payload + expected = (-1200, 2000, 0) if daylight else (0, 0, 0) + assert tuple(payload[key] for key in ("pgrid", "ppv", "pakku")) == expected + writes = dispatch(payload) + assert len(writes[target or "entry_a"]) == 1 + assert sum(len(value) for value in writes.values()) == 1 + + +def test_legacy_single_charger(): + payload = BlueprintRun().run()[0] + assert len(dispatch(payload, available=("entry_b",))["entry_b"]) == 1 + + +@pytest.mark.parametrize("target,selection", [ + ("missing", {"entity_id": "binary_sensor.car_a"}), + ("entry_b", {"entity_id": "binary_sensor.car_a"}), + ("entry_a", {"entity_id": ["binary_sensor.car_a", "binary_sensor.car_b"]}), + ("entry_a", {"entity_id": "binary_sensor.missing"}), + ("entry_a", {"entity_id": []}), + ("entry_a", {"device_id": "example_device"}), + ("entry_a", {"area_id": "example_area"}), + ("entry_a", {"entity_id": "binary_sensor.car_a", "device_id": "example_device"}), +]) +@pytest.mark.parametrize("trigger", ["tick", "sunset"]) +def test_invalid_target_never_falls_back(target, selection, trigger): + run = BlueprintRun(target=target, trigger=trigger, inputs={"car_connected_entity": selection}) + assert run.run() == [] + assert "target go-eCharger" in run.stopped + + +def test_entry_from_wrong_integration_is_rejected(): + run = BlueprintRun(target="entry_a", entries={"entry_a": "other_integration"}) + assert run.run() == [] + assert run.stopped + + +def test_entry_disappearing_after_render_does_not_select_another_charger(): + payload = BlueprintRun(target="entry_a").run()[0] + with pytest.raises(KeyError): + dispatch(payload, available=("entry_b",)) + + +def test_string_and_list_car_selectors(): + for selection in ("binary_sensor.car_a", ["binary_sensor.car_a"]): + assert len(BlueprintRun(target="entry_a", inputs={ + "car_connected_entity": {"entity_id": selection}}).run()) == 1 + + +@pytest.mark.parametrize("daylight,connected,second", [(True, "off", 0), (False, "off", 0), + (False, "on", 5), (False, "on", 25)]) +def test_no_extra_calls(daylight, connected, second): + assert BlueprintRun(daylight=daylight, connected=connected, second=second).run() == [] + + +def test_night_thirty_second_tick(): + assert len(BlueprintRun(daylight=False, second=30).run()) == 1 From 60d9a0995c7cdb480883309262cd35ad16f98e33 Mon Sep 17 00:00:00 2001 From: Matthias Marquardt Date: Fri, 23 Oct 2026 15:38:50 +0200 Subject: [PATCH 07/24] removed tests tests are cool - but --- tests/blueprints/README.md | 20 --- tests/blueprints/harness.py | 179 -------------------------- tests/blueprints/requirements.txt | 3 - tests/blueprints/test_measurements.py | 171 ------------------------ tests/blueprints/test_target.py | 75 ----------- 5 files changed, 448 deletions(-) delete mode 100644 tests/blueprints/README.md delete mode 100644 tests/blueprints/harness.py delete mode 100644 tests/blueprints/requirements.txt delete mode 100644 tests/blueprints/test_measurements.py delete mode 100644 tests/blueprints/test_target.py diff --git a/tests/blueprints/README.md b/tests/blueprints/README.md deleted file mode 100644 index df3b404..0000000 --- a/tests/blueprints/README.md +++ /dev/null @@ -1,20 +0,0 @@ -# Blueprint regression tests - -From the repository root, in an isolated Python 3.12 environment: - -```sh -python -m pip install -r tests/blueprints/requirements.txt -python -m pytest tests/blueprints -q -``` - -Tests load the actual blueprint YAML, resolve input defaults, render its Jinja -templates and evaluate the supported action/condition tree using synthetic -states and entity/config-entry registries. Routing tests additionally execute -the unchanged upstream `set_pv_data` method extracted from `service.py`, with -two fake LAN coordinators. No hardware, credentials or network is used. - -This is not a Home Assistant runtime/schema validation or hardware test. -The state/registry helpers and action evaluator are test doubles; HA import, -selector UI and automation traces must still be checked in a disposable HA -instance before release. WAN filtering and firmware behaviour are outside -these tests. No claim is made that stopping transmission stops charging. diff --git a/tests/blueprints/harness.py b/tests/blueprints/harness.py deleted file mode 100644 index 7db603b..0000000 --- a/tests/blueprints/harness.py +++ /dev/null @@ -1,179 +0,0 @@ -"""Execute the blueprint's actual templates and action tree with synthetic HA data. - -This is an isolated regression harness, not a Home Assistant runtime or a device -test. Registry/state helpers are doubles. Unsupported actions fail explicitly. -Template results use literal parsing as HA does for dict/bool/number results. -""" - -import ast -import logging -import math -from numbers import Number -from pathlib import Path -from types import SimpleNamespace -from unittest.mock import AsyncMock - -from jinja2 import StrictUndefined -from jinja2.sandbox import SandboxedEnvironment -import yaml - -ROOT = Path(__file__).resolve().parents[2] -BLUEPRINT = ROOT / "example/blueprint/automation/go-echarger-pv-surplus-data.yaml" - - -class Input(str): - pass - - -class Loader(yaml.SafeLoader): - pass - - -Loader.add_constructor("!input", lambda loader, node: Input(loader.construct_scalar(node))) - - -def is_number(value): - # Contract of HA is_number: numeric conversion must be finite. - try: - return math.isfinite(float(value)) - except (ValueError, TypeError, OverflowError): - return False - - -class BlueprintRun: - def __init__(self, inputs=None, states=None, target="", connected="on", - registry=None, entries=None, daylight=True, second=0, trigger="tick"): - self.document = yaml.load(BLUEPRINT.read_text(encoding="utf-8"), Loader=Loader) - self.inputs = {} - for name, value in self.document["blueprint"]["input"].items(): - for key, field in value.get("input", {name: value}).items(): - if "default" in field: - self.inputs[key] = field["default"] - self.inputs.update(car_connected_entity={"entity_id": "binary_sensor.car_a"}, - charger_config_entry=target, grid_single_entity="sensor.grid", - pv_entities=["sensor.pv"]) - self.inputs.update(inputs or {}) - self.states = {"binary_sensor.car_a": connected, "binary_sensor.car_b": connected, - "sensor.grid": "-1200", "sensor.pv": "2000"} - self.states.update(states or {}) - self.registry = registry if registry is not None else { - "binary_sensor.car_a": "entry_a", "binary_sensor.car_b": "entry_b"} - self.entries = entries if entries is not None else { - "entry_a": "goecharger_api2", "entry_b": "goecharger_api2"} - self.daylight, self.trigger = daylight, trigger - self.env = SandboxedEnvironment(undefined=StrictUndefined) - self.env.globals.update( - states=lambda entity: self.states.get(entity, "unknown"), - expand=self.expand, - config_entry_id=lambda entity: self.registry.get(entity), - config_entry_attr=lambda entry, attr: self.entries.get(entry) if attr == "domain" else None, - is_number=is_number, - now=lambda: SimpleNamespace(second=second), - ) - self.variables = {} - self.calls = [] - self.stopped = None - - def expand(self, entities): - if isinstance(entities, str): - entities = [entities] - return [SimpleNamespace(entity_id=entity, state=self.states[entity]) - for entity in entities if entity in self.states] - - def render(self, value): - if isinstance(value, Input): - return self.inputs[value] - if isinstance(value, dict): - return {key: self.render(item) for key, item in value.items()} - if isinstance(value, list): - return [self.render(item) for item in value] - if isinstance(value, str) and ("{{" in value or "{%" in value): - rendered = self.env.from_string(value).render(self.variables).strip() - try: - return ast.literal_eval(rendered) - except (ValueError, SyntaxError): - return rendered - return value - - def condition(self, condition): - kind = condition["condition"] - if kind == "template": - result = self.render(condition["value_template"]) - assert isinstance(result, bool), result - return result - if kind == "trigger": - return condition["id"] == self.trigger - if kind == "sun": - return self.daylight - if kind == "and": - return all(self.condition(item) for item in condition["conditions"]) - if kind == "not": - return not any(self.condition(item) for item in condition["conditions"]) - raise AssertionError(f"Unsupported condition: {condition}") - - def actions(self, actions): - for action in actions: - if self.stopped: - return - if "if" in action: - branch = "then" if all(self.condition(c) for c in action["if"]) else "else" - self.actions(action.get(branch, [])) - elif "choose" in action: - for choice in action["choose"]: - if all(self.condition(c) for c in choice["conditions"]): - self.actions(choice["sequence"]) - break - elif "stop" in action: - self.stopped = action["stop"] - elif "condition" in action: - if not self.condition(action): - self.stopped = action.get("alias", "condition failed") - elif "variables" in action: - for key, value in action["variables"].items(): - self.variables[key] = self.render(value) - elif action.get("action") == "goecharger_api2.set_pv_data": - payload = self.render(action["data"]) - assert isinstance(payload, dict) - self.calls.append(payload) - else: - raise AssertionError(f"Unsupported action: {action}") - - def run(self): - for key, value in self.document["variables"].items(): - self.variables[key] = self.render(value) - self.actions(self.document["actions"]) - return self.calls - - -def dispatch(payload, available=("entry_a", "entry_b")): - """Run the unmodified upstream service method with fake LAN coordinators. - - AST extraction avoids importing/starting the complete HA integration. The - production method body is used verbatim; only external types/globals are stubbed. - """ - tree = ast.parse((ROOT / "custom_components/goecharger_api2/service.py").read_text()) - service = next(node for node in tree.body if isinstance(node, ast.ClassDef) - and node.name == "GoeChargerApiV2Service") - method = next(node for node in service.body if isinstance(node, ast.AsyncFunctionDef) - and node.name == "set_pv_data") - module = ast.fix_missing_locations(ast.Module(body=[method], type_ignores=[])) - coordinators = { - entry: SimpleNamespace(name=entry, _serial="example", intg_type="charger", mode="LAN", - async_write_key=AsyncMock(return_value={})) for entry in available - } - namespace = {"ServiceCall": object, "Number": Number, "WAN": "WAN", - "INTG_TYPE": SimpleNamespace(CHARGER=SimpleNamespace(value="charger")), - "Tag": SimpleNamespace(IDS=SimpleNamespace(key="ids")), - "_LOGGER": logging.getLogger(__name__), - "_COORDINATOR_PER_CONFIGENTRYID_MAP": coordinators} - exec(compile(module, "upstream/service.py", "exec"), namespace) - # All awaited calls are immediate AsyncMocks; no event loop or sockets are needed. - coroutine = namespace["set_pv_data"](None, SimpleNamespace(data=payload, return_response=False)) - try: - coroutine.send(None) - except StopIteration: - pass - else: - coroutine.close() - raise AssertionError("Unexpected asynchronous I/O in the service double") - return {entry: obj.async_write_key.call_args_list for entry, obj in coordinators.items()} diff --git a/tests/blueprints/requirements.txt b/tests/blueprints/requirements.txt deleted file mode 100644 index 5b295d8..0000000 --- a/tests/blueprints/requirements.txt +++ /dev/null @@ -1,3 +0,0 @@ -Jinja2==3.1.6 -PyYAML==6.0.3 -pytest==8.4.2 diff --git a/tests/blueprints/test_measurements.py b/tests/blueprints/test_measurements.py deleted file mode 100644 index 1d25273..0000000 --- a/tests/blueprints/test_measurements.py +++ /dev/null @@ -1,171 +0,0 @@ -"""Input failures must not become plausible surplus or incomplete power sums.""" -from collections import Counter - -import pytest - -from harness import BlueprintRun - - -BAD_VALUES = ["unknown", "unavailable", "not a number", "", "NaN", "inf", "-inf", "1e999"] - - -@pytest.mark.parametrize("bad", BAD_VALUES) -@pytest.mark.parametrize("source", ["sensor.grid", "sensor.pv", "sensor.battery"]) -def test_bad_used_source_skips_whole_update(source, bad): - run = BlueprintRun(inputs={"battery_entities": ["sensor.battery"]}, - states={"sensor.battery": "0", source: bad}) - assert run.run() == [] - assert "measurement is invalid" in run.stopped - - -@pytest.mark.parametrize("input_name,value", [ - ("grid_single_entity", "sensor.missing"), ("pv_entities", ["sensor.missing"]), - ("battery_entities", ["sensor.missing"]), -]) -def test_missing_entity(input_name, value): - assert BlueprintRun(inputs={input_name: value}).run() == [] - - -@pytest.mark.parametrize("grid", ["0", "1200", "-1200"]) -def test_real_zero_import_and_export(grid): - payload = BlueprintRun(states={"sensor.grid": grid, "sensor.pv": "0"}).run()[0] - assert payload == {"pgrid": int(grid), "ppv": 0, "pakku": 0} - - -def dual_inputs(): - return {"grid_single_entity": None, "grid_consumption_entity": "sensor.import", - "grid_feedin_entity": "sensor.export"} - - -@pytest.mark.parametrize("missing", ["sensor.import", "sensor.export"]) -def test_dual_missing_input_cannot_become_export(missing): - states = {"sensor.import": "1500", "sensor.export": "1000", missing: "unavailable"} - assert BlueprintRun(inputs=dual_inputs(), states=states).run() == [] - - -def test_dual_valid(): - payload = BlueprintRun(inputs=dual_inputs(), states={ - "sensor.import": "1500", "sensor.export": "1000"}).run()[0] - assert payload["pgrid"] == 500 - - -@pytest.mark.parametrize("missing", ["grid_consumption_entity", "grid_feedin_entity"]) -def test_incomplete_dual_does_not_fall_back_to_phases(missing): - inputs = dict(dual_inputs(), grid_phase_entities=["sensor.l1", "sensor.l2", "sensor.l3"]) - inputs[missing] = None - assert BlueprintRun(inputs=inputs, states={ - "sensor.l1": "-1000", "sensor.l2": "-1000", "sensor.l3": "-1000"}).run() == [] - - -@pytest.mark.parametrize("missing_phase", ["sensor.l1", "sensor.l2", "sensor.l3"]) -def test_missing_phase_never_yields_partial_sum(missing_phase): - states = {"sensor.l1": "-1000", "sensor.l2": "-2000", "sensor.l3": "500", - missing_phase: "unavailable"} - run = BlueprintRun(inputs={"grid_single_entity": None, - "grid_phase_entities": ["sensor.l1", "sensor.l2", "sensor.l3"]}, states=states) - assert run.run() == [] - - -@pytest.mark.parametrize("phases", [[], ["sensor.l1"], ["sensor.l1", "sensor.l2"], - ["sensor.l1", "sensor.l2", "sensor.l3", "sensor.l4"], ["sensor.l1"] * 3]) -def test_phase_method_requires_three_distinct_entities(phases): - assert BlueprintRun(inputs={"grid_single_entity": None, "grid_phase_entities": phases}, - states={f"sensor.l{i}": "0" for i in range(1, 5)}).run() == [] - - -def test_three_phases_and_inversion(): - payload = BlueprintRun(inputs={"grid_single_entity": None, - "grid_phase_entities": ["sensor.l1", "sensor.l2", "sensor.l3"], - "grid_phases_invert": True}, states={ - "sensor.l1": "1000", "sensor.l2": "2000", "sensor.l3": "-500"}).run()[0] - assert payload["pgrid"] == -2500 - - -def test_unused_grid_inputs_do_not_block_selected_single(): - assert len(BlueprintRun(inputs={"grid_consumption_entity": "sensor.missing", - "grid_phase_entities": ["sensor.missing"]}).run()) == 1 - - -def test_failed_single_does_not_fall_back_to_healthy_dual(): - inputs = dict(dual_inputs(), grid_single_entity="sensor.missing") - assert BlueprintRun(inputs=inputs, states={"sensor.import": "0", "sensor.export": "1000"}).run() == [] - - -@pytest.mark.parametrize("enabled,entities", [(False, ["sensor.missing"]), (True, []), (False, [])]) -def test_intentionally_absent_battery(enabled, entities): - payload = BlueprintRun(inputs={"battery_enabled": enabled, "battery_entities": entities}).run()[0] - assert payload["pakku"] == 0 - - -@pytest.mark.parametrize("source_list", ["pv_entities", "battery_entities"]) -def test_multiple_sources_must_all_be_available(source_list): - assert BlueprintRun(inputs={source_list: ["sensor.first", "sensor.missing"]}, - states={"sensor.first": "1200"}).run() == [] - - -def test_empty_pv_is_invalid(): - assert BlueprintRun(inputs={"pv_entities": []}).run() == [] - - -def test_multiple_sources_inversion_and_kw_conversion(): - payload = BlueprintRun(inputs={"power_multiplier": 1000, - "grid_single_invert": True, "pv_entities": ["sensor.pv1", "sensor.pv2"], - "pv_invert": True, "battery_entities": ["sensor.b1", "sensor.b2"], - "battery_invert": True}, states={"sensor.grid": "1.25", "sensor.pv1": "-2", - "sensor.pv2": "-0.75", "sensor.b1": "0.25", "sensor.b2": "0.5"}).run()[0] - assert payload == {"pgrid": -1250, "ppv": 2750, "pakku": -750} - - -def test_rounding_after_conversion(): - payload = BlueprintRun(inputs={"power_multiplier": 1000}, states={ - "sensor.grid": "-0.1234", "sensor.pv": "0.5678"}).run()[0] - assert payload["pgrid"] == -123 - assert payload["ppv"] == 568 - - -@pytest.mark.parametrize("multiplier", [0, -1, "invalid", "NaN", "inf"]) -def test_invalid_multiplier(multiplier): - assert BlueprintRun(inputs={"power_multiplier": multiplier}).run() == [] - - -@pytest.mark.parametrize("source", ["grid", "pv", "battery"]) -def test_overflow_after_unit_conversion(source): - assert BlueprintRun(inputs={"power_multiplier": 1000, "battery_entities": ["sensor.battery"]}, - states={"sensor.battery": "0", f"sensor.{source}": "1e308"}).run() == [] - - -def test_overflow_of_sum(): - assert BlueprintRun(inputs={"pv_entities": ["sensor.first", "sensor.second"]}, - states={"sensor.first": "1e308", "sensor.second": "1e308"}).run() == [] - - -@pytest.mark.parametrize("target,entity", [("", "car_a"), ("entry_a", "car_a"), ("entry_b", "car_b")]) -@pytest.mark.parametrize("trigger,connected", [("tick", "on"), ("sunset", "off")]) -@pytest.mark.parametrize("bad", ["unavailable", "NaN", "inf"]) -def test_night_and_sunset_zeros_survive_invalid_measurements(target, entity, trigger, connected, bad): - run = BlueprintRun(target=target, daylight=False, trigger=trigger, connected=connected, - inputs={"car_connected_entity": {"entity_id": f"binary_sensor.{entity}"}}, - states={"sensor.grid": bad, "sensor.pv": bad}) - payload = run.run()[0] - assert (payload["pgrid"], payload["ppv"], payload["pakku"]) == (0, 0, 0) - assert payload.get("configid", "") == target - - -def test_recovers_on_next_valid_tick(): - assert BlueprintRun(states={"sensor.grid": "unavailable"}).run() == [] - assert BlueprintRun(states={"sensor.grid": "-500"}).run()[0]["pgrid"] == -500 - - -def test_validation_and_arithmetic_use_one_sample_per_source(): - run = BlueprintRun(inputs={"pv_entities": ["sensor.pv", "sensor.pv"]}) - reads = Counter() - - def changing_state(entity): - reads[entity] += 1 - return run.states.get(entity, "unknown") if reads[entity] == 1 else "unavailable" - - run.env.globals["states"] = changing_state - payload = run.run()[0] - assert payload["pgrid"] == -1200 - assert payload["ppv"] == 4000 # Existing sum semantics are unchanged. - assert reads == {"sensor.grid": 1, "sensor.pv": 1} diff --git a/tests/blueprints/test_target.py b/tests/blueprints/test_target.py deleted file mode 100644 index 8110586..0000000 --- a/tests/blueprints/test_target.py +++ /dev/null @@ -1,75 +0,0 @@ -"""Routing regressions: render actual YAML and run the upstream service method.""" -import pytest - -from harness import BlueprintRun, dispatch - - -@pytest.mark.parametrize("target,entity", [("entry_a", "car_a"), ("entry_b", "car_b"), ("", "car_a")]) -@pytest.mark.parametrize("daylight,trigger,connected", [(True, "tick", "on"), - (False, "tick", "on"), - (False, "sunset", "off")]) -def test_routes_all_paths(target, entity, daylight, trigger, connected): - run = BlueprintRun(target=target, daylight=daylight, trigger=trigger, connected=connected, - inputs={"car_connected_entity": {"entity_id": f"binary_sensor.{entity}"}}) - calls = run.run() - assert len(calls) == 1 - payload = calls[0] - if target: - assert payload["configid"] == target - else: - assert "configid" not in payload - expected = (-1200, 2000, 0) if daylight else (0, 0, 0) - assert tuple(payload[key] for key in ("pgrid", "ppv", "pakku")) == expected - writes = dispatch(payload) - assert len(writes[target or "entry_a"]) == 1 - assert sum(len(value) for value in writes.values()) == 1 - - -def test_legacy_single_charger(): - payload = BlueprintRun().run()[0] - assert len(dispatch(payload, available=("entry_b",))["entry_b"]) == 1 - - -@pytest.mark.parametrize("target,selection", [ - ("missing", {"entity_id": "binary_sensor.car_a"}), - ("entry_b", {"entity_id": "binary_sensor.car_a"}), - ("entry_a", {"entity_id": ["binary_sensor.car_a", "binary_sensor.car_b"]}), - ("entry_a", {"entity_id": "binary_sensor.missing"}), - ("entry_a", {"entity_id": []}), - ("entry_a", {"device_id": "example_device"}), - ("entry_a", {"area_id": "example_area"}), - ("entry_a", {"entity_id": "binary_sensor.car_a", "device_id": "example_device"}), -]) -@pytest.mark.parametrize("trigger", ["tick", "sunset"]) -def test_invalid_target_never_falls_back(target, selection, trigger): - run = BlueprintRun(target=target, trigger=trigger, inputs={"car_connected_entity": selection}) - assert run.run() == [] - assert "target go-eCharger" in run.stopped - - -def test_entry_from_wrong_integration_is_rejected(): - run = BlueprintRun(target="entry_a", entries={"entry_a": "other_integration"}) - assert run.run() == [] - assert run.stopped - - -def test_entry_disappearing_after_render_does_not_select_another_charger(): - payload = BlueprintRun(target="entry_a").run()[0] - with pytest.raises(KeyError): - dispatch(payload, available=("entry_b",)) - - -def test_string_and_list_car_selectors(): - for selection in ("binary_sensor.car_a", ["binary_sensor.car_a"]): - assert len(BlueprintRun(target="entry_a", inputs={ - "car_connected_entity": {"entity_id": selection}}).run()) == 1 - - -@pytest.mark.parametrize("daylight,connected,second", [(True, "off", 0), (False, "off", 0), - (False, "on", 5), (False, "on", 25)]) -def test_no_extra_calls(daylight, connected, second): - assert BlueprintRun(daylight=daylight, connected=connected, second=second).run() == [] - - -def test_night_thirty_second_tick(): - assert len(BlueprintRun(daylight=False, second=30).run()) == 1 From 5328e74b75fd56a84743b77beed4a97dff434946 Mon Sep 17 00:00:00 2001 From: marq24 Date: Fri, 23 Oct 2026 15:41:52 +0200 Subject: [PATCH 08/24] housekeeping of tasks load/unload SERVICES (only for first/last) active charger make sure we have read data from a JSON response AI slope only add Tag.AMP after it's init completed remove data from device_info moved to baseline --- custom_components/goecharger_api2/__init__.py | 150 ++++++++---------- .../goecharger_api2/brand/icon.png | Bin 15099 -> 14423 bytes .../goecharger_api2/brand/icon@2x.png | Bin 23353 -> 29791 bytes .../goecharger_api2/config_flow.py | 2 +- custom_components/goecharger_api2/const.py | 2 +- custom_components/goecharger_api2/number.py | 18 ++- .../pygoecharger_ha/__init__.py | 16 +- 7 files changed, 88 insertions(+), 100 deletions(-) diff --git a/custom_components/goecharger_api2/__init__.py b/custom_components/goecharger_api2/__init__.py index 4b1526c..f56831e 100644 --- a/custom_components/goecharger_api2/__init__.py +++ b/custom_components/goecharger_api2/__init__.py @@ -28,14 +28,12 @@ device_registry as device_reg ) from homeassistant.helpers.aiohttp_client import async_get_clientsession -from homeassistant.helpers.device_registry import DeviceEntry, async_entries_for_config_entry from homeassistant.helpers.entity import Entity, EntityDescription from homeassistant.helpers.event import async_track_time_interval, async_call_later from homeassistant.helpers.typing import UNDEFINED, UndefinedType from homeassistant.helpers.update_coordinator import DataUpdateCoordinator from homeassistant.helpers.update_coordinator import UpdateFailed from homeassistant.loader import async_get_integration -from packaging.version import Version from custom_components.goecharger_api2.pygoecharger_ha import ( GoeChargerApiV2Bridge, @@ -70,9 +68,10 @@ WEBSOCKET_WATCHDOG_INTERVAL: Final = timedelta(minutes=5, seconds=1) COMMUNICATION_MODE_WEBSOCKET: Final = "WEBSOCKET" COMMUNICATION_MODE_HTTPGET: Final = "HTTPGET" +ACTIVE_CHARGER_COUNTER: Final = "active_charger_counter" async def async_migrate_entry(hass: HomeAssistant, config_entry: ConfigEntry): - if config_entry.version < CONFIG_VERSION: + if config_entry.version == 1: if config_entry.data is not None and len(config_entry.data) > 0: _LOGGER.debug(f"Migrating configuration from version {config_entry.version}.{config_entry.minor_version}") if config_entry.options is not None and len(config_entry.options): @@ -81,6 +80,23 @@ async def async_migrate_entry(hass: HomeAssistant, config_entry: ConfigEntry): new_data = config_entry.data hass.config_entries.async_update_entry(config_entry, data=new_data, options={}, version=CONFIG_VERSION, minor_version=CONFIG_MINOR_VERSION) _LOGGER.debug(f"Migration to configuration version {config_entry.version}.{config_entry.minor_version} successful") + + # migrate device registry entries -> actually we DELETE ALL... + if config_entry.version == 2 and config_entry.minor_version == 0: + try: + _LOGGER.debug(f"Migrating configuration from version {config_entry.version}.{config_entry.minor_version}") + a_device_reg = device_reg.async_get(hass) + if a_device_reg is not None: + all_devices = device_reg.async_entries_for_config_entry(a_device_reg, config_entry.entry_id) + for a_device in all_devices: + _LOGGER.debug(f"async_migrate_entry(): PURGING device: {a_device.id}") + a_device_reg.async_remove_device(device_id=a_device.id) + except BaseException as ex: + _LOGGER.warning(f"async_migrate_entry(): failed: {type(ex).__name__} - {ex}") + + hass.config_entries.async_update_entry(config_entry, version=CONFIG_VERSION, minor_version=CONFIG_MINOR_VERSION) + _LOGGER.info(f"async_migrate_entry(): Migration to configuration version {config_entry.version}.{config_entry.minor_version} successful") + return True @@ -109,11 +125,11 @@ async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry): use_websocket = True try: if not await coordinator.start_websocket_and_wait_for_first_data(is_integration_init=True): - _LOGGER.warning(f"The coordinator.start_websocket_and_wait_for_first_data() as returned FALSE") + _LOGGER.warning(f"async_setup_entry(): The coordinator.start_websocket_and_wait_for_first_data() as returned FALSE") raise ConfigEntryNotReady(lang_map["coord_no_device_data"]) except BaseException as exc: - _LOGGER.error(f"Error starting websocket connection: {type(exc).__name__} - {exc}", stack_info=True) + _LOGGER.error(f"async_setup_entry(): Error starting websocket connection: {type(exc).__name__} - {exc}", stack_info=True) raise ConfigEntryNotReady(lang_map["websocket_start_failed"]) else: use_websocket = False @@ -130,27 +146,34 @@ async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry): # initialize our service... if coordinator.intg_type == INTG_TYPE.CHARGER.value: - service = GoeChargerApiV2Service(hass, config_entry, coordinator) - hass.services.async_register(DOMAIN, SERVICE_SET_PV_DATA, service.set_pv_data, - supports_response=SupportsResponse.OPTIONAL) - hass.services.async_register(DOMAIN, SERVICE_STOP_CHARGING, service.stop_charging, - supports_response=SupportsResponse.OPTIONAL) + if ACTIVE_CHARGER_COUNTER not in hass.data[DOMAIN]: + hass.data[DOMAIN][ACTIVE_CHARGER_COUNTER] = 0 + hass.data[DOMAIN][ACTIVE_CHARGER_COUNTER] = hass.data[DOMAIN][ACTIVE_CHARGER_COUNTER] + 1 + + if not hass.services.has_service(DOMAIN, SERVICE_SET_PV_DATA): + _LOGGER.info(f"async_setup_entry(): Activate go-eCharger SERVICES") + service = GoeChargerApiV2Service(hass, config_entry, coordinator) + hass.services.async_register(DOMAIN, SERVICE_SET_PV_DATA, service.set_pv_data, + supports_response=SupportsResponse.OPTIONAL) + hass.services.async_register(DOMAIN, SERVICE_STOP_CHARGING, service.stop_charging, + supports_response=SupportsResponse.OPTIONAL) # start the limit to 16a limiiter... if coordinator.limit_to16a: - asyncio.create_task(coordinator.check_for_16a_limit(hass, config_entry.entry_id)) - - # checker that cleaing orphan/outdated device registry entries... - asyncio.create_task(coordinator.cleanup_device_registry(hass)) + config_entry.async_create_background_task( + hass, + coordinator.check_for_16a_limit(hass, config_entry.entry_id), + name="coordinator-check_for_16a_limit-task", + ) # finally, register the websocket-connection watchdog... if use_websocket: # ws watchdog... if hass.state is CoreState.running: - _LOGGER.debug(f"starting watchdog INSTANTLY") + _LOGGER.debug(f"async_setup_entry(): starting watchdog INSTANTLY") await coordinator.start_watchdog() else: - _LOGGER.debug(f"starting watchdog delayed... (when EVENT_HOMEASSISTANT_STARTED is fired)") + _LOGGER.debug(f"async_setup_entry(): starting watchdog delayed... (when EVENT_HOMEASSISTANT_STARTED is fired)") hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STARTED, coordinator.start_watchdog) config_entry.async_on_unload(config_entry.add_update_listener(entry_update_listener)) @@ -158,26 +181,21 @@ async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry): return True -def check_unload_services(hass: HomeAssistant): - active_integration_configs = hass.config_entries.async_entries(domain=DOMAIN, include_disabled=False, include_ignore=False) - if active_integration_configs is not None and len(active_integration_configs) > 0: - return False - else: - return True - - async def async_unload_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> bool: unload_ok = await hass.config_entries.async_unload_platforms(config_entry, PLATFORMS) if unload_ok: if DOMAIN in hass.data and config_entry.entry_id in hass.data[DOMAIN]: coordinator = hass.data[DOMAIN][config_entry.entry_id] + coordinator.cancel_existing_debouncer() coordinator.stop_watchdog() coordinator.clear_data() hass.data[DOMAIN].pop(config_entry.entry_id) + hass.data[DOMAIN][ACTIVE_CHARGER_COUNTER] = hass.data[DOMAIN][ACTIVE_CHARGER_COUNTER] - 1 # ONLY remove the SERVICES if this is the LAST ACTIVE config_entry that will be unloaded! - if check_unload_services(hass): + if hass.data[DOMAIN][ACTIVE_CHARGER_COUNTER] == 0: + _LOGGER.info(f"async_unload_entry(): Unloading last active go-eCharger - will remove SERVICES") hass.services.async_remove(DOMAIN, SERVICE_SET_PV_DATA) hass.services.async_remove(DOMAIN, SERVICE_STOP_CHARGING) @@ -185,7 +203,7 @@ async def async_unload_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> async def entry_update_listener(hass: HomeAssistant, config_entry: ConfigEntry) -> None: - _LOGGER.debug(f"entry_update_listener() called for entry: {config_entry.entry_id}") + _LOGGER.debug(f"entry_update_listener(): called for entry: {config_entry.entry_id}") await hass.config_entries.async_reload(config_entry.entry_id) @@ -268,28 +286,6 @@ async def check_and_write_to_16a(hass: HomeAssistant, config_entry_id: str, brid except Exception as e: _LOGGER.error(f"16A checker: Error while forcing 16A settings: {type(e).__name__} - {e}") -@staticmethod -async def check_device_registry(hass: HomeAssistant): - _LOGGER.info(f"check device registry...") - if hass is not None: - a_device_reg = device_reg.async_get(hass) - if a_device_reg is not None: - key_list = [] - all_domain_devices = [] - for a_config_entry in hass.config_entries.async_entries(DOMAIN): - all_domain_devices.extend(async_entries_for_config_entry(a_device_reg, a_config_entry.entry_id)) - for a_device_entry in all_domain_devices: - if hasattr(a_device_entry, "identifiers"): - ident_value = a_device_entry.identifiers - if len(next(iter(ident_value))) != 4: - _LOGGER.debug(f"found a OLD {DOMAIN} DeviceEntry: {a_device_entry}") - key_list.append(a_device_entry.id) - - if len(key_list) > 0: - _LOGGER.info(f"NEED TO DELETE old {DOMAIN} DeviceEntries: {key_list}") - for a_device_entry_id in key_list: - a_device_reg.async_remove_device(device_id=a_device_entry_id) - class GoeChargerDataUpdateCoordinator(DataUpdateCoordinator): @@ -534,6 +530,7 @@ def clear_data(self): self._CLIENT_COMMUNICATION_ERROR_TS = 0 self._CLIENT_COMMUNICATION_ERROR_COUNT = 0 self._RESTART_TRIGGERED = False + self.cancel_existing_debouncer() self._debounced_update_task = None async def trigger_restart_delayed(self) -> None: @@ -545,9 +542,12 @@ async def trigger_restart_delayed(self) -> None: _LOGGER.info(f"trigger_restart_delayed(): --- RELOAD INTEGRATION NOW ---") await self.hass.config_entries.async_reload(self._config_entry.entry_id) - def request_update_in_sec(self, seconds: float): + def cancel_existing_debouncer(self): if self._debounced_update_task is not None and not self._debounced_update_task.done(): self._debounced_update_task.cancel() + + def request_update_in_sec(self, seconds: float): + self.cancel_existing_debouncer() self._debounced_update_task = asyncio.create_task(self._debounce_coordinator_update(seconds)) async def _debounce_coordinator_update(self, seconds: float): @@ -804,38 +804,20 @@ async def read_versions(self): else: sw_version = "UNKNOWN" - if self.mode == LAN: - self._device_info_dict = { - # be careful when adjusting the 'identifiers' -> since this will create probably new DeviceEntries - # and there exists also code which CLEAN all Devices that does not have 4 (four) identifier values!! - #"identifiers": {(DOMAIN, f"lan@.@{self.intg_type.lower()}@.@{self._serial}")}, - "identifiers": {( - DOMAIN, - self._serial, - self._config_entry.data.get(CONF_HOST), - self._config_entry.title)}, - "manufacturer": MANUFACTURER, - "name": self._config_entry.title, - "model": f"{self._device_info_model_raw}", - "sw_version": f"{sw_version}" - # hw_version - } - else: - self._device_info_dict = { - # be careful when adjusting the 'identifiers' -> since this will create probably new DeviceEntries - # and there exists also code which CLEAN all Devices that does not have 4 (four) identifier values!! - #"identifiers": {(DOMAIN, f"wan@.@{self.intg_type.lower()}@.@{self._serial}")}, - "identifiers": {( - DOMAIN, - self._serial, - self._config_entry.data.get(CONF_TOKEN), - self._config_entry.title)}, - "manufacturer": MANUFACTURER, - "name": self._config_entry.title, - "model": f"{self._device_info_model_raw}", - "sw_version": f"{sw_version}" - # hw_version - } + self._device_info_dict = { + # be careful when adjusting the 'identifiers' -> since this will create probably new DeviceEntries + # and there exists also code which CLEAN all Devices that does not have 4 (four) identifier values!! + "identifiers": {( + DOMAIN, + self.intg_type, + self.mode, + self._serial)}, + "manufacturer": MANUFACTURER, + "name": self._config_entry.title, + "model": f"{self._device_info_model_raw}", + "sw_version": f"{sw_version}" + # hw_version + } return True @@ -845,12 +827,6 @@ async def check_for_16a_limit(self, hass, entry_id): _LOGGER.debug(f"check relevant entities for 16A limit NOW!") await check_and_write_to_16a(hass=hass, config_entry_id=entry_id, bridge=self.bridge, is_lbgroup_id_set=self.is_loadbalancer_group_id_set) - async def cleanup_device_registry(self, hass: HomeAssistant): - _LOGGER.debug(f"check device registry for orphan {DOMAIN} entries... in 20sec") - await asyncio.sleep(20) - _LOGGER.debug(f"check device registry for orphan {DOMAIN} entries NOW!") - await check_device_registry(hass=hass) - def is_valid_charger_entity(self, description: EntityDescription) -> bool: if description.key == Tag.TMA.key and hasattr(description, "idx"): # CORE/ CORE PRO wallbox have temperatures at index 2,3,4,5 diff --git a/custom_components/goecharger_api2/brand/icon.png b/custom_components/goecharger_api2/brand/icon.png index 769c479c30d5f38b6cf43ebe608cc02fb47b2bd4..4acc840626d917b39e52ef38a23b3ebc1e7e56af 100644 GIT binary patch literal 14423 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 z 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 diff --git a/custom_components/goecharger_api2/brand/icon@2x.png b/custom_components/goecharger_api2/brand/icon@2x.png index b39e59f8458719631e3d95070fde2b3e67321513..e85bc865ce0256531224c44ee6fd9b1164911940 100644 GIT binary patch literal 29791 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%0AJG`m>2*xZY!VhC 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 diff --git a/custom_components/goecharger_api2/config_flow.py b/custom_components/goecharger_api2/config_flow.py index 159505a..a76d32a 100644 --- a/custom_components/goecharger_api2/config_flow.py +++ b/custom_components/goecharger_api2/config_flow.py @@ -278,7 +278,7 @@ async def _check_ws_connection_and_flags(self, client:GoeChargerApiV2Bridge): _LOGGER.debug("_check_ws_connection_and_flags(): Waiting for the ws connection to be established (max 30 seconds)") await asyncio.wait_for(ready_event.wait(), timeout=30.0) if ready_event.has_errors(): - _LOGGER.debug(f"_check_ws_connection_and_flags(): ws connection failed! errors: {ready_event.errors}") + _LOGGER.debug(f"_check_ws_connection_and_flags(): ws connection failed! errors: {ready_event._error}") return False if ready_event.is_set(): diff --git a/custom_components/goecharger_api2/const.py b/custom_components/goecharger_api2/const.py index 76df37e..d9caae9 100644 --- a/custom_components/goecharger_api2/const.py +++ b/custom_components/goecharger_api2/const.py @@ -23,7 +23,7 @@ ISSUE_URL: Final = "https://github.com/marq24/ha-goecharger-api2/issues" CONFIG_VERSION: Final = 2 -CONFIG_MINOR_VERSION: Final = 0 +CONFIG_MINOR_VERSION: Final = 1 STARTUP_MESSAGE: Final = f""" ------------------------------------------------------------------- diff --git a/custom_components/goecharger_api2/number.py b/custom_components/goecharger_api2/number.py index 5368fdc..958ebc5 100644 --- a/custom_components/goecharger_api2/number.py +++ b/custom_components/goecharger_api2/number.py @@ -35,12 +35,6 @@ async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry, add_ entity = GoeChargerNumber(coordinator, description) entities.append(entity) - - # currently only the 'Tag.AMP.key' entity needs to be modified - # based on MIN and MAX AMP settings... [have in mind that - # 'data_key' is not unique !!!] - if entity.data_key == Tag.AMP.key: - coordinator.entities[entity.data_key] = entity else: for description in CONTROLLER_NUMBER_SENSORS: entity = GoeChargerNumber(coordinator, description) @@ -53,6 +47,16 @@ class GoeChargerNumber(GoeChargerBaseEntity, NumberEntity): def __init__(self, coordinator: GoeChargerDataUpdateCoordinator, description: ExtNumberEntityDescription): super().__init__(entity_type=Platform.NUMBER, coordinator=coordinator, description=description) + async def async_added_to_hass(self) -> None: + """Run when entity about to be added to hass.""" + await super().async_added_to_hass() + + # currently only the 'Tag.AMP.key' entity needs to be modified + # based on MIN and MAX AMP settings... [have in mind that + # 'data_key' is not unique !!!] + if self.data_key == Tag.AMP.key: + self.coordinator.entities[self.data_key] = self + @property def native_value(self): try: @@ -151,4 +155,4 @@ def check_amp_min_max(self, value): }, blocking=False, ) - ) + ) \ No newline at end of file diff --git a/custom_components/goecharger_api2/pygoecharger_ha/__init__.py b/custom_components/goecharger_api2/pygoecharger_ha/__init__.py index a5a1967..fc4703d 100644 --- a/custom_components/goecharger_api2/pygoecharger_ha/__init__.py +++ b/custom_components/goecharger_api2/pygoecharger_ha/__init__.py @@ -235,6 +235,8 @@ def available_fields(self) -> int: return len(self._versions) + len(self._states) + len(self._config) + len(self._ws_states) def clear_data(self): + if self._ws_debounced_update_task is not None and not self._ws_debounced_update_task.done(): + self._ws_debounced_update_task.cancel() self._LAST_CONFIG_UPDATE_TS = 0 self._LAST_FULL_STATE_UPDATE_TS = 0 self._REQUEST_IDS_DATA = False @@ -277,7 +279,9 @@ async def read_versions(self): self._versions = self._read_filtered_data_from_ws_source(filters=self._FILTER_VERSIONS, log_info="read_versions (via websocket)") else: for attempt in range(5): - self._versions = await self._read_filtered_data(filters=self._FILTER_VERSIONS, log_info=f"read_versions (attempt {attempt+1})") + ret = await self._read_filtered_data(filters=self._FILTER_VERSIONS, log_info=f"read_versions (attempt {attempt+1})") + if ret is not None and len(ret) > 0 and isinstance(ret, NonNullDict): + self._versions = ret if self._versions is not None and len(self._versions) > 0: break if attempt < 4: @@ -367,7 +371,9 @@ async def read_all_states(self): if self.ws_connected: self._states = self._read_filtered_data_from_ws_source(filters=self._FILTER_ALL_STATES, log_info="read_all_states") else: - self._states = await self._read_filtered_data(filters=self._FILTER_ALL_STATES, log_info="read_all_states") + ret = await self._read_filtered_data(filters=self._FILTER_ALL_STATES, log_info="read_all_states") + if ret is not None and len(ret) > 0 and isinstance(ret, NonNullDict): + self._states = ret if len(self._states) > 0: self._LAST_FULL_STATE_UPDATE_TS = time.time() @@ -383,7 +389,9 @@ async def read_all_config(self): if self.ws_connected: self._config = self._read_filtered_data_from_ws_source(filters=self._FILTER_ALL_CONFIG, log_info="read_all_config") else: - self._config = await self._read_filtered_data(filters=self._FILTER_ALL_CONFIG, log_info="read_all_config") + ret = await self._read_filtered_data(filters=self._FILTER_ALL_CONFIG, log_info="read_all_config") + if ret is not None and len(ret) > 0 and isinstance(ret, NonNullDict): + self._config = ret if len(self._config) > 0: self._LAST_CONFIG_UPDATE_TS = time.time() @@ -856,7 +864,7 @@ async def ws_connect(self, a_event: TargetedEvent=None, keep_ws_states_after_clo self._ws_hashed_password = self._ws_compute_hashed_password(hash_type, self.access_password, serial) _logging_pwd = self._ws_hashed_password.decode('utf-8') - _LOGGER.debug(f"ws_connect(): Computed hashed password {hash_type}: {_logging_pwd[:6]}...{_logging_pwd[-6:]}") + _LOGGER.debug(f"ws_connect(): Computed hashed password {hash_type}: {_logging_pwd[:3]}...{_logging_pwd[-3:]}") token1 = normalized_auth.get('token1') token2 = normalized_auth.get('token2') From 30adcbd705ab3f8bb617d3bcd85c6cacb18954d8 Mon Sep 17 00:00:00 2001 From: marq24 Date: Fri, 23 Oct 2026 18:58:13 +0200 Subject: [PATCH 09/24] manually merged PR #174 --- .../go-echarger-pv-surplus-data.yaml | 14 +++++++++---- example/blueprint/automation/readme.md | 20 ++++++++++++++----- 2 files changed, 25 insertions(+), 9 deletions(-) diff --git a/example/blueprint/automation/go-echarger-pv-surplus-data.yaml b/example/blueprint/automation/go-echarger-pv-surplus-data.yaml index 37914b8..676d4df 100644 --- a/example/blueprint/automation/go-echarger-pv-surplus-data.yaml +++ b/example/blueprint/automation/go-echarger-pv-surplus-data.yaml @@ -128,7 +128,9 @@ blueprint: input: grid_phase_entities: name: Grid Phase Entities (L1, L2, L3) - description: Select exactly 3 entities for phases L1, L2, L3 + description: > + Select 3 entities for phases L1, L2, L3. If more are selected, + only the first 3 are used, preserving existing configurations. default: [] selector: entity: @@ -219,13 +221,17 @@ variables: battery_invert_flag: !input battery_invert # Preserve method precedence; an incomplete selected method must not fall back. + # No grid method sends pgrid 0; phase lists use the first three, as before. + # An empty entity ID marks an invalid selected phase method, not PV-only mode. grid_sources: > {% if grid_single_entity %} {{ [grid_single_entity] }} {% elif grid_consumption or grid_feedin %} {{ [grid_consumption, grid_feedin] }} - {% elif grid_phases | length == 3 and grid_phases | unique | list | length == 3 %} - {{ grid_phases }} + {% elif grid_phases | length >= 3 and grid_phases[:3] | unique | list | length == 3 %} + {{ grid_phases[:3] }} + {% elif grid_phases | length > 0 %} + {{ [''] }} {% else %} {{ [] }} {% endif %} @@ -237,7 +243,7 @@ variables: {% endfor %} {{ ns.samples }} measurements_valid: > - {% set ns = namespace(valid=grid_sources | length > 0 and pv_list | length > 0 + {% set ns = namespace(valid=pv_list | length > 0 and is_number(multiplier) and multiplier | float(0) > 0) %} {% for entity in grid_sources + pv_list + (battery_list if battery_enable else []) %} {% if not entity or not is_number(power_samples.get(entity)) %} diff --git a/example/blueprint/automation/readme.md b/example/blueprint/automation/readme.md index e29d3fe..f9c2074 100644 --- a/example/blueprint/automation/readme.md +++ b/example/blueprint/automation/readme.md @@ -60,7 +60,10 @@ The go-eCharger expects **watts**. This factor is applied to grid, PV and batter The default of `1` keeps the behavior of earlier versions, so existing automations keep working unchanged. ### Grid Power Setup -Choose **ONE** method: +Choose **ONE** method when grid measurements are available. Leaving all grid +methods empty preserves PV-only configurations: `pgrid=0` is sent together +with the measured PV and optional battery values. This is an intentional +absence of grid measurement, not a fallback for an unavailable selected sensor. **Single Entity**: Use if you have one entity with positive (import) and negative (export) values - Example: `sensor.meter_power_now` @@ -70,6 +73,10 @@ Choose **ONE** method: **3-Phase Entities**: Use individual phase entities (L1, L2, L3) - Example: `sensor.grid_l1_power`, `sensor.grid_l2_power`, `sensor.grid_l3_power` +- Select three distinct entities. Existing selections with more than three + entries continue to use only the first three; additional entries are ignored. +- For installations with fewer measured phases, use **Single Entity** with a + sensor reporting their total. Selecting only one or two phase entities is invalid. ### PV Power - Select all PV entities (multiple supported) @@ -99,9 +106,12 @@ valid. Each used state is captured once for validation and calculation. Non-finite totals or unit conversion results also skip the update. The existing grid method precedence remains single entity, then consumption / -feed-in, then phases. Both consumption and feed-in must be selected; phases -require exactly three distinct entities. An incomplete selected method does -not fall back to another method. Unused grid inputs are ignored. +feed-in, then phases. Both consumption and feed-in must be selected. A selected +phase method requires at least three entries, with three distinct, usable +entities in the first three positions; only those first three are read and +summed. An incomplete selected method does not fall back to another method or +PV-only mode. Unused grid inputs are ignored. If no grid method is selected, +`pgrid=0` is valid and PV/battery validation still applies. An empty battery selection means no battery measurement and still sends zero. Disabling battery transmission ignores any selected battery entities and also @@ -148,4 +158,4 @@ The go-eCharger calculates its `pvopt_*` averages from the last values it has re ## Support For issues with the blueprint, please create an issue in this repository. -For go-eCharger integration issues, see the [main integration documentation](../../../README.md). +For go-eCharger integration issues, see the [main integration documentation](../../../README.md). \ No newline at end of file From a9f93505d0705ab134f247c61d859f062bf85695 Mon Sep 17 00:00:00 2001 From: marq24 Date: Sat, 24 Oct 2026 13:29:59 +0200 Subject: [PATCH 10/24] PR https://github.com/home-assistant/core/pull/188329 is fixing the issue caused by the default aiohttp client header limit and set it globally to 64 KiB so all systems that have updated to HA 2026.10.4 will be able to fetch updates via HACS - so we can roll back the patch from yesterday. 48px icons for readme --- README.md | 2 +- .../goecharger_api2/brand/icon48.png | Bin 0 -> 1958 bytes custom_components/goecharger_api2/manifest.json | 7 ++----- 3 files changed, 3 insertions(+), 6 deletions(-) create mode 100644 custom_components/goecharger_api2/brand/icon48.png diff --git a/README.md b/README.md index c921f68..3390848 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# Home Assistant Integration 'go-eCharger & go-eController [via WebSocket & APIv2]' +# Home Assistant Integration 'go-eCharger & go-eController [via WebSocket & APIv2]' ![logo](https://github.com/marq24/ha-goecharger-api2/raw/main/logo.png) diff --git a/custom_components/goecharger_api2/brand/icon48.png b/custom_components/goecharger_api2/brand/icon48.png new file mode 100644 index 0000000000000000000000000000000000000000..a752703c239c8291993ab8bb640107c8e9315cd4 GIT binary patch literal 1958 zcmV;X2U+-uP)z@;j|==^1poj5AY({UO#lFTCIA3{ga82g0001h=l}q9FaQARU;qF* zm;eA5aGbhPJOBUy32;bRa{vG?BLDy{BLR4&KXw2B2MDxS2@+f>8e*md)(AO>$+IpTCUbgP!?FXq)00@QsKDlsW{oPB6M9r zZ*t2F0q;PHDN7-_5bxm)&M;@*GrK=HL(@19Tyz)1U4CnwHM95r&N;u|{=RSTZ|{8w z+=yH*mjRGre5EucC8g9=Bk=&-2EKHhz}vsyDG@RX;BI^1e<`EELmZT|y#e?rh-BNa ziJh1$+gpI!M8H#O*U2)QTQd#}OT7)eGa>+F0_I@?UJ$G|H7eO-CU#~iO-V_~o&W?O zm&L%xESHFOG$g7?`kfi@+@cw+P%KFuo#o*Jw0b1HL@d_MjiE5kE9* z+mmIH7yoo`h{>5QrIaZ--#zd8fG(!KQ#TMZO?r9V!l=QU1*F!R!j62~I#=q)gk0>< zH6|~fQuEN=LDc1fMwpgipGNdt*N1*q((+DugJ#{gJ;vK?^^qYcYjsE`ZnVHp&8lhJ57(c`Z{Y6+&<)@S0Huf^Ms1-hOlG=;AxH>L zQ!G*E#trQ*s6X-ge+Cv2K+6Dy0IrD$LiAResJ74~0xXrJDVC}8*h*L6zpw6!-+8p@ zWRcPk44?tPDF7-l!TkefnrNmpx2wDe`w5V#;aHum z{cn3~BrOAmkE>PYBme~;9&c=ZV;FoHfHgeFDs8$4Pq#7hvhK{LA@hzw3oQ;xRm{y-O)?RJYn}_R*ij;;x0O_|8lG~RTH{vo$IhUe;9xxWv@|Hv^kOLsqWe^vaw{O^6 zqrMNo7d*$lWDgzyKHEH3U9vTH?aFBXvjAk7-}j~fs3$<8J$L~4`{HP8`L>64t(+0~ z7Jx|`dR5nn5y{emGH*60p6{`SbNz)Xoo!#)yvuzvYmHjUOXk9YNw+g_b z;8exl<+)dw8^L!!`pGxS57bs_0@-m|fYpDVdwW-#qQBxNtz}bn zi)LZM_Uc1%QT|sWLj5Ln4j2=+9cz77p)xZ7Ht-x<;|##_i81Hb=U(2VY|?T@D?K-7 zOV#1WrUaBQ)cQ@{#(64_@#rj-So!XyxC>qS1AS&X+gXa_%aDf$bK|N3AU(e>>hv}B z2aP)G<4$y20c_+sw%8Mp74KYJ^<%Fw@4AIX3RMevjxF=BGvPcNJ+|e6$|wF9_J?p^ zW)VZM6~KN5I3_000Pr7n2>MK}QLS$|(g&8`Jc+!H98Eh0z*zzN$lD;Qw0N^oO+vOee zYXQ`bB{e?0;eI24V&VGz*8_za^ZLKaoVV)fyZp-m$O2OpPffT5O}NC@ye59~D*?lI zJ~ts(WXl%ZsYr?mFgQ-@6NUgGgo;JP;AutYwFESJb5zryNg@oB6OTaQcdeUG_Zo3C zqlMQH_*&7i?(?et%>bGSknb(K5;D15E_1a*oqwu%%8n@ zI959zpzTkT#1M{bV0Z51C_0TX{nhgRG@)1pU_H;Vwd2_FGU2zBadz%>gP#$c1Mo6{ zIG$tU#uL2bd7jOM+*;wqRreNp(Po^?4)bsjkH!k#AkI!x?I$9e6|&`Wc`3a6pnC+@ s|Ieoeqh&mf{_go>4>v@{Q`~yMFPl&|e%UprL;wH)07*qoM6N<$f=1.1.2"], - "version": "2026.10.3" + "version": "2026.10.7" } From 03b0fc35bc8f6f17a63be34cac6688b4616dd940 Mon Sep 17 00:00:00 2001 From: marq24 Date: Tue, 27 Oct 2026 11:31:59 +0100 Subject: [PATCH 11/24] bugfixes for 3 issues mentioned in #173 --- custom_components/goecharger_api2/__init__.py | 12 ++-- custom_components/goecharger_api2/const.py | 4 +- .../goecharger_api2/manifest.json | 2 +- .../pygoecharger_ha/__init__.py | 36 ++++++----- custom_components/goecharger_api2/service.py | 64 ++++++++++++++----- 5 files changed, 79 insertions(+), 39 deletions(-) diff --git a/custom_components/goecharger_api2/__init__.py b/custom_components/goecharger_api2/__init__.py index f56831e..11acf38 100644 --- a/custom_components/goecharger_api2/__init__.py +++ b/custom_components/goecharger_api2/__init__.py @@ -58,7 +58,7 @@ CONFIG_VERSION, CONFIG_MINOR_VERSION ) from .entity import CustomFriendlyNameEntity -from .service import GoeChargerApiV2Service +from .service import GoeChargerApiV2Service, add_config_entry_to_service, remove_config_entry_from_service _LOGGER: logging.Logger = logging.getLogger(__package__) @@ -157,6 +157,9 @@ async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry): supports_response=SupportsResponse.OPTIONAL) hass.services.async_register(DOMAIN, SERVICE_STOP_CHARGING, service.stop_charging, supports_response=SupportsResponse.OPTIONAL) + else: + # we need to let the service know the config-entry... + add_config_entry_to_service(config_entry, coordinator) # start the limit to 16a limiiter... if coordinator.limit_to16a: @@ -192,6 +195,7 @@ async def async_unload_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> coordinator.clear_data() hass.data[DOMAIN].pop(config_entry.entry_id) hass.data[DOMAIN][ACTIVE_CHARGER_COUNTER] = hass.data[DOMAIN][ACTIVE_CHARGER_COUNTER] - 1 + remove_config_entry_from_service(config_entry) # ONLY remove the SERVICES if this is the LAST ACTIVE config_entry that will be unloaded! if hass.data[DOMAIN][ACTIVE_CHARGER_COUNTER] == 0: @@ -670,9 +674,9 @@ async def async_write_multiple_keys(self, attr:dict, key: str, value, entity: En self.handle_write_result("multiple", value, key, result, entity) return result - except ClientConnectionError as exception: - self._handle_client_connection_error(f"async_write_multiple_keys(): {type(exception).__name__} - {exception}") - raise ValueError(f"ClientConnectionError while writing multiple {key} to wallbox: {type(exception).__name__} - {exception}") from exception + except ClientConnectionError as cce: + self._handle_client_connection_error(f"async_write_multiple_keys(): {type(cce).__name__} - {cce}", cce) + raise ValueError(f"ClientConnectionError while writing multiple {key} to wallbox: {type(cce).__name__} - {cce}") from cce except Exception as e: _LOGGER.error(f"Error while writing multiple {key} to wallbox: {type(e).__name__} - {e}") raise ValueError(f"Exception while writing multiple {key} to wallbox: {type(e).__name__} - {e}") from e diff --git a/custom_components/goecharger_api2/const.py b/custom_components/goecharger_api2/const.py index d9caae9..bbaddb5 100644 --- a/custom_components/goecharger_api2/const.py +++ b/custom_components/goecharger_api2/const.py @@ -542,8 +542,8 @@ class ExtSwitchEntityDescription(SwitchEntityDescription): # pgt ExtNumberEntityDescription( key=Tag.PGT.key, - native_max_value=5000, - native_min_value=-5000, + native_max_value=35000, + native_min_value=-35000, native_step=1, #entity_category=EntityCategory.CONFIG, native_unit_of_measurement=UnitOfPower.WATT, diff --git a/custom_components/goecharger_api2/manifest.json b/custom_components/goecharger_api2/manifest.json index 45b9b4b..8ca2f3f 100644 --- a/custom_components/goecharger_api2/manifest.json +++ b/custom_components/goecharger_api2/manifest.json @@ -9,5 +9,5 @@ "iot_class": "local_push", "issue_tracker": "https://github.com/marq24/ha-goecharger-api2/issues", "requirements": ["msgpack>=1.1.2"], - "version": "2026.10.7" + "version": "2026.10.8" } diff --git a/custom_components/goecharger_api2/pygoecharger_ha/__init__.py b/custom_components/goecharger_api2/pygoecharger_ha/__init__.py index fc4703d..5008b11 100644 --- a/custom_components/goecharger_api2/pygoecharger_ha/__init__.py +++ b/custom_components/goecharger_api2/pygoecharger_ha/__init__.py @@ -504,20 +504,19 @@ async def _read_all_data(self) -> NonNullDict: async def write_value_to_key(self, key, value) -> dict: is_button_press = value is not None and str(value) == IS_TRIGGER - if not is_button_press and self.ws_connected and self._ws_connection is not None: - await self._ws_send_command(key, value) - self._ws_LAST_NEW_DATA_NOTIFY = 0 - return None + if self.ws_connected and self._ws_connection is not None: + if is_button_press: + # ignoring button presses when connected via WebSockets + return {key: value} + else: + if await self._ws_send_command(key, value): + self._ws_LAST_NEW_DATA_NOTIFY = 0 + return {key: value} + else: + return {} else: - if value is None: - args = f"{key}=null" - elif isinstance(value, (bool, int, float)): - args = {key: str(value).lower()} - elif isinstance(value, dict): - args = {key: json.dumps(value).replace(' ','')} - elif isinstance(value, str) and value == IS_TRIGGER: + if is_button_press: # BUTTON's have only effect for POLLING (will not do anything when connected via WebSockets) - # these are special trigger actions that we want to call from the FE... match key: case Tag.INTERNAL_FORCE_CONFIG_READ.key: @@ -530,9 +529,16 @@ async def write_value_to_key(self, key, value) -> dict: return {key: value} else: - args = {key: '"'+str(value)+'"'} + if value is None: + args = f"{key}=null" + elif isinstance(value, (bool, int, float)): + args = {key: str(value).lower()} + elif isinstance(value, dict): + args = {key: json.dumps(value).replace(' ','')} + else: + args = {key: '"'+str(value)+'"'} - return await self._write_values_int(args, a_result_key = key, a_result_value = value) + return await self._write_values_int(args, a_result_key = key, a_result_value = value) async def write_multiple_values_to_keys(self, args, key, value) -> dict: if self.ws_connected and self._ws_connection is not None: @@ -541,7 +547,7 @@ async def write_multiple_values_to_keys(self, args, key, value) -> dict: await asyncio.sleep(0.75) self._ws_LAST_NEW_DATA_NOTIFY = 0 - return None + return {} else: params = {} for a_key, a_value in args.items(): diff --git a/custom_components/goecharger_api2/service.py b/custom_components/goecharger_api2/service.py index f087bea..407e3ea 100644 --- a/custom_components/goecharger_api2/service.py +++ b/custom_components/goecharger_api2/service.py @@ -7,6 +7,8 @@ from homeassistant.config_entries import ConfigEntry from homeassistant.core import ServiceCall, HomeAssistant +from homeassistant.exceptions import ServiceValidationError +from homeassistant.helpers.update_coordinator import DataUpdateCoordinator from custom_components.goecharger_api2.const import WAN from custom_components.goecharger_api2.pygoecharger_ha.const import INTG_TYPE @@ -14,7 +16,7 @@ _LOGGER: logging.Logger = logging.getLogger(__package__) -_COORDINATOR_PER_CONFIGENTRYID_MAP = {} +_COORDINATOR_PER_CONFIG_ENTRY_ID_MAP: dict[str, DataUpdateCoordinator] = {} def _is_finite_number(value) -> bool: """NaN and infinity are Numbers, but would be sent to the charger as invalid JSON.""" @@ -25,20 +27,33 @@ def _is_finite_number(value) -> bool: except (TypeError, ValueError, OverflowError): return False +def add_config_entry_to_service(config: ConfigEntry, coordinator: DataUpdateCoordinator): + global _COORDINATOR_PER_CONFIG_ENTRY_ID_MAP + _COORDINATOR_PER_CONFIG_ENTRY_ID_MAP[config.entry_id] = coordinator + _LOGGER.debug(f"add_config_entry_to_service(): Added config entry {config.entry_id} to _COORDINATOR_PER_CONFIG_ENTRY_ID_MAP now: {len(_COORDINATOR_PER_CONFIG_ENTRY_ID_MAP)} entries") + +def remove_config_entry_from_service(config: ConfigEntry): + global _COORDINATOR_PER_CONFIG_ENTRY_ID_MAP + if config.entry_id in _COORDINATOR_PER_CONFIG_ENTRY_ID_MAP: + _COORDINATOR_PER_CONFIG_ENTRY_ID_MAP.pop(config.entry_id) + _LOGGER.debug(f"remove_config_entry_from_service(): Removed config entry {config.entry_id} from _COORDINATOR_PER_CONFIG_ENTRY_ID_MAP now: {len(_COORDINATOR_PER_CONFIG_ENTRY_ID_MAP)} entries") + class GoeChargerApiV2Service(): - def __init__(self, hass: HomeAssistant, config: ConfigEntry, coordinator): # pylint: disable=unused-argument + def __init__(self, hass: HomeAssistant, config: ConfigEntry, coordinator: DataUpdateCoordinator): # pylint: disable=unused-argument """Initialize the sensor.""" self._hass = hass self._config = config - _COORDINATOR_PER_CONFIGENTRYID_MAP[config.entry_id] = coordinator + # when we create a new instance of the service, we will reset the map, so that only + # the last config entry is used for the service calls + global _COORDINATOR_PER_CONFIG_ENTRY_ID_MAP + _COORDINATOR_PER_CONFIG_ENTRY_ID_MAP = {} + _COORDINATOR_PER_CONFIG_ENTRY_ID_MAP[config.entry_id] = coordinator + _LOGGER.debug(f"__init__(): Added config entry {config.entry_id} to _COORDINATOR_PER_CONFIG_ENTRY_ID_MAP now: {len(_COORDINATOR_PER_CONFIG_ENTRY_ID_MAP)} entries") + self._stop_in_progress = False async def set_pv_data(self, call: ServiceCall): - config_id = call.data.get('configid', None) - if config_id is None: - coordinator = next(iter(_COORDINATOR_PER_CONFIGENTRYID_MAP.values())) - else: - coordinator = _COORDINATOR_PER_CONFIGENTRYID_MAP[config_id] + config_id, coordinator = self.get_coordinator(call) _LOGGER.debug(f"Service-set_pv_data(): Provided config_id: {config_id} -> using {coordinator.name} serial: {coordinator._serial}") # before we're going to 'post' any data to the cloud, we will check if there is ANY vehicle connected @@ -77,7 +92,7 @@ async def set_pv_data(self, call: ServiceCall): "response": resp } - except ValueError as exc: + except (ValueError, TypeError) as exc: if call.return_response: return {"error": str(exc), "date": str(datetime.datetime.now().time())} @@ -86,16 +101,10 @@ async def set_pv_data(self, call: ServiceCall): async def stop_charging(self, call: ServiceCall): if not self._stop_in_progress: + config_id, coordinator = self.get_coordinator(call) self._stop_in_progress = True _LOGGER.debug(f"Force STOP_CHARGING") - - config_id = call.data.get('configid', None) - if config_id is None: - coordinator = next(iter(_COORDINATOR_PER_CONFIGENTRYID_MAP.values())) - else: - coordinator = _COORDINATOR_PER_CONFIGENTRYID_MAP[config_id] _LOGGER.debug(f"Provided config_id: {config_id} -> using {coordinator.name} serial: {coordinator._serial}") - try: resp = await coordinator.async_write_key(Tag.FRC.key, 1) if Tag.FRC.key in resp: @@ -120,10 +129,31 @@ async def stop_charging(self, call: ServiceCall): if call.return_response: return {"error": "A STOP_CHARGING request could not be send", "date": str(datetime.datetime.now().time())} - except ValueError as exc: + except (ValueError, TypeError) as exc: if call.return_response: return {"error": str(exc), "date": str(datetime.datetime.now().time())} else: if call.return_response: return {"error": "A STOP_CHARGING request is already in progress", "date": str(datetime.datetime.now().time())} + + def get_coordinator(self, call): + global _COORDINATOR_PER_CONFIG_ENTRY_ID_MAP + config_id = call.data.get('configid', None) + if config_id is None: + coordinator = next(iter(_COORDINATOR_PER_CONFIG_ENTRY_ID_MAP.values())) + else: + # Fetch the config entry + entry = self._hass.config_entries.async_get_entry(config_id) + # Check if it exists and is not disabled + if entry is not None and not entry.disabled_by: + if config_id in _COORDINATOR_PER_CONFIG_ENTRY_ID_MAP: + coordinator = _COORDINATOR_PER_CONFIG_ENTRY_ID_MAP[config_id] + else: + _LOGGER.warning(f"get_coordinator(): Provided config_id: {config_id} does not exist in _COORDINATOR_PER_CONFIG_ENTRY_ID_MAP") + raise ServiceValidationError(f"The selected goecharger_api2 config entry ({config_id}) is not in the internal _COORDINATOR_PER_CONFIG_ENTRY_ID_MAP dictionary.") + else: + _LOGGER.warning(f"get_coordinator(): Provided config_id: {config_id} does not exist or is disabled in config entries") + raise ServiceValidationError(f"The selected goecharger_api2 config entry ({config_id}) is disabled or invalid.") + + return config_id, coordinator \ No newline at end of file From 98af3e0afead3ccc645971b6a8f7eda9e81c6da7 Mon Sep 17 00:00:00 2001 From: marq24 Date: Wed, 28 Oct 2026 13:29:34 +0100 Subject: [PATCH 12/24] alternate handling of 'cableCurrentLimit' and MORE logging - looks like that this attribute is not present for "none" FLEX Chargers (at least that's what I assume right now) --- custom_components/goecharger_api2/__init__.py | 32 ++++++++++++------- 1 file changed, 20 insertions(+), 12 deletions(-) diff --git a/custom_components/goecharger_api2/__init__.py b/custom_components/goecharger_api2/__init__.py index 11acf38..ac7273a 100644 --- a/custom_components/goecharger_api2/__init__.py +++ b/custom_components/goecharger_api2/__init__.py @@ -231,13 +231,13 @@ async def check_and_write_to_16a(hass: HomeAssistant, config_entry_id: str, brid if hasattr(entity, "orphaned_timestamp") is False or entity.orphaned_timestamp is None: if entity.translation_key not in key_list: key_list.append(entity.translation_key) - _LOGGER.info(f"16A checker: adding entity {entity.translation_key} to list") + _LOGGER.info(f"16A checker: adding entity '{entity.translation_key}' to list") else: - _LOGGER.debug(f"16A checker: entity {entity.translation_key} already in list") + _LOGGER.debug(f"16A checker: entity '{entity.translation_key}' already in list") else: - _LOGGER.debug(f"16A checker: ignoring entity {entity.translation_key} because it is orphaned") + _LOGGER.debug(f"16A checker: ignoring entity '{entity.translation_key}' because it is orphaned") else: - _LOGGER.debug(f"16A checker: ignoring entity {entity.translation_key} because it is disabled") + _LOGGER.debug(f"16A checker: ignoring entity '{entity.translation_key}' because it is disabled") if len(key_list) > 0: _LOGGER.info(f"16A checker: found {len(key_list)} entities with max current of {MAX_A}A - {key_list}") @@ -753,37 +753,45 @@ async def read_versions(self): ccl_obj = self.bridge._versions.none_null_get(Tag.CLL.key, {}) if len(ccl_obj) > 0: ccl_cable_limit_val = ccl_obj.get("cableCurrentLimit", "-1") - _LOGGER.debug(f"read_versions(): read CLL:cableCurrentLimit: '{ccl_cable_limit_val}'") try: ccl_cable_limit_int = int(ccl_cable_limit_val) if ccl_cable_limit_int > 0: + _LOGGER.debug(f"read_versions(): read CLL:cableCurrentLimit: '{ccl_cable_limit_val}'") wb_has_16a_cable_limit = ccl_cable_limit_int <= 16 use_adi_as_fallback = False else: - _LOGGER.debug(f"read_versions(): no 'cableCurrentLimit' found in {ccl_obj}") + _LOGGER.debug(f"read_versions(): no 'cableCurrentLimit' found in CLL object, but there IS content: {ccl_obj} - will not enforce the 16A CABLE limit") + # 20266/10/28 this is astounding for me - as reported in #176 a 'go-eCharger_V5' running + # FW '59.4' does NOT contain the key 'cableCurrentLimit' - I even installed the '59.4' FW + # on my Flex and there the key is still present also in that 'old' FW... so I must assume, + # that ONLY a FLEX Wallbox will contain this 'cableCurrentLimit' key :-/ + wb_has_16a_cable_limit = False + use_adi_as_fallback = False + except BaseException as exc: _LOGGER.debug(f"read_versions(): try to handle CLL:cableCurrentLimit caused: {type(exc).__name__} - {exc}") if self._is_core_wallbox: wb_has_16a_cable_limit = True else: - _LOGGER.debug(f"read_versions(): CLL object is empty! {ccl_obj}") + _LOGGER.debug(f"read_versions(): CLL object is empty! {ccl_obj} (or was not present) - we probably check ADI key...") if wb_has_16a_cable_limit is None and use_adi_as_fallback: wb_has_16a_cable_limit = self.bridge._versions.none_null_get(Tag.ADI.key, False) + _LOGGER.debug(f"read_versions(): USE Fallback to API-ADI key in order to set a possible 16A limit - with the result ADI is: {wb_has_16a_cable_limit} (or not present)") # if we haven't seen any 'wb_has_16a_cable_limit' yet, then we enable the limit just to be sure... if wb_has_16a_cable_limit is None: _LOGGER.debug(f"read_versions(): enforcing 'wb_has_16a_cable_limit=True' cause previously the code did not set any value") wb_has_16a_cable_limit = True - self.limit_to16a = (self._config_entry.data.get(CONF_11KWLIMIT, False) - or self.bridge._versions.none_null_get(Tag.VAR.key, -1) == 11 - or wb_has_16a_cable_limit) + a = self._config_entry.data.get(CONF_11KWLIMIT, False) + b = self.bridge._versions.none_null_get(Tag.VAR.key, -1) == 11 + self.limit_to16a = a or b or wb_has_16a_cable_limit if (self.limit_to16a): - _LOGGER.info(f"read_versions(): Charger LIMIT to 16A is active") + _LOGGER.info(f"read_versions(): Charger LIMIT to 16A is active [CONF_11KWLIMIT: {a}, VAR==11: {b}, DETECTED CABLE-LIMIT: {wb_has_16a_cable_limit}]") else: - _LOGGER.info(f"read_versions(): Charger NO LIMIT detected") + _LOGGER.info(f"read_versions(): Charger NO LIMIT detected [CONF_11KWLIMIT: {a}, VAR==11: {b}, DETECTED CABLE-LIMIT: {wb_has_16a_cable_limit}]") else: # no additional controller stuff... but we need to init some variables self.limit_to16a = False From eb5da464c3c0dbd0111fa63f7b36d847a6d6fbeb Mon Sep 17 00:00:00 2001 From: marq24 Date: Thu, 29 Oct 2026 17:04:57 +0100 Subject: [PATCH 13/24] fixing stuff based on test-suite --- custom_components/goecharger_api2/__init__.py | 21 +++++++++++-------- .../goecharger_api2/pygoecharger_ha/const.py | 5 ++++- custom_components/goecharger_api2/service.py | 2 +- 3 files changed, 17 insertions(+), 11 deletions(-) diff --git a/custom_components/goecharger_api2/__init__.py b/custom_components/goecharger_api2/__init__.py index ac7273a..5d36ca1 100644 --- a/custom_components/goecharger_api2/__init__.py +++ b/custom_components/goecharger_api2/__init__.py @@ -129,6 +129,8 @@ async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry): raise ConfigEntryNotReady(lang_map["coord_no_device_data"]) except BaseException as exc: + if isinstance(exc, ConfigEntryNotReady): + raise exc _LOGGER.error(f"async_setup_entry(): Error starting websocket connection: {type(exc).__name__} - {exc}", stack_info=True) raise ConfigEntryNotReady(lang_map["websocket_start_failed"]) else: @@ -194,14 +196,15 @@ async def async_unload_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> coordinator.stop_watchdog() coordinator.clear_data() hass.data[DOMAIN].pop(config_entry.entry_id) - hass.data[DOMAIN][ACTIVE_CHARGER_COUNTER] = hass.data[DOMAIN][ACTIVE_CHARGER_COUNTER] - 1 - remove_config_entry_from_service(config_entry) + if coordinator.intg_type == INTG_TYPE.CHARGER.value: + hass.data[DOMAIN][ACTIVE_CHARGER_COUNTER] = hass.data[DOMAIN][ACTIVE_CHARGER_COUNTER] - 1 + remove_config_entry_from_service(config_entry) - # ONLY remove the SERVICES if this is the LAST ACTIVE config_entry that will be unloaded! - if hass.data[DOMAIN][ACTIVE_CHARGER_COUNTER] == 0: - _LOGGER.info(f"async_unload_entry(): Unloading last active go-eCharger - will remove SERVICES") - hass.services.async_remove(DOMAIN, SERVICE_SET_PV_DATA) - hass.services.async_remove(DOMAIN, SERVICE_STOP_CHARGING) + # ONLY remove the SERVICES if this is the LAST ACTIVE config_entry that will be unloaded! + if hass.data[DOMAIN][ACTIVE_CHARGER_COUNTER] == 0: + _LOGGER.info(f"async_unload_entry(): Unloading last active go-eCharger - will remove SERVICES") + hass.services.async_remove(DOMAIN, SERVICE_SET_PV_DATA) + hass.services.async_remove(DOMAIN, SERVICE_STOP_CHARGING) return unload_ok @@ -446,7 +449,7 @@ async def start_websocket_and_wait_for_first_data(self, is_integration_init:bool self._ws_start_task = self._config_entry.async_create_background_task(self.hass, target, "ws_connection") if self._ws_start_task is None: - raise ConfigEntryNotReady("start_websocket_and_wait_for_first_data(): Could not create websocket connect task!") + raise ConfigEntryNotReady(self.lang_map["ws_start_task_is_none"]) # 3. Wait ONLY until the first message is processed (or time out) try: @@ -457,7 +460,7 @@ async def start_websocket_and_wait_for_first_data(self, is_integration_init:bool _LOGGER.warning("start_websocket_and_wait_for_first_data(): Connection to websocket timed out after one minute!") self._ws_start_task.cancel() # NOT SURE WHAT TO DO NOW ???! - raise ConfigEntryNotReady("start_websocket_and_wait_for_first_data(): Could not fetch essential data") + raise ConfigEntryNotReady(self.lang_map["ws_fetch_essential_data_timed_out"]) # we must check, if connected_event has errors... if connected_event.has_errors(): diff --git a/custom_components/goecharger_api2/pygoecharger_ha/const.py b/custom_components/goecharger_api2/pygoecharger_ha/const.py index 498fa96..11ac4dd 100644 --- a/custom_components/goecharger_api2/pygoecharger_ha/const.py +++ b/custom_components/goecharger_api2/pygoecharger_ha/const.py @@ -113,7 +113,8 @@ class CT_VALUES(Enum): "coord_null_data": "Es konnten keine Daten abgerufen werden. Bitte prüfe Dein Home Assistant System Protokoll auf mögliche Fehlermeldungen der Integration.", "coord_no_device_data": "Es konnten keine Daten zu Deinem konfigurierten Gerät abgerufen werden. Bitte prüfe Dein Home Assistant System Protokoll auf mögliche Fehlermeldungen der Integration.", "websocket_start_failed": "Der Websocket konnte innerhalb von einer Minute nicht gestartet werden. Bitte prüfe Dein Home Assistant System Protokoll auf mögliche Fehlermeldungen der Integration.", - + "ws_start_task_is_none": "start_websocket_and_wait_for_first_data(): WebSocket-Verbindungs-Task konnte nicht erstellt werden!", + "ws_fetch_essential_data_timed_out": "start_websocket_and_wait_for_first_data(): Zwingend erforderliche Daten nicht verfügbar - Timeout!", "car": { 0: "Unbekannt/Fehler", 1: "Inaktiv/Frei", @@ -286,6 +287,8 @@ class CT_VALUES(Enum): "coord_null_data": "Coordinator could not provided any data. Please check your Home Assistant system log for possible error messages.", "coord_no_device_data": "Coordinator could not fetch essential information from your configured device. Please check your Home Assistant system log for possible error messages.", "websocket_start_failed": "The Websocket could not be started within one minute. Please check your Home Assistant system log for possible error messages.", + "ws_start_task_is_none": "start_websocket_and_wait_for_first_data(): Could not create websocket connect task!", + "ws_fetch_essential_data_timed_out": "start_websocket_and_wait_for_first_data(): Could not fetch essential data - Timeout", "car": { 0: "Unknown/Error", diff --git a/custom_components/goecharger_api2/service.py b/custom_components/goecharger_api2/service.py index 407e3ea..4a55788 100644 --- a/custom_components/goecharger_api2/service.py +++ b/custom_components/goecharger_api2/service.py @@ -71,7 +71,7 @@ async def set_pv_data(self, call: ServiceCall): pgrid = call.data.get('pgrid', None) ppv = call.data.get('ppv', 0) pakku = call.data.get('pakku', 0) - if pgrid is not None and isinstance(pgrid, Number): + if pgrid is not None and _is_finite_number(pgrid): if not _is_finite_number(ppv): ppv = 0 if not _is_finite_number(pakku): From d4e2b381921453211f61f581cd8d7a7ded654ab5 Mon Sep 17 00:00:00 2001 From: marq24 Date: Wed, 28 Oct 2026 13:29:34 +0100 Subject: [PATCH 14/24] first refactoring done (still not 100% happy with the way how to add to a Tag some additional methods... --- custom_components/goecharger_api2/__init__.py | 11 +- custom_components/goecharger_api2/const.py | 19 +++- custom_components/goecharger_api2/number.py | 95 ++++++++++------ .../pygoecharger_ha/__init__.py | 104 +++++++++++------- .../goecharger_api2/pygoecharger_ha/const.py | 6 +- .../goecharger_api2/pygoecharger_ha/keys.py | 92 ++++++++++++++-- custom_components/goecharger_api2/service.py | 43 ++++---- .../goecharger_api2/services.yaml | 25 ++++- .../goecharger_api2/translations/de.json | 1 + .../goecharger_api2/translations/en.json | 1 + 10 files changed, 295 insertions(+), 102 deletions(-) diff --git a/custom_components/goecharger_api2/__init__.py b/custom_components/goecharger_api2/__init__.py index 5d36ca1..7be1c18 100644 --- a/custom_components/goecharger_api2/__init__.py +++ b/custom_components/goecharger_api2/__init__.py @@ -53,6 +53,7 @@ STARTUP_MESSAGE, SERVICE_SET_PV_DATA, SERVICE_STOP_CHARGING, + SERVICE_SET_TEMPORARY_CURRENT, CONF_11KWLIMIT, CONF_INTEGRATION_TYPE, CONFIG_VERSION, CONFIG_MINOR_VERSION @@ -157,6 +158,8 @@ async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry): service = GoeChargerApiV2Service(hass, config_entry, coordinator) hass.services.async_register(DOMAIN, SERVICE_SET_PV_DATA, service.set_pv_data, supports_response=SupportsResponse.OPTIONAL) + hass.services.async_register(DOMAIN, SERVICE_SET_TEMPORARY_CURRENT, service.set_temporary_current_limit, + supports_response=SupportsResponse.OPTIONAL) hass.services.async_register(DOMAIN, SERVICE_STOP_CHARGING, service.stop_charging, supports_response=SupportsResponse.OPTIONAL) else: @@ -205,6 +208,7 @@ async def async_unload_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> _LOGGER.info(f"async_unload_entry(): Unloading last active go-eCharger - will remove SERVICES") hass.services.async_remove(DOMAIN, SERVICE_SET_PV_DATA) hass.services.async_remove(DOMAIN, SERVICE_STOP_CHARGING) + hass.services.async_remove(DOMAIN, SERVICE_SET_TEMPORARY_CURRENT) return unload_ok @@ -642,7 +646,7 @@ async def _async_update_data(self) -> dict: _LOGGER.error(f"_async_update_data(): unexpected: {type(other).__name__} - {other}") raise UpdateFailed() from other - async def async_write_key(self, key: str, value, entity: Entity = None) -> dict: + async def async_write_key(self, key: str, value, entity: Entity = None, require_ack: bool = False) -> dict: if self._CLIENT_COMMUNICATION_ERROR_TS + CLIENT_COMMUNICATION_ERROR_DELAY_IN_SECONDS > time.time(): time_info = CLIENT_COMMUNICATION_ERROR_DELAY_IN_SECONDS - (time.time() - self._CLIENT_COMMUNICATION_ERROR_TS) _LOGGER.info(f"async_write_key(): skipping due to client communication error for the next {time_info} seconds") @@ -652,7 +656,7 @@ async def async_write_key(self, key: str, value, entity: Entity = None) -> dict: raise ValueError("async_write_key(): RESTART is TRIGGERED (waiting for random sleep delay)") try: - result = await self.bridge.write_value_to_key(key, value) + result = await self.bridge.write_value_to_key(key, value, require_ack=require_ack) self.handle_write_result("single", value, key, result, entity) return result @@ -872,6 +876,9 @@ def __init__(self, entity_type:str, coordinator: GoeChargerDataUpdateCoordinator # make sure that we keep the CASE of the key! self.data_key = description.key + if hasattr(description, "tag"): + self.tag:Tag = description.tag + # tuple_idx must have description.translation_key ! if hasattr(description, "tuple_idx") and description.tuple_idx is not None: if description.translation_key is not None: diff --git a/custom_components/goecharger_api2/const.py b/custom_components/goecharger_api2/const.py index bbaddb5..727877b 100644 --- a/custom_components/goecharger_api2/const.py +++ b/custom_components/goecharger_api2/const.py @@ -36,6 +36,7 @@ SERVICE_SET_PV_DATA: Final = "set_pv_data" SERVICE_STOP_CHARGING: Final = "stop_charging" +SERVICE_SET_TEMPORARY_CURRENT: Final = "set_temporary_current_limit" CONF_11KWLIMIT: Final = "limit_to_11kw" CONF_INTEGRATION_TYPE = "integration_type" @@ -56,6 +57,7 @@ class ExtButtonEntityDescription(ButtonEntityDescription): @dataclass(frozen=True) class ExtNumberEntityDescription(NumberEntityDescription): + tag: Tag | None = None write_zero_as_null: bool | None = None handle_as_float: bool | None = None factor: int | None = None @@ -694,6 +696,19 @@ class ExtSwitchEntityDescription(SwitchEntityDescription): icon="mdi:ab-testing", entity_registry_enabled_default=False ), + ExtNumberEntityDescription( + tag=Tag.TCL, + key=Tag.TCL.key, + check_16a_limit=True, + native_min_value=6, + native_max_value=32, + native_step=1, + mode=NumberMode.BOX, + native_unit_of_measurement=UnitOfElectricCurrent.AMPERE, + device_class=NumberDeviceClass.CURRENT, + icon="mdi:current-ac", + entity_registry_enabled_default=False, + ), # zfo ExtNumberEntityDescription( key=Tag.ZFO.key, @@ -2324,8 +2339,8 @@ class ExtSwitchEntityDescription(SwitchEntityDescription): entity_registry_enabled_default=True ), ] -CONTROLLER_NUMBER_SENSORS = [ -] +CONTROLLER_NUMBER_SENSORS = [] + CONTROLLER_SELECT_SENSORS = [ ExtSelectEntityDescription( key=Tag.IPS.key, diff --git a/custom_components/goecharger_api2/number.py b/custom_components/goecharger_api2/number.py index 958ebc5..211981e 100644 --- a/custom_components/goecharger_api2/number.py +++ b/custom_components/goecharger_api2/number.py @@ -57,18 +57,52 @@ async def async_added_to_hass(self) -> None: if self.data_key == Tag.AMP.key: self.coordinator.entities[self.data_key] = self + @property + def available(self): + super_val = super().available + if self.tag is not None and hasattr(self.tag, "tag_func_get_min_max_bounds"): + try: + self.tag.tag_func_get_min_max_bounds(self.coordinator.data, self.coordinator.limit_to16a) + return super_val and self.data_key in self.coordinator.data + except (ValueError, TypeError): + return False + return super_val + + @property + def native_min_value(self): + super_val = super().native_min_value + if self.tag is not None and hasattr(self.tag, "tag_func_get_min_max_bounds"): + try: + return self.tag.tag_func_get_min_max_bounds(self.coordinator.data, self.coordinator.limit_to16a)[0] + except (ValueError, TypeError): + pass + + return super_val + + @property + def native_max_value(self): + super_val = super().native_max_value + if self.tag is not None and hasattr(self.tag, "tag_func_get_min_max_bounds"): + try: + return self.tag.tag_func_get_min_max_bounds(self.coordinator.data, self.coordinator.limit_to16a)[1] + except (ValueError, TypeError): + pass + + return super_val + @property def native_value(self): try: value = None if self.coordinator.data is not None: + value = (self.coordinator.data or {}).get(self.data_key) if self.entity_description.idx is not None: - value = self.coordinator.data[self.data_key][self.entity_description.idx] - else: - value = self.coordinator.data[self.data_key] + # this is still some sort of silly access... + value = value[self.entity_description.idx] if value is None or value == "": return "unknown" + elif self.entity_description.handle_as_float is not None and self.entity_description.handle_as_float: if self.entity_description.factor is not None and self.entity_description.factor > 0: value = float(int(value) / self.entity_description.factor) @@ -89,38 +123,37 @@ def native_value(self): return value async def async_set_native_value(self, value) -> None: + validate_write_success = False + if self.tag is not None: + if hasattr(self.tag, "tag_func_validate_input"): + # ok let's verify the value that we should set... + value = self.tag.tag_func_validate_input(value, self.coordinator.data, self.coordinator.limit_to16a) + + validate_write_success = hasattr(self.tag, "tag_func_validate_output") + # _LOGGER.info(f"set_native {self.data_key} {value} idx? {self.entity_description.idx}) factor: {self.entity_description.factor} float? {self.entity_description.handle_as_float}") try: - if self.entity_description.idx is not None: - # we have to write all values of the object... [not only the set one] - obj = self.coordinator.data[self.data_key] - - if int(value) == 0 and self.entity_description.write_zero_as_null is not None and self.entity_description.write_zero_as_null: - obj[self.entity_description.idx] = None - elif self.entity_description.factor is not None and self.entity_description.factor > 0: - # no special handling for 'handle_as_float' here - since the float's just exist in the GUI... if the - # backend these values are (keep my fingers crossed) always int's - obj[self.entity_description.idx] = int(value * self.entity_description.factor) - elif self.entity_description.handle_as_float is not None and self.entity_description.handle_as_float: - obj[self.entity_description.idx] = float(value) - else: - # we will write all numbers as integer's [no decimal's/fractions!!!] - obj[self.entity_description.idx] = int(value) - - await self.coordinator.async_write_key(self.data_key, obj, self) + desc = self.entity_description + if int(value) == 0 and desc.write_zero_as_null: + final_value_to_set = None + elif desc.factor and desc.factor > 0: + final_value_to_set = int(value * desc.factor) + elif desc.handle_as_float: + final_value_to_set = float(value) + else: + final_value_to_set = int(value) + if desc.idx is None: + final_object_to_write = final_value_to_set else: - if int(value) == 0 and self.entity_description.write_zero_as_null is not None and self.entity_description.write_zero_as_null: - await self.coordinator.async_write_key(self.data_key, None, self) - elif self.entity_description.factor is not None and self.entity_description.factor > 0: - # no special handling for 'handle_as_float' here - since the float's just exist in the GUI... if the - # backend these values are (keep my fingers crossed) always int's - await self.coordinator.async_write_key(self.data_key, int(value * self.entity_description.factor), self) - elif self.entity_description.handle_as_float is not None and self.entity_description.handle_as_float: - await self.coordinator.async_write_key(self.data_key, float(value), self) - else: - # we will write all numbers as integer's [no decimal's/fractions!!!] - await self.coordinator.async_write_key(self.data_key, int(value), self) + final_object_to_write = self.coordinator.data[self.data_key] + final_object_to_write[desc.idx] = final_value_to_set + + result = await self.coordinator.async_write_key(self.data_key, final_object_to_write, self, require_ack=validate_write_success) + + # must we verify that the setting of the value was successful? + if validate_write_success and result: + self.tag.tag_func_validate_output(result, value) except ValueError: return "unavailable" diff --git a/custom_components/goecharger_api2/pygoecharger_ha/__init__.py b/custom_components/goecharger_api2/pygoecharger_ha/__init__.py index 5008b11..0493dac 100644 --- a/custom_components/goecharger_api2/pygoecharger_ha/__init__.py +++ b/custom_components/goecharger_api2/pygoecharger_ha/__init__.py @@ -6,6 +6,7 @@ import logging import random import secrets +from asyncio import Future from collections import ChainMap import time from typing import Any, Dict, Optional @@ -220,6 +221,7 @@ def __init__(self, web_session, intg_type:str, self._ws_hashed_password = None self._ws_request_id_counter = 0 self._ws_request_storage = ExpiringStore() + self._ws_pending_ack: [str, Future] = {} self._ws_debounced_update_task = None self._ws_LAST_UPDATE = 0 self._ws_LAST_NEW_DATA_NOTIFY = 0 @@ -501,7 +503,7 @@ async def _read_all_data(self) -> NonNullDict: _LOGGER.warning(f"_read_all_data(): BaseException: {type(err).__name__}: {err}") return {} - async def write_value_to_key(self, key, value) -> dict: + async def write_value_to_key(self, key, value, require_ack: bool = False) -> dict: is_button_press = value is not None and str(value) == IS_TRIGGER if self.ws_connected and self._ws_connection is not None: @@ -509,7 +511,7 @@ async def write_value_to_key(self, key, value) -> dict: # ignoring button presses when connected via WebSockets return {key: value} else: - if await self._ws_send_command(key, value): + if await self._ws_send_command(key, value, wait_for_response=require_ack): self._ws_LAST_NEW_DATA_NOTIFY = 0 return {key: value} else: @@ -573,7 +575,7 @@ async def _write_values_int(self, a_params, a_result_key, a_result_value) -> dic try: r_json = await res.json() if r_json is not None and len(r_json) > 0: - if a_result_key in r_json and r_json[a_result_key]: + if a_result_key in r_json and r_json[a_result_key] is True: # ignore 'force-update' for 'ids' (PV surplus charging) if a_result_key != Tag.IDS.key: self._LAST_CONFIG_UPDATE_TS = 0 @@ -628,6 +630,7 @@ def ws_check_last_update(self) -> bool: async def ws_close(self, ws): """Close the WebSocket connection cleanly.""" + self._fail_pending_acknowledgements() if self._ws_serial is not None: _LOGGER.debug(f"ws_close(): for '{self._ws_serial}' called") else: @@ -728,9 +731,15 @@ def _ws_normalize_dict(self, data: dict) -> dict: result[key_str] = self._ws_normalize_value(value) return result - async def _ws_send_command(self, key: str, value): - """Send a setValue command via WebSocket""" - _LOGGER.debug(f"_ws_send_command(): Sending {key}:{value} via WebSocket...") + def _fail_pending_acknowledgements(self): + """Wake bounded confirmation waits when a connection disappears.""" + for pending in self._ws_pending_ack.values(): + if not pending.done(): + pending.set_result(False) + self._ws_pending_ack.clear() + + async def _ws_send_command(self, key: str, value, wait_for_response: bool = False): + """Optionally wait for the matching inner setValue response, not just send_json.""" self._ws_request_id_counter += 1 the_request_id_to_use = int(self._ws_request_id_counter) @@ -740,40 +749,54 @@ async def _ws_send_command(self, key: str, value): #if value is None: # value = "null" - original_message = { - "type": "setValue", - "requestId": the_request_id_to_use, - "key": key, - "value": value - } - - # for debugging purpose we want to keep the original message - self._ws_request_storage.set(str(the_request_id_to_use), original_message) - - if self._ws_secured: - # the local websocket needs special handling... - payload = json.dumps(original_message) - h = hmac.new(bytearray(self._ws_hashed_password), bytearray(payload.encode()), hashlib.sha256) - secure_message = { - "type": "securedMsg", - "data": payload, - "requestId": f"{original_message['requestId']}sm", - "hmac": h.hexdigest() + request_key = str(the_request_id_to_use) + pending = asyncio.get_running_loop().create_future() if wait_for_response else None + if pending is not None: + self._ws_pending_ack[request_key] = pending + try: + original_message = { + "type": "setValue", + "requestId": the_request_id_to_use, + "key": key, + "value": value } - # Pack as msgpack with 0x00 prefix - # _LOGGER.debug(f"_ws_send_command(): Sending {key}={value} as SECURED BYTES") - # msg_packed = b'\x00' + msgpack.packb(secure_message) - # await self._ws_connection.send_bytes(msg_packed) + # for debugging purpose we want to keep the original message + self._ws_request_storage.set(str(the_request_id_to_use), original_message) + + if self._ws_secured: + # the local websocket needs special handling... + payload = json.dumps(original_message) + h = hmac.new(bytearray(self._ws_hashed_password), bytearray(payload.encode()), hashlib.sha256) + secure_message = { + "type": "securedMsg", + "data": payload, + "requestId": f"{original_message['requestId']}sm", + "hmac": h.hexdigest() + } - _LOGGER.debug(f"_ws_send_command(): Sending {key}={value} as SECURED JSON") - await self._ws_connection.send_json(secure_message) - else: - # the cloud API can receive real JSON data... - _LOGGER.debug(f"_ws_send_command(): Sending {key}={value} as JSON") - await self._ws_connection.send_json(original_message) + # Pack as msgpack with 0x00 prefix + # _LOGGER.debug(f"_ws_send_command(): Sending {key}={value} as SECURED BYTES") + # msg_packed = b'\x00' + msgpack.packb(secure_message) + # await self._ws_connection.send_bytes(msg_packed) - return True + _LOGGER.debug(f"_ws_send_command(): Sending {key}={value} as SECURED JSON") + await self._ws_connection.send_json(secure_message) + else: + # the cloud API can receive real JSON data... + _LOGGER.debug(f"_ws_send_command(): Sending {key}={value} as JSON") + await self._ws_connection.send_json(original_message) + + if pending is not None: + return await asyncio.wait_for(pending, timeout=3) + return True + except asyncio.TimeoutError: + _LOGGER.warning("Timed out waiting for charger acknowledgement for %s", key) + return False + finally: + self._ws_pending_ack.pop(request_key, None) + if pending is not None and not pending.done(): + pending.cancel() async def ws_connect(self, a_event: TargetedEvent=None, keep_ws_states_after_close: bool=False): """Connect to WebSocket with full authentication and message handling""" @@ -1007,6 +1030,7 @@ async def ws_connect(self, a_event: TargetedEvent=None, keep_ws_states_after_clo # self.ws_connected flag... -> but we must keep it ON self.ws_connected = True + self._fail_pending_acknowledgements() self.ws_connection_start = 0 self._ws_connection = None self._ws_LAST_UPDATE = 0 @@ -1046,10 +1070,16 @@ def extract_ws_message_data(self, data:dict): #_LOGGER.debug(f"extract_ws_message_data(): Received 'deltaStatus' with {len(filtered_data)} keys") elif msg_type == 'response': + pending = self._ws_pending_ack.get(str(data.get('requestId', ''))) + if pending is not None and not pending.done(): + # A secured envelope acknowledgement (e.g. '1sm') is not the + # inner setValue response ('1') and cannot complete this future. + pending.set_result(data.get('success') is True) + the_request_payload = None if 'requestId' in data: the_request_id = data.get('requestId', 'INVALID') - the_request_payload = self._ws_request_storage.get(the_request_id) + the_request_payload = self._ws_request_storage.get(str(the_request_id)) if data.get('success'): status_data = data.get('status', {}) diff --git a/custom_components/goecharger_api2/pygoecharger_ha/const.py b/custom_components/goecharger_api2/pygoecharger_ha/const.py index 11ac4dd..57c4264 100644 --- a/custom_components/goecharger_api2/pygoecharger_ha/const.py +++ b/custom_components/goecharger_api2/pygoecharger_ha/const.py @@ -22,19 +22,19 @@ FILTER_SYSTEMS: Final = "oem,sse,typ,var,hai,cae" FILTER_VERSIONS_NOCARDS: Final = f"ccrv,typ,log,fwc,fwv,var,cll,adi" FILTER_VERSIONS: Final = f"{FILTER_VERSIONS_NOCARDS},{FILTER_CARDS_ID_CLASSIC},{FILTER_CARDS_ID_FWV60}" -FILTER_MIN_STATES: Final = "car,modelStatus,err,nrg,tma,trx" +FILTER_MIN_STATES: Final = "car,modelStatus,err,nrg,tcl,tma,trx" FILTER_IDS_ADDON: Final = ",pakku,ppv,pgrid" # rbt: is the reboot time - and "looks like", that all other timestamps use the rbt "as" start point FILTER_TIMES_ADDON: Final = ",rbt,fsptws,inva,lbp,lccfc,lccfi,lcctc,lfspt,lmsc,lpsc,lcs" -FILTER_ALL_STATES: Final = "alw,acu,adi,amt,atp,awcp,car,{CARDS_ENERGY_FILTER},cbl,ccu,ccw,cdi,cll,cus,ctrls,deltaa,deltap,di1,err,eto,ffb,fhz,fsp,fsptws,inva,lbp,lccfc,lccfi,lcctc,lck,lfspt,lmsc,loa,lpsc,mcpea,mmp,modelStatus,nif,nrg,pakku,pgrid,pha,pnp,ppv,pvopt_averagePAkku,pvopt_averagePGrid,pvopt_averagePPv,pwm,rbc,rbt,rfb,rssi,rst,tlf,tls,tma,tpa,trx,wh,wsms,wst" +FILTER_ALL_STATES: Final = "alw,acu,adi,amt,atp,awcp,car,{CARDS_ENERGY_FILTER},cbl,ccu,ccw,cdi,cll,cus,ctrls,deltaa,deltap,di1,err,eto,ffb,fhz,fsp,fsptws,inva,lbp,lccfc,lccfi,lcctc,lck,lfspt,lmsc,loa,lpsc,mcpea,mmp,modelStatus,nif,nrg,pakku,pgrid,pha,pnp,ppv,pvopt_averagePAkku,pvopt_averagePGrid,pvopt_averagePPv,pwm,rbc,rbt,rfb,rssi,rst,tcl,tlf,tls,tma,tpa,trx,wh,wsms,wst" FILTER_ALL_CONFIG: Final = "acp,acs,ama,amp,ara,ate,att,awc,awe,awp,bac,{CARDS_ID_FILTER},cch,cco,cfi,cid,clp,cmse,ct,cwc,cwe,dwo,esk,fmt,fna,frc,frm,fst,fup,fzf,hai,hsa,iso2ena,lbr,lmo,loe,lof,log,lop,lot,loty,lse,map,mca,mci,mcpd,mptwt,mpwst,nmo,ocppe,pgt,po,psh,psm,psmd,rdbf,rdbs,rdef,rdes,rdre,rdpl,sch_satur,sch_sund,sch_week,sdp,sh,spl3,su,sua,sumd,tds,tof,upo,ust,zfo" FILTER_NOT_USED: Final = "mcc,mcca,mce,mcr,mcs,mcu,men,mlr,mlra,mqcn,mqg,mqss,msb,msp,msr,mtp,ocppai,ocppi,rdbfe,rdbse,rdefe,rdese,rdree,rdple" # found api-keys that are not documented (yet) ?! -FILTER_UNKNOWN_COMON: Final = "aus,ccd,cle,clea,cmmr,cmp,cms,csa,data,die,dii,dll,hla,isgo,la1,la3,lbl,lopr,lrc,lri,lrr,lwf,ocppao,ocppcm,ocppcs,ocppf,ocppla,ocpplo,ocppti,pdi,pgr,rde,smd,tcl,tsi,tzt,ufa,ufe,ufm,ufs,wbw,wda,wsl" +FILTER_UNKNOWN_COMON: Final = "aus,ccd,cle,clea,cmmr,cmp,cms,csa,data,die,dii,dll,hla,isgo,la1,la3,lbl,lopr,lrc,lri,lrr,lwf,ocppao,ocppcm,ocppcs,ocppf,ocppla,ocpplo,ocppti,pdi,pgr,rde,smd,tsi,tzt,ufa,ufe,ufm,ufs,wbw,wda,wsl" FILTER_UNKNOWN_FW59_X_BETA: Final = "bar,dsrc,evt,gmtr,gsa,lto,mhe,mht,ocppdp,ocppmp,ocpptp,orsch,pco,rdbfe,rdbse,rdefe,rdese,rdple,rdree,rmaf,rmav,rmif,rmiv,rsa,rsre,rsrr,tab" FILTER_UNKNOWN_FW56_2_BETA: Final = "bar,gmtr,gsa,mhe,mht,pco,rmaf,rmav,rmif,rmiv,rsa,rsre,rsrr" diff --git a/custom_components/goecharger_api2/pygoecharger_ha/keys.py b/custom_components/goecharger_api2/pygoecharger_ha/keys.py index 3b86819..1c96cf8 100644 --- a/custom_components/goecharger_api2/pygoecharger_ha/keys.py +++ b/custom_components/goecharger_api2/pygoecharger_ha/keys.py @@ -1,21 +1,89 @@ import logging -from enum import Enum -from typing import ( - NamedTuple, Final -) +import math +import types -# from aenum import Enum, extend_enum +from enum import Enum +from numbers import Number +from typing import NamedTuple, Final +from collections.abc import Mapping _LOGGER: logging.Logger = logging.getLogger(__package__) IS_TRIGGER: Final = "TRIGGER" +def is_finite_number(value) -> bool: + """NaN and infinity are Numbers, but would be sent to the charger as invalid JSON.""" + if isinstance(value, bool) or not isinstance(value, Number): + return False + try: + return math.isfinite(float(value)) + except (OverflowError, TypeError, ValueError): + return False + class CAT(Enum): CONFIG = "CONF" STATUS = "STAT" OTHER = "OTHE" CONSTANT = "CONS" +# MARQ24: I am quite unconfident, if I would like this approach - but currently the TAG needs special methods +# at least that's what was introduced with PR #177 +class _TCLTagLogic: + @staticmethod + def tcl_get_min_max_bounds(self, data: Mapping, limit_to_16a: bool = False) -> tuple[int, int]: + """Require known hardware/user bounds rather than guessing an upper limit.""" + #_LOGGER.debug("_TCLTagLogic.tag_func_get_min_max_bounds() called") + if not isinstance(data, Mapping): + raise ValueError("Charger data is unavailable") + + variant = data.get(Tag.VAR.key) + if isinstance(variant, bool) or variant not in (11, 22): + raise ValueError("The charger current rating is unknown") + + lower, upper = data.get(Tag.MCA.key), data.get(Tag.AMA.key) + for value in (lower, upper): + if not is_finite_number(value): + raise ValueError("The charger current bounds are unavailable") + + reported = data.get(self.key) + if reported is not None and ( + not is_finite_number(reported) or reported < 0 or reported != int(reported) + ): + raise ValueError("The temporary current limit is not a valid reported value") + maximum = min(16 if variant == 11 or limit_to_16a else 32, math.floor(upper)) + minimum = max(6, math.ceil(lower)) + if minimum > maximum: + raise ValueError("The charger current bounds are inconsistent") + return minimum, maximum + + @staticmethod + def tcl_validate_input(self, value, data: Mapping, limit_to_16a: bool = False) -> int: + """Only accept whole amperes in range; zero/null reset semantics are not assumed.""" + #_LOGGER.debug("_TCLTagLogic.tcl_validate_input() called") + if not isinstance(data, Mapping) or self.key not in data: + raise ValueError("This charger has not reported temporary-current-limit support") + + minimum, maximum = self.tag_func_get_min_max_bounds(data, limit_to_16a) + if not is_finite_number(value): + raise ValueError("Current must be a finite number of whole amperes") + + if value != int(value) or not minimum <= value <= maximum: + raise ValueError(f"Current must be between {minimum} and {maximum} whole amperes") + + return int(value) + + @staticmethod + def tcl_validate_output(self, result, current: int) -> None: + """The existing bridge returns the submitted value only after a successful write.""" + #_LOGGER.debug("_TCLTagLogic.tcl_validate_output() called") + if not isinstance(result, dict) or isinstance(result.get(self.key), bool) or result.get(self.key) != current: + raise ValueError("The charger did not acknowledge the temporary current limit") + +_TCL_METHODS: Final = { + "tag_func_get_min_max_bounds": _TCLTagLogic.tcl_get_min_max_bounds, + "tag_func_validate_input": _TCLTagLogic.tcl_validate_input, + "tag_func_validate_output": _TCLTagLogic.tcl_validate_output, +} class ApiKey(NamedTuple): key: str @@ -25,13 +93,21 @@ class ApiKey(NamedTuple): # see https://github.com/goecharger/go-eCharger-API-v2/blob/main/apikeys-en.md for details class Tag(ApiKey, Enum): - def __hash__(self) -> int: return hash(self.key) def __str__(self): return self.key + # marq24: this is all hideous - but looks like that is the "only" way + # how to attach custom methods to a Tag implementation + def __getattr__(self, name): + if name.startswith("tag_func_"): + if self == Tag.TCL and name in _TCL_METHODS: + return types.MethodType(_TCL_METHODS[name], self) + + raise AttributeError(f"Tag object has no attribute '{name}'") + # allowChargePause (car compatiblity) ACP = ApiKey(key="acp", cat=CAT.CONFIG, writeable=True) # access_control user setting (Open=0, Wait=1) @@ -624,7 +700,9 @@ def __str__(self): # rsrr | R/W | TYPE | Config | rampupAtStartAndReconnectionRate in %/s # smd | R | TYPE | Status | smart meter data # tab | R | TYPE | Status | time at boot in utc in milliseconds, add rbt to get to current utc time - # tcl | R/W | TYPE | Config | temporary current limit (does not change the user current limit, will be reset after 10min if not updated regulary) + + # tcl | R/W | TYPE | Config | temporary current limit (does not change the user current limit, will be reset after 10min if not updated regularly) + TCL = ApiKey(key="tcl", cat=CAT.STATUS, writeable=True) # tsi | R | TYPE | Status | transaction start rfidid (only available when sendRfid) # tzt | R/W | TYPE | Config | timezone type, freetext string for app selection # ufa | R/W | TYPE | Config | Underfrequency Control activation threshold diff --git a/custom_components/goecharger_api2/service.py b/custom_components/goecharger_api2/service.py index 4a55788..01f0c1f 100644 --- a/custom_components/goecharger_api2/service.py +++ b/custom_components/goecharger_api2/service.py @@ -1,8 +1,6 @@ import asyncio import datetime import logging -import math - from numbers import Number from homeassistant.config_entries import ConfigEntry @@ -12,21 +10,12 @@ from custom_components.goecharger_api2.const import WAN from custom_components.goecharger_api2.pygoecharger_ha.const import INTG_TYPE -from custom_components.goecharger_api2.pygoecharger_ha.keys import Tag +from custom_components.goecharger_api2.pygoecharger_ha.keys import Tag, is_finite_number _LOGGER: logging.Logger = logging.getLogger(__package__) _COORDINATOR_PER_CONFIG_ENTRY_ID_MAP: dict[str, DataUpdateCoordinator] = {} -def _is_finite_number(value) -> bool: - """NaN and infinity are Numbers, but would be sent to the charger as invalid JSON.""" - if not isinstance(value, Number): - return False - try: - return math.isfinite(float(value)) - except (TypeError, ValueError, OverflowError): - return False - def add_config_entry_to_service(config: ConfigEntry, coordinator: DataUpdateCoordinator): global _COORDINATOR_PER_CONFIG_ENTRY_ID_MAP _COORDINATOR_PER_CONFIG_ENTRY_ID_MAP[config.entry_id] = coordinator @@ -71,10 +60,10 @@ async def set_pv_data(self, call: ServiceCall): pgrid = call.data.get('pgrid', None) ppv = call.data.get('ppv', 0) pakku = call.data.get('pakku', 0) - if pgrid is not None and _is_finite_number(pgrid): - if not _is_finite_number(ppv): + if pgrid is not None and is_finite_number(pgrid): + if not is_finite_number(ppv): ppv = 0 - if not _is_finite_number(pakku): + if not is_finite_number(pakku): pakku = 0 payload = { @@ -93,24 +82,39 @@ async def set_pv_data(self, call: ServiceCall): } except (ValueError, TypeError) as exc: + _LOGGER.debug(f"Service-set_pv_data(): Error {type(exc).__name__}: {exc}") if call.return_response: return {"error": str(exc), "date": str(datetime.datetime.now().time())} if call.return_response: return {"error": "No Grid Power provided (or false data)", "date": str(datetime.datetime.now().time())} + async def set_temporary_current_limit(self, call: ServiceCall): + config_id, coordinator = self.get_coordinator(call) + _LOGGER.debug(f"Service-set_temporary_current_limit(): Provided config_id: {config_id} -> using {coordinator.name} serial: {coordinator._serial}") + try: + validated_input_current = Tag.TCL.tag_func_validate_input(call.data.get("current"), coordinator.data, coordinator.limit_to16a) + result_current = await coordinator.async_write_key(Tag.TCL.key, validated_input_current, require_ack=True) + Tag.TCL.tag_func_validate_output(result_current, validated_input_current) + except ValueError as exc: + _LOGGER.debug(f"Service-set_temporary_current_limit(): ValueError: {exc}") + raise ServiceValidationError(str(exc)) from exc + + if call.return_response: + return {"success": True, "current": result_current, "acknowledged": True} + async def stop_charging(self, call: ServiceCall): if not self._stop_in_progress: + _LOGGER.debug(f"Service-stop_charging(): Force STOP_CHARGING") config_id, coordinator = self.get_coordinator(call) self._stop_in_progress = True - _LOGGER.debug(f"Force STOP_CHARGING") - _LOGGER.debug(f"Provided config_id: {config_id} -> using {coordinator.name} serial: {coordinator._serial}") + _LOGGER.debug(f"Service-stop_charging(): Provided config_id: {config_id} -> using {coordinator.name} serial: {coordinator._serial}") try: resp = await coordinator.async_write_key(Tag.FRC.key, 1) if Tag.FRC.key in resp: - _LOGGER.debug(f"STOP_CHARGING: waiting for 5 minutes...") + _LOGGER.debug(f"Service-stop_charging(): STOP_CHARGING: waiting for 5 minutes...") await asyncio.sleep(300) - _LOGGER.debug(f"STOP_CHARGING: 5 minutes are over... disable charging LOCK again") + _LOGGER.debug(f"Service-stop_charging(): STOP_CHARGING: 5 minutes are over... disable charging LOCK again") resp = await coordinator.async_write_key(Tag.FRC.key, 0) @@ -130,6 +134,7 @@ async def stop_charging(self, call: ServiceCall): return {"error": "A STOP_CHARGING request could not be send", "date": str(datetime.datetime.now().time())} except (ValueError, TypeError) as exc: + _LOGGER.debug(f"Service-stop_charging(): Error {type(exc).__name__}: {exc}") if call.return_response: return {"error": str(exc), "date": str(datetime.datetime.now().time())} diff --git a/custom_components/goecharger_api2/services.yaml b/custom_components/goecharger_api2/services.yaml index f948cdf..af4b401 100644 --- a/custom_components/goecharger_api2/services.yaml +++ b/custom_components/goecharger_api2/services.yaml @@ -65,4 +65,27 @@ stop_charging: required: false selector: config_entry: - integration: "goecharger_api2" \ No newline at end of file + integration: "goecharger_api2" +set_temporary_current_limit: + name: Set temporary current limit + description: >- + Set or renew an expiring limit without changing requested/maximum current. + The charger expires it after approximately ten minutes without renewal. + This is not a charging Stop or a failsafe watchdog. + fields: + configid: + name: Charger + required: true + selector: + config_entry: + integration: goecharger_api2 + current: + name: Current + required: true + selector: + number: + min: 6 + max: 32 + step: 1 + unit_of_measurement: A + mode: box diff --git a/custom_components/goecharger_api2/translations/de.json b/custom_components/goecharger_api2/translations/de.json index 3ca4597..1cdca1f 100644 --- a/custom_components/goecharger_api2/translations/de.json +++ b/custom_components/goecharger_api2/translations/de.json @@ -112,6 +112,7 @@ "number": { "ama": {"name": "Max. Stromlimit"}, "amp": {"name": "Angeforderter Strom"}, + "tcl": {"name": "Temporäres Stromlimit"}, "ate": {"name": "Next-Trip Lademenge"}, "att": {"name": "Next-Trip Beenden um (Min. nach 0 Uhr)"}, "awp": {"name": "Schwellenwert für Günstigen-Strompreis (ohne Gebühren!)"}, diff --git a/custom_components/goecharger_api2/translations/en.json b/custom_components/goecharger_api2/translations/en.json index 6352eab..bde96db 100644 --- a/custom_components/goecharger_api2/translations/en.json +++ b/custom_components/goecharger_api2/translations/en.json @@ -112,6 +112,7 @@ "number": { "ama": {"name": "Maximum current limit"}, "amp": {"name": "Requested current"}, + "tcl": {"name": "Temporary current limit"}, "ate": {"name": "Automatic stop energy"}, "att": {"name": "Automatic stop time"}, "awp": {"name": "Awattar maximum price threshold"}, From 4702213aec08d6bf06ca5f010140367fa311bd33 Mon Sep 17 00:00:00 2001 From: marq24 Date: Thu, 29 Oct 2026 18:05:31 +0100 Subject: [PATCH 15/24] fun with tests --- custom_components/goecharger_api2/service.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/custom_components/goecharger_api2/service.py b/custom_components/goecharger_api2/service.py index 4a55788..b3a2fcf 100644 --- a/custom_components/goecharger_api2/service.py +++ b/custom_components/goecharger_api2/service.py @@ -141,7 +141,11 @@ def get_coordinator(self, call): global _COORDINATOR_PER_CONFIG_ENTRY_ID_MAP config_id = call.data.get('configid', None) if config_id is None: - coordinator = next(iter(_COORDINATOR_PER_CONFIG_ENTRY_ID_MAP.values())) + if len(_COORDINATOR_PER_CONFIG_ENTRY_ID_MAP) == 1: + coordinator = next(iter(_COORDINATOR_PER_CONFIG_ENTRY_ID_MAP.values())) + else: + _LOGGER.warning(f"get_coordinator(): no 'config_id' provided but length of _COORDINATOR_PER_CONFIG_ENTRY_ID_MAP != 1: {len(_COORDINATOR_PER_CONFIG_ENTRY_ID_MAP)}") + raise ServiceValidationError(f"No 'config_id' provided BUT zero (or multiple) goecharger_api2 config entries registered with the service.") else: # Fetch the config entry entry = self._hass.config_entries.async_get_entry(config_id) From 738c3c659d0833e938541c0f1ae93dc344dc2fe6 Mon Sep 17 00:00:00 2001 From: Matthias Marquardt Date: Thu, 29 Oct 2026 19:23:25 +0100 Subject: [PATCH 16/24] let's add the tests to main (#178) * Add regression tests for the integration (no charger, no network) 79 passing tests and 11 strict xfail tests that describe known bugs (they turn red as soon as a bug is fixed, so the marker must then be removed). Covered: HTTP requests and answers (official go-e docs), WebSocket messages, password hashing and signing, the 16 A decision table, entity ID stability (snapshot), config entry migration, services, multi-charger registry and unloading. Based on upstream main 98af3e0. Not intended as a pull request to the upstream repository. Co-Authored-By: Claude Sonnet 5.5 * Make known bugs fail visibly instead of xfail Tests that describe unfixed bugs now carry the marker `known_bug` and fail the run. Deselect them with -m "not known_bug". Co-Authored-By: Claude Sonnet 5.5 * Add negative tests: WebSocket login, coordinator outages, setup and config flow Each failure path ends cleanly (error reported, no data kept, no command sent). Positive controls make sure the fakes let valid input pass. Nine deliberate code mutations were all caught by these tests. Co-Authored-By: Claude Sonnet 5.5 * Add contract tests against the official go-e key table api_keys.json is generated from the official table (names and flags only). Unsupported and undocumented keys are classified with a reason, differences between documentation and code are listed and fail without blocking (marker needs_decision) until someone has decided whether they are intended. Co-Authored-By: Claude Sonnet 5.5 * Record per-firmware key list values in deviations.json The per-firmware lists agree with the code for frm and ids; the main table is the odd one out there. For the other deviations the lists agree with the main table. Co-Authored-By: Claude Sonnet 5.5 * Add tests for platforms, polling, writing, coordinator runtime, services/setup and config flow About 370 more tests (491 behaviour and error-path tests in total). Platform entities, the polling read cycle, all write paths, coordinator runtime (limiter, reconnect, watchdog), services, setup/unload and the config flow are covered. One new known_bug marks a swallowed error message in async_setup_entry. Measured path coverage (upper bound of basis paths) rose from 20 % to 61 %. Co-Authored-By: Claude Sonnet 5.5 * Add test report of 2026-10-29 and the measurement tools Coverage, junit and path-trace data plus testreport.md (line, branch and path coverage, paths by criticality). Tools: path tracer plugin, criticality classes, report builder, measure.sh. Co-Authored-By: Claude Sonnet 5.5 * Test every new upstream release with the regression tests Workflow polls for the latest release of the upstream integration, clones it, copies tests/integration into the clone and runs the blocking tests. Known-bug tests that pass are reported as probably fixed. A tag tested/ marks releases already tested. Co-Authored-By: Claude Sonnet 5.5 * Add arc42 architecture documentation and test concept Reconstructed from code, README and the official go-e API documentation; written with AI assistance and reviewed by the fork owner. Statements are marked by their source. Co-Authored-By: Claude Sonnet 5.5 * tests: skip the cll-without-cableCurrentLimit rows on releases that predate 98af3e0 Co-Authored-By: Claude Sonnet 5.5 * tests: HTTP and WebSocket must give the same versions data (issue #176) Co-Authored-By: Claude Sonnet 5.5 * tests: controller key contract and value ranges from the official docs Co-Authored-By: Claude Sonnet 5.5 * ci: manual workflow that builds the full test report Co-Authored-By: Claude Sonnet 5.5 * ci: name the measured version and the tests commit correctly in the report Co-Authored-By: Claude Sonnet 5.5 * ci: publish report, summary and test case list with GitHub Pages Co-Authored-By: Claude Sonnet 5.5 * ci: add arc42 and test concept as pages with table of contents and diagrams Co-Authored-By: Claude Sonnet 5.5 * tests: pin the Home Assistant test framework, option to run against the latest Co-Authored-By: Claude Sonnet 5.5 * docs: update test concept and arc42 to 987 tests, add Max-Ampere reset finding (R30) Co-Authored-By: Claude Sonnet 5.5 * tests: trigger the generic websocket message with a real error, not with the 'no device data' path Upstream eb5da46 now reports 'no device data' with its own message, so the language fallback test must reach the generic message through an exception. Co-Authored-By: Claude Sonnet 5.5 * tests: adapt to the decision that a call without configid is refused when several chargers exist Upstream 4702213 raises ServiceValidationError instead of picking the first charger. The old known_bug about a changing default charger is obsolete; it is replaced by a test of the new rule (skipped on versions that still guess). Co-Authored-By: Claude Sonnet 5.5 --------- Co-authored-by: Oliver-Marcus Eder Co-authored-by: Claude Sonnet 5.5 --- .github/workflows/pages.yaml | 71 + .github/workflows/regression-on-release.yaml | 114 ++ .github/workflows/test-report.yaml | 69 + docs/quality/README.md | 8 + docs/quality/arc42-ha-goecharger-api2.md | 532 ++++++ .../quality/testkonzept-ha-goecharger-api2.md | 250 +++ requirements_dev.txt | 4 +- tests/integration/README.md | 71 + tests/integration/__init__.py | 0 tests/integration/conftest.py | 12 + tests/integration/contract/api_keys.json | 1505 +++++++++++++++++ .../integration/contract/classification.json | 401 +++++ .../integration/contract/controller_keys.json | 420 +++++ tests/integration/contract/deviations.json | 201 +++ .../integration/contract/generate_api_keys.py | 37 + tests/integration/factories.py | 45 + tests/integration/pytest.ini | 6 + tests/integration/snapshots/entity_ids.json | 475 ++++++ tests/integration/test_api_contract.py | 112 ++ tests/integration/test_bridge.py | 45 + tests/integration/test_bridge_http.py | 127 ++ tests/integration/test_bridge_polling.py | 424 +++++ tests/integration/test_bridge_write_paths.py | 151 ++ .../integration/test_config_flow_failures.py | 83 + tests/integration/test_config_flow_paths.py | 191 +++ tests/integration/test_controller_contract.py | 93 + tests/integration/test_coordinator_errors.py | 33 + .../integration/test_coordinator_failures.py | 165 ++ tests/integration/test_coordinator_runtime.py | 535 ++++++ tests/integration/test_entity_ids.py | 77 + tests/integration/test_migration.py | 47 + tests/integration/test_platform_actions.py | 230 +++ tests/integration/test_platform_values.py | 264 +++ tests/integration/test_service.py | 50 + tests/integration/test_services_and_setup.py | 359 ++++ tests/integration/test_services_and_unload.py | 176 ++ tests/integration/test_value_ranges.py | 56 + tests/integration/test_versions_16a.py | 60 + .../test_versions_transport_parity.py | 41 + tests/integration/test_ws_connect_paths.py | 257 +++ tests/integration/test_ws_login_failures.py | 186 ++ tests/integration/test_ws_protocol.py | 192 +++ tests/reports/2026-10-29/coverage-all.json | 1 + .../reports/2026-10-29/coverage-baseline.json | 1 + .../reports/2026-10-29/coverage-blocking.json | 1 + tests/reports/2026-10-29/junit-all.xml | 416 +++++ tests/reports/2026-10-29/pathtrace-all.json | 638 +++++++ .../2026-10-29/pathtrace-baseline.json | 366 ++++ tests/reports/2026-10-29/testreport.md | 269 +++ tests/tools/README.md | 27 + tests/tools/build_report.py | 294 ++++ tests/tools/build_site.py | 98 ++ tests/tools/build_testcases.py | 55 + tests/tools/criticality.py | 115 ++ tests/tools/measure.sh | 27 + tests/tools/pathmetric.py | 40 + tests/tools/pathtracer.py | 72 + tests/tools/release_check.sh | 85 + 58 files changed, 10679 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/pages.yaml create mode 100644 .github/workflows/regression-on-release.yaml create mode 100644 .github/workflows/test-report.yaml create mode 100644 docs/quality/README.md create mode 100644 docs/quality/arc42-ha-goecharger-api2.md create mode 100644 docs/quality/testkonzept-ha-goecharger-api2.md create mode 100644 tests/integration/README.md create mode 100644 tests/integration/__init__.py create mode 100644 tests/integration/conftest.py create mode 100644 tests/integration/contract/api_keys.json create mode 100644 tests/integration/contract/classification.json create mode 100644 tests/integration/contract/controller_keys.json create mode 100644 tests/integration/contract/deviations.json create mode 100644 tests/integration/contract/generate_api_keys.py create mode 100644 tests/integration/factories.py create mode 100644 tests/integration/pytest.ini create mode 100644 tests/integration/snapshots/entity_ids.json create mode 100644 tests/integration/test_api_contract.py create mode 100644 tests/integration/test_bridge.py create mode 100644 tests/integration/test_bridge_http.py create mode 100644 tests/integration/test_bridge_polling.py create mode 100644 tests/integration/test_bridge_write_paths.py create mode 100644 tests/integration/test_config_flow_failures.py create mode 100644 tests/integration/test_config_flow_paths.py create mode 100644 tests/integration/test_controller_contract.py create mode 100644 tests/integration/test_coordinator_errors.py create mode 100644 tests/integration/test_coordinator_failures.py create mode 100644 tests/integration/test_coordinator_runtime.py create mode 100644 tests/integration/test_entity_ids.py create mode 100644 tests/integration/test_migration.py create mode 100644 tests/integration/test_platform_actions.py create mode 100644 tests/integration/test_platform_values.py create mode 100644 tests/integration/test_service.py create mode 100644 tests/integration/test_services_and_setup.py create mode 100644 tests/integration/test_services_and_unload.py create mode 100644 tests/integration/test_value_ranges.py create mode 100644 tests/integration/test_versions_16a.py create mode 100644 tests/integration/test_versions_transport_parity.py create mode 100644 tests/integration/test_ws_connect_paths.py create mode 100644 tests/integration/test_ws_login_failures.py create mode 100644 tests/integration/test_ws_protocol.py create mode 100644 tests/reports/2026-10-29/coverage-all.json create mode 100644 tests/reports/2026-10-29/coverage-baseline.json create mode 100644 tests/reports/2026-10-29/coverage-blocking.json create mode 100644 tests/reports/2026-10-29/junit-all.xml create mode 100644 tests/reports/2026-10-29/pathtrace-all.json create mode 100644 tests/reports/2026-10-29/pathtrace-baseline.json create mode 100644 tests/reports/2026-10-29/testreport.md create mode 100644 tests/tools/README.md create mode 100644 tests/tools/build_report.py create mode 100644 tests/tools/build_site.py create mode 100644 tests/tools/build_testcases.py create mode 100644 tests/tools/criticality.py create mode 100755 tests/tools/measure.sh create mode 100644 tests/tools/pathmetric.py create mode 100644 tests/tools/pathtracer.py create mode 100755 tests/tools/release_check.sh diff --git a/.github/workflows/pages.yaml b/.github/workflows/pages.yaml new file mode 100644 index 0000000..4174691 --- /dev/null +++ b/.github/workflows/pages.yaml @@ -0,0 +1,71 @@ +name: Publish test results (GitHub Pages) + +# Builds the site from the latest results and publishes it with GitHub Pages: +# - the summary of the latest run of "Regression tests on new upstream release" +# - the full test report of the latest run of "Test report (manual)" (with the coverage in detail) +# - the list of all test cases (built from the current tests and the junit file of that report) +# - the arc42 document and the test concept from docs/quality as web pages (Mermaid diagrams included) +# Starts after one of those two workflows has finished, or by hand. Needs Pages with the source +# "GitHub Actions" in the settings of the fork. + +on: + workflow_run: + workflows: ["Regression tests on new upstream release", "Test report (manual)"] + types: [completed] + workflow_dispatch: + +permissions: + contents: read + actions: read + pages: write + id-token: write + +concurrency: + group: pages + cancel-in-progress: false + +jobs: + publish: + runs-on: ubuntu-latest + environment: + name: github-pages + url: ${{ steps.deploy.outputs.page_url }} + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-python@v5 + with: + python-version: "3.14" + + - name: Download the latest results + env: + GH_TOKEN: ${{ github.token }} + run: | + mkdir -p results/report results/regression + fetch() { # + run="$(gh run list --workflow "$1" --status completed --limit 20 --json databaseId,conclusion \ + --jq '[.[] | select(.conclusion=="success" or .conclusion=="failure")][0].databaseId')" + [ -n "$run" ] && gh run download "$run" --pattern "$2*" --dir "$3" || echo "no results for $1" + } + fetch test-report.yaml testreport- results/report + fetch regression-on-release.yaml regression- results/regression + + - name: Build the site + run: | + pip install markdown + report="$(ls -d results/report/testreport-* 2>/dev/null | head -1)" + summary="$(ls results/regression/regression-*/summary.md 2>/dev/null | head -1)" + args="" + if [ -n "$report" ]; then + python tests/tools/build_testcases.py "$report/junit-all.xml" tests/integration testcases.md + args="--report $report --testcases testcases.md" + fi + [ -n "$summary" ] && args="$args --regression $summary" + python tests/tools/build_site.py _site --docs docs/quality $args + + - uses: actions/upload-pages-artifact@v3 + with: + path: _site + + - id: deploy + uses: actions/deploy-pages@v4 diff --git a/.github/workflows/regression-on-release.yaml b/.github/workflows/regression-on-release.yaml new file mode 100644 index 0000000..281bc45 --- /dev/null +++ b/.github/workflows/regression-on-release.yaml @@ -0,0 +1,114 @@ +name: Regression tests on new upstream release + +# Tests every new release of marq24/ha-goecharger-api2 with the tests of this branch. +# GitHub cannot start a workflow in this fork when another repository publishes a release, so the +# workflow asks for the latest release every three hours. A release is tested once: afterwards the tag +# "tested/" is set in this fork. Nothing is rebased or merged: the upstream release is cloned +# fresh and only tests/integration of this branch is copied into it (tests/tools/release_check.sh). +# +# Scheduled workflows only run from the default branch of the repository. This file therefore has to be +# on the default branch of the fork (see tests/tools/README.md). + +on: + schedule: + - cron: "23 */3 * * *" + workflow_dispatch: + inputs: + ref: + description: "Tag or branch of marq24/ha-goecharger-api2 (empty = latest release)" + required: false + default: "" + home_assistant: + description: "Home Assistant test framework: pinned (requirements_dev.txt) or latest (newest release, to see what a Home Assistant update breaks)" + type: choice + options: [pinned, latest] + default: pinned + push: + branches: [tests/regression-suite] + paths: + - "tests/**" + - ".github/workflows/regression-on-release.yaml" + +permissions: + contents: write + +concurrency: + group: regression-on-release + cancel-in-progress: false + +jobs: + regression: + runs-on: ubuntu-latest + steps: + - name: Check out the tests (branch tests/regression-suite) + uses: actions/checkout@v4 + with: + ref: tests/regression-suite + fetch-depth: 0 + + - name: Decide which upstream version to test + id: ref + env: + GH_TOKEN: ${{ github.token }} + INPUT_REF: ${{ inputs.ref }} + run: | + REF="$INPUT_REF" + if [ -z "$REF" ]; then + REF="$(gh api repos/marq24/ha-goecharger-api2/releases/latest --jq .tag_name)" + fi + SKIP=false + if [ "${{ github.event_name }}" = "schedule" ] && git ls-remote --exit-code --tags origin "refs/tags/tested/$REF" >/dev/null 2>&1; then + SKIP=true + fi + echo "ref=$REF" >> "$GITHUB_OUTPUT" + echo "skip=$SKIP" >> "$GITHUB_OUTPUT" + echo "Upstream version: $REF (already tested: $SKIP)" + + - name: Set up Python + if: steps.ref.outputs.skip != 'true' + uses: actions/setup-python@v5 + with: + python-version: "3.14" + cache: pip + cache-dependency-path: requirements_dev.txt + + - name: Install test dependencies + if: steps.ref.outputs.skip != 'true' + env: + HA_MODE: ${{ inputs.home_assistant }} + run: | + python -m pip install -r requirements_dev.txt msgpack + [ "$HA_MODE" = "latest" ] && python -m pip install --upgrade pytest-homeassistant-custom-component || true + python -m pip show homeassistant pytest-homeassistant-custom-component | grep -E "^(Name|Version)" + + - name: Run the regression tests against the upstream version + if: steps.ref.outputs.skip != 'true' + env: + GH_TOKEN: ${{ github.token }} + run: tests/tools/release_check.sh "${{ steps.ref.outputs.ref }}" "$RUNNER_TEMP/check" + + - name: Keep the results + if: always() && steps.ref.outputs.skip != 'true' + uses: actions/upload-artifact@v4 + with: + name: regression-${{ steps.ref.outputs.ref }} + path: | + ${{ runner.temp }}/check/*.xml + ${{ runner.temp }}/check/*.txt + ${{ runner.temp }}/check/summary.md + if-no-files-found: ignore + + - name: Remember that this release was tested + if: always() && steps.ref.outputs.skip != 'true' && github.event_name == 'schedule' + run: | + git config user.name "github-actions" + git config user.email "github-actions@users.noreply.github.com" + git tag -a "tested/${{ steps.ref.outputs.ref }}" -m "regression tests: ${{ job.status }}" + git push origin "tested/${{ steps.ref.outputs.ref }}" + + - name: Sync the fork main with upstream (best effort) + if: github.event_name == 'schedule' + continue-on-error: true + env: + GH_TOKEN: ${{ github.token }} + run: gh repo sync "${{ github.repository }}" --branch main --source marq24/ha-goecharger-api2 diff --git a/.github/workflows/test-report.yaml b/.github/workflows/test-report.yaml new file mode 100644 index 0000000..a1a2f4d --- /dev/null +++ b/.github/workflows/test-report.yaml @@ -0,0 +1,69 @@ +name: Test report (manual) + +# Builds the full test report (line, branch and path coverage, criticality) for a version of +# marq24/ha-goecharger-api2. Started by hand only; nothing is committed. The report is shown in the job +# summary and kept as an artifact. Like regression-on-release.yaml it clones the upstream version fresh and +# copies only tests/integration and tests/tools of this branch into it. + +on: + workflow_dispatch: + inputs: + ref: + description: "Tag or branch of marq24/ha-goecharger-api2 (empty = latest release)" + required: false + default: "" + +permissions: + contents: read + +jobs: + report: + runs-on: ubuntu-latest + steps: + - name: Check out the tests (this branch) + uses: actions/checkout@v4 + + - name: Decide which upstream version to measure + id: ref + env: + GH_TOKEN: ${{ github.token }} + INPUT_REF: ${{ inputs.ref }} + run: | + ref="$INPUT_REF" + [ -n "$ref" ] || ref="$(gh api repos/marq24/ha-goecharger-api2/releases/latest --jq .tag_name)" + echo "ref=$ref" >> "$GITHUB_OUTPUT" + + - uses: actions/setup-python@v5 + with: + python-version: "3.14" + + - name: Install test dependencies + run: pip install -r requirements_dev.txt msgpack pytest-cov radon + + - name: Clone the upstream version and add the tests + env: + REF: ${{ steps.ref.outputs.ref }} + run: | + git clone --depth 1 --branch "$REF" https://github.com/marq24/ha-goecharger-api2 "$RUNNER_TEMP/src" + mkdir -p "$RUNNER_TEMP/src/tests" + cp -R tests/integration tests/tools "$RUNNER_TEMP/src/tests/" + + - name: Measure and build the report + working-directory: ${{ runner.temp }}/src + env: + TESTS_COMMIT: ${{ github.sha }} + SOURCE_REF: ${{ steps.ref.outputs.ref }} + run: sh tests/tools/measure.sh "$RUNNER_TEMP/report" + + - name: Show the report + if: always() + run: | + [ -f "$RUNNER_TEMP/report/testreport.md" ] && cat "$RUNNER_TEMP/report/testreport.md" >> "$GITHUB_STEP_SUMMARY" || true + + - name: Keep the report + if: always() + uses: actions/upload-artifact@v4 + with: + name: testreport-${{ steps.ref.outputs.ref }} + path: ${{ runner.temp }}/report + if-no-files-found: ignore diff --git a/docs/quality/README.md b/docs/quality/README.md new file mode 100644 index 0000000..ad40a81 --- /dev/null +++ b/docs/quality/README.md @@ -0,0 +1,8 @@ +# Quality documentation + +Written with AI assistance (Claude Code) and reviewed by Oliver. Not part of the upstream repository; this fork branch only. + +- `arc42-ha-goecharger-api2.md`: architecture documentation (arc42, German) reconstructed from code, README and the official go-e API documentation. Statements are marked as code, documentation or inference. Chapter 11 lists risks and shows which of them have been fixed upstream. +- `testkonzept-ha-goecharger-api2.md`: test concept (scope, levels, markers, current state, gaps). + +Tests: `tests/integration/`. Measurement report: `tests/reports/`. Tools: `tests/tools/`. diff --git a/docs/quality/arc42-ha-goecharger-api2.md b/docs/quality/arc42-ha-goecharger-api2.md new file mode 100644 index 0000000..cb2b6f4 --- /dev/null +++ b/docs/quality/arc42-ha-goecharger-api2.md @@ -0,0 +1,532 @@ +# arc42 – Architekturdokumentation: `ha-goecharger-api2` (go-e APIv2 Connect) + +| | | +|---|---| +| **Gegenstand** | Home-Assistant-Integration `goecharger_api2` für go-eCharger-Wallboxen und go-eController | +| **Quelle** | `marq24/ha-goecharger-api2`, Branch `main`, Commit `98af3e0` (2026-10-28). Letzte Version (Release) ist `2026.10.8` (Commit `03b0fc3`, 2026-10-27); `main` enthält danach noch einen Commit zur 16-A-Erkennung. Risiken und Bausteine wurden am 2026-10-29 gegen diesen Stand aktualisiert | +| **Erstellt** | 2026-10-27, rekonstruiert aus Code, README, `docs/PVSURPLUS.md`, Workflows und Commit-Historie | +| **Status** | Arbeitsunterlage, mit KI-Unterstützung (Claude Code) erstellt und von Oliver geprüft. Kein Bestandteil des Upstream-Repositories; der Maintainer war an der Erstellung nicht beteiligt. | + +**Lesehinweis zur Verlässlichkeit.** Aussagen sind in drei Stufen markiert: +- **[Code]**: direkt aus dem Quelltext gelesen, mit Datei und Zeile. +- **[Doku]**: steht so im README oder in `docs/`. +- **[Schluss]**: von mir abgeleitet (Absicht, Bewertung). Das ist keine Aussage des Autors. + +Nichts davon wurde gegen einen laufenden Charger geprüft, außer wo ausdrücklich vermerkt. + +--- + +## 1. Einführung und Ziele + +### 1.1 Aufgabenstellung +Die Integration bindet go-eCharger-Wallboxen (und Wallboxen auf deren Hardwarebasis, zum Beispiel Fronius Wattpilot) sowie den go-eController in Home Assistant (HA) ein [Doku]. + +Sie liefert: +- **Lesen:** Status- und Konfigurationswerte als HA-Entitäten. +- **Schreiben:** Einstellungen der Wallbox aus HA heraus. +- **PV-Überschussladen ohne Zusatzhardware:** Eine HA-Automation schickt Netz-, PV- und Batterieleistung über einen Service an die Wallbox [Doku]. + +Umfang laut Code [Code, `const.py`]: + +| Plattform | Charger | Controller | +|---|---|---| +| sensor | 103 | 74 | +| number | 36 | 0 | +| switch | 18 | 10 | +| binary_sensor | 16 | 0 | +| select | 15 | 6 | +| button | 3 | 1 | + +Dazu 319 definierte API-Schlüssel (`Tag`) in `pygoecharger_ha/keys.py`. Die Zahlen sind gezählte `Ext*EntityDescription(`-Aufrufe und können durch bedingte Filterung (Abschnitt 5.2) zur Laufzeit niedriger liegen. + +### 1.2 Qualitätsziele + +| Prio | Ziel | Bedeutung hier | +|---|---|---| +| 1 | **Robustheit gegenüber Geräteverhalten** | Firmware ändert sich (55 bis 60.x), Verbindungen brechen ab, Wallboxen antworten unterschiedlich. Die Integration soll weiterlaufen. | +| 2 | **Aktualität bei geringer Last** | Push per WebSocket statt Polling; im Polling-Betrieb bewusst gedrosselt (Hibernation-Modus). | +| 3 | **Geringer Konfigurationsaufwand** | Alles über die HA-Oberfläche; Entitäten werden aus Beschreibungen erzeugt. | +| 4 | **Sicherer Betrieb der Steuerung** | Schreibzugriffe wirken auf ein physisches Gerät (Stromfluss, Fahrzeug). | + +[Schluss] Die Reihenfolge ist aus dem Code abgeleitet (viel Aufwand bei Reconnect, Watchdog, Firmware-Weichen, wenig bei Tests und Eingabevalidierung). + +### 1.3 Stakeholder + +| Rolle | Erwartung | +|---|---| +| Endnutzer (HA-Betreiber) | Funktioniert nach Firmware-Updates, kein Log-Spam, PV-Laden stabil | +| Maintainer (marq24, s3ppo für den Controller) | Wartbar mit wenig Freizeit | +| Beitragende (zum Beispiel MrCodeLabs mit dem Blueprint) | Klare Erweiterungspunkte | +| go-e GmbH | Nicht beteiligt. Die Integration ist inoffiziell [Doku]. | + +--- + +## 2. Randbedingungen + +| Typ | Randbedingung | +|---|---| +| Plattform | Home Assistant, `hacs.json` verlangt mindestens `2023.8.0`; `requirements_dev.txt` zieht `homeassistant>=2024.8.2`. Der Code nutzt neuere APIs (zum Beispiel `async_update_reload_and_abort`, `_get_reconfigure_entry`). Die tatsächlich unterstützte Mindestversion ist nicht eindeutig [Schluss]. | +| Sprache/Laufzeit | Python, asynchron (`asyncio`, `aiohttp`) | +| Abhängigkeiten | `msgpack>=1.1.2` (manifest); im Code zusätzlich `bcrypt`, `packaging`, `awesomeversion`, `aiohttp` (kommen aus HA oder werden vorausgesetzt). `bcrypt` steht **nicht** im Manifest [Code]. | +| Geräte-API | Undokumentierte WebSocket-Schnittstelle (nachgebaut) und dokumentierte HTTP-API v2 der go-e GmbH | +| Verteilung | HACS (Zip-Release); Lizenz siehe `LICENSE` | +| Organisation | Ein Hauptautor, Freizeitprojekt. Keine automatisierten Tests im Upstream-Repository (die Tests aus PR #171 wurden wieder entfernt). | +| Gerätegrenzen | Die Wallbox verwirft PV-Daten nach 5 Sekunden [Doku]. Die Konfiguration wird im Polling nur selten gelesen. | + +--- + +## 3. Kontextabgrenzung + +### 3.1 Fachlicher Kontext + +```mermaid +flowchart LR + User[Nutzer / Dashboard] + Auto[HA-Automation
oder Blueprint] + HA[Home Assistant] + INT[Integration
goecharger_api2] + WB[go-eCharger / go-eController
LAN] + Cloud[go-e Cloud
api.v3.go-e.io / app.v3.go-e.io] + PV[PV-, Netz-, Batterie-Sensoren
anderer Integrationen] + + User --> HA + PV --> Auto + Auto -- "goecharger_api2.set_pv_data (alle 5 s)" --> INT + Auto -- "goecharger_api2.stop_charging" --> INT + HA --- INT + INT -- "HTTP /api/status, /api/set
oder WebSocket /ws" --> WB + INT -- "HTTPS + Bearer-Token
oder WSS" --> Cloud + Cloud --- WB +``` + +### 3.2 Technischer Kontext (Schnittstellen) + +| Kanal | Richtung | Protokoll | Authentifizierung | +|---|---|---|---| +| LAN-Polling | HA → Wallbox | `GET http(s):///api/status?filter=…`, `GET …/api/set?key=value` | keine (Freischaltung „HTTP API v2“ in der go-e-App) | +| LAN-WebSocket | HA ↔ Wallbox | `ws(s):///ws`, JSON/MessagePack | Challenge-Response mit PBKDF2-SHA512 oder bcrypt, `securedMsg` mit HMAC-SHA256 | +| Cloud-Polling | HA → Cloud | `GET https://.api.v3.go-e.io/api/status`, `/api/set` | `Authorization: Bearer ` | +| Cloud-WebSocket | HA ↔ Cloud | `wss://app.v3.go-e.io/` | Token-Header plus Wallbox-Passwort-Challenge | +| HA-Dienste | Automation → Integration | `goecharger_api2.set_pv_data`, `goecharger_api2.stop_charging` | HA-Berechtigungen | +| Konfiguration | Nutzer → HA | Config-Flow (Anlegen, Rekonfigurieren) | HA-Login | + +[Code] Betriebsart wird **nicht** ausgewählt, sondern ergibt sich aus der Konfiguration: Ist im Config-Entry ein Passwort hinterlegt, nutzt die Integration WebSocket, sonst HTTP-Polling (`__init__.py:123`ff, `pygoecharger_ha/__init__.py:151`ff). + +### 3.3 Offizielle Schnittstellendokumentation der go-e GmbH + +Quelle: GitHub-Repository [`goecharger/go-eCharger-API-v2`](https://github.com/goecharger/go-eCharger-API-v2), Stand Commit `b4d7f85` vom 2026-08-12. Status der go-e-Dienste: https://status.go-e.com/status/go-e. Eine lokale Kopie der gelesenen Dateien liegt in `go-e-api-docs-b4d7f85/`. + +| Dokument | Inhalt | Von der Integration genutzt? | +|---|---|---| +| `API_KEYS_FIRMWARE/apikeys-en.md` | Alle API-Schlüssel mit Lesen/Schreiben, Typ, Kategorie, Beschreibung (rund 400 KB). Zusätzlich eigene Listen für Firmware 59.4, 60.2, 60.3 und 60.4 | Ja, Grundlage von `keys.py` und den Filtern | +| `http-en.md` | Lokale HTTP-API: `/api/status` (optional `filter=`), `/api/set` | Ja (Polling) | +| `cloudapi-en.md` | Cloud-HTTP-API über `https://.api.v3.go-e.io` mit Token | Ja (Cloud-Polling) | +| `mqtt-en.md` | MQTT, ab Firmware 051.5 Schreiben nur nach ausdrücklicher Freigabe | Nein | +| `modbus-en.md`, `ocpp-en.md` | Modbus und OCPP | Nein | +| `gridapi-en.md` | „Coming soon“, ohne Inhalt | Nein | +| `Changelogs_Gemini/`, `Changelogs_PRO/` | Firmware-Änderungen 60.3 bis 60.5 | Nur als Hintergrund | + +**Wichtig:** Eine **WebSocket-Schnittstelle ist in der offiziellen Dokumentation nicht beschrieben.** Das README der Integration nennt sie „undocumented“. Anmeldung und Nachrichtenformat sind nachgebaut (Beobachtung der App und des Wattpilot-Projekts, siehe Kommentare in `ws_connect` und `_ws_compute_hashed_password`). Damit liegt der bevorzugte Datenpfad außerhalb dessen, was go-e zusichert (Abschnitt 11, R16). + +**Was die offizielle Doku für die Integration festlegt** + +| Thema | Offizielle Aussage | Bezug zur Integration | +|---|---|---| +| Aktivierung lokal | HTTP-API muss in der App aktiviert sein (Schlüssel `hai`) | README und Config-Flow verlangen das, falls kein Passwort für WebSocket genutzt wird | +| Gesamtabzug | `/api/status` ohne Filter: „higher system load, lots of traffic, shouldn't be used for productive setups“ | Integration nutzt immer `filter=…` (Abschnitt 6.3), passt dazu | +| Filter | Komma-getrennte Schlüssel, ab Firmware 051.4. Davor eine JSON-Liste mit rund 10 Einträgen | Integration verwendet nur die neue Form; das README fordert Firmware ab 56.1 | +| Schreiben | Jeder Wert als URL-Parameter, JSON-kodiert (Zeichenketten in Anführungszeichen). Antwort: je Schlüssel `true` oder eine Fehlermeldung | Entspricht `_write_values_int` (`'"'+str(value)+'"'`) | +| Rechenlogik | Die Lade-Logik läuft einmal pro Sekunde, geänderte Einstellungen wirken nach etwa einer weiteren Sekunde | Erklärt, warum Schreiben und Wirkung zeitversetzt erscheinen | +| PV-Daten `ids` | `{"pGrid", "pPv", "pAkku"}`; `pGrid < 0` Einspeisung, `pAkku < 0` Batterie lädt, `pPv > 0` Erzeugung, `pPv < 0` Standby. „Needed all 5 seconds. Can be read back within 10 seconds after set.“ `pPv` und `pAkku` optional | Entspricht dem Dienst `set_pv_data`; die Oberfläche begrenzt `ppv` auf Werte ab 0 (`services.yaml`), obwohl negative Werte laut Doku „Standby“ bedeuten | +| Cloud-Aktivierung | Firmware ab 052, App ab 2.3; Schlüssel `cae` oder App-Schalter | Integration liest `cae` im Config-Flow | +| Cloud-Token | In der App sichtbar oder über den Schlüssel `cak` lesbar; neuer Token durch Setzen von `cak` | Integration liest `cak` nicht, speichert aber den Token im Config-Entry | +| Cloud-Rate-Limit | Keins, aber nicht öfter als einmal pro Sekunde abfragen | Integration: Cloud-Intervall mindestens 30 s | +| Cloud `/api/status` | 403 offline oder Cloud-API aus; 404 keine aktuellen Daten; 200 mit Daten, Header `last-modified`. Parameter `age` (Standard 30 s), `filter` | Integration wertet nur 200 und 400 aus und nutzt `age` nicht | +| Cloud `/api/set` | 403, 404 (Charger offline), 422 (alle Schlüssel abgelehnt), **202** (mindestens ein Schlüssel gesendet, ohne Garantie) | Integration behandelt in `_write_values_int` nur 200 und 500 als Antwort (Abschnitt 11, R15) | + +--- + +## 4. Lösungsstrategie + +| Ziel | Ansatz | Wo | +|---|---|---| +| Robustheit | Pro Config-Entry ein `DataUpdateCoordinator`; Fehlerzähler mit 2 Minuten Sperre; nach mehr als 5 Verbindungsfehlern verzögerter Neustart (1 bis 5 Minuten zufällig); WebSocket-Watchdog alle 5 Minuten 1 Sekunde | `__init__.py` | +| Aktualität bei geringer Last | WebSocket-Push mit Entprellung (mindestens 1 Sekunde zwischen HA-Updates, sonst Scan-Intervall); im Polling: „Hibernation“ (nur 6 Schlüssel, solange kein Fahrzeug steckt), Konfiguration höchstens stündlich | `pygoecharger_ha/__init__.py`, `const.py` (Filter) | +| Wenig Code pro Entität | Deklarative Entity-Descriptions in `const.py`; je Plattform eine generische Klasse, die über `key`, `idx`, `tuple_idx`, `lookup`, `factor` den Wert aus dem Datenspeicher liest | `const.py`, `sensor.py`, `number.py`, … | +| Firmware-Vielfalt | Verzweigungen nach Gerätetyp (Core/Phoenix gegenüber klassisch), Firmware-Version (`cards`-Objekt gegenüber Einzelschlüsseln), vorhandenen Schlüsseln | `read_versions()`, `is_valid_charger_entity()` | +| PV-Überschuss ohne Hardware | Service `set_pv_data` schreibt den Schlüssel `ids` mit `pGrid`, `pPv`, `pAkku`; der Blueprint liefert die Messwerte | `service.py`, `example/blueprint/` | +| Trennung | Geräte-Protokoll in der Teilbibliothek `pygoecharger_ha` (kennt Home Assistant nicht, nutzt aber `aiohttp`, `msgpack`, `bcrypt`) | `pygoecharger_ha/` | + +--- + +## 5. Bausteinsicht + +### 5.1 Ebene 1: Gesamtsystem + +```mermaid +flowchart TB + subgraph Integration["custom_components/goecharger_api2"] + INIT["__init__.py
Setup, Coordinator, Basisentität"] + CF["config_flow.py
Anlegen, Rekonfigurieren, Verbindungstest"] + SVC["service.py
set_pv_data, stop_charging"] + PLAT["Plattformen
sensor, number, select,
switch, button, binary_sensor"] + CONST["const.py
Entity-Beschreibungen"] + ENT["entity.py
Anzeigename-Anpassung"] + subgraph Lib["pygoecharger_ha"] + BR["__init__.py
Bridge: HTTP + WebSocket"] + KEYS["keys.py
319 API-Schlüssel (Tag)"] + PC["const.py
Filter, Übersetzungen, Enums"] + end + end + HAcore[Home Assistant Core] + INIT --> BR + INIT --> ENT + PLAT --> INIT + PLAT --> CONST + SVC --> INIT + CF --> BR + BR --> KEYS + BR --> PC + HAcore --> INIT + HAcore --> CF +``` + +### 5.2 Ebene 2: Bausteine + +| Baustein | Verantwortung | Wichtige Punkte | +|---|---|---| +| `GoeChargerDataUpdateCoordinator` (`__init__.py`) | Hält Bridge und Datenzustand je Config-Entry; Polling-Zyklus; Schreibzugriffe; Fehlerbehandlung; WebSocket-Start und Watchdog; Gerätemerkmale | `data` ist eine `ChainMap(_ws_states, _config, _states, _versions)`. Verbindungsfehler gehen über `_handle_client_connection_error`. | +| `GoeChargerApiV2Bridge` (`pygoecharger_ha/__init__.py`) | Wandelt zwischen Gerät und Dictionary: Lesen mit Filter, Schreiben, WebSocket-Handshake und -Nachrichten | Wählt Pfad über `ws_connected`. Schreiben im WebSocket-Modus: sendet den Befehl und gibt `{key: value}` zurück (Einzelschreiben) oder `{}` (Mehrfachschreiben), **bevor** das Gerät bestätigt hat; die Bestätigung kommt später als `response`. Im HTTP-Modus: Ergebnis `{key: value}` oder `{"err": …}`. | +| `GoeChargerBaseEntity` (`__init__.py`) | Gemeinsame Basis: Übersetzungsschlüssel, Entity-ID, `unique_id`, `device_info`, Verfügbarkeit | Entity-ID `…goe__` (LAN) oder `…goe_wan__…` (Cloud). | +| `CustomFriendlyNameEntity` (`entity.py`) | Überschreibt den Anzeigenamen ab HA 2026.2 | Greift auf die private Methode `_Entity__async_calculate_state` zu, also fragil bei HA-Updates [Schluss]. | +| Plattformen | Legen Entitäten aus den Beschreibungslisten an; filtern über `is_valid_charger_entity()` | Wertzugriff mit `idx` (Listen), `tuple_idx` (verschachtelt), `lookup` (Zahl→Text), `factor`, `differential_base_key`. | +| `service.py` | Zwei HA-Dienste | `set_pv_data`: Netzleistung Pflicht, PV und Batterie optional. `stop_charging`: setzt `frc=1`, wartet 300 s, setzt `frc=0`. Ein Register der Charger (`add_config_entry_to_service`, `remove_config_entry_from_service`) bildet `configid` auf den Koordinator ab; `get_coordinator` lehnt unbekannte oder deaktivierte Entries mit `ServiceValidationError` ab. | +| `config_flow.py` | Assistent LAN/Cloud, Verbindungstest über WebSocket oder HTTP, Rekonfiguration | Titel und Typ aus den gelesenen Gerätedaten. | +| `const.py` / `pygoecharger_ha/const.py` | Entity-Beschreibungen; API-Filter (welche Schlüssel für welchen Zyklus); Übersetzungen; Enums (`CAR_VALUES`, `CT_VALUES`) | `FILTER_*`-Zeichenketten bestimmen den Datenumfang je Zyklus. | +| `example/blueprint/…` | Blueprint für den PV-Überschuss | Eigene Qualitätssicherung im Blueprint (Messwertprüfung, Zielcharger) seit PR #171. | + +### 5.3 Zustand und Datenmodell (zentral) + +| Speicher | Inhalt | Wird gefüllt durch | +|---|---|---| +| `_versions` | System- und Versionsdaten (`typ`, `fwv`, `cards`, `cll`, …) | `read_versions()` | +| `_states` | Statuswerte | Polling (`FILTER_ALL_STATES`, im Leerlauf `FILTER_MIN_STATES`) | +| `_config` | Konfigurationswerte | Polling höchstens stündlich (`FILTER_ALL_CONFIG`) | +| `_ws_states` | Alles, was per WebSocket ankommt (Vollstatus, Delta, Antworten) | WebSocket-Nachrichten | + +Die Reihenfolge in der `ChainMap` gibt WebSocket-Werten Vorrang. [Doku] Das README nennt für die Konfiguration „alle 24 Stunden“; im Code steht eine Stunde (`__init__.py`/Bridge `read_all`: `+ 3600`). Die Dokumentation und der Code weichen also voneinander ab. + +--- + +## 6. Laufzeitsicht + +### 6.1 Start (Setup) + +```mermaid +sequenceDiagram + participant HA as HA Core + participant I as async_setup_entry + participant C as Coordinator + participant B as Bridge + participant W as Wallbox + HA->>I: Config-Entry laden + I->>C: Coordinator anlegen + alt Passwort im Entry + I->>C: start_websocket_and_wait_for_first_data() + C->>B: ws_connect() als Hintergrundtask + B->>W: Hello / Auth / Vollstatus + B-->>C: Pflicht-Tags vorhanden (max. 60 s) + C->>C: read_versions() + else Kein Passwort + I->>C: async_refresh() (HTTP) + I->>C: read_versions() + end + I->>HA: Plattformen laden + I->>HA: Services anmelden (nur beim ersten Charger) + opt Limit 16 A + I->>C: Hintergrundtask check_for_16a_limit() + end + opt WebSocket + I->>C: Watchdog starten + end +``` + +Fehler beim Start führen zu `ConfigEntryNotReady`; HA wiederholt das später selbst. + +### 6.2 WebSocket-Verbindung und Anmeldung [Code, `ws_connect`] + +1. Verbinden mit `ws_url` (und im Cloud-Fall mit dem Bearer-Token). +2. `hello` empfangen: Seriennummer, Firmware, ob gesichert. +3. `authRequired` empfangen: Hash-Typ (`pbkdf2` oder `bcrypt`; bei fehlender Angabe nach Gerätetyp gewählt), `token1`, `token2`. +4. Gehashtes Passwort berechnen, `token3` erzeugen, Antwort-Hash senden (SHA-256 über `token3 + token2 + SHA256(token1 + Passwort-Hash)`). +5. Ergebnis innerhalb von 10 Sekunden abwarten. Bei Erfolg `ws_connected = True`. +6. Nachrichtenschleife: `fullStatus` (voll oder teilweise), `deltaStatus` (ohne die Schlüssel in `API_KEYS_TO_IGNORE_FROM_WS`), `response` (Antwort auf Schreibbefehl). +7. Jede neue Nachricht stößt eine entprellte Aktualisierung des Koordinators an. + +Schreibbefehle gehen als `setValue` mit fortlaufender `requestId`. Im lokalen gesicherten Modus wird die Nachricht als `securedMsg` mit HMAC verpackt. + +### 6.3 Polling-Zyklus (ohne WebSocket) + +- Intervall: Scan-Intervall des Config-Entry (LAN Standard 30 s im Dialog, Minimum 5 s; Cloud mindestens 30 s, vorgeschlagen 120 s). +- Ist ein Fahrzeug nicht angeschlossen (`car == 1`), werden nur die Schlüssel `car, modelStatus, err, nrg, tma, trx` (plus Zeitwerte und, nach PV-Daten, `pakku, ppv, pgrid`) gelesen, ein Vollabzug alle 5 Minuten. +- Sobald `car` vom Leerlauf abweicht, folgt sofort ein Vollabzug. +- Konfiguration: höchstens stündlich. Misslingt das Lesen, wird nach 5 Minuten neu versucht. + +### 6.4 Schreiben einer Einstellung + +1. Entität ruft `coordinator.async_write_key(key, value, entity)`. +2. Der Koordinator prüft Sperre nach Fehlern (2 Minuten) und Neustartzustand und wirft sonst `ValueError`. +3. Bridge: WebSocket → senden und sofort `{key: value}` zurückgeben (Mehrfachschreiben: `{}`); die Bestätigung des Geräts kommt später als `response`. HTTP → `GET /api/set`, Ergebnis `{key: value}` oder `{"err": …}`. +4. `handle_write_result()` übernimmt den **gewünschten** Wert sofort in die Daten (auch im WebSocket-Modus) und stößt eine Entitätsaktualisierung an. Lehnt der Charger den Befehl ab, bleibt der Wert, bis das Gerät den Schlüssel erneut meldet (R26). + +### 6.5 PV-Überschuss (`set_pv_data`) + +1. Blueprint ruft alle 5 Sekunden den Dienst mit `pgrid`, `ppv`, `pakku` auf (und optional `configid`). +2. Cloud-Betrieb: Ist kein Fahrzeug verbunden (`car < 2`), wird **nichts** gesendet (Last-Schutz). +3. Der Dienst schreibt den Schlüssel `ids` mit `{"pGrid", "pPv", "pAkku"}`. +4. Im HTTP-Betrieb setzt die Bridge danach ein Flag, damit im Leerlauf zusätzlich `pakku, ppv, pgrid` gelesen werden. +5. Sendet die Automation nicht mehr, hält die Wallbox 5 Sekunden lang alte Werte [Doku]. Dass danach die gleitenden Mittelwerte der Wallbox (`pvopt_average…`) stehen bleiben, war unsere Annahme bei der Blueprint-Entwicklung und ist am Gerät nicht geprüft. Das Risiko (ungewolltes Netzladen) deckt das Blueprint durch sein Verhalten bei ungültigen Messwerten ab (Standard: Nullwerte senden). + +### 6.6 Zwangs-Stopp (`stop_charging`) + +`frc=1` (Laden aus), 300 s warten, `frc=0`. Eine zweite Anfrage während der Wartezeit wird abgewiesen (`_stop_in_progress`). Seit Version 2026.10.8 läuft der Ablauf auch im WebSocket-Modus durch (der frühere `TypeError` ist behoben). Das Flag wird allerdings bei einem Schreibfehler nicht zurückgesetzt: Schlägt `frc=1` oder nach der Wartezeit `frc=0` fehl, sind weitere Stopp-Anfragen bis zum Neuanlegen des Dienstes gesperrt (R1, Rest). + +### 6.7 Fehlerbehandlung und Neustart + +- `ClientConnectionError`: Zeitstempel und Zähler setzen, 2 Minuten lang keine Anfragen, Daten bleiben stehen. +- Mehr als 5 Fehler in Folge: nach 1 bis 5 Minuten Zufallswartezeit wird der Config-Entry neu geladen. +- WebSocket-Watchdog (alle 5 min 1 s): verbindet neu, wenn nicht verbunden; bricht ab und baut neu auf, wenn seit mehr als 50 Sekunden keine Daten kamen. +- `force_async_update_now()`: erzwingt WebSocket-Neustart, aber nicht in den ersten 5 Minuten nach Start und nicht in den ersten 2 Minuten nach einer frischen Verbindung. + +--- + +## 7. Verteilungssicht + +```mermaid +flowchart LR + subgraph GH[GitHub] + Repo[Repo main] + CI["Workflows:
HACS Action, hassfest,
Zip Release"] + Rel[Release: goecharger_api2.zip] + end + HACS[HACS im HA] + subgraph Host["Home-Assistant-Host"] + CC["config/custom_components/goecharger_api2"] + Store[".storage/core.config_entries
(Zugangsdaten im Klartext)"] + end + Dev["go-e Wallbox im LAN"] + Repo --> CI --> Rel --> HACS --> CC + CC --- Store + CC --> Dev +``` + +- **Build:** Beim Veröffentlichen eines Releases zippt der Workflow den Ordner `custom_components/goecharger_api2` [Code, `release.yaml`]. +- **CI:** HACS Action und hassfest laufen bei Push, Pull Request und täglich. Es gibt keine Testausführung [Code]. +- **Betrieb:** Läuft im HA-Prozess, keine eigenen Dienste. Zugangsdaten (Passwort, Cloud-Token) liegen im Config-Entry von HA. + +--- + +## 8. Querschnittliche Konzepte + +### 8.1 Konfiguration und Migration +- Config-Flow-Version 2.1. Daten in `config_entry.data`, `options` bleiben leer. +- Migration von Version 1 nach 2 führt `options` in `data` zusammen. Migration von 2.0 löscht alle Geräte des Entries, damit sie mit neuen Identifiern neu entstehen [Code, `async_migrate_entry`]. +- Gerät-Identifier sind `(DOMAIN, Typ, Modus, Seriennummer)`. Seit `8cb62b2` enthält die Geräteinfo keinen Token mehr. + +### 8.2 Entitäten und Namen +- Übersetzungsschlüssel aus API-Schlüssel plus Index oder Zusatz, `unique_id` = `goecharger_api2.`. +- Übersetzungen in `translations/{de,en,fr}.json`; zusätzlich Wert-Texte (zum Beispiel `car`) in `pygoecharger_ha/const.py` für Deutsch und Englisch. + +### 8.3 Geräte- und Firmware-Varianten +- Core/Phoenix/Pro gegenüber klassisch: unterschiedliche Temperatursensoren (`tma` Index 2 bis 5 gegenüber 0 und 1), einzelne nicht unterstützte Entitäten, Core-exklusives `iso2ena`, anderer Hash-Typ (bcrypt). +- 16-A-Begrenzung: aus Kabelgrenze (`cll.cableCurrentLimit`), Adapter (`adi`), Variante (`var == 11`) oder Option; setzt `native_max_value` auf 16 und schreibt beim Start zu hohe Werte zurück. Seit `98af3e0` gilt: Hat `cll` Inhalt, aber keinen `cableCurrentLimit`, wird **keine** Grenze gesetzt und `adi` nicht herangezogen (Annahme des Autors: nur Flex-Geräte liefern diesen Schlüssel; Anlass war Issue #176). Davor galt bei Core-Geräten ohne diesen Schlüssel vorsichtshalber 16 A (R29). +- `cards` als Objekt oder als 30 Einzelschlüssel (Firmware 60.0). + +### 8.4 Fehler- und Logstrategie +- Fehler in Bridge-Methoden werden meist als Warnung geloggt und als leeres Ergebnis zurückgegeben; Schreibfehler als `ValueError` an die aufrufende Entität. +- Viele breite `except BaseException`: 13 Stellen in `custom_components/` [Code, gezählt]. Sie fangen auch `CancelledError` und `KeyboardInterrupt`. + +### 8.5 Sicherheit +- **Zugangsdaten:** Passwort und Cloud-Token liegen im HA-Config-Entry im Klartext (HA stellt keinen Tresor bereit). Weitere Beobachtungen zu Zugangsdaten wurden dem Maintainer direkt mitgeteilt und sind hier bewusst nicht aufgeführt. +- **Vertrauensgrenze:** Das LAN-HTTP-API hat keine Authentifizierung; Schutz liegt im Netz. + +### 8.6 Nebenläufigkeit +- Ein Event-Loop; Schreibbefehle über WebSocket warten je 0,75 s nacheinander bei Mehrfachschreiben. +- Globale Variable `SCAN_INTERVAL` wird im Koordinator-Konstruktor überschrieben (gilt prozessweit, also für alle Config-Entries) [Code, `__init__.py:331`]. + +### 8.7 Test und Qualitätssicherung +- **Im Repo (Upstream):** HACS-Validierung und hassfest. Keine Unit-, Integrations- oder Blueprint-Tests (gelöscht in `60d9a09`). +- **Regressionstests im Fork (Branch `tests/regression-suite`):** 987 Tests (420 Vertragsprüfungen je Schlüssel, 567 Verhaltens- und Fehlertests; 958 blockierend, 29 beabsichtigt rot), Zeilenabdeckung 93 %, Zweigabdeckung 85 %, Pfadabdeckung (obere Schranke) 61 %. Messberichte liegen im Fork unter `tests/reports/`; ein Workflow prüft jede neue Upstream-Version (siehe Testkonzept). +- **Testkonzept:** `docs/quality/testkonzept-ha-goecharger-api2.md` beschreibt Umfang, Stufen, Marker, Stand und Lücken. +- **Vertrag zur offiziellen API:** Im Fork-Branch prüft `test_api_contract.py`, ob jeder dokumentierte Schlüssel unterstützt oder erklärt ist (92 dokumentierte Schlüssel ohne Tag, 38 genutzte Schlüssel ohne Doku, 14 Abweichungen bei Schreibrecht und Kategorie). Die Listen stehen in `tests/integration/contract/` (`classification.json`, `deviations.json`). +- **Lokal (nicht im Repo):** Unser Fork enthält Blueprint-Tests (240 Fälle) und Integrationstests (12 Fälle) als Arbeitsmittel (`OliEder/ha-goecharger-api2`, Branches `proposal/invalid-data-policy` und `fix/small-bugs`). + +--- + +## 9. Architekturentscheidungen + +Der Autor führt keine Entscheidungsprotokolle. Die folgenden Einträge sind aus Code und Kommentaren rekonstruiert [Schluss]. + +| # | Entscheidung | Begründung (abgeleitet) | Konsequenz | +|---|---|---|---| +| E1 | WebSocket bevorzugen, Polling als Rückfall | Effizienz; einige Geräte (Wattpilot) bieten kein HTTP-API [Doku] | Zwei Datenpfade mit unterschiedlichem Schreibergebnis (`None` gegenüber Dictionary) | +| E2 | Betriebsart = Passwort gesetzt | Einfache Einrichtung | Kein expliziter Schalter; Umschalten nur über Rekonfiguration | +| E3 | Eine Bridge-Teilbibliothek im selben Ordner | Trennung von Protokoll und HA | Nicht separat veröffentlicht; Abhängigkeiten nicht im Manifest vollständig | +| E4 | Deklarative Entity-Beschreibungen | Hunderte Entitäten mit wenig Code | Sehr große `const.py` (3300 Zeilen); Logik versteckt in Optionen (`idx`, `tuple_idx`) | +| E5 | Eigene `ChainMap`-Datenhaltung statt Typmodell | Schnelle Anpassung an neue Schlüssel | Kein Schema; Fehler zeigen sich erst zur Laufzeit (`KeyError` werden abgefangen) | +| E6 | Hibernation im Polling | Geräte- und Cloud-Last senken | Im Leerlauf nur wenige Werte aktuell | +| E7 | PV-Daten über HA-Automation statt Zusatzhardware | Kein Zusatzgerät nötig | Qualität liegt im Blueprint und in den Messwerten des Nutzers | +| E8 | Neustart per Konfigurations-Reload bei dauerhaften Fehlern | Hängende Verbindung selbst heilen | Reload aller Entitäten für ein Verbindungsproblem | +| E9 | Keine automatisierten Tests im Repo | Aufwand/Haltung des Autors | Regressionen fallen erst bei Nutzern auf | + +--- + +## 10. Qualitätsanforderungen + +### 10.1 Qualitätsbaum + +``` +Qualität +├── Zuverlässigkeit +│ ├── Wiederverbinden (Watchdog, Reload) +│ ├── Firmware-Toleranz (Weichen nach Gerätetyp/Version) +│ └── Verhalten bei Teilausfall (Daten stehen lassen) +├── Effizienz +│ ├── Push statt Poll +│ └── Hibernation, gestaffelte Lesezyklen +├── Wartbarkeit +│ ├── Deklarative Entitäten +│ └── (offen) Tests, Typisierung +├── Sicherheit +│ ├── Zugangsdaten +│ └── Sichere Schreibpfade (Stop-Funktion) +└── Bedienbarkeit + └── Einrichtung über Dialog, Übersetzungen +``` + +### 10.2 Qualitätsszenarien + +| ID | Szenario | Erwartung | Heutiger Stand | +|---|---|---|---| +| Q1 | Wallbox ist 10 Minuten im WLAN weg | Keine Dauerlast; automatische Erholung | Fehlerzähler, 2 min Sperre, nach über 5 Fehlern Reload [Code] | +| Q2 | Firmware-Update ändert Schlüssel (60.0) | Integration läuft weiter | Weichen für `cards` und `cll` vorhanden [Code] | +| Q3 | PV-Automation fällt aus | Kein ungewolltes Netzladen | Liegt im Blueprint; die Integration prüft Eingaben nur auf Zahl und Endlichkeit (`ppv`, `pakku`) | +| Q4 | `stop_charging` im WebSocket-Modus | Laden aus für 5 min, dann wieder frei | **Nicht erfüllt:** `TypeError`, `frc=0` wird nie gesendet (siehe 11) | +| Q5 | Zwei Charger in einer HA-Instanz | Dienst wirkt auf den gewählten Charger | **Fraglich** (siehe 11, R2) | +| Q6 | Nutzer ohne Admin-Rechte | Keine Zugangsdaten lesbar | Der Token steht seit `8cb62b2` nicht mehr in der Geräteinfo. Passwort und Token liegen weiter im Config-Entry (HA-Eigenschaft). | + +--- + +## 11. Risiken und technische Schulden + +Nummerierung nur für diese Liste. Status in der Spalte „Beleg“. + +| # | Befund | Wirkung | Beleg | +|---|---|---|---| +| R1 | `stop_charging` im WebSocket-Modus: `write_value_to_key` liefert `None`, danach `Tag.FRC.key in resp` wirft `TypeError`; abgefangen wird nur `ValueError`. `frc=1` ist gesetzt, `frc=0` wird nie gesendet, `_stop_in_progress` bleibt `True`. | Charger bleibt gesperrt, bis jemand `frc` von Hand zurücksetzt. | Im Test nachgespielt. **Nicht** an echtem Gerät geprüft. Noch in `main`. | +| R2 | Multi-Charger: Das Register `_COORDINATOR_PER_CONFIGENTRYID_MAP` wird nur im Konstruktor von `GoeChargerApiV2Service` befüllt. Der Konstruktor läuft nur, wenn der Dienst noch nicht angemeldet ist, also nur für den ersten Charger. Ein zweiter Charger fehlt im Register, `configid=` führt zu `KeyError`. Wird der erste Entry entladen, bleibt dessen Koordinator im Register. | Der dokumentierte Multi-Charger-Betrieb über `configid` funktioniert für den zweiten Charger nicht. | Aus Code gelesen (`service.py:17,33,41`, `__init__.py:153`ff). **Nicht nachgespielt.** | +| R3 | `async_unload_entry`: `ACTIVE_CHARGER_COUNTER` wird nur für Charger erhöht, aber für jeden Entry verringert und danach ohne Absicherung gelesen. Bei einem reinen Controller-Entry fehlt der Schlüssel. | Mögliche `KeyError` beim Entladen oder falscher Zählerstand bei gemischten Entries. | Aus Code gelesen (`__init__.py:148`ff, `194`ff). **Nicht nachgespielt.** | +| R4 | `async_write_multiple_keys`: `_handle_client_connection_error` wird mit einem Argument statt zwei aufgerufen (`__init__.py:674`). | `TypeError` statt sauberer Fehlerbehandlung bei Verbindungsabbruch während des Schreibens (betrifft die Fahrzeugauswahl `ct`). | Im Test nachgespielt. Noch in `main`. | +| R5 | `set_pv_data` prüft `pgrid` nur mit `isinstance(Number)` (`service.py:59`), `ppv` und `pakku` jedoch auf Endlichkeit. | `NaN` oder `inf` als `pgrid` kann als ungültiges JSON zum Charger gehen. | Aus Code gelesen. Geringe Praxisrelevanz, da das Blueprint prüft. | +| R6 | `_abort_if_unique_id_configured()` wird aufgerufen, aber im Config-Flow wird nirgends eine `unique_id` gesetzt (`async_set_unique_id` kommt nicht vor). | Kein Schutz gegen doppelt angelegte Geräte. | Aus Code gelesen (grep). Wirkung nicht vorgeführt. | +| R7 | Dokumentation und Code weichen ab: README „Konfiguration alle 24 Stunden“, Code stündlich (`+ 3600`). | Falsche Erwartung bei Nutzern. | Aus Code und README gelesen. | +| R8 | Zugangsdaten liegen im Klartext im Config-Entry (Eigenschaft von Home Assistant). Weitere Einzelheiten sind bewusst nicht aufgeführt. | – | – | +| R9 | `SCAN_INTERVAL` ist eine globale Variable, die im Konstruktor überschrieben wird. | Bei mehreren Entries gilt der zuletzt gesetzte Wert für den Standard. Der Koordinator nutzt das Intervall allerdings über `update_interval=SCAN_INTERVAL` im Aufruf direkt danach, daher meist harmlos. | Aus Code gelesen. | +| R10 | `check_and_write_to_16a`: Funktion mit `@staticmethod` auf Modulebene, außerdem privater Zugriff auf `bridge._read_filtered_data`. | Wartbarkeit. | Aus Code gelesen. | +| R11 | Fehlende Abhängigkeit im Manifest (`bcrypt`), Mindestversion von HA unklar (`hacs.json` 2023.8.0 gegenüber genutzten neueren APIs). | Installationsfehler auf älteren Systemen möglich. | Aus Code und Manifest gelesen. Ob `bcrypt` über HA mitgeliefert wird, habe ich nicht geprüft. | +| R12 | `CustomFriendlyNameEntity` überschreibt eine private HA-Methode. | Bricht bei HA-Änderungen. | Aus Code gelesen. | +| R13 | Keine Tests im Repo. | Regressionen werden erst von Nutzern gefunden. | Repo-Inhalt. | +| R14 | Bei Teilausfall des Geräts: Daten bleiben stehen, Entitäten bleiben `available`, solange der letzte Update-Erfolg anhält. | Nutzer sieht alte Werte als aktuell. | Aus Code gelesen (`available` = `last_update_success`). Wie sich ein WebSocket-Verlust auf die Verfügbarkeit auswirkt, habe ich nicht geprüft. | + + +**Stand der Risiken nach Version 2026.10.8 und `98af3e0` (2026-10-29, durch Tests im Fork geprüft)** + +| # | Stand | Anmerkung | +|---|---|---| +| R1 | **teilweise behoben** | Der `TypeError` im WebSocket-Modus ist weg (`write_value_to_key` liefert jetzt `{key: value}`). Offen: `_stop_in_progress` wird bei Fehlern nicht zurückgesetzt. Test: `known_bug`. | +| R2 | **behoben** | Register wird für jeden Charger gepflegt, Neuladen ersetzt den alten Koordinator. Offen: ohne `configid` ändert sich der „erste“ Charger nach einem Neuladen. Test: `known_bug`. | +| R3 | offen | `ACTIVE_CHARGER_COUNTER` bei Controllern. Zwei Tests: `known_bug`. | +| R4 | **behoben** | Aufruf hat jetzt zwei Argumente. | +| R5 | offen | `pgrid` ohne Endlichkeitsprüfung. Drei Tests: `known_bug`. | + +Neue Risiken aus dem Abgleich mit `main` und den Tests (2026-10-29): + +| # | Befund | Wirkung | Beleg | +|---|---|---|---| +| R24 | `_write_values_int` prüft die Antwort des Chargers auf den Schlüssel nur auf „wahr“. Laut `http-en.md` kann der Wert `true` oder ein **Fehlertext** sein. Ein Text gilt dem Code als Erfolg. | Ablehnungen des Chargers (auch bei `ids`) würden als Erfolg gemeldet. | Aus Code und Doku abgeleitet, im Test mit Attrappe gezeigt (`known_bug`). Am Gerät **nicht** geprüft. | +| R25 | `async_setup_entry`: Die Meldung „could not fetch essential information“ wird innerhalb eines `try` erzeugt und von `except BaseException` sofort durch die allgemeine WebSocket-Meldung ersetzt. | Nutzer sehen eine unpassende Fehlermeldung. | Im Test gezeigt (`known_bug`). | +| R26 | Nach `03b0fc3` speichert das Schreiben im WebSocket-Modus den gewünschten Wert sofort in `coordinator.data`. | Lehnt der Charger ab (nur eine Warnung im Log), zeigt HA den abgelehnten Wert, bis das Gerät den Schlüssel erneut meldet. | Aus Code abgeleitet, Verhalten im Test festgehalten. | +| R27 | `write_multiple_values_to_keys` liefert im WebSocket-Modus `{}`; `handle_write_result` überspringt nur `None`. | Nach jeder erfolgreichen Fahrzeugauswahl steht ein falscher `ERROR` im Log („could not write multiple value“). | Im Test gezeigt (`known_bug`). | +| R29 | `98af3e0`: Fehlt `cableCurrentLimit` in `cll`, wird nicht mehr auf 16 A begrenzt und `adi` nicht mehr geprüft; Core-Geräte ohne den Schlüssel waren vorher vorsichtshalber begrenzt. | Sliders erlauben höhere Werte; der Charger begrenzt einen Adapter selbst. Die Annahme „nur Flex hat den Schlüssel“ ist nicht belegt. | Aus Diff gelesen, Verhalten im Test festgehalten. | +| R30 | Laut Maintainer (Issue #176) setzt die Integration das Maximum der Stromstärke (Max Ampere) bei jedem Neustart zurück. | Ein zuvor eingestellter Wert geht verloren; die Grenze gilt dann nicht mehr, bis sie neu gesetzt wird. | Aussage des Maintainers, **nicht** von uns nachgespielt und kein Test dazu. Ob das vom 16-A-Begrenzer (R29) oder von der Wertübernahme beim Start kommt, ist offen. | + +Die Zahl R28 wurde bewusst ausgelassen; die Nummerierung folgt der Reihenfolge der Funde. + +**Nachtrag zu R2:** Das ist eine **Regression** aus Commit `5328e74` („housekeeping of tasks“). Vorher wurde pro Charger-Entry immer ein `GoeChargerApiV2Service` angelegt und trug seinen Koordinator ins Register ein, `configid` funktionierte also für jeden Charger. Seit `5328e74` entsteht der Dienst nur noch beim ersten Charger. Zusätzlich bleibt nach dem Neuladen eines Entries (zum Beispiel durch die automatische Neustart-Logik nach mehr als 5 Verbindungsfehlern) bei mehreren Chargern der **alte** Koordinator im Register; der Dienst schreibt dann über eine getrennte Bridge. Aus dem Diff und dem Code gelesen, nicht nachgespielt. + +Ergänzungen aus dem Abgleich von `main` bei `a9f9350` mit dem Stand, auf dem unser PR #173 aufsetzte (`76b8dd8`): + +| # | Befund | Wirkung | Beleg | +|---|---|---|---| +| R20 | Seit `5328e74` überschreibt `read_all_states`, `read_all_config` und `read_versions` den gespeicherten Zustand nur noch, wenn die Antwort nicht leer ist (`if ret is not None and len(ret) > 0 …`). Davor wurde bei einem Fehler der Zustand geleert. `_read_filtered_data` gibt bei jedem Status außer 200/400 (zum Beispiel Cloud 403 oder 404 bei offline) `{}` zurück. | Der Koordinator behält die letzten Werte und meldet Erfolg. Ein offline gegangener Charger zeigt im Cloud-Betrieb die letzten Werte weiter an. Zusätzlich setzt `_async_update_data` den Fehlerzähler zurück, weil die `ChainMap` der alten Werte nicht leer ist. | Aus Diff und Code abgeleitet, **nicht getestet**. Ob die Anzeige wirklich „eingefroren“ bleibt, hängt davon ab, was HA mit unverändertem `last_update_success` macht. | +| R21 | Die Migration von 2.0 auf 2.1 löscht **alle Geräte** des Entries (`async_remove_device`). HA entfernt damit nach meinem Wissen auch die zugehörigen Einträge der Entity-Registry. | Von Nutzern angepasste Namen, Bereiche, Beschriftungen und manuell aktivierte Entitäten (viele Sensoren sind standardmäßig aus) gehen vermutlich verloren und müssen neu gesetzt werden. Die Entity-IDs bleiben gleich, da sie im Code erzeugt werden. | HA-Verhalten aus dem Gedächtnis, im Code nur der Aufruf belegt. **Nicht nachgespielt.** Ob deine Instanz betroffen war, ist nicht geprüft. | +| R22 | Entries mit Version 1 werden direkt auf 2.1 migriert und überspringen das Löschen der Geräte. Die alte Aufräumfunktion `check_device_registry` wurde entfernt. | Alte Geräte mit dem früheren Identifier (der bei Cloud-Entries den Token enthielt) bleiben dort in der Registry stehen. Sehr seltener Randfall. | Aus Diff gelesen. | +| R23 | Die in `main` gemergte Blueprint-Version (PR #171 plus #174, von marq24 manuell eingebaut) enthält **nicht** unsere Erweiterungen: Bei ungültigen Messwerten wird die Tageslieferung **übersprungen** (kein Nullwert), bei falscher Zielauswahl `stop` mit `error: true` im 5-Sekunden-Takt, `mode: single` ohne `max_exceeded: silent`, kein Akku-Schalter, keine Karenzzeit. Das Verhalten steht im Blueprint-Text als bewusste Entscheidung („does not clear its previous averages“). | Fällt ein Sensor am Tag aus, bleiben die gleitenden Mittelwerte der Wallbox stehen. Das ist das Risiko, das wir mit „Nullwerte senden“ vermeiden wollten. | Quelltext gelesen. Deine Instanz nutzt weiter unsere eigene Version (`OliEder/go-echarger-pv-surplus-data.yaml`). | + +Ergänzungen aus dem Abgleich mit der offiziellen Dokumentation (Abschnitt 3.3): + +| # | Befund | Wirkung | Beleg | +|---|---|---|---| +| R15 | Laut Cloud-Doku antwortet `/api/set` bei Erfolg mit **HTTP 202**. `_write_values_int` wertet nur `200` (und `500` mit Inhalt) aus, alles andere gilt als Fehler (`failed with http-status …`, Rückgabe `{}`). | Wenn die Cloud tatsächlich 202 sendet, würden Schreibbefehle im Cloud-Polling trotz Erfolg als Fehler geloggt, und `handle_write_result` meldet „could not write“. Im WebSocket-Betrieb ist der Pfad nicht betroffen. | Doku und Code gelesen. **Ob die Cloud 202 oder 200 liefert, ist nicht geprüft**, ebenso nicht, ob Cloud-Nutzer das Problem sehen. | +| R16 | Der bevorzugte Datenpfad (WebSocket) ist nicht offiziell dokumentiert; Anmeldeverfahren und Nachrichten sind nachgebaut. | Eine Firmware- oder Cloud-Änderung von go-e kann ihn ohne Vorwarnung brechen. Das README erwähnt das schon für Firmware 60.2 und 60.4. | Doku und README gelesen. | +| R17 | Cloud-`/api/status` liefert standardmäßig Daten bis 30 Sekunden Alter (`age=30`); die Integration setzt `age` nicht. | Im Cloud-Polling können Werte bis zu 30 Sekunden alt sein, ohne dass die Integration das erkennt. | Doku und Code gelesen (kein `age` in den Aufrufen). | +| R18 | Offiziell bedeutet `pPv < 0` „Standby“, der Dienst `set_pv_data` begrenzt `ppv` in der Oberfläche auf Werte ab 0. | Standby kann über die Oberfläche nicht gesetzt werden. Ob das gewollt ist, ist unklar. | Doku und `services.yaml` gelesen. | +| R19 | Offiziell ist der Cloud-Token über den Schlüssel `cak` lesbar (also auch über die lokale API, soweit der Schlüssel dort verfügbar ist). | Eine Maßnahme der Integration, den Token nur im Config-Entry zu schützen, wäre dann nur begrenzt wirksam. Ob `cak` tatsächlich lokal ohne Anmeldung lesbar ist, habe ich nicht geprüft. | Nur Doku gelesen. | + +**Offene Annahmen (nicht geprüft):** +- Verhalten des Chargers nach einem Neustart mit gesetztem `frc=1`. +- Ob der Charger den PV-Datenschlüssel `ids` per WebSocket genauso annimmt wie per HTTP. +- Tatsächliche Mindestversion von Home Assistant. + +--- + +## 12. Glossar + +| Begriff | Bedeutung | +|---|---| +| APIv2 | Zweite Generation der go-e-Geräteschnittstelle (HTTP und WebSocket) | +| `frc` | `forceState`: 0 neutral, 1 aus, 2 an („Manueller Lademodus“) | +| `ids` | Schlüssel für PV-Daten (`pGrid`, `pPv`, `pAkku`) | +| `pgrid` | Leistung aus dem Netz (positiv) oder ins Netz (negativ) in Watt | +| `ppv` | PV-Erzeugung in Watt | +| `pakku` | Batterie: negativ lädt, positiv entlädt | +| `car` | Fahrzeugstatus: 1 frei, 2 lädt, 3 wartet, 4 fertig, 5 Fehler | +| `fup` / `lmo` | PV-Überschuss nutzen / Logikmodus (ECO) | +| `hai` / `cae` | HTTP-API lokal erlaubt / Cloud-API erlaubt | +| Core/Phoenix | Neuere Wallbox-Hardwaregeneration mit teils anderen Schlüsseln und bcrypt-Anmeldung | +| LAN / WAN | Lokaler Zugriff / Zugriff über die go-e-Cloud (Seriennummer plus Token) | +| Hibernation | Polling-Sparmodus ohne angeschlossenes Fahrzeug | +| Coordinator | HA-Hilfsklasse, die Daten holt und an Entitäten verteilt | +| Blueprint | Vorlage für HA-Automationen; hier für PV-Überschuss | + +--- + +## Anhang: Quellen + +**Offizielle go-e-Dokumentation** (Stand Commit `b4d7f85`, 2026-08-12), Repository `goecharger/go-eCharger-API-v2`: +- `README.md`, `introduction-en.md` (Einstieg und Verweise) +- `API_KEYS_FIRMWARE/apikeys-en.md` (alle Schlüssel) und die Listen je Firmware 59.4 bis 60.4 +- `http-en.md` (lokale HTTP-API), `cloudapi-en.md` (Cloud-HTTP-API) +- `mqtt-en.md`, `modbus-en.md`, `ocpp-en.md`, `gridapi-en.md` (nicht genutzt) +- `Changelogs_Gemini/`, `Changelogs_PRO/` (Firmware 60.3 bis 60.5) +- Statusseite: https://status.go-e.com/status/go-e + +**Im Repository der Integration:** + +- `custom_components/goecharger_api2/__init__.py`: Setup, Koordinator, Basisentität +- `custom_components/goecharger_api2/pygoecharger_ha/__init__.py`: Bridge, WebSocket-Anmeldung +- `custom_components/goecharger_api2/service.py`, `services.yaml`: Dienste +- `custom_components/goecharger_api2/config_flow.py`: Einrichtung +- `custom_components/goecharger_api2/const.py`, `pygoecharger_ha/const.py`, `keys.py`: Beschreibungen, Filter, Schlüssel +- `README.md`, `docs/PVSURPLUS.md`, `example/blueprint/automation/readme.md` +- `.github/workflows/*.yaml`, `hacs.json`, `manifest.json` diff --git a/docs/quality/testkonzept-ha-goecharger-api2.md b/docs/quality/testkonzept-ha-goecharger-api2.md new file mode 100644 index 0000000..fd0fd34 --- /dev/null +++ b/docs/quality/testkonzept-ha-goecharger-api2.md @@ -0,0 +1,250 @@ +# Testkonzept: `ha-goecharger-api2` + +| | | +|---|---| +| **Gegenstand** | Automatisierte Tests der Home-Assistant-Integration `goecharger_api2` (go-eCharger, go-eController) | +| **Stand** | 2026-10-29, Fork `OliEder/ha-goecharger-api2`, Branch `tests/regression-suite` (Branch `tests/regression-suite`), aufbauend auf `main` bei `98af3e0` | +| **Bezug** | Architektur und Risiken: `arc42-ha-goecharger-api2.md` (Risiken R1 bis R29). Regeln: `CLAUDE.md` / `AGENTS.md` der Arbeitsumgebung. | +| **Status der Tests** | Die Tests liegen im Fork. Es gibt keinen PR an das Upstream-Repository. Das Dokument wurde mit KI-Unterstützung (Claude Code) erstellt und von Oliver geprüft. | + +**Lesehinweis.** Alle Tests laufen mit Attrappen. Sie belegen, was der Code tut und was die Doku sagt, **nicht**, was ein echter Charger tut. Wo das für eine Aussage wichtig ist, steht es dabei. + +--- + +## 1. Zweck und Geltungsbereich + +**Zweck.** Die Tests sollen Regressionen in der Integration früh melden, bekannte Fehler sichtbar halten und die Übereinstimmung mit der offiziellen go-e-API-Dokumentation prüfen. Sie sind auch die Grundlage für spätere Änderungen und für Gespräche mit dem Maintainer. + +**Im Geltungsbereich** +- Bridge (`pygoecharger_ha`): HTTP lokal und Cloud, WebSocket (Anmeldung, Nachrichten, Befehle). +- Koordinator: Lesen, Schreiben, Fehlerzähler, Pause, Neustart, Start (`ConfigEntryNotReady`). +- Dienste (`set_pv_data`, `stop_charging`), Multi-Charger-Register, Entladen von Entries. +- Config-Flow (Anlegen, Fehlerfälle), Migration der Config-Entries. +- Stabilität der Entity-IDs. +- Vertrag zwischen offizieller Schlüsseltabelle und `keys.py`. +- Das PV-Blueprint hat eigene Tests (240 Fälle, Branch `proposal/invalid-data-policy`). Sie sind **nicht** Teil dieses Konzepts. + +**Nicht im Geltungsbereich (bewusst)** +- Prüfung gegen einen echten Charger im Dauerbetrieb, Last- und Leistungstests. +- Oberfläche von Home Assistant (Dashboards, Übersetzungen). +- MQTT, Modbus, OCPP-Protokolle und Grid-API (nutzt die Integration nicht). +- go-eController: Seine Schlüssel stehen nicht in der Charger-Doku, es fehlt eine Quelle für einen Vertrag. + +--- + +## 2. Qualitätsziele, die geprüft werden + +Aus den Qualitätsszenarien des arc42-Dokuments (Abschnitt 10.2): + +| arc42 | Szenario | Wie geprüft | +|---|---|---| +| Q1 | Charger ist nicht erreichbar: keine Dauerlast, Erholung | `test_coordinator_failures.py` (Fehlerzähler, Pause, einmaliger Neustart) | +| Q2 | Firmware ändert Schlüssel | `test_versions_16a.py`, `test_api_contract.py` (nur Namen und Flags, nicht Inhalte) | +| Q3 | PV-Automation fällt aus: kein ungewolltes Netzladen | **nicht Teil dieser Suite** (liegt im Blueprint), nur `set_pv_data`-Eingaben in `test_service.py` | +| Q4 | `stop_charging` im WebSocket-Modus | `test_services_and_unload.py` | +| Q5 | Zwei Charger in einer Instanz | `test_services_and_unload.py` | +| Q6 | Zugangsdaten nicht lesbar | **nicht geprüft** | + +--- + +## 3. Risikobasierte Priorisierung + +Reihenfolge der Testtiefe nach Auswirkung auf den Betrieb (steuert ein physisches Gerät) und nach Eintrittswahrscheinlichkeit. + +| Priorität | Bereich | Begründung | +|---|---|---| +| 1 | Steuerpfade: `stop_charging`, `set_pv_data`, Schreiben von Einstellungen | Wirken auf Stromfluss und Fahrzeug | +| 2 | Verbindungsverlust und Erholung (HTTP und WebSocket) | Häufigster Fehler im Alltag (WLAN, Router, Firmware-Update) | +| 3 | Anmeldung per WebSocket | Nachgebaut, nicht offiziell dokumentiert, bricht bei Firmware-Änderungen | +| 4 | Stabilität der Entity-IDs und Migration | Ein Fehler trifft alle Bestandsnutzer auf einmal | +| 5 | Vertrag zur offiziellen API | Früherkennung von Doku- und Code-Drift | +| 6 | Eingabevalidierung der Entitäten | Geringe Wirkung, Gerät begrenzt selbst | + +**Abdeckung der Risiken aus arc42 (Abschnitt 11)** + +| Risiko | Test | Stand | +|---|---|---| +| R1 `stop_charging` WebSocket (`TypeError`) | `test_stop_charging_in_websocket_mode_sends_lock_and_unlock` | behoben, Test grün | +| R1 Flag `_stop_in_progress` bleibt hängen | `test_failed_lock_write_...`, `test_failed_unlock_write_...` | `known_bug`, rot | +| R2 Multi-Charger-Register | `test_second_charger_is_reachable_...`, `test_reloaded_charger_...` | behoben, grün | +| R2 Standard-Charger wechselt nach Reload | `test_default_charger_does_not_change_after_reload` | `known_bug`, rot | +| R3 Zähler `ACTIVE_CHARGER_COUNTER` | zwei Tests in `test_services_and_unload.py` | `known_bug`, rot | +| R4 `__init__.py:674` | `test_write_multiple_keys_connection_error_is_counted` | behoben, grün | +| R5 `pgrid` ohne Endlichkeitsprüfung | `test_non_finite_grid_value_is_rejected` | `known_bug`, rot | +| R6 keine `unique_id` im Config-Flow (kein Schutz vor Doppelanlage) | – | **nicht abgedeckt** | +| R7 README „24 h“, Code „1 h“ | `test_read_all_reads_the_configuration_only_every_hour` | Code-Verhalten festgehalten (Abweichung zum README bleibt) | +| R8 Zugangsdaten | – | bewusst nicht Teil der Tests | +| R15 Cloud-Antwort 202 | `test_cloud_write_accepts_http_202` | `known_bug`, rot (nur nach Doku) | +| R16 WebSocket nicht dokumentiert | `test_ws_protocol.py`, `test_ws_login_failures.py` | Verhalten festgehalten | +| R17 Cloud `age` nicht gesetzt | – | **nicht abgedeckt** | +| R18 `ppv` negativ (Standby) | – | **nicht abgedeckt** | +| R20 alte Werte nach fehlgeschlagenem Lesen | `test_failed_full_read_keeps_the_previous_states` | Verhalten festgehalten, Absicht ungeklärt | +| R21 Migration löscht Geräte | `test_version_2_0_deletes_the_devices_and_moves_to_2_1` | Verhalten festgehalten | +| Fehlermeldung bei `ConfigEntryNotReady` wird überdeckt (neu) | `test_missing_device_data_is_reported_with_its_own_message` | `known_bug`, rot | +| Fehlertext des Chargers gilt als Erfolg (neu) | `test_write_rejected_by_charger_returns_err` | `known_bug`, rot | + +--- + +## 4. Teststufen und Testarten + +| Stufe / Art | Zweck | Dateien | +|---|---|---| +| **Vertrag (Schema)** | Offizielle Schlüsseltabelle gegen `keys.py`, Filter und Entitäten | `test_api_contract.py`, `contract/` | +| **Komponente** | Bridge gegen gemockte HTTP-Schicht oder Attrappe der WebSocket-Verbindung | `test_bridge_http.py`, `test_ws_protocol.py`, `test_bridge.py` | +| **Nahtstelle (Seam)** | Koordinator, Bridge und Dienste im Zusammenspiel; Dienste gegen Register | `test_coordinator_failures.py`, `test_services_and_unload.py`, `test_coordinator_errors.py` | +| **Ablauf** | Config-Flow über die Home-Assistant-Flow-Verwaltung; Migration | `test_config_flow_failures.py`, `test_migration.py` | +| **Regression (Snapshot)** | Entity-IDs bleiben stabil | `test_entity_ids.py`, `snapshots/entity_ids.json` | +| **Entscheidungstabelle** | 16-A-Limit | `test_versions_16a.py` | +| **Negativ / Fehlerpfade** | Fehlschläge enden sauber | in allen Dateien, Schwerpunkt `test_ws_login_failures.py`, `test_coordinator_failures.py`, `test_config_flow_failures.py` | + +Nicht vorhanden: Ende-zu-Ende gegen ein echtes Gerät, Komponententests der Entitäten (Wertdarstellung), Last- und Dauertests. + +--- + +## 5. Teststrategie und Regeln + +Verbindlich nach `CLAUDE.md` / `AGENTS.md`. Der Stand wird hier ehrlich festgehalten: + +| Regel | Stand | Maßnahme | +|---|---|---| +| **Arrange-Act-Assert** für jeden Funktionstest | **Teilweise.** Der Aufbau ist meist erkennbar, aber nicht überall mit Leerzeilen oder Kommentaren getrennt. Es gibt keine Prüfung. | Neue Tests strikt so schreiben; vorhandene bei Gelegenheit umstellen | +| **Mindestens zwei Fehlerfälle** je Netzwerk- oder Dateizugriff | **Erfüllt** für HTTP-Lesen (3 Status plus ungültiges JSON), WebSocket-Anmeldung (über 10 Fälle), Config-Flow (6). **Knapp** für HTTP-Schreiben (Fehlertext, 500 ohne Inhalt, Zeitüberschreitung fehlen teilweise). Dateizugriffe gibt es nicht. | HTTP-Schreiben und Cloud-Statuscodes ergänzen | +| **Grenzwerte** (Null, negativ, maximale Länge) | **Nicht erfüllt.** Nur NaN/inf bei `set_pv_data`. | Siehe Abschnitt 10, soweit die Doku Bereiche nennt | +| **Seam-Tests** an neuen Nahtstellen | **Erfüllt** für Koordinator mit Bridge und Dienste mit Register. **Offen:** Entität mit Koordinator (Schreibpfad). | Entitäten-Tests ergänzen | +| **Formale API-Schemas** einbetten | **Teilweise.** Schlüsseltabelle als `api_keys.json`. **Offen:** Schema der HTTP-Endpunkte (lokal, Cloud). | Endpunkt-Schema aus `http-en.md` und `cloudapi-en.md` ableiten | + +**Weitere Regeln dieses Konzepts** +1. Ein bekannter Fehler bekommt einen Test, der **rot** läuft (Marker `known_bug`). Kein `xfail`, damit der Fehler im Lauf sichtbar bleibt. +2. Jede Aussage über das Verhalten des Chargers braucht eine Quelle: offizielle Doku, Beobachtung am Gerät oder ausdrücklich „Vermutung“. Tests, die eine Vermutung festhalten, sagen das im Docstring. +3. Positivkontrolle: Jede Attrappe, die Fehler simuliert, hat einen Test, der zeigt, dass gültige Eingaben durchgehen. +4. Keine Tests gegen das Netzwerk. Das Test-Framework blockiert echte Sockets. + +--- + +## 6. Testumgebung + +- **Software:** Python 3.14.3, Home Assistant 2026.10.4 (festgelegt durch `pytest-homeassistant-custom-component`), `pytest`, `pytest-asyncio` (Modus `auto`). `msgpack` muss zusätzlich installiert werden, weil das Test-Framework die Anforderungen aus dem Manifest nicht installiert. +- **Ort:** lokale virtuelle Umgebung (Python 3.14, `pip install -r requirements_dev.txt`); die Tests laufen im Branch `tests/regression-suite`. Messbericht und Werkzeuge: `tests/reports/`, `tests/tools/`. +- **Aufruf:** + - alles: `python -m pytest tests/integration` + - blockierende Menge: `python -m pytest tests/integration -m "not known_bug and not needs_decision"` +- **Dauer:** rund 10 Sekunden für alle Tests. +- **Voraussetzungen:** kein Charger, kein Netzwerk, keine Zugangsdaten. + +--- + +## 7. Testdaten und Attrappen + +| Attrappe | Wofür | Grenze | +|---|---|---| +| `aioclient_mock` (Home Assistant) | HTTP-Antworten lokal und Cloud | Die Attrappe liefert kein `content_length`; Fälle mit `500` ohne/mit Inhalt stehen in `test_bridge.py` mit einer eigenen Antwort-Klasse | +| `FakeSocket`, `FakeSession` (`test_ws_login_failures.py`) | Skript der Anmeldung und der Nachrichten | Das Skript bildet die **nachgebaute** Anmeldung ab. Eine Änderung durch go-e erkennt sie nicht. | +| Fake-WebSocket-Objekt (`send_json`) | Ausgehende Befehle | wie oben | +| `MockConfigEntry` | Config-Entries für Koordinator, Migration, Flow | – | +| `api_keys.json` | Offizielle Schlüsseltabelle (Namen, R/W, Typ, Kategorie) | Bei 98 von 272 Zeilen steht als Typ nur `TYPE`; keine Beschreibungen gespeichert (kein Lizenztext im offiziellen Repo) | +| Beispieldaten | `123456`, `192.0.2.10` (reservierter Bereich), erfundene Tokens | enthalten keine echten Daten | + +**Grenze aller Attrappen:** Sie kodieren unsere Annahmen. Dass der Charger sich so verhält, belegt erst der lesende Abgleich am echten Gerät (Abschnitt 10, Punkt 4). + +--- + +## 8. Marker und Blockier-Regeln + +| Marker | Bedeutung | Blockiert? | +|---|---|---| +| `known_bug` | Der Code hat einen nicht behobenen Fehler; der Test beschreibt das richtige Verhalten | Ja: der Gesamtlauf ist rot, bis der Fehler behoben ist | +| `needs_decision` | Doku und Code weichen ab; unklar, ob gewollt oder Doku-Lücke | Nein: rot, aber nicht blockierend, bis entschieden ist | +| (kein Marker) | Verhalten, das funktionieren muss | Ja | + +- Eine **neue** Abweichung zur Doku hat keinen Marker und blockiert sofort. +- Wird ein Fehler behoben, schlägt das Entfernen des Markers nicht automatisch an. Der Test wird **grün**, der Marker bleibt stehen und muss von Hand entfernt werden. Das ist ein Nachteil gegenüber `xfail(strict)`. Mitigation: nach jedem Update von `main` die Marker prüfen (`-m known_bug -q`). +- Blockierende Menge heute: grün (958 bestanden). Bekannte Fehler: 29 (15 `known_bug`, 14 `needs_decision`). + +--- + +## 9. Aktueller Stand + +Stand 2026-10-29 (Messung nach dem Ausbau um Controller-Vertrag, Wertebereiche und Transportvergleich), Branch `tests/regression-suite` im Fork. Details und Rohdaten: `tests/reports/2026-10-29/testreport.md`. + +| Kennzahl | Wert | +|---|---| +| Tests gesammelt | 987, davon 420 parametrisierte Vertragsprüfungen je Schlüssel; **567 Verhaltens- und Fehlertests** | +| Ergebnis | 958 bestanden, 29 beabsichtigt rot (15 `known_bug`, 14 `needs_decision`); blockierende Menge 958 von 958 grün | +| Zeilenabdeckung | 92,9 % (vorher 55,2 %) | +| Zweigabdeckung | 84,9 % (vorher 31,8 %) | +| **Pfadabdeckung** (obere Schranke der Basispfade) | **61 %** alle Funktionen, **62 %** Kernmodule (vorher 20 % und 27 %) | +| Pfadabdeckung nach Kritikalität | K1 kritisch 66 %, K2 hoch 60 %, K3 mittel 64 %, K4 niedrig 48 % | + +**Hinweis zur Pfadabdeckung:** `coverage.py` misst keine Pfade. Die Zahl stammt aus einem eigenen Plugin (`tests/tools/pathtracer.py`) und ist eine obere Schranke: Je Funktion zählt `min(beobachtete verschiedene Pfade, zyklomatische Komplexität)`. Sie lässt sich durch viele leicht verschiedene Eingaben steigern, ohne dass die Prüfung besser wird. Als Gegenprobe dienen die Mutationsprüfungen (Abschnitt 11). + +| Datei | Inhalt | +|---|---| +| `test_api_contract.py` | Schlüsseltabelle gegen Code (420 Prüfungen) | +| `test_platform_values.py`, `test_platform_actions.py` | Wertdarstellung und Schreiben der Entitäten | +| `test_bridge_http.py`, `test_bridge_write_paths.py`, `test_bridge_polling.py` | HTTP-Anfragen, Schreibpfade, Lesezyklus | +| `test_ws_protocol.py`, `test_ws_login_failures.py`, `test_ws_connect_paths.py` | WebSocket: Nachrichten, Anmeldefehler, erfolgreiche Anmeldung und Schleife | +| `test_coordinator_failures.py`, `test_coordinator_runtime.py`, `test_coordinator_errors.py` | Fehlerzähler und Pause, Laufzeitpfade, Schreibfehler | +| `test_services_and_unload.py`, `test_services_and_setup.py`, `test_service.py` | Dienste, Register, Start und Ende eines Entries | +| `test_config_flow_failures.py`, `test_config_flow_paths.py`, `test_migration.py` | Config-Flow, Migration | +| `test_versions_16a.py`, `test_versions_transport_parity.py`, `test_entity_ids.py`, `test_bridge.py` | 16-A-Entscheidung (je nach Code-Stand), gleiches Ergebnis über HTTP und WebSocket, Entity-IDs, Schreibfehler der Bridge | +| `test_controller_contract.py` | Controller-Schlüssel gegen die Tabelle der go-eController-API (59 Prüfungen, 3 davon beabsichtigt rot) | +| `test_value_ranges.py` | Wertebereiche und Optionslisten aus dem Modbus-Profil (`amp`, `ama`, `lbr`, `frc`, `psm` u. a.) | + +**Kritikalität der Pfade.** Jede Funktion ist einer Klasse zugeordnet (K1 ändert den Charger oder die Konfiguration, K2 Verbindung, Anmeldung, Start, K3 Anzeige, K4 Hilfsfunktionen). Die Zuordnung mit Begründung steht in `tests/tools/criticality.py`; der Report listet je Klasse die offenen Lücken (Abschnitt 5.1). + +--- + +## 10. Lücken und Reihenfolge der nächsten Schritte + +Erledigt seit der ersten Fassung: Plattformen, Lesezyklus im Polling, Schreibpfade, Koordinator-Laufzeit (16-A-Begrenzer, Wiederverbindung, Watchdog), Start und Ende eines Entries, erfolgreiche Anmeldung per WebSocket, Config-Flow-Pfade. + +1. **K1 und K2 zuerst:** Der Report (Abschnitt 5.1) nennt je Funktion, wo sicher noch Basispfade fehlen, zum Beispiel in `check_and_write_to_16a`, `async_migrate_entry`, `ws_connect`, `read_versions` des Koordinators und `read_all_states`. +2. **R20 (alte Werte nach fehlgeschlagenem Lesen, Cloud) und R7 (Konfigurationsintervall):** Das Verhalten wird jetzt als Charakterisierung festgehalten (`test_failed_full_read_keeps_the_previous_states`); ein Test auf das gewünschte Verhalten fehlt, weil der Maintainer die Absicht nicht geklärt hat. +3. **Endpunkt-Verträge:** Cloud 403, 404, 422, 202, `age`, `last-modified`, `token` als GET-Parameter, mehrere Schlüssel in einem `set`; dazu ein Schema der Endpunkte aus `http-en.md` und `cloudapi-en.md`. +4. **Schreib-Kodierung je Schlüsseltyp** und **Grenzwerte** (Regel „explizite Grenzen“): nur dort, wo die Doku Typ oder Bereich nennt. Bei 98 Schlüsseln mit Platzhalter-Typ geht das nicht. +5. **Lesender Abgleich am echten Charger:** die Vertragsdaten (Statuscodes, Schlüssel, Typen) gegen `GET /api/status` prüfen. Nur lesend, kein `frc`, kein `ids`. Erst das belegt, dass die Attrappen dem Gerät entsprechen. +6. **Lese-Seite je Firmware:** Beispiel-Antworten aus den Listen für 59.4, 60.2, 60.3 und 60.4. +7. **Entity-Anlage:** `async_setup_entry` je Plattform und `entity.py` (Anzeigename) sind noch nicht ausgeführt. +8. **Verbindungsverlust im laufenden WebSocket-Betrieb:** Verfügbarkeit der Entitäten. +9. **R6 (`unique_id`):** Doppelanlage verhindern. +10. **Struktur:** Arrange-Act-Assert konsequent anwenden (die neuen Tests folgen dem Muster weitgehend, ohne dass es geprüft wird); Abdeckungs-Untergrenze und Marker-Warnung automatisieren. + +--- + +## 11. Qualität der Tests selbst + +- **Mutationsprüfung von Hand:** An verschiedenen Stellen wurden gezielt Fehler in den Code eingebaut und geprüft, ob ein Test rot wird. Bisher **33 Änderungen, alle erkannt**: 19 für die erste Suite (Wertkodierung, Präfixe, HMAC, Löschen der Geräte, 16-A-Grenze, Token, Anmeldeprüfungen, Neustart-Schwelle, Pausen, Flow-Abbruchgrund, Start-Prüfung, Schlüsselzuordnung) und 14 für die erweiterte Suite (Sensorfaktor, Fahrzeugprofil, Schalter-Regeln, Hibernation-Grenze, Konfigurations-Wiederholung, 16-A-Grenze im Begrenzer, Cloud-Fahrzeugprüfung, Zähler, Mindestintervall, Standard-Hash, Temperatur-Sensoren des Core, `ids`-Flag, Null-als-Null). **Eine** Änderung (Temperatur-Sensoren des Core) wurde zuerst nicht erkannt; ein zusätzlicher Testfall schloss die Lücke. Die Prüfung ist **nicht automatisiert** und deckt nicht den gesamten Code ab. +- **Positivkontrollen** für die Attrappen (Anmeldung, Config-Flow). +- **Stabilität:** Die blockierende Menge lief dreimal hintereinander grün (kein flackerndes Verhalten festgestellt). +- **Unabhängige Zweitprüfung:** für die Tests selbst nicht durchgeführt. +- **Bekannte Schwächen:** Die Zahl 420 in der Vertragsdatei verzerrt die Gesamtzahl; die Pfadzahl ist eine obere Schranke und steigerbar; die Attrappen kodieren unsere Annahmen. + +--- + +## 12. Pflege und Änderungsprozess + +| Anlass | Vorgehen | +|---|---| +| go-e veröffentlicht neue Doku | `generate_api_keys.py` mit neuem Commit ausführen, Diff prüfen, `classification.json` und `deviations.json` anpassen | +| Upstream behebt einen Fehler | Marker `known_bug` entfernen, Test muss grün sein; ggf. Eintrag in `deviations.json` oder `classification.json` streichen | +| Upstream ändert Entity-IDs absichtlich | Snapshot mit `UPDATE_SNAPSHOT=1` neu erzeugen und den Diff prüfen; ein Änderungsvorschlag an Nutzer wäre ein Bruch für Bestandsnutzer | +| Neue Entität oder neuer Schlüssel | Vertragstest schlägt an; Schlüssel zuordnen oder in die Klassifikation aufnehmen | +| Abweichung `needs_decision` geklärt | Eintrag entfernen oder Doku-Lücke vermerken und Marker entfernen | + +**Zuständig:** Oliver (Fork und Tests). Änderungen am Upstream-Repository macht ausschließlich der Maintainer. + +--- + +## 13. Eintritts- und Abschlusskriterien + +- **Vor jedem Push in den Fork:** blockierende Menge grün (`-m "not known_bug and not needs_decision"`). +- **Vor jedem Update auf einen neuen Upstream-Stand:** `main` holen, Gesamtlauf starten, neue Rotfälle einordnen (neuer Fehler oder behobener bekannter Fehler). +- **Abschluss des Testkonzepts „offizielle API“:** Punkte 1 bis 3 aus Abschnitt 10 umgesetzt, Punkt 4 mindestens einmal durchgeführt und dokumentiert. + +--- + +## 14. Annahmen, Grenzen, offene Entscheidungen + +- Die Tests prüfen den Code gegen Doku und gegen unsere Annahmen, nicht gegen das Gerät. +- Ob die 11 `known_bug`- und 14 `needs_decision`-Fälle Fehler sind, ist teils offen (siehe `tests/integration/contract/deviations.json`). +- Offen: Soll es Mutationstests automatisiert geben (zum Beispiel `mutmut`)? Das wäre ein Aufwand von einem halben bis einem Tag, ohne Gewähr für Nutzen bei einer Codebasis dieser Struktur. diff --git a/requirements_dev.txt b/requirements_dev.txt index 2e0ce26..43e00ba 100644 --- a/requirements_dev.txt +++ b/requirements_dev.txt @@ -1 +1,3 @@ -pytest-homeassistant-custom-component>=0.13.154 +# pinned: 0.13.375 brings Home Assistant 2026.10.4. Raise it on purpose (see tests/tools/README.md). +pytest-homeassistant-custom-component==0.13.375 +msgpack>=1.1.2 diff --git a/tests/integration/README.md b/tests/integration/README.md new file mode 100644 index 0000000..fb4030e --- /dev/null +++ b/tests/integration/README.md @@ -0,0 +1,71 @@ +# Regression tests + +Tests for the integration without a charger, without network access and without credentials: the test +framework blocks real sockets, HTTP is mocked, the WebSocket connection is a fake object. Bridge and +coordinator are the real code. + +```sh +python3.14 -m venv .venv && . .venv/bin/activate +python -m pip install -r requirements_dev.txt +python -m pytest tests/integration -q +``` + +Python 3.14 is required by the current `pytest-homeassistant-custom-component`, which also pins the +Home Assistant release it was built for. `msgpack` is listed in the manifest, but the test framework +does not install manifest requirements, so it is part of `requirements_dev.txt`. + +## What is covered + +| File | Area | Basis | +|---|---|---| +| `test_bridge_http.py` | HTTP requests and answers: filter, value encoding, cloud URL and token, error statuses | official go-e docs (`http-en.md`, `cloudapi-en.md`) | +| `test_ws_protocol.py` | WebSocket messages, password hashing, message signing, start condition | current implementation (protocol is not officially documented) | +| `test_versions_16a.py` | When a charger is limited to 16 A (cable limit, adapter, variant, option) | decision table; rows after 98af3e0 pin the current behaviour | +| `test_entity_ids.py` | Entity IDs and unique IDs stay stable (snapshot, uniqueness, LAN/WAN prefix) | snapshot in `snapshots/entity_ids.json` | +| `test_ws_login_failures.py` | WebSocket login failures: no serial, device offline, unknown hash, missing tokens, wrong password, timeout, unreachable device, dropped connection (plus a positive control) | current implementation | +| `test_coordinator_failures.py` | Error counter, 2 minute pause, restart after more than 5 errors, writes during an outage, `ConfigEntryNotReady` at start | code | +| `test_config_flow_failures.py` | Wrong, silent or unreachable devices and wrong cloud token end the flow with `auth_lan` / `auth_wan` and create no entry (plus a positive control) | code | +| `test_api_contract.py` | Official key table against the code: every documented key supported or classified, undocumented keys explained, read/write flag and category equal to the docs | `contract/api_keys.json` (generated from the official table) | +| `test_platform_values.py`, `test_platform_actions.py` | Entities: how raw values become states (sensor, number, select, switch, binary sensor) and what a write sends (number, select incl. car profiles, switch, button) | code | +| `test_bridge_polling.py` | Read cycle: hibernation, configuration refresh, versions, state housekeeping | code, README (hibernation) | +| `test_bridge_write_paths.py` | Every path from a value to the charger and back (HTTP, WebSocket, answers) | official docs, code | +| `test_ws_connect_paths.py` | Successful WebSocket logins and the message loop; address building | current implementation | +| `test_coordinator_runtime.py` | Write results, 16 A limiter, device detection, WebSocket start, reconnect, watchdog, housekeeping | code | +| `test_services_and_setup.py` | `set_pv_data`, `stop_charging`, charger lookup, setup and unload of entries | code | +| `test_config_flow_paths.py` | Successful setups, interval rules, reconfiguration, WebSocket check | code | +| `test_migration.py` | Config entry migration 1 -> 2.1 and 2.0 -> 2.1 | code | +| `test_services_and_unload.py` | `stop_charging`, multi-charger registry, unloading entries | reported bugs | +| `test_service.py` | `set_pv_data` input validation | reported bugs | +| `test_bridge.py`, `test_coordinator_errors.py` | write errors, connection error counting | reported bugs | + +## Known bugs fail on purpose + +A test marked `@pytest.mark.known_bug("...")` describes behaviour that is wrong today, so the run is +red until the bug is fixed. To get a green run without them: `python -m pytest tests/integration -m "not known_bug"`. +List them with `python -m pytest tests/integration -m known_bug -q`. When a bug is fixed, remove the marker. + +## Markers + +| Marker | Meaning | Blocking? | +|---|---|---| +| `known_bug` | the code has a bug that is not fixed yet | yes: the run is red until it is fixed | +| `needs_decision` | documentation and code differ; not yet decided whether this is intended or a documentation gap | no: fails on purpose, but is not blocking | + +Blocking set only: `python -m pytest tests/integration -m "not known_bug and not needs_decision"`. +A new difference to the documentation has no marker, so it is blocking. + +## Contract data (`contract/`) + +- `api_keys.json`: key names, R/W, type and category from the official key table (no descriptions). Regenerate with `generate_api_keys.py` when go-e publishes a new version, and review the diff. +- `classification.json`: documented keys without a `Tag` (unsupported, decision open) and keys the code uses that the official table does not list, each with a reason. +- `deviations.json`: known differences between documentation and code (status `needs_decision`). + +## Entity ID snapshot + +If an entity ID changes on purpose, regenerate the file and review the diff: +`UPDATE_SNAPSHOT=1 python -m pytest tests/integration/test_entity_ids.py -k snapshot` +Changing an ID breaks dashboards, automations and statistics of existing users. + +## Helpers + +`factories.py` builds config entries, real coordinators and bridges (with an optional fake WebSocket connection) for the tests. diff --git a/tests/integration/__init__.py b/tests/integration/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/integration/conftest.py b/tests/integration/conftest.py new file mode 100644 index 0000000..70c164a --- /dev/null +++ b/tests/integration/conftest.py @@ -0,0 +1,12 @@ +"""Fixtures for the integration tests. + +The tests need the packages from requirements_dev.txt and run with Python 3.14 (the version +required by pytest-homeassistant-custom-component for the current Home Assistant release). +""" +import pytest + + +@pytest.fixture(autouse=True) +def auto_enable_custom_integrations(enable_custom_integrations): + """Allow Home Assistant to load the integration from custom_components/.""" + yield diff --git a/tests/integration/contract/api_keys.json b/tests/integration/contract/api_keys.json new file mode 100644 index 0000000..4f9d7ad --- /dev/null +++ b/tests/integration/contract/api_keys.json @@ -0,0 +1,1505 @@ +{ + "source": { + "repository": "goecharger/go-eCharger-API-v2", + "file": "API_KEYS_FIRMWARE/apikeys-en.md", + "commit": "b4d7f85f", + "date": "2026-08-12" + }, + "keys": { + "acp": { + "rw": "R/W", + "type": "bool", + "category": "Config" + }, + "acs": { + "rw": "R/W", + "type": "uint8", + "category": "Config" + }, + "acu": { + "rw": "R", + "type": "int", + "category": "Status" + }, + "adi": { + "rw": "R", + "type": "bool", + "category": "Status" + }, + "alw": { + "rw": "R", + "type": "bool", + "category": "Status" + }, + "ama": { + "rw": "R/W", + "type": "uint8", + "category": "Config" + }, + "amp": { + "rw": "R/W", + "type": "uint8", + "category": "Config" + }, + "amt": { + "rw": "R", + "type": "int", + "category": "Status" + }, + "ara": { + "rw": "R/W", + "type": "bool", + "category": "Config" + }, + "ate": { + "rw": "R/W", + "type": "double", + "category": "Config" + }, + "atp": { + "rw": "R", + "type": "optional<...>", + "category": "Status" + }, + "att": { + "rw": "R/W", + "type": "seconds", + "category": "Config" + }, + "avgfhz": { + "rw": "R", + "type": "TYPE", + "category": "Status" + }, + "awc": { + "rw": "R/W", + "type": "uint8", + "category": "Config" + }, + "awcp": { + "rw": "R", + "type": "optional<...>", + "category": "Status" + }, + "awe": { + "rw": "R/W", + "type": "bool", + "category": "Config" + }, + "awp": { + "rw": "R/W", + "type": "float", + "category": "Config" + }, + "awpl": { + "rw": "W", + "type": "TYPE", + "category": "Status" + }, + "bac": { + "rw": "R/W", + "type": "uint8_t", + "category": "Config" + }, + "bar": { + "rw": "R/W", + "type": "TYPE", + "category": "Config" + }, + "c0e": { + "rw": "R/W", + "type": "uint64_t", + "category": "Config" + }, + "c0i": { + "rw": "R/W", + "type": "bool", + "category": "Config" + }, + "c0n": { + "rw": "R/W", + "type": "string", + "category": "Config" + }, + "c1e": { + "rw": "R/W", + "type": "uint64_t", + "category": "Config" + }, + "c1i": { + "rw": "R/W", + "type": "bool", + "category": "Config" + }, + "c1n": { + "rw": "R/W", + "type": "string", + "category": "Config" + }, + "c2e": { + "rw": "R/W", + "type": "uint64_t", + "category": "Config" + }, + "c2i": { + "rw": "R/W", + "type": "bool", + "category": "Config" + }, + "c2n": { + "rw": "R/W", + "type": "string", + "category": "Config" + }, + "c3e": { + "rw": "R/W", + "type": "uint64_t", + "category": "Config" + }, + "c3i": { + "rw": "R/W", + "type": "bool", + "category": "Config" + }, + "c3n": { + "rw": "R/W", + "type": "string", + "category": "Config" + }, + "c4e": { + "rw": "R/W", + "type": "uint64_t", + "category": "Config" + }, + "c4i": { + "rw": "R/W", + "type": "bool", + "category": "Config" + }, + "c4n": { + "rw": "R/W", + "type": "string", + "category": "Config" + }, + "c5e": { + "rw": "R/W", + "type": "uint64_t", + "category": "Config" + }, + "c5i": { + "rw": "R/W", + "type": "bool", + "category": "Config" + }, + "c5n": { + "rw": "R/W", + "type": "string", + "category": "Config" + }, + "c6e": { + "rw": "R/W", + "type": "uint64_t", + "category": "Config" + }, + "c6i": { + "rw": "R/W", + "type": "bool", + "category": "Config" + }, + "c6n": { + "rw": "R/W", + "type": "string", + "category": "Config" + }, + "c7e": { + "rw": "R/W", + "type": "uint64_t", + "category": "Config" + }, + "c7i": { + "rw": "R/W", + "type": "bool", + "category": "Config" + }, + "c7n": { + "rw": "R/W", + "type": "string", + "category": "Config" + }, + "c8e": { + "rw": "R/W", + "type": "uint64_t", + "category": "Config" + }, + "c8i": { + "rw": "R/W", + "type": "bool", + "category": "Config" + }, + "c8n": { + "rw": "R/W", + "type": "string", + "category": "Config" + }, + "c9e": { + "rw": "R/W", + "type": "uint64_t", + "category": "Config" + }, + "c9i": { + "rw": "R/W", + "type": "bool", + "category": "Config" + }, + "c9n": { + "rw": "R/W", + "type": "string", + "category": "Config" + }, + "car": { + "rw": "R", + "type": "optional<...>", + "category": "Status" + }, + "cards": { + "rw": "R/W", + "type": "array", + "category": "Config" + }, + "cbl": { + "rw": "R", + "type": "optional<...>", + "category": "Status" + }, + "ccd": { + "rw": "R", + "type": "TYPE", + "category": "Status" + }, + "cch": { + "rw": "R/W", + "type": "string", + "category": "Config" + }, + "cco": { + "rw": "R/W", + "type": "double", + "category": "Config" + }, + "ccrv": { + "rw": "R", + "type": "string", + "category": "Constant" + }, + "ccu": { + "rw": "R", + "type": "optional<...>", + "category": "Status" + }, + "ccw": { + "rw": "R", + "type": "optional<...>", + "category": "Status" + }, + "cdi": { + "rw": "R", + "type": "object", + "category": "Status" + }, + "cfi": { + "rw": "R/W", + "type": "string", + "category": "Config" + }, + "cid": { + "rw": "R/W", + "type": "string", + "category": "Config" + }, + "cle": { + "rw": "R", + "type": "TYPE", + "category": "Status" + }, + "clea": { + "rw": "R", + "type": "TYPE", + "category": "Status" + }, + "cll": { + "rw": "R", + "type": "TYPE", + "category": "Status" + }, + "clp": { + "rw": "R/W", + "type": "array", + "category": "Config" + }, + "cmmr": { + "rw": "R", + "type": "TYPE", + "category": "Config" + }, + "cmp": { + "rw": "R", + "type": "TYPE", + "category": "Config" + }, + "cms": { + "rw": "R", + "type": "TYPE", + "category": "Config" + }, + "cmse": { + "rw": "R", + "type": "TYPE", + "category": "Config" + }, + "csa": { + "rw": "R", + "type": "TYPE", + "category": "Status" + }, + "ct": { + "rw": "R/W", + "type": "TYPE", + "category": "Config" + }, + "ctrls": { + "rw": "R", + "type": "TYPE", + "category": "Status" + }, + "cus": { + "rw": "R", + "type": "uint8", + "category": "Status" + }, + "cwc": { + "rw": "R/W", + "type": "string", + "category": "Config" + }, + "cwe": { + "rw": "R/W", + "type": "bool", + "category": "Config" + }, + "data": { + "rw": "R", + "type": "TYPE", + "category": "Status" + }, + "del": { + "rw": "W", + "type": "uint8", + "category": "Other" + }, + "deltaa": { + "rw": "R", + "type": "float", + "category": "Other" + }, + "deltap": { + "rw": "R", + "type": "float", + "category": "Status" + }, + "delw": { + "rw": "W", + "type": "uint8", + "category": "Other" + }, + "di1": { + "rw": "R/W", + "type": "TYPE", + "category": "Config" + }, + "die": { + "rw": "R/W", + "type": "TYPE", + "category": "Config" + }, + "dii": { + "rw": "R/W", + "type": "TYPE", + "category": "Config" + }, + "dll": { + "rw": "R", + "type": "TYPE", + "category": "Status" + }, + "dns": { + "rw": "R", + "type": "object", + "category": "Status" + }, + "dsrc": { + "rw": "R", + "type": "TYPE", + "category": "Status" + }, + "dwo": { + "rw": "R/W", + "type": "optional<...>", + "category": "Config" + }, + "err": { + "rw": "R", + "type": "optional<...>", + "category": "Status" + }, + "esk": { + "rw": "R/W", + "type": "bool", + "category": "Config" + }, + "eto": { + "rw": "R", + "type": "uint64", + "category": "Status" + }, + "ferm": { + "rw": "R", + "type": "uint8", + "category": "Status" + }, + "ffb": { + "rw": "R", + "type": "uint8", + "category": "Status" + }, + "fhz": { + "rw": "R", + "type": "optional<...>", + "category": "Status" + }, + "fmt": { + "rw": "R/W", + "type": "milliseconds", + "category": "Config" + }, + "fna": { + "rw": "R/W", + "type": "string", + "category": "Config" + }, + "frc": { + "rw": "R/W", + "type": "uint8", + "category": "Config" + }, + "frm": { + "rw": "R", + "type": "uint8", + "category": "Config" + }, + "fsp": { + "rw": "R", + "type": "bool", + "category": "Status" + }, + "fsptws": { + "rw": "R", + "type": "optional<...>", + "category": "Status" + }, + "fst": { + "rw": "R/W", + "type": "float", + "category": "Config" + }, + "fup": { + "rw": "R/W", + "type": "bool", + "category": "Config" + }, + "fwc": { + "rw": "R", + "type": "string", + "category": "Constant" + }, + "fwv": { + "rw": "R", + "type": "string", + "category": "Constant" + }, + "fzf": { + "rw": "R/W", + "type": "bool", + "category": "Config" + }, + "gmtr": { + "rw": "R/W", + "type": "TYPE", + "category": "Config" + }, + "gsa": { + "rw": "R/W", + "type": "TYPE", + "category": "Status" + }, + "hai": { + "rw": "R/W", + "type": "TYPE", + "category": "Config" + }, + "hla": { + "rw": "R/W", + "type": "TYPE", + "category": "Config" + }, + "host": { + "rw": "R", + "type": "optional<...>", + "category": "Status" + }, + "hsa": { + "rw": "R/W", + "type": "bool", + "category": "Config" + }, + "ido": { + "rw": "R", + "type": "optional<...>", + "category": "Config" + }, + "ids": { + "rw": "R/W", + "type": "bool", + "category": "Other" + }, + "inva": { + "rw": "R", + "type": "milliseconds", + "category": "Status" + }, + "la1": { + "rw": "R/W", + "type": "TYPE", + "category": "Config" + }, + "la3": { + "rw": "R/W", + "type": "TYPE", + "category": "Config" + }, + "lbl": { + "rw": "R", + "type": "TYPE", + "category": "Config" + }, + "lbp": { + "rw": "R", + "type": "milliseconds", + "category": "Status" + }, + "lbr": { + "rw": "R/W", + "type": "uint8", + "category": "Config" + }, + "lccfc": { + "rw": "R", + "type": "optional<...>", + "category": "Status" + }, + "lccfi": { + "rw": "R", + "type": "optional<...>", + "category": "Status" + }, + "lcctc": { + "rw": "R", + "type": "optional<...>", + "category": "Status" + }, + "lck": { + "rw": "R", + "type": "uint8", + "category": "Status" + }, + "lcs": { + "rw": "R", + "type": "TYPE", + "category": "Status" + }, + "led": { + "rw": "R", + "type": "object", + "category": "Status" + }, + "lfspt": { + "rw": "R", + "type": "optional<...>", + "category": "Status" + }, + "lmo": { + "rw": "R/W", + "type": "uint8", + "category": "Config" + }, + "lmsc": { + "rw": "R", + "type": "milliseconds", + "category": "Status" + }, + "loa": { + "rw": "R", + "type": "optional<...>", + "category": "Status" + }, + "loc": { + "rw": "R", + "type": "string", + "category": "Status" + }, + "loe": { + "rw": "R/W", + "type": "bool", + "category": "Config" + }, + "lof": { + "rw": "R/W", + "type": "uint8", + "category": "Config" + }, + "log": { + "rw": "R/W", + "type": "string", + "category": "Config" + }, + "lop": { + "rw": "R/W", + "type": "uint16", + "category": "Config" + }, + "lopr": { + "rw": "R/W", + "type": "TYPE", + "category": "Config" + }, + "lot": { + "rw": "R/W", + "type": "uint32", + "category": "Config" + }, + "loty": { + "rw": "R/W", + "type": "uint8", + "category": "Config" + }, + "lpsc": { + "rw": "R", + "type": "milliseconds", + "category": "Status" + }, + "lrc": { + "rw": "R", + "type": "TYPE", + "category": "Status" + }, + "lri": { + "rw": "R", + "type": "TYPE", + "category": "Status" + }, + "lrn": { + "rw": "W", + "type": "uint8", + "category": "Other" + }, + "lrr": { + "rw": "R", + "type": "TYPE", + "category": "Status" + }, + "lse": { + "rw": "R/W", + "type": "bool", + "category": "Config" + }, + "lto": { + "rw": "R", + "type": "TYPE", + "category": "Status" + }, + "lwf": { + "rw": "R", + "type": "TYPE", + "category": "Status" + }, + "map": { + "rw": "R/W", + "type": "array", + "category": "Config" + }, + "mca": { + "rw": "R/W", + "type": "uint8", + "category": "Config" + }, + "mcc": { + "rw": "R", + "type": "TYPE", + "category": "Status" + }, + "mcca": { + "rw": "R", + "type": "TYPE", + "category": "Status" + }, + "mce": { + "rw": "R/W", + "type": "TYPE", + "category": "Config" + }, + "mci": { + "rw": "R/W", + "type": "milliseconds", + "category": "Config" + }, + "mcpd": { + "rw": "R/W", + "type": "milliseconds", + "category": "Config" + }, + "mcpea": { + "rw": "R/W", + "type": "optional<...>", + "category": "Status" + }, + "mcr": { + "rw": "R/W", + "type": "TYPE", + "category": "Config" + }, + "mcs": { + "rw": "R", + "type": "TYPE", + "category": "Status" + }, + "mcu": { + "rw": "R/W", + "type": "TYPE", + "category": "Config" + }, + "men": { + "rw": "R/W", + "type": "TYPE", + "category": "Config" + }, + "mhe": { + "rw": "R/W", + "type": "TYPE", + "category": "Config" + }, + "mht": { + "rw": "R/W", + "type": "TYPE", + "category": "Config" + }, + "mlr": { + "rw": "R", + "type": "TYPE", + "category": "Status" + }, + "mlra": { + "rw": "R", + "type": "TYPE", + "category": "Status" + }, + "mmp": { + "rw": "R", + "type": "float", + "category": "Status" + }, + "modelStatus": { + "rw": "R", + "type": "uint8", + "category": "Status" + }, + "mptwt": { + "rw": "R/W", + "type": "milliseconds", + "category": "Config" + }, + "mpwst": { + "rw": "R/W", + "type": "milliseconds", + "category": "Config" + }, + "mqcn": { + "rw": "R/W", + "type": "TYPE", + "category": "Config" + }, + "mqg": { + "rw": "R/W", + "type": "TYPE", + "category": "Config" + }, + "mqss": { + "rw": "R/W", + "type": "TYPE", + "category": "Config" + }, + "msb": { + "rw": "R/W", + "type": "TYPE", + "category": "Config" + }, + "msp": { + "rw": "R/W", + "type": "TYPE", + "category": "Config" + }, + "msr": { + "rw": "R/W", + "type": "TYPE", + "category": "Config" + }, + "mtp": { + "rw": "R/W", + "type": "TYPE", + "category": "Config" + }, + "nif": { + "rw": "R", + "type": "string", + "category": "Status" + }, + "nmo": { + "rw": "R/W", + "type": "bool", + "category": "Config" + }, + "nrg": { + "rw": "R", + "type": "array", + "category": "Status" + }, + "ocppa": { + "rw": "R", + "type": "bool", + "category": "Status" + }, + "ocppaa": { + "rw": "R", + "type": "null or milliseconds", + "category": "Status" + }, + "ocppai": { + "rw": "R/W", + "type": "seconds", + "category": "Config" + }, + "ocppao": { + "rw": "R/W", + "type": "TYPE", + "category": "Status" + }, + "ocppc": { + "rw": "R", + "type": "bool", + "category": "Status" + }, + "ocppca": { + "rw": "R", + "type": "null or milliseconds", + "category": "Status" + }, + "ocppcc": { + "rw": "R/W", + "type": "string", + "category": "Config" + }, + "ocppck": { + "rw": "R/W", + "type": "string", + "category": "Config" + }, + "ocppcm": { + "rw": "R/W", + "type": "TYPE", + "category": "Status" + }, + "ocppcn": { + "rw": "R/W", + "type": "bool", + "category": "Config" + }, + "ocppcs": { + "rw": "R", + "type": "TYPE", + "category": "Status" + }, + "ocppd": { + "rw": "R/W", + "type": "string", + "category": "Config" + }, + "ocppe": { + "rw": "R/W", + "type": "bool", + "category": "Config" + }, + "ocppf": { + "rw": "R/W", + "type": "TYPE", + "category": "Config" + }, + "ocppg": { + "rw": "R/W", + "type": "bool", + "category": "Config" + }, + "ocpph": { + "rw": "R/W", + "type": "seconds", + "category": "Config" + }, + "ocppi": { + "rw": "R/W", + "type": "seconds", + "category": "Config" + }, + "ocppla": { + "rw": "R/W", + "type": "TYPE", + "category": "Status" + }, + "ocpple": { + "rw": "R", + "type": "string or null", + "category": "Status" + }, + "ocpplea": { + "rw": "R", + "type": "null or milliseconds", + "category": "Status" + }, + "ocpplo": { + "rw": "R/W", + "type": "TYPE", + "category": "Status" + }, + "ocppr": { + "rw": "R/W", + "type": "bool", + "category": "Config" + }, + "ocpprl": { + "rw": "R/W", + "type": "bool", + "category": "Config" + }, + "ocpps": { + "rw": "R", + "type": "bool", + "category": "Status" + }, + "ocppsc": { + "rw": "R/W", + "type": "string", + "category": "Config" + }, + "ocppss": { + "rw": "R/W", + "type": "bool", + "category": "Config" + }, + "ocppti": { + "rw": "R/W", + "type": "TYPE", + "category": "Status" + }, + "ocppu": { + "rw": "R/W", + "type": "string", + "category": "Config" + }, + "oct": { + "rw": "W", + "type": "string", + "category": "Other" + }, + "ocu": { + "rw": "R", + "type": "array", + "category": "Status" + }, + "oem": { + "rw": "R", + "type": "string", + "category": "Constant" + }, + "pakku": { + "rw": "R", + "type": "optional<...>", + "category": "Status" + }, + "pco": { + "rw": "R", + "type": "TYPE", + "category": "Config" + }, + "pdi": { + "rw": "R/W", + "type": "TYPE", + "category": "Config" + }, + "pgr": { + "rw": "R/W", + "type": "TYPE", + "category": "Config" + }, + "pgrid": { + "rw": "R", + "type": "optional<...>", + "category": "Status" + }, + "pgt": { + "rw": "R/W", + "type": "float", + "category": "Config" + }, + "pha": { + "rw": "R", + "type": "optional<...>", + "category": "Status" + }, + "pnp": { + "rw": "R", + "type": "uint8", + "category": "Status" + }, + "po": { + "rw": "R/W", + "type": "float", + "category": "Config" + }, + "ppv": { + "rw": "R", + "type": "optional<...>", + "category": "Status" + }, + "psh": { + "rw": "R/W", + "type": "float", + "category": "Config" + }, + "psmd": { + "rw": "R/W", + "type": "milliseconds", + "category": "Config" + }, + "pvopt_averagePAkku": { + "rw": "R", + "type": "float", + "category": "Status" + }, + "pvopt_averagePGrid": { + "rw": "R", + "type": "float", + "category": "Status" + }, + "pvopt_averagePPv": { + "rw": "R", + "type": "float", + "category": "Status" + }, + "pwm": { + "rw": "R", + "type": "uint8", + "category": "Status" + }, + "rbc": { + "rw": "R", + "type": "uint32", + "category": "Status" + }, + "rbt": { + "rw": "R", + "type": "milliseconds", + "category": "Status" + }, + "rdbf": { + "rw": "R/W", + "type": "TYPE", + "category": "Config" + }, + "rdbfe": { + "rw": "R/W", + "type": "TYPE", + "category": "Config" + }, + "rdbs": { + "rw": "R/W", + "type": "TYPE", + "category": "Config" + }, + "rdbse": { + "rw": "R/W", + "type": "TYPE", + "category": "Config" + }, + "rde": { + "rw": "R/W", + "type": "TYPE", + "category": "Config" + }, + "rdef": { + "rw": "R/W", + "type": "TYPE", + "category": "Config" + }, + "rdefe": { + "rw": "R/W", + "type": "TYPE", + "category": "Config" + }, + "rdes": { + "rw": "R/W", + "type": "TYPE", + "category": "Config" + }, + "rdese": { + "rw": "R/W", + "type": "TYPE", + "category": "Config" + }, + "rdpl": { + "rw": "R/W", + "type": "TYPE", + "category": "Config" + }, + "rdple": { + "rw": "R/W", + "type": "TYPE", + "category": "Config" + }, + "rdre": { + "rw": "R/W", + "type": "TYPE", + "category": "Config" + }, + "rdree": { + "rw": "R/W", + "type": "TYPE", + "category": "Config" + }, + "rfb": { + "rw": "R", + "type": "int", + "category": "Status" + }, + "rmaf": { + "rw": "R/W", + "type": "TYPE", + "category": "Config" + }, + "rmav": { + "rw": "R/W", + "type": "TYPE", + "category": "Config" + }, + "rmif": { + "rw": "R/W", + "type": "TYPE", + "category": "Config" + }, + "rmiv": { + "rw": "R/W", + "type": "TYPE", + "category": "Config" + }, + "rsa": { + "rw": "R/W", + "type": "TYPE", + "category": "Status" + }, + "rsre": { + "rw": "R/W", + "type": "TYPE", + "category": "Config" + }, + "rsrr": { + "rw": "R/W", + "type": "TYPE", + "category": "Config" + }, + "rssi": { + "rw": "R", + "type": "optional<...>", + "category": "Status" + }, + "rst": { + "rw": "W", + "type": "any", + "category": "Other" + }, + "scaa": { + "rw": "R", + "type": "milliseconds", + "category": "Status" + }, + "scan": { + "rw": "R", + "type": "array", + "category": "Status" + }, + "sch_satur": { + "rw": "R/W", + "type": "object", + "category": "Config" + }, + "sch_sund": { + "rw": "R/W", + "type": "object", + "category": "Config" + }, + "sch_week": { + "rw": "R/W", + "type": "object", + "category": "Config" + }, + "sdp": { + "rw": "R/W", + "type": "uint8_t", + "category": "Config" + }, + "sh": { + "rw": "R/W", + "type": "float", + "category": "Config" + }, + "smd": { + "rw": "R", + "type": "TYPE", + "category": "Status" + }, + "spl3": { + "rw": "R/W", + "type": "float", + "category": "Config" + }, + "sse": { + "rw": "R", + "type": "string", + "category": "Constant" + }, + "su": { + "rw": "R/W", + "type": "bool", + "category": "Config" + }, + "sua": { + "rw": "R/W", + "type": "bool", + "category": "Config" + }, + "sumd": { + "rw": "R/W", + "type": "milliseconds", + "category": "Config" + }, + "t0h": { + "rw": "R/W", + "type": "TYPE", + "category": "Config" + }, + "t0l": { + "rw": "R/W", + "type": "TYPE", + "category": "Config" + }, + "t1h": { + "rw": "R/W", + "type": "TYPE", + "category": "Config" + }, + "t1l": { + "rw": "R/W", + "type": "TYPE", + "category": "Config" + }, + "tab": { + "rw": "R", + "type": "TYPE", + "category": "Status" + }, + "tcl": { + "rw": "R/W", + "type": "TYPE", + "category": "Config" + }, + "tds": { + "rw": "R/W", + "type": "uint8", + "category": "Config" + }, + "tlf": { + "rw": "R", + "type": "bool", + "category": "Status" + }, + "tls": { + "rw": "R", + "type": "bool", + "category": "Status" + }, + "tma": { + "rw": "R", + "type": "array", + "category": "Status" + }, + "tof": { + "rw": "R/W", + "type": "minutes", + "category": "Config" + }, + "tpa": { + "rw": "R", + "type": "float", + "category": "Status" + }, + "trx": { + "rw": "R/W", + "type": "optional<...>", + "category": "Status" + }, + "tse": { + "rw": "R/W", + "type": "bool", + "category": "Config" + }, + "tsi": { + "rw": "R", + "type": "TYPE", + "category": "Status" + }, + "tsss": { + "rw": "R", + "type": "uint8", + "category": "Config" + }, + "typ": { + "rw": "R", + "type": "string", + "category": "Constant" + }, + "tzt": { + "rw": "R/W", + "type": "TYPE", + "category": "Config" + }, + "ufa": { + "rw": "R/W", + "type": "TYPE", + "category": "Config" + }, + "ufe": { + "rw": "R/W", + "type": "TYPE", + "category": "Config" + }, + "ufm": { + "rw": "R/W", + "type": "TYPE", + "category": "Config" + }, + "ufs": { + "rw": "R/W", + "type": "TYPE", + "category": "Config" + }, + "upo": { + "rw": "R/W", + "type": "bool", + "category": "Config" + }, + "ust": { + "rw": "R/W", + "type": "uint8", + "category": "Config" + }, + "utc": { + "rw": "R/W", + "type": "string", + "category": "Status" + }, + "var": { + "rw": "R", + "type": "uint8", + "category": "Constant" + }, + "wbw": { + "rw": "R", + "type": "TYPE", + "category": "Config" + }, + "wcb": { + "rw": "R", + "type": "string", + "category": "Status" + }, + "wda": { + "rw": "R/W", + "type": "TYPE", + "category": "Config" + }, + "wfb": { + "rw": "R", + "type": "array", + "category": "Status" + }, + "wh": { + "rw": "R", + "type": "double", + "category": "Status" + }, + "wifis": { + "rw": "R/W", + "type": "array", + "category": "Config" + }, + "wpb": { + "rw": "R", + "type": "array", + "category": "Status" + }, + "wsc": { + "rw": "R", + "type": "uint8", + "category": "Status" + }, + "wsl": { + "rw": "R", + "type": "TYPE", + "category": "Status" + }, + "wsm": { + "rw": "R", + "type": "string", + "category": "Status" + }, + "wsms": { + "rw": "R", + "type": "uint8", + "category": "Status" + }, + "wst": { + "rw": "R", + "type": "uint8", + "category": "Status" + }, + "zfo": { + "rw": "R/W", + "type": "float", + "category": "Config" + } + } +} diff --git a/tests/integration/contract/classification.json b/tests/integration/contract/classification.json new file mode 100644 index 0000000..aa9626d --- /dev/null +++ b/tests/integration/contract/classification.json @@ -0,0 +1,401 @@ +{ + "_comment": "Keys of the official table that the integration does not map to a Tag. 'decision': open = nobody has decided yet whether the gap is intended. Remove an entry when the key gets supported.", + "handled_outside_tag": { + "c0e": "RFID card keys of firmware 60.0+ are requested through the FILTER_CARDS_* lists and read in sensor.py, not through Tag", + "c0i": "RFID card keys of firmware 60.0+ are requested through the FILTER_CARDS_* lists and read in sensor.py, not through Tag", + "c0n": "RFID card keys of firmware 60.0+ are requested through the FILTER_CARDS_* lists and read in sensor.py, not through Tag", + "c1e": "RFID card keys of firmware 60.0+ are requested through the FILTER_CARDS_* lists and read in sensor.py, not through Tag", + "c1i": "RFID card keys of firmware 60.0+ are requested through the FILTER_CARDS_* lists and read in sensor.py, not through Tag", + "c1n": "RFID card keys of firmware 60.0+ are requested through the FILTER_CARDS_* lists and read in sensor.py, not through Tag", + "c2e": "RFID card keys of firmware 60.0+ are requested through the FILTER_CARDS_* lists and read in sensor.py, not through Tag", + "c2i": "RFID card keys of firmware 60.0+ are requested through the FILTER_CARDS_* lists and read in sensor.py, not through Tag", + "c2n": "RFID card keys of firmware 60.0+ are requested through the FILTER_CARDS_* lists and read in sensor.py, not through Tag", + "c3e": "RFID card keys of firmware 60.0+ are requested through the FILTER_CARDS_* lists and read in sensor.py, not through Tag", + "c3i": "RFID card keys of firmware 60.0+ are requested through the FILTER_CARDS_* lists and read in sensor.py, not through Tag", + "c3n": "RFID card keys of firmware 60.0+ are requested through the FILTER_CARDS_* lists and read in sensor.py, not through Tag", + "c4e": "RFID card keys of firmware 60.0+ are requested through the FILTER_CARDS_* lists and read in sensor.py, not through Tag", + "c4i": "RFID card keys of firmware 60.0+ are requested through the FILTER_CARDS_* lists and read in sensor.py, not through Tag", + "c4n": "RFID card keys of firmware 60.0+ are requested through the FILTER_CARDS_* lists and read in sensor.py, not through Tag", + "c5e": "RFID card keys of firmware 60.0+ are requested through the FILTER_CARDS_* lists and read in sensor.py, not through Tag", + "c5i": "RFID card keys of firmware 60.0+ are requested through the FILTER_CARDS_* lists and read in sensor.py, not through Tag", + "c5n": "RFID card keys of firmware 60.0+ are requested through the FILTER_CARDS_* lists and read in sensor.py, not through Tag", + "c6e": "RFID card keys of firmware 60.0+ are requested through the FILTER_CARDS_* lists and read in sensor.py, not through Tag", + "c6i": "RFID card keys of firmware 60.0+ are requested through the FILTER_CARDS_* lists and read in sensor.py, not through Tag", + "c6n": "RFID card keys of firmware 60.0+ are requested through the FILTER_CARDS_* lists and read in sensor.py, not through Tag", + "c7e": "RFID card keys of firmware 60.0+ are requested through the FILTER_CARDS_* lists and read in sensor.py, not through Tag", + "c7i": "RFID card keys of firmware 60.0+ are requested through the FILTER_CARDS_* lists and read in sensor.py, not through Tag", + "c7n": "RFID card keys of firmware 60.0+ are requested through the FILTER_CARDS_* lists and read in sensor.py, not through Tag", + "c8e": "RFID card keys of firmware 60.0+ are requested through the FILTER_CARDS_* lists and read in sensor.py, not through Tag", + "c8i": "RFID card keys of firmware 60.0+ are requested through the FILTER_CARDS_* lists and read in sensor.py, not through Tag", + "c8n": "RFID card keys of firmware 60.0+ are requested through the FILTER_CARDS_* lists and read in sensor.py, not through Tag", + "c9e": "RFID card keys of firmware 60.0+ are requested through the FILTER_CARDS_* lists and read in sensor.py, not through Tag", + "c9i": "RFID card keys of firmware 60.0+ are requested through the FILTER_CARDS_* lists and read in sensor.py, not through Tag", + "c9n": "RFID card keys of firmware 60.0+ are requested through the FILTER_CARDS_* lists and read in sensor.py, not through Tag" + }, + "not_supported": { + "avgfhz": { + "group": "grid frequency, grid protection and ramp-up", + "decision": "open" + }, + "gmtr": { + "group": "grid frequency, grid protection and ramp-up", + "decision": "open" + }, + "gsa": { + "group": "grid frequency, grid protection and ramp-up", + "decision": "open" + }, + "pgr": { + "group": "grid frequency, grid protection and ramp-up", + "decision": "open" + }, + "rmaf": { + "group": "grid frequency, grid protection and ramp-up", + "decision": "open" + }, + "rmav": { + "group": "grid frequency, grid protection and ramp-up", + "decision": "open" + }, + "rmif": { + "group": "grid frequency, grid protection and ramp-up", + "decision": "open" + }, + "rmiv": { + "group": "grid frequency, grid protection and ramp-up", + "decision": "open" + }, + "rsa": { + "group": "grid frequency, grid protection and ramp-up", + "decision": "open" + }, + "rsre": { + "group": "grid frequency, grid protection and ramp-up", + "decision": "open" + }, + "rsrr": { + "group": "grid frequency, grid protection and ramp-up", + "decision": "open" + }, + "ufa": { + "group": "grid frequency, grid protection and ramp-up", + "decision": "open" + }, + "ufe": { + "group": "grid frequency, grid protection and ramp-up", + "decision": "open" + }, + "ufm": { + "group": "grid frequency, grid protection and ramp-up", + "decision": "open" + }, + "ufs": { + "group": "grid frequency, grid protection and ramp-up", + "decision": "open" + }, + "rdbfe": { + "group": "random start/stop delays (end timestamps)", + "decision": "open" + }, + "rdbse": { + "group": "random start/stop delays (end timestamps)", + "decision": "open" + }, + "rdefe": { + "group": "random start/stop delays (end timestamps)", + "decision": "open" + }, + "rdese": { + "group": "random start/stop delays (end timestamps)", + "decision": "open" + }, + "rdple": { + "group": "random start/stop delays (end timestamps)", + "decision": "open" + }, + "rdree": { + "group": "random start/stop delays (end timestamps)", + "decision": "open" + }, + "ocppao": { + "group": "OCPP", + "decision": "open" + }, + "ocppcm": { + "group": "OCPP", + "decision": "open" + }, + "ocppcs": { + "group": "OCPP", + "decision": "open" + }, + "ocppf": { + "group": "OCPP", + "decision": "open" + }, + "ocppla": { + "group": "OCPP", + "decision": "open" + }, + "ocpplo": { + "group": "OCPP", + "decision": "open" + }, + "ocppti": { + "group": "OCPP", + "decision": "open" + }, + "lrc": { + "group": "RFID reading", + "decision": "open" + }, + "lri": { + "group": "RFID reading", + "decision": "open" + }, + "lrr": { + "group": "RFID reading", + "decision": "open" + }, + "rde": { + "group": "RFID reading", + "decision": "open" + }, + "tsi": { + "group": "RFID reading", + "decision": "open" + }, + "ccd": { + "group": "controller scan and cloud key", + "decision": "open" + }, + "cmmr": { + "group": "controller scan and cloud key", + "decision": "open" + }, + "cmp": { + "group": "controller scan and cloud key", + "decision": "open" + }, + "cms": { + "group": "controller scan and cloud key", + "decision": "open" + }, + "csa": { + "group": "controller scan and cloud key", + "decision": "open" + }, + "pco": { + "group": "controller scan and cloud key", + "decision": "open" + }, + "led": { + "group": "LED strip and animation", + "decision": "open" + }, + "t0h": { + "group": "LED strip and animation", + "decision": "open" + }, + "t0l": { + "group": "LED strip and animation", + "decision": "open" + }, + "t1h": { + "group": "LED strip and animation", + "decision": "open" + }, + "t1l": { + "group": "LED strip and animation", + "decision": "open" + }, + "die": { + "group": "digital input", + "decision": "open" + }, + "dii": { + "group": "digital input", + "decision": "open" + }, + "pdi": { + "group": "digital input", + "decision": "open" + }, + "mhe": { + "group": "MQTT Home Assistant discovery", + "decision": "open" + }, + "mht": { + "group": "MQTT Home Assistant discovery", + "decision": "open" + }, + "lto": { + "group": "time and timezone", + "decision": "open" + }, + "tab": { + "group": "time and timezone", + "decision": "open" + }, + "tzt": { + "group": "time and timezone", + "decision": "open" + }, + "bar": { + "group": "network and buttons", + "decision": "open" + }, + "hla": { + "group": "network and buttons", + "decision": "open" + }, + "lbl": { + "group": "network and buttons", + "decision": "open" + }, + "wbw": { + "group": "network and buttons", + "decision": "open" + }, + "la1": { + "group": "current limits and load balancing", + "decision": "open" + }, + "la3": { + "group": "current limits and load balancing", + "decision": "open" + }, + "lopr": { + "group": "current limits and load balancing", + "decision": "open" + }, + "tcl": { + "group": "current limits and load balancing", + "decision": "open" + }, + "dsrc": { + "group": "data sources", + "decision": "open" + }, + "smd": { + "group": "data sources", + "decision": "open" + } + }, + "undocumented_in_code": { + "aus": { + "reason": "listed only in the per-firmware key lists, missing from the main key table" + }, + "bri": { + "reason": "not in the charger key table; the integration uses them for the go-eController or for MQTT/modbus settings (source not verified)" + }, + "cae": { + "reason": "documented in cloudapi-en.md (text), missing from the key table" + }, + "cce": { + "reason": "listed only in the per-firmware key lists, missing from the main key table" + }, + "ccf": { + "reason": "not in the charger key table; the integration uses them for the go-eController or for MQTT/modbus settings (source not verified)" + }, + "ccn": { + "reason": "not in the charger key table; the integration uses them for the go-eController or for MQTT/modbus settings (source not verified)" + }, + "ccp": { + "reason": "not in the charger key table; the integration uses them for the go-eController or for MQTT/modbus settings (source not verified)" + }, + "cec": { + "reason": "not in the charger key table; the integration uses them for the go-eController or for MQTT/modbus settings (source not verified)" + }, + "cpc": { + "reason": "not in the charger key table; the integration uses them for the go-eController or for MQTT/modbus settings (source not verified)" + }, + "ecp": { + "reason": "not in the charger key table; the integration uses them for the go-eController or for MQTT/modbus settings (source not verified)" + }, + "evt": { + "reason": "listed only in the per-firmware key lists, missing from the main key table" + }, + "iim": { + "reason": "not in the charger key table; the integration uses them for the go-eController or for MQTT/modbus settings (source not verified)" + }, + "ips": { + "reason": "not in the charger key table; the integration uses them for the go-eController or for MQTT/modbus settings (source not verified)" + }, + "isgo": { + "reason": "listed only in the per-firmware key lists, missing from the main key table" + }, + "isn": { + "reason": "not in the charger key table; the integration uses them for the go-eController or for MQTT/modbus settings (source not verified)" + }, + "iso2ena": { + "reason": "used by the integration (selects), no row in any official key list that was checked" + }, + "isv": { + "reason": "not in the charger key table; the integration uses them for the go-eController or for MQTT/modbus settings (source not verified)" + }, + "mck": { + "reason": "not in the charger key table; the integration uses them for the go-eController or for MQTT/modbus settings (source not verified)" + }, + "mecd": { + "reason": "not in the charger key table; the integration uses them for the go-eController or for MQTT/modbus settings (source not verified)" + }, + "mece": { + "reason": "not in the charger key table; the integration uses them for the go-eController or for MQTT/modbus settings (source not verified)" + }, + "mecf": { + "reason": "not in the charger key table; the integration uses them for the go-eController or for MQTT/modbus settings (source not verified)" + }, + "mecu": { + "reason": "not in the charger key table; the integration uses them for the go-eController or for MQTT/modbus settings (source not verified)" + }, + "mme": { + "reason": "not in the charger key table; the integration uses them for the go-eController or for MQTT/modbus settings (source not verified)" + }, + "mmh": { + "reason": "not in the charger key table; the integration uses them for the go-eController or for MQTT/modbus settings (source not verified)" + }, + "mqcc": { + "reason": "not in the charger key table; the integration uses them for the go-eController or for MQTT/modbus settings (source not verified)" + }, + "mro": { + "reason": "not in the charger key table; the integration uses them for the go-eController or for MQTT/modbus settings (source not verified)" + }, + "msc": { + "reason": "not in the charger key table; the integration uses them for the go-eController or for MQTT/modbus settings (source not verified)" + }, + "mwo": { + "reason": "not in the charger key table; the integration uses them for the go-eController or for MQTT/modbus settings (source not verified)" + }, + "ocppdp": { + "reason": "listed only in the per-firmware key lists, missing from the main key table" + }, + "ocppmp": { + "reason": "listed only in the per-firmware key lists, missing from the main key table" + }, + "ocpptp": { + "reason": "listed only in the per-firmware key lists, missing from the main key table" + }, + "orsch": { + "reason": "listed only in the per-firmware key lists, missing from the main key table" + }, + "psm": { + "reason": "used by the integration (selects), no row in any official key list that was checked" + }, + "simo": { + "reason": "listed only in the per-firmware key lists, missing from the main key table" + }, + "usn": { + "reason": "not in the charger key table; the integration uses them for the go-eController or for MQTT/modbus settings (source not verified)" + }, + "usv": { + "reason": "not in the charger key table; the integration uses them for the go-eController or for MQTT/modbus settings (source not verified)" + }, + "zfocore": { + "reason": "internal pseudo keys of the integration (buttons), not part of the go-e API" + }, + "zforeal": { + "reason": "internal pseudo keys of the integration (buttons), not part of the go-e API" + } + } +} diff --git a/tests/integration/contract/controller_keys.json b/tests/integration/contract/controller_keys.json new file mode 100644 index 0000000..2411874 --- /dev/null +++ b/tests/integration/contract/controller_keys.json @@ -0,0 +1,420 @@ +{ + "source": { + "repository": "goecharger/go-eController-API", + "file": "apikeys-en.md", + "commit": "094d08d", + "date": "2026-04-20" + }, + "keys": { + "awc": { + "rw": "R/W", + "type": "TYPE", + "category": "Config" + }, + "awcp": { + "rw": "R", + "type": "TYPE", + "category": "Status" + }, + "awp": { + "rw": "R/W", + "type": "TYPE", + "category": "Config" + }, + "awpl": { + "rw": "W", + "type": "TYPE", + "category": "Status" + }, + "bri": { + "rw": "R", + "type": "TYPE", + "category": "Status" + }, + "cce": { + "rw": "R", + "type": "TYPE", + "category": "Status" + }, + "ccf": { + "rw": "R/W", + "type": "TYPE", + "category": "Status" + }, + "ccn": { + "rw": "R/W", + "type": "TYPE", + "category": "Config" + }, + "ccp": { + "rw": "R", + "type": "TYPE", + "category": "Status" + }, + "ccw": { + "rw": "R", + "type": "TYPE", + "category": "Status" + }, + "cec": { + "rw": "R", + "type": "TYPE", + "category": "Status" + }, + "cle": { + "rw": "R", + "type": "TYPE", + "category": "Status" + }, + "clea": { + "rw": "R", + "type": "TYPE", + "category": "Status" + }, + "cpc": { + "rw": "R", + "type": "TYPE", + "category": "Status" + }, + "cwe": { + "rw": "R/W", + "type": "TYPE", + "category": "Config" + }, + "data": { + "rw": "R", + "type": "TYPE", + "category": "Status" + }, + "delw": { + "rw": "W", + "type": "TYPE", + "category": "Unknown" + }, + "dll": { + "rw": "R", + "type": "TYPE", + "category": "Status" + }, + "dns": { + "rw": "R", + "type": "TYPE", + "category": "Status" + }, + "fna": { + "rw": "R/W", + "type": "TYPE", + "category": "Config" + }, + "fwv": { + "rw": "R", + "type": "TYPE", + "category": "Status" + }, + "hai": { + "rw": "R/W", + "type": "TYPE", + "category": "Config" + }, + "host": { + "rw": "R", + "type": "TYPE", + "category": "Status" + }, + "hsa": { + "rw": "W", + "type": "TYPE", + "category": "Config" + }, + "iim": { + "rw": "R/W", + "type": "TYPE", + "category": "Config" + }, + "ips": { + "rw": "R/W", + "type": "TYPE", + "category": "Config" + }, + "isn": { + "rw": "R", + "type": "TYPE", + "category": "Config" + }, + "isv": { + "rw": "R", + "type": "TYPE", + "category": "Config" + }, + "loc": { + "rw": "R", + "type": "TYPE", + "category": "Status" + }, + "lwf": { + "rw": "R", + "type": "TYPE", + "category": "Status" + }, + "mcc": { + "rw": "R", + "type": "TYPE", + "category": "Status" + }, + "mcca": { + "rw": "R", + "type": "TYPE", + "category": "Status" + }, + "mce": { + "rw": "R/W", + "type": "TYPE", + "category": "Config" + }, + "mcr": { + "rw": "R/W", + "type": "TYPE", + "category": "Config" + }, + "mcs": { + "rw": "R", + "type": "TYPE", + "category": "Status" + }, + "mcu": { + "rw": "R/W", + "type": "TYPE", + "category": "Config" + }, + "mecd": { + "rw": "R", + "type": "TYPE", + "category": "Status" + }, + "mece": { + "rw": "R/W", + "type": "TYPE", + "category": "Config" + }, + "mecf": { + "rw": "R/W", + "type": "TYPE", + "category": "Status" + }, + "mecu": { + "rw": "R/W", + "type": "TYPE", + "category": "Config" + }, + "men": { + "rw": "R/W", + "type": "TYPE", + "category": "Config" + }, + "mlr": { + "rw": "R", + "type": "TYPE", + "category": "Status" + }, + "mlra": { + "rw": "R", + "type": "TYPE", + "category": "Status" + }, + "mme": { + "rw": "R/W", + "type": "TYPE", + "category": "Config" + }, + "mmh": { + "rw": "R/W", + "type": "TYPE", + "category": "Config" + }, + "mmp": { + "rw": "R/W", + "type": "TYPE", + "category": "Config" + }, + "mqcn": { + "rw": "R/W", + "type": "TYPE", + "category": "Config" + }, + "mqg": { + "rw": "R/W", + "type": "TYPE", + "category": "Config" + }, + "mqss": { + "rw": "R/W", + "type": "TYPE", + "category": "Config" + }, + "msb": { + "rw": "R/W", + "type": "TYPE", + "category": "Config" + }, + "msp": { + "rw": "R/W", + "type": "TYPE", + "category": "Config" + }, + "msr": { + "rw": "R/W", + "type": "TYPE", + "category": "Config" + }, + "mtp": { + "rw": "R/W", + "type": "TYPE", + "category": "Config" + }, + "nif": { + "rw": "R", + "type": "TYPE", + "category": "Status" + }, + "oct": { + "rw": "W", + "type": "TYPE", + "category": "Config" + }, + "ocu": { + "rw": "R", + "type": "TYPE", + "category": "Config" + }, + "oem": { + "rw": "R", + "type": "TYPE", + "category": "Constant" + }, + "rbc": { + "rw": "R", + "type": "TYPE", + "category": "Status" + }, + "rbt": { + "rw": "R", + "type": "TYPE", + "category": "Status" + }, + "rssi": { + "rw": "R", + "type": "TYPE", + "category": "Status" + }, + "rst": { + "rw": "W", + "type": "TYPE", + "category": "Unknown" + }, + "scaa": { + "rw": "R", + "type": "TYPE", + "category": "Status" + }, + "scan": { + "rw": "R", + "type": "TYPE", + "category": "Status" + }, + "sse": { + "rw": "R", + "type": "TYPE", + "category": "Constant" + }, + "tds": { + "rw": "R/W", + "type": "TYPE", + "category": "Config" + }, + "tof": { + "rw": "R/W", + "type": "TYPE", + "category": "Config" + }, + "tse": { + "rw": "R/W", + "type": "TYPE", + "category": "Config" + }, + "tsss": { + "rw": "R", + "type": "TYPE", + "category": "Status" + }, + "typ": { + "rw": "R", + "type": "TYPE", + "category": "Constant" + }, + "usn": { + "rw": "R", + "type": "TYPE", + "category": "Config" + }, + "usv": { + "rw": "R", + "type": "TYPE", + "category": "Status" + }, + "utc": { + "rw": "R/W", + "type": "TYPE", + "category": "Status" + }, + "wcb": { + "rw": "R", + "type": "TYPE", + "category": "Status" + }, + "wda": { + "rw": "R/W", + "type": "TYPE", + "category": "Config" + }, + "wfb": { + "rw": "R", + "type": "TYPE", + "category": "Status" + }, + "wifis": { + "rw": "R/W", + "type": "TYPE", + "category": "Config" + }, + "wpb": { + "rw": "R", + "type": "TYPE", + "category": "Status" + }, + "wsc": { + "rw": "R", + "type": "TYPE", + "category": "Status" + }, + "wsl": { + "rw": "R", + "type": "TYPE", + "category": "Status" + }, + "wsm": { + "rw": "R", + "type": "TYPE", + "category": "Status" + }, + "wsms": { + "rw": "R", + "type": "TYPE", + "category": "Status" + }, + "wst": { + "rw": "R", + "type": "TYPE", + "category": "Status" + } + } +} diff --git a/tests/integration/contract/deviations.json b/tests/integration/contract/deviations.json new file mode 100644 index 0000000..911d22f --- /dev/null +++ b/tests/integration/contract/deviations.json @@ -0,0 +1,201 @@ +{ + "_comment": "Differences between the official key table and keys.py. status needs_decision: not yet clear whether the code is wrong, the difference is intended, or the documentation has a gap. Remove an entry when it is resolved. per_firmware_lists: values of the same key in the per-firmware key lists (59.4 to 60.4); they often differ from the main table.", + "deviations": [ + { + "key": "cco", + "aspect": "writeable", + "documented": "R/W", + "code": "read-only", + "status": "needs_decision", + "per_firmware_lists": { + "59.4": "R/W/Config", + "60.2": "R/W/Config", + "60.3": "R/W/Config", + "60.4": "R/W/Config" + }, + "per_firmware_lists_agree_with": "main table (code differs from both)" + }, + { + "key": "di1", + "aspect": "writeable", + "documented": "R/W", + "code": "read-only", + "status": "needs_decision", + "per_firmware_lists": { + "59.4": "R/W/Config", + "60.2": "R/W/Config", + "60.3": "R/W/Config", + "60.4": "R/W/Config" + }, + "per_firmware_lists_agree_with": "main table (code differs from both)" + }, + { + "key": "esk", + "aspect": "writeable", + "documented": "R/W", + "code": "read-only", + "status": "needs_decision", + "per_firmware_lists": { + "59.4": "R/W/Config", + "60.2": "R/W/Config", + "60.3": "R/W/Config", + "60.4": "R/W/Config" + }, + "per_firmware_lists_agree_with": "main table (code differs from both)" + }, + { + "key": "frm", + "aspect": "writeable", + "documented": "R", + "code": "writeable", + "status": "needs_decision", + "per_firmware_lists": { + "59.4": "R/W/Config", + "60.2": "R/W/Config", + "60.3": "R/W/Config", + "60.4": "R/W/Config" + }, + "per_firmware_lists_agree_with": "code (main table is the odd one out)" + }, + { + "key": "rdbf", + "aspect": "writeable", + "documented": "R/W", + "code": "read-only", + "status": "needs_decision", + "per_firmware_lists": { + "59.4": "R/W/Config", + "60.2": "R/W/Config", + "60.3": "R/W/Config", + "60.4": "R/W/Config" + }, + "per_firmware_lists_agree_with": "main table (code differs from both)" + }, + { + "key": "rdbs", + "aspect": "writeable", + "documented": "R/W", + "code": "read-only", + "status": "needs_decision", + "per_firmware_lists": { + "59.4": "R/W/Config", + "60.2": "R/W/Config", + "60.3": "R/W/Config", + "60.4": "R/W/Config" + }, + "per_firmware_lists_agree_with": "main table (code differs from both)" + }, + { + "key": "rdef", + "aspect": "writeable", + "documented": "R/W", + "code": "read-only", + "status": "needs_decision", + "per_firmware_lists": { + "59.4": "R/W/Config", + "60.2": "R/W/Config", + "60.3": "R/W/Config", + "60.4": "R/W/Config" + }, + "per_firmware_lists_agree_with": "main table (code differs from both)" + }, + { + "key": "rdes", + "aspect": "writeable", + "documented": "R/W", + "code": "read-only", + "status": "needs_decision", + "per_firmware_lists": { + "59.4": "R/W/Config", + "60.2": "R/W/Config", + "60.3": "R/W/Config", + "60.4": "R/W/Config" + }, + "per_firmware_lists_agree_with": "main table (code differs from both)" + }, + { + "key": "rdpl", + "aspect": "writeable", + "documented": "R/W", + "code": "read-only", + "status": "needs_decision", + "per_firmware_lists": { + "59.4": "R/W/Config", + "60.2": "R/W/Config", + "60.3": "R/W/Config", + "60.4": "R/W/Config" + }, + "per_firmware_lists_agree_with": "main table (code differs from both)" + }, + { + "key": "rdre", + "aspect": "writeable", + "documented": "R/W", + "code": "read-only", + "status": "needs_decision", + "per_firmware_lists": { + "59.4": "R/W/Config", + "60.2": "R/W/Config", + "60.3": "R/W/Config", + "60.4": "R/W/Config" + }, + "per_firmware_lists_agree_with": "main table (code differs from both)" + }, + { + "key": "deltaa", + "aspect": "category", + "documented": "Other", + "code": "Status", + "status": "needs_decision", + "per_firmware_lists": { + "59.4": "R/Unknown", + "60.2": "R/Unknown", + "60.3": "R/Unknown", + "60.4": "R/Unknown" + }, + "per_firmware_lists_agree_with": "main table (code differs from both)" + }, + { + "key": "di1", + "aspect": "category", + "documented": "Config", + "code": "Status", + "status": "needs_decision", + "per_firmware_lists": { + "59.4": "R/W/Config", + "60.2": "R/W/Config", + "60.3": "R/W/Config", + "60.4": "R/W/Config" + }, + "per_firmware_lists_agree_with": "main table (code differs from both)" + }, + { + "key": "ids", + "aspect": "category", + "documented": "Other", + "code": "Status", + "status": "needs_decision", + "per_firmware_lists": { + "59.4": "W/Status", + "60.2": "W/Status", + "60.3": "W/Status", + "60.4": "W/Status" + }, + "per_firmware_lists_agree_with": "code (main table is the odd one out)" + }, + { + "key": "lcs", + "aspect": "category", + "documented": "Status", + "code": "Config", + "status": "needs_decision", + "per_firmware_lists": { + "59.4": "R/Status", + "60.2": "R/Status", + "60.3": "R/Status", + "60.4": "R/Status" + }, + "per_firmware_lists_agree_with": "main table (code differs from both)" + } + ] +} diff --git a/tests/integration/contract/generate_api_keys.py b/tests/integration/contract/generate_api_keys.py new file mode 100644 index 0000000..271eaf6 --- /dev/null +++ b/tests/integration/contract/generate_api_keys.py @@ -0,0 +1,37 @@ +"""Builds api_keys.json from the official key table (go-eCharger-API-v2, API_KEYS_FIRMWARE/apikeys-en.md). + +Only key names and flags are stored (R/W, type, category), not the descriptions. +Usage: python generate_api_keys.py [controller] +With the last argument the key table of the go-eController API (goecharger/go-eController-API) is read +and controller_keys.json is written instead. +""" +import json +import re +import sys +from pathlib import Path + + +def parse(path): + keys = {} + for line in Path(path).read_text().splitlines(): + if line.startswith("|--") or line.startswith("| Key") or "|" not in line: + continue + cells = [c.strip() for c in line.strip().strip("|").split("|")] + if len(cells) < 5: + continue + names = re.sub(r"\(.*?\)", "", cells[0]).split() + for name in (n for n in names if re.fullmatch(r"[A-Za-z0-9_]+", n)): + keys[name] = {"rw": cells[1], "type": re.sub(r"<.*", "<...>", cells[2]), "category": cells[3]} + return dict(sorted(keys.items())) + + +if __name__ == "__main__": + source, commit, date = sys.argv[1:4] + controller = len(sys.argv) > 4 and sys.argv[4] == "controller" + origin = ("goecharger/go-eController-API", "apikeys-en.md") if controller else \ + ("goecharger/go-eCharger-API-v2", "API_KEYS_FIRMWARE/apikeys-en.md") + out = {"source": {"repository": origin[0], "file": origin[1], "commit": commit, "date": date}, + "keys": parse(source)} + target = Path(__file__).with_name("controller_keys.json" if controller else "api_keys.json") + target.write_text(json.dumps(out, indent=1) + "\n") + print(f"{len(out['keys'])} keys written to {target}") diff --git a/tests/integration/factories.py b/tests/integration/factories.py new file mode 100644 index 0000000..b44c138 --- /dev/null +++ b/tests/integration/factories.py @@ -0,0 +1,45 @@ +"""Shared builders for the tests: config entries, coordinators and fake WebSocket connections.""" +from collections import ChainMap +from types import SimpleNamespace +from unittest.mock import AsyncMock + +from pytest_homeassistant_custom_component.common import MockConfigEntry + +from custom_components.goecharger_api2 import GoeChargerDataUpdateCoordinator +from custom_components.goecharger_api2.const import DOMAIN + + +def make_entry(hass, serial="123456", intg_type="charger", mode="lan", password=None, **extra): + data = {"mode": mode, "id": serial, "integration_type": intg_type, "scan_interval": 30, **extra} + if mode == "lan": + data.setdefault("host", "192.0.2.10") + else: + data.setdefault("token", "token") + if password: + data["password"] = password + entry = MockConfigEntry(domain=DOMAIN, title=f"go-e {serial}", data=data) + entry.add_to_hass(hass) + return entry + + +def make_coordinator(hass, data=None, websocket=False, **entry_kwargs): + """Real coordinator and bridge. `data` becomes coordinator.data (a plain dict, as the entities only read it).""" + coordinator = GoeChargerDataUpdateCoordinator(hass, make_entry(hass, **entry_kwargs)) + bridge = coordinator.bridge + coordinator.data = data if data is not None else ChainMap( + bridge._ws_states, bridge._config, bridge._states, bridge._versions) + if websocket: + bridge.ws_connected = True + bridge._ws_connection = SimpleNamespace(send_json=AsyncMock()) + return coordinator + + +def make_bridge(hass, intg_type="charger", password=None, websocket=False): + from homeassistant.helpers.aiohttp_client import async_get_clientsession + from custom_components.goecharger_api2.pygoecharger_ha import GoeChargerApiV2Bridge + bridge = GoeChargerApiV2Bridge(web_session=async_get_clientsession(hass), intg_type=intg_type, + host="192.0.2.10", access_password=password) + if websocket: + bridge.ws_connected = True + bridge._ws_connection = SimpleNamespace(send_json=AsyncMock()) + return bridge diff --git a/tests/integration/pytest.ini b/tests/integration/pytest.ini new file mode 100644 index 0000000..19bb58a --- /dev/null +++ b/tests/integration/pytest.ini @@ -0,0 +1,6 @@ +[pytest] +asyncio_mode = auto +pythonpath = ../.. +markers = + known_bug(description): the test describes a bug that is not fixed yet; it fails on purpose. Deselect with -m "not known_bug". + needs_decision(description): documentation and code differ; it is not yet decided whether this is intended or a documentation gap. Fails on purpose, not blocking. Deselect with -m "not needs_decision". diff --git a/tests/integration/snapshots/entity_ids.json b/tests/integration/snapshots/entity_ids.json new file mode 100644 index 0000000..4433b30 --- /dev/null +++ b/tests/integration/snapshots/entity_ids.json @@ -0,0 +1,475 @@ +{ + "charger_lan": [ + "binary_sensor.goe_123456_adi", + "binary_sensor.goe_123456_alw", + "binary_sensor.goe_123456_car_0", + "binary_sensor.goe_123456_ctrls_0_connected", + "binary_sensor.goe_123456_ctrls_0_paired", + "binary_sensor.goe_123456_di1", + "binary_sensor.goe_123456_esk", + "binary_sensor.goe_123456_fsp", + "binary_sensor.goe_123456_pha_0", + "binary_sensor.goe_123456_pha_1", + "binary_sensor.goe_123456_pha_2", + "binary_sensor.goe_123456_pha_3", + "binary_sensor.goe_123456_pha_4", + "binary_sensor.goe_123456_pha_5", + "binary_sensor.goe_123456_tlf", + "binary_sensor.goe_123456_tls", + "button.goe_123456_rst", + "button.goe_123456_zfocore", + "button.goe_123456_zforeal", + "number.goe_123456_ama", + "number.goe_123456_amp", + "number.goe_123456_ate", + "number.goe_123456_att", + "number.goe_123456_awp", + "number.goe_123456_cco", + "number.goe_123456_dwo", + "number.goe_123456_fmt", + "number.goe_123456_fst", + "number.goe_123456_lbr", + "number.goe_123456_lof", + "number.goe_123456_lop", + "number.goe_123456_lot_amp", + "number.goe_123456_lot_dyn", + "number.goe_123456_lot_sta", + "number.goe_123456_lot_ts", + "number.goe_123456_mca", + "number.goe_123456_mci", + "number.goe_123456_mcpd", + "number.goe_123456_mcpea", + "number.goe_123456_mptwt", + "number.goe_123456_mpwst", + "number.goe_123456_pgt", + "number.goe_123456_po", + "number.goe_123456_psh", + "number.goe_123456_psmd", + "number.goe_123456_rdbf", + "number.goe_123456_rdbs", + "number.goe_123456_rdef", + "number.goe_123456_rdes", + "number.goe_123456_rdpl", + "number.goe_123456_rdre", + "number.goe_123456_sh", + "number.goe_123456_spl3", + "number.goe_123456_sumd", + "number.goe_123456_zfo", + "select.goe_123456_bac", + "select.goe_123456_ct", + "select.goe_123456_frc", + "select.goe_123456_frm", + "select.goe_123456_iso2ena", + "select.goe_123456_lmo", + "select.goe_123456_loty", + "select.goe_123456_psm", + "select.goe_123456_sch_satur_control", + "select.goe_123456_sch_sund_control", + "select.goe_123456_sch_week_control", + "select.goe_123456_sdp", + "select.goe_123456_tds", + "select.goe_123456_trx", + "select.goe_123456_ust", + "sensor.goe_123456_acu", + "sensor.goe_123456_amt", + "sensor.goe_123456_awcp_marketprice", + "sensor.goe_123456_car", + "sensor.goe_123456_car_value", + "sensor.goe_123456_cards_0_energy", + "sensor.goe_123456_cards_1_energy", + "sensor.goe_123456_cards_2_energy", + "sensor.goe_123456_cards_3_energy", + "sensor.goe_123456_cards_4_energy", + "sensor.goe_123456_cards_5_energy", + "sensor.goe_123456_cards_6_energy", + "sensor.goe_123456_cards_7_energy", + "sensor.goe_123456_cards_8_energy", + "sensor.goe_123456_cards_9_energy", + "sensor.goe_123456_cbl", + "sensor.goe_123456_ccw_ssid", + "sensor.goe_123456_cdi_type", + "sensor.goe_123456_cdi_value", + "sensor.goe_123456_cll", + "sensor.goe_123456_cus", + "sensor.goe_123456_cus_value", + "sensor.goe_123456_deltaa", + "sensor.goe_123456_deltap", + "sensor.goe_123456_err", + "sensor.goe_123456_err_value", + "sensor.goe_123456_eto", + "sensor.goe_123456_ffb", + "sensor.goe_123456_ffb_value", + "sensor.goe_123456_fhz", + "sensor.goe_123456_frm", + "sensor.goe_123456_frm_value", + "sensor.goe_123456_fsptws", + "sensor.goe_123456_fsptws_delta", + "sensor.goe_123456_inva", + "sensor.goe_123456_inva_delta", + "sensor.goe_123456_lbp", + "sensor.goe_123456_lbp_delta", + "sensor.goe_123456_lccfc", + "sensor.goe_123456_lccfc_delta", + "sensor.goe_123456_lccfi", + "sensor.goe_123456_lccfi_delta", + "sensor.goe_123456_lcctc", + "sensor.goe_123456_lcctc_delta", + "sensor.goe_123456_lck", + "sensor.goe_123456_lck_value", + "sensor.goe_123456_lcs", + "sensor.goe_123456_lcs_delta", + "sensor.goe_123456_lfspt", + "sensor.goe_123456_lfspt_delta", + "sensor.goe_123456_lmsc", + "sensor.goe_123456_lmsc_delta", + "sensor.goe_123456_loa", + "sensor.goe_123456_lpsc", + "sensor.goe_123456_lpsc_delta", + "sensor.goe_123456_map", + "sensor.goe_123456_mmp", + "sensor.goe_123456_modelstatus", + "sensor.goe_123456_modelstatus_value", + "sensor.goe_123456_nif", + "sensor.goe_123456_nrg_0", + "sensor.goe_123456_nrg_1", + "sensor.goe_123456_nrg_10", + "sensor.goe_123456_nrg_11", + "sensor.goe_123456_nrg_12", + "sensor.goe_123456_nrg_13", + "sensor.goe_123456_nrg_14", + "sensor.goe_123456_nrg_15", + "sensor.goe_123456_nrg_2", + "sensor.goe_123456_nrg_3", + "sensor.goe_123456_nrg_4", + "sensor.goe_123456_nrg_5", + "sensor.goe_123456_nrg_6", + "sensor.goe_123456_nrg_7", + "sensor.goe_123456_nrg_8", + "sensor.goe_123456_nrg_9", + "sensor.goe_123456_pakku", + "sensor.goe_123456_pgrid", + "sensor.goe_123456_pnp", + "sensor.goe_123456_ppv", + "sensor.goe_123456_pvopt_averagepakku", + "sensor.goe_123456_pvopt_averagepgrid", + "sensor.goe_123456_pvopt_averageppv", + "sensor.goe_123456_pwm", + "sensor.goe_123456_pwm_value", + "sensor.goe_123456_rbc", + "sensor.goe_123456_rbt", + "sensor.goe_123456_rfb", + "sensor.goe_123456_rssi", + "sensor.goe_123456_tma_0", + "sensor.goe_123456_tma_1", + "sensor.goe_123456_tma_2", + "sensor.goe_123456_tma_3", + "sensor.goe_123456_tma_4", + "sensor.goe_123456_tma_5", + "sensor.goe_123456_tpa", + "sensor.goe_123456_wh", + "sensor.goe_123456_wsms", + "sensor.goe_123456_wsms_value", + "sensor.goe_123456_wst", + "sensor.goe_123456_wst_value", + "switch.goe_123456_acp", + "switch.goe_123456_acs", + "switch.goe_123456_ara", + "switch.goe_123456_awe", + "switch.goe_123456_cae", + "switch.goe_123456_cmse", + "switch.goe_123456_cwe", + "switch.goe_123456_fup", + "switch.goe_123456_fzf", + "switch.goe_123456_hai", + "switch.goe_123456_loe", + "switch.goe_123456_lse", + "switch.goe_123456_nmo", + "switch.goe_123456_ocppe", + "switch.goe_123456_su", + "switch.goe_123456_sua", + "switch.goe_123456_upo" + ], + "charger_wan": [ + "binary_sensor.goe_wan_123456_adi", + "binary_sensor.goe_wan_123456_alw", + "binary_sensor.goe_wan_123456_car_0", + "binary_sensor.goe_wan_123456_ctrls_0_connected", + "binary_sensor.goe_wan_123456_ctrls_0_paired", + "binary_sensor.goe_wan_123456_di1", + "binary_sensor.goe_wan_123456_esk", + "binary_sensor.goe_wan_123456_fsp", + "binary_sensor.goe_wan_123456_pha_0", + "binary_sensor.goe_wan_123456_pha_1", + "binary_sensor.goe_wan_123456_pha_2", + "binary_sensor.goe_wan_123456_pha_3", + "binary_sensor.goe_wan_123456_pha_4", + "binary_sensor.goe_wan_123456_pha_5", + "binary_sensor.goe_wan_123456_tlf", + "binary_sensor.goe_wan_123456_tls", + "button.goe_wan_123456_rst", + "button.goe_wan_123456_zfocore", + "button.goe_wan_123456_zforeal", + "number.goe_wan_123456_ama", + "number.goe_wan_123456_amp", + "number.goe_wan_123456_ate", + "number.goe_wan_123456_att", + "number.goe_wan_123456_awp", + "number.goe_wan_123456_cco", + "number.goe_wan_123456_dwo", + "number.goe_wan_123456_fmt", + "number.goe_wan_123456_fst", + "number.goe_wan_123456_lbr", + "number.goe_wan_123456_lof", + "number.goe_wan_123456_lop", + "number.goe_wan_123456_lot_amp", + "number.goe_wan_123456_lot_dyn", + "number.goe_wan_123456_lot_sta", + "number.goe_wan_123456_lot_ts", + "number.goe_wan_123456_mca", + "number.goe_wan_123456_mci", + "number.goe_wan_123456_mcpd", + "number.goe_wan_123456_mcpea", + "number.goe_wan_123456_mptwt", + "number.goe_wan_123456_mpwst", + "number.goe_wan_123456_pgt", + "number.goe_wan_123456_po", + "number.goe_wan_123456_psh", + "number.goe_wan_123456_psmd", + "number.goe_wan_123456_rdbf", + "number.goe_wan_123456_rdbs", + "number.goe_wan_123456_rdef", + "number.goe_wan_123456_rdes", + "number.goe_wan_123456_rdpl", + "number.goe_wan_123456_rdre", + "number.goe_wan_123456_sh", + "number.goe_wan_123456_spl3", + "number.goe_wan_123456_sumd", + "number.goe_wan_123456_zfo", + "select.goe_wan_123456_bac", + "select.goe_wan_123456_ct", + "select.goe_wan_123456_frc", + "select.goe_wan_123456_frm", + "select.goe_wan_123456_iso2ena", + "select.goe_wan_123456_lmo", + "select.goe_wan_123456_loty", + "select.goe_wan_123456_psm", + "select.goe_wan_123456_sch_satur_control", + "select.goe_wan_123456_sch_sund_control", + "select.goe_wan_123456_sch_week_control", + "select.goe_wan_123456_sdp", + "select.goe_wan_123456_tds", + "select.goe_wan_123456_trx", + "select.goe_wan_123456_ust", + "sensor.goe_wan_123456_acu", + "sensor.goe_wan_123456_amt", + "sensor.goe_wan_123456_awcp_marketprice", + "sensor.goe_wan_123456_car", + "sensor.goe_wan_123456_car_value", + "sensor.goe_wan_123456_cards_0_energy", + "sensor.goe_wan_123456_cards_1_energy", + "sensor.goe_wan_123456_cards_2_energy", + "sensor.goe_wan_123456_cards_3_energy", + "sensor.goe_wan_123456_cards_4_energy", + "sensor.goe_wan_123456_cards_5_energy", + "sensor.goe_wan_123456_cards_6_energy", + "sensor.goe_wan_123456_cards_7_energy", + "sensor.goe_wan_123456_cards_8_energy", + "sensor.goe_wan_123456_cards_9_energy", + "sensor.goe_wan_123456_cbl", + "sensor.goe_wan_123456_ccw_ssid", + "sensor.goe_wan_123456_cdi_type", + "sensor.goe_wan_123456_cdi_value", + "sensor.goe_wan_123456_cll", + "sensor.goe_wan_123456_cus", + "sensor.goe_wan_123456_cus_value", + "sensor.goe_wan_123456_deltaa", + "sensor.goe_wan_123456_deltap", + "sensor.goe_wan_123456_err", + "sensor.goe_wan_123456_err_value", + "sensor.goe_wan_123456_eto", + "sensor.goe_wan_123456_ffb", + "sensor.goe_wan_123456_ffb_value", + "sensor.goe_wan_123456_fhz", + "sensor.goe_wan_123456_frm", + "sensor.goe_wan_123456_frm_value", + "sensor.goe_wan_123456_fsptws", + "sensor.goe_wan_123456_fsptws_delta", + "sensor.goe_wan_123456_inva", + "sensor.goe_wan_123456_inva_delta", + "sensor.goe_wan_123456_lbp", + "sensor.goe_wan_123456_lbp_delta", + "sensor.goe_wan_123456_lccfc", + "sensor.goe_wan_123456_lccfc_delta", + "sensor.goe_wan_123456_lccfi", + "sensor.goe_wan_123456_lccfi_delta", + "sensor.goe_wan_123456_lcctc", + "sensor.goe_wan_123456_lcctc_delta", + "sensor.goe_wan_123456_lck", + "sensor.goe_wan_123456_lck_value", + "sensor.goe_wan_123456_lcs", + "sensor.goe_wan_123456_lcs_delta", + "sensor.goe_wan_123456_lfspt", + "sensor.goe_wan_123456_lfspt_delta", + "sensor.goe_wan_123456_lmsc", + "sensor.goe_wan_123456_lmsc_delta", + "sensor.goe_wan_123456_loa", + "sensor.goe_wan_123456_lpsc", + "sensor.goe_wan_123456_lpsc_delta", + "sensor.goe_wan_123456_map", + "sensor.goe_wan_123456_mmp", + "sensor.goe_wan_123456_modelstatus", + "sensor.goe_wan_123456_modelstatus_value", + "sensor.goe_wan_123456_nif", + "sensor.goe_wan_123456_nrg_0", + "sensor.goe_wan_123456_nrg_1", + "sensor.goe_wan_123456_nrg_10", + "sensor.goe_wan_123456_nrg_11", + "sensor.goe_wan_123456_nrg_12", + "sensor.goe_wan_123456_nrg_13", + "sensor.goe_wan_123456_nrg_14", + "sensor.goe_wan_123456_nrg_15", + "sensor.goe_wan_123456_nrg_2", + "sensor.goe_wan_123456_nrg_3", + "sensor.goe_wan_123456_nrg_4", + "sensor.goe_wan_123456_nrg_5", + "sensor.goe_wan_123456_nrg_6", + "sensor.goe_wan_123456_nrg_7", + "sensor.goe_wan_123456_nrg_8", + "sensor.goe_wan_123456_nrg_9", + "sensor.goe_wan_123456_pakku", + "sensor.goe_wan_123456_pgrid", + "sensor.goe_wan_123456_pnp", + "sensor.goe_wan_123456_ppv", + "sensor.goe_wan_123456_pvopt_averagepakku", + "sensor.goe_wan_123456_pvopt_averagepgrid", + "sensor.goe_wan_123456_pvopt_averageppv", + "sensor.goe_wan_123456_pwm", + "sensor.goe_wan_123456_pwm_value", + "sensor.goe_wan_123456_rbc", + "sensor.goe_wan_123456_rbt", + "sensor.goe_wan_123456_rfb", + "sensor.goe_wan_123456_rssi", + "sensor.goe_wan_123456_tma_0", + "sensor.goe_wan_123456_tma_1", + "sensor.goe_wan_123456_tma_2", + "sensor.goe_wan_123456_tma_3", + "sensor.goe_wan_123456_tma_4", + "sensor.goe_wan_123456_tma_5", + "sensor.goe_wan_123456_tpa", + "sensor.goe_wan_123456_wh", + "sensor.goe_wan_123456_wsms", + "sensor.goe_wan_123456_wsms_value", + "sensor.goe_wan_123456_wst", + "sensor.goe_wan_123456_wst_value", + "switch.goe_wan_123456_acp", + "switch.goe_wan_123456_acs", + "switch.goe_wan_123456_ara", + "switch.goe_wan_123456_awe", + "switch.goe_wan_123456_cae", + "switch.goe_wan_123456_cmse", + "switch.goe_wan_123456_cwe", + "switch.goe_wan_123456_fup", + "switch.goe_wan_123456_fzf", + "switch.goe_wan_123456_hai", + "switch.goe_wan_123456_loe", + "switch.goe_wan_123456_lse", + "switch.goe_wan_123456_nmo", + "switch.goe_wan_123456_ocppe", + "switch.goe_wan_123456_su", + "switch.goe_wan_123456_sua", + "switch.goe_wan_123456_upo" + ], + "controller_lan": [ + "button.goe_123456_rst", + "select.goe_123456_ips_0", + "select.goe_123456_ips_1", + "select.goe_123456_ips_2", + "select.goe_123456_ips_3", + "select.goe_123456_ips_4", + "select.goe_123456_ips_5", + "sensor.goe_123456_ccp_0", + "sensor.goe_123456_ccp_1", + "sensor.goe_123456_ccp_10", + "sensor.goe_123456_ccp_11", + "sensor.goe_123456_ccp_12", + "sensor.goe_123456_ccp_13", + "sensor.goe_123456_ccp_14", + "sensor.goe_123456_ccp_15", + "sensor.goe_123456_ccp_2", + "sensor.goe_123456_ccp_3", + "sensor.goe_123456_ccp_4", + "sensor.goe_123456_ccp_5", + "sensor.goe_123456_ccp_6", + "sensor.goe_123456_ccp_7", + "sensor.goe_123456_ccp_8", + "sensor.goe_123456_ccp_9", + "sensor.goe_123456_ccw_ssid", + "sensor.goe_123456_cec_0_0", + "sensor.goe_123456_cec_0_1", + "sensor.goe_123456_cec_10_0", + "sensor.goe_123456_cec_10_1", + "sensor.goe_123456_cec_11_0", + "sensor.goe_123456_cec_11_1", + "sensor.goe_123456_cec_12_0", + "sensor.goe_123456_cec_12_1", + "sensor.goe_123456_cec_13_0", + "sensor.goe_123456_cec_13_1", + "sensor.goe_123456_cec_14_0", + "sensor.goe_123456_cec_14_1", + "sensor.goe_123456_cec_15_0", + "sensor.goe_123456_cec_15_1", + "sensor.goe_123456_cec_1_0", + "sensor.goe_123456_cec_1_1", + "sensor.goe_123456_cec_2_0", + "sensor.goe_123456_cec_2_1", + "sensor.goe_123456_cec_3_0", + "sensor.goe_123456_cec_3_1", + "sensor.goe_123456_cec_4_0", + "sensor.goe_123456_cec_4_1", + "sensor.goe_123456_cec_5_0", + "sensor.goe_123456_cec_5_1", + "sensor.goe_123456_cec_6_0", + "sensor.goe_123456_cec_6_1", + "sensor.goe_123456_cec_7_0", + "sensor.goe_123456_cec_7_1", + "sensor.goe_123456_cec_8_0", + "sensor.goe_123456_cec_8_1", + "sensor.goe_123456_cec_9_0", + "sensor.goe_123456_cec_9_1", + "sensor.goe_123456_isv_0_f", + "sensor.goe_123456_isv_0_i", + "sensor.goe_123456_isv_0_p", + "sensor.goe_123456_isv_1_f", + "sensor.goe_123456_isv_1_i", + "sensor.goe_123456_isv_1_p", + "sensor.goe_123456_isv_2_f", + "sensor.goe_123456_isv_2_i", + "sensor.goe_123456_isv_2_p", + "sensor.goe_123456_isv_3_f", + "sensor.goe_123456_isv_3_i", + "sensor.goe_123456_isv_3_p", + "sensor.goe_123456_isv_4_f", + "sensor.goe_123456_isv_4_i", + "sensor.goe_123456_isv_4_p", + "sensor.goe_123456_isv_5_f", + "sensor.goe_123456_isv_5_i", + "sensor.goe_123456_isv_5_p", + "sensor.goe_123456_mecu", + "sensor.goe_123456_mmh", + "sensor.goe_123456_usv_0_u1", + "sensor.goe_123456_usv_0_u2", + "sensor.goe_123456_usv_0_u3", + "sensor.goe_123456_usv_0_un", + "sensor.goe_123456_wst_value", + "switch.goe_123456_hai", + "switch.goe_123456_iim_0", + "switch.goe_123456_iim_1", + "switch.goe_123456_iim_2", + "switch.goe_123456_iim_3", + "switch.goe_123456_iim_4", + "switch.goe_123456_iim_5", + "switch.goe_123456_mece", + "switch.goe_123456_mme", + "switch.goe_123456_wda" + ] +} diff --git a/tests/integration/test_api_contract.py b/tests/integration/test_api_contract.py new file mode 100644 index 0000000..27b16b5 --- /dev/null +++ b/tests/integration/test_api_contract.py @@ -0,0 +1,112 @@ +"""Contract between the official go-e key table and the integration. + +Source of truth: `contract/api_keys.json`, generated from the official key table (see +`contract/generate_api_keys.py` for the commit it comes from). The tests check three things: + +1. Every documented key is either mapped to a `Tag` or listed in `contract/classification.json` + (unsupported keys are documented there with a group, decision "open"). +2. Every key the code uses that the official table does not list is explained in the same file. +3. Read/write flag and category of every mapped key match the documentation. Known differences are + listed in `contract/deviations.json`; their tests carry the marker `needs_decision` (they fail on + purpose, but are not blocking until someone has decided whether the difference is intended or the + documentation has a gap). A NEW difference has no marker and is blocking. +""" +import json +import re +from pathlib import Path + +import pytest + +from custom_components.goecharger_api2 import const as entity_const +from custom_components.goecharger_api2.pygoecharger_ha import const as bridge_const +from custom_components.goecharger_api2.pygoecharger_ha.keys import CAT, Tag + +CONTRACT = Path(__file__).parent / "contract" +DOC = json.loads((CONTRACT / "api_keys.json").read_text())["keys"] +CLASSIFICATION = json.loads((CONTRACT / "classification.json").read_text()) +DEVIATIONS = json.loads((CONTRACT / "deviations.json").read_text())["deviations"] + +TAGS = {tag.key: tag for tag in Tag} +CARD_KEY = re.compile(r"c\d[nei]") +CATEGORY = {"Config": CAT.CONFIG, "Status": CAT.STATUS, "Constant": CAT.CONSTANT, "Other": CAT.OTHER} + + +def _filter_keys(): + keys = set() + for name in dir(bridge_const): + value = getattr(bridge_const, name) + if name.startswith("FILTER_") and isinstance(value, str): + keys |= {k for k in re.split(r"[,\s]+", re.sub(r"\{.*?\}", "", value)) if k} + return keys + + +def _entity_keys(): + return {d.key for name in dir(entity_const) if isinstance(getattr(entity_const, name), list) + for d in getattr(entity_const, name) if hasattr(d, "key")} + + +FILTER_KEYS = _filter_keys() +CODE_KEYS = set(TAGS) | FILTER_KEYS | _entity_keys() + + +def _known(aspect): + return {d["key"] for d in DEVIATIONS if d["aspect"] == aspect} + + +def _cases(aspect): + known = _known(aspect) + return [pytest.param(key, id=key, marks=[pytest.mark.needs_decision] if key in known else []) + for key in sorted(set(DOC) & set(TAGS))] + + +# ----------------------------------------------------------------------------- documented keys + +def test_every_documented_key_is_supported_or_classified(): + classified = set(CLASSIFICATION["handled_outside_tag"]) | set(CLASSIFICATION["not_supported"]) + unclassified = sorted(set(DOC) - set(TAGS) - classified) + assert unclassified == [], "new documented keys: map them to a Tag or add them to classification.json" + + +def test_classification_has_no_stale_entries(): + listed = set(CLASSIFICATION["handled_outside_tag"]) | set(CLASSIFICATION["not_supported"]) + stale = sorted(k for k in listed if k not in DOC or k in TAGS) + assert stale == [], "supported or no longer documented: remove them from classification.json" + + +def test_card_keys_are_really_requested_through_the_card_filters(): + requested = {k for name in ("FILTER_CARDS_ID_FWV60", "FILTER_CARDS_ENGY_FWV60", "FILTER_CARDS_NAME_FWV60") + for k in getattr(bridge_const, name).split(",")} + missing = sorted(k for k in CLASSIFICATION["handled_outside_tag"] if k not in requested) + assert missing == [] + + +# ----------------------------------------------------------------------------- keys used but not documented + +def test_every_undocumented_key_used_by_the_code_is_explained(): + explained = set(CLASSIFICATION["undocumented_in_code"]) | set(CLASSIFICATION["handled_outside_tag"]) + unexplained = sorted(k for k in CODE_KEYS - set(DOC) if k not in explained) + assert unexplained == [], "not in the official table: add them to classification.json with a reason" + + +def test_undocumented_classification_has_no_stale_entries(): + stale = sorted(k for k in CLASSIFICATION["undocumented_in_code"] if k in DOC or k not in CODE_KEYS) + assert stale == [], "documented now, or no longer used: remove them from classification.json" + + +# ----------------------------------------------------------------------------- flags of mapped keys + +@pytest.mark.parametrize("key", _cases("writeable")) +def test_write_flag_matches_documentation(key): + documented = DOC[key]["rw"] in ("R/W", "W") + assert TAGS[key].writeable is documented, f"documented {DOC[key]['rw']!r}, keys.py writeable={TAGS[key].writeable}" + + +@pytest.mark.parametrize("key", _cases("category")) +def test_category_matches_documentation(key): + assert TAGS[key].cat == CATEGORY[DOC[key]["category"]], \ + f"documented {DOC[key]['category']!r}, keys.py {TAGS[key].cat.name}" + + +def test_deviation_list_has_no_stale_entries(): + stale = sorted(f"{d['key']}:{d['aspect']}" for d in DEVIATIONS if d["key"] not in TAGS or d["key"] not in DOC) + assert stale == [] diff --git a/tests/integration/test_bridge.py b/tests/integration/test_bridge.py new file mode 100644 index 0000000..c641933 --- /dev/null +++ b/tests/integration/test_bridge.py @@ -0,0 +1,45 @@ +"""Bridge: constructor validation and error handling of the HTTP write.""" +import logging +from types import SimpleNamespace +from unittest.mock import AsyncMock + +import pytest + +from custom_components.goecharger_api2.pygoecharger_ha import GoeChargerApiV2Bridge + + +def test_constructor_needs_host_or_serial_and_token(): + with pytest.raises(ValueError, match="host, or a serial"): + GoeChargerApiV2Bridge(web_session=None, intg_type="charger") + + +class _Response: + def __init__(self, status, content_length, json_body=None): + self.status = status + self.content_length = content_length + self.headers = {} if content_length is None else {"Content-Length": str(content_length)} + self.json = AsyncMock(return_value=json_body) + + async def __aenter__(self): + return self + + async def __aexit__(self, *exc): + return False + + +def _bridge(response): + session = SimpleNamespace(get=lambda *args, **kwargs: response) + return GoeChargerApiV2Bridge(web_session=session, intg_type="charger", host="192.0.2.10") + + +async def test_error_response_with_body_is_returned(): + bridge = _bridge(_Response(500, content_length=20, json_body={"amp": "error"})) + assert await bridge._write_values_int({"amp": "16"}, "amp", 16) == {"err": {"amp": "error"}} + + +async def test_error_response_without_content_length_does_not_raise_keyerror(caplog): + """Chunked responses have no Content-Length header: this used to raise a KeyError.""" + bridge = _bridge(_Response(500, content_length=None)) + with caplog.at_level(logging.WARNING): + assert await bridge._write_values_int({"amp": "16"}, "amp", 16) == {} + assert "KeyError" not in caplog.text diff --git a/tests/integration/test_bridge_http.py b/tests/integration/test_bridge_http.py new file mode 100644 index 0000000..3a0b678 --- /dev/null +++ b/tests/integration/test_bridge_http.py @@ -0,0 +1,127 @@ +"""Bridge, HTTP path: what is requested from the charger and how answers are interpreted. + +The HTTP layer is mocked with Home Assistant's `aioclient_mock`; the bridge is the real code. +The expected requests follow the official go-e HTTP API v2 documentation (http-en.md, cloudapi-en.md). +""" +import pytest +from homeassistant.helpers.aiohttp_client import async_get_clientsession + +from custom_components.goecharger_api2.pygoecharger_ha import GoeChargerApiV2Bridge, NonNullDict + +LAN_STATUS = "http://192.0.2.10/api/status" +LAN_SET = "http://192.0.2.10/api/set" + + +def _lan(hass): + return GoeChargerApiV2Bridge(web_session=async_get_clientsession(hass), intg_type="charger", + host="192.0.2.10") + + +def _cloud(hass, serial="1234"): + return GoeChargerApiV2Bridge(web_session=async_get_clientsession(hass), intg_type="charger", + serial=serial, token="secret-token") + + +def _query(aioclient_mock, index=-1): + return dict(aioclient_mock.mock_calls[index][1].query) + + +# ----------------------------------------------------------------------------- reading + +async def test_read_filtered_sends_comma_separated_filter(hass, aioclient_mock): + aioclient_mock.get(LAN_STATUS, json={"car": 1, "amp": 16}) + result = await _lan(hass)._read_filtered_data("car,amp", "test") + assert _query(aioclient_mock) == {"filter": "car,amp"} + assert result == {"car": 1, "amp": 16} + assert isinstance(result, NonNullDict) + + +@pytest.mark.parametrize("status", [403, 404, 500]) +async def test_read_filtered_returns_empty_for_error_status(hass, aioclient_mock, status): + aioclient_mock.get(LAN_STATUS, status=status, json={"error": "x"}) + assert await _lan(hass)._read_filtered_data("car", "test") == {} + + +async def test_read_filtered_returns_empty_for_invalid_json(hass, aioclient_mock): + aioclient_mock.get(LAN_STATUS, text="not json") + assert await _lan(hass)._read_filtered_data("car", "test") == {} + + +async def test_none_null_get_treats_json_null_as_missing(hass, aioclient_mock): + aioclient_mock.get(LAN_STATUS, json={"car": None}) + result = await _lan(hass)._read_filtered_data("car", "test") + assert result.none_null_get("car", 7) == 7 + + +# ----------------------------------------------------------------------------- cloud addressing + +async def test_cloud_url_pads_serial_and_sends_bearer_token(hass, aioclient_mock): + aioclient_mock.get("https://001234.api.v3.go-e.io/api/status", json={"car": 1}) + await _cloud(hass)._read_filtered_data("car", "test") + headers = aioclient_mock.mock_calls[0][3] + assert headers == {"Authorization": "Bearer secret-token"} + + +async def test_lan_requests_send_no_authorization_header(hass, aioclient_mock): + aioclient_mock.get(LAN_STATUS, json={"car": 1}) + await _lan(hass)._read_filtered_data("car", "test") + assert aioclient_mock.mock_calls[0][3] is None + + +# ----------------------------------------------------------------------------- writing + +@pytest.mark.parametrize("value, expected", [ + (16, "16"), + (True, "true"), + (False, "false"), + (3.5, "3.5"), + ("my charger", '"my charger"'), + ({"pGrid": -500.0, "pPv": 1500.0}, '{"pGrid":-500.0,"pPv":1500.0}'), +]) +async def test_write_encodes_value_as_json_query_parameter(hass, aioclient_mock, value, expected): + aioclient_mock.get(LAN_SET, json={"x": True}) + await _lan(hass).write_value_to_key("x", value) + assert _query(aioclient_mock) == {"x": expected} + + +async def test_write_none_is_sent_as_null(hass, aioclient_mock): + aioclient_mock.get(LAN_SET, json={"dwo": True}) + await _lan(hass).write_value_to_key("dwo", None) + assert str(aioclient_mock.mock_calls[0][1]).endswith("/api/set?dwo=null") + + +async def test_write_success_returns_key_and_value(hass, aioclient_mock): + aioclient_mock.get(LAN_SET, json={"amp": True}) + assert await _lan(hass).write_value_to_key("amp", 10) == {"amp": 10} + + +@pytest.mark.known_bug(( + "http-en.md: the answer holds 'true for success, or a string error message' per key. The bridge " + "tests the value for truthiness, so an error string counts as success. Not verified on a real charger.")) +async def test_write_rejected_by_charger_returns_err(hass, aioclient_mock): + aioclient_mock.get(LAN_SET, json={"amp": "value out of range"}) + result = await _lan(hass).write_value_to_key("amp", 99) + assert result == {"err": {"amp": "value out of range"}} + + +async def test_write_ids_sets_flag_to_read_pv_values_in_hibernation(hass, aioclient_mock): + aioclient_mock.get(LAN_SET, json={"ids": True}) + bridge = _lan(hass) + await bridge.write_value_to_key("ids", {"pGrid": 0, "pPv": 0, "pAkku": 0}) + assert bridge._REQUEST_IDS_DATA is True + + +async def test_write_other_key_forces_config_and_state_refresh(hass, aioclient_mock): + aioclient_mock.get(LAN_SET, json={"amp": True}) + bridge = _lan(hass) + bridge._LAST_CONFIG_UPDATE_TS = bridge._LAST_FULL_STATE_UPDATE_TS = 123.0 + await bridge.write_value_to_key("amp", 10) + assert bridge._LAST_CONFIG_UPDATE_TS == 0 and bridge._LAST_FULL_STATE_UPDATE_TS == 0 + + +@pytest.mark.known_bug(( + "Official cloud docs: /api/set answers HTTP 202 on success; the bridge only accepts 200. " + "Not verified against the real cloud. Remove the marker when the behaviour is clarified.")) +async def test_cloud_write_accepts_http_202(hass, aioclient_mock): + aioclient_mock.get("https://001234.api.v3.go-e.io/api/set", status=202, json={"amp": True}) + assert await _cloud(hass).write_value_to_key("amp", 10) == {"amp": 10} diff --git a/tests/integration/test_bridge_polling.py b/tests/integration/test_bridge_polling.py new file mode 100644 index 0000000..951f0f9 --- /dev/null +++ b/tests/integration/test_bridge_polling.py @@ -0,0 +1,424 @@ +"""Bridge, polling: the read cycle (hibernation, configuration refresh, versions) and the state housekeeping. + +Behaviour is derived from the code and the README (hibernation mode). The HTTP layer is mocked; time +is controlled by setting the bridge's "last update" timestamps. +""" +import asyncio +import time +from types import SimpleNamespace +from unittest.mock import AsyncMock, patch + +import pytest + +from custom_components.goecharger_api2.pygoecharger_ha import NonNullDict +from custom_components.goecharger_api2.pygoecharger_ha.const import ( + FILTER_CARDS_ID_CLASSIC, + FILTER_CARDS_ID_FWV60, + FILTER_CONTROLER_ALL_STATES, + FILTER_CONTROLER_MIN_STATES, + FILTER_IDS_ADDON, + FILTER_MIN_STATES, + FILTER_TIMES_ADDON, +) + +from .factories import make_bridge, make_coordinator + +STATUS = "http://192.0.2.10/api/status" +SLEEP = "custom_components.goecharger_api2.pygoecharger_ha.asyncio.sleep" + + +def _filters(aioclient_mock): + return [dict(c[1].query).get("filter") for c in aioclient_mock.mock_calls] + + +# ----------------------------------------------------------------------------- states: hibernation + +async def test_first_read_requests_all_states_and_stores_the_time(hass, aioclient_mock): + aioclient_mock.get(STATUS, json={"car": 1, "amp": 6}) + bridge = make_bridge(hass) + await bridge.read_all_states() + assert bridge._states == {"car": 1, "amp": 6} + assert bridge._LAST_FULL_STATE_UPDATE_TS > 0 + assert len(_filters(aioclient_mock)) == 1 and "alw" in _filters(aioclient_mock)[0] + + +async def test_idle_charger_reads_only_the_minimal_states(hass, aioclient_mock): + aioclient_mock.get(STATUS, json={"car": 1, "nrg": [1]}) + bridge = make_bridge(hass) + bridge._states = {"car": 1} + bridge._LAST_FULL_STATE_UPDATE_TS = time.time() + await bridge.read_all_states() + expected = FILTER_MIN_STATES + FILTER_TIMES_ADDON + assert _filters(aioclient_mock) == [expected] + assert bridge._states["nrg"] == [1] + + +async def test_idle_charger_also_reads_pv_values_after_pv_data_was_sent(hass, aioclient_mock): + aioclient_mock.get(STATUS, json={"car": 1}) + bridge = make_bridge(hass) + bridge._states = {"car": 1} + bridge._LAST_FULL_STATE_UPDATE_TS = time.time() + bridge._REQUEST_IDS_DATA = True + await bridge.read_all_states() + assert _filters(aioclient_mock) == [FILTER_MIN_STATES + FILTER_IDS_ADDON + FILTER_TIMES_ADDON] + assert bridge._REQUEST_IDS_DATA is False + + +async def test_idle_charger_reads_everything_after_five_minutes(hass, aioclient_mock): + aioclient_mock.get(STATUS, json={"car": 1, "amp": 6}) + bridge = make_bridge(hass) + bridge._states = {"car": 1} + bridge._LAST_FULL_STATE_UPDATE_TS = time.time() - 301 + await bridge.read_all_states() + assert "alw" in _filters(aioclient_mock)[0] + + +async def test_charger_that_starts_charging_triggers_a_full_read(hass, aioclient_mock): + aioclient_mock.get(STATUS, json={"car": 2, "amp": 6}) + bridge = make_bridge(hass) + bridge._states = {"car": 1} + bridge._LAST_FULL_STATE_UPDATE_TS = time.time() + await bridge.read_all_states() + filters = _filters(aioclient_mock) + assert len(filters) == 2 and filters[0].startswith("car,modelStatus") and "alw" in filters[1] + + +async def test_charger_that_is_not_idle_reads_everything_every_time(hass, aioclient_mock): + aioclient_mock.get(STATUS, json={"car": 2}) + bridge = make_bridge(hass) + bridge._states = {"car": 2} + bridge._LAST_FULL_STATE_UPDATE_TS = time.time() + await bridge.read_all_states() + assert "alw" in _filters(aioclient_mock)[0] + + +async def test_empty_minimal_answer_changes_nothing(hass, aioclient_mock): + aioclient_mock.get(STATUS, status=404) + bridge = make_bridge(hass) + bridge._states = {"car": 1, "nrg": [5]} + bridge._LAST_FULL_STATE_UPDATE_TS = time.time() + await bridge.read_all_states() + assert bridge._states == {"car": 1, "nrg": [5]} + + +async def test_failed_full_read_keeps_the_previous_states(hass, aioclient_mock): + """Characterisation (arc42 R20): an empty answer does not clear the old values, and the 'last full + update' time is renewed although nothing was read.""" + aioclient_mock.get(STATUS, status=403) + bridge = make_bridge(hass) + bridge._states = {"car": 2, "amp": 6} + await bridge.read_all_states() + assert bridge._states == {"car": 2, "amp": 6} + assert bridge._LAST_FULL_STATE_UPDATE_TS > 0 + + +async def test_controller_reads_minimal_states_within_five_minutes(hass, aioclient_mock): + aioclient_mock.get(STATUS, json={"usv": [1]}) + bridge = make_bridge(hass, intg_type="controller") + bridge._LAST_FULL_STATE_UPDATE_TS = time.time() + await bridge.read_all_states() + assert _filters(aioclient_mock)[0].startswith(FILTER_CONTROLER_MIN_STATES) + + +async def test_controller_reads_everything_after_five_minutes(hass, aioclient_mock): + aioclient_mock.get(STATUS, json={"usv": [1]}) + bridge = make_bridge(hass, intg_type="controller") + await bridge.read_all_states() + assert _filters(aioclient_mock) == [FILTER_CONTROLER_ALL_STATES] + + +async def test_websocket_connection_reads_states_from_memory_not_from_http(hass, aioclient_mock): + bridge = make_bridge(hass, websocket=True) + bridge._ws_states = {"car": 2, "alw": True, "amp": 16, "zzz": 1} + await bridge.read_all_states() + assert aioclient_mock.call_count == 0 + assert bridge._states == {"car": 2, "alw": True} # amp is configuration, zzz is not requested + + +async def test_websocket_idle_charger_uses_the_minimal_filter_from_memory(hass, aioclient_mock): + bridge = make_bridge(hass, websocket=True) + bridge._ws_states = {"car": 1, "nrg": [1], "amp": 6} + bridge._states = {"car": 1} + bridge._LAST_FULL_STATE_UPDATE_TS = time.time() + await bridge.read_all_states() + assert aioclient_mock.call_count == 0 + assert bridge._states == {"car": 1, "nrg": [1]} + + +# ----------------------------------------------------------------------------- configuration + +async def test_config_read_stores_values_and_time(hass, aioclient_mock): + aioclient_mock.get(STATUS, json={"amp": 10, "frc": 0}) + bridge = make_bridge(hass) + await bridge.read_all_config() + assert bridge._config == {"amp": 10, "frc": 0} and bridge._LAST_CONFIG_UPDATE_TS > 0 + + +async def test_failed_config_read_waits_five_minutes_before_the_next_try(hass, aioclient_mock): + aioclient_mock.get(STATUS, status=500) + bridge = make_bridge(hass) + before = time.time() + await bridge.read_all_config() + # next regular refresh (one hour after the stored time) is due in about 300 seconds + assert before + 280 < bridge._LAST_CONFIG_UPDATE_TS + 3600 < before + 320 + + +async def test_config_read_replaces_websocket_values_that_differ(hass, aioclient_mock): + aioclient_mock.get(STATUS, json={"frc": 1, "amp": 10}) + bridge = make_bridge(hass) + bridge._ws_states = {"frc": 0, "amp": 10, "car": 1} + await bridge.read_all_config() + assert "frc" not in bridge._ws_states and bridge._ws_states["amp"] == 10 and bridge._ws_states["car"] == 1 + + +async def test_config_read_over_websocket_uses_memory(hass, aioclient_mock): + bridge = make_bridge(hass, websocket=True) + bridge._ws_states = {"amp": 16, "unrelated": 1} + await bridge.read_all_config() + assert aioclient_mock.call_count == 0 and bridge._config == {"amp": 16} + + +async def test_config_read_without_filter_does_nothing(hass, aioclient_mock): + bridge = make_bridge(hass) + bridge._FILTER_ALL_CONFIG = "" + await bridge.read_all_config() + assert aioclient_mock.call_count == 0 and bridge._LAST_CONFIG_UPDATE_TS == 0 + + +async def test_force_config_update_resets_the_timers_and_reads_again(hass, aioclient_mock): + aioclient_mock.get(STATUS, json={"amp": 10}) + bridge = make_bridge(hass) + bridge._LAST_FULL_STATE_UPDATE_TS = 123.0 + await bridge.force_config_update() + assert bridge._LAST_FULL_STATE_UPDATE_TS == 0 and bridge._config == {"amp": 10} + + +async def test_force_config_update_over_websocket_still_works(hass, aioclient_mock): + bridge = make_bridge(hass, websocket=True) + bridge._ws_states = {"amp": 16} + await bridge.force_config_update() + assert bridge._config == {"amp": 16} + + +# ----------------------------------------------------------------------------- read_all + +async def test_read_all_returns_a_merged_view_with_websocket_values_first(hass, aioclient_mock): + aioclient_mock.get(STATUS, json={"car": 1, "amp": 6}) + bridge = make_bridge(hass) + bridge._LAST_CONFIG_UPDATE_TS = time.time() + bridge._ws_states = {"amp": 99} + bridge._versions = {"fwv": "60.3"} + merged = await bridge.read_all() + assert merged["amp"] == 99 and merged["car"] == 1 and merged["fwv"] == "60.3" + + +async def test_read_all_reads_the_configuration_only_every_hour(hass, aioclient_mock): + aioclient_mock.get(STATUS, json={"car": 1}) + bridge = make_bridge(hass) + bridge._LAST_CONFIG_UPDATE_TS = time.time() + await bridge.read_all() + assert len(_filters(aioclient_mock)) == 1 # states only + aioclient_mock.clear_requests() + aioclient_mock.get(STATUS, json={"car": 1}) + bridge._LAST_CONFIG_UPDATE_TS = time.time() - 3601 + await bridge.read_all() + assert len(_filters(aioclient_mock)) == 2 # states and configuration + + +async def test_read_all_over_websocket_returns_the_websocket_dictionary(hass, aioclient_mock): + bridge = make_bridge(hass, websocket=True) + bridge._ws_states = {"car": 1} + bridge._LAST_CONFIG_UPDATE_TS = time.time() + assert await bridge.read_all() is bridge._ws_states + + +# ----------------------------------------------------------------------------- versions and system + +async def test_read_versions_over_websocket_uses_memory(hass, aioclient_mock): + bridge = make_bridge(hass, websocket=True) + bridge._ws_states = {"fwv": "60.3", "typ": "x", "cards": [{"cardId": 1}], "amp": 6} + assert await bridge.read_versions() is True + assert "amp" not in bridge._versions and bridge._versions["fwv"] == "60.3" + assert aioclient_mock.call_count == 0 + + +async def test_read_versions_without_any_data_fails(hass): + bridge = make_bridge(hass, websocket=True) + assert await bridge.read_versions() is False + + +async def test_read_versions_retries_until_data_arrives(hass): + bridge = make_bridge(hass) + bridge._read_filtered_data = AsyncMock(side_effect=[{}, NonNullDict({"fwv": "59.4", "cards": [1]})]) + with patch(SLEEP, AsyncMock()) as sleep: + assert await bridge.read_versions() is True + assert bridge._versions["fwv"] == "59.4" and bridge._read_filtered_data.await_count == 2 and sleep.await_count == 1 + + +async def test_read_versions_gives_up_after_five_attempts(hass, aioclient_mock): + aioclient_mock.get(STATUS, status=500) + bridge = make_bridge(hass) + with patch(SLEEP, AsyncMock()) as sleep: + assert await bridge.read_versions() is False + assert aioclient_mock.call_count == 5 and sleep.await_count == 4 + + +async def test_firmware_with_cards_object_keeps_the_classic_card_filter(hass, aioclient_mock): + aioclient_mock.get(STATUS, json={"fwv": "60.3", "cards": [{"cardId": 1}]}) + bridge = make_bridge(hass) + await bridge.read_versions() + assert FILTER_CARDS_ID_CLASSIC in bridge._FILTER_ALL_CONFIG and "c0i" not in bridge._FILTER_ALL_CONFIG + + +async def test_firmware_without_cards_object_switches_to_single_card_keys(hass, aioclient_mock): + aioclient_mock.get(STATUS, json={"fwv": "60.0-beta"}) + bridge = make_bridge(hass) + await bridge.read_versions() + assert FILTER_CARDS_ID_FWV60 in bridge._FILTER_ALL_CONFIG + + +async def test_controller_versions_do_not_touch_the_card_filters(hass, aioclient_mock): + aioclient_mock.get(STATUS, json={"fwv": "1.1.1"}) + bridge = make_bridge(hass, intg_type="controller") + before = bridge._FILTER_ALL_CONFIG + assert await bridge.read_versions() is True and bridge._FILTER_ALL_CONFIG == before + + +async def test_read_system_over_http_and_websocket(hass, aioclient_mock): + aioclient_mock.get(STATUS, json={"sse": "123456", "typ": "x"}) + assert await make_bridge(hass).read_system() == {"sse": "123456", "typ": "x"} + ws_bridge = make_bridge(hass, websocket=True) + ws_bridge._ws_states = {"sse": "1", "other": 2} + assert await ws_bridge.read_system() == {"sse": "1"} + + +# ----------------------------------------------------------------------------- read everything at once + +async def test_read_all_data_returns_the_whole_status(hass, aioclient_mock): + aioclient_mock.get(STATUS, json={"a": 1}) + assert await make_bridge(hass)._read_all_data() == {"a": 1} + + +@pytest.mark.parametrize("response", [dict(status=403), dict(status=404), dict(text="no json"), dict(json={})]) +async def test_read_all_data_gives_nothing_for_errors(hass, aioclient_mock, response): + aioclient_mock.get(STATUS, **response) + assert await make_bridge(hass)._read_all_data() == {} + + +async def test_read_all_data_sends_the_cloud_token(hass, aioclient_mock): + from homeassistant.helpers.aiohttp_client import async_get_clientsession + from custom_components.goecharger_api2.pygoecharger_ha import GoeChargerApiV2Bridge + aioclient_mock.get("https://001234.api.v3.go-e.io/api/status", json={"a": 1}) + bridge = GoeChargerApiV2Bridge(web_session=async_get_clientsession(hass), intg_type="charger", + serial="1234", token="tok") + await bridge._read_all_data() + assert aioclient_mock.mock_calls[0][3] == {"Authorization": "Bearer tok"} + + +# ----------------------------------------------------------------------------- housekeeping + +async def test_clear_data_resets_everything_and_cancels_the_pending_update(hass): + bridge = make_bridge(hass) + bridge._states, bridge._config, bridge._versions, bridge._ws_states = {"a": 1}, {"b": 2}, {"c": 3}, {"d": 4} + bridge._LAST_CONFIG_UPDATE_TS = bridge._LAST_FULL_STATE_UPDATE_TS = 5 + task = asyncio.create_task(asyncio.sleep(3600)) + bridge._ws_debounced_update_task = task + bridge.clear_data() + await asyncio.sleep(0) + assert task.cancelled() + assert (bridge._states, bridge._config, bridge._versions, bridge._ws_states) == ({}, {}, {}, {}) + assert bridge._LAST_CONFIG_UPDATE_TS == 0 and bridge.available_fields() == 0 + + +async def test_reset_timestamps_over_http_only_resets(hass): + bridge = make_bridge(hass) + bridge._LAST_CONFIG_UPDATE_TS = bridge._LAST_FULL_STATE_UPDATE_TS = 5 + await bridge.reset_stored_update_ts() + assert bridge._LAST_CONFIG_UPDATE_TS == bridge._LAST_FULL_STATE_UPDATE_TS == 0 + + +async def test_reset_timestamps_over_websocket_asks_the_coordinator_to_reconnect(hass): + bridge = make_bridge(hass, websocket=True) + bridge.coordinator = SimpleNamespace(force_async_update_now=AsyncMock()) + await bridge.reset_stored_update_ts() + bridge.coordinator.force_async_update_now.assert_awaited_once() + + +async def test_reset_timestamps_over_websocket_without_coordinator_does_not_fail(hass): + bridge = make_bridge(hass, websocket=True) + await bridge.reset_stored_update_ts() + + +async def test_websocket_update_reaches_the_coordinator_after_the_debounce(hass): + bridge = make_bridge(hass) + bridge.coordinator = SimpleNamespace(_ws_data_update_notify_interval_in_seconds=1, async_set_updated_data=lambda d: received.append(d)) + received = [] + bridge._ws_states = {"a": 1} + bridge._ws_LAST_NEW_DATA_NOTIFY = time.time() + with patch(SLEEP, AsyncMock()): + bridge._ws_notify_for_new_data() + await bridge._ws_debounced_update_task + assert received and received[0]["a"] == 1 and bridge._ws_LAST_NEW_DATA_NOTIFY > 0 + + +async def test_websocket_update_without_wait_when_the_last_one_is_old(hass): + bridge = make_bridge(hass) + received = [] + bridge.coordinator = SimpleNamespace(_ws_data_update_notify_interval_in_seconds=1, async_set_updated_data=received.append) + bridge._ws_LAST_NEW_DATA_NOTIFY = 0 + with patch(SLEEP, AsyncMock()) as sleep: + bridge._ws_notify_for_new_data() + await bridge._ws_debounced_update_task + sleep.assert_not_awaited() + assert len(received) == 1 + + +async def test_a_second_websocket_update_cancels_the_first_pending_one(hass): + bridge = make_bridge(hass) + bridge.coordinator = SimpleNamespace(_ws_data_update_notify_interval_in_seconds=60, async_set_updated_data=lambda d: None) + bridge._ws_LAST_NEW_DATA_NOTIFY = time.time() + bridge._ws_notify_for_new_data() + first = bridge._ws_debounced_update_task + bridge._ws_notify_for_new_data() + await asyncio.sleep(0) + assert first.cancelled() and bridge._ws_debounced_update_task is not first + bridge._ws_debounced_update_task.cancel() + + +async def test_websocket_silence_is_detected_after_fifty_seconds(hass): + bridge = make_bridge(hass) + bridge._ws_LAST_UPDATE = time.time() - 10 + assert bridge.ws_check_last_update() is True + bridge._ws_LAST_UPDATE = time.time() - 60 + assert bridge.ws_check_last_update() is False + + +async def test_ws_close_handles_missing_and_failing_connections(hass): + bridge = make_bridge(hass) + bridge.ws_connected = True + await bridge.ws_close(None) + assert bridge.ws_connected is False + failing = SimpleNamespace(close=AsyncMock(side_effect=RuntimeError("boom"))) + await bridge.ws_close(failing) + bridge._ws_serial = "123456" + ok = SimpleNamespace(close=AsyncMock()) + await bridge.ws_close(ok) + ok.close.assert_awaited_once() + + +async def test_expiring_store_forgets_old_entries(): + from custom_components.goecharger_api2.pygoecharger_ha import ExpiringStore + store = ExpiringStore(ttl=0) + store.set("1", {"a": 1}) + time.sleep(0.01) + assert store.get("1") is None and len(store) == 0 + store = ExpiringStore(ttl=60) + store.set("1", {"a": 1}) + assert store.get("1") == {"a": 1} and store.get("2") is None and len(store) == 1 + store._store["1"]["expires_at"] = 0 + assert store.get("1") is None + + +async def test_nonnull_dict_returns_default_for_null(hass): + data = NonNullDict({"a": None, "b": 1}) + assert data.none_null_get("a", 7) == 7 and data.none_null_get("b", 7) == 1 and data.none_null_get("c") is None diff --git a/tests/integration/test_bridge_write_paths.py b/tests/integration/test_bridge_write_paths.py new file mode 100644 index 0000000..f8d8356 --- /dev/null +++ b/tests/integration/test_bridge_write_paths.py @@ -0,0 +1,151 @@ +"""Bridge, writing: every path from a value to the charger and back (HTTP and WebSocket). + +HTTP is mocked with `aioclient_mock`, the WebSocket connection is a fake object. A write must either +return the written value, an error object, or an empty result, never raise for a bad answer. +""" +import time +from types import SimpleNamespace +from unittest.mock import AsyncMock, patch + +import aiohttp +import pytest + +from custom_components.goecharger_api2.pygoecharger_ha import GoeChargerApiV2Bridge +from custom_components.goecharger_api2.pygoecharger_ha.keys import IS_TRIGGER, Tag + +from .factories import make_bridge + +SET = "http://192.0.2.10/api/set" +SLEEP = "custom_components.goecharger_api2.pygoecharger_ha.asyncio.sleep" + + +# ----------------------------------------------------------------------------- one key, over HTTP + +async def test_force_config_read_button_rereads_the_configuration(hass, aioclient_mock): + bridge = make_bridge(hass) + bridge.force_config_update = AsyncMock() + key = Tag.INTERNAL_FORCE_CONFIG_READ.key + assert await bridge.write_value_to_key(key, IS_TRIGGER) == {key: IS_TRIGGER} + bridge.force_config_update.assert_awaited_once() + assert aioclient_mock.call_count == 0 + + +async def test_force_refresh_button_resets_the_timers(hass, aioclient_mock): + bridge = make_bridge(hass) + bridge.reset_stored_update_ts = AsyncMock() + key = Tag.INTERNAL_FORCE_REFRESH_ALL.key + assert await bridge.write_value_to_key(key, IS_TRIGGER) == {key: IS_TRIGGER} + bridge.reset_stored_update_ts.assert_awaited_once() + + +async def test_any_other_trigger_does_nothing_and_sends_nothing(hass, aioclient_mock): + bridge = make_bridge(hass) + assert await bridge.write_value_to_key("other", IS_TRIGGER) == {"other": IS_TRIGGER} + assert aioclient_mock.call_count == 0 + + +async def test_websocket_flag_without_open_connection_falls_back_to_http(hass, aioclient_mock): + aioclient_mock.get(SET, json={"amp": True}) + bridge = make_bridge(hass) + bridge.ws_connected = True + bridge._ws_connection = None + assert await bridge.write_value_to_key("amp", 10) == {"amp": 10} + assert aioclient_mock.call_count == 1 + + +async def test_websocket_send_that_reports_failure_gives_an_empty_result(hass): + bridge = make_bridge(hass, websocket=True) + bridge._ws_send_command = AsyncMock(return_value=False) + assert await bridge.write_value_to_key("amp", 10) == {} + + +# ----------------------------------------------------------------------------- several keys + +async def test_multiple_keys_over_websocket_are_sent_one_after_the_other(hass): + bridge = make_bridge(hass, websocket=True) + bridge._ws_LAST_NEW_DATA_NOTIFY = 5 + with patch(SLEEP, AsyncMock()) as sleep: + result = await bridge.write_multiple_values_to_keys({"ct": "x", "mca": 6}, "ct", "default") + sent = [(c.args[0]["key"], c.args[0]["value"]) for c in bridge._ws_connection.send_json.await_args_list] + assert sent == [("ct", "x"), ("mca", 6)] and sleep.await_count == 2 and result == {} + assert bridge._ws_LAST_NEW_DATA_NOTIFY == 0 + + +async def test_multiple_keys_over_http_are_encoded_by_type(hass, aioclient_mock): + aioclient_mock.get(SET, json={"ct": True}) + bridge = make_bridge(hass) + args = {"acp": True, "mca": 6, "x": 1.5, "name": "abc", "obj": {"a": 1}, "ignored": [1, 2]} + assert await bridge.write_multiple_values_to_keys(args, "ct", "default") == {"ct": "default"} + query = dict(aioclient_mock.mock_calls[0][1].query) + assert query == {"acp": "true", "mca": "6", "x": "1.5", "name": '"abc"', "obj": '{"a":1}'} + + +# ----------------------------------------------------------------------------- the answer of the charger + +async def test_charger_answer_with_the_key_resets_the_read_timers(hass, aioclient_mock): + aioclient_mock.get(SET, json={"amp": True}) + bridge = make_bridge(hass) + bridge._LAST_CONFIG_UPDATE_TS = bridge._LAST_FULL_STATE_UPDATE_TS = 9 + await bridge.write_value_to_key("amp", 6) + assert bridge._LAST_CONFIG_UPDATE_TS == 0 and bridge._REQUEST_IDS_DATA is False + + +async def test_pv_data_answer_keeps_the_timers_and_asks_for_pv_values_for_a_charger(hass, aioclient_mock): + aioclient_mock.get(SET, json={"ids": True}) + bridge = make_bridge(hass) + bridge._LAST_CONFIG_UPDATE_TS = 9 + await bridge.write_value_to_key("ids", {"pGrid": 0}) + assert bridge._LAST_CONFIG_UPDATE_TS == 9 and bridge._REQUEST_IDS_DATA is True + + +async def test_pv_data_answer_for_a_controller_does_not_set_the_flag(hass, aioclient_mock): + aioclient_mock.get(SET, json={"ids": True}) + bridge = make_bridge(hass, intg_type="controller") + await bridge.write_value_to_key("ids", {"pGrid": 0}) + assert bridge._REQUEST_IDS_DATA is False + + +async def test_answer_without_the_key_is_an_error_object(hass, aioclient_mock): + aioclient_mock.get(SET, json={"other": True}) + assert await make_bridge(hass).write_value_to_key("amp", 6) == {"err": {"other": True}} + + +async def test_answer_that_denies_the_key_with_false_is_an_error_object(hass, aioclient_mock): + aioclient_mock.get(SET, json={"amp": False}) + assert await make_bridge(hass).write_value_to_key("amp", 6) == {"err": {"amp": False}} + + +async def test_empty_answer_gives_an_empty_result(hass, aioclient_mock): + aioclient_mock.get(SET, json={}) + assert await make_bridge(hass).write_value_to_key("amp", 6) == {} + + +async def test_answer_that_is_not_json_gives_an_empty_result(hass, aioclient_mock): + aioclient_mock.get(SET, text="not json", headers={"Content-Type": "application/json"}) + assert await make_bridge(hass).write_value_to_key("amp", 6) == {} + + +async def test_answer_with_the_wrong_content_type_gives_an_empty_result(hass, aioclient_mock): + aioclient_mock.get(SET, text="", headers={"Content-Type": "text/html"}) + assert await make_bridge(hass).write_value_to_key("amp", 6) == {} + + +@pytest.mark.parametrize("status", [403, 404, 422, 503]) +async def test_other_statuses_give_an_empty_result(hass, aioclient_mock, status): + aioclient_mock.get(SET, status=status, json={"amp": True}) + assert await make_bridge(hass).write_value_to_key("amp", 6) == {} + + +async def test_a_lost_connection_while_writing_is_raised_for_the_coordinator_to_handle(hass, aioclient_mock): + aioclient_mock.get(SET, exc=aiohttp.ClientConnectionError("down")) + with pytest.raises(aiohttp.ClientConnectionError): + await make_bridge(hass).write_value_to_key("amp", 6) + + +async def test_cloud_write_sends_the_bearer_token(hass, aioclient_mock): + from homeassistant.helpers.aiohttp_client import async_get_clientsession + aioclient_mock.get("https://001234.api.v3.go-e.io/api/set", json={"amp": True}) + bridge = GoeChargerApiV2Bridge(web_session=async_get_clientsession(hass), intg_type="charger", + serial="1234", token="tok") + assert await bridge.write_value_to_key("amp", 6) == {"amp": 6} + assert aioclient_mock.mock_calls[0][3] == {"Authorization": "Bearer tok"} diff --git a/tests/integration/test_config_flow_failures.py b/tests/integration/test_config_flow_failures.py new file mode 100644 index 0000000..0d6e364 --- /dev/null +++ b/tests/integration/test_config_flow_failures.py @@ -0,0 +1,83 @@ +"""Config flow: wrong or unreachable devices must end the flow with a clear reason and create no entry.""" +from unittest.mock import AsyncMock, patch + +import pytest +from homeassistant import config_entries +from homeassistant.data_entry_flow import FlowResultType + +from custom_components.goecharger_api2 import config_flow +from custom_components.goecharger_api2.const import DOMAIN + +LAN_STATUS = "http://192.0.2.10/api/status" +CLOUD_STATUS = "https://123456.api.v3.go-e.io/api/status" +GOOD_STATUS = {"sse": "123456", "typ": "go-eCharger_V4", "var": 11, "fwv": "59.4"} + +LAN_INPUT = {"integration_type": "charger", "host": "192.0.2.10", "scan_interval": 30, + "delay": False, "limit_to_11kw": False} +WAN_INPUT = {"integration_type": "charger", "id": "123456", "token": "token", "scan_interval": 120, + "delay": False} + + +async def _start(hass, system): + result = await hass.config_entries.flow.async_init(DOMAIN, context={"source": config_entries.SOURCE_USER}) + assert result["type"] == FlowResultType.FORM and result["step_id"] == "user" + result = await hass.config_entries.flow.async_configure(result["flow_id"], {"stype": system}) + assert result["type"] == FlowResultType.FORM + return result + + +# ----------------------------------------------------------------------------- positive control + +async def test_valid_lan_device_creates_an_entry(hass, aioclient_mock): + aioclient_mock.get(LAN_STATUS, json=GOOD_STATUS) + result = await _start(hass, "lan") + with patch("custom_components.goecharger_api2.async_setup_entry", AsyncMock(return_value=True)): + result = await hass.config_entries.flow.async_configure(result["flow_id"], LAN_INPUT) + assert result["type"] == FlowResultType.CREATE_ENTRY + assert result["title"] == "go-eCharger API v2 [123456] Local" + assert result["data"]["id"] == "123456" and result["data"]["mode"] == "lan" + + +# ----------------------------------------------------------------------------- failures + +@pytest.mark.parametrize("status", [403, 404, 500]) +async def test_lan_device_that_answers_with_an_error_is_rejected(hass, aioclient_mock, status): + aioclient_mock.get(LAN_STATUS, status=status) + result = await _start(hass, "lan") + result = await hass.config_entries.flow.async_configure(result["flow_id"], LAN_INPUT) + assert result["type"] == FlowResultType.ABORT and result["reason"] == "auth_lan" + assert hass.config_entries.async_entries(DOMAIN) == [] + + +async def test_lan_device_with_an_empty_answer_is_rejected(hass, aioclient_mock): + aioclient_mock.get(LAN_STATUS, json={}) + result = await _start(hass, "lan") + result = await hass.config_entries.flow.async_configure(result["flow_id"], LAN_INPUT) + assert result["type"] == FlowResultType.ABORT and result["reason"] == "auth_lan" + + +async def test_unreachable_lan_device_is_rejected(hass, aioclient_mock): + from aiohttp import ClientConnectionError + aioclient_mock.get(LAN_STATUS, exc=ClientConnectionError("no route")) + result = await _start(hass, "lan") + result = await hass.config_entries.flow.async_configure(result["flow_id"], LAN_INPUT) + assert result["type"] == FlowResultType.ABORT and result["reason"] == "auth_lan" + + +async def test_failed_websocket_check_falls_back_to_http_and_then_rejects(hass, aioclient_mock): + aioclient_mock.get(LAN_STATUS, status=403) + result = await _start(hass, "lan") + with patch.object(config_flow.GoeChargerApiV2FlowHandler, "_check_ws_connection_and_flags", + AsyncMock(return_value=False)) as ws_check: + result = await hass.config_entries.flow.async_configure( + result["flow_id"], {**LAN_INPUT, "password": "wrong"}) + ws_check.assert_awaited_once() + assert result["type"] == FlowResultType.ABORT and result["reason"] == "auth_lan" + + +async def test_cloud_device_with_a_wrong_token_is_rejected(hass, aioclient_mock): + aioclient_mock.get(CLOUD_STATUS, status=403) + result = await _start(hass, "wan") + result = await hass.config_entries.flow.async_configure(result["flow_id"], WAN_INPUT) + assert result["type"] == FlowResultType.ABORT and result["reason"] == "auth_wan" + assert hass.config_entries.async_entries(DOMAIN) == [] diff --git a/tests/integration/test_config_flow_paths.py b/tests/integration/test_config_flow_paths.py new file mode 100644 index 0000000..76d7c58 --- /dev/null +++ b/tests/integration/test_config_flow_paths.py @@ -0,0 +1,191 @@ +"""Config flow: successful setups, the value rules, reconfiguration and the WebSocket check. + +The charger is mocked: HTTP with `aioclient_mock`, the WebSocket check with a fake connection. +""" +import asyncio +from types import SimpleNamespace +from unittest.mock import AsyncMock, patch + +import pytest +from homeassistant import config_entries +from homeassistant.data_entry_flow import FlowResultType +from pytest_homeassistant_custom_component.common import MockConfigEntry + +from custom_components.goecharger_api2 import config_flow +from custom_components.goecharger_api2.const import DOMAIN +from custom_components.goecharger_api2.pygoecharger_ha import GoeChargerApiV2Bridge, TargetedEvent + +LAN_STATUS = "http://192.0.2.10/api/status" +CLOUD_STATUS = "https://123456.api.v3.go-e.io/api/status" +CHARGER = {"sse": "123456", "typ": "go-eCharger_V4", "var": 22, "fwv": "59.4"} +CONTROLLER = {"sse": "654321", "typ": "go-eController", "fwv": "1.1.1"} +SETUP = "custom_components.goecharger_api2.async_setup_entry" + + +async def _start(hass, system): + result = await hass.config_entries.flow.async_init(DOMAIN, context={"source": config_entries.SOURCE_USER}) + return await hass.config_entries.flow.async_configure(result["flow_id"], {"stype": system}) + + +def _lan(**extra): + return {"integration_type": "charger", "host": "192.0.2.10", "scan_interval": 30, "delay": False, + "limit_to_11kw": False, **extra} + + +def _wan(**extra): + return {"integration_type": "charger", "id": "123456", "token": "tok", "scan_interval": 120, "delay": False, **extra} + + +async def _create(hass, system, data): + result = await _start(hass, system) + with patch(SETUP, AsyncMock(return_value=True)): + return await hass.config_entries.flow.async_configure(result["flow_id"], data) + + +# ----------------------------------------------------------------------------- creating entries + +async def test_cloud_charger_is_created_with_its_title_and_a_minimum_interval(hass, aioclient_mock): + aioclient_mock.get(CLOUD_STATUS, json=CHARGER) + result = await _create(hass, "wan", _wan(scan_interval=10)) + assert result["type"] == FlowResultType.CREATE_ENTRY + assert result["title"] == "go-eCharger API v2 [123456] Cloud" + assert result["data"]["mode"] == "wan" and result["data"]["scan_interval"] == 30 + assert result["data"]["type"].endswith("Cloud") and "password" not in result["data"] + + +async def test_local_charger_gets_at_least_five_seconds_and_keeps_its_model(hass, aioclient_mock): + aioclient_mock.get(LAN_STATUS, json=CHARGER) + result = await _create(hass, "lan", _lan(scan_interval=1)) + assert result["data"]["scan_interval"] == 5 and result["data"]["model"] == "22" + assert result["data"]["type"] == "go-eCharger V4 [22 kW] Local" + + +async def test_local_controller_is_created_with_its_own_title(hass, aioclient_mock): + aioclient_mock.get(LAN_STATUS, json=CONTROLLER) + result = await _create(hass, "lan", _lan(integration_type="controller")) + assert result["title"] == "go-eController API v2 [654321] Local" + assert result["data"]["type"] == "go-eController Local" and result["data"]["model"] == "eControl" + + +async def test_cloud_controller_is_created_with_its_own_title(hass, aioclient_mock): + aioclient_mock.get(CLOUD_STATUS, json={**CONTROLLER, "sse": "123456"}) + result = await _create(hass, "wan", _wan(integration_type="controller")) + assert result["title"] == "go-eController API v2 [123456] Cloud" + + +def _ws_found(states): + async def check(self, client): + client._ws_states = dict(states) + client.ws_connected = True + return True + return check + + +@pytest.mark.parametrize("flags", [ + {"hai": True, "cae": True}, {"hai": False, "cae": False}, {}]) +async def test_websocket_login_creates_the_entry_with_the_password(hass, flags): + states = {**CHARGER, **flags} + with patch.object(config_flow.GoeChargerApiV2FlowHandler, "_check_ws_connection_and_flags", _ws_found(states)): + result = await _create(hass, "lan", _lan(password=" secret ")) + assert result["type"] == FlowResultType.CREATE_ENTRY + assert result["data"]["password"] == " secret " and result["data"]["id"] == "123456" + + +async def test_websocket_check_for_the_cloud_also_keeps_the_password(hass): + with patch.object(config_flow.GoeChargerApiV2FlowHandler, "_check_ws_connection_and_flags", _ws_found(CHARGER)): + result = await _create(hass, "wan", _wan(password="secret")) + assert result["data"]["password"] == "secret" and result["data"]["mode"] == "wan" + + +# ----------------------------------------------------------------------------- reconfiguring + +async def _reconfigure(hass, data): + entry = MockConfigEntry(domain=DOMAIN, title="t", data=data, version=2, minor_version=1) + entry.add_to_hass(hass) + return entry, await hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_RECONFIGURE, "entry_id": entry.entry_id}) + + +async def test_reconfigure_a_local_charger_shows_the_stored_values(hass): + entry, result = await _reconfigure(hass, {"mode": "lan", "host": "192.0.2.10", "id": "123456", + "integration_type": "charger", "scan_interval": 45, "limit_to_11kw": True}) + assert result["type"] == FlowResultType.FORM and result["step_id"] == "user_lan" + defaults = {str(k): k.default() if callable(k.default) else None for k in result["data_schema"].schema} + assert defaults["host"] == "192.0.2.10" and defaults["scan_interval"] == 45 and defaults["limit_to_11kw"] is True + + +async def test_reconfigure_a_cloud_charger_shows_the_cloud_form(hass): + entry, result = await _reconfigure(hass, {"mode": "wan", "id": "123456", "token": "tok", + "integration_type": "charger", "scan_interval": 90}) + assert result["type"] == FlowResultType.FORM and result["step_id"] == "user_wan" + + +async def test_reconfigure_with_a_working_device_updates_and_reloads_the_entry(hass, aioclient_mock): + aioclient_mock.get(LAN_STATUS, json=CHARGER) + entry, result = await _reconfigure(hass, {"mode": "lan", "host": "192.0.2.10", "id": "123456", + "integration_type": "charger", "scan_interval": 45}) + with patch.object(hass.config_entries, "async_reload", AsyncMock()): + result = await hass.config_entries.flow.async_configure(result["flow_id"], _lan(scan_interval=60)) + assert result["type"] == FlowResultType.ABORT and result["reason"] == "reconfigure_successful" + assert entry.data["scan_interval"] == 60 + + +async def test_reconfigure_with_a_working_cloud_device_updates_the_entry(hass, aioclient_mock): + aioclient_mock.get(CLOUD_STATUS, json=CHARGER) + entry, result = await _reconfigure(hass, {"mode": "wan", "id": "123456", "token": "tok", + "integration_type": "charger", "scan_interval": 90}) + with patch.object(hass.config_entries, "async_reload", AsyncMock()): + result = await hass.config_entries.flow.async_configure(result["flow_id"], _wan(scan_interval=200)) + assert result["reason"] == "reconfigure_successful" and entry.data["scan_interval"] == 200 + + +async def test_the_first_step_offers_local_and_cloud(hass): + result = await hass.config_entries.flow.async_init(DOMAIN, context={"source": config_entries.SOURCE_USER}) + assert result["type"] == FlowResultType.FORM and result["step_id"] == "user" + + +# ----------------------------------------------------------------------------- the WebSocket check + +def _handler(hass): + handler = config_flow.GoeChargerApiV2FlowHandler() + handler.hass = hass + return handler + + +def _client(connect): + client = GoeChargerApiV2Bridge(web_session=None, intg_type="charger", host="192.0.2.10", access_password="secret") + client.ws_connect = connect + return client + + +async def test_check_reports_success_when_the_first_data_arrives(hass): + async def connect(a_event=None, keep_ws_states_after_close=False): + a_event.set() + client = _client(connect) + with patch.object(config_flow.asyncio, "sleep", AsyncMock()): + assert await _handler(hass)._check_ws_connection_and_flags(client) is True + + +async def test_check_reports_failure_for_errors_from_the_connection(hass): + async def connect(a_event=None, keep_ws_states_after_close=False): + a_event.set_error(Exception("Authentication failed")) + with patch.object(config_flow.asyncio, "sleep", AsyncMock()): + assert await _handler(hass)._check_ws_connection_and_flags(_client(connect)) is False + + +@pytest.mark.parametrize("error", [asyncio.TimeoutError(), asyncio.CancelledError()]) +async def test_check_reports_failure_when_the_wait_ends_without_data(hass, error): + async def connect(a_event=None, keep_ws_states_after_close=False): + await asyncio.sleep(3600) + real_wait_for = asyncio.wait_for + calls = [] + + async def wait_for(awaitable, timeout): + calls.append(timeout) + if len(calls) == 1: + awaitable.close() if hasattr(awaitable, "close") else None + raise error + return await real_wait_for(awaitable, 0.5) + + with patch.object(config_flow.asyncio, "wait_for", wait_for), patch.object(config_flow.asyncio, "sleep", AsyncMock()): + assert await _handler(hass)._check_ws_connection_and_flags(_client(connect)) is False diff --git a/tests/integration/test_controller_contract.py b/tests/integration/test_controller_contract.py new file mode 100644 index 0000000..4cd10d4 --- /dev/null +++ b/tests/integration/test_controller_contract.py @@ -0,0 +1,93 @@ +"""Contract between the official go-eController key table and the integration. + +Source of truth: `contract/controller_keys.json`, generated from `go-eController-API/apikeys-en.md` (see +`contract/generate_api_keys.py`). The charger contract in test_api_contract.py is built from the charger +table only, so before these tests the controller paths were not checked against any documentation. + +Checks: +1. Every key a controller entity uses is documented for controllers. +2. Controller entities that write (switch, select, button) use keys the documentation marks as writable, + and keys.py marks them as writeable as well. +3. Every key the code requests from a controller is documented. `cae`, `ecp` and `lcs` are requested but + not documented; they are marked `needs_decision` (fails on purpose, not blocking) until someone has + checked on a device whether they are leftovers or a documentation gap. +""" +import json +import re +from pathlib import Path + +import pytest + +from custom_components.goecharger_api2 import const as entity_const +from custom_components.goecharger_api2.pygoecharger_ha import const as bridge_const +from custom_components.goecharger_api2.pygoecharger_ha.keys import Tag + +CONTROLLER_DOC = json.loads((Path(__file__).parent / "contract" / "controller_keys.json").read_text())["keys"] +TAGS = {tag.key: tag for tag in Tag} + +# requested from a controller although the controller documentation does not list them +UNDOCUMENTED_REQUESTS = {"cae", "ecp", "lcs"} + + +def _keys(list_name): + return sorted({d.key for d in getattr(entity_const, list_name)}) + + +def _requested_keys(): + keys = set() + for name in dir(bridge_const): + value = getattr(bridge_const, name) + if name.startswith("FILTER_CONTROLER_") and isinstance(value, str): + keys |= {k for k in re.split(r"[,\s]+", value) if k} + return keys + + +ENTITY_LISTS = ["CONTROLLER_BINARY_SENSORS", "CONTROLLER_BUTTONS", "CONTROLLER_NUMBER_SENSORS", + "CONTROLLER_SELECT_SENSORS", "CONTROLLER_SENSOR_SENSORS", "CONTROLLER_SWITCH_SENSORS"] +WRITING_LISTS = ["CONTROLLER_BUTTONS", "CONTROLLER_NUMBER_SENSORS", "CONTROLLER_SELECT_SENSORS", + "CONTROLLER_SWITCH_SENSORS"] + +ENTITY_KEYS = sorted({k for name in ENTITY_LISTS for k in _keys(name)}) +WRITING_KEYS = sorted({k for name in WRITING_LISTS for k in _keys(name)}) +REQUESTED = sorted(_requested_keys()) + + +def test_contract_file_is_the_controller_table(): + assert len(CONTROLLER_DOC) == 82 + assert all(v["rw"] in ("R", "W", "R/W") for v in CONTROLLER_DOC.values()) + + +def test_there_are_controller_entities_to_check(): + assert ENTITY_KEYS and WRITING_KEYS + + +@pytest.mark.parametrize("key", ENTITY_KEYS) +def test_controller_entity_key_is_documented(key): + assert key in CONTROLLER_DOC, f"{key!r} is used by a controller entity but not in the controller key table" + + +@pytest.mark.parametrize("key", WRITING_KEYS) +def test_writing_controller_entity_uses_a_writable_key(key): + assert "W" in CONTROLLER_DOC[key]["rw"], f"{key!r} is documented as {CONTROLLER_DOC[key]['rw']!r}" + + +@pytest.mark.parametrize("key", WRITING_KEYS) +def test_writing_controller_entity_key_is_writeable_in_keys_py(key): + assert TAGS[key].writeable, f"{key!r} is written by a controller entity but keys.py says writeable=False" + + +@pytest.mark.parametrize("key", [pytest.param(k, id=k, marks=[pytest.mark.needs_decision] if k in UNDOCUMENTED_REQUESTS else []) + for k in REQUESTED]) +def test_key_requested_from_a_controller_is_documented(key): + assert key in CONTROLLER_DOC, f"{key!r} is requested from controllers but not in the controller key table" + + +def test_undocumented_request_list_has_no_stale_entries(): + stale = sorted(k for k in UNDOCUMENTED_REQUESTS if k in CONTROLLER_DOC or k not in REQUESTED) + assert stale == [], "documented now, or no longer requested: remove them from UNDOCUMENTED_REQUESTS" + + +def test_controller_does_not_get_the_temporary_current_limit(): + # answers the question in PR #177: neither `tcl` nor `cll` exist in the controller API + assert "tcl" not in CONTROLLER_DOC and "cll" not in CONTROLLER_DOC + assert "tcl" not in REQUESTED and "cll" not in REQUESTED diff --git a/tests/integration/test_coordinator_errors.py b/tests/integration/test_coordinator_errors.py new file mode 100644 index 0000000..db2c57d --- /dev/null +++ b/tests/integration/test_coordinator_errors.py @@ -0,0 +1,33 @@ +"""Coordinator: connection errors while writing must be counted and reported as ValueError.""" +from unittest.mock import AsyncMock + +import pytest +from aiohttp import ClientConnectionError +from pytest_homeassistant_custom_component.common import MockConfigEntry + +from custom_components.goecharger_api2 import GoeChargerDataUpdateCoordinator + +DOMAIN = "goecharger_api2" + + +@pytest.fixture +async def coordinator(hass): + entry = MockConfigEntry(domain=DOMAIN, title="test", data={ + "mode": "lan", "host": "192.0.2.10", "id": "123456", "integration_type": "charger", + "scan_interval": 30}) + entry.add_to_hass(hass) + return GoeChargerDataUpdateCoordinator(hass, entry) + + +async def test_write_key_connection_error_is_counted(coordinator): + coordinator.bridge.write_value_to_key = AsyncMock(side_effect=ClientConnectionError("down")) + with pytest.raises(ValueError, match="ClientConnectionError while writing amp"): + await coordinator.async_write_key("amp", 16) + assert coordinator._CLIENT_COMMUNICATION_ERROR_COUNT == 1 + + +async def test_write_multiple_keys_connection_error_is_counted(coordinator): + coordinator.bridge.write_multiple_values_to_keys = AsyncMock(side_effect=ClientConnectionError("down")) + with pytest.raises(ValueError, match="ClientConnectionError while writing multiple ct"): + await coordinator.async_write_multiple_keys({"ct": "1"}, "ct", "default") + assert coordinator._CLIENT_COMMUNICATION_ERROR_COUNT == 1 diff --git a/tests/integration/test_coordinator_failures.py b/tests/integration/test_coordinator_failures.py new file mode 100644 index 0000000..94d5379 --- /dev/null +++ b/tests/integration/test_coordinator_failures.py @@ -0,0 +1,165 @@ +"""Coordinator and setup: what happens when the charger cannot be reached or answers badly. + +Covers the error counter, the 2 minute pause after a connection error, the delayed restart after more +than 5 errors, writes during that pause, and the start of the config entry (ConfigEntryNotReady). +""" +import time +from collections import ChainMap +from unittest.mock import AsyncMock, patch + +import pytest +from aiohttp import ClientConnectionError +from homeassistant.exceptions import ConfigEntryNotReady +from homeassistant.helpers.update_coordinator import UpdateFailed +from pytest_homeassistant_custom_component.common import MockConfigEntry + +from custom_components.goecharger_api2 import GoeChargerDataUpdateCoordinator, async_setup_entry +from custom_components.goecharger_api2.const import DOMAIN + + +def _entry(hass, password=None): + data = {"mode": "lan", "host": "192.0.2.10", "id": "123456", "integration_type": "charger", + "scan_interval": 30} + if password: + data["password"] = password + entry = MockConfigEntry(domain=DOMAIN, title="t", data=data) + entry.add_to_hass(hass) + return entry + + +@pytest.fixture +async def coordinator(hass): + coordinator = GoeChargerDataUpdateCoordinator(hass, _entry(hass)) + bridge = coordinator.bridge + coordinator.data = ChainMap(bridge._ws_states, bridge._config, bridge._states, bridge._versions) + coordinator.trigger_restart_delayed = AsyncMock() + return coordinator + + +# ----------------------------------------------------------------------------- reading + +async def test_connection_error_while_reading_raises_update_failed_and_counts(coordinator): + coordinator.bridge.read_all = AsyncMock(side_effect=ClientConnectionError("down")) + with pytest.raises(UpdateFailed, match="ClientConnectionError"): + await coordinator._async_update_data() + assert coordinator._CLIENT_COMMUNICATION_ERROR_COUNT == 1 + + +async def test_unexpected_error_while_reading_raises_update_failed(coordinator): + coordinator.bridge.read_all = AsyncMock(side_effect=RuntimeError("boom")) + with pytest.raises(UpdateFailed): + await coordinator._async_update_data() + + +async def test_no_request_is_made_during_the_pause_after_a_connection_error(coordinator): + coordinator.bridge.read_all = AsyncMock(side_effect=ClientConnectionError("down")) + with pytest.raises(UpdateFailed): + await coordinator._async_update_data() + coordinator.bridge.read_all.reset_mock() + + assert await coordinator._async_update_data() is coordinator.data # pause: old data, no request + coordinator.bridge.read_all.assert_not_awaited() + + +async def test_more_than_five_errors_trigger_exactly_one_restart(hass, coordinator): + coordinator.bridge.read_all = AsyncMock(side_effect=ClientConnectionError("down")) + for _ in range(6): + coordinator._CLIENT_COMMUNICATION_ERROR_TS = 0 # skip the pause between the attempts + with pytest.raises(UpdateFailed): + await coordinator._async_update_data() + await hass.async_block_till_done() + assert coordinator._RESTART_TRIGGERED is True + coordinator.trigger_restart_delayed.assert_awaited_once() + + # while the restart is pending, further polls neither fail nor trigger a second restart + for _ in range(2): + coordinator._CLIENT_COMMUNICATION_ERROR_TS = 0 + assert await coordinator._async_update_data() is coordinator.data + await hass.async_block_till_done() + coordinator.trigger_restart_delayed.assert_awaited_once() + + +async def test_five_errors_do_not_trigger_a_restart_yet(hass, coordinator): + coordinator.bridge.read_all = AsyncMock(side_effect=ClientConnectionError("down")) + for _ in range(5): + coordinator._CLIENT_COMMUNICATION_ERROR_TS = 0 + with pytest.raises(UpdateFailed): + await coordinator._async_update_data() + await hass.async_block_till_done() + assert coordinator._RESTART_TRIGGERED is False + coordinator.trigger_restart_delayed.assert_not_awaited() + + +async def test_no_request_is_made_while_a_restart_is_pending(coordinator): + coordinator._RESTART_TRIGGERED = True + coordinator.bridge.read_all = AsyncMock() + assert await coordinator._async_update_data() is coordinator.data + coordinator.bridge.read_all.assert_not_awaited() + + +async def test_a_successful_read_clears_the_error_state(coordinator): + coordinator._CLIENT_COMMUNICATION_ERROR_COUNT = 3 + coordinator._CLIENT_COMMUNICATION_ERROR_TS = 0 + coordinator.bridge.read_all = AsyncMock(return_value={"car": 1}) + assert await coordinator._async_update_data() == {"car": 1} + assert coordinator._CLIENT_COMMUNICATION_ERROR_COUNT == 0 + + +# ----------------------------------------------------------------------------- writing during an outage + +@pytest.mark.parametrize("pause", ["error_pause", "restart_pending"]) +async def test_writes_are_refused_without_touching_the_charger(coordinator, pause): + if pause == "error_pause": + coordinator._CLIENT_COMMUNICATION_ERROR_TS = time.time() + else: + coordinator._RESTART_TRIGGERED = True + coordinator.bridge.write_value_to_key = AsyncMock() + coordinator.bridge.write_multiple_values_to_keys = AsyncMock() + + with pytest.raises(ValueError): + await coordinator.async_write_key("amp", 10) + with pytest.raises(ValueError): + await coordinator.async_write_multiple_keys({"ct": "1"}, "ct", "default") + coordinator.bridge.write_value_to_key.assert_not_awaited() + coordinator.bridge.write_multiple_values_to_keys.assert_not_awaited() + + +async def test_unexpected_write_error_becomes_value_error(coordinator): + coordinator.bridge.write_value_to_key = AsyncMock(side_effect=RuntimeError("boom")) + with pytest.raises(ValueError, match="RuntimeError"): + await coordinator.async_write_key("amp", 10) + + +# ----------------------------------------------------------------------------- start of the config entry + +async def test_setup_fails_when_the_first_poll_fails(hass): + entry = _entry(hass) + with patch.object(GoeChargerDataUpdateCoordinator, "_async_update_data", + AsyncMock(side_effect=UpdateFailed("down"))): + with pytest.raises(ConfigEntryNotReady): + await async_setup_entry(hass, entry) + + +async def test_setup_fails_when_versions_cannot_be_read(hass): + entry = _entry(hass) + with patch.object(GoeChargerDataUpdateCoordinator, "_async_update_data", AsyncMock(return_value={"car": 1})), \ + patch.object(GoeChargerDataUpdateCoordinator, "read_versions", AsyncMock(return_value=False)): + with pytest.raises(ConfigEntryNotReady): + await async_setup_entry(hass, entry) + + +async def test_setup_fails_when_the_websocket_delivers_no_data(hass): + entry = _entry(hass, password="secret") + with patch.object(GoeChargerDataUpdateCoordinator, "start_websocket_and_wait_for_first_data", + AsyncMock(return_value=False)): + with pytest.raises(ConfigEntryNotReady): + await async_setup_entry(hass, entry) + + +async def test_setup_fails_when_the_websocket_start_raises(hass): + entry = _entry(hass, password="secret") + with patch.object(GoeChargerDataUpdateCoordinator, "start_websocket_and_wait_for_first_data", + AsyncMock(side_effect=ConfigEntryNotReady("timeout"))): + with pytest.raises(ConfigEntryNotReady): + await async_setup_entry(hass, entry) + assert entry.entry_id not in hass.data.get(DOMAIN, {}) diff --git a/tests/integration/test_coordinator_runtime.py b/tests/integration/test_coordinator_runtime.py new file mode 100644 index 0000000..26be57a --- /dev/null +++ b/tests/integration/test_coordinator_runtime.py @@ -0,0 +1,535 @@ +"""Coordinator at runtime: write results, the 16 A limiter, device detection, WebSocket start, reconnect and housekeeping. + +The coordinator and the bridge are the real code. Timers (`async_call_later`, `async_track_time_interval`) +and sleeps are replaced so that no test waits or leaves a timer behind. +""" +import asyncio +import logging +import time +from collections import ChainMap +from types import SimpleNamespace +from unittest.mock import AsyncMock, MagicMock, patch + +import pytest +from homeassistant.components.number import NumberDeviceClass +from homeassistant.exceptions import ConfigEntryNotReady +from homeassistant.helpers import device_registry as dr + +from custom_components.goecharger_api2 import check_and_write_to_16a +from custom_components.goecharger_api2.const import DOMAIN +from custom_components.goecharger_api2.pygoecharger_ha import NonNullDict +from custom_components.goecharger_api2.pygoecharger_ha.keys import Tag + +from .factories import make_coordinator + +PKG = "custom_components.goecharger_api2" + + +# ----------------------------------------------------------------------------- handle_write_result + +async def test_websocket_write_result_is_left_to_the_websocket_processor(hass): + coordinator = make_coordinator(hass, data={"amp": 6}, websocket=True) + coordinator.handle_write_result("single", 10, "amp", None, None) + assert coordinator.data == {"amp": 6} + + +async def test_http_write_result_updates_the_data_and_refreshes_the_entity(hass): + coordinator = make_coordinator(hass, data={"amp": 6}) + entity = MagicMock() + coordinator.handle_write_result("single", 10, "amp", {"amp": 10}, entity) + assert coordinator.data["amp"] == 10 + entity.async_schedule_update_ha_state.assert_called_once_with(force_refresh=True) + + +async def test_write_result_without_the_key_is_logged_as_error(hass, caplog): + coordinator = make_coordinator(hass, data={"amp": 6}) + with caplog.at_level(logging.ERROR): + coordinator.handle_write_result("single", 10, "amp", {"err": {"amp": "denied"}}, None) + assert coordinator.data["amp"] == 6 and "could not write" in caplog.text + + +async def test_pv_data_result_rebuilds_the_data_and_notifies_listeners_without_refresh(hass): + coordinator = make_coordinator(hass, data={}) + coordinator.async_update_listeners = MagicMock() + entity = MagicMock() + coordinator.handle_write_result("single", {}, Tag.IDS.key, {Tag.IDS.key: {}}, entity) + assert isinstance(coordinator.data, ChainMap) + coordinator.async_update_listeners.assert_called_once() + entity.async_schedule_update_ha_state.assert_not_called() + + +async def test_refresh_button_result_schedules_a_delayed_update(hass): + coordinator = make_coordinator(hass, data={}) + coordinator.request_update_in_sec = MagicMock() + entity = MagicMock() + key = Tag.INTERNAL_FORCE_REFRESH_ALL.key + coordinator.handle_write_result("single", "TRIGGER", key, {key: "TRIGGER"}, entity) + coordinator.request_update_in_sec.assert_called_once_with(2.5) + entity.async_schedule_update_ha_state.assert_not_called() + + +async def test_write_result_without_entity_does_not_fail(hass): + coordinator = make_coordinator(hass, data={}) + coordinator.handle_write_result("single", 1, "amp", {"amp": 1}, None) + assert coordinator.data["amp"] == 1 + + +async def test_controller_treats_pv_key_like_any_other(hass): + coordinator = make_coordinator(hass, data={}, intg_type="controller") + coordinator.async_update_listeners = MagicMock() + entity = MagicMock() + coordinator.handle_write_result("single", 1, Tag.IDS.key, {Tag.IDS.key: 1}, entity) + coordinator.async_update_listeners.assert_not_called() + entity.async_schedule_update_ha_state.assert_called_once() + + +# ----------------------------------------------------------------------------- 16 A limiter + +def _current_entity(key, max_value=16, **kwargs): + values = dict(original_device_class=NumberDeviceClass.CURRENT, capabilities={"max": max_value}, + disabled=False, orphaned_timestamp=None, translation_key=key) + values.update(kwargs) + return SimpleNamespace(**values) + + +async def _run_limiter(hass, entities, read_result, lb_group=False): + bridge = SimpleNamespace(_read_filtered_data=AsyncMock(return_value=read_result), + write_value_to_key=AsyncMock()) + with patch(f"{PKG}.entity_reg.async_entries_for_config_entry", return_value=entities): + await check_and_write_to_16a(hass, "entry", bridge, lb_group) + return bridge + + +async def test_limiter_lowers_a_value_above_16_a(hass): + bridge = await _run_limiter(hass, [_current_entity("amp")], {"amp": 20}) + bridge.write_value_to_key.assert_awaited_once_with("amp", 16) + + +async def test_limiter_leaves_values_up_to_16_a_alone(hass): + bridge = await _run_limiter(hass, [_current_entity("amp")], {"amp": 16}) + bridge.write_value_to_key.assert_not_awaited() + + +async def test_limiter_lowers_a_value_inside_an_object(hass): + entities = [_current_entity("lot_amp"), _current_entity("lot_sta")] + bridge = await _run_limiter(hass, entities, {"lot": {"amp": 32, "sta": 10}}) + bridge.write_value_to_key.assert_awaited_once_with("lot", {"amp": 16, "sta": 10}) + assert bridge._read_filtered_data.await_args.kwargs["filters"] == "lot" + + +async def test_limiter_skips_the_load_balancer_value_when_a_group_is_set(hass): + bridge = await _run_limiter(hass, [_current_entity("lot_amp"), _current_entity("amp")], + {"lot": {"amp": 32}, "amp": 20}, lb_group=True) + bridge.write_value_to_key.assert_awaited_once_with("amp", 16) + + +async def test_limiter_ignores_entities_that_do_not_count(hass): + entities = [ + _current_entity("a", max_value=32), + _current_entity("b", original_device_class=None), + _current_entity("c", disabled=True), + _current_entity("d", orphaned_timestamp=123.0), + _current_entity("e", capabilities={}), + ] + bridge = await _run_limiter(hass, entities, {}) + bridge._read_filtered_data.assert_not_awaited() + + +async def test_limiter_asks_for_a_shared_key_only_once(hass): + bridge = await _run_limiter(hass, [_current_entity("amp"), _current_entity("amp")], {"amp": 10}) + assert bridge._read_filtered_data.await_args.kwargs["filters"] == "amp" + + +async def test_limiter_ignores_values_that_are_neither_numbers_nor_objects(hass): + bridge = await _run_limiter(hass, [_current_entity("amp"), _current_entity("fna")], {"amp": 20, "fna": "name"}) + bridge.write_value_to_key.assert_awaited_once_with("amp", 16) + + +async def test_limiter_survives_a_failing_read(hass, caplog): + bridge = SimpleNamespace(_read_filtered_data=AsyncMock(side_effect=RuntimeError("boom")), + write_value_to_key=AsyncMock()) + with patch(f"{PKG}.entity_reg.async_entries_for_config_entry", return_value=[_current_entity("amp")]): + await check_and_write_to_16a(hass, "entry", bridge, False) + assert "Error while forcing 16A settings" in caplog.text + bridge.write_value_to_key.assert_not_awaited() + + +async def test_limiter_task_waits_and_then_runs_the_check(hass): + coordinator = make_coordinator(hass, data={}) + coordinator.is_loadbalancer_group_id_set = True + with patch(f"{PKG}.asyncio.sleep", AsyncMock()) as sleep, \ + patch(f"{PKG}.check_and_write_to_16a", AsyncMock()) as check: + await coordinator.check_for_16a_limit(hass, "entry-id") + sleep.assert_awaited_once_with(15) + check.assert_awaited_once_with(hass=hass, config_entry_id="entry-id", bridge=coordinator.bridge, is_lbgroup_id_set=True) + + +# ----------------------------------------------------------------------------- device detection (read_versions) + +def _versions_coordinator(hass, versions, **kwargs): + coordinator = make_coordinator(hass, data={}, **kwargs) + coordinator.bridge.read_versions = AsyncMock(return_value=True) + coordinator.bridge._versions = NonNullDict(versions) + return coordinator + + +async def test_read_versions_fails_when_the_bridge_has_no_versions(hass): + coordinator = make_coordinator(hass, data={}) + coordinator.bridge.read_versions = AsyncMock(return_value=False) + assert await coordinator.read_versions() is False + + +async def test_read_versions_fails_for_a_plain_dictionary(hass): + coordinator = make_coordinator(hass, data={}) + coordinator.bridge.read_versions = AsyncMock(return_value=True) + coordinator.bridge._versions = {"typ": "x"} + assert await coordinator.read_versions() is False + + +async def test_cards_object_lists_only_activated_cards(hass): + coordinator = _versions_coordinator(hass, {"typ": "go-eCharger_V4", "cards": [{"cardId": 1}, {"cardId": 0}, {"cardId": 7}]}) + await coordinator.read_versions() + assert coordinator._cards_as_object_present is True and coordinator.available_cards_idx == ["1", "3"] + + +async def test_single_card_keys_are_used_without_a_cards_object(hass): + coordinator = _versions_coordinator(hass, {"typ": "go-eCharger_V4", "c0i": "A", "c2i": "B"}) + await coordinator.read_versions() + assert coordinator._cards_as_object_present is False and coordinator.available_cards_idx == ["1", "3"] + + +async def test_no_cards_at_all_gives_an_empty_list(hass): + coordinator = _versions_coordinator(hass, {"typ": "go-eCharger_V4"}) + await coordinator.read_versions() + assert coordinator.available_cards_idx == [] + + +@pytest.mark.parametrize("typ, expected", [ + ("go-eCharger_Phoenix", True), ("go-eCharger_Core", True), ("go-eCharger Gemini Pro", True), + ("go-eCharger_V4", False)]) +async def test_core_wallbox_is_recognised_by_its_type(hass, typ, expected): + coordinator = _versions_coordinator(hass, {"typ": typ}) + await coordinator.read_versions() + assert coordinator._is_core_wallbox is expected + + +async def test_type_falls_back_to_the_type_stored_at_setup(hass): + coordinator = _versions_coordinator(hass, {}, type="go-eCharger_Phoenix [11 kW] Local") + await coordinator.read_versions() + assert coordinator._is_core_wallbox is True + + +async def test_unreadable_cable_limit_on_a_core_wallbox_limits_to_16_a(hass): + coordinator = _versions_coordinator(hass, {"typ": "go-eCharger_Phoenix", "cll": {"cableCurrentLimit": "n/a"}}) + await coordinator.read_versions() + assert coordinator.limit_to16a is True + + +async def test_unreadable_cable_limit_on_another_wallbox_uses_the_adapter_flag(hass): + coordinator = _versions_coordinator(hass, {"typ": "go-eCharger_V4", "cll": {"cableCurrentLimit": "n/a"}, "adi": True}) + await coordinator.read_versions() + assert coordinator.limit_to16a is True + + +async def test_empty_cll_object_falls_back_to_the_adapter_flag(hass): + coordinator = _versions_coordinator(hass, {"typ": "go-eCharger_V4", "cll": {}, "adi": False}) + await coordinator.read_versions() + assert coordinator.limit_to16a is False + + +async def test_load_balancer_group_is_detected(hass): + coordinator = _versions_coordinator(hass, {"typ": "go-eCharger_V4", "log": "group-1"}) + await coordinator.read_versions() + assert coordinator.is_loadbalancer_group_id_set is True + + +async def test_blank_load_balancer_group_is_not_a_group(hass): + coordinator = _versions_coordinator(hass, {"typ": "go-eCharger_V4", "log": " "}) + await coordinator.read_versions() + assert coordinator.is_loadbalancer_group_id_set is False + + +async def test_device_info_uses_type_mode_serial_and_cleaned_firmware(hass): + coordinator = _versions_coordinator(hass, {"typ": "go-eCharger_V4", "fwv": "60.3-beta", "var": 11}, type="V4 [11 kW] Local") + await coordinator.read_versions() + info = coordinator._device_info_dict + assert info["identifiers"] == {(DOMAIN, "charger", "lan", "123456")} + assert info["sw_version"] == "60.3" and "[16A limited]" in info["model"] and "HTTP v2 API" in info["model"] + + +async def test_device_info_without_firmware_and_with_websocket_label(hass): + coordinator = _versions_coordinator(hass, {"typ": "go-eCharger_V4", "adi": False}, password="secret") + await coordinator.read_versions() + info = coordinator._device_info_dict + assert info["sw_version"] == "UNKNOWN" and info["model"].endswith("WebSocket") + + +async def test_controller_has_no_charger_features(hass): + coordinator = _versions_coordinator(hass, {"typ": "go-eController", "fwv": "1.1.1"}, intg_type="controller") + await coordinator.read_versions() + assert coordinator.limit_to16a is False + assert coordinator._device_info_dict["identifiers"] == {(DOMAIN, "controller", "lan", "123456")} + + +# ----------------------------------------------------------------------------- which entities exist + +@pytest.mark.parametrize("core, key, idx, expected", [ + (True, "tma", 0, False), (True, "tma", 1, False), (True, "tma", 2, True), (True, "tma", 5, True), + (False, "tma", 0, True), (False, "tma", 1, True), (False, "tma", 2, False), + (True, "cus", None, False), (True, "bac", None, False), (False, "cus", None, True), + (False, "iso2ena", None, False), (True, "iso2ena", None, True), (True, "amp", None, True), +]) +async def test_entity_filter_depends_on_the_wallbox_generation(hass, core, key, idx, expected): + coordinator = make_coordinator(hass, data={}) + coordinator._is_core_wallbox = core + description = SimpleNamespace(key=key) if idx is None else SimpleNamespace(key=key, idx=idx) + assert coordinator.is_valid_charger_entity(description) is expected + + +async def test_temperature_entity_without_index_is_always_valid(hass): + coordinator = make_coordinator(hass, data={}) + coordinator._is_core_wallbox = True + assert coordinator.is_valid_charger_entity(SimpleNamespace(key="tma")) is True + + +# ----------------------------------------------------------------------------- WebSocket start + +def _fake_connect(coordinator, *, set_event=True, error=None): + async def connect(a_event=None, keep_ws_states_after_close=False): + if error is not None: + a_event.set_error(error) + elif set_event: + a_event.set() + coordinator.bridge.ws_connect = connect + + +async def test_websocket_start_succeeds_when_the_first_data_arrives(hass): + coordinator = make_coordinator(hass, password="secret") + _fake_connect(coordinator) + with patch(f"{PKG}.async_call_later", MagicMock()) as later: + assert await coordinator.start_websocket_and_wait_for_first_data() is True + later.assert_called_once() + + +async def test_websocket_start_reports_errors_from_the_connection(hass): + coordinator = make_coordinator(hass, password="secret") + _fake_connect(coordinator, error=Exception("Authentication failed")) + with patch(f"{PKG}.async_call_later", MagicMock()): + assert await coordinator.start_websocket_and_wait_for_first_data() is False + + +async def test_websocket_start_times_out_without_data(hass): + coordinator = make_coordinator(hass, password="secret") + _fake_connect(coordinator, set_event=False) + real_timeout = asyncio.timeout + with patch(f"{PKG}.asyncio.timeout", lambda _: real_timeout(0.01)): + with pytest.raises(ConfigEntryNotReady): + await coordinator.start_websocket_and_wait_for_first_data() + + +@pytest.mark.parametrize("versions, expected", [ + (AsyncMock(return_value=True), True), + (AsyncMock(return_value=False), False), + (AsyncMock(side_effect=RuntimeError("boom")), False), +]) +async def test_websocket_start_at_setup_also_needs_the_versions(hass, versions, expected): + coordinator = make_coordinator(hass, password="secret") + _fake_connect(coordinator) + coordinator.read_versions = versions + with patch(f"{PKG}.async_call_later", MagicMock()): + assert await coordinator.start_websocket_and_wait_for_first_data(is_integration_init=True) is expected + + +# ----------------------------------------------------------------------------- forced reconnect + +async def test_forced_reconnect_is_ignored_shortly_after_integration_start(hass): + coordinator = make_coordinator(hass, password="secret") + coordinator.start_websocket_and_wait_for_first_data = AsyncMock() + await coordinator.force_async_update_now() + coordinator.start_websocket_and_wait_for_first_data.assert_not_awaited() + + +async def test_forced_reconnect_is_ignored_shortly_after_a_fresh_connection(hass): + coordinator = make_coordinator(hass, password="secret") + coordinator._integration_start = time.time() - 1000 + coordinator.bridge.ws_connection_start = time.time() + coordinator.start_websocket_and_wait_for_first_data = AsyncMock() + await coordinator.force_async_update_now() + coordinator.start_websocket_and_wait_for_first_data.assert_not_awaited() + + +@pytest.mark.parametrize("reconnect_ok", [True, False]) +async def test_forced_reconnect_restarts_the_websocket(hass, reconnect_ok): + coordinator = make_coordinator(hass, password="secret") + coordinator._integration_start = time.time() - 1000 + coordinator.start_websocket_and_wait_for_first_data = AsyncMock(return_value=reconnect_ok) + with patch(f"{PKG}.asyncio.sleep", AsyncMock()) as sleep: + await coordinator.force_async_update_now() + sleep.assert_awaited_once_with(2) + coordinator.start_websocket_and_wait_for_first_data.assert_awaited_once_with(is_integration_init=False) + + +# ----------------------------------------------------------------------------- watchdog + +async def test_watchdog_starts_a_connection_when_there_is_none(hass): + coordinator = make_coordinator(hass, password="secret") + _fake_connect(coordinator) + with patch(f"{PKG}.async_call_later", MagicMock()) as later: + await coordinator._async_watchdog_check() + await asyncio.sleep(0) + assert coordinator._ws_start_task is not None and later.called + + +async def test_watchdog_leaves_a_healthy_connection_alone(hass): + coordinator = make_coordinator(hass, password="secret", websocket=True) + coordinator.bridge._ws_LAST_UPDATE = time.time() + coordinator._ws_start_task = None + await coordinator._async_watchdog_check() + assert coordinator._ws_start_task is None + + +async def test_watchdog_cancels_a_connection_without_data(hass): + coordinator = make_coordinator(hass, password="secret", websocket=True) + coordinator.bridge._ws_LAST_UPDATE = time.time() - 120 + task = asyncio.create_task(asyncio.sleep(3600)) + coordinator._ws_start_task = task + with patch(f"{PKG}.async_call_later", MagicMock()): + await coordinator._async_watchdog_check() + await asyncio.sleep(0) + assert task.cancelled() and coordinator._ws_start_task is None + + +async def test_start_watchdog_checks_once_and_registers_the_timer(hass): + coordinator = make_coordinator(hass, password="secret", websocket=True) + coordinator.bridge._ws_LAST_UPDATE = time.time() + unsubscribe = MagicMock() + with patch(f"{PKG}.async_track_time_interval", MagicMock(return_value=unsubscribe)) as track, \ + patch(f"{PKG}.async_call_later", MagicMock()): + await coordinator.start_watchdog() + coordinator.stop_watchdog() + track.assert_called_once() + unsubscribe.assert_called_once() + + +async def test_stop_watchdog_without_a_timer_does_nothing(hass): + coordinator = make_coordinator(hass, password="secret") + coordinator._watchdog = None + coordinator.stop_watchdog() + + +async def test_running_connection_task_is_cancelled_and_forgotten(hass): + coordinator = make_coordinator(hass, password="secret") + task = asyncio.create_task(asyncio.sleep(3600)) + coordinator._ws_start_task = task + coordinator._check_for_ws_task_and_cancel_if_running() + await asyncio.sleep(0) + assert task.cancelled() and coordinator._ws_start_task is None + + +async def test_finished_connection_task_is_left_alone(hass): + coordinator = make_coordinator(hass, password="secret") + task = asyncio.create_task(asyncio.sleep(0)) + await task + coordinator._ws_start_task = task + coordinator._check_for_ws_task_and_cancel_if_running() + assert coordinator._ws_start_task is task + + +async def test_failing_cancel_is_logged_not_raised(hass): + coordinator = make_coordinator(hass, password="secret") + task = MagicMock() + task.done.return_value = False + task.cancel.side_effect = RuntimeError("boom") + coordinator._ws_start_task = task + coordinator._check_for_ws_task_and_cancel_if_running() + assert coordinator._ws_start_task is None + + +# ----------------------------------------------------------------------------- device registry label + +async def _device_for(hass, coordinator): + entry = coordinator._config_entry + coordinator.config_entry = entry + coordinator._device_info_dict = {"identifiers": {(DOMAIN, "charger", "lan", "123456")}} + coordinator._device_info_model_raw = "model" + return dr.async_get(hass).async_get_or_create(config_entry_id=entry.entry_id, identifiers={(DOMAIN, "charger", "lan", "123456")}) + + +async def test_device_label_shows_a_working_websocket(hass): + coordinator = make_coordinator(hass, password="secret", websocket=True) + device = await _device_for(hass, coordinator) + coordinator.bridge._ws_LAST_UPDATE = time.time() + await coordinator.call_later_update_device_registry(None) + assert dr.async_get(hass).async_get(device.id).model == "model ✅" + + +async def test_device_label_shows_a_broken_websocket(hass): + coordinator = make_coordinator(hass, password="secret") + device = await _device_for(hass, coordinator) + await coordinator.call_later_update_device_registry(None) + assert dr.async_get(hass).async_get(device.id).model == "model ⛔" + + +async def test_device_label_is_skipped_without_identifiers(hass): + coordinator = make_coordinator(hass, password="secret") + coordinator._device_info_dict = {} + await coordinator.call_later_update_device_registry(None) + + +async def test_device_label_is_skipped_in_polling_mode(hass): + coordinator = make_coordinator(hass) + device = await _device_for(hass, coordinator) + await coordinator.call_later_update_device_registry(None) + assert dr.async_get(hass).async_get(device.id).model is None + + +async def test_device_label_ignores_a_device_that_is_not_registered(hass): + coordinator = make_coordinator(hass, password="secret") + coordinator.config_entry = coordinator._config_entry + coordinator._device_info_dict = {"identifiers": {(DOMAIN, "charger", "lan", "000000")}} + await coordinator.call_later_update_device_registry(None) + + +# ----------------------------------------------------------------------------- housekeeping + +async def test_delayed_update_refreshes_the_coordinator(hass): + coordinator = make_coordinator(hass, data={}) + coordinator.async_refresh = AsyncMock() + coordinator.request_update_in_sec(0) + await coordinator._debounced_update_task + coordinator.async_refresh.assert_awaited_once() + + +async def test_a_new_delayed_update_replaces_the_pending_one(hass): + coordinator = make_coordinator(hass, data={}) + coordinator.async_refresh = AsyncMock() + coordinator.request_update_in_sec(3600) + first = coordinator._debounced_update_task + coordinator.request_update_in_sec(3600) + await asyncio.sleep(0) + assert first.cancelled() + coordinator.cancel_existing_debouncer() + + +async def test_clear_data_resets_errors_and_empties_the_data(hass): + coordinator = make_coordinator(hass, data={"a": 1}) + coordinator._CLIENT_COMMUNICATION_ERROR_COUNT = 3 + coordinator._CLIENT_COMMUNICATION_ERROR_TS = 5 + coordinator._RESTART_TRIGGERED = True + coordinator.clear_data() + assert coordinator.data == {} and coordinator._CLIENT_COMMUNICATION_ERROR_COUNT == 0 + assert coordinator._CLIENT_COMMUNICATION_ERROR_TS == 0 and coordinator._RESTART_TRIGGERED is False + + +async def test_delayed_restart_reloads_the_config_entry(hass): + coordinator = make_coordinator(hass, data={}) + with patch(f"{PKG}.asyncio.sleep", AsyncMock()), \ + patch.object(hass.config_entries, "async_reload", AsyncMock()) as reload: + await coordinator.trigger_restart_delayed() + reload.assert_awaited_once_with(coordinator._config_entry.entry_id) + + +async def test_event_callback_accepts_any_event(hass): + coordinator = make_coordinator(hass, data={}) + assert coordinator(SimpleNamespace()) is True + assert coordinator.cards_as_object_present() is False diff --git a/tests/integration/test_entity_ids.py b/tests/integration/test_entity_ids.py new file mode 100644 index 0000000..39ac38f --- /dev/null +++ b/tests/integration/test_entity_ids.py @@ -0,0 +1,77 @@ +"""Entity IDs must not change by accident. + +Users have dashboards, automations, statistics and renamed entities that reference these IDs. +The expected IDs are stored in `snapshots/entity_ids.json`. If an ID changes on purpose, regenerate +the file with `UPDATE_SNAPSHOT=1 python -m pytest tests/integration/test_entity_ids.py` and review the diff. +""" +import json +import os +from pathlib import Path + +import pytest +from pytest_homeassistant_custom_component.common import MockConfigEntry + +from custom_components.goecharger_api2 import GoeChargerBaseEntity, GoeChargerDataUpdateCoordinator +from custom_components.goecharger_api2 import const + +SNAPSHOT = Path(__file__).parent / "snapshots" / "entity_ids.json" + +CHARGER_LISTS = { + "binary_sensor": const.BINARY_SENSORS, "button": const.BUTTONS, "number": const.NUMBER_SENSORS, + "select": const.SELECT_SENSORS, "sensor": const.SENSOR_SENSORS, "switch": const.SWITCH_SENSORS, +} +CONTROLLER_LISTS = { + "binary_sensor": const.CONTROLLER_BINARY_SENSORS, "button": const.CONTROLLER_BUTTONS, + "number": const.CONTROLLER_NUMBER_SENSORS, "select": const.CONTROLLER_SELECT_SENSORS, + "sensor": const.CONTROLLER_SENSOR_SENSORS, "switch": const.CONTROLLER_SWITCH_SENSORS, +} + +VARIANTS = { + "charger_lan": ({"mode": "lan", "host": "192.0.2.10"}, "charger", CHARGER_LISTS), + "charger_wan": ({"mode": "wan", "token": "t"}, "charger", CHARGER_LISTS), + "controller_lan": ({"mode": "lan", "host": "192.0.2.10"}, "controller", CONTROLLER_LISTS), +} + + +def _entities(hass, variant): + data, intg_type, lists = VARIANTS[variant] + entry = MockConfigEntry(domain="goecharger_api2", title="t", + data={**data, "id": "123456", "integration_type": intg_type}) + entry.add_to_hass(hass) + coordinator = GoeChargerDataUpdateCoordinator(hass, entry) + return [GoeChargerBaseEntity(platform, coordinator, description) + for platform, descriptions in lists.items() for description in descriptions] + + +@pytest.mark.parametrize("variant", VARIANTS) +async def test_entity_ids_match_snapshot(hass, variant): + actual = sorted(entity.entity_id for entity in _entities(hass, variant)) + if os.environ.get("UPDATE_SNAPSHOT"): + stored = json.loads(SNAPSHOT.read_text()) if SNAPSHOT.exists() else {} + stored[variant] = actual + SNAPSHOT.parent.mkdir(exist_ok=True) + SNAPSHOT.write_text(json.dumps(dict(sorted(stored.items())), indent=1) + "\n") + pytest.skip("snapshot updated") + expected = json.loads(SNAPSHOT.read_text())[variant] + assert {"added": sorted(set(actual) - set(expected)), "removed": sorted(set(expected) - set(actual))} == \ + {"added": [], "removed": []} + + +@pytest.mark.parametrize("variant", VARIANTS) +async def test_entity_ids_are_unique(hass, variant): + ids = [entity.entity_id for entity in _entities(hass, variant)] + duplicates = sorted({i for i in ids if ids.count(i) > 1}) + assert duplicates == [] + + +@pytest.mark.parametrize("variant", VARIANTS) +async def test_unique_id_is_derived_from_the_entity_id(hass, variant): + for entity in _entities(hass, variant): + assert entity.unique_id == f"goecharger_api2.{entity.entity_id.split('.')[1]}" + + +async def test_wan_entities_are_prefixed_differently_from_lan(hass): + lan = {e.entity_id for e in _entities(hass, "charger_lan")} + wan = {e.entity_id for e in _entities(hass, "charger_wan")} + assert lan.isdisjoint(wan) + assert all(".goe_wan_123456_" in i for i in wan) and all(".goe_123456_" in i for i in lan) diff --git a/tests/integration/test_migration.py b/tests/integration/test_migration.py new file mode 100644 index 0000000..54a7fe9 --- /dev/null +++ b/tests/integration/test_migration.py @@ -0,0 +1,47 @@ +"""Config entry migration (async_migrate_entry). + +Version 2.0 -> 2.1 deletes all devices of the entry. Home Assistant removes the entities of a deleted +device as well (assumed, not tested here), so user settings on those entities are lost once. The +test pins that the purge happens exactly once. +""" +from homeassistant.helpers import device_registry as dr +from pytest_homeassistant_custom_component.common import MockConfigEntry + +from custom_components.goecharger_api2 import async_migrate_entry +from custom_components.goecharger_api2.const import DOMAIN + + +def _entry(hass, version, minor, data=None, options=None): + entry = MockConfigEntry(domain=DOMAIN, title="t", version=version, minor_version=minor, + data=data or {"host": "192.0.2.10", "id": "123456"}, options=options or {}) + entry.add_to_hass(hass) + return entry + + +def _device(hass, entry): + return dr.async_get(hass).async_get_or_create( + config_entry_id=entry.entry_id, identifiers={(DOMAIN, "old-identifier")}) + + +async def test_version_1_merges_options_into_data(hass): + entry = _entry(hass, 1, 0, options={"scan_interval": 60}) + assert await async_migrate_entry(hass, entry) is True + assert entry.data["scan_interval"] == 60 and entry.data["host"] == "192.0.2.10" + assert dict(entry.options) == {} + assert (entry.version, entry.minor_version) == (2, 1) + + +async def test_version_2_0_deletes_the_devices_and_moves_to_2_1(hass): + entry = _entry(hass, 2, 0) + device = _device(hass, entry) + assert await async_migrate_entry(hass, entry) is True + assert dr.async_get(hass).async_get(device.id) is None + assert (entry.version, entry.minor_version) == (2, 1) + + +async def test_version_2_1_is_left_alone(hass): + entry = _entry(hass, 2, 1) + device = _device(hass, entry) + assert await async_migrate_entry(hass, entry) is True + assert dr.async_get(hass).async_get(device.id) is not None + assert (entry.version, entry.minor_version) == (2, 1) diff --git a/tests/integration/test_platform_actions.py b/tests/integration/test_platform_actions.py new file mode 100644 index 0000000..4dc0145 --- /dev/null +++ b/tests/integration/test_platform_actions.py @@ -0,0 +1,230 @@ +"""Platform entities, writing and switch/binary-sensor states. + +Writing: what an entity sends to the coordinator when a user changes it, and that a failing write never +raises into Home Assistant. Reading: how switches and binary sensors turn raw values into on/off. +""" +from unittest.mock import AsyncMock + +import pytest + +from custom_components.goecharger_api2.binary_sensor import GoeChargerApiV2BinarySensor +from custom_components.goecharger_api2.button import GoeChargerApiV2Button +from custom_components.goecharger_api2.const import ( + ExtBinarySensorEntityDescription as BinaryDesc, + ExtButtonEntityDescription as ButtonDesc, + ExtSelectEntityDescription as SelectDesc, + ExtSwitchEntityDescription as SwitchDesc, +) +from custom_components.goecharger_api2.select import GoeChargerSelect +from custom_components.goecharger_api2.switch import GoeChargerSwitch + +from .factories import make_coordinator + + +def _select(hass, data, **description): + coordinator = make_coordinator(hass, data=data) + coordinator.available_cards_idx = [] + coordinator.async_write_key = AsyncMock() + coordinator.async_write_multiple_keys = AsyncMock() + return GoeChargerSelect(coordinator, SelectDesc(options=["0", "1"], **description)), coordinator + + +# ----------------------------------------------------------------------------- select: writing + +async def test_select_writes_an_integer(hass): + select, coordinator = _select(hass, {"lmo": "3"}, key="lmo") + await select.async_select_option("4") + coordinator.async_write_key.assert_awaited_once_with("lmo", 4, select) + + +async def test_select_null_option_writes_none(hass): + select, coordinator = _select(hass, {"trx": 1}, key="trx") + await select.async_select_option("null") + coordinator.async_write_key.assert_awaited_once_with("trx", None, select) + + +async def test_select_with_idx_writes_the_whole_object(hass): + select, coordinator = _select(hass, {"x": {"control": "a", "other": 1}}, key="x", idx="control") + await select.async_select_option("b") + coordinator.async_write_key.assert_awaited_once_with("x", {"control": "b", "other": 1}, select) + + +async def test_select_with_idx_and_null_writes_none_into_the_object(hass): + select, coordinator = _select(hass, {"x": {"control": "a"}}, key="x", idx="control") + await select.async_select_option("null") + coordinator.async_write_key.assert_awaited_once_with("x", {"control": None}, select) + + +async def test_select_with_idx_and_missing_key_writes_nothing(hass): + select, coordinator = _select(hass, {}, key="x", idx="control") + assert await select.async_select_option("b") == "unavailable" + coordinator.async_write_key.assert_not_awaited() + + +async def test_select_schedule_writes_an_integer_and_removes_the_seconds(hass): + obj = {"control": 0, "ranges": [{"begin": {"hour": 1, "second": 0}, "end": {"hour": 2, "second": 0}}, + {"begin": {"hour": 3}, "end": {"hour": 4}}]} + select, coordinator = _select(hass, {"sch_week": obj}, key="sch_week", idx="control") + await select.async_select_option("2") + written = coordinator.async_write_key.await_args.args[1] + assert written["control"] == 2 + assert all("second" not in r["begin"] and "second" not in r["end"] for r in written["ranges"]) + + +@pytest.mark.parametrize("option, expected", [ + ("default", {"ct": "default", "mca": 6, "acp": True, "su": False, "psm": 0, "mcpd": 0}), + ("renaultzoe", {"ct": "renaultZoe", "mca": 10, "acp": True, "su": True, "psm": 0, "mcpd": 0}), + ("vwid3_2", {"ct": "vwID3_2", "mca": 6, "acp": False, "su": False, "psm": 0, "mcpd": 0}), + ("daciaspring", {"ct": "daciaSpring", "mca": 6, "acp": True, "su": False, "psm": 1, "mcpd": 0}), + ("ssangyong", {"ct": "ssangyong", "mca": 6, "acp": True, "su": False, "psm": 0, "mcpd": 0}), + ("fordexplorer", {"ct": "fordExplorer", "mca": 6, "acp": True, "su": True, "psm": 0, "mcpd": 120000}), + ("cuprabornalternative", {"ct": "cupraBornAlternative", "mca": 6, "acp": True, "su": True, "psm": 0, "mcpd": 120000}), + ("kiasoul", {"ct": "kiaSoul", "mca": 6, "acp": True, "su": True, "psm": 0, "mcpd": 0}), +]) +async def test_select_car_type_writes_the_whole_profile(hass, option, expected): + select, coordinator = _select(hass, {"ct": "default"}, key="ct") + await select.async_select_option(option) + coordinator.async_write_multiple_keys.assert_awaited_once() + attr = coordinator.async_write_multiple_keys.await_args.kwargs["attr"] + assert attr == expected + + +async def test_select_swallows_write_errors(hass): + select, coordinator = _select(hass, {"lmo": "3"}, key="lmo") + coordinator.async_write_key.side_effect = ValueError("down") + assert await select.async_select_option("4") == "unavailable" + + +# ----------------------------------------------------------------------------- switch + +def _switch(hass, data, **description): + coordinator = make_coordinator(hass, data=data) + coordinator.async_write_key = AsyncMock() + return GoeChargerSwitch(coordinator, SwitchDesc(**description)), coordinator + + +@pytest.mark.parametrize("description, turn_on, expected", [ + (dict(key="bac"), True, True), + (dict(key="bac"), False, False), + (dict(key="bac", is_zero_or_one=True), True, 1), + (dict(key="bac", is_zero_or_one=True), False, 0), +]) +async def test_switch_writes_boolean_or_number(hass, description, turn_on, expected): + switch, coordinator = _switch(hass, {"bac": 1}, **description) + await (switch.async_turn_on() if turn_on else switch.async_turn_off()) + coordinator.async_write_key.assert_awaited_once_with("bac", expected, switch) + assert type(coordinator.async_write_key.await_args.args[1]) is type(expected) + + +@pytest.mark.parametrize("turn_on, flag, expected", [ + (True, None, True), (False, None, False), (True, True, 1), (False, True, 0)]) +async def test_switch_with_idx_writes_the_whole_object(hass, turn_on, flag, expected): + switch, coordinator = _switch(hass, {"x": {"a": 5, "b": None}}, key="x", idx="b", is_zero_or_one=flag) + await (switch.async_turn_on() if turn_on else switch.async_turn_off()) + coordinator.async_write_key.assert_awaited_once_with("x", {"a": 5, "b": expected}, switch) + + +@pytest.mark.parametrize("turn_on", [True, False]) +async def test_switch_with_idx_and_missing_key_writes_nothing(hass, turn_on): + switch, coordinator = _switch(hass, {}, key="x", idx="b") + assert await (switch.async_turn_on() if turn_on else switch.async_turn_off()) == "unavailable" + coordinator.async_write_key.assert_not_awaited() + + +@pytest.mark.parametrize("turn_on", [True, False]) +async def test_switch_swallows_write_errors(hass, turn_on): + switch, coordinator = _switch(hass, {"bac": 1}, key="bac") + coordinator.async_write_key.side_effect = ValueError("down") + assert await (switch.async_turn_on() if turn_on else switch.async_turn_off()) == "unavailable" + + +STATE_CASES = [ + ("bool true", {"x": True}, dict(key="x"), True), + ("bool false", {"x": False}, dict(key="x"), False), + ("positive integer", {"x": 3}, dict(key="x"), True), + ("zero", {"x": 0}, dict(key="x"), False), + ("text on", {"x": "ON"}, dict(key="x"), True), + ("text other", {"x": "off"}, dict(key="x"), False), + ("null", {"x": None}, dict(key="x"), False), + ("empty text", {"x": ""}, dict(key="x"), False), + ("missing key", {"y": 1}, dict(key="x"), False), + ("idx", {"x": [0, 1]}, dict(key="x", idx=1), True), + ("wrong idx", {"x": {}}, dict(key="x", idx=1), False), + ("list value", {"x": [1]}, dict(key="x"), False), +] + + +@pytest.mark.parametrize("data, description, expected", [c[1:] for c in STATE_CASES], ids=[c[0] for c in STATE_CASES]) +async def test_switch_is_on(hass, data, description, expected): + switch, _ = _switch(hass, data, **description) + assert switch.is_on is expected + + +async def test_switch_without_data_and_with_idx_on_null_have_no_state(hass): + switch, coordinator = _switch(hass, {"x": None}, key="x", idx=0) + assert switch.is_on is None and switch.state is None + + +async def test_switch_state_and_icon(hass): + switch, _ = _switch(hass, {"x": False}, key="x", icon="mdi:on", icon_off="mdi:off") + assert switch.state == "off" and switch.icon == "mdi:off" + switch.coordinator.data["x"] = True + assert switch.state == "on" and switch.icon == "mdi:on" + + +# ----------------------------------------------------------------------------- binary sensor + +def _binary(hass, data, **description): + return GoeChargerApiV2BinarySensor(make_coordinator(hass, data=data), BinaryDesc(**description)) + + +BINARY_CASES = [ + ("bool", {"x": True}, dict(key="x"), True), + ("text on", {"x": "on"}, dict(key="x"), True), + ("text other", {"x": "no"}, dict(key="x"), False), + ("integer is not on", {"x": 5}, dict(key="x"), False), + ("null", {"x": None}, dict(key="x"), False), + ("empty", {"x": ""}, dict(key="x"), False), + ("missing key", {"y": 1}, dict(key="x"), False), + ("idx", {"x": [False, True]}, dict(key="x", idx=1), True), + ("car connected above idle", {"car": 2}, dict(key="car", idx=0), True), + ("car idle is not connected", {"car": 1}, dict(key="car", idx=0), False), + ("tuple idx", {"x": {"a": {"b": True}}}, dict(key="x", tuple_idx=["a", "b"]), True), + ("idx out of range", {"x": [True]}, dict(key="x", idx=4), False), + ("tuple idx out of range", {"x": [[True]]}, dict(key="x", tuple_idx=[0, 3]), False), + ("wrong key in tuple idx", {"x": {"a": {}}}, dict(key="x", tuple_idx=["a", "b"]), False), + ("single tuple idx is ignored", {"x": 1}, dict(key="x", tuple_idx=["a"]), False), +] + + +@pytest.mark.parametrize("data, description, expected", [c[1:] for c in BINARY_CASES], ids=[c[0] for c in BINARY_CASES]) +async def test_binary_sensor_is_on(hass, data, description, expected): + assert _binary(hass, data, **description).is_on is expected + + +async def test_binary_sensor_with_wrong_type_has_no_state(hass): + assert _binary(hass, {"x": None}, key="x", idx=0).is_on is None + + +async def test_binary_sensor_icon_follows_the_state(hass): + sensor = _binary(hass, {"x": False}, key="x", icon="mdi:on", icon_off="mdi:off") + assert sensor.icon == "mdi:off" + sensor.coordinator.data["x"] = True + assert sensor.icon == "mdi:on" + + +# ----------------------------------------------------------------------------- button + +async def test_button_press_sends_its_payload(hass): + coordinator = make_coordinator(hass, data={}) + coordinator.async_write_key = AsyncMock() + button = GoeChargerApiV2Button(coordinator, ButtonDesc(key="zfocore", payload="TRIGGER")) + await button.async_press() + coordinator.async_write_key.assert_awaited_once_with("zfocore", "TRIGGER", button) + + +async def test_button_press_swallows_write_errors(hass): + coordinator = make_coordinator(hass, data={}) + coordinator.async_write_key = AsyncMock(side_effect=ValueError("down")) + button = GoeChargerApiV2Button(coordinator, ButtonDesc(key="zfocore", payload="TRIGGER")) + assert await button.async_press() == "unavailable" diff --git a/tests/integration/test_platform_values.py b/tests/integration/test_platform_values.py new file mode 100644 index 0000000..f21ce67 --- /dev/null +++ b/tests/integration/test_platform_values.py @@ -0,0 +1,264 @@ +"""Platform entities, reading: how raw charger values become sensor, number, select, switch and binary sensor states. + +Each case builds an entity description by hand (so exactly one feature is exercised), puts a raw value +into the coordinator data and checks the state. Failure cases (missing key, wrong type, wrong index) +must give an empty state and no exception. +""" +from datetime import datetime, time + +import pytest +from homeassistant.components.sensor import SensorDeviceClass + +from custom_components.goecharger_api2.binary_sensor import GoeChargerApiV2BinarySensor +from custom_components.goecharger_api2.const import ( + ExtBinarySensorEntityDescription as BinaryDesc, + ExtNumberEntityDescription as NumberDesc, + ExtSelectEntityDescription as SelectDesc, + ExtSensorEntityDescription as SensorDesc, + ExtSwitchEntityDescription as SwitchDesc, +) +from custom_components.goecharger_api2.number import GoeChargerNumber +from custom_components.goecharger_api2.select import GoeChargerSelect +from custom_components.goecharger_api2.sensor import GoeChargerSensor +from custom_components.goecharger_api2.switch import GoeChargerSwitch + +from .factories import make_coordinator + + +def _sensor(hass, data, cards_as_object=True, **description): + coordinator = make_coordinator(hass, data=data) + coordinator._cards_as_object_present = cards_as_object + return GoeChargerSensor(coordinator, SensorDesc(**description)), coordinator + + +# ----------------------------------------------------------------------------- sensor + +SENSOR_CASES = [ + ("plain", {"amp": 16}, dict(key="amp"), 16), + ("empty string", {"amp": ""}, dict(key="amp"), None), + ("null", {"amp": None}, dict(key="amp"), None), + ("missing key", {}, dict(key="amp"), None), + ("idx", {"nrg": [230, 231, 232]}, dict(key="nrg", idx=1), 231), + ("idx out of range", {"nrg": [230]}, dict(key="nrg", idx=5), None), + ("idx on null", {"nrg": None}, dict(key="nrg", idx=0), None), + ("tuple idx", {"x": {"a": {"b": 3}}}, dict(key="x", tuple_idx=["a", "b"]), 3), + ("tuple idx missing", {"x": {"a": {}}}, dict(key="x", tuple_idx=["a", "b"]), None), + ("cards as object", {"cards": [{"name": "A", "energy": 5}]}, dict(key="cards", tuple_idx=[0, "energy"]), 5), + ("factor", {"x": 1000}, dict(key="x", factor=10), 100), + ("factor zero ignored", {"x": 1000}, dict(key="x", factor=0), 1000), + ("differential base", {"x": 100, "rbt": 150}, dict(key="x", differential_base_key="rbt"), 50), + ("differential base zero", {"x": 100, "rbt": 0}, dict(key="x", differential_base_key="rbt"), 100), + ("differential and factor", {"x": 100, "rbt": 1150}, dict(key="x", differential_base_key="rbt", factor=10), 105), + ("differential base missing", {"x": 100}, dict(key="x", differential_base_key="rbt"), None), + ("datetime", {"x": datetime(2026, 10, 29, 8, 30, 45)}, dict(key="x"), "2026-10-29 08:30"), + ("time", {"x": time(7, 5, 9)}, dict(key="x"), "07:05"), + ("bool true", {"x": True}, dict(key="x"), "on"), + ("bool false", {"x": False}, dict(key="x"), "off"), + ("string unchanged", {"x": "text"}, dict(key="x"), "text"), +] + + +@pytest.mark.parametrize("data, description, expected", [c[1:] for c in SENSOR_CASES], ids=[c[0] for c in SENSOR_CASES]) +async def test_sensor_native_value(hass, data, description, expected): + sensor, _ = _sensor(hass, data, **description) + assert sensor.native_value == expected + + +async def test_sensor_without_data_has_no_value(hass): + sensor, coordinator = _sensor(hass, {}, key="amp") + coordinator.data = None + assert sensor.native_value is None + + +@pytest.mark.parametrize("sub_key, key, expected", [ + ("energy", "c1e", 77), ("name", "c1n", "Anna"), ("cardId", "c1i", "A1B2")]) +async def test_sensor_card_values_of_firmware_60_use_single_keys(hass, sub_key, key, expected): + data = {"c1e": 77, "c1n": "Anna", "c1i": "A1B2"} + sensor, _ = _sensor(hass, data, cards_as_object=False, key="cards", tuple_idx=[1, sub_key]) + assert sensor.native_value == expected + + +async def test_sensor_unknown_card_sub_key_gives_no_value(hass): + sensor, _ = _sensor(hass, {"c1e": 77}, cards_as_object=False, key="cards", tuple_idx=[1, "colour"]) + assert sensor.native_value is None + + +async def test_sensor_card_key_missing_gives_no_value(hass): + sensor, _ = _sensor(hass, {}, cards_as_object=False, key="cards", tuple_idx=[1, "energy"]) + assert sensor.native_value is None + + +async def test_sensor_lookup_translates_the_value(hass): + sensor, coordinator = _sensor(hass, {"car": 2}, key="car", lookup=True) + assert sensor.native_value == coordinator.lang_map["car"][2] + + +async def test_sensor_lookup_without_translation_keeps_the_raw_value(hass, caplog): + sensor, _ = _sensor(hass, {"zzz": 5}, key="zzz", lookup=True) + assert sensor.native_value == 5 + assert "not found in translations" in caplog.text + + +async def test_sensor_lookup_with_unknown_code_gives_no_value(hass): + sensor, _ = _sensor(hass, {"car": 99}, key="car", lookup=True) + assert sensor.native_value is None + + +async def test_sensor_cll_object_becomes_readable_text(hass): + data = {"cll": {"cableCurrentLimit": 32, "unknownLimit": 1}} + sensor, coordinator = _sensor(hass, data, key="cll") + value = sensor.native_value + first = coordinator.lang_map.get("cll_cablecurrentlimit", "cable_current_limit") + assert value.startswith(f"{first}: 32") and value.endswith("unknown_limit: 1") + + +async def test_sensor_camel_case_is_converted_to_snake_case(): + assert GoeChargerSensor._camel_to_snake("cableCurrentLimit") == "cable_current_limit" + assert GoeChargerSensor._camel_to_snake("Max-Value 2") == "max_value_2" + + +async def test_sensor_date_state_is_unknown_without_value(hass): + sensor, _ = _sensor(hass, {"x": None}, key="x", device_class=SensorDeviceClass.DATE) + assert sensor.state == "unknown" + + +async def test_sensor_date_state_passes_the_value_through(hass): + sensor, _ = _sensor(hass, {"x": "2026-10-29"}, key="x", device_class=SensorDeviceClass.DATE) + assert sensor.state == "2026-10-29" + + +# ----------------------------------------------------------------------------- number + +def _number(hass, data, **description): + coordinator = make_coordinator(hass, data=data) + return GoeChargerNumber(coordinator, NumberDesc(**description)), coordinator + + +NUMBER_CASES = [ + ("plain", {"amp": 16}, dict(key="amp"), 16), + ("null is unknown", {"amp": None}, dict(key="amp"), "unknown"), + ("empty is unknown", {"amp": ""}, dict(key="amp"), "unknown"), + ("missing key is unknown", {}, dict(key="amp"), "unknown"), + ("idx", {"lop": [1, 2, 3]}, dict(key="lop", idx=2), 3), + ("idx on null gives none", {"lop": None}, dict(key="lop", idx=0), None), + ("float", {"x": 5}, dict(key="x", handle_as_float=True), 5.0), + ("float with factor", {"x": 150}, dict(key="x", handle_as_float=True, factor=100), 1.5), + ("integer with factor", {"x": 150}, dict(key="x", factor=10), 15), +] + + +@pytest.mark.parametrize("data, description, expected", [c[1:] for c in NUMBER_CASES], ids=[c[0] for c in NUMBER_CASES]) +async def test_number_native_value(hass, data, description, expected): + number, _ = _number(hass, data, **description) + assert number.native_value == expected + + +WRITE_CASES = [ + ("integer", {"amp": 6}, dict(key="amp"), 10, ("amp", 10)), + ("float value is cut to integer", {"amp": 6}, dict(key="amp"), 10.9, ("amp", 10)), + ("zero as null", {"x": 5}, dict(key="x", write_zero_as_null=True), 0, ("x", None)), + ("zero stays zero", {"x": 5}, dict(key="x"), 0, ("x", 0)), + ("factor", {"x": 5}, dict(key="x", factor=100), 1.5, ("x", 150)), + ("float", {"x": 5}, dict(key="x", handle_as_float=True), 2, ("x", 2.0)), +] + + +@pytest.mark.parametrize("data, description, value, expected", [c[1:] for c in WRITE_CASES], ids=[c[0] for c in WRITE_CASES]) +async def test_number_set_value_writes_the_converted_value(hass, data, description, value, expected): + from unittest.mock import AsyncMock + number, coordinator = _number(hass, data, **description) + coordinator.async_write_key = AsyncMock() + await number.async_set_native_value(value) + coordinator.async_write_key.assert_awaited_once_with(expected[0], expected[1], number) + + +IDX_WRITE_CASES = [ + ("integer", dict(idx=1), 9, [1, 9, 3]), + ("zero as null", dict(idx=1, write_zero_as_null=True), 0, [1, None, 3]), + ("factor", dict(idx=1, factor=10), 2, [1, 20, 3]), + ("float", dict(idx=1, handle_as_float=True), 4, [1, 4.0, 3]), +] + + +@pytest.mark.parametrize("description, value, expected", [c[1:] for c in IDX_WRITE_CASES], ids=[c[0] for c in IDX_WRITE_CASES]) +async def test_number_set_value_writes_the_whole_list(hass, description, value, expected): + from unittest.mock import AsyncMock + number, coordinator = _number(hass, {"lop": [1, 2, 3]}, key="lop", **description) + coordinator.async_write_key = AsyncMock() + await number.async_set_native_value(value) + coordinator.async_write_key.assert_awaited_once_with("lop", expected, number) + + +async def test_number_set_value_swallows_write_errors(hass): + from unittest.mock import AsyncMock + number, coordinator = _number(hass, {"amp": 6}, key="amp") + coordinator.async_write_key = AsyncMock(side_effect=ValueError("down")) + assert await number.async_set_native_value(10) == "unavailable" + + +# ----------------------------------------------------------------------------- number: limits of amp + +def _amp(value): + from types import SimpleNamespace + return SimpleNamespace(_attr_native_max_value=32, _attr_native_min_value=6, native_value=value, + entity_id="number.goe_123456_amp") + + +LIMIT_CASES = [ + ("max below amp lowers amp", "ama", 10, 20, 10), + ("max above amp changes nothing", "ama", 32, 20, None), + ("min above amp raises amp", "mca", 25, 20, 25), + ("min below amp changes nothing", "mca", 6, 20, None), + ("max equal to stored max changes nothing", "ama", 32, 40, None), +] + + +@pytest.mark.parametrize("key, value, amp_value, expected", [c[1:] for c in LIMIT_CASES], ids=[c[0] for c in LIMIT_CASES]) +async def test_number_amp_limits_adjust_amp_entity(hass, key, value, amp_value, expected): + from pytest_homeassistant_custom_component.common import async_mock_service + calls = async_mock_service(hass, "number", "set_value") + number, coordinator = _number(hass, {key: value}, key=key) + number.hass = hass + coordinator.entities["amp"] = _amp(amp_value) + number.check_amp_min_max(value) + await hass.async_block_till_done() + assert [c.data["value"] for c in calls] == ([] if expected is None else [expected]) + + +async def test_number_without_amp_entity_ignores_limits(hass): + number, _ = _number(hass, {"ama": 10}, key="ama") + number.check_amp_min_max(10) # no amp entity registered: must not raise + + +# ----------------------------------------------------------------------------- select + +def _select(hass, data, **description): + coordinator = make_coordinator(hass, data=data) + coordinator.available_cards_idx = ["1", "2"] + return GoeChargerSelect(coordinator, SelectDesc(options=["0", "1", "2"], **description)), coordinator + + +SELECT_CASES = [ + ("string", {"lmo": "3"}, dict(key="lmo"), "3"), + ("integer becomes text", {"lmo": 3}, dict(key="lmo"), "3"), + ("idx", {"x": ["a", "b"]}, dict(key="x", idx=1), "b"), + ("idx with missing key", {}, dict(key="x", idx=0), "unavailable"), + ("missing key is unknown", {}, dict(key="lmo"), "unknown"), + ("empty is unknown", {"lmo": ""}, dict(key="lmo"), "unknown"), + ("transaction without card is null", {"trx": None}, dict(key="trx"), "null"), + ("car type without value is default", {"ct": None}, dict(key="ct"), "default"), + ("car type is lower case", {"ct": "VWID5"}, dict(key="ct"), "vwid5"), + ("wrong index is unknown", {"x": {}}, dict(key="x", idx=1), "unknown"), + ("index on null gives none", {"x": None}, dict(key="x", idx=0), None), +] + + +@pytest.mark.parametrize("data, description, expected", [c[1:] for c in SELECT_CASES], ids=[c[0] for c in SELECT_CASES]) +async def test_select_current_option(hass, data, description, expected): + select, _ = _select(hass, data, **description) + assert select.current_option == expected + + +async def test_select_transaction_offers_the_active_cards(hass): + select, _ = _select(hass, {}, key="trx") + assert select.options == ["null", "0", "1", "2"] diff --git a/tests/integration/test_service.py b/tests/integration/test_service.py new file mode 100644 index 0000000..3dd73c1 --- /dev/null +++ b/tests/integration/test_service.py @@ -0,0 +1,50 @@ +"""set_pv_data: non-finite numbers must never reach the charger.""" +import math +from types import SimpleNamespace +from unittest.mock import AsyncMock + +import pytest + +from custom_components.goecharger_api2 import service as service_module +from custom_components.goecharger_api2.service import GoeChargerApiV2Service +from custom_components.goecharger_api2.pygoecharger_ha.keys import Tag + + +@pytest.fixture +def svc(hass): + service_module._COORDINATOR_PER_CONFIG_ENTRY_ID_MAP.clear() + coordinator = SimpleNamespace( + intg_type="charger", mode="lan", name="test charger", _serial="123456", data={}, + async_write_key=AsyncMock(return_value={Tag.IDS.key: True})) + service = GoeChargerApiV2Service(hass, SimpleNamespace(entry_id="entry"), coordinator) + yield service, coordinator + service_module._COORDINATOR_PER_CONFIG_ENTRY_ID_MAP.clear() + + +def _call(**data): + return SimpleNamespace(data=data, return_response=True) + + +async def test_valid_values_are_written(svc): + service, coordinator = svc + result = await service.set_pv_data(_call(pgrid=-500, ppv=1500, pakku=200)) + assert result["success"] == "true" + coordinator.async_write_key.assert_awaited_once_with( + Tag.IDS.key, {"pGrid": -500.0, "pPv": 1500.0, "pAkku": 200.0}) + + +@pytest.mark.known_bug("service.py checks pgrid with isinstance(Number) only; NaN and inf pass") +@pytest.mark.parametrize("bad", [math.nan, math.inf, -math.inf]) +async def test_non_finite_grid_value_is_rejected(svc, bad): + service, coordinator = svc + result = await service.set_pv_data(_call(pgrid=bad, ppv=1500)) + assert "error" in result + coordinator.async_write_key.assert_not_awaited() + + +@pytest.mark.parametrize("bad", [math.nan, math.inf, -math.inf]) +async def test_non_finite_pv_and_battery_value_become_zero(svc, bad): + service, coordinator = svc + await service.set_pv_data(_call(pgrid=100, ppv=bad, pakku=bad)) + coordinator.async_write_key.assert_awaited_once_with( + Tag.IDS.key, {"pGrid": 100.0, "pPv": 0.0, "pAkku": 0.0}) diff --git a/tests/integration/test_services_and_setup.py b/tests/integration/test_services_and_setup.py new file mode 100644 index 0000000..5381603 --- /dev/null +++ b/tests/integration/test_services_and_setup.py @@ -0,0 +1,359 @@ +"""Services (PV data, stop charging, charger lookup) and the start and end of a config entry. + +Setup and unload run the real functions of the integration; only the platform loading, the data +polling and the WebSocket start are replaced. +""" +import inspect +from unittest.mock import AsyncMock, MagicMock, patch + +import pytest +from homeassistant.config_entries import ConfigEntryDisabler +from homeassistant.const import EVENT_HOMEASSISTANT_STARTED +from homeassistant.core import CoreState +from homeassistant.exceptions import ServiceValidationError + +from custom_components.goecharger_api2 import ( + ACTIVE_CHARGER_COUNTER, + GoeChargerDataUpdateCoordinator, + async_migrate_entry, + async_setup, + async_setup_entry, + async_unload_entry, + entry_update_listener, +) +from custom_components.goecharger_api2 import service as service_module +from custom_components.goecharger_api2.const import DOMAIN, SERVICE_SET_PV_DATA, SERVICE_STOP_CHARGING +from custom_components.goecharger_api2.service import GoeChargerApiV2Service, add_config_entry_to_service +from custom_components.goecharger_api2.pygoecharger_ha import NonNullDict +from custom_components.goecharger_api2.pygoecharger_ha.keys import Tag + +from .factories import make_coordinator, make_entry + +PKG = "custom_components.goecharger_api2" + + +@pytest.fixture(autouse=True) +def clean_registry(): + service_module._COORDINATOR_PER_CONFIG_ENTRY_ID_MAP = {} + yield + service_module._COORDINATOR_PER_CONFIG_ENTRY_ID_MAP = {} + + +def _call(return_response=True, **data): + return MagicMock(data=data, return_response=return_response) + + +def _service(hass, mode="lan", car=2, **kwargs): + coordinator = make_coordinator(hass, data={Tag.CAR_CONNECTED.key: car}, mode=mode, **kwargs) + coordinator.async_write_key = AsyncMock(return_value={Tag.IDS.key: True}) + return GoeChargerApiV2Service(hass, coordinator._config_entry, coordinator), coordinator + + +# ----------------------------------------------------------------------------- set_pv_data + +async def test_cloud_charger_without_a_car_gets_no_pv_data(hass): + service, coordinator = _service(hass, mode="wan", car=1) + result = await service.set_pv_data(_call(pgrid=-500)) + assert "No CAR CONNECTED" in result["error"] + coordinator.async_write_key.assert_not_awaited() + + +async def test_cloud_charger_without_a_car_returns_nothing_when_no_answer_is_wanted(hass): + service, coordinator = _service(hass, mode="wan", car=0) + assert await service.set_pv_data(_call(return_response=False, pgrid=-500)) is None + coordinator.async_write_key.assert_not_awaited() + + +async def test_cloud_charger_with_a_car_gets_pv_data(hass): + service, coordinator = _service(hass, mode="wan", car=2) + result = await service.set_pv_data(_call(pgrid=-500)) + assert result["success"] == "true" + coordinator.async_write_key.assert_awaited_once() + + +async def test_cloud_charger_with_unreadable_car_state_still_gets_pv_data(hass): + service, coordinator = _service(hass, mode="wan", car="n/a") + await service.set_pv_data(_call(pgrid=0)) + coordinator.async_write_key.assert_awaited_once() + + +async def test_cloud_charger_without_car_information_gets_no_pv_data(hass): + coordinator = make_coordinator(hass, data={}, mode="wan") + coordinator.async_write_key = AsyncMock() + service = GoeChargerApiV2Service(hass, coordinator._config_entry, coordinator) + result = await service.set_pv_data(_call(pgrid=0)) + assert "error" in result and coordinator.async_write_key.await_count == 0 + + +async def test_missing_grid_power_is_an_error(hass): + service, coordinator = _service(hass) + result = await service.set_pv_data(_call(ppv=100)) + assert "No Grid Power" in result["error"] + coordinator.async_write_key.assert_not_awaited() + + +async def test_missing_grid_power_without_wanted_answer_returns_nothing(hass): + service, _ = _service(hass) + assert await service.set_pv_data(_call(return_response=False, ppv=100)) is None + + +async def test_successful_pv_data_returns_nothing_when_no_answer_is_wanted(hass): + service, coordinator = _service(hass) + assert await service.set_pv_data(_call(return_response=False, pgrid=-1, ppv=2, pakku=3)) is None + coordinator.async_write_key.assert_awaited_once_with(Tag.IDS.key, {"pGrid": -1.0, "pPv": 2.0, "pAkku": 3.0}) + + +@pytest.mark.parametrize("error", [ValueError("down"), TypeError("odd")]) +@pytest.mark.parametrize("wanted", [True, False]) +async def test_failed_pv_write_is_reported_not_raised(hass, error, wanted): + service, coordinator = _service(hass) + coordinator.async_write_key.side_effect = error + result = await service.set_pv_data(_call(return_response=wanted, pgrid=0)) + assert (result is None) if not wanted else ("error" in result and str(error) in result["error"]) + + +# ----------------------------------------------------------------------------- stop_charging + +async def test_stop_charging_reports_when_the_lock_was_not_confirmed(hass): + service, coordinator = _service(hass) + coordinator.async_write_key.return_value = {} + result = await service.stop_charging(_call()) + assert "could not be send" in result["error"] and service._stop_in_progress is False + + +async def test_stop_charging_without_wanted_answer_returns_nothing(hass): + service, coordinator = _service(hass) + coordinator.async_write_key.return_value = {} + assert await service.stop_charging(_call(return_response=False)) is None + + +async def test_stop_charging_locks_waits_and_unlocks(hass): + service, coordinator = _service(hass) + coordinator.async_write_key.side_effect = [{Tag.FRC.key: 1}, {Tag.FRC.key: 0}] + with patch(f"{PKG}.service.asyncio.sleep", AsyncMock()) as sleep: + result = await service.stop_charging(_call()) + sleep.assert_awaited_once_with(300) + assert result["success"] == "true" + assert [c.args for c in coordinator.async_write_key.await_args_list] == [(Tag.FRC.key, 1), (Tag.FRC.key, 0)] + + +async def test_stop_charging_without_wanted_answer_still_unlocks(hass): + service, coordinator = _service(hass) + coordinator.async_write_key.side_effect = [{Tag.FRC.key: 1}, {Tag.FRC.key: 0}] + with patch(f"{PKG}.service.asyncio.sleep", AsyncMock()): + assert await service.stop_charging(_call(return_response=False)) is None + assert coordinator.async_write_key.await_count == 2 + + +async def test_second_stop_request_is_refused_while_one_runs(hass): + service, _ = _service(hass) + service._stop_in_progress = True + assert "already in progress" in (await service.stop_charging(_call()))["error"] + assert await service.stop_charging(_call(return_response=False)) is None + + +@pytest.mark.parametrize("wanted", [True, False]) +async def test_stop_charging_reports_write_errors(hass, wanted): + service, coordinator = _service(hass) + coordinator.async_write_key.side_effect = ValueError("down") + result = await service.stop_charging(_call(return_response=wanted)) + assert (result is None) if not wanted else ("down" in result["error"]) + + +# ----------------------------------------------------------------------------- choosing the charger + +async def test_without_configid_the_first_registered_charger_is_used(hass): + service, coordinator = _service(hass) + assert service.get_coordinator(_call())[1] is coordinator + + +# since 4702213 a call without configid is only answered when exactly one charger is registered +REFUSES_GUESSING = "zero (or multiple)" in inspect.getsource(GoeChargerApiV2Service.get_coordinator) + + +async def test_with_an_empty_registry_no_charger_can_be_chosen(hass): + service, _ = _service(hass) + service_module._COORDINATOR_PER_CONFIG_ENTRY_ID_MAP.clear() + expected = ServiceValidationError if REFUSES_GUESSING else (StopIteration, RuntimeError) + with pytest.raises(expected): + service.get_coordinator(_call()) + + +@pytest.mark.skipif(not REFUSES_GUESSING, reason="this version still picks the first registered charger") +async def test_without_configid_and_with_two_chargers_no_charger_is_guessed(hass): + service, _ = _service(hass) + other = make_coordinator(hass, serial="222222") + add_config_entry_to_service(other._config_entry, other) + with pytest.raises(ServiceValidationError, match="zero \\(or multiple\\)"): + service.get_coordinator(_call()) + + +async def test_configid_of_a_registered_enabled_charger_is_accepted(hass): + service, coordinator = _service(hass) + config_id = coordinator._config_entry.entry_id + assert service.get_coordinator(_call(configid=config_id)) == (config_id, coordinator) + + +async def test_unknown_configid_is_refused(hass): + service, _ = _service(hass) + with pytest.raises(ServiceValidationError, match="disabled or invalid"): + service.get_coordinator(_call(configid="does-not-exist")) + + +async def test_configid_of_a_disabled_entry_is_refused(hass): + service, coordinator = _service(hass) + entry = coordinator._config_entry + await hass.config_entries.async_set_disabled_by(entry.entry_id, ConfigEntryDisabler.USER) + with pytest.raises(ServiceValidationError, match="disabled or invalid"): + service.get_coordinator(_call(configid=entry.entry_id)) + + +async def test_configid_of_an_entry_missing_in_the_registry_is_refused(hass): + service, _ = _service(hass) + other = make_entry(hass, serial="999999") + with pytest.raises(ServiceValidationError, match="not in the internal"): + service.get_coordinator(_call(configid=other.entry_id)) + + +async def test_registry_helpers_ignore_unknown_entries(hass): + entry = make_entry(hass, serial="777777") + service_module.remove_config_entry_from_service(entry) + assert entry.entry_id not in service_module._COORDINATOR_PER_CONFIG_ENTRY_ID_MAP + + +# ----------------------------------------------------------------------------- start of an entry + +@pytest.fixture +def setup_patches(): + with patch.object(GoeChargerDataUpdateCoordinator, "_async_update_data", AsyncMock(return_value={"car": 1})), \ + patch.object(GoeChargerDataUpdateCoordinator, "read_versions", AsyncMock(return_value=True)), \ + patch.object(GoeChargerDataUpdateCoordinator, "check_for_16a_limit", AsyncMock()) as limiter, \ + patch.object(GoeChargerDataUpdateCoordinator, "start_websocket_and_wait_for_first_data", AsyncMock(return_value=True)) as ws_start, \ + patch.object(GoeChargerDataUpdateCoordinator, "start_watchdog", AsyncMock()) as watchdog: + yield SimpleNamespace_(limiter=limiter, ws_start=ws_start, watchdog=watchdog) + + +class SimpleNamespace_: + def __init__(self, **kwargs): + self.__dict__.update(kwargs) + + +async def _setup(hass, entry, limit=False): + with patch.object(hass.config_entries, "async_forward_entry_setups", AsyncMock()) as forward: + result = await async_setup_entry(hass, entry) + return result, forward + + +async def test_polling_charger_starts_registers_services_and_counts(hass, setup_patches): + entry = make_entry(hass) + result, forward = await _setup(hass, entry) + assert result is True + assert isinstance(hass.data[DOMAIN][entry.entry_id], GoeChargerDataUpdateCoordinator) + assert hass.data[DOMAIN][ACTIVE_CHARGER_COUNTER] == 1 + assert hass.services.has_service(DOMAIN, SERVICE_SET_PV_DATA) and hass.services.has_service(DOMAIN, SERVICE_STOP_CHARGING) + forward.assert_awaited_once() + setup_patches.ws_start.assert_not_awaited() + setup_patches.watchdog.assert_not_awaited() + + +async def test_second_charger_shares_the_services_and_joins_the_registry(hass, setup_patches): + first, second = make_entry(hass, serial="111111"), make_entry(hass, serial="222222") + await _setup(hass, first) + await _setup(hass, second) + assert hass.data[DOMAIN][ACTIVE_CHARGER_COUNTER] == 2 + assert set(service_module._COORDINATOR_PER_CONFIG_ENTRY_ID_MAP) == {first.entry_id, second.entry_id} + + +async def test_controller_does_not_register_charger_services(hass, setup_patches): + entry = make_entry(hass, intg_type="controller") + await _setup(hass, entry) + assert not hass.services.has_service(DOMAIN, SERVICE_SET_PV_DATA) + assert ACTIVE_CHARGER_COUNTER not in hass.data[DOMAIN] + + +async def test_websocket_charger_starts_the_connection_and_the_watchdog(hass, setup_patches): + entry = make_entry(hass, password="secret") + await _setup(hass, entry) + setup_patches.ws_start.assert_awaited_once_with(is_integration_init=True) + setup_patches.watchdog.assert_awaited_once() + + +async def test_watchdog_waits_for_home_assistant_to_finish_starting(hass, setup_patches): + entry = make_entry(hass, password="secret") + hass.set_state(CoreState.starting) + await _setup(hass, entry) + setup_patches.watchdog.assert_not_awaited() + hass.bus.async_fire(EVENT_HOMEASSISTANT_STARTED) + await hass.async_block_till_done() + setup_patches.watchdog.assert_awaited_once() + + +async def test_limited_charger_starts_the_16_a_task(hass, setup_patches): + async def limited(self): + self.limit_to16a = True + return True + + entry = make_entry(hass) + with patch.object(GoeChargerDataUpdateCoordinator, "read_versions", limited): + await _setup(hass, entry) + await hass.async_block_till_done() + setup_patches.limiter.assert_awaited_once() + + +async def test_unknown_language_falls_back_to_english_texts(hass, setup_patches): + from homeassistant.exceptions import ConfigEntryNotReady + hass.config.language = "xx" + entry = make_entry(hass, password="secret") + setup_patches.ws_start.side_effect = RuntimeError("connection broke") + with pytest.raises(ConfigEntryNotReady, match="The Websocket could not be started"): + await _setup(hass, entry) + + +@pytest.mark.known_bug("the specific message 'could not fetch essential information' is swallowed by the broad except and replaced by the generic websocket message (__init__.py:131)") +async def test_missing_device_data_is_reported_with_its_own_message(hass, setup_patches): + from homeassistant.exceptions import ConfigEntryNotReady + entry = make_entry(hass, password="secret") + setup_patches.ws_start.return_value = False + with pytest.raises(ConfigEntryNotReady, match="could not fetch essential"): + await _setup(hass, entry) + + +# ----------------------------------------------------------------------------- end of an entry + +async def test_unloading_the_last_charger_removes_the_services(hass, setup_patches): + entry = make_entry(hass) + await _setup(hass, entry) + with patch.object(hass.config_entries, "async_unload_platforms", AsyncMock(return_value=True)): + assert await async_unload_entry(hass, entry) is True + assert entry.entry_id not in hass.data[DOMAIN] + assert not hass.services.has_service(DOMAIN, SERVICE_SET_PV_DATA) + assert entry.entry_id not in service_module._COORDINATOR_PER_CONFIG_ENTRY_ID_MAP + + +async def test_unloading_one_of_two_chargers_keeps_the_services(hass, setup_patches): + first, second = make_entry(hass, serial="111111"), make_entry(hass, serial="222222") + await _setup(hass, first) + await _setup(hass, second) + with patch.object(hass.config_entries, "async_unload_platforms", AsyncMock(return_value=True)): + await async_unload_entry(hass, first) + assert hass.services.has_service(DOMAIN, SERVICE_SET_PV_DATA) + assert hass.data[DOMAIN][ACTIVE_CHARGER_COUNTER] == 1 + + +async def test_failed_platform_unload_keeps_everything(hass, setup_patches): + entry = make_entry(hass) + await _setup(hass, entry) + with patch.object(hass.config_entries, "async_unload_platforms", AsyncMock(return_value=False)): + assert await async_unload_entry(hass, entry) is False + assert entry.entry_id in hass.data[DOMAIN] and hass.services.has_service(DOMAIN, SERVICE_SET_PV_DATA) + + +async def test_changed_entry_is_reloaded(hass): + entry = make_entry(hass) + with patch.object(hass.config_entries, "async_reload", AsyncMock()) as reload: + await entry_update_listener(hass, entry) + reload.assert_awaited_once_with(entry.entry_id) + + +async def test_yaml_setup_is_accepted_and_ignored(hass): + assert await async_setup(hass, {}) is True diff --git a/tests/integration/test_services_and_unload.py b/tests/integration/test_services_and_unload.py new file mode 100644 index 0000000..1828cc4 --- /dev/null +++ b/tests/integration/test_services_and_unload.py @@ -0,0 +1,176 @@ +"""Services (set_pv_data, stop_charging), the multi-charger registry and unloading of entries. + +Tests marked `xfail(strict=True)` describe behaviour that is wrong today. They turn the run red +(XPASS) as soon as the bug is fixed, so the marker must then be removed. All other tests pin +behaviour that works. The bridge is real; only the WebSocket connection object is faked. +""" +import logging +from collections import ChainMap +from types import SimpleNamespace +from unittest.mock import AsyncMock, MagicMock, patch + +import pytest +from pytest_homeassistant_custom_component.common import MockConfigEntry + +from custom_components.goecharger_api2 import ( + ACTIVE_CHARGER_COUNTER, + GoeChargerDataUpdateCoordinator, + async_unload_entry, +) +from custom_components.goecharger_api2 import service as service_module +from custom_components.goecharger_api2.const import DOMAIN, SERVICE_SET_PV_DATA, SERVICE_STOP_CHARGING +from custom_components.goecharger_api2.service import ( + GoeChargerApiV2Service, + add_config_entry_to_service, + remove_config_entry_from_service, +) +from custom_components.goecharger_api2.pygoecharger_ha.keys import Tag + +BRIDGE_SLEEP = "custom_components.goecharger_api2.pygoecharger_ha.asyncio.sleep" +SERVICE_SLEEP = "custom_components.goecharger_api2.service.asyncio.sleep" + + +def _entry(hass, serial="123456", intg_type="charger"): + entry = MockConfigEntry(domain=DOMAIN, title=f"go-e {serial}", data={ + "mode": "lan", "host": "192.0.2.10", "id": serial, "integration_type": intg_type, + "scan_interval": 30}) + entry.add_to_hass(hass) + return entry + + +def _coordinator(hass, entry, websocket): + coordinator = GoeChargerDataUpdateCoordinator(hass, entry) + bridge = coordinator.bridge + coordinator.data = ChainMap(bridge._ws_states, bridge._config, bridge._states, bridge._versions) + if websocket: + bridge.ws_connected = True + bridge._ws_connection = SimpleNamespace(send_json=AsyncMock()) + return coordinator + + +def _sent(coordinator): + """(key, value) of every setValue message that was sent over the fake WebSocket.""" + calls = coordinator.bridge._ws_connection.send_json.await_args_list + return [(c.args[0]["key"], c.args[0]["value"]) for c in calls] + + +def _call(**data): + return SimpleNamespace(data=data, return_response=True) + + +@pytest.fixture(autouse=True) +def clean_registry(): + service_module._COORDINATOR_PER_CONFIG_ENTRY_ID_MAP = {} + yield + service_module._COORDINATOR_PER_CONFIG_ENTRY_ID_MAP = {} + + +# --------------------------------------------------------------------------- stop_charging + +async def test_stop_charging_in_websocket_mode_sends_lock_and_unlock(hass): + """The reported TypeError is gone: frc=1, wait, frc=0.""" + coordinator = _coordinator(hass, _entry(hass), websocket=True) + service = GoeChargerApiV2Service(hass, coordinator._config_entry, coordinator) + with patch(SERVICE_SLEEP, AsyncMock()): + result = await service.stop_charging(_call()) + assert result["success"] == "true" + assert _sent(coordinator) == [(Tag.FRC.key, 1), (Tag.FRC.key, 0)] + assert service._stop_in_progress is False + + +@pytest.mark.known_bug("stop_charging does not reset _stop_in_progress when the first write fails (service.py)") +async def test_failed_lock_write_blocks_later_stop_requests(hass): + """a ValueError on the first write leaves _stop_in_progress True forever.""" + coordinator = _coordinator(hass, _entry(hass), websocket=True) + service = GoeChargerApiV2Service(hass, coordinator._config_entry, coordinator) + coordinator.async_write_key = AsyncMock(side_effect=ValueError("charger unreachable")) + await service.stop_charging(_call()) + assert service._stop_in_progress is False + + +@pytest.mark.known_bug("stop_charging does not reset _stop_in_progress when the unlock write fails (service.py)") +async def test_failed_unlock_write_leaves_flag_stuck(hass): + """if frc=0 cannot be written after the wait, the charger stays locked + and the flag blocks every later stop_charging call.""" + coordinator = _coordinator(hass, _entry(hass), websocket=True) + service = GoeChargerApiV2Service(hass, coordinator._config_entry, coordinator) + coordinator.async_write_key = AsyncMock(side_effect=[{Tag.FRC.key: 1}, ValueError("down")]) + with patch(SERVICE_SLEEP, AsyncMock()): + await service.stop_charging(_call()) + assert service._stop_in_progress is False + + +# --------------------------------------------------------------------------- WebSocket write results + +@pytest.mark.known_bug("write_multiple_values_to_keys returns {} over WebSocket; handle_write_result only skips on None") +async def test_websocket_multi_write_logs_no_error_on_success(hass, caplog): + """a successful multi-key write in WebSocket mode is logged as an ERROR.""" + coordinator = _coordinator(hass, _entry(hass), websocket=True) + with patch(BRIDGE_SLEEP, AsyncMock()), caplog.at_level(logging.ERROR): + await coordinator.async_write_multiple_keys({"ct": "1", "mca": 6}, "ct", "default") + assert [k for k, _ in _sent(coordinator)] == ["ct", "mca"] + errors = [r.message for r in caplog.records if r.levelno >= logging.ERROR] + assert errors == [] + + +async def test_websocket_single_write_stores_requested_value_before_confirmation(hass): + """Characterisation: the requested value is stored at once, not after the device's answer. + If the charger rejects the command, this value stays until the device reports the key again.""" + coordinator = _coordinator(hass, _entry(hass), websocket=True) + await coordinator.async_write_key("amp", 10) + assert coordinator.data["amp"] == 10 + + +# --------------------------------------------------------------------------- multi-charger registry + +async def test_second_charger_is_reachable_by_configid(hass): + a = _coordinator(hass, _entry(hass, "111111"), websocket=False) + b = _coordinator(hass, _entry(hass, "222222"), websocket=False) + service = GoeChargerApiV2Service(hass, a._config_entry, a) + add_config_entry_to_service(b._config_entry, b) + _, found = service.get_coordinator(_call(configid=b._config_entry.entry_id)) + assert found is b + + +async def test_reloaded_charger_replaces_stale_coordinator(hass): + a = _coordinator(hass, _entry(hass, "111111"), websocket=False) + b = _coordinator(hass, _entry(hass, "222222"), websocket=False) + service = GoeChargerApiV2Service(hass, a._config_entry, a) + add_config_entry_to_service(b._config_entry, b) + + remove_config_entry_from_service(a._config_entry) # unload of A + new_a = _coordinator(hass, a._config_entry, websocket=False) # reload of A + add_config_entry_to_service(a._config_entry, new_a) + + _, found = service.get_coordinator(_call(configid=a._config_entry.entry_id)) + assert found is new_a + + +# --------------------------------------------------------------------------- unload counting + +def _register_services(hass): + async def _noop(call): + return None + hass.services.async_register(DOMAIN, SERVICE_SET_PV_DATA, _noop) + hass.services.async_register(DOMAIN, SERVICE_STOP_CHARGING, _noop) + + +@pytest.mark.known_bug("ACTIVE_CHARGER_COUNTER is raised for chargers only but lowered for every entry") +async def test_unloading_a_controller_keeps_the_charger_services(hass): + """the active-charger counter is only raised for chargers but lowered for every + entry, so unloading a controller removes the services of a still-running charger.""" + controller = _entry(hass, "999999", intg_type="controller") + hass.data[DOMAIN] = {controller.entry_id: MagicMock(), ACTIVE_CHARGER_COUNTER: 1} # one charger + _register_services(hass) + with patch.object(hass.config_entries, "async_unload_platforms", AsyncMock(return_value=True)): + await async_unload_entry(hass, controller) + assert hass.services.has_service(DOMAIN, SERVICE_SET_PV_DATA) + + +@pytest.mark.known_bug("ACTIVE_CHARGER_COUNTER key does not exist when only controllers are configured") +async def test_unloading_a_controller_without_any_charger_does_not_raise(hass): + """with only controllers configured the counter key does not exist.""" + controller = _entry(hass, "999999", intg_type="controller") + hass.data[DOMAIN] = {controller.entry_id: MagicMock()} + with patch.object(hass.config_entries, "async_unload_platforms", AsyncMock(return_value=True)): + assert await async_unload_entry(hass, controller) is True diff --git a/tests/integration/test_value_ranges.py b/tests/integration/test_value_ranges.py new file mode 100644 index 0000000..233022a --- /dev/null +++ b/tests/integration/test_value_ranges.py @@ -0,0 +1,56 @@ +"""Value ranges and option sets of the entities against the documentation. + +Sources (go-eCharger-API-v2, official repository): +- key table (`API_KEYS_FIRMWARE/apikeys-en.md`): option sets with their meaning and `lbr` 0-255 +- Modbus profile (`modbus-en.md`): amp/ama as whole amps 6 to 32 ("AMPERE_VOLATILE", "AMPERE_EEPROM"), + `frc` and `psm` "only accepts values 0, 1, 2", `dwo` any number of Wh or "null" for no limit + +Only numbers and option values are stored here, not the descriptions. The comparison is against what +the entity description offers; Home Assistant itself rejects values outside min/max before the +integration is called, so these limits are what a user can actually send. +""" +import pytest + +from custom_components.goecharger_api2 import const as entity_const + +# key -> (min, max) documented +NUMBER_RANGES = {"amp": (6, 32), "ama": (6, 32), "lbr": (0, 255)} +# key -> the set of values the charger accepts according to the documentation +OPTION_SETS = { + "frc": {"0", "1", "2"}, # Neutral, Off, On + "psm": {"0", "1", "2"}, # Modbus: "only accepts values 0, 1, 2" + "ust": {"0", "1", "2"}, # Normal, AutoUnlock, AlwaysLock + "tds": {"0", "1", "2"}, # None, EuropeanSummerTime, UsDaylightTime + "lmo": {"3", "4", "5"}, # Default, Awattar, AutomaticStop + "bac": {"0", "1", "2", "3"}, # AlwaysLock .. NeverLock +} + + +def _description(list_name, key): + matches = [d for d in getattr(entity_const, list_name) if d.key == key] + assert len(matches) == 1, f"{key}: expected one description in {list_name}, found {len(matches)}" + return matches[0] + + +@pytest.mark.parametrize("key, bounds", NUMBER_RANGES.items(), ids=NUMBER_RANGES) +def test_number_range_equals_documentation(key, bounds): + d = _description("NUMBER_SENSORS", key) + assert (d.native_min_value, d.native_max_value) == bounds + + +@pytest.mark.parametrize("key", NUMBER_RANGES) +def test_number_step_is_a_whole_unit(key): + assert _description("NUMBER_SENSORS", key).native_step == 1 + + +@pytest.mark.parametrize("key, values", OPTION_SETS.items(), ids=OPTION_SETS) +def test_select_options_equal_documentation(key, values): + d = _description("SELECT_SENSORS", key) + assert set(d.options) == values and len(d.options) == len(values), "no duplicates, no missing or extra option" + + +def test_energy_limit_stays_inside_what_the_api_accepts(): + # documented: any number of Wh (null = no limit); the integration offers 0 to 1,000,000 Wh in steps of 10 + d = _description("NUMBER_SENSORS", "dwo") + assert 0 <= d.native_min_value < d.native_max_value and d.native_step > 0 + assert d.native_min_value >= 0, "a negative energy limit is not meaningful" diff --git a/tests/integration/test_versions_16a.py b/tests/integration/test_versions_16a.py new file mode 100644 index 0000000..3cd4ee9 --- /dev/null +++ b/tests/integration/test_versions_16a.py @@ -0,0 +1,60 @@ +"""Coordinator.read_versions: when is a charger treated as limited to 16 A? + +This is a decision table over cable limit (`cll`), 16 A adapter (`adi`), variant (`var`), device type +and the user option. The rows marked "current behaviour" pin what the code does today (since 98af3e0) +without claiming that it is the right choice for every device. +""" +import inspect +from unittest.mock import AsyncMock + +import pytest +from pytest_homeassistant_custom_component.common import MockConfigEntry + +from custom_components.goecharger_api2 import GoeChargerDataUpdateCoordinator +from custom_components.goecharger_api2.pygoecharger_ha import NonNullDict + +NON_CORE = "go-eCharger_V4" +CORE = "go-eCharger_Phoenix" + + +# The rule "cll without cableCurrentLimit -> no 16 A limit" exists since 98af3e0. Older releases limit such +# chargers, so that row is skipped there (the skip is visible in the report, it is not a silent pass). +RULE_SINCE_98AF3E0 = "will not enforce the 16A CABLE limit" in inspect.getsource( + GoeChargerDataUpdateCoordinator.read_versions) + + +def _cll(**fields): + return {"cll": fields} + + +CASES = [ + # id, device, extra versions, option, expected + ("cable_limit_32", NON_CORE, _cll(cableCurrentLimit=32), False, False), + ("cable_limit_16", NON_CORE, _cll(cableCurrentLimit=16), False, True), + ("cable_limit_6", NON_CORE, _cll(cableCurrentLimit=6), False, True), + ("variant_11kw_always_limits", NON_CORE, {"var": 11, **_cll(cableCurrentLimit=32)}, False, True), + ("user_option_forces_limit", NON_CORE, _cll(cableCurrentLimit=32), True, True), + ("no_cll_adapter_in_use", NON_CORE, {"adi": True}, False, True), + ("no_cll_no_adapter", NON_CORE, {"adi": False}, False, False), + ("core_without_cll_is_limited", CORE, {}, False, True), + # current behaviour since 98af3e0 (issue #176): cll present but no cableCurrentLimit -> no limit + ("cll_without_cable_limit_non_core", NON_CORE, _cll(currentLimitMax=32), False, False), + ("cll_without_cable_limit_core", CORE, _cll(currentLimitMax=32), False, False), +] + + +@pytest.mark.parametrize("device, extra, option, expected", + [c[1:] for c in CASES], ids=[c[0] for c in CASES]) +async def test_limit_to_16a_decision(request, hass, device, extra, option, expected): + if request.node.callspec.id.startswith("cll_without_cable_limit") and not RULE_SINCE_98AF3E0: + pytest.skip("this release predates the 98af3e0 rule for cll without cableCurrentLimit") + entry = MockConfigEntry(domain="goecharger_api2", title="t", data={ + "mode": "lan", "host": "192.0.2.10", "id": "123456", "integration_type": "charger", + "limit_to_11kw": option}) + entry.add_to_hass(hass) + coordinator = GoeChargerDataUpdateCoordinator(hass, entry) + coordinator.bridge.read_versions = AsyncMock(return_value=True) + coordinator.bridge._versions = NonNullDict({"typ": device, "fwv": "60.3", "var": 22, **extra}) + + assert await coordinator.read_versions() is True + assert coordinator.limit_to16a is expected diff --git a/tests/integration/test_versions_transport_parity.py b/tests/integration/test_versions_transport_parity.py new file mode 100644 index 0000000..68b35da --- /dev/null +++ b/tests/integration/test_versions_transport_parity.py @@ -0,0 +1,41 @@ +"""Bridge.read_versions: the same device answer must lead to the same `_versions` via HTTP and via WebSocket. + +Background is issue #176: on a charger with firmware 59.4 the `cll` object contained `cableCurrentLimit` +when read via the HTTP API, but not when read via the WebSocket. The bridge only passes the data on, so +these tests pin that it does not change the content depending on the transport. They cannot show what a +real charger sends over each transport; that needs a read-only comparison on the device. +""" +import pytest + +from custom_components.goecharger_api2.pygoecharger_ha import NonNullDict +from custom_components.goecharger_api2.pygoecharger_ha.const import FILTER_VERSIONS + +from .factories import make_bridge + +STATUS = "http://192.0.2.10/api/status" + +PAYLOADS = [ + pytest.param({"typ": "go-eCharger_V4", "fwv": "59.4", "var": 22, + "cll": {"cableCurrentLimit": 32, "currentLimitMax": 32}}, id="cll_with_cable_limit"), + pytest.param({"typ": "go-eCharger_V4", "fwv": "59.4", "var": 22, + "cll": {"currentLimitMax": 32, "requestedCurrent": 16}}, id="cll_without_cable_limit"), + pytest.param({"typ": "go-eCharger_V4", "fwv": "59.4", "var": 22}, id="no_cll"), +] + + +def test_cll_is_part_of_the_versions_filter(): + assert "cll" in FILTER_VERSIONS.split(",") + + +@pytest.mark.parametrize("payload", PAYLOADS) +async def test_versions_are_identical_via_http_and_websocket(hass, aioclient_mock, payload): + aioclient_mock.get(STATUS, json=payload) + via_http = make_bridge(hass) + await via_http.read_versions() + + via_ws = make_bridge(hass, websocket=True) + via_ws._ws_states = {**payload, "unrelated": 1} + await via_ws.read_versions() + + assert isinstance(via_http._versions, NonNullDict) and isinstance(via_ws._versions, NonNullDict) + assert dict(via_http._versions) == dict(via_ws._versions) == payload diff --git a/tests/integration/test_ws_connect_paths.py b/tests/integration/test_ws_connect_paths.py new file mode 100644 index 0000000..c9964d2 --- /dev/null +++ b/tests/integration/test_ws_connect_paths.py @@ -0,0 +1,257 @@ +"""WebSocket connection: the paths of a login that succeeds and of the message loop afterwards. + +The fake socket plays a script (see test_ws_login_failures.py for the failing logins). The handshake is +the current implementation, not an official protocol. +""" +import asyncio +import json +import logging +from types import SimpleNamespace + +import aiohttp +import msgpack +import pytest + +from custom_components.goecharger_api2.pygoecharger_ha import GoeChargerApiV2Bridge, TargetedEvent + +from .factories import make_bridge +from .test_ws_login_failures import AUTH_OK, AUTH_REQUIRED, HELLO, FakeSession, FakeSocket, _text + + +class StreamSocket(FakeSocket): + """Like FakeSocket, but `after` holds ready-made message objects.""" + + def __aiter__(self): + async def stream(): + for message in self._after: + yield message + return stream() + + +def _bridge(socket, password="secret", **kwargs): + return GoeChargerApiV2Bridge(web_session=FakeSession(socket), intg_type="charger", host="192.0.2.10", + access_password=password, **kwargs) + + +def _msg(kind, data): + return SimpleNamespace(type=kind, data=data) + + +FULL = {"type": "fullStatus", "status": {"car": 1, "amp": 6}} + + +async def _run(socket, **kwargs): + bridge = _bridge(socket) + await bridge.ws_connect(**kwargs) + return bridge + + +# ----------------------------------------------------------------------------- the handshake + +@pytest.mark.parametrize("hello, expected_secured", [ + ({**HELLO, "version": "60.4", "secured": True}, True), + ({**HELLO, "version": "beta"}, False), + ({k: v for k, v in HELLO.items() if k != "version"}, False), +]) +async def test_hello_variants_are_accepted(hello, expected_secured): + socket = StreamSocket([hello, AUTH_REQUIRED, AUTH_OK], after=[_text(FULL)]) + bridge = _bridge(socket) + seen = {} + original = bridge.extract_ws_message_data + + def spy(data): + seen.update(secured=bridge._ws_secured, serial=bridge._ws_serial, info=dict(bridge._ws_device_info)) + return original(data) + + bridge.extract_ws_message_data = spy + await bridge.ws_connect() + assert seen["secured"] is expected_secured and seen["serial"] == "123456" and "type" not in seen["info"] + + +@pytest.mark.parametrize("auth, device, expected_hash", [ + ({"type": "authRequired", "token1": "a", "token2": "b", "hash": "bcrypt"}, "go-eCharger_V4", "bcrypt"), + ({"type": "authRequired", "token1": "a", "token2": "b"}, "go-eCharger_V4", "pbkdf2"), + ({"type": "authRequired", "token1": "a", "token2": "b"}, "go-eCharger_Phoenix", "bcrypt"), + ({"type": "authRequired", "token1": "a", "token2": "b"}, "go-eCharger_Core", "bcrypt"), + ({"type": "authRequired", "token1": "a", "token2": "b", "hash": "PBKDF2"}, "go-eCharger_Phoenix", "pbkdf2"), +]) +async def test_hash_type_is_taken_from_the_device_or_chosen_by_its_generation(auth, device, expected_hash): + socket = StreamSocket([{**HELLO, "devicetype": device}, auth, AUTH_OK], after=[]) + bridge = _bridge(socket) + used = [] + original = bridge._ws_compute_hashed_password + bridge._ws_compute_hashed_password = lambda hash_type, pwd, serial: (used.append(hash_type), original(hash_type, pwd, serial))[1] + await bridge.ws_connect() + assert used == [expected_hash] + + +async def test_cloud_connection_sends_the_token_and_uses_the_cloud_address(): + captured = {} + socket = StreamSocket([HELLO, AUTH_REQUIRED, AUTH_OK], after=[]) + + class Session(FakeSession): + def ws_connect(self, **kwargs): + captured.update(kwargs) + return super().ws_connect(**kwargs) + + bridge = GoeChargerApiV2Bridge(web_session=Session(socket), intg_type="charger", serial="123456", + token="tok", access_password="secret") + await bridge.ws_connect() + assert captured["url"] == "wss://app.v3.go-e.io/123456" and captured["headers"] == {"Authorization": "Bearer tok"} + + +async def test_local_connection_sends_no_headers(): + captured = {} + socket = StreamSocket([HELLO, AUTH_REQUIRED, AUTH_OK], after=[]) + + class Session(FakeSession): + def ws_connect(self, **kwargs): + captured.update(kwargs) + return super().ws_connect(**kwargs) + + bridge = GoeChargerApiV2Bridge(web_session=Session(socket), intg_type="charger", host="https://wallbox.local", + access_password="secret") + await bridge.ws_connect() + assert captured["url"] == "wss://wallbox.local/ws" and captured["headers"] is None + + +# ----------------------------------------------------------------------------- the message loop + +async def test_text_and_binary_messages_both_update_the_states(): + after = [_msg(aiohttp.WSMsgType.TEXT, json.dumps(FULL)), + _msg(aiohttp.WSMsgType.BINARY, b"\x00" + msgpack.packb({"type": "deltaStatus", "status": {"amp": 10}}))] + bridge = _bridge(StreamSocket([HELLO, AUTH_REQUIRED, AUTH_OK], after=after)) + states = {} + original = bridge.extract_ws_message_data + bridge.extract_ws_message_data = lambda d: (states.update(bridge._ws_states), original(d))[1] + await bridge.ws_connect(keep_ws_states_after_close=True) + assert bridge._ws_states == {"car": 1, "amp": 10} and bridge.ws_connected is True + + +async def test_new_data_marks_the_time_and_notifies_the_coordinator(): + bridge = _bridge(StreamSocket([HELLO, AUTH_REQUIRED, AUTH_OK], after=[_text(FULL)])) + notified = [] + bridge._ws_notify_for_new_data = lambda: notified.append(bridge._ws_LAST_UPDATE) + await bridge.ws_connect() + assert len(notified) == 1 and notified[0] > 0 + + +async def test_broken_binary_message_is_logged_and_the_loop_continues(caplog): + after = [_msg(aiohttp.WSMsgType.BINARY, b"\xc1\xc1"), _text(FULL)] + bridge = _bridge(StreamSocket([HELLO, AUTH_REQUIRED, AUTH_OK], after=after)) + notified = [] + bridge._ws_notify_for_new_data = lambda: notified.append(1) + with caplog.at_level(logging.WARNING): + await bridge.ws_connect() + assert "Error processing BINARY message" in caplog.text and notified == [1] + + +async def test_broken_text_message_is_logged_and_the_loop_continues(caplog): + after = [_msg(aiohttp.WSMsgType.TEXT, "{not json"), _text(FULL)] + bridge = _bridge(StreamSocket([HELLO, AUTH_REQUIRED, AUTH_OK], after=after)) + notified = [] + bridge._ws_notify_for_new_data = lambda: notified.append(1) + with caplog.at_level(logging.WARNING): + await bridge.ws_connect() + assert "Error processing TEXT message" in caplog.text and notified == [1] + + +@pytest.mark.parametrize("kind", [aiohttp.WSMsgType.CLOSED, aiohttp.WSMsgType.ERROR]) +async def test_close_or_error_message_ends_the_loop(kind): + after = [_msg(kind, None), _text(FULL)] # the message after the close must never be read + bridge = _bridge(StreamSocket([HELLO, AUTH_REQUIRED, AUTH_OK], after=after)) + bridge._ws_notify_for_new_data = lambda: pytest.fail("data after close was processed") + await bridge.ws_connect() + assert bridge.ws_connected is False + + +async def test_unknown_message_type_is_logged_and_ignored(caplog): + bridge = _bridge(StreamSocket([HELLO, AUTH_REQUIRED, AUTH_OK], after=[_msg(aiohttp.WSMsgType.PING, b"")])) + with caplog.at_level(logging.WARNING): + await bridge.ws_connect() + assert "Unknown message type" in caplog.text + + +async def test_start_event_is_set_once_all_required_keys_have_arrived(): + keys = TargetedEvent(is_charger=True).tag_keys + first = {"type": "fullStatus", "partial": True, "status": {keys[0]: 1}} + rest = {"type": "deltaStatus", "status": {k: 1 for k in keys}} + event = TargetedEvent(is_charger=True) + bridge = _bridge(StreamSocket([HELLO, AUTH_REQUIRED, AUTH_OK], after=[_text(first), _text(rest)])) + await bridge.ws_connect(a_event=event) + assert event.is_set() and not event.has_errors() + + +# ----------------------------------------------------------------------------- how a connection ends + +async def test_connection_end_clears_the_states_by_default(): + bridge = await _run(StreamSocket([HELLO, AUTH_REQUIRED, AUTH_OK], after=[_text(FULL)])) + assert bridge._ws_states == {} and bridge.ws_connected is False and bridge._ws_connection is None + + +async def test_connection_end_can_keep_the_states_for_the_setup_check(): + bridge = await _run(StreamSocket([HELLO, AUTH_REQUIRED, AUTH_OK], after=[_text(FULL)]), keep_ws_states_after_close=True) + assert bridge._ws_states == {"car": 1, "amp": 6} and bridge.ws_connected is True + + +@pytest.mark.parametrize("error", [asyncio.TimeoutError(), asyncio.CancelledError()]) +async def test_timeout_and_cancel_are_reported_to_the_waiting_start(error): + bridge = GoeChargerApiV2Bridge(web_session=FakeSession(error=error), intg_type="charger", host="192.0.2.10", + access_password="secret") + event = TargetedEvent(is_charger=True) + await bridge.ws_connect(a_event=event) + assert event.has_errors() and bridge.ws_connected is False + + +async def test_errors_without_a_waiting_event_do_not_raise(): + bridge = GoeChargerApiV2Bridge(web_session=FakeSession(error=aiohttp.ClientConnectionError("x")), + intg_type="charger", host="192.0.2.10", access_password="secret") + assert await bridge.ws_connect() is None + + +# ----------------------------------------------------------------------------- building the addresses + +@pytest.mark.parametrize("host, password, expected_http, expected_ws", [ + ("wallbox.local", None, "http://wallbox.local", None), + ("wallbox.local", " ", "http://wallbox.local", None), + ("wallbox.local", " secret ", "http://wallbox.local", "ws://wallbox.local/ws"), + ("http://wallbox.local", "secret", "http://wallbox.local", "ws://wallbox.local/ws"), + ("https://wallbox.local", "secret", "https://wallbox.local", "wss://wallbox.local/ws"), +]) +async def test_local_addresses(host, password, expected_http, expected_ws): + bridge = GoeChargerApiV2Bridge(web_session=None, intg_type="charger", host=host, access_password=password) + assert bridge.host_url == expected_http and bridge.ws_url == expected_ws + if password and password.strip(): + assert bridge.access_password == "secret" + + +@pytest.mark.parametrize("password, expected_ws", [(None, None), ("secret", "wss://app.v3.go-e.io/1234")]) +async def test_cloud_addresses(password, expected_ws): + bridge = GoeChargerApiV2Bridge(web_session=None, intg_type="charger", serial="1234", token="tok", + access_password=password) + assert bridge.host_url == "https://001234.api.v3.go-e.io" and bridge.token == "Bearer tok" + assert bridge.ws_url == expected_ws + + +async def test_a_serial_without_a_token_is_not_enough(): + with pytest.raises(ValueError): + GoeChargerApiV2Bridge(web_session=None, intg_type="charger", serial="1234") + + +@pytest.mark.parametrize("language, expected", [("de", "de"), ("xx", "en")]) +async def test_bridge_texts_follow_the_language_with_english_fallback(language, expected): + from custom_components.goecharger_api2.pygoecharger_ha.const import TRANSLATIONS + bridge = GoeChargerApiV2Bridge(web_session=None, intg_type="charger", host="h", lang=language) + assert bridge.lang_map is TRANSLATIONS[expected] + + +async def test_normalizing_unreadable_bytes_gives_hex_and_keeps_other_types(): + bridge = make_bridge_no_hass() + assert bridge._ws_normalize_value(b"\xff\xfe") == "fffe" + assert bridge._ws_normalize_value((b"a", 1)) == ("a", 1) + assert bridge._ws_normalize_value(3.5) == 3.5 + assert bridge._ws_normalize_dict({1: b"x"}) == {"1": "x"} + + +def make_bridge_no_hass(): + return GoeChargerApiV2Bridge(web_session=None, intg_type="charger", host="h") diff --git a/tests/integration/test_ws_login_failures.py b/tests/integration/test_ws_login_failures.py new file mode 100644 index 0000000..3b97458 --- /dev/null +++ b/tests/integration/test_ws_login_failures.py @@ -0,0 +1,186 @@ +"""WebSocket login: every way the handshake can fail must end cleanly. + +"Cleanly" means: the waiting start code is told about the error (TargetedEvent), the connection is +reported as not connected, no data is kept and no command is sent. The handshake follows the +current implementation (hello -> auth required -> auth -> auth result); it is not officially documented. +A positive control (`test_successful_login_...`) makes sure the fake server really lets a valid login pass. +""" +import asyncio +import json +from types import SimpleNamespace +from unittest.mock import AsyncMock, patch + +import aiohttp +import pytest + +from custom_components.goecharger_api2.pygoecharger_ha import GoeChargerApiV2Bridge, TargetedEvent + +WAIT_FOR = "custom_components.goecharger_api2.pygoecharger_ha.asyncio.wait_for" +HELLO = {"type": "hello", "serial": "123456", "version": "59.4", "secured": False} +AUTH_REQUIRED = {"type": "authRequired", "token1": "t1", "token2": "t2", "hash": "pbkdf2"} +AUTH_OK = {"type": "authSuccess"} + + +def _text(payload): + return SimpleNamespace(type=aiohttp.WSMsgType.TEXT, data=json.dumps(payload)) + + +class FakeSocket: + """Plays a script: the first receive() calls answer the handshake, `after` is streamed afterwards.""" + + def __init__(self, handshake, after=()): + self._handshake = list(handshake) + self._after = list(after) + self.send_json = AsyncMock() + + async def receive(self): + item = self._handshake.pop(0) + if item == "hang": + await asyncio.sleep(3600) + return _text(item) + + def __aiter__(self): + async def stream(): + for payload in self._after: + yield _text(payload) + return stream() + + async def close(self): + return None + + +class FakeSession: + def __init__(self, socket=None, error=None): + self._socket, self._error = socket, error + + def ws_connect(self, **kwargs): + session = self + + class _Ctx: + async def __aenter__(self): + if session._error: + raise session._error + return session._socket + + async def __aexit__(self, *exc): + return False + return _Ctx() + + +def _bridge(session, password="secret"): + return GoeChargerApiV2Bridge(web_session=session, intg_type="charger", host="192.0.2.10", + access_password=password) + + +async def _login(bridge): + event = TargetedEvent(is_charger=True) + result = await bridge.ws_connect(a_event=event) + return event, result + + +def _assert_clean_failure(bridge, event, socket, message): + assert event.is_set() and event.has_errors() + assert message in str(event._error) + assert bridge.ws_connected is False + assert bridge._ws_states == {} + if socket is not None: + socket.send_json.assert_awaited_once() # at most the auth answer, never a setValue command + assert socket.send_json.await_args.args[0].get("type") != "setValue" + + +async def test_successful_login_reports_data_and_resets_state_afterwards(): + """Positive control for the fake server.""" + status = {"type": "fullStatus", "status": {key: 1 for key in TargetedEvent(is_charger=True).tag_keys}} + socket = FakeSocket([HELLO, AUTH_REQUIRED, AUTH_OK], after=[status]) + bridge = _bridge(FakeSession(socket)) + event, _ = await _login(bridge) + assert event.is_set() and not event.has_errors() + assert socket.send_json.await_args.args[0]["type"] == "auth" + assert bridge.ws_connected is False # the stream ended, so the connection is closed again + + +async def test_no_password_means_no_websocket_url(): + bridge = _bridge(FakeSession(FakeSocket([])), password=None) + event, _ = await _login(bridge) + _assert_clean_failure(bridge, event, None, "WebSocket URL not configured") + + +async def test_hello_without_serial_fails(): + socket = FakeSocket([{"type": "hello"}]) + bridge = _bridge(FakeSession(socket)) + event, _ = await _login(bridge) + assert event.has_errors() and "No serial" in str(event._error) + assert bridge.ws_connected is False + socket.send_json.assert_not_awaited() + + +async def test_device_reports_offline(): + socket = FakeSocket([HELLO, {"type": "offline"}]) + bridge = _bridge(FakeSession(socket)) + event, _ = await _login(bridge) + assert event.has_errors() and "offline" in str(event._error) + assert bridge.ws_connected is False + socket.send_json.assert_not_awaited() + + +async def test_unsupported_hash_type_fails(): + socket = FakeSocket([HELLO, {**AUTH_REQUIRED, "hash": "rot13"}]) + bridge = _bridge(FakeSession(socket)) + event, _ = await _login(bridge) + assert event.has_errors() and "Unsupported authentication hash type" in str(event._error) + socket.send_json.assert_not_awaited() + + +@pytest.mark.parametrize("missing", ["token1", "token2"]) +async def test_missing_challenge_token_fails(missing): + challenge = {k: v for k, v in AUTH_REQUIRED.items() if k != missing} + socket = FakeSocket([HELLO, challenge]) + bridge = _bridge(FakeSession(socket)) + event, _ = await _login(bridge) + assert event.has_errors() and "Missing authentication tokens" in str(event._error) + socket.send_json.assert_not_awaited() + + +@pytest.mark.parametrize("answer", [ + {"type": "authError", "message": "wrong password"}, + {"type": "response", "success": False}, +]) +async def test_wrong_password_fails(answer): + socket = FakeSocket([HELLO, AUTH_REQUIRED, answer]) + bridge = _bridge(FakeSession(socket)) + event, _ = await _login(bridge) + _assert_clean_failure(bridge, event, socket, "Authentication failed") + + +async def test_no_answer_to_the_auth_message_times_out(): + socket = FakeSocket([HELLO, AUTH_REQUIRED, "hang"]) + bridge = _bridge(FakeSession(socket)) + real_wait_for = asyncio.wait_for + with patch(WAIT_FOR, lambda coro, timeout: real_wait_for(coro, 0.01)): + event, _ = await _login(bridge) + _assert_clean_failure(bridge, event, socket, "Authentication TIMEOUT") + + +async def test_unreachable_device_reports_the_connection_error(): + error = aiohttp.ClientConnectionError("no route to host") + bridge = _bridge(FakeSession(error=error)) + event, _ = await _login(bridge) + assert event.has_errors() and event._error is error + assert bridge.ws_connected is False and bridge._ws_states == {} + + +async def test_unexpected_error_during_handshake_is_reported_not_raised(): + error = RuntimeError("boom") + bridge = _bridge(FakeSession(error=error)) + event, result = await _login(bridge) + assert result is None and event._error is error + assert bridge.ws_connected is False + + +async def test_connection_that_drops_after_login_resets_the_state(): + socket = FakeSocket([HELLO, AUTH_REQUIRED, AUTH_OK], + after=[{"type": "fullStatus", "status": {"car": 1}}]) + bridge = _bridge(FakeSession(socket)) + await bridge.ws_connect() + assert bridge.ws_connected is False + assert bridge._ws_states == {} and bridge._ws_connection is None diff --git a/tests/integration/test_ws_protocol.py b/tests/integration/test_ws_protocol.py new file mode 100644 index 0000000..4924191 --- /dev/null +++ b/tests/integration/test_ws_protocol.py @@ -0,0 +1,192 @@ +"""Bridge, WebSocket path: message handling, password hashing and message signing. + +The WebSocket protocol is not part of the official go-e documentation. These tests pin the behaviour +of the current implementation so that refactorings do not change it unnoticed; they are not a +statement about what the charger firmware really does. +""" +import hashlib +import hmac +import json +import logging +from types import SimpleNamespace +from unittest.mock import AsyncMock + +import bcrypt +import msgpack +import pytest + +from custom_components.goecharger_api2.pygoecharger_ha import ( + GoeChargerApiV2Bridge, + TargetedEvent, + bcryptjs_base64_encode_serial, +) +from custom_components.goecharger_api2.pygoecharger_ha.const import API_KEYS_TO_IGNORE_FROM_WS + +BCRYPT_ALPHABET = set("./ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789") + + +@pytest.fixture +def bridge(): + return GoeChargerApiV2Bridge(web_session=None, intg_type="charger", host="192.0.2.10", + access_password="secret") + + +# ----------------------------------------------------------------------------- incoming messages + +def test_full_status_replaces_all_states(bridge): + bridge._ws_states = {"a": 1} + assert bridge.extract_ws_message_data({"type": "fullStatus", "status": {"a": 2, "b": 3}}) is True + assert bridge._ws_states == {"a": 2, "b": 3} + + +def test_full_status_keeps_keys_that_are_missing_in_the_message(bridge): + bridge._ws_states = {"a": 1, "z": 9} + bridge.extract_ws_message_data({"type": "fullStatus", "status": {"a": 2}}) + assert bridge._ws_states == {"a": 2, "z": 9} + + +def test_partial_full_status_is_merged(bridge): + bridge._ws_states = {"a": 1} + bridge.extract_ws_message_data({"type": "fullStatus", "partial": True, "status": {"b": 3}}) + assert bridge._ws_states == {"a": 1, "b": 3} + + +def test_delta_status_updates_values(bridge): + bridge._ws_states = {"a": 1} + assert bridge.extract_ws_message_data({"type": "deltaStatus", "status": {"a": 5}}) is True + assert bridge._ws_states == {"a": 5} + + +def test_delta_status_ignores_configured_keys(bridge): + ignored = API_KEYS_TO_IGNORE_FROM_WS[0] + assert bridge.extract_ws_message_data({"type": "deltaStatus", "status": {ignored: 1}}) is False + assert ignored not in bridge._ws_states + + +def test_successful_response_updates_states(bridge): + assert bridge.extract_ws_message_data( + {"type": "response", "success": True, "requestId": 1, "status": {"frc": 1}}) is True + assert bridge._ws_states == {"frc": 1} + + +def test_failed_response_is_logged_and_changes_nothing(bridge, caplog): + bridge._ws_request_storage.set("7", {"key": "amp", "value": 99}) + with caplog.at_level(logging.WARNING): + result = bridge.extract_ws_message_data( + {"type": "response", "success": False, "requestId": "7", "message": "denied"}) + assert result is False + assert bridge._ws_states == {} + assert "Command failed" in caplog.text and "amp" in caplog.text + + +@pytest.mark.parametrize("message", [ + {"type": "hello"}, + {"type": "fullStatus", "status": {}}, + {"type": "deltaStatus"}, + {}, +]) +def test_other_messages_report_no_new_data(bridge, message): + assert bridge.extract_ws_message_data(message) is False + + +def test_decode_accepts_json_text_and_msgpack_with_or_without_prefix(bridge): + payload = {"type": "hello", "serial": "123456"} + assert bridge._ws_decode_message(json.dumps(payload)) == payload + assert bridge._ws_decode_message(msgpack.packb(payload)) == payload + assert bridge._ws_decode_message(b"\x00" + msgpack.packb(payload)) == payload + + +def test_normalize_turns_bytes_into_strings(bridge): + assert bridge._ws_normalize_dict({b"serial": b"123456", "list": [b"a", {b"k": b"v"}]}) == \ + {"serial": "123456", "list": ["a", {"k": "v"}]} + + +# ----------------------------------------------------------------------------- password hashing + +def test_pbkdf2_hash_is_32_bytes_and_follows_the_documented_steps(bridge): + expected = hashlib.pbkdf2_hmac("sha512", b"secret", b"123456", 100000, 256) + import base64 + result = bridge._ws_compute_hashed_password("pbkdf2", "secret", "123456") + assert len(result) == 32 + assert result == base64.b64encode(expected)[:32] + + +def test_bcrypt_hash_is_deterministic_and_verifiable(bridge): + first = bridge._ws_compute_hashed_password("bcrypt", "secret", "123456") + second = bridge._ws_compute_hashed_password("bcrypt", "secret", "123456") + assert first == second and len(first) == 31 + salt = f"$2a$08${bcryptjs_base64_encode_serial('123456', 16)}" + assert bcrypt.hashpw(hashlib.sha256(b"secret").hexdigest().encode(), salt.encode()).endswith(first) + + +def test_unknown_hash_type_returns_none(bridge): + assert bridge._ws_compute_hashed_password("md5", "secret", "123456") is None + + +def test_bcrypt_salt_encoding_has_22_valid_characters(): + encoded = bcryptjs_base64_encode_serial("123456", 16) + assert len(encoded) == 22 and set(encoded) <= BCRYPT_ALPHABET + + +def test_bcrypt_salt_encoding_rejects_non_numeric_serial(): + with pytest.raises(ValueError): + bcryptjs_base64_encode_serial("12AB56", 16) + + +def test_auth_hash_follows_challenge_response_formula(bridge): + hashed = b"hashed-password" + inner = hashlib.sha256(b"token1" + hashed).hexdigest() + expected = hashlib.sha256(("token3" + "token2" + inner).encode()).hexdigest() + assert bridge._ws_compute_auth_hash("token1", "token2", "token3", hashed) == expected + + +# ----------------------------------------------------------------------------- outgoing commands + +async def test_plain_command_has_documented_shape(bridge): + bridge._ws_connection = SimpleNamespace(send_json=AsyncMock()) + await bridge._ws_send_command("amp", 10) + first = bridge._ws_connection.send_json.await_args.args[0] + assert first == {"type": "setValue", "requestId": 1, "key": "amp", "value": 10} + await bridge._ws_send_command("frc", 1) + assert bridge._ws_connection.send_json.await_args.args[0]["requestId"] == 2 + + +async def test_secured_command_is_signed_with_hmac_sha256(bridge): + bridge._ws_connection = SimpleNamespace(send_json=AsyncMock()) + bridge._ws_secured = True + bridge._ws_hashed_password = b"hashed-password" + await bridge._ws_send_command("frc", 1) + message = bridge._ws_connection.send_json.await_args.args[0] + assert message["type"] == "securedMsg" and message["requestId"] == "1sm" + assert json.loads(message["data"]) == {"type": "setValue", "requestId": 1, "key": "frc", "value": 1} + assert message["hmac"] == hmac.new(b"hashed-password", message["data"].encode(), hashlib.sha256).hexdigest() + + +async def test_command_sets_flag_and_returns_requested_value_over_websocket(bridge): + bridge.ws_connected = True + bridge._ws_connection = SimpleNamespace(send_json=AsyncMock()) + bridge._ws_LAST_NEW_DATA_NOTIFY = 123.0 + assert await bridge.write_value_to_key("frc", 1) == {"frc": 1} + assert bridge._ws_LAST_NEW_DATA_NOTIFY == 0 + + +async def test_button_press_is_ignored_over_websocket(bridge): + bridge.ws_connected = True + bridge._ws_connection = SimpleNamespace(send_json=AsyncMock()) + assert await bridge.write_value_to_key("zfocore", "TRIGGER") == {"zfocore": "TRIGGER"} + bridge._ws_connection.send_json.assert_not_awaited() + + +# ----------------------------------------------------------------------------- start condition + +def test_targeted_event_waits_for_the_required_charger_keys(): + event = TargetedEvent(is_charger=True) + assert "hai" not in event.tag_keys and "cll" not in event.tag_keys + assert event.check_and_set({"typ": 1}) is False and not event.is_set() + assert event.check_and_set({key: 1 for key in event.tag_keys}) is True and event.is_set() + + +def test_targeted_event_for_controller_needs_different_keys(): + charger, controller = TargetedEvent(is_charger=True), TargetedEvent(is_charger=False) + assert set(charger.tag_keys) != set(controller.tag_keys) + assert "fwv" in controller.tag_keys diff --git a/tests/reports/2026-10-29/coverage-all.json b/tests/reports/2026-10-29/coverage-all.json new file mode 100644 index 0000000..c1e3c2d --- /dev/null +++ b/tests/reports/2026-10-29/coverage-all.json @@ -0,0 +1 @@ +{"meta": {"format": 3, "version": "7.16.2", "timestamp": "2026-10-29T11:48:24.962084", "branch_coverage": true, "show_contexts": false}, "files": {"custom_components/goecharger_api2/__init__.py": {"executed_lines": [1, 2, 3, 4, 5, 6, 7, 9, 10, 11, 12, 23, 24, 25, 30, 31, 32, 33, 34, 35, 36, 38, 45, 46, 60, 61, 63, 65, 66, 67, 68, 69, 70, 71, 73, 74, 75, 76, 77, 78, 81, 82, 85, 86, 87, 88, 89, 90, 91, 92, 93, 97, 98, 100, 103, 105, 108, 109, 110, 111, 112, 113, 116, 117, 118, 120, 122, 123, 124, 125, 126, 127, 128, 129, 131, 132, 133, 135, 136, 137, 138, 140, 141, 144, 145, 148, 149, 150, 151, 153, 154, 155, 156, 158, 162, 165, 166, 173, 175, 176, 177, 179, 180, 182, 184, 187, 188, 190, 191, 192, 193, 194, 195, 196, 197, 198, 201, 202, 203, 204, 206, 209, 210, 211, 214, 215, 216, 217, 218, 219, 220, 221, 224, 225, 226, 227, 228, 230, 231, 232, 233, 234, 236, 238, 240, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 254, 255, 257, 258, 259, 260, 261, 262, 269, 270, 271, 272, 273, 275, 276, 278, 279, 280, 281, 283, 284, 285, 286, 287, 288, 289, 290, 291, 294, 296, 297, 298, 300, 301, 302, 303, 304, 306, 307, 310, 311, 312, 314, 315, 316, 325, 326, 336, 337, 339, 340, 341, 343, 346, 347, 348, 349, 350, 351, 352, 353, 354, 356, 358, 359, 360, 361, 362, 363, 368, 371, 374, 375, 377, 378, 379, 380, 381, 382, 383, 384, 386, 387, 388, 391, 392, 393, 394, 396, 398, 403, 404, 406, 410, 411, 412, 413, 416, 417, 418, 419, 423, 424, 425, 428, 431, 432, 433, 435, 437, 438, 442, 445, 446, 448, 452, 453, 454, 456, 457, 458, 460, 463, 464, 465, 468, 470, 471, 472, 473, 474, 475, 476, 478, 479, 481, 482, 484, 486, 487, 493, 494, 495, 496, 498, 499, 500, 501, 502, 503, 504, 505, 506, 508, 509, 510, 511, 512, 513, 514, 515, 516, 518, 519, 520, 521, 524, 525, 526, 528, 529, 530, 531, 532, 533, 534, 535, 536, 537, 538, 540, 542, 544, 545, 546, 547, 549, 550, 551, 553, 554, 555, 557, 558, 559, 561, 562, 565, 567, 569, 570, 572, 574, 575, 577, 578, 579, 580, 582, 583, 584, 586, 587, 588, 591, 595, 596, 597, 598, 599, 600, 601, 602, 604, 606, 608, 609, 613, 614, 615, 616, 617, 618, 619, 621, 622, 623, 624, 625, 631, 633, 634, 635, 636, 638, 639, 640, 642, 643, 644, 645, 646, 647, 648, 649, 651, 652, 653, 654, 656, 657, 658, 659, 660, 661, 663, 664, 665, 666, 667, 668, 669, 670, 672, 673, 674, 675, 677, 678, 679, 684, 685, 686, 688, 692, 693, 694, 696, 700, 701, 702, 703, 704, 707, 710, 711, 712, 713, 714, 715, 716, 719, 720, 721, 723, 725, 727, 729, 730, 731, 732, 733, 734, 736, 737, 738, 739, 740, 742, 743, 745, 750, 751, 752, 753, 754, 755, 756, 757, 758, 759, 760, 761, 763, 768, 769, 771, 772, 773, 774, 776, 778, 779, 780, 783, 784, 785, 787, 788, 789, 791, 792, 794, 797, 799, 800, 801, 803, 805, 806, 808, 811, 812, 813, 814, 815, 817, 819, 834, 836, 837, 838, 839, 840, 842, 843, 845, 846, 848, 849, 852, 855, 858, 859, 861, 863, 864, 866, 867, 870, 873, 874, 875, 877, 878, 879, 880, 881, 882, 883, 885, 886, 887, 888, 889, 890, 892, 894, 895, 897, 898, 900, 907, 911, 912, 915, 916, 920, 921, 923, 925], "summary": {"covered_lines": 577, "num_statements": 604, "percent_covered": 93.5632183908046, "percent_covered_display": "94", "missing_lines": 27, "excluded_lines": 0, "percent_statements_covered": 95.52980132450331, "percent_statements_covered_display": "96", "num_branches": 266, "num_partial_branches": 19, "covered_branches": 237, "missing_branches": 29, "percent_branches_covered": 89.09774436090225, "percent_branches_covered_display": "89"}, "missing_lines": [80, 94, 95, 364, 390, 449, 610, 611, 637, 680, 681, 682, 909, 913, 918, 931, 932, 933, 935, 936, 938, 939, 940, 943, 944, 945, 947], "excluded_lines": [], "executed_branches": [[74, 75], [74, 85], [75, 76], [77, 78], [85, 86], [85, 100], [89, 90], [91, 92], [91, 97], [109, 110], [109, 116], [117, 118], [117, 120], [124, 125], [124, 135], [127, 128], [127, 144], [137, 138], [137, 140], [140, 141], [140, 144], [148, 149], [148, 165], [149, 150], [149, 151], [153, 154], [153, 162], [165, 166], [165, 173], [173, 175], [173, 182], [175, 176], [175, 179], [190, 191], [190, 206], [191, 192], [191, 201], [201, 202], [201, 206], [218, 219], [220, 221], [225, 226], [225, 242], [226, 225], [226, 227], [227, 225], [227, 228], [228, 225], [228, 230], [230, 231], [230, 240], [231, 232], [231, 238], [232, 233], [232, 236], [242, -214], [242, 243], [246, 247], [246, 257], [247, 248], [247, 254], [249, 250], [249, 251], [254, 246], [254, 255], [260, 261], [260, 283], [262, 269], [262, 273], [269, 260], [269, 270], [271, 272], [273, 260], [273, 275], [276, 260], [276, 278], [278, 276], [278, 279], [280, 281], [284, -214], [284, 285], [285, 286], [285, 288], [311, 312], [311, 314], [314, 315], [314, 325], [340, 341], [340, 343], [353, 354], [353, 356], [363, 368], [379, -377], [379, 380], [380, 381], [382, 383], [382, 386], [386, 387], [387, 388], [391, -377], [391, 392], [393, 394], [393, 396], [411, 412], [411, 416], [417, 418], [417, 423], [432, 433], [432, 435], [448, 452], [463, 464], [463, 468], [470, 471], [470, 478], [472, 473], [472, 478], [494, -493], [494, 495], [499, -498], [499, 500], [509, 510], [509, 518], [514, 515], [519, -508], [519, 520], [532, 533], [550, -549], [550, 551], [562, 565], [562, 567], [569, 570], [569, 572], [575, 577], [575, 586], [577, 578], [577, 582], [582, 583], [582, 586], [586, -561], [586, 587], [587, -561], [587, 588], [597, 598], [597, 604], [599, 600], [609, 613], [613, 614], [613, 617], [617, 618], [617, 621], [623, 624], [643, 644], [643, 647], [647, 648], [647, 651], [664, 665], [664, 668], [668, 669], [668, 672], [685, 686], [685, 688], [688, 692], [688, 700], [692, 693], [692, 696], [700, 701], [700, 707], [702, 703], [702, 707], [711, 712], [711, 715], [713, 714], [715, 716], [715, 719], [720, 721], [720, 723], [727, 729], [727, 797], [730, 731], [730, 736], [731, 732], [731, 742], [732, 733], [732, 734], [736, 737], [736, 742], [738, 739], [738, 740], [742, 743], [742, 745], [752, 753], [752, 778], [754, 755], [754, 776], [758, 759], [758, 763], [773, 774], [773, 778], [778, 779], [778, 783], [783, 784], [783, 787], [791, 792], [791, 794], [800, 801], [800, 803], [805, 806], [805, 808], [811, 812], [811, 817], [813, 814], [813, 819], [843, 845], [843, 852], [845, 846], [845, 848], [848, 849], [848, 861], [852, 855], [852, 858], [858, 859], [858, 861], [873, 874], [873, 885], [874, 875], [874, 877], [877, 878], [877, 881], [881, 882], [885, 886], [885, 887], [887, 888], [887, 889], [889, 890], [889, 892], [897, 898], [897, 900]], "missing_branches": [[75, 85], [77, 80], [89, 97], [218, -214], [220, -214], [271, 260], [280, 276], [363, 364], [380, -377], [386, -377], [387, 390], [448, 449], [514, -508], [532, 534], [599, -591], [609, 610], [623, 631], [713, 715], [881, 894], [932, 933], [932, 935], [935, 936], [935, 938], [939, 940], [939, 943], [943, 944], [943, 947], [944, 945], [944, 947]], "functions": {"async_migrate_entry": {"executed_lines": [74, 75, 76, 77, 78, 81, 82, 85, 86, 87, 88, 89, 90, 91, 92, 93, 97, 98, 100], "summary": {"covered_lines": 19, "num_statements": 22, "percent_covered": 82.3529411764706, "percent_covered_display": "82", "missing_lines": 3, "excluded_lines": 0, "percent_statements_covered": 86.36363636363636, "percent_statements_covered_display": "86", "num_branches": 12, "num_partial_branches": 3, "covered_branches": 9, "missing_branches": 3, "percent_branches_covered": 75.0, "percent_branches_covered_display": "75"}, "missing_lines": [80, 94, 95], "excluded_lines": [], "start_line": 73, "executed_branches": [[74, 75], [74, 85], [75, 76], [77, 78], [85, 86], [85, 100], [89, 90], [91, 92], [91, 97]], "missing_branches": [[75, 85], [77, 80], [89, 97]]}, "async_setup": {"executed_lines": [105], "summary": {"covered_lines": 1, "num_statements": 1, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 103, "executed_branches": [], "missing_branches": []}, "async_setup_entry": {"executed_lines": [109, 110, 111, 112, 113, 116, 117, 118, 120, 122, 123, 124, 125, 126, 127, 128, 129, 131, 132, 133, 135, 136, 137, 138, 140, 141, 144, 145, 148, 149, 150, 151, 153, 154, 155, 156, 158, 162, 165, 166, 173, 175, 176, 177, 179, 180, 182, 184], "summary": {"covered_lines": 48, "num_statements": 48, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 24, "num_partial_branches": 0, "covered_branches": 24, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 108, "executed_branches": [[109, 110], [109, 116], [117, 118], [117, 120], [124, 125], [124, 135], [127, 128], [127, 144], [137, 138], [137, 140], [140, 141], [140, 144], [148, 149], [148, 165], [149, 150], [149, 151], [153, 154], [153, 162], [165, 166], [165, 173], [173, 175], [173, 182], [175, 176], [175, 179]], "missing_branches": []}, "async_unload_entry": {"executed_lines": [188, 190, 191, 192, 193, 194, 195, 196, 197, 198, 201, 202, 203, 204, 206], "summary": {"covered_lines": 15, "num_statements": 15, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 6, "num_partial_branches": 0, "covered_branches": 6, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 187, "executed_branches": [[190, 191], [190, 206], [191, 192], [191, 201], [201, 202], [201, 206]], "missing_branches": []}, "entry_update_listener": {"executed_lines": [210, 211], "summary": {"covered_lines": 2, "num_statements": 2, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 209, "executed_branches": [], "missing_branches": []}, "check_and_write_to_16a": {"executed_lines": [216, 217, 218, 219, 220, 221, 224, 225, 226, 227, 228, 230, 231, 232, 233, 234, 236, 238, 240, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 254, 255, 257, 258, 259, 260, 261, 262, 269, 270, 271, 272, 273, 275, 276, 278, 279, 280, 281, 283, 284, 285, 286, 287, 288, 289, 290, 291], "summary": {"covered_lines": 58, "num_statements": 58, "percent_covered": 96.22641509433963, "percent_covered_display": "96", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 48, "num_partial_branches": 4, "covered_branches": 44, "missing_branches": 4, "percent_branches_covered": 91.66666666666667, "percent_branches_covered_display": "92"}, "missing_lines": [], "excluded_lines": [], "start_line": 215, "executed_branches": [[218, 219], [220, 221], [225, 226], [225, 242], [226, 225], [226, 227], [227, 225], [227, 228], [228, 225], [228, 230], [230, 231], [230, 240], [231, 232], [231, 238], [232, 233], [232, 236], [242, -214], [242, 243], [246, 247], [246, 257], [247, 248], [247, 254], [249, 250], [249, 251], [254, 246], [254, 255], [260, 261], [260, 283], [262, 269], [262, 273], [269, 260], [269, 270], [271, 272], [273, 260], [273, 275], [276, 260], [276, 278], [278, 276], [278, 279], [280, 281], [284, -214], [284, 285], [285, 286], [285, 288]], "missing_branches": [[218, -214], [220, -214], [271, 260], [280, 276]]}, "GoeChargerDataUpdateCoordinator.__init__": {"executed_lines": [301, 302, 303, 304, 306, 307, 310, 311, 312, 314, 315, 316, 325, 326, 336, 337, 339, 340, 341, 343, 346, 347, 348, 349, 350, 351, 352, 353, 354, 356, 358, 359, 360, 361, 362, 363, 368, 371, 374, 375], "summary": {"covered_lines": 40, "num_statements": 41, "percent_covered": 96.07843137254902, "percent_covered_display": "96", "missing_lines": 1, "excluded_lines": 0, "percent_statements_covered": 97.5609756097561, "percent_statements_covered_display": "98", "num_branches": 10, "num_partial_branches": 1, "covered_branches": 9, "missing_branches": 1, "percent_branches_covered": 90.0, "percent_branches_covered_display": "90"}, "missing_lines": [364], "excluded_lines": [], "start_line": 300, "executed_branches": [[311, 312], [311, 314], [314, 315], [314, 325], [340, 341], [340, 343], [353, 354], [353, 356], [363, 368]], "missing_branches": [[363, 364]]}, "GoeChargerDataUpdateCoordinator.call_later_update_device_registry": {"executed_lines": [378, 379, 380, 381, 382, 383, 384, 386, 387, 388, 391, 392, 393, 394, 396, 398], "summary": {"covered_lines": 16, "num_statements": 17, "percent_covered": 87.09677419354838, "percent_covered_display": "87", "missing_lines": 1, "excluded_lines": 0, "percent_statements_covered": 94.11764705882354, "percent_statements_covered_display": "94", "num_branches": 14, "num_partial_branches": 3, "covered_branches": 11, "missing_branches": 3, "percent_branches_covered": 78.57142857142857, "percent_branches_covered_display": "79"}, "missing_lines": [390], "excluded_lines": [], "start_line": 377, "executed_branches": [[379, -377], [379, 380], [380, 381], [382, 383], [382, 386], [386, 387], [387, 388], [391, -377], [391, 392], [393, 394], [393, 396]], "missing_branches": [[380, -377], [386, -377], [387, 390]]}, "GoeChargerDataUpdateCoordinator.cards_as_object_present": {"executed_lines": [404], "summary": {"covered_lines": 1, "num_statements": 1, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 403, "executed_branches": [], "missing_branches": []}, "GoeChargerDataUpdateCoordinator.force_async_update_now": {"executed_lines": [410, 411, 412, 413, 416, 417, 418, 419, 423, 424, 425, 428, 431, 432, 433, 435], "summary": {"covered_lines": 16, "num_statements": 16, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 6, "num_partial_branches": 0, "covered_branches": 6, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 406, "executed_branches": [[411, 412], [411, 416], [417, 418], [417, 423], [432, 433], [432, 435]], "missing_branches": []}, "GoeChargerDataUpdateCoordinator.start_websocket_and_wait_for_first_data": {"executed_lines": [438, 442, 445, 446, 448, 452, 453, 454, 456, 457, 458, 460, 463, 464, 465, 468, 470, 471, 472, 473, 474, 475, 476, 478, 479, 481, 482], "summary": {"covered_lines": 27, "num_statements": 28, "percent_covered": 94.44444444444444, "percent_covered_display": "94", "missing_lines": 1, "excluded_lines": 0, "percent_statements_covered": 96.42857142857143, "percent_statements_covered_display": "96", "num_branches": 8, "num_partial_branches": 1, "covered_branches": 7, "missing_branches": 1, "percent_branches_covered": 87.5, "percent_branches_covered_display": "88"}, "missing_lines": [449], "excluded_lines": [], "start_line": 437, "executed_branches": [[448, 452], [463, 464], [463, 468], [470, 471], [470, 478], [472, 473], [472, 478]], "missing_branches": [[448, 449]]}, "GoeChargerDataUpdateCoordinator.start_watchdog": {"executed_lines": [486, 487], "summary": {"covered_lines": 2, "num_statements": 2, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 484, "executed_branches": [], "missing_branches": []}, "GoeChargerDataUpdateCoordinator.stop_watchdog": {"executed_lines": [494, 495, 496], "summary": {"covered_lines": 3, "num_statements": 3, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 2, "num_partial_branches": 0, "covered_branches": 2, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 493, "executed_branches": [[494, -493], [494, 495]], "missing_branches": []}, "GoeChargerDataUpdateCoordinator._check_for_ws_task_and_cancel_if_running": {"executed_lines": [499, 500, 501, 502, 503, 504, 505, 506], "summary": {"covered_lines": 8, "num_statements": 8, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 2, "num_partial_branches": 0, "covered_branches": 2, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 498, "executed_branches": [[499, -498], [499, 500]], "missing_branches": []}, "GoeChargerDataUpdateCoordinator._async_watchdog_check": {"executed_lines": [509, 510, 511, 512, 513, 514, 515, 516, 518, 519, 520, 521], "summary": {"covered_lines": 12, "num_statements": 12, "percent_covered": 94.44444444444444, "percent_covered_display": "94", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 6, "num_partial_branches": 1, "covered_branches": 5, "missing_branches": 1, "percent_branches_covered": 83.33333333333333, "percent_branches_covered_display": "83"}, "missing_lines": [], "excluded_lines": [], "start_line": 508, "executed_branches": [[509, 510], [509, 518], [514, 515], [519, -508], [519, 520]], "missing_branches": [[514, -508]]}, "GoeChargerDataUpdateCoordinator.__call__": {"executed_lines": [525, 526], "summary": {"covered_lines": 2, "num_statements": 2, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 524, "executed_branches": [], "missing_branches": []}, "GoeChargerDataUpdateCoordinator.clear_data": {"executed_lines": [529, 530, 531, 532, 533, 534, 535, 536, 537, 538], "summary": {"covered_lines": 10, "num_statements": 10, "percent_covered": 91.66666666666667, "percent_covered_display": "92", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 2, "num_partial_branches": 1, "covered_branches": 1, "missing_branches": 1, "percent_branches_covered": 50.0, "percent_branches_covered_display": "50"}, "missing_lines": [], "excluded_lines": [], "start_line": 528, "executed_branches": [[532, 533]], "missing_branches": [[532, 534]]}, "GoeChargerDataUpdateCoordinator.trigger_restart_delayed": {"executed_lines": [542, 544, 545, 546, 547], "summary": {"covered_lines": 5, "num_statements": 5, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 540, "executed_branches": [], "missing_branches": []}, "GoeChargerDataUpdateCoordinator.cancel_existing_debouncer": {"executed_lines": [550, 551], "summary": {"covered_lines": 2, "num_statements": 2, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 2, "num_partial_branches": 0, "covered_branches": 2, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 549, "executed_branches": [[550, -549], [550, 551]], "missing_branches": []}, "GoeChargerDataUpdateCoordinator.request_update_in_sec": {"executed_lines": [554, 555], "summary": {"covered_lines": 2, "num_statements": 2, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 553, "executed_branches": [], "missing_branches": []}, "GoeChargerDataUpdateCoordinator._debounce_coordinator_update": {"executed_lines": [558, 559], "summary": {"covered_lines": 2, "num_statements": 2, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 557, "executed_branches": [], "missing_branches": []}, "GoeChargerDataUpdateCoordinator.handle_write_result": {"executed_lines": [562, 565, 567, 569, 570, 572, 574, 575, 577, 578, 579, 580, 582, 583, 584, 586, 587, 588], "summary": {"covered_lines": 18, "num_statements": 18, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 14, "num_partial_branches": 0, "covered_branches": 14, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 561, "executed_branches": [[562, 565], [562, 567], [569, 570], [569, 572], [575, 577], [575, 586], [577, 578], [577, 582], [582, 583], [582, 586], [586, -561], [586, 587], [587, -561], [587, 588]], "missing_branches": []}, "GoeChargerDataUpdateCoordinator._handle_client_connection_error": {"executed_lines": [595, 596, 597, 598, 599, 600, 601, 602, 604], "summary": {"covered_lines": 9, "num_statements": 9, "percent_covered": 92.3076923076923, "percent_covered_display": "92", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 4, "num_partial_branches": 1, "covered_branches": 3, "missing_branches": 1, "percent_branches_covered": 75.0, "percent_branches_covered_display": "75"}, "missing_lines": [], "excluded_lines": [], "start_line": 591, "executed_branches": [[597, 598], [597, 604], [599, 600]], "missing_branches": [[599, -591]]}, "GoeChargerDataUpdateCoordinator._async_update_data": {"executed_lines": [608, 609, 613, 614, 615, 616, 617, 618, 619, 621, 622, 623, 624, 625, 631, 633, 634, 635, 636, 638, 639, 640], "summary": {"covered_lines": 22, "num_statements": 25, "percent_covered": 84.84848484848484, "percent_covered_display": "85", "missing_lines": 3, "excluded_lines": 0, "percent_statements_covered": 88.0, "percent_statements_covered_display": "88", "num_branches": 8, "num_partial_branches": 2, "covered_branches": 6, "missing_branches": 2, "percent_branches_covered": 75.0, "percent_branches_covered_display": "75"}, "missing_lines": [610, 611, 637], "excluded_lines": [], "start_line": 606, "executed_branches": [[609, 613], [613, 614], [613, 617], [617, 618], [617, 621], [623, 624]], "missing_branches": [[609, 610], [623, 631]]}, "GoeChargerDataUpdateCoordinator.async_write_key": {"executed_lines": [643, 644, 645, 646, 647, 648, 649, 651, 652, 653, 654, 656, 657, 658, 659, 660, 661], "summary": {"covered_lines": 17, "num_statements": 17, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 4, "num_partial_branches": 0, "covered_branches": 4, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 642, "executed_branches": [[643, 644], [643, 647], [647, 648], [647, 651]], "missing_branches": []}, "GoeChargerDataUpdateCoordinator.async_write_multiple_keys": {"executed_lines": [664, 665, 666, 667, 668, 669, 670, 672, 673, 674, 675, 677, 678, 679], "summary": {"covered_lines": 14, "num_statements": 17, "percent_covered": 85.71428571428571, "percent_covered_display": "86", "missing_lines": 3, "excluded_lines": 0, "percent_statements_covered": 82.3529411764706, "percent_statements_covered_display": "82", "num_branches": 4, "num_partial_branches": 0, "covered_branches": 4, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [680, 681, 682], "excluded_lines": [], "start_line": 663, "executed_branches": [[664, 665], [664, 668], [668, 669], [668, 672]], "missing_branches": []}, "GoeChargerDataUpdateCoordinator.read_versions": {"executed_lines": [685, 686, 688, 692, 693, 694, 696, 700, 701, 702, 703, 704, 707, 710, 711, 712, 713, 714, 715, 716, 719, 720, 721, 723, 725, 727, 729, 730, 731, 732, 733, 734, 736, 737, 738, 739, 740, 742, 743, 745, 750, 751, 752, 753, 754, 755, 756, 757, 758, 759, 760, 761, 763, 768, 769, 771, 772, 773, 774, 776, 778, 779, 780, 783, 784, 785, 787, 788, 789, 791, 792, 794, 797, 799, 800, 801, 803, 805, 806, 808, 811, 812, 813, 814, 815, 817, 819, 834], "summary": {"covered_lines": 88, "num_statements": 88, "percent_covered": 99.29577464788733, "percent_covered_display": "99", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 54, "num_partial_branches": 1, "covered_branches": 53, "missing_branches": 1, "percent_branches_covered": 98.14814814814815, "percent_branches_covered_display": "98"}, "missing_lines": [], "excluded_lines": [], "start_line": 684, "executed_branches": [[685, 686], [685, 688], [688, 692], [688, 700], [692, 693], [692, 696], [700, 701], [700, 707], [702, 703], [702, 707], [711, 712], [711, 715], [713, 714], [715, 716], [715, 719], [720, 721], [720, 723], [727, 729], [727, 797], [730, 731], [730, 736], [731, 732], [731, 742], [732, 733], [732, 734], [736, 737], [736, 742], [738, 739], [738, 740], [742, 743], [742, 745], [752, 753], [752, 778], [754, 755], [754, 776], [758, 759], [758, 763], [773, 774], [773, 778], [778, 779], [778, 783], [783, 784], [783, 787], [791, 792], [791, 794], [800, 801], [800, 803], [805, 806], [805, 808], [811, 812], [811, 817], [813, 814], [813, 819]], "missing_branches": [[713, 715]]}, "GoeChargerDataUpdateCoordinator.check_for_16a_limit": {"executed_lines": [837, 838, 839, 840], "summary": {"covered_lines": 4, "num_statements": 4, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 836, "executed_branches": [], "missing_branches": []}, "GoeChargerDataUpdateCoordinator.is_valid_charger_entity": {"executed_lines": [843, 845, 846, 848, 849, 852, 855, 858, 859, 861], "summary": {"covered_lines": 10, "num_statements": 10, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 10, "num_partial_branches": 0, "covered_branches": 10, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 842, "executed_branches": [[843, 845], [843, 852], [845, 846], [845, 848], [848, 849], [848, 861], [852, 855], [852, 858], [858, 859], [858, 861]], "missing_branches": []}, "GoeChargerBaseEntity.__init__": {"executed_lines": [867, 870, 873, 874, 875, 877, 878, 879, 880, 881, 882, 883, 885, 886, 887, 888, 889, 890, 892, 894, 895, 897, 898, 900], "summary": {"covered_lines": 24, "num_statements": 24, "percent_covered": 97.5, "percent_covered_display": "98", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 16, "num_partial_branches": 1, "covered_branches": 15, "missing_branches": 1, "percent_branches_covered": 93.75, "percent_branches_covered_display": "94"}, "missing_lines": [], "excluded_lines": [], "start_line": 866, "executed_branches": [[873, 874], [873, 885], [874, 875], [874, 877], [877, 878], [877, 881], [881, 882], [885, 886], [885, 887], [887, 888], [887, 889], [889, 890], [889, 892], [897, 898], [897, 900]], "missing_branches": [[881, 894]]}, "GoeChargerBaseEntity._name_internal": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 1, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 1, "excluded_lines": 0, "percent_statements_covered": 0.0, "percent_statements_covered_display": "0", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [909], "excluded_lines": [], "start_line": 907, "executed_branches": [], "missing_branches": []}, "GoeChargerBaseEntity.device_info": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 1, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 1, "excluded_lines": 0, "percent_statements_covered": 0.0, "percent_statements_covered_display": "0", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [913], "excluded_lines": [], "start_line": 912, "executed_branches": [], "missing_branches": []}, "GoeChargerBaseEntity.available": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 1, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 1, "excluded_lines": 0, "percent_statements_covered": 0.0, "percent_statements_covered_display": "0", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [918], "excluded_lines": [], "start_line": 916, "executed_branches": [], "missing_branches": []}, "GoeChargerBaseEntity.unique_id": {"executed_lines": [923], "summary": {"covered_lines": 1, "num_statements": 1, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 921, "executed_branches": [], "missing_branches": []}, "GoeChargerBaseEntity._friendly_name_internal": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 12, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 12, "excluded_lines": 0, "percent_statements_covered": 0.0, "percent_statements_covered_display": "0", "num_branches": 10, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 10, "percent_branches_covered": 0.0, "percent_branches_covered_display": "0"}, "missing_lines": [931, 932, 933, 935, 936, 938, 939, 940, 943, 944, 945, 947], "excluded_lines": [], "start_line": 925, "executed_branches": [], "missing_branches": [[932, 933], [932, 935], [935, 936], [935, 938], [939, 940], [939, 943], [943, 944], [943, 947], [944, 945], [944, 947]]}, "": {"executed_lines": [1, 2, 3, 4, 5, 6, 7, 9, 10, 11, 12, 23, 24, 25, 30, 31, 32, 33, 34, 35, 36, 38, 45, 46, 60, 61, 63, 65, 66, 67, 68, 69, 70, 71, 73, 103, 108, 187, 209, 214, 215, 294, 296, 297, 298, 300, 377, 403, 406, 437, 484, 493, 498, 508, 524, 528, 540, 549, 553, 557, 561, 591, 606, 642, 663, 684, 836, 842, 863, 864, 866, 907, 911, 912, 915, 916, 920, 921, 925], "summary": {"covered_lines": 79, "num_statements": 79, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 1, "executed_branches": [], "missing_branches": []}}, "classes": {"GoeChargerDataUpdateCoordinator": {"executed_lines": [301, 302, 303, 304, 306, 307, 310, 311, 312, 314, 315, 316, 325, 326, 336, 337, 339, 340, 341, 343, 346, 347, 348, 349, 350, 351, 352, 353, 354, 356, 358, 359, 360, 361, 362, 363, 368, 371, 374, 375, 378, 379, 380, 381, 382, 383, 384, 386, 387, 388, 391, 392, 393, 394, 396, 398, 404, 410, 411, 412, 413, 416, 417, 418, 419, 423, 424, 425, 428, 431, 432, 433, 435, 438, 442, 445, 446, 448, 452, 453, 454, 456, 457, 458, 460, 463, 464, 465, 468, 470, 471, 472, 473, 474, 475, 476, 478, 479, 481, 482, 486, 487, 494, 495, 496, 499, 500, 501, 502, 503, 504, 505, 506, 509, 510, 511, 512, 513, 514, 515, 516, 518, 519, 520, 521, 525, 526, 529, 530, 531, 532, 533, 534, 535, 536, 537, 538, 542, 544, 545, 546, 547, 550, 551, 554, 555, 558, 559, 562, 565, 567, 569, 570, 572, 574, 575, 577, 578, 579, 580, 582, 583, 584, 586, 587, 588, 595, 596, 597, 598, 599, 600, 601, 602, 604, 608, 609, 613, 614, 615, 616, 617, 618, 619, 621, 622, 623, 624, 625, 631, 633, 634, 635, 636, 638, 639, 640, 643, 644, 645, 646, 647, 648, 649, 651, 652, 653, 654, 656, 657, 658, 659, 660, 661, 664, 665, 666, 667, 668, 669, 670, 672, 673, 674, 675, 677, 678, 679, 685, 686, 688, 692, 693, 694, 696, 700, 701, 702, 703, 704, 707, 710, 711, 712, 713, 714, 715, 716, 719, 720, 721, 723, 725, 727, 729, 730, 731, 732, 733, 734, 736, 737, 738, 739, 740, 742, 743, 745, 750, 751, 752, 753, 754, 755, 756, 757, 758, 759, 760, 761, 763, 768, 769, 771, 772, 773, 774, 776, 778, 779, 780, 783, 784, 785, 787, 788, 789, 791, 792, 794, 797, 799, 800, 801, 803, 805, 806, 808, 811, 812, 813, 814, 815, 817, 819, 834, 837, 838, 839, 840, 843, 845, 846, 848, 849, 852, 855, 858, 859, 861], "summary": {"covered_lines": 330, "num_statements": 339, "percent_covered": 95.91002044989776, "percent_covered_display": "96", "missing_lines": 9, "excluded_lines": 0, "percent_statements_covered": 97.34513274336283, "percent_statements_covered_display": "97", "num_branches": 150, "num_partial_branches": 11, "covered_branches": 139, "missing_branches": 11, "percent_branches_covered": 92.66666666666667, "percent_branches_covered_display": "93"}, "missing_lines": [364, 390, 449, 610, 611, 637, 680, 681, 682], "excluded_lines": [], "start_line": 294, "executed_branches": [[311, 312], [311, 314], [314, 315], [314, 325], [340, 341], [340, 343], [353, 354], [353, 356], [363, 368], [379, -377], [379, 380], [380, 381], [382, 383], [382, 386], [386, 387], [387, 388], [391, -377], [391, 392], [393, 394], [393, 396], [411, 412], [411, 416], [417, 418], [417, 423], [432, 433], [432, 435], [448, 452], [463, 464], [463, 468], [470, 471], [470, 478], [472, 473], [472, 478], [494, -493], [494, 495], [499, -498], [499, 500], [509, 510], [509, 518], [514, 515], [519, -508], [519, 520], [532, 533], [550, -549], [550, 551], [562, 565], [562, 567], [569, 570], [569, 572], [575, 577], [575, 586], [577, 578], [577, 582], [582, 583], [582, 586], [586, -561], [586, 587], [587, -561], [587, 588], [597, 598], [597, 604], [599, 600], [609, 613], [613, 614], [613, 617], [617, 618], [617, 621], [623, 624], [643, 644], [643, 647], [647, 648], [647, 651], [664, 665], [664, 668], [668, 669], [668, 672], [685, 686], [685, 688], [688, 692], [688, 700], [692, 693], [692, 696], [700, 701], [700, 707], [702, 703], [702, 707], [711, 712], [711, 715], [713, 714], [715, 716], [715, 719], [720, 721], [720, 723], [727, 729], [727, 797], [730, 731], [730, 736], [731, 732], [731, 742], [732, 733], [732, 734], [736, 737], [736, 742], [738, 739], [738, 740], [742, 743], [742, 745], [752, 753], [752, 778], [754, 755], [754, 776], [758, 759], [758, 763], [773, 774], [773, 778], [778, 779], [778, 783], [783, 784], [783, 787], [791, 792], [791, 794], [800, 801], [800, 803], [805, 806], [805, 808], [811, 812], [811, 817], [813, 814], [813, 819], [843, 845], [843, 852], [845, 846], [845, 848], [848, 849], [848, 861], [852, 855], [852, 858], [858, 859], [858, 861]], "missing_branches": [[363, 364], [380, -377], [386, -377], [387, 390], [448, 449], [514, -508], [532, 534], [599, -591], [609, 610], [623, 631], [713, 715]]}, "GoeChargerBaseEntity": {"executed_lines": [867, 870, 873, 874, 875, 877, 878, 879, 880, 881, 882, 883, 885, 886, 887, 888, 889, 890, 892, 894, 895, 897, 898, 900, 923], "summary": {"covered_lines": 25, "num_statements": 40, "percent_covered": 60.60606060606061, "percent_covered_display": "61", "missing_lines": 15, "excluded_lines": 0, "percent_statements_covered": 62.5, "percent_statements_covered_display": "62", "num_branches": 26, "num_partial_branches": 1, "covered_branches": 15, "missing_branches": 11, "percent_branches_covered": 57.69230769230769, "percent_branches_covered_display": "58"}, "missing_lines": [909, 913, 918, 931, 932, 933, 935, 936, 938, 939, 940, 943, 944, 945, 947], "excluded_lines": [], "start_line": 863, "executed_branches": [[873, 874], [873, 885], [874, 875], [874, 877], [877, 878], [877, 881], [881, 882], [885, 886], [885, 887], [887, 888], [887, 889], [889, 890], [889, 892], [897, 898], [897, 900]], "missing_branches": [[881, 894], [932, 933], [932, 935], [935, 936], [935, 938], [939, 940], [939, 943], [943, 944], [943, 947], [944, 945], [944, 947]]}, "": {"executed_lines": [1, 2, 3, 4, 5, 6, 7, 9, 10, 11, 12, 23, 24, 25, 30, 31, 32, 33, 34, 35, 36, 38, 45, 46, 60, 61, 63, 65, 66, 67, 68, 69, 70, 71, 73, 74, 75, 76, 77, 78, 81, 82, 85, 86, 87, 88, 89, 90, 91, 92, 93, 97, 98, 100, 103, 105, 108, 109, 110, 111, 112, 113, 116, 117, 118, 120, 122, 123, 124, 125, 126, 127, 128, 129, 131, 132, 133, 135, 136, 137, 138, 140, 141, 144, 145, 148, 149, 150, 151, 153, 154, 155, 156, 158, 162, 165, 166, 173, 175, 176, 177, 179, 180, 182, 184, 187, 188, 190, 191, 192, 193, 194, 195, 196, 197, 198, 201, 202, 203, 204, 206, 209, 210, 211, 214, 215, 216, 217, 218, 219, 220, 221, 224, 225, 226, 227, 228, 230, 231, 232, 233, 234, 236, 238, 240, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 254, 255, 257, 258, 259, 260, 261, 262, 269, 270, 271, 272, 273, 275, 276, 278, 279, 280, 281, 283, 284, 285, 286, 287, 288, 289, 290, 291, 294, 296, 297, 298, 300, 377, 403, 406, 437, 484, 493, 498, 508, 524, 528, 540, 549, 553, 557, 561, 591, 606, 642, 663, 684, 836, 842, 863, 864, 866, 907, 911, 912, 915, 916, 920, 921, 925], "summary": {"covered_lines": 222, "num_statements": 225, "percent_covered": 96.82539682539682, "percent_covered_display": "97", "missing_lines": 3, "excluded_lines": 0, "percent_statements_covered": 98.66666666666667, "percent_statements_covered_display": "99", "num_branches": 90, "num_partial_branches": 7, "covered_branches": 83, "missing_branches": 7, "percent_branches_covered": 92.22222222222223, "percent_branches_covered_display": "92"}, "missing_lines": [80, 94, 95], "excluded_lines": [], "start_line": 1, "executed_branches": [[74, 75], [74, 85], [75, 76], [77, 78], [85, 86], [85, 100], [89, 90], [91, 92], [91, 97], [109, 110], [109, 116], [117, 118], [117, 120], [124, 125], [124, 135], [127, 128], [127, 144], [137, 138], [137, 140], [140, 141], [140, 144], [148, 149], [148, 165], [149, 150], [149, 151], [153, 154], [153, 162], [165, 166], [165, 173], [173, 175], [173, 182], [175, 176], [175, 179], [190, 191], [190, 206], [191, 192], [191, 201], [201, 202], [201, 206], [218, 219], [220, 221], [225, 226], [225, 242], [226, 225], [226, 227], [227, 225], [227, 228], [228, 225], [228, 230], [230, 231], [230, 240], [231, 232], [231, 238], [232, 233], [232, 236], [242, -214], [242, 243], [246, 247], [246, 257], [247, 248], [247, 254], [249, 250], [249, 251], [254, 246], [254, 255], [260, 261], [260, 283], [262, 269], [262, 273], [269, 260], [269, 270], [271, 272], [273, 260], [273, 275], [276, 260], [276, 278], [278, 276], [278, 279], [280, 281], [284, -214], [284, 285], [285, 286], [285, 288]], "missing_branches": [[75, 85], [77, 80], [89, 97], [218, -214], [220, -214], [271, 260], [280, 276]]}}}, "custom_components/goecharger_api2/binary_sensor.py": {"executed_lines": [1, 3, 4, 5, 6, 7, 9, 10, 11, 12, 15, 18, 36, 37, 38, 39, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 53, 54, 56, 58, 61, 62, 63, 64, 66, 67, 68, 69, 70, 73, 74, 75, 76, 77, 78, 80, 81, 83, 84, 86, 88, 90, 92, 93, 95, 96, 98], "summary": {"covered_lines": 58, "num_statements": 71, "percent_covered": 77.14285714285714, "percent_covered_display": "77", "missing_lines": 13, "excluded_lines": 0, "percent_statements_covered": 81.69014084507042, "percent_statements_covered_display": "82", "num_branches": 34, "num_partial_branches": 3, "covered_branches": 23, "missing_branches": 11, "percent_branches_covered": 67.6470588235294, "percent_branches_covered_display": "68"}, "missing_lines": [19, 20, 21, 23, 24, 25, 26, 27, 29, 30, 31, 33, 72], "excluded_lines": [], "executed_branches": [[45, 46], [46, 47], [46, 61], [47, 48], [47, 51], [51, 53], [51, 58], [53, 54], [53, 56], [61, 62], [63, 64], [63, 80], [67, 68], [67, 69], [69, 70], [80, 81], [80, 90], [81, 83], [81, 88], [83, 84], [83, 86], [95, 96], [95, 98]], "missing_branches": [[23, 24], [23, 29], [24, 25], [24, 33], [25, 24], [25, 26], [29, 30], [29, 33], [45, 80], [61, 63], [69, 72]], "functions": {"async_setup_entry": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 12, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 12, "excluded_lines": 0, "percent_statements_covered": 0.0, "percent_statements_covered_display": "0", "num_branches": 8, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 8, "percent_branches_covered": 0.0, "percent_branches_covered_display": "0"}, "missing_lines": [19, 20, 21, 23, 24, 25, 26, 27, 29, 30, 31, 33], "excluded_lines": [], "start_line": 18, "executed_branches": [], "missing_branches": [[23, 24], [23, 29], [24, 25], [24, 33], [25, 24], [25, 26], [29, 30], [29, 33]]}, "GoeChargerApiV2BinarySensor.__init__": {"executed_lines": [38, 39], "summary": {"covered_lines": 2, "num_statements": 2, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 37, "executed_branches": [], "missing_branches": []}, "GoeChargerApiV2BinarySensor.is_on": {"executed_lines": [43, 44, 45, 46, 47, 48, 49, 50, 51, 53, 54, 56, 58, 61, 62, 63, 64, 66, 67, 68, 69, 70, 73, 74, 75, 76, 77, 78, 80, 81, 83, 84, 86, 88, 90], "summary": {"covered_lines": 35, "num_statements": 36, "percent_covered": 93.33333333333333, "percent_covered_display": "93", "missing_lines": 1, "excluded_lines": 0, "percent_statements_covered": 97.22222222222223, "percent_statements_covered_display": "97", "num_branches": 24, "num_partial_branches": 3, "covered_branches": 21, "missing_branches": 3, "percent_branches_covered": 87.5, "percent_branches_covered_display": "88"}, "missing_lines": [72], "excluded_lines": [], "start_line": 42, "executed_branches": [[45, 46], [46, 47], [46, 61], [47, 48], [47, 51], [51, 53], [51, 58], [53, 54], [53, 56], [61, 62], [63, 64], [63, 80], [67, 68], [67, 69], [69, 70], [80, 81], [80, 90], [81, 83], [81, 88], [83, 84], [83, 86]], "missing_branches": [[45, 80], [61, 63], [69, 72]]}, "GoeChargerApiV2BinarySensor.icon": {"executed_lines": [95, 96, 98], "summary": {"covered_lines": 3, "num_statements": 3, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 2, "num_partial_branches": 0, "covered_branches": 2, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 93, "executed_branches": [[95, 96], [95, 98]], "missing_branches": []}, "": {"executed_lines": [1, 3, 4, 5, 6, 7, 9, 10, 11, 12, 15, 18, 36, 37, 41, 42, 92, 93], "summary": {"covered_lines": 18, "num_statements": 18, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 1, "executed_branches": [], "missing_branches": []}}, "classes": {"GoeChargerApiV2BinarySensor": {"executed_lines": [38, 39, 43, 44, 45, 46, 47, 48, 49, 50, 51, 53, 54, 56, 58, 61, 62, 63, 64, 66, 67, 68, 69, 70, 73, 74, 75, 76, 77, 78, 80, 81, 83, 84, 86, 88, 90, 95, 96, 98], "summary": {"covered_lines": 40, "num_statements": 41, "percent_covered": 94.02985074626865, "percent_covered_display": "94", "missing_lines": 1, "excluded_lines": 0, "percent_statements_covered": 97.5609756097561, "percent_statements_covered_display": "98", "num_branches": 26, "num_partial_branches": 3, "covered_branches": 23, "missing_branches": 3, "percent_branches_covered": 88.46153846153847, "percent_branches_covered_display": "88"}, "missing_lines": [72], "excluded_lines": [], "start_line": 36, "executed_branches": [[45, 46], [46, 47], [46, 61], [47, 48], [47, 51], [51, 53], [51, 58], [53, 54], [53, 56], [61, 62], [63, 64], [63, 80], [67, 68], [67, 69], [69, 70], [80, 81], [80, 90], [81, 83], [81, 88], [83, 84], [83, 86], [95, 96], [95, 98]], "missing_branches": [[45, 80], [61, 63], [69, 72]]}, "": {"executed_lines": [1, 3, 4, 5, 6, 7, 9, 10, 11, 12, 15, 18, 36, 37, 41, 42, 92, 93], "summary": {"covered_lines": 18, "num_statements": 30, "percent_covered": 47.36842105263158, "percent_covered_display": "47", "missing_lines": 12, "excluded_lines": 0, "percent_statements_covered": 60.0, "percent_statements_covered_display": "60", "num_branches": 8, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 8, "percent_branches_covered": 0.0, "percent_branches_covered_display": "0"}, "missing_lines": [19, 20, 21, 23, 24, 25, 26, 27, 29, 30, 31, 33], "excluded_lines": [], "start_line": 1, "executed_branches": [], "missing_branches": [[23, 24], [23, 29], [24, 25], [24, 33], [25, 24], [25, 26], [29, 30], [29, 33]]}}}, "custom_components/goecharger_api2/button.py": {"executed_lines": [1, 3, 4, 5, 6, 7, 9, 10, 11, 13, 16, 34, 35, 36, 38, 39, 40, 41, 42], "summary": {"covered_lines": 19, "num_statements": 31, "percent_covered": 48.717948717948715, "percent_covered_display": "49", "missing_lines": 12, "excluded_lines": 0, "percent_statements_covered": 61.29032258064516, "percent_statements_covered_display": "61", "num_branches": 8, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 8, "percent_branches_covered": 0.0, "percent_branches_covered_display": "0"}, "missing_lines": [17, 18, 19, 21, 22, 23, 24, 25, 27, 28, 29, 31], "excluded_lines": [], "executed_branches": [], "missing_branches": [[21, 22], [21, 27], [22, 23], [22, 31], [23, 22], [23, 24], [27, 28], [27, 31]], "functions": {"async_setup_entry": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 12, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 12, "excluded_lines": 0, "percent_statements_covered": 0.0, "percent_statements_covered_display": "0", "num_branches": 8, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 8, "percent_branches_covered": 0.0, "percent_branches_covered_display": "0"}, "missing_lines": [17, 18, 19, 21, 22, 23, 24, 25, 27, 28, 29, 31], "excluded_lines": [], "start_line": 16, "executed_branches": [], "missing_branches": [[21, 22], [21, 27], [22, 23], [22, 31], [23, 22], [23, 24], [27, 28], [27, 31]]}, "GoeChargerApiV2Button.__init__": {"executed_lines": [36], "summary": {"covered_lines": 1, "num_statements": 1, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 35, "executed_branches": [], "missing_branches": []}, "GoeChargerApiV2Button.async_press": {"executed_lines": [39, 40, 41, 42], "summary": {"covered_lines": 4, "num_statements": 4, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 38, "executed_branches": [], "missing_branches": []}, "": {"executed_lines": [1, 3, 4, 5, 6, 7, 9, 10, 11, 13, 16, 34, 35, 38], "summary": {"covered_lines": 14, "num_statements": 14, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 1, "executed_branches": [], "missing_branches": []}}, "classes": {"GoeChargerApiV2Button": {"executed_lines": [36, 39, 40, 41, 42], "summary": {"covered_lines": 5, "num_statements": 5, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 34, "executed_branches": [], "missing_branches": []}, "": {"executed_lines": [1, 3, 4, 5, 6, 7, 9, 10, 11, 13, 16, 34, 35, 38], "summary": {"covered_lines": 14, "num_statements": 26, "percent_covered": 41.1764705882353, "percent_covered_display": "41", "missing_lines": 12, "excluded_lines": 0, "percent_statements_covered": 53.84615384615385, "percent_statements_covered_display": "54", "num_branches": 8, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 8, "percent_branches_covered": 0.0, "percent_branches_covered_display": "0"}, "missing_lines": [17, 18, 19, 21, 22, 23, 24, 25, 27, 28, 29, 31], "excluded_lines": [], "start_line": 1, "executed_branches": [], "missing_branches": [[21, 22], [21, 27], [22, 23], [22, 31], [23, 22], [23, 24], [27, 28], [27, 31]]}}}, "custom_components/goecharger_api2/config_flow.py": {"executed_lines": [1, 2, 3, 4, 6, 7, 8, 9, 10, 21, 22, 24, 33, 39, 41, 43, 44, 47, 50, 51, 52, 54, 56, 58, 59, 60, 62, 63, 64, 66, 67, 68, 70, 71, 72, 74, 75, 76, 78, 79, 80, 81, 82, 83, 84, 85, 87, 88, 89, 90, 91, 92, 93, 94, 96, 97, 98, 99, 101, 102, 105, 107, 111, 129, 131, 137, 138, 139, 140, 141, 143, 144, 145, 146, 147, 148, 149, 151, 152, 154, 155, 156, 158, 160, 161, 163, 172, 196, 198, 204, 205, 207, 208, 209, 211, 212, 213, 214, 215, 216, 217, 219, 220, 224, 225, 226, 228, 230, 231, 233, 242, 268, 271, 275, 276, 277, 278, 279, 280, 281, 282, 284, 285, 289, 290, 306, 307, 308, 310, 311, 312, 316, 318, 319, 324, 325, 329, 331, 332, 333, 334, 340, 341, 342, 343, 344, 345, 347, 349, 351, 352, 353, 354, 357, 358, 359, 360, 366, 367, 368, 370, 371, 372, 376, 378, 379, 380, 381, 382, 385], "summary": {"covered_lines": 170, "num_statements": 178, "percent_covered": 95.04504504504504, "percent_covered_display": "95", "missing_lines": 8, "excluded_lines": 0, "percent_statements_covered": 95.50561797752809, "percent_statements_covered_display": "96", "num_branches": 44, "num_partial_branches": 3, "covered_branches": 41, "missing_branches": 3, "percent_branches_covered": 93.18181818181819, "percent_branches_covered_display": "93"}, "missing_lines": [142, 210, 320, 321, 326, 327, 383, 384], "excluded_lines": [], "executed_branches": [[78, 79], [78, 87], [98, 99], [98, 107], [101, 102], [101, 105], [137, 138], [137, 163], [140, 141], [140, 160], [141, 143], [147, 148], [147, 151], [155, 156], [155, 158], [204, 205], [204, 233], [208, 209], [208, 230], [209, 211], [215, 216], [215, 219], [225, 226], [225, 228], [280, 281], [280, 284], [284, 285], [341, 342], [341, 349], [343, 344], [343, 347], [349, 351], [349, 366], [353, 354], [353, 357], [359, 360], [359, 366], [367, 368], [367, 385], [371, 372], [371, 376]], "missing_branches": [[141, 142], [209, 210], [284, 316]], "functions": {"GoeChargerApiV2FlowHandler.__init__": {"executed_lines": [56, 58, 59, 60, 62, 63, 64, 66, 67, 68, 70, 71, 72], "summary": {"covered_lines": 13, "num_statements": 13, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 54, "executed_branches": [], "missing_branches": []}, "GoeChargerApiV2FlowHandler.async_step_reconfigure": {"executed_lines": [75, 76, 78, 79, 80, 81, 82, 83, 84, 85, 87, 88, 89, 90, 91, 92, 93, 94], "summary": {"covered_lines": 18, "num_statements": 18, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 2, "num_partial_branches": 0, "covered_branches": 2, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 74, "executed_branches": [[78, 79], [78, 87]], "missing_branches": []}, "GoeChargerApiV2FlowHandler.async_step_user": {"executed_lines": [97, 98, 99, 101, 102, 105, 107, 111], "summary": {"covered_lines": 8, "num_statements": 8, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 4, "num_partial_branches": 0, "covered_branches": 4, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 96, "executed_branches": [[98, 99], [98, 107], [101, 102], [101, 105]], "missing_branches": []}, "GoeChargerApiV2FlowHandler.async_step_user_lan": {"executed_lines": [131, 137, 138, 139, 140, 141, 143, 144, 145, 146, 147, 148, 149, 151, 152, 154, 155, 156, 158, 160, 161, 163, 172], "summary": {"covered_lines": 23, "num_statements": 24, "percent_covered": 94.11764705882354, "percent_covered_display": "94", "missing_lines": 1, "excluded_lines": 0, "percent_statements_covered": 95.83333333333333, "percent_statements_covered_display": "96", "num_branches": 10, "num_partial_branches": 1, "covered_branches": 9, "missing_branches": 1, "percent_branches_covered": 90.0, "percent_branches_covered_display": "90"}, "missing_lines": [142], "excluded_lines": [], "start_line": 129, "executed_branches": [[137, 138], [137, 163], [140, 141], [140, 160], [141, 143], [147, 148], [147, 151], [155, 156], [155, 158]], "missing_branches": [[141, 142]]}, "GoeChargerApiV2FlowHandler.async_step_user_wan": {"executed_lines": [198, 204, 205, 207, 208, 209, 211, 212, 213, 214, 215, 216, 217, 219, 220, 224, 225, 226, 228, 230, 231, 233, 242], "summary": {"covered_lines": 23, "num_statements": 24, "percent_covered": 94.11764705882354, "percent_covered_display": "94", "missing_lines": 1, "excluded_lines": 0, "percent_statements_covered": 95.83333333333333, "percent_statements_covered_display": "96", "num_branches": 10, "num_partial_branches": 1, "covered_branches": 9, "missing_branches": 1, "percent_branches_covered": 90.0, "percent_branches_covered_display": "90"}, "missing_lines": [210], "excluded_lines": [], "start_line": 196, "executed_branches": [[204, 205], [204, 233], [208, 209], [208, 230], [209, 211], [215, 216], [215, 219], [225, 226], [225, 228]], "missing_branches": [[209, 210]]}, "GoeChargerApiV2FlowHandler._check_ws_connection_and_flags": {"executed_lines": [271, 275, 276, 277, 278, 279, 280, 281, 282, 284, 285, 289, 290, 306, 307, 308, 310, 311, 312, 316, 318, 319, 324, 325, 329], "summary": {"covered_lines": 25, "num_statements": 29, "percent_covered": 84.84848484848484, "percent_covered_display": "85", "missing_lines": 4, "excluded_lines": 0, "percent_statements_covered": 86.20689655172414, "percent_statements_covered_display": "86", "num_branches": 4, "num_partial_branches": 1, "covered_branches": 3, "missing_branches": 1, "percent_branches_covered": 75.0, "percent_branches_covered_display": "75"}, "missing_lines": [320, 321, 326, 327], "excluded_lines": [], "start_line": 268, "executed_branches": [[280, 281], [280, 284], [284, 285]], "missing_branches": [[284, 316]]}, "GoeChargerApiV2FlowHandler._test_host": {"executed_lines": [332, 333, 334, 340, 341, 342, 343, 344, 345, 347, 349, 351, 352, 353, 354, 357, 358, 359, 360, 366, 367, 368, 370, 371, 372, 376, 378, 379, 380, 381, 382, 385], "summary": {"covered_lines": 32, "num_statements": 34, "percent_covered": 95.83333333333333, "percent_covered_display": "96", "missing_lines": 2, "excluded_lines": 0, "percent_statements_covered": 94.11764705882354, "percent_statements_covered_display": "94", "num_branches": 14, "num_partial_branches": 0, "covered_branches": 14, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [383, 384], "excluded_lines": [], "start_line": 331, "executed_branches": [[341, 342], [341, 349], [343, 344], [343, 347], [349, 351], [349, 366], [353, 354], [353, 357], [359, 360], [359, 366], [367, 368], [367, 385], [371, 372], [371, 376]], "missing_branches": []}, "": {"executed_lines": [1, 2, 3, 4, 6, 7, 8, 9, 10, 21, 22, 24, 33, 39, 41, 43, 44, 47, 50, 51, 52, 54, 74, 96, 129, 196, 268, 331], "summary": {"covered_lines": 28, "num_statements": 28, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 1, "executed_branches": [], "missing_branches": []}}, "classes": {"GoeChargerApiV2FlowHandler": {"executed_lines": [56, 58, 59, 60, 62, 63, 64, 66, 67, 68, 70, 71, 72, 75, 76, 78, 79, 80, 81, 82, 83, 84, 85, 87, 88, 89, 90, 91, 92, 93, 94, 97, 98, 99, 101, 102, 105, 107, 111, 131, 137, 138, 139, 140, 141, 143, 144, 145, 146, 147, 148, 149, 151, 152, 154, 155, 156, 158, 160, 161, 163, 172, 198, 204, 205, 207, 208, 209, 211, 212, 213, 214, 215, 216, 217, 219, 220, 224, 225, 226, 228, 230, 231, 233, 242, 271, 275, 276, 277, 278, 279, 280, 281, 282, 284, 285, 289, 290, 306, 307, 308, 310, 311, 312, 316, 318, 319, 324, 325, 329, 332, 333, 334, 340, 341, 342, 343, 344, 345, 347, 349, 351, 352, 353, 354, 357, 358, 359, 360, 366, 367, 368, 370, 371, 372, 376, 378, 379, 380, 381, 382, 385], "summary": {"covered_lines": 142, "num_statements": 150, "percent_covered": 94.3298969072165, "percent_covered_display": "94", "missing_lines": 8, "excluded_lines": 0, "percent_statements_covered": 94.66666666666667, "percent_statements_covered_display": "95", "num_branches": 44, "num_partial_branches": 3, "covered_branches": 41, "missing_branches": 3, "percent_branches_covered": 93.18181818181819, "percent_branches_covered_display": "93"}, "missing_lines": [142, 210, 320, 321, 326, 327, 383, 384], "excluded_lines": [], "start_line": 47, "executed_branches": [[78, 79], [78, 87], [98, 99], [98, 107], [101, 102], [101, 105], [137, 138], [137, 163], [140, 141], [140, 160], [141, 143], [147, 148], [147, 151], [155, 156], [155, 158], [204, 205], [204, 233], [208, 209], [208, 230], [209, 211], [215, 216], [215, 219], [225, 226], [225, 228], [280, 281], [280, 284], [284, 285], [341, 342], [341, 349], [343, 344], [343, 347], [349, 351], [349, 366], [353, 354], [353, 357], [359, 360], [359, 366], [367, 368], [367, 385], [371, 372], [371, 376]], "missing_branches": [[141, 142], [209, 210], [284, 316]]}, "": {"executed_lines": [1, 2, 3, 4, 6, 7, 8, 9, 10, 21, 22, 24, 33, 39, 41, 43, 44, 47, 50, 51, 52, 54, 74, 96, 129, 196, 268, 331], "summary": {"covered_lines": 28, "num_statements": 28, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 1, "executed_branches": [], "missing_branches": []}}}, "custom_components/goecharger_api2/const.py": {"executed_lines": [1, 2, 4, 5, 6, 7, 8, 9, 10, 16, 17, 20, 21, 22, 23, 25, 26, 28, 37, 38, 39, 40, 42, 43, 45, 46, 47, 48, 49, 52, 53, 54, 57, 58, 59, 60, 61, 62, 63, 66, 67, 68, 71, 72, 73, 74, 75, 76, 77, 80, 81, 82, 83, 84, 87, 89, 216, 242, 710, 837, 1953, 2086, 2097, 2315, 2317, 2327, 2329, 2385, 3242], "summary": {"covered_lines": 69, "num_statements": 69, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "executed_branches": [], "missing_branches": [], "functions": {"": {"executed_lines": [1, 2, 4, 5, 6, 7, 8, 9, 10, 16, 17, 20, 21, 22, 23, 25, 26, 28, 37, 38, 39, 40, 42, 43, 45, 46, 47, 48, 49, 52, 53, 54, 57, 58, 59, 60, 61, 62, 63, 66, 67, 68, 71, 72, 73, 74, 75, 76, 77, 80, 81, 82, 83, 84, 87, 89, 216, 242, 710, 837, 1953, 2086, 2097, 2315, 2317, 2327, 2329, 2385, 3242], "summary": {"covered_lines": 69, "num_statements": 69, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 1, "executed_branches": [], "missing_branches": []}}, "classes": {"ExtBinarySensorEntityDescription": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 0, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 46, "executed_branches": [], "missing_branches": []}, "ExtButtonEntityDescription": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 0, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 53, "executed_branches": [], "missing_branches": []}, "ExtNumberEntityDescription": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 0, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 58, "executed_branches": [], "missing_branches": []}, "ExtSelectEntityDescription": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 0, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 67, "executed_branches": [], "missing_branches": []}, "ExtSensorEntityDescription": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 0, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 72, "executed_branches": [], "missing_branches": []}, "ExtSwitchEntityDescription": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 0, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 81, "executed_branches": [], "missing_branches": []}, "": {"executed_lines": [1, 2, 4, 5, 6, 7, 8, 9, 10, 16, 17, 20, 21, 22, 23, 25, 26, 28, 37, 38, 39, 40, 42, 43, 45, 46, 47, 48, 49, 52, 53, 54, 57, 58, 59, 60, 61, 62, 63, 66, 67, 68, 71, 72, 73, 74, 75, 76, 77, 80, 81, 82, 83, 84, 87, 89, 216, 242, 710, 837, 1953, 2086, 2097, 2315, 2317, 2327, 2329, 2385, 3242], "summary": {"covered_lines": 69, "num_statements": 69, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 1, "executed_branches": [], "missing_branches": []}}}, "custom_components/goecharger_api2/entity.py": {"executed_lines": [1, 3, 4, 5, 7, 9, 13, 16], "summary": {"covered_lines": 8, "num_statements": 31, "percent_covered": 17.77777777777778, "percent_covered_display": "18", "missing_lines": 23, "excluded_lines": 0, "percent_statements_covered": 25.806451612903224, "percent_statements_covered_display": "26", "num_branches": 14, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 14, "percent_branches_covered": 0.0, "percent_branches_covered_display": "0"}, "missing_lines": [20, 22, 23, 26, 27, 30, 33, 34, 35, 36, 38, 39, 40, 41, 42, 44, 45, 46, 49, 50, 51, 52, 54], "excluded_lines": [], "executed_branches": [], "missing_branches": [[22, 23], [22, 26], [26, 27], [26, 30], [33, 34], [33, 54], [38, 39], [38, 44], [39, 38], [39, 40], [44, 45], [44, 49], [49, 50], [49, 54]], "functions": {"CustomFriendlyNameEntity._Entity__async_calculate_state": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 23, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 23, "excluded_lines": 0, "percent_statements_covered": 0.0, "percent_statements_covered_display": "0", "num_branches": 14, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 14, "percent_branches_covered": 0.0, "percent_branches_covered_display": "0"}, "missing_lines": [20, 22, 23, 26, 27, 30, 33, 34, 35, 36, 38, 39, 40, 41, 42, 44, 45, 46, 49, 50, 51, 52, 54], "excluded_lines": [], "start_line": 16, "executed_branches": [], "missing_branches": [[22, 23], [22, 26], [26, 27], [26, 30], [33, 34], [33, 54], [38, 39], [38, 44], [39, 38], [39, 40], [44, 45], [44, 49], [49, 50], [49, 54]]}, "": {"executed_lines": [1, 3, 4, 5, 7, 9, 13, 16], "summary": {"covered_lines": 8, "num_statements": 8, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 1, "executed_branches": [], "missing_branches": []}}, "classes": {"CustomFriendlyNameEntity": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 23, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 23, "excluded_lines": 0, "percent_statements_covered": 0.0, "percent_statements_covered_display": "0", "num_branches": 14, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 14, "percent_branches_covered": 0.0, "percent_branches_covered_display": "0"}, "missing_lines": [20, 22, 23, 26, 27, 30, 33, 34, 35, 36, 38, 39, 40, 41, 42, 44, 45, 46, 49, 50, 51, 52, 54], "excluded_lines": [], "start_line": 13, "executed_branches": [], "missing_branches": [[22, 23], [22, 26], [26, 27], [26, 30], [33, 34], [33, 54], [38, 39], [38, 44], [39, 38], [39, 40], [44, 45], [44, 49], [49, 50], [49, 54]]}, "": {"executed_lines": [1, 3, 4, 5, 7, 9, 13, 16], "summary": {"covered_lines": 8, "num_statements": 8, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 1, "executed_branches": [], "missing_branches": []}}}, "custom_components/goecharger_api2/number.py": {"executed_lines": [1, 2, 4, 10, 11, 12, 13, 15, 16, 17, 19, 21, 46, 47, 48, 50, 60, 61, 62, 63, 64, 65, 66, 68, 70, 71, 72, 73, 74, 76, 77, 78, 80, 81, 83, 84, 87, 89, 91, 93, 94, 96, 98, 99, 100, 103, 104, 105, 108, 110, 113, 114, 115, 118, 119, 120, 123, 125, 126, 128, 129, 130, 131, 133, 134, 135, 137, 138, 139, 140, 142, 143, 144, 146, 147, 148], "summary": {"covered_lines": 76, "num_statements": 94, "percent_covered": 77.46478873239437, "percent_covered_display": "77", "missing_lines": 18, "excluded_lines": 0, "percent_statements_covered": 80.85106382978724, "percent_statements_covered_display": "81", "num_branches": 48, "num_partial_branches": 2, "covered_branches": 34, "missing_branches": 14, "percent_branches_covered": 70.83333333333333, "percent_branches_covered_display": "71"}, "missing_lines": [22, 23, 24, 26, 27, 28, 31, 32, 34, 36, 37, 39, 40, 41, 43, 52, 57, 58], "excluded_lines": [], "executed_branches": [[64, 65], [65, 66], [65, 68], [70, 71], [70, 72], [72, 73], [72, 77], [73, 74], [73, 76], [77, 78], [77, 87], [94, 96], [94, 113], [98, 99], [98, 100], [100, 103], [100, 104], [104, 105], [104, 108], [113, 114], [113, 115], [115, 118], [115, 119], [119, 120], [119, 123], [129, -128], [129, 130], [131, -128], [131, 133], [138, 139], [138, 146], [143, 144], [146, -128], [146, 147]], "missing_branches": [[26, 27], [26, 39], [27, 28], [27, 43], [28, 31], [28, 36], [31, 32], [31, 34], [39, 40], [39, 43], [57, -50], [57, 58], [64, 70], [143, 146]], "functions": {"async_setup_entry": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 15, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 15, "excluded_lines": 0, "percent_statements_covered": 0.0, "percent_statements_covered_display": "0", "num_branches": 10, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 10, "percent_branches_covered": 0.0, "percent_branches_covered_display": "0"}, "missing_lines": [22, 23, 24, 26, 27, 28, 31, 32, 34, 36, 37, 39, 40, 41, 43], "excluded_lines": [], "start_line": 21, "executed_branches": [], "missing_branches": [[26, 27], [26, 39], [27, 28], [27, 43], [28, 31], [28, 36], [31, 32], [31, 34], [39, 40], [39, 43]]}, "GoeChargerNumber.__init__": {"executed_lines": [48], "summary": {"covered_lines": 1, "num_statements": 1, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 47, "executed_branches": [], "missing_branches": []}, "GoeChargerNumber.async_added_to_hass": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 3, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 3, "excluded_lines": 0, "percent_statements_covered": 0.0, "percent_statements_covered_display": "0", "num_branches": 2, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 2, "percent_branches_covered": 0.0, "percent_branches_covered_display": "0"}, "missing_lines": [52, 57, 58], "excluded_lines": [], "start_line": 50, "executed_branches": [], "missing_branches": [[57, -50], [57, 58]]}, "GoeChargerNumber.native_value": {"executed_lines": [62, 63, 64, 65, 66, 68, 70, 71, 72, 73, 74, 76, 77, 78, 80, 81, 83, 84, 87, 89], "summary": {"covered_lines": 20, "num_statements": 20, "percent_covered": 96.875, "percent_covered_display": "97", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 12, "num_partial_branches": 1, "covered_branches": 11, "missing_branches": 1, "percent_branches_covered": 91.66666666666667, "percent_branches_covered_display": "92"}, "missing_lines": [], "excluded_lines": [], "start_line": 61, "executed_branches": [[64, 65], [65, 66], [65, 68], [70, 71], [70, 72], [72, 73], [72, 77], [73, 74], [73, 76], [77, 78], [77, 87]], "missing_branches": [[64, 70]]}, "GoeChargerNumber.async_set_native_value": {"executed_lines": [93, 94, 96, 98, 99, 100, 103, 104, 105, 108, 110, 113, 114, 115, 118, 119, 120, 123, 125, 126], "summary": {"covered_lines": 20, "num_statements": 20, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 14, "num_partial_branches": 0, "covered_branches": 14, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 91, "executed_branches": [[94, 96], [94, 113], [98, 99], [98, 100], [100, 103], [100, 104], [104, 105], [104, 108], [113, 114], [113, 115], [115, 118], [115, 119], [119, 120], [119, 123]], "missing_branches": []}, "GoeChargerNumber.check_amp_min_max": {"executed_lines": [129, 130, 131, 133, 134, 135, 137, 138, 139, 140, 142, 143, 144, 146, 147, 148], "summary": {"covered_lines": 16, "num_statements": 16, "percent_covered": 96.15384615384616, "percent_covered_display": "96", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 10, "num_partial_branches": 1, "covered_branches": 9, "missing_branches": 1, "percent_branches_covered": 90.0, "percent_branches_covered_display": "90"}, "missing_lines": [], "excluded_lines": [], "start_line": 128, "executed_branches": [[129, -128], [129, 130], [131, -128], [131, 133], [138, 139], [138, 146], [143, 144], [146, -128], [146, 147]], "missing_branches": [[143, 146]]}, "": {"executed_lines": [1, 2, 4, 10, 11, 12, 13, 15, 16, 17, 19, 21, 46, 47, 50, 60, 61, 91, 128], "summary": {"covered_lines": 19, "num_statements": 19, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 1, "executed_branches": [], "missing_branches": []}}, "classes": {"GoeChargerNumber": {"executed_lines": [48, 62, 63, 64, 65, 66, 68, 70, 71, 72, 73, 74, 76, 77, 78, 80, 81, 83, 84, 87, 89, 93, 94, 96, 98, 99, 100, 103, 104, 105, 108, 110, 113, 114, 115, 118, 119, 120, 123, 125, 126, 129, 130, 131, 133, 134, 135, 137, 138, 139, 140, 142, 143, 144, 146, 147, 148], "summary": {"covered_lines": 57, "num_statements": 60, "percent_covered": 92.85714285714286, "percent_covered_display": "93", "missing_lines": 3, "excluded_lines": 0, "percent_statements_covered": 95.0, "percent_statements_covered_display": "95", "num_branches": 38, "num_partial_branches": 2, "covered_branches": 34, "missing_branches": 4, "percent_branches_covered": 89.47368421052632, "percent_branches_covered_display": "89"}, "missing_lines": [52, 57, 58], "excluded_lines": [], "start_line": 46, "executed_branches": [[64, 65], [65, 66], [65, 68], [70, 71], [70, 72], [72, 73], [72, 77], [73, 74], [73, 76], [77, 78], [77, 87], [94, 96], [94, 113], [98, 99], [98, 100], [100, 103], [100, 104], [104, 105], [104, 108], [113, 114], [113, 115], [115, 118], [115, 119], [119, 120], [119, 123], [129, -128], [129, 130], [131, -128], [131, 133], [138, 139], [138, 146], [143, 144], [146, -128], [146, 147]], "missing_branches": [[57, -50], [57, 58], [64, 70], [143, 146]]}, "": {"executed_lines": [1, 2, 4, 10, 11, 12, 13, 15, 16, 17, 19, 21, 46, 47, 50, 60, 61, 91, 128], "summary": {"covered_lines": 19, "num_statements": 34, "percent_covered": 43.18181818181818, "percent_covered_display": "43", "missing_lines": 15, "excluded_lines": 0, "percent_statements_covered": 55.88235294117647, "percent_statements_covered_display": "56", "num_branches": 10, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 10, "percent_branches_covered": 0.0, "percent_branches_covered_display": "0"}, "missing_lines": [22, 23, 24, 26, 27, 28, 31, 32, 34, 36, 37, 39, 40, 41, 43], "excluded_lines": [], "start_line": 1, "executed_branches": [], "missing_branches": [[26, 27], [26, 39], [27, 28], [27, 43], [28, 31], [28, 36], [31, 32], [31, 34], [39, 40], [39, 43]]}}}, "custom_components/goecharger_api2/pygoecharger_ha/__init__.py": {"executed_lines": [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 14, 15, 16, 18, 43, 45, 47, 49, 50, 51, 52, 53, 56, 59, 62, 64, 66, 67, 69, 71, 72, 73, 76, 77, 79, 81, 82, 84, 85, 86, 88, 89, 90, 91, 93, 96, 97, 98, 100, 102, 106, 108, 110, 111, 112, 113, 114, 117, 119, 121, 122, 123, 124, 126, 127, 128, 131, 133, 137, 138, 140, 142, 145, 146, 148, 149, 152, 153, 155, 156, 158, 160, 164, 165, 168, 169, 170, 172, 173, 174, 176, 177, 178, 179, 180, 181, 182, 183, 184, 185, 186, 188, 189, 190, 191, 192, 194, 195, 196, 198, 199, 201, 202, 203, 204, 206, 208, 209, 210, 211, 212, 213, 216, 217, 218, 219, 220, 221, 222, 223, 224, 225, 226, 227, 228, 229, 230, 231, 232, 234, 235, 237, 238, 239, 240, 241, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 253, 255, 256, 257, 258, 259, 262, 263, 264, 267, 268, 271, 273, 275, 276, 279, 281, 282, 283, 284, 285, 286, 287, 289, 291, 292, 293, 295, 304, 305, 306, 307, 309, 310, 311, 312, 314, 315, 316, 317, 319, 320, 323, 324, 326, 327, 329, 331, 332, 333, 335, 336, 337, 338, 339, 340, 342, 343, 344, 345, 348, 350, 351, 353, 355, 357, 361, 362, 365, 367, 368, 371, 372, 374, 375, 376, 377, 378, 380, 381, 382, 383, 384, 385, 387, 388, 389, 390, 392, 393, 394, 396, 397, 398, 403, 404, 405, 406, 407, 408, 416, 417, 420, 422, 423, 424, 426, 427, 428, 429, 431, 433, 434, 435, 436, 437, 438, 440, 441, 442, 443, 444, 445, 446, 447, 448, 449, 451, 452, 453, 454, 455, 457, 458, 460, 461, 467, 473, 475, 476, 477, 478, 480, 481, 482, 483, 484, 485, 486, 487, 489, 490, 496, 502, 504, 505, 507, 508, 510, 512, 513, 514, 516, 518, 521, 522, 523, 524, 525, 530, 532, 533, 534, 535, 536, 537, 539, 541, 543, 544, 545, 546, 547, 549, 550, 552, 553, 554, 555, 556, 557, 558, 559, 560, 562, 563, 565, 566, 568, 570, 571, 572, 573, 574, 575, 576, 578, 579, 580, 582, 583, 584, 586, 588, 589, 594, 595, 596, 597, 603, 610, 617, 618, 619, 621, 622, 623, 624, 626, 627, 629, 631, 632, 634, 636, 637, 638, 639, 640, 641, 642, 643, 645, 647, 649, 650, 651, 653, 654, 655, 656, 657, 658, 661, 662, 663, 665, 667, 668, 670, 677, 678, 682, 685, 686, 688, 690, 692, 693, 694, 696, 698, 699, 700, 702, 703, 704, 707, 709, 710, 711, 712, 713, 714, 715, 716, 717, 718, 719, 721, 723, 725, 726, 727, 728, 729, 731, 733, 734, 735, 743, 751, 753, 755, 756, 757, 769, 770, 773, 774, 776, 778, 780, 781, 782, 784, 785, 786, 787, 788, 790, 791, 793, 795, 796, 797, 798, 801, 802, 803, 804, 805, 806, 807, 808, 809, 811, 812, 813, 814, 815, 816, 817, 818, 820, 821, 823, 824, 825, 826, 828, 829, 830, 831, 832, 835, 836, 837, 839, 840, 841, 842, 843, 845, 846, 847, 848, 851, 854, 855, 856, 858, 859, 860, 861, 862, 865, 871, 872, 873, 875, 876, 877, 878, 879, 880, 881, 884, 885, 888, 907, 908, 911, 913, 914, 915, 916, 917, 918, 919, 921, 922, 923, 924, 925, 926, 927, 928, 929, 931, 932, 933, 936, 937, 939, 940, 941, 942, 943, 945, 946, 948, 949, 950, 951, 953, 954, 956, 957, 958, 961, 964, 966, 967, 970, 971, 973, 974, 975, 976, 977, 978, 979, 980, 981, 982, 983, 984, 985, 986, 987, 988, 990, 992, 993, 994, 995, 1002, 1003, 1004, 1008, 1010, 1011, 1012, 1013, 1014, 1016, 1017, 1018, 1020, 1021, 1022, 1023, 1024, 1027, 1028, 1029, 1030, 1032, 1034, 1035, 1037, 1038, 1039, 1041, 1043, 1044, 1045, 1048, 1049, 1050, 1051, 1052, 1054, 1055, 1056, 1057, 1058, 1059, 1062, 1064, 1065, 1070, 1072, 1074, 1075, 1084, 1086, 1087, 1088, 1091, 1093, 1094, 1095, 1096, 1097, 1098, 1099, 1100, 1101, 1102, 1103, 1104, 1105, 1108, 1109, 1110, 1111], "summary": {"covered_lines": 687, "num_statements": 721, "percent_covered": 93.93939393939394, "percent_covered_display": "94", "missing_lines": 34, "excluded_lines": 0, "percent_statements_covered": 95.28432732316227, "percent_statements_covered_display": "95", "num_branches": 302, "num_partial_branches": 26, "covered_branches": 274, "missing_branches": 28, "percent_branches_covered": 90.72847682119205, "percent_branches_covered_display": "91"}, "missing_lines": [116, 463, 464, 468, 469, 470, 471, 492, 493, 498, 499, 500, 501, 591, 592, 598, 599, 600, 601, 605, 606, 607, 608, 705, 866, 867, 868, 869, 996, 997, 1060, 1067, 1106, 1107], "excluded_lines": [], "executed_branches": [[51, 52], [52, 53], [52, 56], [71, 72], [71, 76], [111, 112], [111, 117], [113, 114], [123, -119], [123, 124], [140, 142], [140, 160], [145, 146], [145, 148], [152, 153], [152, 172], [155, 156], [155, 158], [160, 164], [160, 172], [168, 169], [168, 172], [172, 173], [172, 176], [176, 177], [176, 188], [203, 204], [203, 206], [238, 239], [238, 240], [262, -255], [262, 263], [263, -255], [263, 264], [268, 271], [268, 273], [276, 279], [276, 281], [281, 282], [281, 291], [283, 284], [283, 285], [285, 286], [285, 287], [287, 281], [287, 289], [291, 292], [291, 295], [295, 304], [295, 317], [305, 306], [305, 309], [309, 310], [309, 314], [323, 324], [323, 326], [326, 327], [326, 329], [333, 335], [333, 338], [335, 336], [335, 342], [336, 337], [336, 342], [338, 339], [339, 340], [339, 342], [342, 343], [342, 371], [344, 345], [344, 348], [350, 351], [350, 353], [355, -331], [355, 357], [361, 362], [361, 365], [365, -331], [365, 367], [371, 372], [371, 374], [375, 376], [375, 377], [377, 378], [384, -380], [384, 385], [388, 389], [388, 420], [389, 390], [389, 392], [393, 394], [393, 396], [396, 397], [396, 416], [398, -387], [398, 403], [403, -387], [403, 404], [404, 405], [406, 403], [406, 407], [428, 429], [437, 438], [437, 440], [443, 444], [443, 467], [446, 447], [446, 473], [448, 449], [448, 451], [453, 454], [453, 457], [454, 453], [454, 455], [477, 478], [477, 480], [483, 484], [483, 496], [486, 487], [486, 502], [507, 508], [507, 518], [508, 510], [508, 512], [512, 513], [512, 516], [518, 521], [518, 532], [522, 523], [522, 524], [524, 525], [524, 530], [532, 533], [532, 534], [534, 535], [534, 536], [536, 537], [536, 539], [544, 545], [544, 552], [545, 546], [545, 549], [553, 554], [553, 560], [554, 555], [554, 556], [556, 557], [556, 558], [558, 553], [558, 559], [565, 566], [565, 568], [572, 573], [572, 594], [575, 576], [575, 610], [576, 578], [576, 586], [578, 579], [578, 582], [582, 583], [582, 584], [594, 595], [594, 603], [622, 623], [622, 626], [631, 632], [631, 634], [638, 639], [638, 647], [650, 651], [650, 653], [654, -653], [654, 655], [656, 657], [656, 661], [661, 662], [668, 670], [668, 678], [678, 682], [678, 688], [698, 699], [698, 703], [699, 700], [699, 702], [703, 704], [709, 710], [709, 714], [714, 715], [714, 716], [716, 717], [716, 718], [718, 719], [718, 721], [726, 727], [726, 729], [753, 755], [753, 773], [784, 785], [784, 790], [786, 787], [790, 791], [790, 793], [805, 806], [805, 811], [807, 808], [812, 813], [812, 820], [830, 831], [839, 840], [839, 845], [841, 842], [847, 848], [847, 858], [855, 856], [855, 858], [858, 859], [858, 865], [860, 861], [865, 871], [877, 878], [877, 884], [879, 880], [917, 918], [925, 926], [925, 931], [927, 928], [936, 937], [936, 990], [939, 940], [939, 948], [948, 949], [948, 956], [956, 957], [956, 961], [964, 936], [964, 966], [970, 936], [970, 971], [975, 976], [975, 990], [979, 980], [983, 984], [987, 988], [1002, 1003], [1002, 1008], [1020, 1021], [1020, 1037], [1022, 1023], [1022, 1072], [1023, 1024], [1023, 1027], [1028, 1029], [1028, 1032], [1037, 1038], [1037, 1048], [1039, 1041], [1039, 1072], [1043, 1044], [1043, 1072], [1048, 1049], [1048, 1070], [1050, 1051], [1054, 1055], [1054, 1064], [1056, 1057], [1059, 1062], [1064, 1065], [1086, 1087], [1086, 1091], [1095, 1096], [1099, 1100], [1099, 1102], [1105, 1108]], "missing_branches": [[51, 66], [113, 116], [338, 342], [377, -331], [404, 403], [428, 431], [661, -653], [703, 705], [786, 788], [807, 809], [830, 832], [841, 843], [860, 862], [865, 866], [867, 868], [867, 869], [879, 881], [917, 919], [927, 929], [979, 990], [983, 990], [987, 990], [1050, 1054], [1056, 1072], [1059, 1060], [1064, 1067], [1095, 1111], [1105, 1106]], "functions": {"TargetedEvent.__init__": {"executed_lines": [50, 51, 52, 53, 56, 59, 62, 64, 66, 67], "summary": {"covered_lines": 10, "num_statements": 10, "percent_covered": 92.85714285714286, "percent_covered_display": "93", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 4, "num_partial_branches": 1, "covered_branches": 3, "missing_branches": 1, "percent_branches_covered": 75.0, "percent_branches_covered_display": "75"}, "missing_lines": [], "excluded_lines": [], "start_line": 49, "executed_branches": [[51, 52], [52, 53], [52, 56]], "missing_branches": [[51, 66]]}, "TargetedEvent.check_and_set": {"executed_lines": [71, 72, 73, 76, 77, 79], "summary": {"covered_lines": 6, "num_statements": 6, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 2, "num_partial_branches": 0, "covered_branches": 2, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 69, "executed_branches": [[71, 72], [71, 76]], "missing_branches": []}, "TargetedEvent.has_errors": {"executed_lines": [82], "summary": {"covered_lines": 1, "num_statements": 1, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 81, "executed_branches": [], "missing_branches": []}, "TargetedEvent.set_error": {"executed_lines": [85, 86], "summary": {"covered_lines": 2, "num_statements": 2, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 84, "executed_branches": [], "missing_branches": []}, "NonNullDict.none_null_get": {"executed_lines": [90, 91], "summary": {"covered_lines": 2, "num_statements": 2, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 89, "executed_branches": [], "missing_branches": []}, "ExpiringStore.__init__": {"executed_lines": [97, 98], "summary": {"covered_lines": 2, "num_statements": 2, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 96, "executed_branches": [], "missing_branches": []}, "ExpiringStore.set": {"executed_lines": [102, 106], "summary": {"covered_lines": 2, "num_statements": 2, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 100, "executed_branches": [], "missing_branches": []}, "ExpiringStore.get": {"executed_lines": [110, 111, 112, 113, 114, 117], "summary": {"covered_lines": 6, "num_statements": 7, "percent_covered": 81.81818181818181, "percent_covered_display": "82", "missing_lines": 1, "excluded_lines": 0, "percent_statements_covered": 85.71428571428571, "percent_statements_covered_display": "86", "num_branches": 4, "num_partial_branches": 1, "covered_branches": 3, "missing_branches": 1, "percent_branches_covered": 75.0, "percent_branches_covered_display": "75"}, "missing_lines": [116], "excluded_lines": [], "start_line": 108, "executed_branches": [[111, 112], [111, 117], [113, 114]], "missing_branches": [[113, 116]]}, "ExpiringStore._cleanup": {"executed_lines": [121, 122, 123, 124], "summary": {"covered_lines": 4, "num_statements": 4, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 2, "num_partial_branches": 0, "covered_branches": 2, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 119, "executed_branches": [[123, -119], [123, 124]], "missing_branches": []}, "ExpiringStore.__len__": {"executed_lines": [127, 128], "summary": {"covered_lines": 2, "num_statements": 2, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 126, "executed_branches": [], "missing_branches": []}, "GoeChargerApiV2Bridge.__init__": {"executed_lines": [137, 138, 140, 142, 145, 146, 148, 149, 152, 153, 155, 156, 158, 160, 164, 165, 168, 169, 170, 172, 173, 174, 176, 177, 178, 179, 180, 181, 182, 183, 184, 185, 186, 188, 189, 190, 191, 192, 194, 195, 196, 198, 199, 201, 202, 203, 204, 206, 208, 209, 210, 211, 212, 213, 216, 217, 218, 219, 220, 221, 222, 223, 224, 225, 226, 227, 228, 229, 230, 231, 232], "summary": {"covered_lines": 71, "num_statements": 71, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 18, "num_partial_branches": 0, "covered_branches": 18, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 133, "executed_branches": [[140, 142], [140, 160], [145, 146], [145, 148], [152, 153], [152, 172], [155, 156], [155, 158], [160, 164], [160, 172], [168, 169], [168, 172], [172, 173], [172, 176], [176, 177], [176, 188], [203, 204], [203, 206]], "missing_branches": []}, "GoeChargerApiV2Bridge.available_fields": {"executed_lines": [235], "summary": {"covered_lines": 1, "num_statements": 1, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 234, "executed_branches": [], "missing_branches": []}, "GoeChargerApiV2Bridge.clear_data": {"executed_lines": [238, 239, 240, 241, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 253], "summary": {"covered_lines": 16, "num_statements": 16, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 2, "num_partial_branches": 0, "covered_branches": 2, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 237, "executed_branches": [[238, 239], [238, 240]], "missing_branches": []}, "GoeChargerApiV2Bridge.reset_stored_update_ts": {"executed_lines": [256, 257, 258, 259, 262, 263, 264], "summary": {"covered_lines": 7, "num_statements": 7, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 4, "num_partial_branches": 0, "covered_branches": 4, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 255, "executed_branches": [[262, -255], [262, 263], [263, -255], [263, 264]], "missing_branches": []}, "GoeChargerApiV2Bridge.read_system": {"executed_lines": [268, 271, 273], "summary": {"covered_lines": 3, "num_statements": 3, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 2, "num_partial_branches": 0, "covered_branches": 2, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 267, "executed_branches": [[268, 271], [268, 273]], "missing_branches": []}, "GoeChargerApiV2Bridge.read_versions": {"executed_lines": [276, 279, 281, 282, 283, 284, 285, 286, 287, 289, 291, 292, 293, 295, 304, 305, 306, 307, 309, 310, 311, 312, 314, 315, 316, 317], "summary": {"covered_lines": 26, "num_statements": 26, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 18, "num_partial_branches": 0, "covered_branches": 18, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 275, "executed_branches": [[276, 279], [276, 281], [281, 282], [281, 291], [283, 284], [283, 285], [285, 286], [285, 287], [287, 281], [287, 289], [291, 292], [291, 295], [295, 304], [295, 317], [305, 306], [305, 309], [309, 310], [309, 314]], "missing_branches": []}, "GoeChargerApiV2Bridge.read_all": {"executed_lines": [320, 323, 324, 326, 327, 329], "summary": {"covered_lines": 6, "num_statements": 6, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 4, "num_partial_branches": 0, "covered_branches": 4, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 319, "executed_branches": [[323, 324], [323, 326], [326, 327], [326, 329]], "missing_branches": []}, "GoeChargerApiV2Bridge.read_all_states": {"executed_lines": [332, 333, 335, 336, 337, 338, 339, 340, 342, 343, 344, 345, 348, 350, 351, 353, 355, 357, 361, 362, 365, 367, 368, 371, 372, 374, 375, 376, 377, 378], "summary": {"covered_lines": 30, "num_statements": 30, "percent_covered": 96.55172413793103, "percent_covered_display": "97", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 28, "num_partial_branches": 2, "covered_branches": 26, "missing_branches": 2, "percent_branches_covered": 92.85714285714286, "percent_branches_covered_display": "93"}, "missing_lines": [], "excluded_lines": [], "start_line": 331, "executed_branches": [[333, 335], [333, 338], [335, 336], [335, 342], [336, 337], [336, 342], [338, 339], [339, 340], [339, 342], [342, 343], [342, 371], [344, 345], [344, 348], [350, 351], [350, 353], [355, -331], [355, 357], [361, 362], [361, 365], [365, -331], [365, 367], [371, 372], [371, 374], [375, 376], [375, 377], [377, 378]], "missing_branches": [[338, 342], [377, -331]]}, "GoeChargerApiV2Bridge.force_config_update": {"executed_lines": [381, 382, 383, 384, 385], "summary": {"covered_lines": 5, "num_statements": 5, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 2, "num_partial_branches": 0, "covered_branches": 2, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 380, "executed_branches": [[384, -380], [384, 385]], "missing_branches": []}, "GoeChargerApiV2Bridge.read_all_config": {"executed_lines": [388, 389, 390, 392, 393, 394, 396, 397, 398, 403, 404, 405, 406, 407, 408, 416, 417, 420], "summary": {"covered_lines": 18, "num_statements": 18, "percent_covered": 97.05882352941177, "percent_covered_display": "97", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 16, "num_partial_branches": 1, "covered_branches": 15, "missing_branches": 1, "percent_branches_covered": 93.75, "percent_branches_covered_display": "94"}, "missing_lines": [], "excluded_lines": [], "start_line": 387, "executed_branches": [[388, 389], [388, 420], [389, 390], [389, 392], [393, 394], [393, 396], [396, 397], [396, 416], [398, -387], [398, 403], [403, -387], [403, 404], [404, 405], [406, 403], [406, 407]], "missing_branches": [[404, 403]]}, "GoeChargerApiV2Bridge._read_filtered_data_from_ws_source": {"executed_lines": [423, 424, 426, 427, 428, 429, 431], "summary": {"covered_lines": 7, "num_statements": 7, "percent_covered": 88.88888888888889, "percent_covered_display": "89", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 2, "num_partial_branches": 1, "covered_branches": 1, "missing_branches": 1, "percent_branches_covered": 50.0, "percent_branches_covered_display": "50"}, "missing_lines": [], "excluded_lines": [], "start_line": 422, "executed_branches": [[428, 429]], "missing_branches": [[428, 431]]}, "GoeChargerApiV2Bridge._read_filtered_data": {"executed_lines": [434, 435, 436, 437, 438, 440, 441, 442, 443, 444, 445, 446, 447, 448, 449, 451, 452, 453, 454, 455, 457, 458, 460, 461, 467, 473], "summary": {"covered_lines": 26, "num_statements": 32, "percent_covered": 86.36363636363636, "percent_covered_display": "86", "missing_lines": 6, "excluded_lines": 0, "percent_statements_covered": 81.25, "percent_statements_covered_display": "81", "num_branches": 12, "num_partial_branches": 0, "covered_branches": 12, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [463, 464, 468, 469, 470, 471], "excluded_lines": [], "start_line": 433, "executed_branches": [[437, 438], [437, 440], [443, 444], [443, 467], [446, 447], [446, 473], [448, 449], [448, 451], [453, 454], [453, 457], [454, 453], [454, 455]], "missing_branches": []}, "GoeChargerApiV2Bridge._read_all_data": {"executed_lines": [476, 477, 478, 480, 481, 482, 483, 484, 485, 486, 487, 489, 490, 496, 502], "summary": {"covered_lines": 15, "num_statements": 21, "percent_covered": 77.77777777777777, "percent_covered_display": "78", "missing_lines": 6, "excluded_lines": 0, "percent_statements_covered": 71.42857142857143, "percent_statements_covered_display": "71", "num_branches": 6, "num_partial_branches": 0, "covered_branches": 6, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [492, 493, 498, 499, 500, 501], "excluded_lines": [], "start_line": 475, "executed_branches": [[477, 478], [477, 480], [483, 484], [483, 496], [486, 487], [486, 502]], "missing_branches": []}, "GoeChargerApiV2Bridge.write_value_to_key": {"executed_lines": [505, 507, 508, 510, 512, 513, 514, 516, 518, 521, 522, 523, 524, 525, 530, 532, 533, 534, 535, 536, 537, 539, 541], "summary": {"covered_lines": 23, "num_statements": 23, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 18, "num_partial_branches": 0, "covered_branches": 18, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 504, "executed_branches": [[507, 508], [507, 518], [508, 510], [508, 512], [512, 513], [512, 516], [518, 521], [518, 532], [522, 523], [522, 524], [524, 525], [524, 530], [532, 533], [532, 534], [534, 535], [534, 536], [536, 537], [536, 539]], "missing_branches": []}, "GoeChargerApiV2Bridge.write_multiple_values_to_keys": {"executed_lines": [544, 545, 546, 547, 549, 550, 552, 553, 554, 555, 556, 557, 558, 559, 560], "summary": {"covered_lines": 15, "num_statements": 15, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 12, "num_partial_branches": 0, "covered_branches": 12, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 543, "executed_branches": [[544, 545], [544, 552], [545, 546], [545, 549], [553, 554], [553, 560], [554, 555], [554, 556], [556, 557], [556, 558], [558, 553], [558, 559]], "missing_branches": []}, "GoeChargerApiV2Bridge._write_values_int": {"executed_lines": [563, 565, 566, 568, 570, 571, 572, 573, 574, 575, 576, 578, 579, 580, 582, 583, 584, 586, 588, 589, 594, 595, 596, 597, 603, 610], "summary": {"covered_lines": 26, "num_statements": 36, "percent_covered": 80.0, "percent_covered_display": "80", "missing_lines": 10, "excluded_lines": 0, "percent_statements_covered": 72.22222222222223, "percent_statements_covered_display": "72", "num_branches": 14, "num_partial_branches": 0, "covered_branches": 14, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [591, 592, 598, 599, 600, 601, 605, 606, 607, 608], "excluded_lines": [], "start_line": 562, "executed_branches": [[565, 566], [565, 568], [572, 573], [572, 594], [575, 576], [575, 610], [576, 578], [576, 586], [578, 579], [578, 582], [582, 583], [582, 584], [594, 595], [594, 603]], "missing_branches": []}, "GoeChargerApiV2Bridge.ws_set_coordinator": {"executed_lines": [618, 619], "summary": {"covered_lines": 2, "num_statements": 2, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 617, "executed_branches": [], "missing_branches": []}, "GoeChargerApiV2Bridge.ws_check_last_update": {"executed_lines": [622, 623, 624, 626, 627], "summary": {"covered_lines": 5, "num_statements": 5, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 2, "num_partial_branches": 0, "covered_branches": 2, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 621, "executed_branches": [[622, 623], [622, 626]], "missing_branches": []}, "GoeChargerApiV2Bridge.ws_close": {"executed_lines": [631, 632, 634, 636, 637, 638, 639, 640, 641, 642, 643, 645, 647], "summary": {"covered_lines": 13, "num_statements": 13, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 4, "num_partial_branches": 0, "covered_branches": 4, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 629, "executed_branches": [[631, 632], [631, 634], [638, 639], [638, 647]], "missing_branches": []}, "GoeChargerApiV2Bridge._ws_notify_for_new_data": {"executed_lines": [650, 651, 653, 665], "summary": {"covered_lines": 4, "num_statements": 4, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 2, "num_partial_branches": 0, "covered_branches": 2, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 649, "executed_branches": [[650, 651], [650, 653]], "missing_branches": []}, "GoeChargerApiV2Bridge._ws_notify_for_new_data._ws_debounce_coordinator_update": {"executed_lines": [654, 655, 656, 657, 658, 661, 662, 663], "summary": {"covered_lines": 8, "num_statements": 8, "percent_covered": 92.85714285714286, "percent_covered_display": "93", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 6, "num_partial_branches": 1, "covered_branches": 5, "missing_branches": 1, "percent_branches_covered": 83.33333333333333, "percent_branches_covered_display": "83"}, "missing_lines": [], "excluded_lines": [], "start_line": 653, "executed_branches": [[654, -653], [654, 655], [656, 657], [656, 661], [661, 662]], "missing_branches": [[661, -653]]}, "GoeChargerApiV2Bridge._ws_compute_hashed_password": {"executed_lines": [668, 670, 677, 678, 682, 685, 686, 688], "summary": {"covered_lines": 8, "num_statements": 8, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 4, "num_partial_branches": 0, "covered_branches": 4, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 667, "executed_branches": [[668, 670], [668, 678], [678, 682], [678, 688]], "missing_branches": []}, "GoeChargerApiV2Bridge._ws_compute_auth_hash": {"executed_lines": [692, 693, 694], "summary": {"covered_lines": 3, "num_statements": 3, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 690, "executed_branches": [], "missing_branches": []}, "GoeChargerApiV2Bridge._ws_decode_message": {"executed_lines": [698, 699, 700, 702, 703, 704], "summary": {"covered_lines": 6, "num_statements": 7, "percent_covered": 84.61538461538461, "percent_covered_display": "85", "missing_lines": 1, "excluded_lines": 0, "percent_statements_covered": 85.71428571428571, "percent_statements_covered_display": "86", "num_branches": 6, "num_partial_branches": 1, "covered_branches": 5, "missing_branches": 1, "percent_branches_covered": 83.33333333333333, "percent_branches_covered_display": "83"}, "missing_lines": [705], "excluded_lines": [], "start_line": 696, "executed_branches": [[698, 699], [698, 703], [699, 700], [699, 702], [703, 704]], "missing_branches": [[703, 705]]}, "GoeChargerApiV2Bridge._ws_normalize_value": {"executed_lines": [709, 710, 711, 712, 713, 714, 715, 716, 717, 718, 719, 721], "summary": {"covered_lines": 12, "num_statements": 12, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 8, "num_partial_branches": 0, "covered_branches": 8, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 707, "executed_branches": [[709, 710], [709, 714], [714, 715], [714, 716], [716, 717], [716, 718], [718, 719], [718, 721]], "missing_branches": []}, "GoeChargerApiV2Bridge._ws_normalize_dict": {"executed_lines": [725, 726, 727, 728, 729], "summary": {"covered_lines": 5, "num_statements": 5, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 2, "num_partial_branches": 0, "covered_branches": 2, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 723, "executed_branches": [[726, 727], [726, 729]], "missing_branches": []}, "GoeChargerApiV2Bridge._ws_send_command": {"executed_lines": [733, 734, 735, 743, 751, 753, 755, 756, 757, 769, 770, 773, 774, 776], "summary": {"covered_lines": 14, "num_statements": 14, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 2, "num_partial_branches": 0, "covered_branches": 2, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 731, "executed_branches": [[753, 755], [753, 773]], "missing_branches": []}, "GoeChargerApiV2Bridge.ws_connect": {"executed_lines": [780, 781, 782, 784, 785, 786, 787, 788, 790, 791, 793, 795, 796, 797, 798, 801, 802, 803, 804, 805, 806, 807, 808, 809, 811, 812, 813, 814, 815, 816, 817, 818, 820, 821, 823, 824, 825, 826, 828, 829, 830, 831, 832, 835, 836, 837, 839, 840, 841, 842, 843, 845, 846, 847, 848, 851, 854, 855, 856, 858, 859, 860, 861, 862, 865, 871, 872, 873, 875, 876, 877, 878, 879, 880, 881, 884, 885, 888, 907, 908, 911, 913, 914, 915, 916, 917, 918, 919, 921, 922, 923, 924, 925, 926, 927, 928, 929, 931, 932, 933, 936, 937, 939, 940, 941, 942, 943, 945, 946, 948, 949, 950, 951, 953, 954, 956, 957, 958, 961, 964, 966, 967, 970, 971, 973, 974, 975, 976, 977, 978, 979, 980, 981, 982, 983, 984, 985, 986, 987, 988, 990, 992, 993, 994, 995, 1002, 1003, 1004, 1008, 1010, 1011, 1012, 1013, 1014], "summary": {"covered_lines": 154, "num_statements": 160, "percent_covered": 90.990990990991, "percent_covered_display": "91", "missing_lines": 6, "excluded_lines": 0, "percent_statements_covered": 96.25, "percent_statements_covered_display": "96", "num_branches": 62, "num_partial_branches": 12, "covered_branches": 48, "missing_branches": 14, "percent_branches_covered": 77.41935483870968, "percent_branches_covered_display": "77"}, "missing_lines": [866, 867, 868, 869, 996, 997], "excluded_lines": [], "start_line": 778, "executed_branches": [[784, 785], [784, 790], [786, 787], [790, 791], [790, 793], [805, 806], [805, 811], [807, 808], [812, 813], [812, 820], [830, 831], [839, 840], [839, 845], [841, 842], [847, 848], [847, 858], [855, 856], [855, 858], [858, 859], [858, 865], [860, 861], [865, 871], [877, 878], [877, 884], [879, 880], [917, 918], [925, 926], [925, 931], [927, 928], [936, 937], [936, 990], [939, 940], [939, 948], [948, 949], [948, 956], [956, 957], [956, 961], [964, 936], [964, 966], [970, 936], [970, 971], [975, 976], [975, 990], [979, 980], [983, 984], [987, 988], [1002, 1003], [1002, 1008]], "missing_branches": [[786, 788], [807, 809], [830, 832], [841, 843], [860, 862], [865, 866], [867, 868], [867, 869], [879, 881], [917, 919], [927, 929], [979, 990], [983, 990], [987, 990]]}, "GoeChargerApiV2Bridge.extract_ws_message_data": {"executed_lines": [1017, 1018, 1020, 1021, 1022, 1023, 1024, 1027, 1028, 1029, 1030, 1032, 1034, 1035, 1037, 1038, 1039, 1041, 1043, 1044, 1045, 1048, 1049, 1050, 1051, 1052, 1054, 1055, 1056, 1057, 1058, 1059, 1062, 1064, 1065, 1070, 1072], "summary": {"covered_lines": 37, "num_statements": 39, "percent_covered": 90.76923076923077, "percent_covered_display": "91", "missing_lines": 2, "excluded_lines": 0, "percent_statements_covered": 94.87179487179488, "percent_statements_covered_display": "95", "num_branches": 26, "num_partial_branches": 4, "covered_branches": 22, "missing_branches": 4, "percent_branches_covered": 84.61538461538461, "percent_branches_covered_display": "85"}, "missing_lines": [1060, 1067], "excluded_lines": [], "start_line": 1016, "executed_branches": [[1020, 1021], [1020, 1037], [1022, 1023], [1022, 1072], [1023, 1024], [1023, 1027], [1028, 1029], [1028, 1032], [1037, 1038], [1037, 1048], [1039, 1041], [1039, 1072], [1043, 1044], [1043, 1072], [1048, 1049], [1048, 1070], [1050, 1051], [1054, 1055], [1054, 1064], [1056, 1057], [1059, 1062], [1064, 1065]], "missing_branches": [[1050, 1054], [1056, 1072], [1059, 1060], [1064, 1067]]}, "bcryptjs_base64_encode_serial": {"executed_lines": [1084, 1086, 1087, 1088, 1091, 1093, 1094, 1095, 1096, 1097, 1098, 1099, 1100, 1101, 1102, 1103, 1104, 1105, 1108, 1109, 1110, 1111], "summary": {"covered_lines": 22, "num_statements": 24, "percent_covered": 87.5, "percent_covered_display": "88", "missing_lines": 2, "excluded_lines": 0, "percent_statements_covered": 91.66666666666667, "percent_statements_covered_display": "92", "num_branches": 8, "num_partial_branches": 2, "covered_branches": 6, "missing_branches": 2, "percent_branches_covered": 75.0, "percent_branches_covered_display": "75"}, "missing_lines": [1106, 1107], "excluded_lines": [], "start_line": 1075, "executed_branches": [[1086, 1087], [1086, 1091], [1095, 1096], [1099, 1100], [1099, 1102], [1105, 1108]], "missing_branches": [[1095, 1111], [1105, 1106]]}, "": {"executed_lines": [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 14, 15, 16, 18, 43, 45, 47, 49, 69, 81, 84, 88, 89, 93, 96, 100, 108, 119, 126, 131, 133, 234, 237, 255, 267, 275, 319, 331, 380, 387, 422, 433, 475, 504, 543, 562, 617, 621, 629, 649, 667, 690, 696, 707, 723, 731, 778, 1016, 1074, 1075], "summary": {"covered_lines": 62, "num_statements": 62, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 1, "executed_branches": [], "missing_branches": []}}, "classes": {"TargetedEvent": {"executed_lines": [50, 51, 52, 53, 56, 59, 62, 64, 66, 67, 71, 72, 73, 76, 77, 79, 82, 85, 86], "summary": {"covered_lines": 19, "num_statements": 19, "percent_covered": 96.0, "percent_covered_display": "96", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 6, "num_partial_branches": 1, "covered_branches": 5, "missing_branches": 1, "percent_branches_covered": 83.33333333333333, "percent_branches_covered_display": "83"}, "missing_lines": [], "excluded_lines": [], "start_line": 47, "executed_branches": [[51, 52], [52, 53], [52, 56], [71, 72], [71, 76]], "missing_branches": [[51, 66]]}, "NonNullDict": {"executed_lines": [90, 91], "summary": {"covered_lines": 2, "num_statements": 2, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 88, "executed_branches": [], "missing_branches": []}, "ExpiringStore": {"executed_lines": [97, 98, 102, 106, 110, 111, 112, 113, 114, 117, 121, 122, 123, 124, 127, 128], "summary": {"covered_lines": 16, "num_statements": 17, "percent_covered": 91.30434782608695, "percent_covered_display": "91", "missing_lines": 1, "excluded_lines": 0, "percent_statements_covered": 94.11764705882354, "percent_statements_covered_display": "94", "num_branches": 6, "num_partial_branches": 1, "covered_branches": 5, "missing_branches": 1, "percent_branches_covered": 83.33333333333333, "percent_branches_covered_display": "83"}, "missing_lines": [116], "excluded_lines": [], "start_line": 93, "executed_branches": [[111, 112], [111, 117], [113, 114], [123, -119], [123, 124]], "missing_branches": [[113, 116]]}, "GoeChargerApiV2Bridge": {"executed_lines": [137, 138, 140, 142, 145, 146, 148, 149, 152, 153, 155, 156, 158, 160, 164, 165, 168, 169, 170, 172, 173, 174, 176, 177, 178, 179, 180, 181, 182, 183, 184, 185, 186, 188, 189, 190, 191, 192, 194, 195, 196, 198, 199, 201, 202, 203, 204, 206, 208, 209, 210, 211, 212, 213, 216, 217, 218, 219, 220, 221, 222, 223, 224, 225, 226, 227, 228, 229, 230, 231, 232, 235, 238, 239, 240, 241, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 253, 256, 257, 258, 259, 262, 263, 264, 268, 271, 273, 276, 279, 281, 282, 283, 284, 285, 286, 287, 289, 291, 292, 293, 295, 304, 305, 306, 307, 309, 310, 311, 312, 314, 315, 316, 317, 320, 323, 324, 326, 327, 329, 332, 333, 335, 336, 337, 338, 339, 340, 342, 343, 344, 345, 348, 350, 351, 353, 355, 357, 361, 362, 365, 367, 368, 371, 372, 374, 375, 376, 377, 378, 381, 382, 383, 384, 385, 388, 389, 390, 392, 393, 394, 396, 397, 398, 403, 404, 405, 406, 407, 408, 416, 417, 420, 423, 424, 426, 427, 428, 429, 431, 434, 435, 436, 437, 438, 440, 441, 442, 443, 444, 445, 446, 447, 448, 449, 451, 452, 453, 454, 455, 457, 458, 460, 461, 467, 473, 476, 477, 478, 480, 481, 482, 483, 484, 485, 486, 487, 489, 490, 496, 502, 505, 507, 508, 510, 512, 513, 514, 516, 518, 521, 522, 523, 524, 525, 530, 532, 533, 534, 535, 536, 537, 539, 541, 544, 545, 546, 547, 549, 550, 552, 553, 554, 555, 556, 557, 558, 559, 560, 563, 565, 566, 568, 570, 571, 572, 573, 574, 575, 576, 578, 579, 580, 582, 583, 584, 586, 588, 589, 594, 595, 596, 597, 603, 610, 618, 619, 622, 623, 624, 626, 627, 631, 632, 634, 636, 637, 638, 639, 640, 641, 642, 643, 645, 647, 650, 651, 653, 654, 655, 656, 657, 658, 661, 662, 663, 665, 668, 670, 677, 678, 682, 685, 686, 688, 692, 693, 694, 698, 699, 700, 702, 703, 704, 709, 710, 711, 712, 713, 714, 715, 716, 717, 718, 719, 721, 725, 726, 727, 728, 729, 733, 734, 735, 743, 751, 753, 755, 756, 757, 769, 770, 773, 774, 776, 780, 781, 782, 784, 785, 786, 787, 788, 790, 791, 793, 795, 796, 797, 798, 801, 802, 803, 804, 805, 806, 807, 808, 809, 811, 812, 813, 814, 815, 816, 817, 818, 820, 821, 823, 824, 825, 826, 828, 829, 830, 831, 832, 835, 836, 837, 839, 840, 841, 842, 843, 845, 846, 847, 848, 851, 854, 855, 856, 858, 859, 860, 861, 862, 865, 871, 872, 873, 875, 876, 877, 878, 879, 880, 881, 884, 885, 888, 907, 908, 911, 913, 914, 915, 916, 917, 918, 919, 921, 922, 923, 924, 925, 926, 927, 928, 929, 931, 932, 933, 936, 937, 939, 940, 941, 942, 943, 945, 946, 948, 949, 950, 951, 953, 954, 956, 957, 958, 961, 964, 966, 967, 970, 971, 973, 974, 975, 976, 977, 978, 979, 980, 981, 982, 983, 984, 985, 986, 987, 988, 990, 992, 993, 994, 995, 1002, 1003, 1004, 1008, 1010, 1011, 1012, 1013, 1014, 1017, 1018, 1020, 1021, 1022, 1023, 1024, 1027, 1028, 1029, 1030, 1032, 1034, 1035, 1037, 1038, 1039, 1041, 1043, 1044, 1045, 1048, 1049, 1050, 1051, 1052, 1054, 1055, 1056, 1057, 1058, 1059, 1062, 1064, 1065, 1070, 1072], "summary": {"covered_lines": 566, "num_statements": 597, "percent_covered": 93.7428896473265, "percent_covered_display": "94", "missing_lines": 31, "excluded_lines": 0, "percent_statements_covered": 94.80737018425461, "percent_statements_covered_display": "95", "num_branches": 282, "num_partial_branches": 22, "covered_branches": 258, "missing_branches": 24, "percent_branches_covered": 91.48936170212765, "percent_branches_covered_display": "91"}, "missing_lines": [463, 464, 468, 469, 470, 471, 492, 493, 498, 499, 500, 501, 591, 592, 598, 599, 600, 601, 605, 606, 607, 608, 705, 866, 867, 868, 869, 996, 997, 1060, 1067], "excluded_lines": [], "start_line": 131, "executed_branches": [[140, 142], [140, 160], [145, 146], [145, 148], [152, 153], [152, 172], [155, 156], [155, 158], [160, 164], [160, 172], [168, 169], [168, 172], [172, 173], [172, 176], [176, 177], [176, 188], [203, 204], [203, 206], [238, 239], [238, 240], [262, -255], [262, 263], [263, -255], [263, 264], [268, 271], [268, 273], [276, 279], [276, 281], [281, 282], [281, 291], [283, 284], [283, 285], [285, 286], [285, 287], [287, 281], [287, 289], [291, 292], [291, 295], [295, 304], [295, 317], [305, 306], [305, 309], [309, 310], [309, 314], [323, 324], [323, 326], [326, 327], [326, 329], [333, 335], [333, 338], [335, 336], [335, 342], [336, 337], [336, 342], [338, 339], [339, 340], [339, 342], [342, 343], [342, 371], [344, 345], [344, 348], [350, 351], [350, 353], [355, -331], [355, 357], [361, 362], [361, 365], [365, -331], [365, 367], [371, 372], [371, 374], [375, 376], [375, 377], [377, 378], [384, -380], [384, 385], [388, 389], [388, 420], [389, 390], [389, 392], [393, 394], [393, 396], [396, 397], [396, 416], [398, -387], [398, 403], [403, -387], [403, 404], [404, 405], [406, 403], [406, 407], [428, 429], [437, 438], [437, 440], [443, 444], [443, 467], [446, 447], [446, 473], [448, 449], [448, 451], [453, 454], [453, 457], [454, 453], [454, 455], [477, 478], [477, 480], [483, 484], [483, 496], [486, 487], [486, 502], [507, 508], [507, 518], [508, 510], [508, 512], [512, 513], [512, 516], [518, 521], [518, 532], [522, 523], [522, 524], [524, 525], [524, 530], [532, 533], [532, 534], [534, 535], [534, 536], [536, 537], [536, 539], [544, 545], [544, 552], [545, 546], [545, 549], [553, 554], [553, 560], [554, 555], [554, 556], [556, 557], [556, 558], [558, 553], [558, 559], [565, 566], [565, 568], [572, 573], [572, 594], [575, 576], [575, 610], [576, 578], [576, 586], [578, 579], [578, 582], [582, 583], [582, 584], [594, 595], [594, 603], [622, 623], [622, 626], [631, 632], [631, 634], [638, 639], [638, 647], [650, 651], [650, 653], [654, -653], [654, 655], [656, 657], [656, 661], [661, 662], [668, 670], [668, 678], [678, 682], [678, 688], [698, 699], [698, 703], [699, 700], [699, 702], [703, 704], [709, 710], [709, 714], [714, 715], [714, 716], [716, 717], [716, 718], [718, 719], [718, 721], [726, 727], [726, 729], [753, 755], [753, 773], [784, 785], [784, 790], [786, 787], [790, 791], [790, 793], [805, 806], [805, 811], [807, 808], [812, 813], [812, 820], [830, 831], [839, 840], [839, 845], [841, 842], [847, 848], [847, 858], [855, 856], [855, 858], [858, 859], [858, 865], [860, 861], [865, 871], [877, 878], [877, 884], [879, 880], [917, 918], [925, 926], [925, 931], [927, 928], [936, 937], [936, 990], [939, 940], [939, 948], [948, 949], [948, 956], [956, 957], [956, 961], [964, 936], [964, 966], [970, 936], [970, 971], [975, 976], [975, 990], [979, 980], [983, 984], [987, 988], [1002, 1003], [1002, 1008], [1020, 1021], [1020, 1037], [1022, 1023], [1022, 1072], [1023, 1024], [1023, 1027], [1028, 1029], [1028, 1032], [1037, 1038], [1037, 1048], [1039, 1041], [1039, 1072], [1043, 1044], [1043, 1072], [1048, 1049], [1048, 1070], [1050, 1051], [1054, 1055], [1054, 1064], [1056, 1057], [1059, 1062], [1064, 1065]], "missing_branches": [[338, 342], [377, -331], [404, 403], [428, 431], [661, -653], [703, 705], [786, 788], [807, 809], [830, 832], [841, 843], [860, 862], [865, 866], [867, 868], [867, 869], [879, 881], [917, 919], [927, 929], [979, 990], [983, 990], [987, 990], [1050, 1054], [1056, 1072], [1059, 1060], [1064, 1067]]}, "": {"executed_lines": [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 14, 15, 16, 18, 43, 45, 47, 49, 69, 81, 84, 88, 89, 93, 96, 100, 108, 119, 126, 131, 133, 234, 237, 255, 267, 275, 319, 331, 380, 387, 422, 433, 475, 504, 543, 562, 617, 621, 629, 649, 667, 690, 696, 707, 723, 731, 778, 1016, 1074, 1075, 1084, 1086, 1087, 1088, 1091, 1093, 1094, 1095, 1096, 1097, 1098, 1099, 1100, 1101, 1102, 1103, 1104, 1105, 1108, 1109, 1110, 1111], "summary": {"covered_lines": 84, "num_statements": 86, "percent_covered": 95.74468085106383, "percent_covered_display": "96", "missing_lines": 2, "excluded_lines": 0, "percent_statements_covered": 97.67441860465117, "percent_statements_covered_display": "98", "num_branches": 8, "num_partial_branches": 2, "covered_branches": 6, "missing_branches": 2, "percent_branches_covered": 75.0, "percent_branches_covered_display": "75"}, "missing_lines": [1106, 1107], "excluded_lines": [], "start_line": 1, "executed_branches": [[1086, 1087], [1086, 1091], [1095, 1096], [1099, 1100], [1099, 1102], [1105, 1108]], "missing_branches": [[1095, 1111], [1105, 1106]]}}}, "custom_components/goecharger_api2/pygoecharger_ha/const.py": {"executed_lines": [1, 2, 6, 11, 12, 13, 14, 16, 17, 18, 19, 22, 23, 24, 25, 26, 29, 31, 32, 34, 37, 39, 40, 41, 46, 47, 48, 51, 52, 53, 56, 57, 58, 60, 61, 62, 63, 64, 65, 66, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 90, 111], "summary": {"covered_lines": 61, "num_statements": 61, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "executed_branches": [], "missing_branches": [], "functions": {"": {"executed_lines": [1, 2, 6, 11, 12, 13, 14, 16, 17, 18, 19, 22, 23, 24, 25, 26, 29, 31, 32, 34, 37, 39, 40, 41, 46, 47, 48, 51, 52, 53, 56, 57, 58, 60, 61, 62, 63, 64, 65, 66, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 90, 111], "summary": {"covered_lines": 61, "num_statements": 61, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 1, "executed_branches": [], "missing_branches": []}}, "classes": {"INTG_TYPE": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 0, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 56, "executed_branches": [], "missing_branches": []}, "CAR_VALUES": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 0, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 60, "executed_branches": [], "missing_branches": []}, "CT_VALUES": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 0, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 70, "executed_branches": [], "missing_branches": []}, "": {"executed_lines": [1, 2, 6, 11, 12, 13, 14, 16, 17, 18, 19, 22, 23, 24, 25, 26, 29, 31, 32, 34, 37, 39, 40, 41, 46, 47, 48, 51, 52, 53, 56, 57, 58, 60, 61, 62, 63, 64, 65, 66, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 90, 111], "summary": {"covered_lines": 61, "num_statements": 61, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 1, "executed_branches": [], "missing_branches": []}}}, "custom_components/goecharger_api2/pygoecharger_ha/keys.py": {"executed_lines": [1, 2, 3, 9, 11, 13, 14, 15, 16, 17, 20, 23, 24, 27, 29, 32, 36, 38, 40, 42, 44, 46, 48, 50, 52, 54, 56, 58, 60, 62, 64, 66, 68, 70, 72, 74, 76, 78, 80, 82, 84, 95, 98, 100, 102, 104, 107, 109, 111, 113, 115, 117, 119, 121, 123, 125, 127, 129, 131, 133, 135, 137, 139, 141, 143, 145, 147, 149, 151, 153, 155, 157, 159, 161, 163, 165, 167, 169, 171, 173, 175, 179, 181, 183, 185, 187, 189, 191, 193, 195, 197, 199, 201, 203, 205, 207, 209, 211, 213, 215, 217, 219, 221, 223, 225, 227, 229, 232, 234, 236, 238, 240, 242, 244, 246, 248, 250, 252, 254, 256, 258, 260, 262, 264, 266, 268, 270, 272, 274, 276, 278, 280, 282, 284, 286, 288, 290, 292, 294, 296, 298, 300, 302, 304, 306, 308, 310, 312, 314, 316, 318, 320, 322, 324, 326, 328, 330, 332, 334, 336, 338, 340, 342, 344, 346, 350, 353, 354, 355, 359, 361, 366, 372, 378, 384, 390, 396, 402, 409, 411, 413, 415, 426, 428, 430, 432, 434, 436, 438, 440, 442, 444, 446, 448, 450, 452, 454, 456, 458, 460, 462, 464, 466, 523, 525, 527, 529, 531, 533, 535, 537, 539, 541, 543, 545, 547, 549, 551, 560, 562, 564, 566, 568, 570, 664, 668, 670, 673, 689, 691, 693, 695, 697, 699, 701, 703, 705, 707, 737, 739, 743, 753, 767, 785, 787, 789, 791, 793, 805, 807, 809, 811, 813], "summary": {"covered_lines": 253, "num_statements": 255, "percent_covered": 99.2156862745098, "percent_covered_display": "99", "missing_lines": 2, "excluded_lines": 0, "percent_statements_covered": 99.2156862745098, "percent_statements_covered_display": "99", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [30, 33], "excluded_lines": [], "executed_branches": [], "missing_branches": [], "functions": {"Tag.__hash__": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 1, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 1, "excluded_lines": 0, "percent_statements_covered": 0.0, "percent_statements_covered_display": "0", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [30], "excluded_lines": [], "start_line": 29, "executed_branches": [], "missing_branches": []}, "Tag.__str__": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 1, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 1, "excluded_lines": 0, "percent_statements_covered": 0.0, "percent_statements_covered_display": "0", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [33], "excluded_lines": [], "start_line": 32, "executed_branches": [], "missing_branches": []}, "": {"executed_lines": [1, 2, 3, 9, 11, 13, 14, 15, 16, 17, 20, 23, 24, 27, 29, 32, 36, 38, 40, 42, 44, 46, 48, 50, 52, 54, 56, 58, 60, 62, 64, 66, 68, 70, 72, 74, 76, 78, 80, 82, 84, 95, 98, 100, 102, 104, 107, 109, 111, 113, 115, 117, 119, 121, 123, 125, 127, 129, 131, 133, 135, 137, 139, 141, 143, 145, 147, 149, 151, 153, 155, 157, 159, 161, 163, 165, 167, 169, 171, 173, 175, 179, 181, 183, 185, 187, 189, 191, 193, 195, 197, 199, 201, 203, 205, 207, 209, 211, 213, 215, 217, 219, 221, 223, 225, 227, 229, 232, 234, 236, 238, 240, 242, 244, 246, 248, 250, 252, 254, 256, 258, 260, 262, 264, 266, 268, 270, 272, 274, 276, 278, 280, 282, 284, 286, 288, 290, 292, 294, 296, 298, 300, 302, 304, 306, 308, 310, 312, 314, 316, 318, 320, 322, 324, 326, 328, 330, 332, 334, 336, 338, 340, 342, 344, 346, 350, 353, 354, 355, 359, 361, 366, 372, 378, 384, 390, 396, 402, 409, 411, 413, 415, 426, 428, 430, 432, 434, 436, 438, 440, 442, 444, 446, 448, 450, 452, 454, 456, 458, 460, 462, 464, 466, 523, 525, 527, 529, 531, 533, 535, 537, 539, 541, 543, 545, 547, 549, 551, 560, 562, 564, 566, 568, 570, 664, 668, 670, 673, 689, 691, 693, 695, 697, 699, 701, 703, 705, 707, 737, 739, 743, 753, 767, 785, 787, 789, 791, 793, 805, 807, 809, 811, 813], "summary": {"covered_lines": 253, "num_statements": 253, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 1, "executed_branches": [], "missing_branches": []}}, "classes": {"CAT": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 0, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 13, "executed_branches": [], "missing_branches": []}, "ApiKey": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 0, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 20, "executed_branches": [], "missing_branches": []}, "Tag": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 2, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 2, "excluded_lines": 0, "percent_statements_covered": 0.0, "percent_statements_covered_display": "0", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [30, 33], "excluded_lines": [], "start_line": 27, "executed_branches": [], "missing_branches": []}, "": {"executed_lines": [1, 2, 3, 9, 11, 13, 14, 15, 16, 17, 20, 23, 24, 27, 29, 32, 36, 38, 40, 42, 44, 46, 48, 50, 52, 54, 56, 58, 60, 62, 64, 66, 68, 70, 72, 74, 76, 78, 80, 82, 84, 95, 98, 100, 102, 104, 107, 109, 111, 113, 115, 117, 119, 121, 123, 125, 127, 129, 131, 133, 135, 137, 139, 141, 143, 145, 147, 149, 151, 153, 155, 157, 159, 161, 163, 165, 167, 169, 171, 173, 175, 179, 181, 183, 185, 187, 189, 191, 193, 195, 197, 199, 201, 203, 205, 207, 209, 211, 213, 215, 217, 219, 221, 223, 225, 227, 229, 232, 234, 236, 238, 240, 242, 244, 246, 248, 250, 252, 254, 256, 258, 260, 262, 264, 266, 268, 270, 272, 274, 276, 278, 280, 282, 284, 286, 288, 290, 292, 294, 296, 298, 300, 302, 304, 306, 308, 310, 312, 314, 316, 318, 320, 322, 324, 326, 328, 330, 332, 334, 336, 338, 340, 342, 344, 346, 350, 353, 354, 355, 359, 361, 366, 372, 378, 384, 390, 396, 402, 409, 411, 413, 415, 426, 428, 430, 432, 434, 436, 438, 440, 442, 444, 446, 448, 450, 452, 454, 456, 458, 460, 462, 464, 466, 523, 525, 527, 529, 531, 533, 535, 537, 539, 541, 543, 545, 547, 549, 551, 560, 562, 564, 566, 568, 570, 664, 668, 670, 673, 689, 691, 693, 695, 697, 699, 701, 703, 705, 707, 737, 739, 743, 753, 767, 785, 787, 789, 791, 793, 805, 807, 809, 811, 813], "summary": {"covered_lines": 253, "num_statements": 253, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 1, "executed_branches": [], "missing_branches": []}}}, "custom_components/goecharger_api2/select.py": {"executed_lines": [1, 3, 4, 5, 6, 7, 9, 10, 12, 13, 14, 16, 19, 37, 38, 39, 40, 43, 59, 61, 62, 63, 64, 65, 66, 67, 68, 70, 71, 73, 74, 76, 79, 82, 83, 84, 85, 87, 89, 90, 92, 93, 95, 96, 97, 98, 99, 101, 102, 104, 105, 107, 108, 109, 111, 113, 116, 117, 118, 119, 120, 122, 124, 126, 127, 129, 130, 133, 135, 165, 167, 169, 172, 173, 175, 178, 179, 181, 184, 185, 187, 190, 191, 193, 196, 198, 199, 200], "summary": {"covered_lines": 88, "num_statements": 100, "percent_covered": 86.53846153846153, "percent_covered_display": "87", "missing_lines": 12, "excluded_lines": 0, "percent_statements_covered": 88.0, "percent_statements_covered_display": "88", "num_branches": 56, "num_partial_branches": 1, "covered_branches": 47, "missing_branches": 9, "percent_branches_covered": 83.92857142857143, "percent_branches_covered_display": "84"}, "missing_lines": [20, 21, 22, 24, 25, 26, 27, 28, 30, 31, 32, 34], "excluded_lines": [], "executed_branches": [[39, 40], [39, 59], [65, 66], [66, 67], [66, 73], [67, 68], [67, 70], [73, 74], [73, 76], [79, 82], [79, 89], [82, 83], [82, 84], [84, 85], [84, 87], [89, 90], [89, 92], [92, 93], [92, 99], [104, 105], [104, 129], [105, 107], [105, 126], [108, 109], [108, 111], [111, 113], [111, 122], [116, 117], [116, 122], [117, 118], [117, 119], [119, 116], [119, 120], [129, 130], [129, 133], [133, 135], [133, 198], [165, 167], [165, 169], [172, 173], [172, 175], [178, 179], [178, 181], [184, 185], [184, 187], [190, 191], [190, 193]], "missing_branches": [[24, 25], [24, 30], [25, 26], [25, 34], [26, 25], [26, 27], [30, 31], [30, 34], [65, 79]], "functions": {"async_setup_entry": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 12, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 12, "excluded_lines": 0, "percent_statements_covered": 0.0, "percent_statements_covered_display": "0", "num_branches": 8, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 8, "percent_branches_covered": 0.0, "percent_branches_covered_display": "0"}, "missing_lines": [20, 21, 22, 24, 25, 26, 27, 28, 30, 31, 32, 34], "excluded_lines": [], "start_line": 19, "executed_branches": [], "missing_branches": [[24, 25], [24, 30], [25, 26], [25, 34], [26, 25], [26, 27], [30, 31], [30, 34]]}, "GoeChargerSelect.__init__": {"executed_lines": [39, 40, 43, 59], "summary": {"covered_lines": 4, "num_statements": 4, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 2, "num_partial_branches": 0, "covered_branches": 2, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 38, "executed_branches": [[39, 40], [39, 59]], "missing_branches": []}, "GoeChargerSelect.current_option": {"executed_lines": [63, 64, 65, 66, 67, 68, 70, 71, 73, 74, 76, 79, 82, 83, 84, 85, 87, 89, 90, 92, 93, 95, 96, 97, 98, 99], "summary": {"covered_lines": 26, "num_statements": 26, "percent_covered": 97.72727272727273, "percent_covered_display": "98", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 18, "num_partial_branches": 1, "covered_branches": 17, "missing_branches": 1, "percent_branches_covered": 94.44444444444444, "percent_branches_covered_display": "94"}, "missing_lines": [], "excluded_lines": [], "start_line": 62, "executed_branches": [[65, 66], [66, 67], [66, 73], [67, 68], [67, 70], [73, 74], [73, 76], [79, 82], [79, 89], [82, 83], [82, 84], [84, 85], [84, 87], [89, 90], [89, 92], [92, 93], [92, 99]], "missing_branches": [[65, 79]]}, "GoeChargerSelect.async_select_option": {"executed_lines": [102, 104, 105, 107, 108, 109, 111, 113, 116, 117, 118, 119, 120, 122, 124, 126, 127, 129, 130, 133, 135, 165, 167, 169, 172, 173, 175, 178, 179, 181, 184, 185, 187, 190, 191, 193, 196, 198, 199, 200], "summary": {"covered_lines": 40, "num_statements": 40, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 28, "num_partial_branches": 0, "covered_branches": 28, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 101, "executed_branches": [[104, 105], [104, 129], [105, 107], [105, 126], [108, 109], [108, 111], [111, 113], [111, 122], [116, 117], [116, 122], [117, 118], [117, 119], [119, 116], [119, 120], [129, 130], [129, 133], [133, 135], [133, 198], [165, 167], [165, 169], [172, 173], [172, 175], [178, 179], [178, 181], [184, 185], [184, 187], [190, 191], [190, 193]], "missing_branches": []}, "": {"executed_lines": [1, 3, 4, 5, 6, 7, 9, 10, 12, 13, 14, 16, 19, 37, 38, 61, 62, 101], "summary": {"covered_lines": 18, "num_statements": 18, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 1, "executed_branches": [], "missing_branches": []}}, "classes": {"GoeChargerSelect": {"executed_lines": [39, 40, 43, 59, 63, 64, 65, 66, 67, 68, 70, 71, 73, 74, 76, 79, 82, 83, 84, 85, 87, 89, 90, 92, 93, 95, 96, 97, 98, 99, 102, 104, 105, 107, 108, 109, 111, 113, 116, 117, 118, 119, 120, 122, 124, 126, 127, 129, 130, 133, 135, 165, 167, 169, 172, 173, 175, 178, 179, 181, 184, 185, 187, 190, 191, 193, 196, 198, 199, 200], "summary": {"covered_lines": 70, "num_statements": 70, "percent_covered": 99.15254237288136, "percent_covered_display": "99", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 48, "num_partial_branches": 1, "covered_branches": 47, "missing_branches": 1, "percent_branches_covered": 97.91666666666667, "percent_branches_covered_display": "98"}, "missing_lines": [], "excluded_lines": [], "start_line": 37, "executed_branches": [[39, 40], [39, 59], [65, 66], [66, 67], [66, 73], [67, 68], [67, 70], [73, 74], [73, 76], [79, 82], [79, 89], [82, 83], [82, 84], [84, 85], [84, 87], [89, 90], [89, 92], [92, 93], [92, 99], [104, 105], [104, 129], [105, 107], [105, 126], [108, 109], [108, 111], [111, 113], [111, 122], [116, 117], [116, 122], [117, 118], [117, 119], [119, 116], [119, 120], [129, 130], [129, 133], [133, 135], [133, 198], [165, 167], [165, 169], [172, 173], [172, 175], [178, 179], [178, 181], [184, 185], [184, 187], [190, 191], [190, 193]], "missing_branches": [[65, 79]]}, "": {"executed_lines": [1, 3, 4, 5, 6, 7, 9, 10, 12, 13, 14, 16, 19, 37, 38, 61, 62, 101], "summary": {"covered_lines": 18, "num_statements": 30, "percent_covered": 47.36842105263158, "percent_covered_display": "47", "missing_lines": 12, "excluded_lines": 0, "percent_statements_covered": 60.0, "percent_statements_covered_display": "60", "num_branches": 8, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 8, "percent_branches_covered": 0.0, "percent_branches_covered_display": "0"}, "missing_lines": [20, 21, 22, 24, 25, 26, 27, 28, 30, 31, 32, 34], "excluded_lines": [], "start_line": 1, "executed_branches": [], "missing_branches": [[24, 25], [24, 30], [25, 26], [25, 34], [26, 25], [26, 27], [30, 31], [30, 34]]}}}, "custom_components/goecharger_api2/sensor.py": {"executed_lines": [1, 2, 3, 4, 6, 7, 8, 9, 10, 11, 13, 14, 15, 16, 18, 21, 38, 39, 40, 42, 44, 45, 47, 48, 49, 50, 51, 52, 54, 55, 57, 58, 59, 60, 61, 65, 66, 67, 68, 69, 70, 71, 72, 77, 78, 81, 82, 83, 84, 85, 86, 88, 89, 91, 93, 94, 95, 96, 99, 100, 101, 102, 103, 105, 107, 109, 111, 112, 114, 115, 116, 118, 122, 125, 126, 127, 128, 130, 131, 133, 134, 135, 136, 137, 138, 139, 140, 141, 143, 144, 147, 148, 149, 150, 153], "summary": {"covered_lines": 95, "num_statements": 109, "percent_covered": 85.20710059171597, "percent_covered_display": "85", "missing_lines": 14, "excluded_lines": 0, "percent_statements_covered": 87.1559633027523, "percent_statements_covered_display": "87", "num_branches": 60, "num_partial_branches": 3, "covered_branches": 49, "missing_branches": 11, "percent_branches_covered": 81.66666666666667, "percent_branches_covered_display": "82"}, "missing_lines": [22, 23, 24, 26, 27, 28, 29, 30, 32, 33, 34, 36, 63, 145], "excluded_lines": [], "executed_branches": [[57, 58], [59, 60], [59, 61], [69, 70], [69, 111], [70, 71], [70, 94], [77, 78], [77, 93], [81, 82], [81, 83], [83, 84], [83, 85], [85, 86], [85, 88], [88, 89], [88, 91], [94, 95], [94, 96], [96, 99], [96, 109], [100, 101], [100, 107], [102, 103], [102, 105], [111, 112], [111, 114], [114, 115], [114, 122], [115, 116], [115, 118], [122, 125], [122, 133], [125, 126], [125, 130], [127, 128], [127, 130], [130, 131], [130, 133], [133, 134], [133, 135], [135, 136], [135, 137], [137, 138], [137, 153], [138, 139], [138, 140], [140, 141], [144, 147]], "missing_branches": [[26, 27], [26, 32], [27, 28], [27, 36], [28, 27], [28, 29], [32, 33], [32, 36], [57, 63], [140, 153], [144, 145]], "functions": {"async_setup_entry": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 12, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 12, "excluded_lines": 0, "percent_statements_covered": 0.0, "percent_statements_covered_display": "0", "num_branches": 8, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 8, "percent_branches_covered": 0.0, "percent_branches_covered_display": "0"}, "missing_lines": [22, 23, 24, 26, 27, 28, 29, 30, 32, 33, 34, 36], "excluded_lines": [], "start_line": 21, "executed_branches": [], "missing_branches": [[26, 27], [26, 32], [27, 28], [27, 36], [28, 27], [28, 29], [32, 33], [32, 36]]}, "GoeChargerSensor.__init__": {"executed_lines": [45], "summary": {"covered_lines": 1, "num_statements": 1, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 44, "executed_branches": [], "missing_branches": []}, "GoeChargerSensor._camel_to_snake": {"executed_lines": [49, 50, 51, 52], "summary": {"covered_lines": 4, "num_statements": 4, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 48, "executed_branches": [], "missing_branches": []}, "GoeChargerSensor.state": {"executed_lines": [57, 58, 59, 60, 61], "summary": {"covered_lines": 5, "num_statements": 6, "percent_covered": 80.0, "percent_covered_display": "80", "missing_lines": 1, "excluded_lines": 0, "percent_statements_covered": 83.33333333333333, "percent_statements_covered_display": "83", "num_branches": 4, "num_partial_branches": 1, "covered_branches": 3, "missing_branches": 1, "percent_branches_covered": 75.0, "percent_branches_covered_display": "75"}, "missing_lines": [63], "excluded_lines": [], "start_line": 55, "executed_branches": [[57, 58], [59, 60], [59, 61]], "missing_branches": [[57, 63]]}, "GoeChargerSensor.native_value": {"executed_lines": [67, 68, 69, 70, 71, 72, 77, 78, 81, 82, 83, 84, 85, 86, 88, 89, 91, 93, 94, 95, 96, 99, 100, 101, 102, 103, 105, 107, 109, 111, 112, 114, 115, 116, 118, 122, 125, 126, 127, 128, 130, 131, 133, 134, 135, 136, 137, 138, 139, 140, 141, 143, 144, 147, 148, 149, 150, 153], "summary": {"covered_lines": 58, "num_statements": 59, "percent_covered": 97.19626168224299, "percent_covered_display": "97", "missing_lines": 1, "excluded_lines": 0, "percent_statements_covered": 98.30508474576271, "percent_statements_covered_display": "98", "num_branches": 48, "num_partial_branches": 2, "covered_branches": 46, "missing_branches": 2, "percent_branches_covered": 95.83333333333333, "percent_branches_covered_display": "96"}, "missing_lines": [145], "excluded_lines": [], "start_line": 66, "executed_branches": [[69, 70], [69, 111], [70, 71], [70, 94], [77, 78], [77, 93], [81, 82], [81, 83], [83, 84], [83, 85], [85, 86], [85, 88], [88, 89], [88, 91], [94, 95], [94, 96], [96, 99], [96, 109], [100, 101], [100, 107], [102, 103], [102, 105], [111, 112], [111, 114], [114, 115], [114, 122], [115, 116], [115, 118], [122, 125], [122, 133], [125, 126], [125, 130], [127, 128], [127, 130], [130, 131], [130, 133], [133, 134], [133, 135], [135, 136], [135, 137], [137, 138], [137, 153], [138, 139], [138, 140], [140, 141], [144, 147]], "missing_branches": [[140, 153], [144, 145]]}, "": {"executed_lines": [1, 2, 3, 4, 6, 7, 8, 9, 10, 11, 13, 14, 15, 16, 18, 21, 38, 39, 40, 42, 44, 47, 48, 54, 55, 65, 66], "summary": {"covered_lines": 27, "num_statements": 27, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 1, "executed_branches": [], "missing_branches": []}}, "classes": {"GoeChargerSensor": {"executed_lines": [45, 49, 50, 51, 52, 57, 58, 59, 60, 61, 67, 68, 69, 70, 71, 72, 77, 78, 81, 82, 83, 84, 85, 86, 88, 89, 91, 93, 94, 95, 96, 99, 100, 101, 102, 103, 105, 107, 109, 111, 112, 114, 115, 116, 118, 122, 125, 126, 127, 128, 130, 131, 133, 134, 135, 136, 137, 138, 139, 140, 141, 143, 144, 147, 148, 149, 150, 153], "summary": {"covered_lines": 68, "num_statements": 70, "percent_covered": 95.90163934426229, "percent_covered_display": "96", "missing_lines": 2, "excluded_lines": 0, "percent_statements_covered": 97.14285714285714, "percent_statements_covered_display": "97", "num_branches": 52, "num_partial_branches": 3, "covered_branches": 49, "missing_branches": 3, "percent_branches_covered": 94.23076923076923, "percent_branches_covered_display": "94"}, "missing_lines": [63, 145], "excluded_lines": [], "start_line": 42, "executed_branches": [[57, 58], [59, 60], [59, 61], [69, 70], [69, 111], [70, 71], [70, 94], [77, 78], [77, 93], [81, 82], [81, 83], [83, 84], [83, 85], [85, 86], [85, 88], [88, 89], [88, 91], [94, 95], [94, 96], [96, 99], [96, 109], [100, 101], [100, 107], [102, 103], [102, 105], [111, 112], [111, 114], [114, 115], [114, 122], [115, 116], [115, 118], [122, 125], [122, 133], [125, 126], [125, 130], [127, 128], [127, 130], [130, 131], [130, 133], [133, 134], [133, 135], [135, 136], [135, 137], [137, 138], [137, 153], [138, 139], [138, 140], [140, 141], [144, 147]], "missing_branches": [[57, 63], [140, 153], [144, 145]]}, "": {"executed_lines": [1, 2, 3, 4, 6, 7, 8, 9, 10, 11, 13, 14, 15, 16, 18, 21, 38, 39, 40, 42, 44, 47, 48, 54, 55, 65, 66], "summary": {"covered_lines": 27, "num_statements": 39, "percent_covered": 57.4468085106383, "percent_covered_display": "57", "missing_lines": 12, "excluded_lines": 0, "percent_statements_covered": 69.23076923076923, "percent_statements_covered_display": "69", "num_branches": 8, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 8, "percent_branches_covered": 0.0, "percent_branches_covered_display": "0"}, "missing_lines": [22, 23, 24, 26, 27, 28, 29, 30, 32, 33, 34, 36], "excluded_lines": [], "start_line": 1, "executed_branches": [], "missing_branches": [[26, 27], [26, 32], [27, 28], [27, 36], [28, 27], [28, 29], [32, 33], [32, 36]]}}}, "custom_components/goecharger_api2/service.py": {"executed_lines": [1, 2, 3, 4, 6, 8, 9, 10, 11, 13, 14, 15, 17, 19, 21, 23, 25, 26, 30, 32, 33, 35, 37, 38, 39, 41, 42, 44, 45, 49, 50, 51, 53, 55, 56, 57, 60, 62, 63, 64, 65, 66, 67, 69, 71, 72, 73, 74, 75, 76, 77, 78, 80, 85, 86, 87, 88, 89, 95, 96, 97, 99, 100, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 115, 117, 118, 119, 126, 128, 129, 130, 132, 133, 134, 137, 138, 140, 142, 143, 144, 147, 149, 150, 151, 153, 154, 156, 157, 159], "summary": {"covered_lines": 101, "num_statements": 104, "percent_covered": 97.26027397260275, "percent_covered_display": "97", "missing_lines": 3, "excluded_lines": 0, "percent_statements_covered": 97.11538461538461, "percent_statements_covered_display": "97", "num_branches": 42, "num_partial_branches": 1, "covered_branches": 41, "missing_branches": 1, "percent_branches_covered": 97.61904761904762, "percent_branches_covered_display": "98"}, "missing_lines": [24, 27, 28], "excluded_lines": [], "executed_branches": [[23, 25], [37, -35], [37, 38], [60, 62], [60, 71], [63, 64], [63, 71], [64, 65], [64, 71], [66, 67], [66, 69], [74, 75], [74, 99], [75, 76], [75, 77], [77, 78], [77, 80], [88, 89], [88, 99], [96, 97], [96, 99], [99, -55], [99, 100], [103, 104], [103, 137], [110, 111], [110, 126], [118, -102], [118, 119], [129, -102], [129, 130], [133, -102], [133, 134], [137, -102], [137, 138], [143, 144], [143, 147], [149, 150], [149, 156], [150, 151], [150, 153]], "missing_branches": [[23, 24]], "functions": {"_is_finite_number": {"executed_lines": [23, 25, 26], "summary": {"covered_lines": 3, "num_statements": 6, "percent_covered": 50.0, "percent_covered_display": "50", "missing_lines": 3, "excluded_lines": 0, "percent_statements_covered": 50.0, "percent_statements_covered_display": "50", "num_branches": 2, "num_partial_branches": 1, "covered_branches": 1, "missing_branches": 1, "percent_branches_covered": 50.0, "percent_branches_covered_display": "50"}, "missing_lines": [24, 27, 28], "excluded_lines": [], "start_line": 21, "executed_branches": [[23, 25]], "missing_branches": [[23, 24]]}, "add_config_entry_to_service": {"executed_lines": [32, 33], "summary": {"covered_lines": 2, "num_statements": 2, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 30, "executed_branches": [], "missing_branches": []}, "remove_config_entry_from_service": {"executed_lines": [37, 38, 39], "summary": {"covered_lines": 3, "num_statements": 3, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 2, "num_partial_branches": 0, "covered_branches": 2, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 35, "executed_branches": [[37, -35], [37, 38]], "missing_branches": []}, "GoeChargerApiV2Service.__init__": {"executed_lines": [44, 45, 49, 50, 51, 53], "summary": {"covered_lines": 6, "num_statements": 6, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 42, "executed_branches": [], "missing_branches": []}, "GoeChargerApiV2Service.set_pv_data": {"executed_lines": [56, 57, 60, 62, 63, 64, 65, 66, 67, 69, 71, 72, 73, 74, 75, 76, 77, 78, 80, 85, 86, 87, 88, 89, 95, 96, 97, 99, 100], "summary": {"covered_lines": 29, "num_statements": 29, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 20, "num_partial_branches": 0, "covered_branches": 20, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 55, "executed_branches": [[60, 62], [60, 71], [63, 64], [63, 71], [64, 65], [64, 71], [66, 67], [66, 69], [74, 75], [74, 99], [75, 76], [75, 77], [77, 78], [77, 80], [88, 89], [88, 99], [96, 97], [96, 99], [99, -55], [99, 100]], "missing_branches": []}, "GoeChargerApiV2Service.stop_charging": {"executed_lines": [103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 115, 117, 118, 119, 126, 128, 129, 130, 132, 133, 134, 137, 138], "summary": {"covered_lines": 24, "num_statements": 24, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 12, "num_partial_branches": 0, "covered_branches": 12, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 102, "executed_branches": [[103, 104], [103, 137], [110, 111], [110, 126], [118, -102], [118, 119], [129, -102], [129, 130], [133, -102], [133, 134], [137, -102], [137, 138]], "missing_branches": []}, "GoeChargerApiV2Service.get_coordinator": {"executed_lines": [142, 143, 144, 147, 149, 150, 151, 153, 154, 156, 157, 159], "summary": {"covered_lines": 12, "num_statements": 12, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 6, "num_partial_branches": 0, "covered_branches": 6, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 140, "executed_branches": [[143, 144], [143, 147], [149, 150], [149, 156], [150, 151], [150, 153]], "missing_branches": []}, "": {"executed_lines": [1, 2, 3, 4, 6, 8, 9, 10, 11, 13, 14, 15, 17, 19, 21, 30, 35, 41, 42, 55, 102, 140], "summary": {"covered_lines": 22, "num_statements": 22, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 1, "executed_branches": [], "missing_branches": []}}, "classes": {"GoeChargerApiV2Service": {"executed_lines": [44, 45, 49, 50, 51, 53, 56, 57, 60, 62, 63, 64, 65, 66, 67, 69, 71, 72, 73, 74, 75, 76, 77, 78, 80, 85, 86, 87, 88, 89, 95, 96, 97, 99, 100, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 115, 117, 118, 119, 126, 128, 129, 130, 132, 133, 134, 137, 138, 142, 143, 144, 147, 149, 150, 151, 153, 154, 156, 157, 159], "summary": {"covered_lines": 71, "num_statements": 71, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 38, "num_partial_branches": 0, "covered_branches": 38, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 41, "executed_branches": [[60, 62], [60, 71], [63, 64], [63, 71], [64, 65], [64, 71], [66, 67], [66, 69], [74, 75], [74, 99], [75, 76], [75, 77], [77, 78], [77, 80], [88, 89], [88, 99], [96, 97], [96, 99], [99, -55], [99, 100], [103, 104], [103, 137], [110, 111], [110, 126], [118, -102], [118, 119], [129, -102], [129, 130], [133, -102], [133, 134], [137, -102], [137, 138], [143, 144], [143, 147], [149, 150], [149, 156], [150, 151], [150, 153]], "missing_branches": []}, "": {"executed_lines": [1, 2, 3, 4, 6, 8, 9, 10, 11, 13, 14, 15, 17, 19, 21, 23, 25, 26, 30, 32, 33, 35, 37, 38, 39, 41, 42, 55, 102, 140], "summary": {"covered_lines": 30, "num_statements": 33, "percent_covered": 89.1891891891892, "percent_covered_display": "89", "missing_lines": 3, "excluded_lines": 0, "percent_statements_covered": 90.9090909090909, "percent_statements_covered_display": "91", "num_branches": 4, "num_partial_branches": 1, "covered_branches": 3, "missing_branches": 1, "percent_branches_covered": 75.0, "percent_branches_covered_display": "75"}, "missing_lines": [24, 27, 28], "excluded_lines": [], "start_line": 1, "executed_branches": [[23, 25], [37, -35], [37, 38]], "missing_branches": [[23, 24]]}}}, "custom_components/goecharger_api2/switch.py": {"executed_lines": [1, 2, 4, 5, 6, 7, 8, 10, 11, 12, 14, 17, 35, 36, 37, 38, 40, 42, 43, 44, 46, 47, 48, 50, 52, 53, 55, 56, 58, 60, 61, 63, 64, 66, 68, 69, 70, 72, 73, 74, 76, 78, 79, 81, 82, 84, 86, 87, 88, 89, 91, 92, 93, 94, 95, 96, 97, 98, 100, 102, 103, 104, 105, 107, 108, 109, 110, 111, 113, 114, 115, 116, 118, 119, 121, 122, 124, 126, 128, 130, 131, 133, 134, 135, 137, 138, 140, 141, 143], "summary": {"covered_lines": 89, "num_statements": 101, "percent_covered": 85.23489932885906, "percent_covered_display": "85", "missing_lines": 12, "excluded_lines": 0, "percent_statements_covered": 88.11881188118812, "percent_statements_covered_display": "88", "num_branches": 48, "num_partial_branches": 2, "covered_branches": 38, "missing_branches": 10, "percent_branches_covered": 79.16666666666667, "percent_branches_covered_display": "79"}, "missing_lines": [18, 19, 20, 22, 23, 24, 25, 26, 28, 29, 30, 32], "excluded_lines": [], "executed_branches": [[43, 44], [43, 55], [44, 46], [44, 52], [47, 48], [47, 50], [55, 56], [55, 58], [69, 70], [69, 81], [70, 72], [70, 78], [73, 74], [73, 76], [81, 82], [81, 84], [95, 96], [96, 97], [96, 102], [97, 98], [97, 100], [102, 103], [104, 105], [104, 113], [113, 114], [113, 128], [114, 115], [114, 119], [115, 116], [115, 118], [119, 121], [119, 126], [121, 122], [121, 124], [133, 134], [133, 135], [140, 141], [140, 143]], "missing_branches": [[22, 23], [22, 28], [23, 24], [23, 32], [24, 23], [24, 25], [28, 29], [28, 32], [95, 113], [102, 104]], "functions": {"async_setup_entry": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 12, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 12, "excluded_lines": 0, "percent_statements_covered": 0.0, "percent_statements_covered_display": "0", "num_branches": 8, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 8, "percent_branches_covered": 0.0, "percent_branches_covered_display": "0"}, "missing_lines": [18, 19, 20, 22, 23, 24, 25, 26, 28, 29, 30, 32], "excluded_lines": [], "start_line": 17, "executed_branches": [], "missing_branches": [[22, 23], [22, 28], [23, 24], [23, 32], [24, 23], [24, 25], [28, 29], [28, 32]]}, "GoeChargerSwitch.__init__": {"executed_lines": [37, 38], "summary": {"covered_lines": 2, "num_statements": 2, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 36, "executed_branches": [], "missing_branches": []}, "GoeChargerSwitch.async_turn_on": {"executed_lines": [42, 43, 44, 46, 47, 48, 50, 52, 53, 55, 56, 58, 60, 61, 63, 64], "summary": {"covered_lines": 16, "num_statements": 16, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 8, "num_partial_branches": 0, "covered_branches": 8, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 40, "executed_branches": [[43, 44], [43, 55], [44, 46], [44, 52], [47, 48], [47, 50], [55, 56], [55, 58]], "missing_branches": []}, "GoeChargerSwitch.async_turn_off": {"executed_lines": [68, 69, 70, 72, 73, 74, 76, 78, 79, 81, 82, 84, 86, 87, 88, 89], "summary": {"covered_lines": 16, "num_statements": 16, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 8, "num_partial_branches": 0, "covered_branches": 8, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 66, "executed_branches": [[69, 70], [69, 81], [70, 72], [70, 78], [73, 74], [73, 76], [81, 82], [81, 84]], "missing_branches": []}, "GoeChargerSwitch.is_on": {"executed_lines": [93, 94, 95, 96, 97, 98, 100, 102, 103, 104, 105, 107, 108, 109, 110, 111, 113, 114, 115, 116, 118, 119, 121, 122, 124, 126, 128], "summary": {"covered_lines": 27, "num_statements": 27, "percent_covered": 95.74468085106383, "percent_covered_display": "96", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 20, "num_partial_branches": 2, "covered_branches": 18, "missing_branches": 2, "percent_branches_covered": 90.0, "percent_branches_covered_display": "90"}, "missing_lines": [], "excluded_lines": [], "start_line": 92, "executed_branches": [[95, 96], [96, 97], [96, 102], [97, 98], [97, 100], [102, 103], [104, 105], [104, 113], [113, 114], [113, 128], [114, 115], [114, 119], [115, 116], [115, 118], [119, 121], [119, 126], [121, 122], [121, 124]], "missing_branches": [[95, 113], [102, 104]]}, "GoeChargerSwitch.state": {"executed_lines": [133, 134, 135], "summary": {"covered_lines": 3, "num_statements": 3, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 2, "num_partial_branches": 0, "covered_branches": 2, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 131, "executed_branches": [[133, 134], [133, 135]], "missing_branches": []}, "GoeChargerSwitch.icon": {"executed_lines": [140, 141, 143], "summary": {"covered_lines": 3, "num_statements": 3, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 2, "num_partial_branches": 0, "covered_branches": 2, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 138, "executed_branches": [[140, 141], [140, 143]], "missing_branches": []}, "": {"executed_lines": [1, 2, 4, 5, 6, 7, 8, 10, 11, 12, 14, 17, 35, 36, 40, 66, 91, 92, 130, 131, 137, 138], "summary": {"covered_lines": 22, "num_statements": 22, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 1, "executed_branches": [], "missing_branches": []}}, "classes": {"GoeChargerSwitch": {"executed_lines": [37, 38, 42, 43, 44, 46, 47, 48, 50, 52, 53, 55, 56, 58, 60, 61, 63, 64, 68, 69, 70, 72, 73, 74, 76, 78, 79, 81, 82, 84, 86, 87, 88, 89, 93, 94, 95, 96, 97, 98, 100, 102, 103, 104, 105, 107, 108, 109, 110, 111, 113, 114, 115, 116, 118, 119, 121, 122, 124, 126, 128, 133, 134, 135, 140, 141, 143], "summary": {"covered_lines": 67, "num_statements": 67, "percent_covered": 98.13084112149532, "percent_covered_display": "98", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 40, "num_partial_branches": 2, "covered_branches": 38, "missing_branches": 2, "percent_branches_covered": 95.0, "percent_branches_covered_display": "95"}, "missing_lines": [], "excluded_lines": [], "start_line": 35, "executed_branches": [[43, 44], [43, 55], [44, 46], [44, 52], [47, 48], [47, 50], [55, 56], [55, 58], [69, 70], [69, 81], [70, 72], [70, 78], [73, 74], [73, 76], [81, 82], [81, 84], [95, 96], [96, 97], [96, 102], [97, 98], [97, 100], [102, 103], [104, 105], [104, 113], [113, 114], [113, 128], [114, 115], [114, 119], [115, 116], [115, 118], [119, 121], [119, 126], [121, 122], [121, 124], [133, 134], [133, 135], [140, 141], [140, 143]], "missing_branches": [[95, 113], [102, 104]]}, "": {"executed_lines": [1, 2, 4, 5, 6, 7, 8, 10, 11, 12, 14, 17, 35, 36, 40, 66, 91, 92, 130, 131, 137, 138], "summary": {"covered_lines": 22, "num_statements": 34, "percent_covered": 52.38095238095238, "percent_covered_display": "52", "missing_lines": 12, "excluded_lines": 0, "percent_statements_covered": 64.70588235294117, "percent_statements_covered_display": "65", "num_branches": 8, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 8, "percent_branches_covered": 0.0, "percent_branches_covered_display": "0"}, "missing_lines": [18, 19, 20, 22, 23, 24, 25, 26, 28, 29, 30, 32], "excluded_lines": [], "start_line": 1, "executed_branches": [], "missing_branches": [[22, 23], [22, 28], [23, 24], [23, 32], [24, 23], [24, 25], [28, 29], [28, 32]]}}}}, "totals": {"covered_lines": 2351, "num_statements": 2529, "percent_covered": 90.84323384526225, "percent_covered_display": "91", "missing_lines": 178, "excluded_lines": 0, "percent_statements_covered": 92.9616449189403, "percent_statements_covered_display": "93", "num_branches": 922, "num_partial_branches": 60, "covered_branches": 784, "missing_branches": 138, "percent_branches_covered": 85.03253796095444, "percent_branches_covered_display": "85"}} \ No newline at end of file diff --git a/tests/reports/2026-10-29/coverage-baseline.json b/tests/reports/2026-10-29/coverage-baseline.json new file mode 100644 index 0000000..f259c51 --- /dev/null +++ b/tests/reports/2026-10-29/coverage-baseline.json @@ -0,0 +1 @@ +{"meta": {"format": 3, "version": "7.16.2", "timestamp": "2026-10-29T11:24:54.047351", "branch_coverage": true, "show_contexts": false}, "files": {"custom_components/goecharger_api2/__init__.py": {"executed_lines": [1, 2, 3, 4, 5, 6, 7, 9, 10, 11, 12, 23, 24, 25, 30, 31, 32, 33, 34, 35, 36, 38, 45, 46, 60, 61, 63, 65, 66, 67, 68, 69, 70, 71, 73, 74, 75, 76, 77, 78, 81, 82, 85, 86, 87, 88, 89, 90, 91, 92, 93, 97, 98, 100, 103, 105, 108, 109, 110, 111, 112, 113, 116, 117, 118, 122, 123, 124, 125, 126, 127, 128, 129, 131, 132, 133, 135, 136, 137, 138, 140, 141, 187, 188, 190, 191, 192, 193, 194, 195, 196, 197, 198, 201, 202, 203, 204, 206, 209, 214, 215, 294, 296, 297, 298, 300, 301, 302, 303, 304, 306, 307, 310, 311, 312, 314, 315, 316, 325, 326, 336, 337, 339, 340, 341, 346, 347, 348, 349, 350, 351, 352, 353, 354, 356, 358, 359, 360, 361, 362, 363, 368, 371, 374, 375, 377, 403, 406, 437, 484, 493, 498, 508, 524, 528, 540, 549, 553, 557, 561, 562, 567, 569, 570, 572, 574, 575, 577, 582, 586, 587, 591, 595, 596, 597, 598, 599, 600, 601, 602, 604, 606, 608, 609, 613, 614, 615, 616, 617, 618, 619, 621, 622, 623, 624, 625, 631, 633, 634, 635, 636, 638, 639, 640, 642, 643, 644, 645, 646, 647, 648, 649, 651, 652, 653, 654, 656, 657, 658, 659, 660, 661, 663, 664, 665, 666, 667, 668, 669, 670, 672, 673, 674, 675, 677, 678, 679, 684, 685, 688, 692, 696, 700, 707, 710, 711, 712, 713, 714, 715, 719, 720, 721, 723, 725, 727, 729, 730, 736, 737, 738, 740, 742, 743, 750, 751, 752, 753, 754, 755, 756, 757, 758, 759, 760, 761, 763, 768, 769, 778, 779, 780, 783, 784, 785, 787, 788, 789, 791, 792, 794, 799, 800, 803, 805, 806, 808, 811, 812, 813, 819, 834, 836, 842, 863, 864, 866, 867, 870, 873, 874, 875, 877, 878, 879, 880, 885, 886, 887, 888, 889, 890, 892, 894, 895, 897, 898, 900, 907, 911, 912, 915, 916, 920, 921, 923, 925], "summary": {"covered_lines": 337, "num_statements": 604, "percent_covered": 50.0, "percent_covered_display": "50", "missing_lines": 267, "excluded_lines": 0, "percent_statements_covered": 55.794701986754966, "percent_statements_covered_display": "56", "num_branches": 266, "num_partial_branches": 36, "covered_branches": 98, "missing_branches": 168, "percent_branches_covered": 36.8421052631579, "percent_branches_covered_display": "37"}, "missing_lines": [80, 94, 95, 120, 144, 145, 148, 149, 150, 151, 153, 154, 155, 156, 158, 162, 165, 166, 173, 175, 176, 177, 179, 180, 182, 184, 210, 211, 216, 217, 218, 219, 220, 221, 224, 225, 226, 227, 228, 230, 231, 232, 233, 234, 236, 238, 240, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 254, 255, 257, 258, 259, 260, 261, 262, 269, 270, 271, 272, 273, 275, 276, 278, 279, 280, 281, 283, 284, 285, 286, 287, 288, 289, 290, 291, 343, 364, 378, 379, 380, 381, 382, 383, 384, 386, 387, 388, 390, 391, 392, 393, 394, 396, 398, 404, 410, 411, 412, 413, 416, 417, 418, 419, 423, 424, 425, 428, 431, 432, 433, 435, 438, 442, 445, 446, 448, 449, 452, 453, 454, 456, 457, 458, 460, 463, 464, 465, 468, 470, 471, 472, 473, 474, 475, 476, 478, 479, 481, 482, 486, 487, 494, 495, 496, 499, 500, 501, 502, 503, 504, 505, 506, 509, 510, 511, 512, 513, 514, 515, 516, 518, 519, 520, 521, 525, 526, 529, 530, 531, 532, 533, 534, 535, 536, 537, 538, 542, 544, 545, 546, 547, 550, 551, 554, 555, 558, 559, 565, 578, 579, 580, 583, 584, 588, 610, 611, 637, 680, 681, 682, 686, 693, 694, 701, 702, 703, 704, 716, 731, 732, 733, 734, 739, 745, 771, 772, 773, 774, 776, 797, 801, 814, 815, 817, 837, 838, 839, 840, 843, 845, 846, 848, 849, 852, 855, 858, 859, 861, 881, 882, 883, 909, 913, 918, 931, 932, 933, 935, 936, 938, 939, 940, 943, 944, 945, 947], "excluded_lines": [], "executed_branches": [[74, 75], [74, 85], [75, 76], [77, 78], [85, 86], [85, 100], [89, 90], [91, 92], [91, 97], [109, 110], [117, 118], [124, 125], [124, 135], [127, 128], [137, 138], [137, 140], [140, 141], [190, 191], [191, 192], [191, 201], [201, 202], [311, 312], [311, 314], [314, 315], [314, 325], [340, 341], [353, 354], [353, 356], [363, 368], [562, 567], [569, 570], [569, 572], [575, 577], [577, 582], [582, 586], [586, 587], [587, -561], [597, 598], [597, 604], [599, 600], [609, 613], [613, 614], [613, 617], [617, 618], [617, 621], [623, 624], [643, 644], [643, 647], [647, 648], [647, 651], [664, 665], [664, 668], [668, 669], [668, 672], [685, 688], [688, 692], [692, 696], [700, 707], [711, 712], [713, 714], [715, 719], [720, 721], [720, 723], [727, 729], [730, 736], [736, 737], [736, 742], [738, 740], [742, 743], [752, 753], [752, 778], [754, 755], [758, 759], [758, 763], [778, 779], [778, 783], [783, 784], [783, 787], [791, 792], [791, 794], [800, 803], [805, 806], [805, 808], [811, 812], [813, 819], [873, 874], [873, 885], [874, 875], [874, 877], [877, 878], [885, 886], [885, 887], [887, 888], [887, 889], [889, 890], [889, 892], [897, 898], [897, 900]], "missing_branches": [[75, 85], [77, 80], [89, 97], [109, 116], [117, 120], [127, 144], [140, 144], [148, 149], [148, 165], [149, 150], [149, 151], [153, 154], [153, 162], [165, 166], [165, 173], [173, 175], [173, 182], [175, 176], [175, 179], [190, 206], [201, 206], [218, -214], [218, 219], [220, -214], [220, 221], [225, 226], [225, 242], [226, 225], [226, 227], [227, 225], [227, 228], [228, 225], [228, 230], [230, 231], [230, 240], [231, 232], [231, 238], [232, 233], [232, 236], [242, -214], [242, 243], [246, 247], [246, 257], [247, 248], [247, 254], [249, 250], [249, 251], [254, 246], [254, 255], [260, 261], [260, 283], [262, 269], [262, 273], [269, 260], [269, 270], [271, 260], [271, 272], [273, 260], [273, 275], [276, 260], [276, 278], [278, 276], [278, 279], [280, 276], [280, 281], [284, -214], [284, 285], [285, 286], [285, 288], [340, 343], [363, 364], [379, -377], [379, 380], [380, -377], [380, 381], [382, 383], [382, 386], [386, -377], [386, 387], [387, 388], [387, 390], [391, -377], [391, 392], [393, 394], [393, 396], [411, 412], [411, 416], [417, 418], [417, 423], [432, 433], [432, 435], [448, 449], [448, 452], [463, 464], [463, 468], [470, 471], [470, 478], [472, 473], [472, 478], [494, -493], [494, 495], [499, -498], [499, 500], [509, 510], [509, 518], [514, -508], [514, 515], [519, -508], [519, 520], [532, 533], [532, 534], [550, -549], [550, 551], [562, 565], [575, 586], [577, 578], [582, 583], [586, -561], [587, 588], [599, -591], [609, 610], [623, 631], [685, 686], [688, 700], [692, 693], [700, 701], [702, 703], [702, 707], [711, 715], [713, 715], [715, 716], [727, 797], [730, 731], [731, 732], [731, 742], [732, 733], [732, 734], [738, 739], [742, 745], [754, 776], [773, 774], [773, 778], [800, 801], [811, 817], [813, 814], [843, 845], [843, 852], [845, 846], [845, 848], [848, 849], [848, 861], [852, 855], [852, 858], [858, 859], [858, 861], [877, 881], [881, 882], [881, 894], [932, 933], [932, 935], [935, 936], [935, 938], [939, 940], [939, 943], [943, 944], [943, 947], [944, 945], [944, 947]], "functions": {"async_migrate_entry": {"executed_lines": [74, 75, 76, 77, 78, 81, 82, 85, 86, 87, 88, 89, 90, 91, 92, 93, 97, 98, 100], "summary": {"covered_lines": 19, "num_statements": 22, "percent_covered": 82.3529411764706, "percent_covered_display": "82", "missing_lines": 3, "excluded_lines": 0, "percent_statements_covered": 86.36363636363636, "percent_statements_covered_display": "86", "num_branches": 12, "num_partial_branches": 3, "covered_branches": 9, "missing_branches": 3, "percent_branches_covered": 75.0, "percent_branches_covered_display": "75"}, "missing_lines": [80, 94, 95], "excluded_lines": [], "start_line": 73, "executed_branches": [[74, 75], [74, 85], [75, 76], [77, 78], [85, 86], [85, 100], [89, 90], [91, 92], [91, 97]], "missing_branches": [[75, 85], [77, 80], [89, 97]]}, "async_setup": {"executed_lines": [105], "summary": {"covered_lines": 1, "num_statements": 1, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 103, "executed_branches": [], "missing_branches": []}, "async_setup_entry": {"executed_lines": [109, 110, 111, 112, 113, 116, 117, 118, 122, 123, 124, 125, 126, 127, 128, 129, 131, 132, 133, 135, 136, 137, 138, 140, 141], "summary": {"covered_lines": 25, "num_statements": 48, "percent_covered": 45.833333333333336, "percent_covered_display": "46", "missing_lines": 23, "excluded_lines": 0, "percent_statements_covered": 52.083333333333336, "percent_statements_covered_display": "52", "num_branches": 24, "num_partial_branches": 4, "covered_branches": 8, "missing_branches": 16, "percent_branches_covered": 33.333333333333336, "percent_branches_covered_display": "33"}, "missing_lines": [120, 144, 145, 148, 149, 150, 151, 153, 154, 155, 156, 158, 162, 165, 166, 173, 175, 176, 177, 179, 180, 182, 184], "excluded_lines": [], "start_line": 108, "executed_branches": [[109, 110], [117, 118], [124, 125], [124, 135], [127, 128], [137, 138], [137, 140], [140, 141]], "missing_branches": [[109, 116], [117, 120], [127, 144], [140, 144], [148, 149], [148, 165], [149, 150], [149, 151], [153, 154], [153, 162], [165, 166], [165, 173], [173, 175], [173, 182], [175, 176], [175, 179]]}, "async_unload_entry": {"executed_lines": [188, 190, 191, 192, 193, 194, 195, 196, 197, 198, 201, 202, 203, 204, 206], "summary": {"covered_lines": 15, "num_statements": 15, "percent_covered": 90.47619047619048, "percent_covered_display": "90", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 6, "num_partial_branches": 2, "covered_branches": 4, "missing_branches": 2, "percent_branches_covered": 66.66666666666667, "percent_branches_covered_display": "67"}, "missing_lines": [], "excluded_lines": [], "start_line": 187, "executed_branches": [[190, 191], [191, 192], [191, 201], [201, 202]], "missing_branches": [[190, 206], [201, 206]]}, "entry_update_listener": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 2, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 2, "excluded_lines": 0, "percent_statements_covered": 0.0, "percent_statements_covered_display": "0", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [210, 211], "excluded_lines": [], "start_line": 209, "executed_branches": [], "missing_branches": []}, "check_and_write_to_16a": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 58, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 58, "excluded_lines": 0, "percent_statements_covered": 0.0, "percent_statements_covered_display": "0", "num_branches": 48, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 48, "percent_branches_covered": 0.0, "percent_branches_covered_display": "0"}, "missing_lines": [216, 217, 218, 219, 220, 221, 224, 225, 226, 227, 228, 230, 231, 232, 233, 234, 236, 238, 240, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 254, 255, 257, 258, 259, 260, 261, 262, 269, 270, 271, 272, 273, 275, 276, 278, 279, 280, 281, 283, 284, 285, 286, 287, 288, 289, 290, 291], "excluded_lines": [], "start_line": 215, "executed_branches": [], "missing_branches": [[218, -214], [218, 219], [220, -214], [220, 221], [225, 226], [225, 242], [226, 225], [226, 227], [227, 225], [227, 228], [228, 225], [228, 230], [230, 231], [230, 240], [231, 232], [231, 238], [232, 233], [232, 236], [242, -214], [242, 243], [246, 247], [246, 257], [247, 248], [247, 254], [249, 250], [249, 251], [254, 246], [254, 255], [260, 261], [260, 283], [262, 269], [262, 273], [269, 260], [269, 270], [271, 260], [271, 272], [273, 260], [273, 275], [276, 260], [276, 278], [278, 276], [278, 279], [280, 276], [280, 281], [284, -214], [284, 285], [285, 286], [285, 288]]}, "GoeChargerDataUpdateCoordinator.__init__": {"executed_lines": [301, 302, 303, 304, 306, 307, 310, 311, 312, 314, 315, 316, 325, 326, 336, 337, 339, 340, 341, 346, 347, 348, 349, 350, 351, 352, 353, 354, 356, 358, 359, 360, 361, 362, 363, 368, 371, 374, 375], "summary": {"covered_lines": 39, "num_statements": 41, "percent_covered": 92.15686274509804, "percent_covered_display": "92", "missing_lines": 2, "excluded_lines": 0, "percent_statements_covered": 95.1219512195122, "percent_statements_covered_display": "95", "num_branches": 10, "num_partial_branches": 2, "covered_branches": 8, "missing_branches": 2, "percent_branches_covered": 80.0, "percent_branches_covered_display": "80"}, "missing_lines": [343, 364], "excluded_lines": [], "start_line": 300, "executed_branches": [[311, 312], [311, 314], [314, 315], [314, 325], [340, 341], [353, 354], [353, 356], [363, 368]], "missing_branches": [[340, 343], [363, 364]]}, "GoeChargerDataUpdateCoordinator.call_later_update_device_registry": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 17, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 17, "excluded_lines": 0, "percent_statements_covered": 0.0, "percent_statements_covered_display": "0", "num_branches": 14, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 14, "percent_branches_covered": 0.0, "percent_branches_covered_display": "0"}, "missing_lines": [378, 379, 380, 381, 382, 383, 384, 386, 387, 388, 390, 391, 392, 393, 394, 396, 398], "excluded_lines": [], "start_line": 377, "executed_branches": [], "missing_branches": [[379, -377], [379, 380], [380, -377], [380, 381], [382, 383], [382, 386], [386, -377], [386, 387], [387, 388], [387, 390], [391, -377], [391, 392], [393, 394], [393, 396]]}, "GoeChargerDataUpdateCoordinator.cards_as_object_present": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 1, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 1, "excluded_lines": 0, "percent_statements_covered": 0.0, "percent_statements_covered_display": "0", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [404], "excluded_lines": [], "start_line": 403, "executed_branches": [], "missing_branches": []}, "GoeChargerDataUpdateCoordinator.force_async_update_now": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 16, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 16, "excluded_lines": 0, "percent_statements_covered": 0.0, "percent_statements_covered_display": "0", "num_branches": 6, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 6, "percent_branches_covered": 0.0, "percent_branches_covered_display": "0"}, "missing_lines": [410, 411, 412, 413, 416, 417, 418, 419, 423, 424, 425, 428, 431, 432, 433, 435], "excluded_lines": [], "start_line": 406, "executed_branches": [], "missing_branches": [[411, 412], [411, 416], [417, 418], [417, 423], [432, 433], [432, 435]]}, "GoeChargerDataUpdateCoordinator.start_websocket_and_wait_for_first_data": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 28, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 28, "excluded_lines": 0, "percent_statements_covered": 0.0, "percent_statements_covered_display": "0", "num_branches": 8, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 8, "percent_branches_covered": 0.0, "percent_branches_covered_display": "0"}, "missing_lines": [438, 442, 445, 446, 448, 449, 452, 453, 454, 456, 457, 458, 460, 463, 464, 465, 468, 470, 471, 472, 473, 474, 475, 476, 478, 479, 481, 482], "excluded_lines": [], "start_line": 437, "executed_branches": [], "missing_branches": [[448, 449], [448, 452], [463, 464], [463, 468], [470, 471], [470, 478], [472, 473], [472, 478]]}, "GoeChargerDataUpdateCoordinator.start_watchdog": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 2, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 2, "excluded_lines": 0, "percent_statements_covered": 0.0, "percent_statements_covered_display": "0", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [486, 487], "excluded_lines": [], "start_line": 484, "executed_branches": [], "missing_branches": []}, "GoeChargerDataUpdateCoordinator.stop_watchdog": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 3, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 3, "excluded_lines": 0, "percent_statements_covered": 0.0, "percent_statements_covered_display": "0", "num_branches": 2, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 2, "percent_branches_covered": 0.0, "percent_branches_covered_display": "0"}, "missing_lines": [494, 495, 496], "excluded_lines": [], "start_line": 493, "executed_branches": [], "missing_branches": [[494, -493], [494, 495]]}, "GoeChargerDataUpdateCoordinator._check_for_ws_task_and_cancel_if_running": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 8, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 8, "excluded_lines": 0, "percent_statements_covered": 0.0, "percent_statements_covered_display": "0", "num_branches": 2, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 2, "percent_branches_covered": 0.0, "percent_branches_covered_display": "0"}, "missing_lines": [499, 500, 501, 502, 503, 504, 505, 506], "excluded_lines": [], "start_line": 498, "executed_branches": [], "missing_branches": [[499, -498], [499, 500]]}, "GoeChargerDataUpdateCoordinator._async_watchdog_check": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 12, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 12, "excluded_lines": 0, "percent_statements_covered": 0.0, "percent_statements_covered_display": "0", "num_branches": 6, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 6, "percent_branches_covered": 0.0, "percent_branches_covered_display": "0"}, "missing_lines": [509, 510, 511, 512, 513, 514, 515, 516, 518, 519, 520, 521], "excluded_lines": [], "start_line": 508, "executed_branches": [], "missing_branches": [[509, 510], [509, 518], [514, -508], [514, 515], [519, -508], [519, 520]]}, "GoeChargerDataUpdateCoordinator.__call__": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 2, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 2, "excluded_lines": 0, "percent_statements_covered": 0.0, "percent_statements_covered_display": "0", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [525, 526], "excluded_lines": [], "start_line": 524, "executed_branches": [], "missing_branches": []}, "GoeChargerDataUpdateCoordinator.clear_data": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 10, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 10, "excluded_lines": 0, "percent_statements_covered": 0.0, "percent_statements_covered_display": "0", "num_branches": 2, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 2, "percent_branches_covered": 0.0, "percent_branches_covered_display": "0"}, "missing_lines": [529, 530, 531, 532, 533, 534, 535, 536, 537, 538], "excluded_lines": [], "start_line": 528, "executed_branches": [], "missing_branches": [[532, 533], [532, 534]]}, "GoeChargerDataUpdateCoordinator.trigger_restart_delayed": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 5, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 5, "excluded_lines": 0, "percent_statements_covered": 0.0, "percent_statements_covered_display": "0", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [542, 544, 545, 546, 547], "excluded_lines": [], "start_line": 540, "executed_branches": [], "missing_branches": []}, "GoeChargerDataUpdateCoordinator.cancel_existing_debouncer": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 2, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 2, "excluded_lines": 0, "percent_statements_covered": 0.0, "percent_statements_covered_display": "0", "num_branches": 2, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 2, "percent_branches_covered": 0.0, "percent_branches_covered_display": "0"}, "missing_lines": [550, 551], "excluded_lines": [], "start_line": 549, "executed_branches": [], "missing_branches": [[550, -549], [550, 551]]}, "GoeChargerDataUpdateCoordinator.request_update_in_sec": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 2, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 2, "excluded_lines": 0, "percent_statements_covered": 0.0, "percent_statements_covered_display": "0", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [554, 555], "excluded_lines": [], "start_line": 553, "executed_branches": [], "missing_branches": []}, "GoeChargerDataUpdateCoordinator._debounce_coordinator_update": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 2, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 2, "excluded_lines": 0, "percent_statements_covered": 0.0, "percent_statements_covered_display": "0", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [558, 559], "excluded_lines": [], "start_line": 557, "executed_branches": [], "missing_branches": []}, "GoeChargerDataUpdateCoordinator.handle_write_result": {"executed_lines": [562, 567, 569, 570, 572, 574, 575, 577, 582, 586, 587], "summary": {"covered_lines": 11, "num_statements": 18, "percent_covered": 59.375, "percent_covered_display": "59", "missing_lines": 7, "excluded_lines": 0, "percent_statements_covered": 61.111111111111114, "percent_statements_covered_display": "61", "num_branches": 14, "num_partial_branches": 6, "covered_branches": 8, "missing_branches": 6, "percent_branches_covered": 57.142857142857146, "percent_branches_covered_display": "57"}, "missing_lines": [565, 578, 579, 580, 583, 584, 588], "excluded_lines": [], "start_line": 561, "executed_branches": [[562, 567], [569, 570], [569, 572], [575, 577], [577, 582], [582, 586], [586, 587], [587, -561]], "missing_branches": [[562, 565], [575, 586], [577, 578], [582, 583], [586, -561], [587, 588]]}, "GoeChargerDataUpdateCoordinator._handle_client_connection_error": {"executed_lines": [595, 596, 597, 598, 599, 600, 601, 602, 604], "summary": {"covered_lines": 9, "num_statements": 9, "percent_covered": 92.3076923076923, "percent_covered_display": "92", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 4, "num_partial_branches": 1, "covered_branches": 3, "missing_branches": 1, "percent_branches_covered": 75.0, "percent_branches_covered_display": "75"}, "missing_lines": [], "excluded_lines": [], "start_line": 591, "executed_branches": [[597, 598], [597, 604], [599, 600]], "missing_branches": [[599, -591]]}, "GoeChargerDataUpdateCoordinator._async_update_data": {"executed_lines": [608, 609, 613, 614, 615, 616, 617, 618, 619, 621, 622, 623, 624, 625, 631, 633, 634, 635, 636, 638, 639, 640], "summary": {"covered_lines": 22, "num_statements": 25, "percent_covered": 84.84848484848484, "percent_covered_display": "85", "missing_lines": 3, "excluded_lines": 0, "percent_statements_covered": 88.0, "percent_statements_covered_display": "88", "num_branches": 8, "num_partial_branches": 2, "covered_branches": 6, "missing_branches": 2, "percent_branches_covered": 75.0, "percent_branches_covered_display": "75"}, "missing_lines": [610, 611, 637], "excluded_lines": [], "start_line": 606, "executed_branches": [[609, 613], [613, 614], [613, 617], [617, 618], [617, 621], [623, 624]], "missing_branches": [[609, 610], [623, 631]]}, "GoeChargerDataUpdateCoordinator.async_write_key": {"executed_lines": [643, 644, 645, 646, 647, 648, 649, 651, 652, 653, 654, 656, 657, 658, 659, 660, 661], "summary": {"covered_lines": 17, "num_statements": 17, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 4, "num_partial_branches": 0, "covered_branches": 4, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 642, "executed_branches": [[643, 644], [643, 647], [647, 648], [647, 651]], "missing_branches": []}, "GoeChargerDataUpdateCoordinator.async_write_multiple_keys": {"executed_lines": [664, 665, 666, 667, 668, 669, 670, 672, 673, 674, 675, 677, 678, 679], "summary": {"covered_lines": 14, "num_statements": 17, "percent_covered": 85.71428571428571, "percent_covered_display": "86", "missing_lines": 3, "excluded_lines": 0, "percent_statements_covered": 82.3529411764706, "percent_statements_covered_display": "82", "num_branches": 4, "num_partial_branches": 0, "covered_branches": 4, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [680, 681, 682], "excluded_lines": [], "start_line": 663, "executed_branches": [[664, 665], [664, 668], [668, 669], [668, 672]], "missing_branches": []}, "GoeChargerDataUpdateCoordinator.read_versions": {"executed_lines": [685, 688, 692, 696, 700, 707, 710, 711, 712, 713, 714, 715, 719, 720, 721, 723, 725, 727, 729, 730, 736, 737, 738, 740, 742, 743, 750, 751, 752, 753, 754, 755, 756, 757, 758, 759, 760, 761, 763, 768, 769, 778, 779, 780, 783, 784, 785, 787, 788, 789, 791, 792, 794, 799, 800, 803, 805, 806, 808, 811, 812, 813, 819, 834], "summary": {"covered_lines": 64, "num_statements": 88, "percent_covered": 66.90140845070422, "percent_covered_display": "67", "missing_lines": 24, "excluded_lines": 0, "percent_statements_covered": 72.72727272727273, "percent_statements_covered_display": "73", "num_branches": 54, "num_partial_branches": 15, "covered_branches": 31, "missing_branches": 23, "percent_branches_covered": 57.407407407407405, "percent_branches_covered_display": "57"}, "missing_lines": [686, 693, 694, 701, 702, 703, 704, 716, 731, 732, 733, 734, 739, 745, 771, 772, 773, 774, 776, 797, 801, 814, 815, 817], "excluded_lines": [], "start_line": 684, "executed_branches": [[685, 688], [688, 692], [692, 696], [700, 707], [711, 712], [713, 714], [715, 719], [720, 721], [720, 723], [727, 729], [730, 736], [736, 737], [736, 742], [738, 740], [742, 743], [752, 753], [752, 778], [754, 755], [758, 759], [758, 763], [778, 779], [778, 783], [783, 784], [783, 787], [791, 792], [791, 794], [800, 803], [805, 806], [805, 808], [811, 812], [813, 819]], "missing_branches": [[685, 686], [688, 700], [692, 693], [700, 701], [702, 703], [702, 707], [711, 715], [713, 715], [715, 716], [727, 797], [730, 731], [731, 732], [731, 742], [732, 733], [732, 734], [738, 739], [742, 745], [754, 776], [773, 774], [773, 778], [800, 801], [811, 817], [813, 814]]}, "GoeChargerDataUpdateCoordinator.check_for_16a_limit": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 4, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 4, "excluded_lines": 0, "percent_statements_covered": 0.0, "percent_statements_covered_display": "0", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [837, 838, 839, 840], "excluded_lines": [], "start_line": 836, "executed_branches": [], "missing_branches": []}, "GoeChargerDataUpdateCoordinator.is_valid_charger_entity": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 10, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 10, "excluded_lines": 0, "percent_statements_covered": 0.0, "percent_statements_covered_display": "0", "num_branches": 10, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 10, "percent_branches_covered": 0.0, "percent_branches_covered_display": "0"}, "missing_lines": [843, 845, 846, 848, 849, 852, 855, 858, 859, 861], "excluded_lines": [], "start_line": 842, "executed_branches": [], "missing_branches": [[843, 845], [843, 852], [845, 846], [845, 848], [848, 849], [848, 861], [852, 855], [852, 858], [858, 859], [858, 861]]}, "GoeChargerBaseEntity.__init__": {"executed_lines": [867, 870, 873, 874, 875, 877, 878, 879, 880, 885, 886, 887, 888, 889, 890, 892, 894, 895, 897, 898, 900], "summary": {"covered_lines": 21, "num_statements": 24, "percent_covered": 85.0, "percent_covered_display": "85", "missing_lines": 3, "excluded_lines": 0, "percent_statements_covered": 87.5, "percent_statements_covered_display": "88", "num_branches": 16, "num_partial_branches": 1, "covered_branches": 13, "missing_branches": 3, "percent_branches_covered": 81.25, "percent_branches_covered_display": "81"}, "missing_lines": [881, 882, 883], "excluded_lines": [], "start_line": 866, "executed_branches": [[873, 874], [873, 885], [874, 875], [874, 877], [877, 878], [885, 886], [885, 887], [887, 888], [887, 889], [889, 890], [889, 892], [897, 898], [897, 900]], "missing_branches": [[877, 881], [881, 882], [881, 894]]}, "GoeChargerBaseEntity._name_internal": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 1, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 1, "excluded_lines": 0, "percent_statements_covered": 0.0, "percent_statements_covered_display": "0", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [909], "excluded_lines": [], "start_line": 907, "executed_branches": [], "missing_branches": []}, "GoeChargerBaseEntity.device_info": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 1, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 1, "excluded_lines": 0, "percent_statements_covered": 0.0, "percent_statements_covered_display": "0", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [913], "excluded_lines": [], "start_line": 912, "executed_branches": [], "missing_branches": []}, "GoeChargerBaseEntity.available": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 1, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 1, "excluded_lines": 0, "percent_statements_covered": 0.0, "percent_statements_covered_display": "0", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [918], "excluded_lines": [], "start_line": 916, "executed_branches": [], "missing_branches": []}, "GoeChargerBaseEntity.unique_id": {"executed_lines": [923], "summary": {"covered_lines": 1, "num_statements": 1, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 921, "executed_branches": [], "missing_branches": []}, "GoeChargerBaseEntity._friendly_name_internal": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 12, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 12, "excluded_lines": 0, "percent_statements_covered": 0.0, "percent_statements_covered_display": "0", "num_branches": 10, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 10, "percent_branches_covered": 0.0, "percent_branches_covered_display": "0"}, "missing_lines": [931, 932, 933, 935, 936, 938, 939, 940, 943, 944, 945, 947], "excluded_lines": [], "start_line": 925, "executed_branches": [], "missing_branches": [[932, 933], [932, 935], [935, 936], [935, 938], [939, 940], [939, 943], [943, 944], [943, 947], [944, 945], [944, 947]]}, "": {"executed_lines": [1, 2, 3, 4, 5, 6, 7, 9, 10, 11, 12, 23, 24, 25, 30, 31, 32, 33, 34, 35, 36, 38, 45, 46, 60, 61, 63, 65, 66, 67, 68, 69, 70, 71, 73, 103, 108, 187, 209, 214, 215, 294, 296, 297, 298, 300, 377, 403, 406, 437, 484, 493, 498, 508, 524, 528, 540, 549, 553, 557, 561, 591, 606, 642, 663, 684, 836, 842, 863, 864, 866, 907, 911, 912, 915, 916, 920, 921, 925], "summary": {"covered_lines": 79, "num_statements": 79, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 1, "executed_branches": [], "missing_branches": []}}, "classes": {"GoeChargerDataUpdateCoordinator": {"executed_lines": [301, 302, 303, 304, 306, 307, 310, 311, 312, 314, 315, 316, 325, 326, 336, 337, 339, 340, 341, 346, 347, 348, 349, 350, 351, 352, 353, 354, 356, 358, 359, 360, 361, 362, 363, 368, 371, 374, 375, 562, 567, 569, 570, 572, 574, 575, 577, 582, 586, 587, 595, 596, 597, 598, 599, 600, 601, 602, 604, 608, 609, 613, 614, 615, 616, 617, 618, 619, 621, 622, 623, 624, 625, 631, 633, 634, 635, 636, 638, 639, 640, 643, 644, 645, 646, 647, 648, 649, 651, 652, 653, 654, 656, 657, 658, 659, 660, 661, 664, 665, 666, 667, 668, 669, 670, 672, 673, 674, 675, 677, 678, 679, 685, 688, 692, 696, 700, 707, 710, 711, 712, 713, 714, 715, 719, 720, 721, 723, 725, 727, 729, 730, 736, 737, 738, 740, 742, 743, 750, 751, 752, 753, 754, 755, 756, 757, 758, 759, 760, 761, 763, 768, 769, 778, 779, 780, 783, 784, 785, 787, 788, 789, 791, 792, 794, 799, 800, 803, 805, 806, 808, 811, 812, 813, 819, 834], "summary": {"covered_lines": 176, "num_statements": 339, "percent_covered": 49.079754601226995, "percent_covered_display": "49", "missing_lines": 163, "excluded_lines": 0, "percent_statements_covered": 51.91740412979351, "percent_statements_covered_display": "52", "num_branches": 150, "num_partial_branches": 26, "covered_branches": 64, "missing_branches": 86, "percent_branches_covered": 42.666666666666664, "percent_branches_covered_display": "43"}, "missing_lines": [343, 364, 378, 379, 380, 381, 382, 383, 384, 386, 387, 388, 390, 391, 392, 393, 394, 396, 398, 404, 410, 411, 412, 413, 416, 417, 418, 419, 423, 424, 425, 428, 431, 432, 433, 435, 438, 442, 445, 446, 448, 449, 452, 453, 454, 456, 457, 458, 460, 463, 464, 465, 468, 470, 471, 472, 473, 474, 475, 476, 478, 479, 481, 482, 486, 487, 494, 495, 496, 499, 500, 501, 502, 503, 504, 505, 506, 509, 510, 511, 512, 513, 514, 515, 516, 518, 519, 520, 521, 525, 526, 529, 530, 531, 532, 533, 534, 535, 536, 537, 538, 542, 544, 545, 546, 547, 550, 551, 554, 555, 558, 559, 565, 578, 579, 580, 583, 584, 588, 610, 611, 637, 680, 681, 682, 686, 693, 694, 701, 702, 703, 704, 716, 731, 732, 733, 734, 739, 745, 771, 772, 773, 774, 776, 797, 801, 814, 815, 817, 837, 838, 839, 840, 843, 845, 846, 848, 849, 852, 855, 858, 859, 861], "excluded_lines": [], "start_line": 294, "executed_branches": [[311, 312], [311, 314], [314, 315], [314, 325], [340, 341], [353, 354], [353, 356], [363, 368], [562, 567], [569, 570], [569, 572], [575, 577], [577, 582], [582, 586], [586, 587], [587, -561], [597, 598], [597, 604], [599, 600], [609, 613], [613, 614], [613, 617], [617, 618], [617, 621], [623, 624], [643, 644], [643, 647], [647, 648], [647, 651], [664, 665], [664, 668], [668, 669], [668, 672], [685, 688], [688, 692], [692, 696], [700, 707], [711, 712], [713, 714], [715, 719], [720, 721], [720, 723], [727, 729], [730, 736], [736, 737], [736, 742], [738, 740], [742, 743], [752, 753], [752, 778], [754, 755], [758, 759], [758, 763], [778, 779], [778, 783], [783, 784], [783, 787], [791, 792], [791, 794], [800, 803], [805, 806], [805, 808], [811, 812], [813, 819]], "missing_branches": [[340, 343], [363, 364], [379, -377], [379, 380], [380, -377], [380, 381], [382, 383], [382, 386], [386, -377], [386, 387], [387, 388], [387, 390], [391, -377], [391, 392], [393, 394], [393, 396], [411, 412], [411, 416], [417, 418], [417, 423], [432, 433], [432, 435], [448, 449], [448, 452], [463, 464], [463, 468], [470, 471], [470, 478], [472, 473], [472, 478], [494, -493], [494, 495], [499, -498], [499, 500], [509, 510], [509, 518], [514, -508], [514, 515], [519, -508], [519, 520], [532, 533], [532, 534], [550, -549], [550, 551], [562, 565], [575, 586], [577, 578], [582, 583], [586, -561], [587, 588], [599, -591], [609, 610], [623, 631], [685, 686], [688, 700], [692, 693], [700, 701], [702, 703], [702, 707], [711, 715], [713, 715], [715, 716], [727, 797], [730, 731], [731, 732], [731, 742], [732, 733], [732, 734], [738, 739], [742, 745], [754, 776], [773, 774], [773, 778], [800, 801], [811, 817], [813, 814], [843, 845], [843, 852], [845, 846], [845, 848], [848, 849], [848, 861], [852, 855], [852, 858], [858, 859], [858, 861]]}, "GoeChargerBaseEntity": {"executed_lines": [867, 870, 873, 874, 875, 877, 878, 879, 880, 885, 886, 887, 888, 889, 890, 892, 894, 895, 897, 898, 900, 923], "summary": {"covered_lines": 22, "num_statements": 40, "percent_covered": 53.03030303030303, "percent_covered_display": "53", "missing_lines": 18, "excluded_lines": 0, "percent_statements_covered": 55.0, "percent_statements_covered_display": "55", "num_branches": 26, "num_partial_branches": 1, "covered_branches": 13, "missing_branches": 13, "percent_branches_covered": 50.0, "percent_branches_covered_display": "50"}, "missing_lines": [881, 882, 883, 909, 913, 918, 931, 932, 933, 935, 936, 938, 939, 940, 943, 944, 945, 947], "excluded_lines": [], "start_line": 863, "executed_branches": [[873, 874], [873, 885], [874, 875], [874, 877], [877, 878], [885, 886], [885, 887], [887, 888], [887, 889], [889, 890], [889, 892], [897, 898], [897, 900]], "missing_branches": [[877, 881], [881, 882], [881, 894], [932, 933], [932, 935], [935, 936], [935, 938], [939, 940], [939, 943], [943, 944], [943, 947], [944, 945], [944, 947]]}, "": {"executed_lines": [1, 2, 3, 4, 5, 6, 7, 9, 10, 11, 12, 23, 24, 25, 30, 31, 32, 33, 34, 35, 36, 38, 45, 46, 60, 61, 63, 65, 66, 67, 68, 69, 70, 71, 73, 74, 75, 76, 77, 78, 81, 82, 85, 86, 87, 88, 89, 90, 91, 92, 93, 97, 98, 100, 103, 105, 108, 109, 110, 111, 112, 113, 116, 117, 118, 122, 123, 124, 125, 126, 127, 128, 129, 131, 132, 133, 135, 136, 137, 138, 140, 141, 187, 188, 190, 191, 192, 193, 194, 195, 196, 197, 198, 201, 202, 203, 204, 206, 209, 214, 215, 294, 296, 297, 298, 300, 377, 403, 406, 437, 484, 493, 498, 508, 524, 528, 540, 549, 553, 557, 561, 591, 606, 642, 663, 684, 836, 842, 863, 864, 866, 907, 911, 912, 915, 916, 920, 921, 925], "summary": {"covered_lines": 139, "num_statements": 225, "percent_covered": 50.79365079365079, "percent_covered_display": "51", "missing_lines": 86, "excluded_lines": 0, "percent_statements_covered": 61.77777777777778, "percent_statements_covered_display": "62", "num_branches": 90, "num_partial_branches": 9, "covered_branches": 21, "missing_branches": 69, "percent_branches_covered": 23.333333333333332, "percent_branches_covered_display": "23"}, "missing_lines": [80, 94, 95, 120, 144, 145, 148, 149, 150, 151, 153, 154, 155, 156, 158, 162, 165, 166, 173, 175, 176, 177, 179, 180, 182, 184, 210, 211, 216, 217, 218, 219, 220, 221, 224, 225, 226, 227, 228, 230, 231, 232, 233, 234, 236, 238, 240, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 254, 255, 257, 258, 259, 260, 261, 262, 269, 270, 271, 272, 273, 275, 276, 278, 279, 280, 281, 283, 284, 285, 286, 287, 288, 289, 290, 291], "excluded_lines": [], "start_line": 1, "executed_branches": [[74, 75], [74, 85], [75, 76], [77, 78], [85, 86], [85, 100], [89, 90], [91, 92], [91, 97], [109, 110], [117, 118], [124, 125], [124, 135], [127, 128], [137, 138], [137, 140], [140, 141], [190, 191], [191, 192], [191, 201], [201, 202]], "missing_branches": [[75, 85], [77, 80], [89, 97], [109, 116], [117, 120], [127, 144], [140, 144], [148, 149], [148, 165], [149, 150], [149, 151], [153, 154], [153, 162], [165, 166], [165, 173], [173, 175], [173, 182], [175, 176], [175, 179], [190, 206], [201, 206], [218, -214], [218, 219], [220, -214], [220, 221], [225, 226], [225, 242], [226, 225], [226, 227], [227, 225], [227, 228], [228, 225], [228, 230], [230, 231], [230, 240], [231, 232], [231, 238], [232, 233], [232, 236], [242, -214], [242, 243], [246, 247], [246, 257], [247, 248], [247, 254], [249, 250], [249, 251], [254, 246], [254, 255], [260, 261], [260, 283], [262, 269], [262, 273], [269, 260], [269, 270], [271, 260], [271, 272], [273, 260], [273, 275], [276, 260], [276, 278], [278, 276], [278, 279], [280, 276], [280, 281], [284, -214], [284, 285], [285, 286], [285, 288]]}}}, "custom_components/goecharger_api2/binary_sensor.py": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 71, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 71, "excluded_lines": 0, "percent_statements_covered": 0.0, "percent_statements_covered_display": "0", "num_branches": 34, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 34, "percent_branches_covered": 0.0, "percent_branches_covered_display": "0"}, "missing_lines": [1, 3, 4, 5, 6, 7, 9, 10, 11, 12, 15, 18, 19, 20, 21, 23, 24, 25, 26, 27, 29, 30, 31, 33, 36, 37, 38, 39, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 53, 54, 56, 58, 61, 62, 63, 64, 66, 67, 68, 69, 70, 72, 73, 74, 75, 76, 77, 78, 80, 81, 83, 84, 86, 88, 90, 92, 93, 95, 96, 98], "excluded_lines": [], "executed_branches": [], "missing_branches": [[23, 24], [23, 29], [24, 25], [24, 33], [25, 24], [25, 26], [29, 30], [29, 33], [45, 46], [45, 80], [46, 47], [46, 61], [47, 48], [47, 51], [51, 53], [51, 58], [53, 54], [53, 56], [61, 62], [61, 63], [63, 64], [63, 80], [67, 68], [67, 69], [69, 70], [69, 72], [80, 81], [80, 90], [81, 83], [81, 88], [83, 84], [83, 86], [95, 96], [95, 98]], "functions": {"async_setup_entry": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 12, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 12, "excluded_lines": 0, "percent_statements_covered": 0.0, "percent_statements_covered_display": "0", "num_branches": 8, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 8, "percent_branches_covered": 0.0, "percent_branches_covered_display": "0"}, "missing_lines": [19, 20, 21, 23, 24, 25, 26, 27, 29, 30, 31, 33], "excluded_lines": [], "start_line": 18, "executed_branches": [], "missing_branches": [[23, 24], [23, 29], [24, 25], [24, 33], [25, 24], [25, 26], [29, 30], [29, 33]]}, "GoeChargerApiV2BinarySensor.__init__": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 2, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 2, "excluded_lines": 0, "percent_statements_covered": 0.0, "percent_statements_covered_display": "0", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [38, 39], "excluded_lines": [], "start_line": 37, "executed_branches": [], "missing_branches": []}, "GoeChargerApiV2BinarySensor.is_on": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 36, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 36, "excluded_lines": 0, "percent_statements_covered": 0.0, "percent_statements_covered_display": "0", "num_branches": 24, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 24, "percent_branches_covered": 0.0, "percent_branches_covered_display": "0"}, "missing_lines": [43, 44, 45, 46, 47, 48, 49, 50, 51, 53, 54, 56, 58, 61, 62, 63, 64, 66, 67, 68, 69, 70, 72, 73, 74, 75, 76, 77, 78, 80, 81, 83, 84, 86, 88, 90], "excluded_lines": [], "start_line": 42, "executed_branches": [], "missing_branches": [[45, 46], [45, 80], [46, 47], [46, 61], [47, 48], [47, 51], [51, 53], [51, 58], [53, 54], [53, 56], [61, 62], [61, 63], [63, 64], [63, 80], [67, 68], [67, 69], [69, 70], [69, 72], [80, 81], [80, 90], [81, 83], [81, 88], [83, 84], [83, 86]]}, "GoeChargerApiV2BinarySensor.icon": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 3, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 3, "excluded_lines": 0, "percent_statements_covered": 0.0, "percent_statements_covered_display": "0", "num_branches": 2, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 2, "percent_branches_covered": 0.0, "percent_branches_covered_display": "0"}, "missing_lines": [95, 96, 98], "excluded_lines": [], "start_line": 93, "executed_branches": [], "missing_branches": [[95, 96], [95, 98]]}, "": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 18, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 18, "excluded_lines": 0, "percent_statements_covered": 0.0, "percent_statements_covered_display": "0", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [1, 3, 4, 5, 6, 7, 9, 10, 11, 12, 15, 18, 36, 37, 41, 42, 92, 93], "excluded_lines": [], "start_line": 1, "executed_branches": [], "missing_branches": []}}, "classes": {"GoeChargerApiV2BinarySensor": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 41, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 41, "excluded_lines": 0, "percent_statements_covered": 0.0, "percent_statements_covered_display": "0", "num_branches": 26, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 26, "percent_branches_covered": 0.0, "percent_branches_covered_display": "0"}, "missing_lines": [38, 39, 43, 44, 45, 46, 47, 48, 49, 50, 51, 53, 54, 56, 58, 61, 62, 63, 64, 66, 67, 68, 69, 70, 72, 73, 74, 75, 76, 77, 78, 80, 81, 83, 84, 86, 88, 90, 95, 96, 98], "excluded_lines": [], "start_line": 36, "executed_branches": [], "missing_branches": [[45, 46], [45, 80], [46, 47], [46, 61], [47, 48], [47, 51], [51, 53], [51, 58], [53, 54], [53, 56], [61, 62], [61, 63], [63, 64], [63, 80], [67, 68], [67, 69], [69, 70], [69, 72], [80, 81], [80, 90], [81, 83], [81, 88], [83, 84], [83, 86], [95, 96], [95, 98]]}, "": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 30, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 30, "excluded_lines": 0, "percent_statements_covered": 0.0, "percent_statements_covered_display": "0", "num_branches": 8, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 8, "percent_branches_covered": 0.0, "percent_branches_covered_display": "0"}, "missing_lines": [1, 3, 4, 5, 6, 7, 9, 10, 11, 12, 15, 18, 19, 20, 21, 23, 24, 25, 26, 27, 29, 30, 31, 33, 36, 37, 41, 42, 92, 93], "excluded_lines": [], "start_line": 1, "executed_branches": [], "missing_branches": [[23, 24], [23, 29], [24, 25], [24, 33], [25, 24], [25, 26], [29, 30], [29, 33]]}}}, "custom_components/goecharger_api2/button.py": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 31, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 31, "excluded_lines": 0, "percent_statements_covered": 0.0, "percent_statements_covered_display": "0", "num_branches": 8, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 8, "percent_branches_covered": 0.0, "percent_branches_covered_display": "0"}, "missing_lines": [1, 3, 4, 5, 6, 7, 9, 10, 11, 13, 16, 17, 18, 19, 21, 22, 23, 24, 25, 27, 28, 29, 31, 34, 35, 36, 38, 39, 40, 41, 42], "excluded_lines": [], "executed_branches": [], "missing_branches": [[21, 22], [21, 27], [22, 23], [22, 31], [23, 22], [23, 24], [27, 28], [27, 31]], "functions": {"async_setup_entry": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 12, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 12, "excluded_lines": 0, "percent_statements_covered": 0.0, "percent_statements_covered_display": "0", "num_branches": 8, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 8, "percent_branches_covered": 0.0, "percent_branches_covered_display": "0"}, "missing_lines": [17, 18, 19, 21, 22, 23, 24, 25, 27, 28, 29, 31], "excluded_lines": [], "start_line": 16, "executed_branches": [], "missing_branches": [[21, 22], [21, 27], [22, 23], [22, 31], [23, 22], [23, 24], [27, 28], [27, 31]]}, "GoeChargerApiV2Button.__init__": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 1, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 1, "excluded_lines": 0, "percent_statements_covered": 0.0, "percent_statements_covered_display": "0", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [36], "excluded_lines": [], "start_line": 35, "executed_branches": [], "missing_branches": []}, "GoeChargerApiV2Button.async_press": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 4, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 4, "excluded_lines": 0, "percent_statements_covered": 0.0, "percent_statements_covered_display": "0", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [39, 40, 41, 42], "excluded_lines": [], "start_line": 38, "executed_branches": [], "missing_branches": []}, "": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 14, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 14, "excluded_lines": 0, "percent_statements_covered": 0.0, "percent_statements_covered_display": "0", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [1, 3, 4, 5, 6, 7, 9, 10, 11, 13, 16, 34, 35, 38], "excluded_lines": [], "start_line": 1, "executed_branches": [], "missing_branches": []}}, "classes": {"GoeChargerApiV2Button": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 5, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 5, "excluded_lines": 0, "percent_statements_covered": 0.0, "percent_statements_covered_display": "0", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [36, 39, 40, 41, 42], "excluded_lines": [], "start_line": 34, "executed_branches": [], "missing_branches": []}, "": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 26, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 26, "excluded_lines": 0, "percent_statements_covered": 0.0, "percent_statements_covered_display": "0", "num_branches": 8, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 8, "percent_branches_covered": 0.0, "percent_branches_covered_display": "0"}, "missing_lines": [1, 3, 4, 5, 6, 7, 9, 10, 11, 13, 16, 17, 18, 19, 21, 22, 23, 24, 25, 27, 28, 29, 31, 34, 35, 38], "excluded_lines": [], "start_line": 1, "executed_branches": [], "missing_branches": [[21, 22], [21, 27], [22, 23], [22, 31], [23, 22], [23, 24], [27, 28], [27, 31]]}}}, "custom_components/goecharger_api2/config_flow.py": {"executed_lines": [1, 2, 3, 4, 6, 7, 8, 9, 10, 21, 22, 24, 33, 39, 41, 43, 44, 47, 50, 51, 52, 54, 56, 58, 59, 60, 62, 63, 64, 66, 67, 68, 70, 71, 72, 74, 96, 97, 98, 99, 101, 102, 105, 107, 111, 129, 131, 137, 138, 139, 140, 141, 143, 144, 145, 146, 147, 148, 149, 154, 155, 158, 160, 161, 163, 172, 196, 198, 204, 205, 207, 208, 230, 231, 233, 242, 268, 331, 332, 333, 334, 340, 341, 342, 343, 347, 349, 366, 367, 368, 370, 371, 372, 378, 379, 380, 381, 382, 385], "summary": {"covered_lines": 99, "num_statements": 178, "percent_covered": 54.054054054054056, "percent_covered_display": "54", "missing_lines": 79, "excluded_lines": 0, "percent_statements_covered": 55.61797752808989, "percent_statements_covered_display": "56", "num_branches": 44, "num_partial_branches": 7, "covered_branches": 21, "missing_branches": 23, "percent_branches_covered": 47.72727272727273, "percent_branches_covered_display": "48"}, "missing_lines": [75, 76, 78, 79, 80, 81, 82, 83, 84, 85, 87, 88, 89, 90, 91, 92, 93, 94, 142, 151, 152, 156, 209, 210, 211, 212, 213, 214, 215, 216, 217, 219, 220, 224, 225, 226, 228, 271, 275, 276, 277, 278, 279, 280, 281, 282, 284, 285, 289, 290, 306, 307, 308, 310, 311, 312, 316, 318, 319, 320, 321, 324, 325, 326, 327, 329, 344, 345, 351, 352, 353, 354, 357, 358, 359, 360, 376, 383, 384], "excluded_lines": [], "executed_branches": [[98, 99], [98, 107], [101, 102], [101, 105], [137, 138], [137, 163], [140, 141], [140, 160], [141, 143], [147, 148], [155, 158], [204, 205], [204, 233], [208, 230], [341, 342], [341, 349], [343, 347], [349, 366], [367, 368], [367, 385], [371, 372]], "missing_branches": [[78, 79], [78, 87], [141, 142], [147, 151], [155, 156], [208, 209], [209, 210], [209, 211], [215, 216], [215, 219], [225, 226], [225, 228], [280, 281], [280, 284], [284, 285], [284, 316], [343, 344], [349, 351], [353, 354], [353, 357], [359, 360], [359, 366], [371, 376]], "functions": {"GoeChargerApiV2FlowHandler.__init__": {"executed_lines": [56, 58, 59, 60, 62, 63, 64, 66, 67, 68, 70, 71, 72], "summary": {"covered_lines": 13, "num_statements": 13, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 54, "executed_branches": [], "missing_branches": []}, "GoeChargerApiV2FlowHandler.async_step_reconfigure": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 18, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 18, "excluded_lines": 0, "percent_statements_covered": 0.0, "percent_statements_covered_display": "0", "num_branches": 2, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 2, "percent_branches_covered": 0.0, "percent_branches_covered_display": "0"}, "missing_lines": [75, 76, 78, 79, 80, 81, 82, 83, 84, 85, 87, 88, 89, 90, 91, 92, 93, 94], "excluded_lines": [], "start_line": 74, "executed_branches": [], "missing_branches": [[78, 79], [78, 87]]}, "GoeChargerApiV2FlowHandler.async_step_user": {"executed_lines": [97, 98, 99, 101, 102, 105, 107, 111], "summary": {"covered_lines": 8, "num_statements": 8, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 4, "num_partial_branches": 0, "covered_branches": 4, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 96, "executed_branches": [[98, 99], [98, 107], [101, 102], [101, 105]], "missing_branches": []}, "GoeChargerApiV2FlowHandler.async_step_user_lan": {"executed_lines": [131, 137, 138, 139, 140, 141, 143, 144, 145, 146, 147, 148, 149, 154, 155, 158, 160, 161, 163, 172], "summary": {"covered_lines": 20, "num_statements": 24, "percent_covered": 79.41176470588235, "percent_covered_display": "79", "missing_lines": 4, "excluded_lines": 0, "percent_statements_covered": 83.33333333333333, "percent_statements_covered_display": "83", "num_branches": 10, "num_partial_branches": 3, "covered_branches": 7, "missing_branches": 3, "percent_branches_covered": 70.0, "percent_branches_covered_display": "70"}, "missing_lines": [142, 151, 152, 156], "excluded_lines": [], "start_line": 129, "executed_branches": [[137, 138], [137, 163], [140, 141], [140, 160], [141, 143], [147, 148], [155, 158]], "missing_branches": [[141, 142], [147, 151], [155, 156]]}, "GoeChargerApiV2FlowHandler.async_step_user_wan": {"executed_lines": [198, 204, 205, 207, 208, 230, 231, 233, 242], "summary": {"covered_lines": 9, "num_statements": 24, "percent_covered": 35.294117647058826, "percent_covered_display": "35", "missing_lines": 15, "excluded_lines": 0, "percent_statements_covered": 37.5, "percent_statements_covered_display": "38", "num_branches": 10, "num_partial_branches": 1, "covered_branches": 3, "missing_branches": 7, "percent_branches_covered": 30.0, "percent_branches_covered_display": "30"}, "missing_lines": [209, 210, 211, 212, 213, 214, 215, 216, 217, 219, 220, 224, 225, 226, 228], "excluded_lines": [], "start_line": 196, "executed_branches": [[204, 205], [204, 233], [208, 230]], "missing_branches": [[208, 209], [209, 210], [209, 211], [215, 216], [215, 219], [225, 226], [225, 228]]}, "GoeChargerApiV2FlowHandler._check_ws_connection_and_flags": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 29, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 29, "excluded_lines": 0, "percent_statements_covered": 0.0, "percent_statements_covered_display": "0", "num_branches": 4, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 4, "percent_branches_covered": 0.0, "percent_branches_covered_display": "0"}, "missing_lines": [271, 275, 276, 277, 278, 279, 280, 281, 282, 284, 285, 289, 290, 306, 307, 308, 310, 311, 312, 316, 318, 319, 320, 321, 324, 325, 326, 327, 329], "excluded_lines": [], "start_line": 268, "executed_branches": [], "missing_branches": [[280, 281], [280, 284], [284, 285], [284, 316]]}, "GoeChargerApiV2FlowHandler._test_host": {"executed_lines": [332, 333, 334, 340, 341, 342, 343, 347, 349, 366, 367, 368, 370, 371, 372, 378, 379, 380, 381, 382, 385], "summary": {"covered_lines": 21, "num_statements": 34, "percent_covered": 58.333333333333336, "percent_covered_display": "58", "missing_lines": 13, "excluded_lines": 0, "percent_statements_covered": 61.76470588235294, "percent_statements_covered_display": "62", "num_branches": 14, "num_partial_branches": 3, "covered_branches": 7, "missing_branches": 7, "percent_branches_covered": 50.0, "percent_branches_covered_display": "50"}, "missing_lines": [344, 345, 351, 352, 353, 354, 357, 358, 359, 360, 376, 383, 384], "excluded_lines": [], "start_line": 331, "executed_branches": [[341, 342], [341, 349], [343, 347], [349, 366], [367, 368], [367, 385], [371, 372]], "missing_branches": [[343, 344], [349, 351], [353, 354], [353, 357], [359, 360], [359, 366], [371, 376]]}, "": {"executed_lines": [1, 2, 3, 4, 6, 7, 8, 9, 10, 21, 22, 24, 33, 39, 41, 43, 44, 47, 50, 51, 52, 54, 74, 96, 129, 196, 268, 331], "summary": {"covered_lines": 28, "num_statements": 28, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 1, "executed_branches": [], "missing_branches": []}}, "classes": {"GoeChargerApiV2FlowHandler": {"executed_lines": [56, 58, 59, 60, 62, 63, 64, 66, 67, 68, 70, 71, 72, 97, 98, 99, 101, 102, 105, 107, 111, 131, 137, 138, 139, 140, 141, 143, 144, 145, 146, 147, 148, 149, 154, 155, 158, 160, 161, 163, 172, 198, 204, 205, 207, 208, 230, 231, 233, 242, 332, 333, 334, 340, 341, 342, 343, 347, 349, 366, 367, 368, 370, 371, 372, 378, 379, 380, 381, 382, 385], "summary": {"covered_lines": 71, "num_statements": 150, "percent_covered": 47.422680412371136, "percent_covered_display": "47", "missing_lines": 79, "excluded_lines": 0, "percent_statements_covered": 47.333333333333336, "percent_statements_covered_display": "47", "num_branches": 44, "num_partial_branches": 7, "covered_branches": 21, "missing_branches": 23, "percent_branches_covered": 47.72727272727273, "percent_branches_covered_display": "48"}, "missing_lines": [75, 76, 78, 79, 80, 81, 82, 83, 84, 85, 87, 88, 89, 90, 91, 92, 93, 94, 142, 151, 152, 156, 209, 210, 211, 212, 213, 214, 215, 216, 217, 219, 220, 224, 225, 226, 228, 271, 275, 276, 277, 278, 279, 280, 281, 282, 284, 285, 289, 290, 306, 307, 308, 310, 311, 312, 316, 318, 319, 320, 321, 324, 325, 326, 327, 329, 344, 345, 351, 352, 353, 354, 357, 358, 359, 360, 376, 383, 384], "excluded_lines": [], "start_line": 47, "executed_branches": [[98, 99], [98, 107], [101, 102], [101, 105], [137, 138], [137, 163], [140, 141], [140, 160], [141, 143], [147, 148], [155, 158], [204, 205], [204, 233], [208, 230], [341, 342], [341, 349], [343, 347], [349, 366], [367, 368], [367, 385], [371, 372]], "missing_branches": [[78, 79], [78, 87], [141, 142], [147, 151], [155, 156], [208, 209], [209, 210], [209, 211], [215, 216], [215, 219], [225, 226], [225, 228], [280, 281], [280, 284], [284, 285], [284, 316], [343, 344], [349, 351], [353, 354], [353, 357], [359, 360], [359, 366], [371, 376]]}, "": {"executed_lines": [1, 2, 3, 4, 6, 7, 8, 9, 10, 21, 22, 24, 33, 39, 41, 43, 44, 47, 50, 51, 52, 54, 74, 96, 129, 196, 268, 331], "summary": {"covered_lines": 28, "num_statements": 28, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 1, "executed_branches": [], "missing_branches": []}}}, "custom_components/goecharger_api2/const.py": {"executed_lines": [1, 2, 4, 5, 6, 7, 8, 9, 10, 16, 17, 20, 21, 22, 23, 25, 26, 28, 37, 38, 39, 40, 42, 43, 45, 46, 47, 48, 49, 52, 53, 54, 57, 58, 59, 60, 61, 62, 63, 66, 67, 68, 71, 72, 73, 74, 75, 76, 77, 80, 81, 82, 83, 84, 87, 89, 216, 242, 710, 837, 1953, 2086, 2097, 2315, 2317, 2327, 2329, 2385, 3242], "summary": {"covered_lines": 69, "num_statements": 69, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "executed_branches": [], "missing_branches": [], "functions": {"": {"executed_lines": [1, 2, 4, 5, 6, 7, 8, 9, 10, 16, 17, 20, 21, 22, 23, 25, 26, 28, 37, 38, 39, 40, 42, 43, 45, 46, 47, 48, 49, 52, 53, 54, 57, 58, 59, 60, 61, 62, 63, 66, 67, 68, 71, 72, 73, 74, 75, 76, 77, 80, 81, 82, 83, 84, 87, 89, 216, 242, 710, 837, 1953, 2086, 2097, 2315, 2317, 2327, 2329, 2385, 3242], "summary": {"covered_lines": 69, "num_statements": 69, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 1, "executed_branches": [], "missing_branches": []}}, "classes": {"ExtBinarySensorEntityDescription": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 0, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 46, "executed_branches": [], "missing_branches": []}, "ExtButtonEntityDescription": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 0, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 53, "executed_branches": [], "missing_branches": []}, "ExtNumberEntityDescription": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 0, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 58, "executed_branches": [], "missing_branches": []}, "ExtSelectEntityDescription": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 0, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 67, "executed_branches": [], "missing_branches": []}, "ExtSensorEntityDescription": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 0, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 72, "executed_branches": [], "missing_branches": []}, "ExtSwitchEntityDescription": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 0, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 81, "executed_branches": [], "missing_branches": []}, "": {"executed_lines": [1, 2, 4, 5, 6, 7, 8, 9, 10, 16, 17, 20, 21, 22, 23, 25, 26, 28, 37, 38, 39, 40, 42, 43, 45, 46, 47, 48, 49, 52, 53, 54, 57, 58, 59, 60, 61, 62, 63, 66, 67, 68, 71, 72, 73, 74, 75, 76, 77, 80, 81, 82, 83, 84, 87, 89, 216, 242, 710, 837, 1953, 2086, 2097, 2315, 2317, 2327, 2329, 2385, 3242], "summary": {"covered_lines": 69, "num_statements": 69, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 1, "executed_branches": [], "missing_branches": []}}}, "custom_components/goecharger_api2/entity.py": {"executed_lines": [1, 3, 4, 5, 7, 9, 13, 16], "summary": {"covered_lines": 8, "num_statements": 31, "percent_covered": 17.77777777777778, "percent_covered_display": "18", "missing_lines": 23, "excluded_lines": 0, "percent_statements_covered": 25.806451612903224, "percent_statements_covered_display": "26", "num_branches": 14, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 14, "percent_branches_covered": 0.0, "percent_branches_covered_display": "0"}, "missing_lines": [20, 22, 23, 26, 27, 30, 33, 34, 35, 36, 38, 39, 40, 41, 42, 44, 45, 46, 49, 50, 51, 52, 54], "excluded_lines": [], "executed_branches": [], "missing_branches": [[22, 23], [22, 26], [26, 27], [26, 30], [33, 34], [33, 54], [38, 39], [38, 44], [39, 38], [39, 40], [44, 45], [44, 49], [49, 50], [49, 54]], "functions": {"CustomFriendlyNameEntity._Entity__async_calculate_state": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 23, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 23, "excluded_lines": 0, "percent_statements_covered": 0.0, "percent_statements_covered_display": "0", "num_branches": 14, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 14, "percent_branches_covered": 0.0, "percent_branches_covered_display": "0"}, "missing_lines": [20, 22, 23, 26, 27, 30, 33, 34, 35, 36, 38, 39, 40, 41, 42, 44, 45, 46, 49, 50, 51, 52, 54], "excluded_lines": [], "start_line": 16, "executed_branches": [], "missing_branches": [[22, 23], [22, 26], [26, 27], [26, 30], [33, 34], [33, 54], [38, 39], [38, 44], [39, 38], [39, 40], [44, 45], [44, 49], [49, 50], [49, 54]]}, "": {"executed_lines": [1, 3, 4, 5, 7, 9, 13, 16], "summary": {"covered_lines": 8, "num_statements": 8, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 1, "executed_branches": [], "missing_branches": []}}, "classes": {"CustomFriendlyNameEntity": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 23, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 23, "excluded_lines": 0, "percent_statements_covered": 0.0, "percent_statements_covered_display": "0", "num_branches": 14, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 14, "percent_branches_covered": 0.0, "percent_branches_covered_display": "0"}, "missing_lines": [20, 22, 23, 26, 27, 30, 33, 34, 35, 36, 38, 39, 40, 41, 42, 44, 45, 46, 49, 50, 51, 52, 54], "excluded_lines": [], "start_line": 13, "executed_branches": [], "missing_branches": [[22, 23], [22, 26], [26, 27], [26, 30], [33, 34], [33, 54], [38, 39], [38, 44], [39, 38], [39, 40], [44, 45], [44, 49], [49, 50], [49, 54]]}, "": {"executed_lines": [1, 3, 4, 5, 7, 9, 13, 16], "summary": {"covered_lines": 8, "num_statements": 8, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 1, "executed_branches": [], "missing_branches": []}}}, "custom_components/goecharger_api2/number.py": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 94, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 94, "excluded_lines": 0, "percent_statements_covered": 0.0, "percent_statements_covered_display": "0", "num_branches": 48, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 48, "percent_branches_covered": 0.0, "percent_branches_covered_display": "0"}, "missing_lines": [1, 2, 4, 10, 11, 12, 13, 15, 16, 17, 19, 21, 22, 23, 24, 26, 27, 28, 31, 32, 34, 36, 37, 39, 40, 41, 43, 46, 47, 48, 50, 52, 57, 58, 60, 61, 62, 63, 64, 65, 66, 68, 70, 71, 72, 73, 74, 76, 77, 78, 80, 81, 83, 84, 87, 89, 91, 93, 94, 96, 98, 99, 100, 103, 104, 105, 108, 110, 113, 114, 115, 118, 119, 120, 123, 125, 126, 128, 129, 130, 131, 133, 134, 135, 137, 138, 139, 140, 142, 143, 144, 146, 147, 148], "excluded_lines": [], "executed_branches": [], "missing_branches": [[26, 27], [26, 39], [27, 28], [27, 43], [28, 31], [28, 36], [31, 32], [31, 34], [39, 40], [39, 43], [57, -50], [57, 58], [64, 65], [64, 70], [65, 66], [65, 68], [70, 71], [70, 72], [72, 73], [72, 77], [73, 74], [73, 76], [77, 78], [77, 87], [94, 96], [94, 113], [98, 99], [98, 100], [100, 103], [100, 104], [104, 105], [104, 108], [113, 114], [113, 115], [115, 118], [115, 119], [119, 120], [119, 123], [129, -128], [129, 130], [131, -128], [131, 133], [138, 139], [138, 146], [143, 144], [143, 146], [146, -128], [146, 147]], "functions": {"async_setup_entry": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 15, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 15, "excluded_lines": 0, "percent_statements_covered": 0.0, "percent_statements_covered_display": "0", "num_branches": 10, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 10, "percent_branches_covered": 0.0, "percent_branches_covered_display": "0"}, "missing_lines": [22, 23, 24, 26, 27, 28, 31, 32, 34, 36, 37, 39, 40, 41, 43], "excluded_lines": [], "start_line": 21, "executed_branches": [], "missing_branches": [[26, 27], [26, 39], [27, 28], [27, 43], [28, 31], [28, 36], [31, 32], [31, 34], [39, 40], [39, 43]]}, "GoeChargerNumber.__init__": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 1, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 1, "excluded_lines": 0, "percent_statements_covered": 0.0, "percent_statements_covered_display": "0", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [48], "excluded_lines": [], "start_line": 47, "executed_branches": [], "missing_branches": []}, "GoeChargerNumber.async_added_to_hass": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 3, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 3, "excluded_lines": 0, "percent_statements_covered": 0.0, "percent_statements_covered_display": "0", "num_branches": 2, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 2, "percent_branches_covered": 0.0, "percent_branches_covered_display": "0"}, "missing_lines": [52, 57, 58], "excluded_lines": [], "start_line": 50, "executed_branches": [], "missing_branches": [[57, -50], [57, 58]]}, "GoeChargerNumber.native_value": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 20, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 20, "excluded_lines": 0, "percent_statements_covered": 0.0, "percent_statements_covered_display": "0", "num_branches": 12, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 12, "percent_branches_covered": 0.0, "percent_branches_covered_display": "0"}, "missing_lines": [62, 63, 64, 65, 66, 68, 70, 71, 72, 73, 74, 76, 77, 78, 80, 81, 83, 84, 87, 89], "excluded_lines": [], "start_line": 61, "executed_branches": [], "missing_branches": [[64, 65], [64, 70], [65, 66], [65, 68], [70, 71], [70, 72], [72, 73], [72, 77], [73, 74], [73, 76], [77, 78], [77, 87]]}, "GoeChargerNumber.async_set_native_value": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 20, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 20, "excluded_lines": 0, "percent_statements_covered": 0.0, "percent_statements_covered_display": "0", "num_branches": 14, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 14, "percent_branches_covered": 0.0, "percent_branches_covered_display": "0"}, "missing_lines": [93, 94, 96, 98, 99, 100, 103, 104, 105, 108, 110, 113, 114, 115, 118, 119, 120, 123, 125, 126], "excluded_lines": [], "start_line": 91, "executed_branches": [], "missing_branches": [[94, 96], [94, 113], [98, 99], [98, 100], [100, 103], [100, 104], [104, 105], [104, 108], [113, 114], [113, 115], [115, 118], [115, 119], [119, 120], [119, 123]]}, "GoeChargerNumber.check_amp_min_max": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 16, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 16, "excluded_lines": 0, "percent_statements_covered": 0.0, "percent_statements_covered_display": "0", "num_branches": 10, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 10, "percent_branches_covered": 0.0, "percent_branches_covered_display": "0"}, "missing_lines": [129, 130, 131, 133, 134, 135, 137, 138, 139, 140, 142, 143, 144, 146, 147, 148], "excluded_lines": [], "start_line": 128, "executed_branches": [], "missing_branches": [[129, -128], [129, 130], [131, -128], [131, 133], [138, 139], [138, 146], [143, 144], [143, 146], [146, -128], [146, 147]]}, "": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 19, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 19, "excluded_lines": 0, "percent_statements_covered": 0.0, "percent_statements_covered_display": "0", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [1, 2, 4, 10, 11, 12, 13, 15, 16, 17, 19, 21, 46, 47, 50, 60, 61, 91, 128], "excluded_lines": [], "start_line": 1, "executed_branches": [], "missing_branches": []}}, "classes": {"GoeChargerNumber": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 60, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 60, "excluded_lines": 0, "percent_statements_covered": 0.0, "percent_statements_covered_display": "0", "num_branches": 38, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 38, "percent_branches_covered": 0.0, "percent_branches_covered_display": "0"}, "missing_lines": [48, 52, 57, 58, 62, 63, 64, 65, 66, 68, 70, 71, 72, 73, 74, 76, 77, 78, 80, 81, 83, 84, 87, 89, 93, 94, 96, 98, 99, 100, 103, 104, 105, 108, 110, 113, 114, 115, 118, 119, 120, 123, 125, 126, 129, 130, 131, 133, 134, 135, 137, 138, 139, 140, 142, 143, 144, 146, 147, 148], "excluded_lines": [], "start_line": 46, "executed_branches": [], "missing_branches": [[57, -50], [57, 58], [64, 65], [64, 70], [65, 66], [65, 68], [70, 71], [70, 72], [72, 73], [72, 77], [73, 74], [73, 76], [77, 78], [77, 87], [94, 96], [94, 113], [98, 99], [98, 100], [100, 103], [100, 104], [104, 105], [104, 108], [113, 114], [113, 115], [115, 118], [115, 119], [119, 120], [119, 123], [129, -128], [129, 130], [131, -128], [131, 133], [138, 139], [138, 146], [143, 144], [143, 146], [146, -128], [146, 147]]}, "": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 34, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 34, "excluded_lines": 0, "percent_statements_covered": 0.0, "percent_statements_covered_display": "0", "num_branches": 10, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 10, "percent_branches_covered": 0.0, "percent_branches_covered_display": "0"}, "missing_lines": [1, 2, 4, 10, 11, 12, 13, 15, 16, 17, 19, 21, 22, 23, 24, 26, 27, 28, 31, 32, 34, 36, 37, 39, 40, 41, 43, 46, 47, 50, 60, 61, 91, 128], "excluded_lines": [], "start_line": 1, "executed_branches": [], "missing_branches": [[26, 27], [26, 39], [27, 28], [27, 43], [28, 31], [28, 36], [31, 32], [31, 34], [39, 40], [39, 43]]}}}, "custom_components/goecharger_api2/pygoecharger_ha/__init__.py": {"executed_lines": [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 14, 15, 16, 18, 43, 45, 47, 49, 50, 51, 52, 53, 56, 59, 62, 64, 66, 67, 69, 71, 72, 73, 76, 77, 79, 81, 82, 84, 85, 86, 88, 89, 90, 91, 93, 96, 97, 98, 100, 102, 106, 108, 110, 111, 112, 113, 114, 117, 119, 121, 122, 123, 126, 131, 133, 137, 138, 140, 142, 145, 146, 152, 153, 155, 158, 160, 164, 165, 168, 172, 173, 174, 176, 177, 178, 179, 180, 181, 182, 183, 184, 185, 186, 188, 189, 190, 191, 192, 194, 195, 196, 198, 199, 201, 202, 203, 204, 208, 209, 210, 211, 212, 213, 216, 217, 218, 219, 220, 221, 222, 223, 224, 225, 226, 227, 228, 229, 230, 231, 232, 234, 237, 255, 267, 268, 273, 275, 276, 281, 282, 283, 284, 285, 286, 291, 295, 304, 305, 309, 310, 311, 312, 317, 319, 331, 380, 387, 422, 433, 434, 435, 436, 437, 438, 440, 441, 442, 443, 444, 445, 446, 447, 448, 449, 451, 452, 453, 454, 455, 457, 458, 460, 461, 467, 473, 475, 504, 505, 507, 508, 510, 512, 513, 514, 518, 532, 533, 534, 535, 536, 537, 539, 541, 543, 544, 545, 546, 547, 549, 550, 562, 563, 565, 566, 568, 570, 571, 572, 573, 574, 575, 576, 578, 579, 580, 582, 583, 584, 594, 595, 596, 597, 603, 610, 617, 621, 629, 631, 632, 636, 637, 638, 639, 640, 641, 645, 649, 650, 653, 654, 665, 667, 668, 670, 677, 678, 682, 685, 686, 688, 690, 692, 693, 694, 696, 698, 699, 700, 702, 703, 704, 707, 709, 710, 711, 714, 715, 716, 717, 718, 721, 723, 725, 726, 727, 728, 729, 731, 733, 734, 735, 743, 751, 753, 755, 756, 757, 769, 770, 773, 774, 776, 778, 780, 781, 782, 784, 785, 786, 787, 788, 790, 793, 795, 796, 797, 798, 801, 802, 803, 804, 805, 806, 807, 808, 809, 811, 812, 813, 814, 815, 823, 824, 825, 826, 828, 829, 830, 831, 832, 835, 836, 837, 839, 840, 841, 842, 843, 845, 846, 847, 858, 859, 860, 861, 862, 865, 871, 872, 873, 875, 876, 877, 878, 879, 880, 881, 884, 885, 888, 907, 908, 911, 913, 914, 915, 916, 917, 918, 919, 921, 922, 923, 924, 925, 926, 927, 928, 929, 931, 932, 933, 936, 937, 939, 948, 949, 950, 951, 964, 966, 967, 970, 971, 973, 974, 975, 976, 977, 981, 985, 986, 987, 988, 990, 992, 993, 994, 995, 1002, 1003, 1004, 1010, 1011, 1012, 1013, 1014, 1016, 1017, 1018, 1020, 1021, 1022, 1023, 1024, 1027, 1028, 1029, 1030, 1032, 1034, 1035, 1037, 1038, 1039, 1041, 1043, 1044, 1045, 1048, 1049, 1050, 1051, 1052, 1054, 1055, 1056, 1057, 1058, 1059, 1062, 1064, 1065, 1070, 1072, 1074, 1075, 1084, 1086, 1087, 1088, 1091, 1093, 1094, 1095, 1096, 1097, 1098, 1099, 1100, 1101, 1102, 1103, 1104, 1105, 1108, 1109, 1110, 1111], "summary": {"covered_lines": 491, "num_statements": 721, "percent_covered": 63.24535679374389, "percent_covered_display": "63", "missing_lines": 230, "excluded_lines": 0, "percent_statements_covered": 68.0998613037448, "percent_statements_covered_display": "68", "num_branches": 302, "num_partial_branches": 52, "covered_branches": 156, "missing_branches": 146, "percent_branches_covered": 51.65562913907285, "percent_branches_covered_display": "52"}, "missing_lines": [116, 124, 127, 128, 148, 149, 156, 169, 170, 206, 235, 238, 239, 240, 241, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 253, 256, 257, 258, 259, 262, 263, 264, 271, 279, 287, 289, 292, 293, 306, 307, 314, 315, 316, 320, 323, 324, 326, 327, 329, 332, 333, 335, 336, 337, 338, 339, 340, 342, 343, 344, 345, 348, 350, 351, 353, 355, 357, 361, 362, 365, 367, 368, 371, 372, 374, 375, 376, 377, 378, 381, 382, 383, 384, 385, 388, 389, 390, 392, 393, 394, 396, 397, 398, 403, 404, 405, 406, 407, 408, 416, 417, 420, 423, 424, 426, 427, 428, 429, 431, 463, 464, 468, 469, 470, 471, 476, 477, 478, 480, 481, 482, 483, 484, 485, 486, 487, 489, 490, 492, 493, 496, 498, 499, 500, 501, 502, 516, 521, 522, 523, 524, 525, 530, 552, 553, 554, 555, 556, 557, 558, 559, 560, 586, 588, 589, 591, 592, 598, 599, 600, 601, 605, 606, 607, 608, 618, 619, 622, 623, 624, 626, 627, 634, 642, 643, 647, 651, 655, 656, 657, 658, 661, 662, 663, 705, 712, 713, 719, 791, 816, 817, 818, 820, 821, 848, 851, 854, 855, 856, 866, 867, 868, 869, 940, 941, 942, 943, 945, 946, 953, 954, 956, 957, 958, 961, 978, 979, 980, 982, 983, 984, 996, 997, 1008, 1060, 1067, 1106, 1107], "excluded_lines": [], "executed_branches": [[51, 52], [52, 53], [52, 56], [71, 72], [71, 76], [111, 112], [111, 117], [113, 114], [123, -119], [140, 142], [140, 160], [145, 146], [152, 153], [152, 172], [155, 158], [160, 164], [160, 172], [168, 172], [172, 173], [172, 176], [176, 177], [176, 188], [203, 204], [268, 273], [276, 281], [281, 282], [283, 284], [285, 286], [291, 295], [295, 304], [305, 309], [309, 310], [437, 438], [437, 440], [443, 444], [443, 467], [446, 447], [446, 473], [448, 449], [448, 451], [453, 454], [453, 457], [454, 453], [454, 455], [507, 508], [507, 518], [508, 510], [508, 512], [512, 513], [518, 532], [532, 533], [532, 534], [534, 535], [534, 536], [536, 537], [536, 539], [544, 545], [545, 546], [545, 549], [565, 566], [565, 568], [572, 573], [572, 594], [575, 576], [576, 578], [578, 579], [578, 582], [582, 583], [594, 595], [594, 603], [631, 632], [638, 639], [650, 653], [654, -653], [668, 670], [668, 678], [678, 682], [678, 688], [698, 699], [698, 703], [699, 700], [699, 702], [703, 704], [709, 710], [709, 714], [714, 715], [714, 716], [716, 717], [716, 718], [718, 721], [726, 727], [726, 729], [753, 755], [753, 773], [784, 785], [784, 790], [786, 787], [790, 793], [805, 806], [805, 811], [807, 808], [812, 813], [830, 831], [839, 840], [839, 845], [841, 842], [847, 858], [858, 859], [858, 865], [860, 861], [865, 871], [877, 878], [877, 884], [879, 880], [917, 918], [925, 926], [925, 931], [927, 928], [936, 937], [936, 990], [939, 948], [948, 949], [964, 966], [970, 936], [970, 971], [975, 976], [987, 988], [1002, 1003], [1020, 1021], [1020, 1037], [1022, 1023], [1022, 1072], [1023, 1024], [1023, 1027], [1028, 1029], [1028, 1032], [1037, 1038], [1037, 1048], [1039, 1041], [1039, 1072], [1043, 1044], [1043, 1072], [1048, 1049], [1048, 1070], [1050, 1051], [1054, 1055], [1054, 1064], [1056, 1057], [1059, 1062], [1064, 1065], [1086, 1087], [1086, 1091], [1095, 1096], [1099, 1100], [1099, 1102], [1105, 1108]], "missing_branches": [[51, 66], [113, 116], [123, 124], [145, 148], [155, 156], [168, 169], [203, 206], [238, 239], [238, 240], [262, -255], [262, 263], [263, -255], [263, 264], [268, 271], [276, 279], [281, 291], [283, 285], [285, 287], [287, 281], [287, 289], [291, 292], [295, 317], [305, 306], [309, 314], [323, 324], [323, 326], [326, 327], [326, 329], [333, 335], [333, 338], [335, 336], [335, 342], [336, 337], [336, 342], [338, 339], [338, 342], [339, 340], [339, 342], [342, 343], [342, 371], [344, 345], [344, 348], [350, 351], [350, 353], [355, -331], [355, 357], [361, 362], [361, 365], [365, -331], [365, 367], [371, 372], [371, 374], [375, 376], [375, 377], [377, -331], [377, 378], [384, -380], [384, 385], [388, 389], [388, 420], [389, 390], [389, 392], [393, 394], [393, 396], [396, 397], [396, 416], [398, -387], [398, 403], [403, -387], [403, 404], [404, 403], [404, 405], [406, 403], [406, 407], [428, 429], [428, 431], [477, 478], [477, 480], [483, 484], [483, 496], [486, 487], [486, 502], [512, 516], [518, 521], [522, 523], [522, 524], [524, 525], [524, 530], [544, 552], [553, 554], [553, 560], [554, 555], [554, 556], [556, 557], [556, 558], [558, 553], [558, 559], [575, 610], [576, 586], [582, 584], [622, 623], [622, 626], [631, 634], [638, 647], [650, 651], [654, 655], [656, 657], [656, 661], [661, -653], [661, 662], [703, 705], [718, 719], [786, 788], [790, 791], [807, 809], [812, 820], [830, 832], [841, 843], [847, 848], [855, 856], [855, 858], [860, 862], [865, 866], [867, 868], [867, 869], [879, 881], [917, 919], [927, 929], [939, 940], [948, 956], [956, 957], [956, 961], [964, 936], [975, 990], [979, 980], [979, 990], [983, 984], [983, 990], [987, 990], [1002, 1008], [1050, 1054], [1056, 1072], [1059, 1060], [1064, 1067], [1095, 1111], [1105, 1106]], "functions": {"TargetedEvent.__init__": {"executed_lines": [50, 51, 52, 53, 56, 59, 62, 64, 66, 67], "summary": {"covered_lines": 10, "num_statements": 10, "percent_covered": 92.85714285714286, "percent_covered_display": "93", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 4, "num_partial_branches": 1, "covered_branches": 3, "missing_branches": 1, "percent_branches_covered": 75.0, "percent_branches_covered_display": "75"}, "missing_lines": [], "excluded_lines": [], "start_line": 49, "executed_branches": [[51, 52], [52, 53], [52, 56]], "missing_branches": [[51, 66]]}, "TargetedEvent.check_and_set": {"executed_lines": [71, 72, 73, 76, 77, 79], "summary": {"covered_lines": 6, "num_statements": 6, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 2, "num_partial_branches": 0, "covered_branches": 2, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 69, "executed_branches": [[71, 72], [71, 76]], "missing_branches": []}, "TargetedEvent.has_errors": {"executed_lines": [82], "summary": {"covered_lines": 1, "num_statements": 1, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 81, "executed_branches": [], "missing_branches": []}, "TargetedEvent.set_error": {"executed_lines": [85, 86], "summary": {"covered_lines": 2, "num_statements": 2, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 84, "executed_branches": [], "missing_branches": []}, "NonNullDict.none_null_get": {"executed_lines": [90, 91], "summary": {"covered_lines": 2, "num_statements": 2, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 89, "executed_branches": [], "missing_branches": []}, "ExpiringStore.__init__": {"executed_lines": [97, 98], "summary": {"covered_lines": 2, "num_statements": 2, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 96, "executed_branches": [], "missing_branches": []}, "ExpiringStore.set": {"executed_lines": [102, 106], "summary": {"covered_lines": 2, "num_statements": 2, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 100, "executed_branches": [], "missing_branches": []}, "ExpiringStore.get": {"executed_lines": [110, 111, 112, 113, 114, 117], "summary": {"covered_lines": 6, "num_statements": 7, "percent_covered": 81.81818181818181, "percent_covered_display": "82", "missing_lines": 1, "excluded_lines": 0, "percent_statements_covered": 85.71428571428571, "percent_statements_covered_display": "86", "num_branches": 4, "num_partial_branches": 1, "covered_branches": 3, "missing_branches": 1, "percent_branches_covered": 75.0, "percent_branches_covered_display": "75"}, "missing_lines": [116], "excluded_lines": [], "start_line": 108, "executed_branches": [[111, 112], [111, 117], [113, 114]], "missing_branches": [[113, 116]]}, "ExpiringStore._cleanup": {"executed_lines": [121, 122, 123], "summary": {"covered_lines": 3, "num_statements": 4, "percent_covered": 66.66666666666667, "percent_covered_display": "67", "missing_lines": 1, "excluded_lines": 0, "percent_statements_covered": 75.0, "percent_statements_covered_display": "75", "num_branches": 2, "num_partial_branches": 1, "covered_branches": 1, "missing_branches": 1, "percent_branches_covered": 50.0, "percent_branches_covered_display": "50"}, "missing_lines": [124], "excluded_lines": [], "start_line": 119, "executed_branches": [[123, -119]], "missing_branches": [[123, 124]]}, "ExpiringStore.__len__": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 2, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 2, "excluded_lines": 0, "percent_statements_covered": 0.0, "percent_statements_covered_display": "0", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [127, 128], "excluded_lines": [], "start_line": 126, "executed_branches": [], "missing_branches": []}, "GoeChargerApiV2Bridge.__init__": {"executed_lines": [137, 138, 140, 142, 145, 146, 152, 153, 155, 158, 160, 164, 165, 168, 172, 173, 174, 176, 177, 178, 179, 180, 181, 182, 183, 184, 185, 186, 188, 189, 190, 191, 192, 194, 195, 196, 198, 199, 201, 202, 203, 204, 208, 209, 210, 211, 212, 213, 216, 217, 218, 219, 220, 221, 222, 223, 224, 225, 226, 227, 228, 229, 230, 231, 232], "summary": {"covered_lines": 65, "num_statements": 71, "percent_covered": 88.76404494382022, "percent_covered_display": "89", "missing_lines": 6, "excluded_lines": 0, "percent_statements_covered": 91.54929577464789, "percent_statements_covered_display": "92", "num_branches": 18, "num_partial_branches": 4, "covered_branches": 14, "missing_branches": 4, "percent_branches_covered": 77.77777777777777, "percent_branches_covered_display": "78"}, "missing_lines": [148, 149, 156, 169, 170, 206], "excluded_lines": [], "start_line": 133, "executed_branches": [[140, 142], [140, 160], [145, 146], [152, 153], [152, 172], [155, 158], [160, 164], [160, 172], [168, 172], [172, 173], [172, 176], [176, 177], [176, 188], [203, 204]], "missing_branches": [[145, 148], [155, 156], [168, 169], [203, 206]]}, "GoeChargerApiV2Bridge.available_fields": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 1, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 1, "excluded_lines": 0, "percent_statements_covered": 0.0, "percent_statements_covered_display": "0", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [235], "excluded_lines": [], "start_line": 234, "executed_branches": [], "missing_branches": []}, "GoeChargerApiV2Bridge.clear_data": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 16, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 16, "excluded_lines": 0, "percent_statements_covered": 0.0, "percent_statements_covered_display": "0", "num_branches": 2, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 2, "percent_branches_covered": 0.0, "percent_branches_covered_display": "0"}, "missing_lines": [238, 239, 240, 241, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 253], "excluded_lines": [], "start_line": 237, "executed_branches": [], "missing_branches": [[238, 239], [238, 240]]}, "GoeChargerApiV2Bridge.reset_stored_update_ts": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 7, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 7, "excluded_lines": 0, "percent_statements_covered": 0.0, "percent_statements_covered_display": "0", "num_branches": 4, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 4, "percent_branches_covered": 0.0, "percent_branches_covered_display": "0"}, "missing_lines": [256, 257, 258, 259, 262, 263, 264], "excluded_lines": [], "start_line": 255, "executed_branches": [], "missing_branches": [[262, -255], [262, 263], [263, -255], [263, 264]]}, "GoeChargerApiV2Bridge.read_system": {"executed_lines": [268, 273], "summary": {"covered_lines": 2, "num_statements": 3, "percent_covered": 60.0, "percent_covered_display": "60", "missing_lines": 1, "excluded_lines": 0, "percent_statements_covered": 66.66666666666667, "percent_statements_covered_display": "67", "num_branches": 2, "num_partial_branches": 1, "covered_branches": 1, "missing_branches": 1, "percent_branches_covered": 50.0, "percent_branches_covered_display": "50"}, "missing_lines": [271], "excluded_lines": [], "start_line": 267, "executed_branches": [[268, 273]], "missing_branches": [[268, 271]]}, "GoeChargerApiV2Bridge.read_versions": {"executed_lines": [276, 281, 282, 283, 284, 285, 286, 291, 295, 304, 305, 309, 310, 311, 312, 317], "summary": {"covered_lines": 16, "num_statements": 26, "percent_covered": 54.54545454545455, "percent_covered_display": "55", "missing_lines": 10, "excluded_lines": 0, "percent_statements_covered": 61.53846153846154, "percent_statements_covered_display": "62", "num_branches": 18, "num_partial_branches": 8, "covered_branches": 8, "missing_branches": 10, "percent_branches_covered": 44.44444444444444, "percent_branches_covered_display": "44"}, "missing_lines": [279, 287, 289, 292, 293, 306, 307, 314, 315, 316], "excluded_lines": [], "start_line": 275, "executed_branches": [[276, 281], [281, 282], [283, 284], [285, 286], [291, 295], [295, 304], [305, 309], [309, 310]], "missing_branches": [[276, 279], [281, 291], [283, 285], [285, 287], [287, 281], [287, 289], [291, 292], [295, 317], [305, 306], [309, 314]]}, "GoeChargerApiV2Bridge.read_all": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 6, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 6, "excluded_lines": 0, "percent_statements_covered": 0.0, "percent_statements_covered_display": "0", "num_branches": 4, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 4, "percent_branches_covered": 0.0, "percent_branches_covered_display": "0"}, "missing_lines": [320, 323, 324, 326, 327, 329], "excluded_lines": [], "start_line": 319, "executed_branches": [], "missing_branches": [[323, 324], [323, 326], [326, 327], [326, 329]]}, "GoeChargerApiV2Bridge.read_all_states": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 30, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 30, "excluded_lines": 0, "percent_statements_covered": 0.0, "percent_statements_covered_display": "0", "num_branches": 28, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 28, "percent_branches_covered": 0.0, "percent_branches_covered_display": "0"}, "missing_lines": [332, 333, 335, 336, 337, 338, 339, 340, 342, 343, 344, 345, 348, 350, 351, 353, 355, 357, 361, 362, 365, 367, 368, 371, 372, 374, 375, 376, 377, 378], "excluded_lines": [], "start_line": 331, "executed_branches": [], "missing_branches": [[333, 335], [333, 338], [335, 336], [335, 342], [336, 337], [336, 342], [338, 339], [338, 342], [339, 340], [339, 342], [342, 343], [342, 371], [344, 345], [344, 348], [350, 351], [350, 353], [355, -331], [355, 357], [361, 362], [361, 365], [365, -331], [365, 367], [371, 372], [371, 374], [375, 376], [375, 377], [377, -331], [377, 378]]}, "GoeChargerApiV2Bridge.force_config_update": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 5, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 5, "excluded_lines": 0, "percent_statements_covered": 0.0, "percent_statements_covered_display": "0", "num_branches": 2, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 2, "percent_branches_covered": 0.0, "percent_branches_covered_display": "0"}, "missing_lines": [381, 382, 383, 384, 385], "excluded_lines": [], "start_line": 380, "executed_branches": [], "missing_branches": [[384, -380], [384, 385]]}, "GoeChargerApiV2Bridge.read_all_config": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 18, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 18, "excluded_lines": 0, "percent_statements_covered": 0.0, "percent_statements_covered_display": "0", "num_branches": 16, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 16, "percent_branches_covered": 0.0, "percent_branches_covered_display": "0"}, "missing_lines": [388, 389, 390, 392, 393, 394, 396, 397, 398, 403, 404, 405, 406, 407, 408, 416, 417, 420], "excluded_lines": [], "start_line": 387, "executed_branches": [], "missing_branches": [[388, 389], [388, 420], [389, 390], [389, 392], [393, 394], [393, 396], [396, 397], [396, 416], [398, -387], [398, 403], [403, -387], [403, 404], [404, 403], [404, 405], [406, 403], [406, 407]]}, "GoeChargerApiV2Bridge._read_filtered_data_from_ws_source": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 7, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 7, "excluded_lines": 0, "percent_statements_covered": 0.0, "percent_statements_covered_display": "0", "num_branches": 2, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 2, "percent_branches_covered": 0.0, "percent_branches_covered_display": "0"}, "missing_lines": [423, 424, 426, 427, 428, 429, 431], "excluded_lines": [], "start_line": 422, "executed_branches": [], "missing_branches": [[428, 429], [428, 431]]}, "GoeChargerApiV2Bridge._read_filtered_data": {"executed_lines": [434, 435, 436, 437, 438, 440, 441, 442, 443, 444, 445, 446, 447, 448, 449, 451, 452, 453, 454, 455, 457, 458, 460, 461, 467, 473], "summary": {"covered_lines": 26, "num_statements": 32, "percent_covered": 86.36363636363636, "percent_covered_display": "86", "missing_lines": 6, "excluded_lines": 0, "percent_statements_covered": 81.25, "percent_statements_covered_display": "81", "num_branches": 12, "num_partial_branches": 0, "covered_branches": 12, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [463, 464, 468, 469, 470, 471], "excluded_lines": [], "start_line": 433, "executed_branches": [[437, 438], [437, 440], [443, 444], [443, 467], [446, 447], [446, 473], [448, 449], [448, 451], [453, 454], [453, 457], [454, 453], [454, 455]], "missing_branches": []}, "GoeChargerApiV2Bridge._read_all_data": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 21, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 21, "excluded_lines": 0, "percent_statements_covered": 0.0, "percent_statements_covered_display": "0", "num_branches": 6, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 6, "percent_branches_covered": 0.0, "percent_branches_covered_display": "0"}, "missing_lines": [476, 477, 478, 480, 481, 482, 483, 484, 485, 486, 487, 489, 490, 492, 493, 496, 498, 499, 500, 501, 502], "excluded_lines": [], "start_line": 475, "executed_branches": [], "missing_branches": [[477, 478], [477, 480], [483, 484], [483, 496], [486, 487], [486, 502]]}, "GoeChargerApiV2Bridge.write_value_to_key": {"executed_lines": [505, 507, 508, 510, 512, 513, 514, 518, 532, 533, 534, 535, 536, 537, 539, 541], "summary": {"covered_lines": 16, "num_statements": 23, "percent_covered": 68.29268292682927, "percent_covered_display": "68", "missing_lines": 7, "excluded_lines": 0, "percent_statements_covered": 69.56521739130434, "percent_statements_covered_display": "70", "num_branches": 18, "num_partial_branches": 2, "covered_branches": 12, "missing_branches": 6, "percent_branches_covered": 66.66666666666667, "percent_branches_covered_display": "67"}, "missing_lines": [516, 521, 522, 523, 524, 525, 530], "excluded_lines": [], "start_line": 504, "executed_branches": [[507, 508], [507, 518], [508, 510], [508, 512], [512, 513], [518, 532], [532, 533], [532, 534], [534, 535], [534, 536], [536, 537], [536, 539]], "missing_branches": [[512, 516], [518, 521], [522, 523], [522, 524], [524, 525], [524, 530]]}, "GoeChargerApiV2Bridge.write_multiple_values_to_keys": {"executed_lines": [544, 545, 546, 547, 549, 550], "summary": {"covered_lines": 6, "num_statements": 15, "percent_covered": 33.333333333333336, "percent_covered_display": "33", "missing_lines": 9, "excluded_lines": 0, "percent_statements_covered": 40.0, "percent_statements_covered_display": "40", "num_branches": 12, "num_partial_branches": 1, "covered_branches": 3, "missing_branches": 9, "percent_branches_covered": 25.0, "percent_branches_covered_display": "25"}, "missing_lines": [552, 553, 554, 555, 556, 557, 558, 559, 560], "excluded_lines": [], "start_line": 543, "executed_branches": [[544, 545], [545, 546], [545, 549]], "missing_branches": [[544, 552], [553, 554], [553, 560], [554, 555], [554, 556], [556, 557], [556, 558], [558, 553], [558, 559]]}, "GoeChargerApiV2Bridge._write_values_int": {"executed_lines": [563, 565, 566, 568, 570, 571, 572, 573, 574, 575, 576, 578, 579, 580, 582, 583, 584, 594, 595, 596, 597, 603, 610], "summary": {"covered_lines": 23, "num_statements": 36, "percent_covered": 68.0, "percent_covered_display": "68", "missing_lines": 13, "excluded_lines": 0, "percent_statements_covered": 63.888888888888886, "percent_statements_covered_display": "64", "num_branches": 14, "num_partial_branches": 3, "covered_branches": 11, "missing_branches": 3, "percent_branches_covered": 78.57142857142857, "percent_branches_covered_display": "79"}, "missing_lines": [586, 588, 589, 591, 592, 598, 599, 600, 601, 605, 606, 607, 608], "excluded_lines": [], "start_line": 562, "executed_branches": [[565, 566], [565, 568], [572, 573], [572, 594], [575, 576], [576, 578], [578, 579], [578, 582], [582, 583], [594, 595], [594, 603]], "missing_branches": [[575, 610], [576, 586], [582, 584]]}, "GoeChargerApiV2Bridge.ws_set_coordinator": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 2, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 2, "excluded_lines": 0, "percent_statements_covered": 0.0, "percent_statements_covered_display": "0", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [618, 619], "excluded_lines": [], "start_line": 617, "executed_branches": [], "missing_branches": []}, "GoeChargerApiV2Bridge.ws_check_last_update": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 5, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 5, "excluded_lines": 0, "percent_statements_covered": 0.0, "percent_statements_covered_display": "0", "num_branches": 2, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 2, "percent_branches_covered": 0.0, "percent_branches_covered_display": "0"}, "missing_lines": [622, 623, 624, 626, 627], "excluded_lines": [], "start_line": 621, "executed_branches": [], "missing_branches": [[622, 623], [622, 626]]}, "GoeChargerApiV2Bridge.ws_close": {"executed_lines": [631, 632, 636, 637, 638, 639, 640, 641, 645], "summary": {"covered_lines": 9, "num_statements": 13, "percent_covered": 64.70588235294117, "percent_covered_display": "65", "missing_lines": 4, "excluded_lines": 0, "percent_statements_covered": 69.23076923076923, "percent_statements_covered_display": "69", "num_branches": 4, "num_partial_branches": 2, "covered_branches": 2, "missing_branches": 2, "percent_branches_covered": 50.0, "percent_branches_covered_display": "50"}, "missing_lines": [634, 642, 643, 647], "excluded_lines": [], "start_line": 629, "executed_branches": [[631, 632], [638, 639]], "missing_branches": [[631, 634], [638, 647]]}, "GoeChargerApiV2Bridge._ws_notify_for_new_data": {"executed_lines": [650, 653, 665], "summary": {"covered_lines": 3, "num_statements": 4, "percent_covered": 66.66666666666667, "percent_covered_display": "67", "missing_lines": 1, "excluded_lines": 0, "percent_statements_covered": 75.0, "percent_statements_covered_display": "75", "num_branches": 2, "num_partial_branches": 1, "covered_branches": 1, "missing_branches": 1, "percent_branches_covered": 50.0, "percent_branches_covered_display": "50"}, "missing_lines": [651], "excluded_lines": [], "start_line": 649, "executed_branches": [[650, 653]], "missing_branches": [[650, 651]]}, "GoeChargerApiV2Bridge._ws_notify_for_new_data._ws_debounce_coordinator_update": {"executed_lines": [654], "summary": {"covered_lines": 1, "num_statements": 8, "percent_covered": 14.285714285714286, "percent_covered_display": "14", "missing_lines": 7, "excluded_lines": 0, "percent_statements_covered": 12.5, "percent_statements_covered_display": "12", "num_branches": 6, "num_partial_branches": 1, "covered_branches": 1, "missing_branches": 5, "percent_branches_covered": 16.666666666666668, "percent_branches_covered_display": "17"}, "missing_lines": [655, 656, 657, 658, 661, 662, 663], "excluded_lines": [], "start_line": 653, "executed_branches": [[654, -653]], "missing_branches": [[654, 655], [656, 657], [656, 661], [661, -653], [661, 662]]}, "GoeChargerApiV2Bridge._ws_compute_hashed_password": {"executed_lines": [668, 670, 677, 678, 682, 685, 686, 688], "summary": {"covered_lines": 8, "num_statements": 8, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 4, "num_partial_branches": 0, "covered_branches": 4, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 667, "executed_branches": [[668, 670], [668, 678], [678, 682], [678, 688]], "missing_branches": []}, "GoeChargerApiV2Bridge._ws_compute_auth_hash": {"executed_lines": [692, 693, 694], "summary": {"covered_lines": 3, "num_statements": 3, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 690, "executed_branches": [], "missing_branches": []}, "GoeChargerApiV2Bridge._ws_decode_message": {"executed_lines": [698, 699, 700, 702, 703, 704], "summary": {"covered_lines": 6, "num_statements": 7, "percent_covered": 84.61538461538461, "percent_covered_display": "85", "missing_lines": 1, "excluded_lines": 0, "percent_statements_covered": 85.71428571428571, "percent_statements_covered_display": "86", "num_branches": 6, "num_partial_branches": 1, "covered_branches": 5, "missing_branches": 1, "percent_branches_covered": 83.33333333333333, "percent_branches_covered_display": "83"}, "missing_lines": [705], "excluded_lines": [], "start_line": 696, "executed_branches": [[698, 699], [698, 703], [699, 700], [699, 702], [703, 704]], "missing_branches": [[703, 705]]}, "GoeChargerApiV2Bridge._ws_normalize_value": {"executed_lines": [709, 710, 711, 714, 715, 716, 717, 718, 721], "summary": {"covered_lines": 9, "num_statements": 12, "percent_covered": 80.0, "percent_covered_display": "80", "missing_lines": 3, "excluded_lines": 0, "percent_statements_covered": 75.0, "percent_statements_covered_display": "75", "num_branches": 8, "num_partial_branches": 1, "covered_branches": 7, "missing_branches": 1, "percent_branches_covered": 87.5, "percent_branches_covered_display": "88"}, "missing_lines": [712, 713, 719], "excluded_lines": [], "start_line": 707, "executed_branches": [[709, 710], [709, 714], [714, 715], [714, 716], [716, 717], [716, 718], [718, 721]], "missing_branches": [[718, 719]]}, "GoeChargerApiV2Bridge._ws_normalize_dict": {"executed_lines": [725, 726, 727, 728, 729], "summary": {"covered_lines": 5, "num_statements": 5, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 2, "num_partial_branches": 0, "covered_branches": 2, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 723, "executed_branches": [[726, 727], [726, 729]], "missing_branches": []}, "GoeChargerApiV2Bridge._ws_send_command": {"executed_lines": [733, 734, 735, 743, 751, 753, 755, 756, 757, 769, 770, 773, 774, 776], "summary": {"covered_lines": 14, "num_statements": 14, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 2, "num_partial_branches": 0, "covered_branches": 2, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 731, "executed_branches": [[753, 755], [753, 773]], "missing_branches": []}, "GoeChargerApiV2Bridge.ws_connect": {"executed_lines": [780, 781, 782, 784, 785, 786, 787, 788, 790, 793, 795, 796, 797, 798, 801, 802, 803, 804, 805, 806, 807, 808, 809, 811, 812, 813, 814, 815, 823, 824, 825, 826, 828, 829, 830, 831, 832, 835, 836, 837, 839, 840, 841, 842, 843, 845, 846, 847, 858, 859, 860, 861, 862, 865, 871, 872, 873, 875, 876, 877, 878, 879, 880, 881, 884, 885, 888, 907, 908, 911, 913, 914, 915, 916, 917, 918, 919, 921, 922, 923, 924, 925, 926, 927, 928, 929, 931, 932, 933, 936, 937, 939, 948, 949, 950, 951, 964, 966, 967, 970, 971, 973, 974, 975, 976, 977, 981, 985, 986, 987, 988, 990, 992, 993, 994, 995, 1002, 1003, 1004, 1010, 1011, 1012, 1013, 1014], "summary": {"covered_lines": 124, "num_statements": 160, "percent_covered": 71.17117117117117, "percent_covered_display": "71", "missing_lines": 36, "excluded_lines": 0, "percent_statements_covered": 77.5, "percent_statements_covered_display": "78", "num_branches": 62, "num_partial_branches": 18, "covered_branches": 34, "missing_branches": 28, "percent_branches_covered": 54.83870967741935, "percent_branches_covered_display": "55"}, "missing_lines": [791, 816, 817, 818, 820, 821, 848, 851, 854, 855, 856, 866, 867, 868, 869, 940, 941, 942, 943, 945, 946, 953, 954, 956, 957, 958, 961, 978, 979, 980, 982, 983, 984, 996, 997, 1008], "excluded_lines": [], "start_line": 778, "executed_branches": [[784, 785], [784, 790], [786, 787], [790, 793], [805, 806], [805, 811], [807, 808], [812, 813], [830, 831], [839, 840], [839, 845], [841, 842], [847, 858], [858, 859], [858, 865], [860, 861], [865, 871], [877, 878], [877, 884], [879, 880], [917, 918], [925, 926], [925, 931], [927, 928], [936, 937], [936, 990], [939, 948], [948, 949], [964, 966], [970, 936], [970, 971], [975, 976], [987, 988], [1002, 1003]], "missing_branches": [[786, 788], [790, 791], [807, 809], [812, 820], [830, 832], [841, 843], [847, 848], [855, 856], [855, 858], [860, 862], [865, 866], [867, 868], [867, 869], [879, 881], [917, 919], [927, 929], [939, 940], [948, 956], [956, 957], [956, 961], [964, 936], [975, 990], [979, 980], [979, 990], [983, 984], [983, 990], [987, 990], [1002, 1008]]}, "GoeChargerApiV2Bridge.extract_ws_message_data": {"executed_lines": [1017, 1018, 1020, 1021, 1022, 1023, 1024, 1027, 1028, 1029, 1030, 1032, 1034, 1035, 1037, 1038, 1039, 1041, 1043, 1044, 1045, 1048, 1049, 1050, 1051, 1052, 1054, 1055, 1056, 1057, 1058, 1059, 1062, 1064, 1065, 1070, 1072], "summary": {"covered_lines": 37, "num_statements": 39, "percent_covered": 90.76923076923077, "percent_covered_display": "91", "missing_lines": 2, "excluded_lines": 0, "percent_statements_covered": 94.87179487179488, "percent_statements_covered_display": "95", "num_branches": 26, "num_partial_branches": 4, "covered_branches": 22, "missing_branches": 4, "percent_branches_covered": 84.61538461538461, "percent_branches_covered_display": "85"}, "missing_lines": [1060, 1067], "excluded_lines": [], "start_line": 1016, "executed_branches": [[1020, 1021], [1020, 1037], [1022, 1023], [1022, 1072], [1023, 1024], [1023, 1027], [1028, 1029], [1028, 1032], [1037, 1038], [1037, 1048], [1039, 1041], [1039, 1072], [1043, 1044], [1043, 1072], [1048, 1049], [1048, 1070], [1050, 1051], [1054, 1055], [1054, 1064], [1056, 1057], [1059, 1062], [1064, 1065]], "missing_branches": [[1050, 1054], [1056, 1072], [1059, 1060], [1064, 1067]]}, "bcryptjs_base64_encode_serial": {"executed_lines": [1084, 1086, 1087, 1088, 1091, 1093, 1094, 1095, 1096, 1097, 1098, 1099, 1100, 1101, 1102, 1103, 1104, 1105, 1108, 1109, 1110, 1111], "summary": {"covered_lines": 22, "num_statements": 24, "percent_covered": 87.5, "percent_covered_display": "88", "missing_lines": 2, "excluded_lines": 0, "percent_statements_covered": 91.66666666666667, "percent_statements_covered_display": "92", "num_branches": 8, "num_partial_branches": 2, "covered_branches": 6, "missing_branches": 2, "percent_branches_covered": 75.0, "percent_branches_covered_display": "75"}, "missing_lines": [1106, 1107], "excluded_lines": [], "start_line": 1075, "executed_branches": [[1086, 1087], [1086, 1091], [1095, 1096], [1099, 1100], [1099, 1102], [1105, 1108]], "missing_branches": [[1095, 1111], [1105, 1106]]}, "": {"executed_lines": [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 14, 15, 16, 18, 43, 45, 47, 49, 69, 81, 84, 88, 89, 93, 96, 100, 108, 119, 126, 131, 133, 234, 237, 255, 267, 275, 319, 331, 380, 387, 422, 433, 475, 504, 543, 562, 617, 621, 629, 649, 667, 690, 696, 707, 723, 731, 778, 1016, 1074, 1075], "summary": {"covered_lines": 62, "num_statements": 62, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 1, "executed_branches": [], "missing_branches": []}}, "classes": {"TargetedEvent": {"executed_lines": [50, 51, 52, 53, 56, 59, 62, 64, 66, 67, 71, 72, 73, 76, 77, 79, 82, 85, 86], "summary": {"covered_lines": 19, "num_statements": 19, "percent_covered": 96.0, "percent_covered_display": "96", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 6, "num_partial_branches": 1, "covered_branches": 5, "missing_branches": 1, "percent_branches_covered": 83.33333333333333, "percent_branches_covered_display": "83"}, "missing_lines": [], "excluded_lines": [], "start_line": 47, "executed_branches": [[51, 52], [52, 53], [52, 56], [71, 72], [71, 76]], "missing_branches": [[51, 66]]}, "NonNullDict": {"executed_lines": [90, 91], "summary": {"covered_lines": 2, "num_statements": 2, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 88, "executed_branches": [], "missing_branches": []}, "ExpiringStore": {"executed_lines": [97, 98, 102, 106, 110, 111, 112, 113, 114, 117, 121, 122, 123], "summary": {"covered_lines": 13, "num_statements": 17, "percent_covered": 73.91304347826087, "percent_covered_display": "74", "missing_lines": 4, "excluded_lines": 0, "percent_statements_covered": 76.47058823529412, "percent_statements_covered_display": "76", "num_branches": 6, "num_partial_branches": 2, "covered_branches": 4, "missing_branches": 2, "percent_branches_covered": 66.66666666666667, "percent_branches_covered_display": "67"}, "missing_lines": [116, 124, 127, 128], "excluded_lines": [], "start_line": 93, "executed_branches": [[111, 112], [111, 117], [113, 114], [123, -119]], "missing_branches": [[113, 116], [123, 124]]}, "GoeChargerApiV2Bridge": {"executed_lines": [137, 138, 140, 142, 145, 146, 152, 153, 155, 158, 160, 164, 165, 168, 172, 173, 174, 176, 177, 178, 179, 180, 181, 182, 183, 184, 185, 186, 188, 189, 190, 191, 192, 194, 195, 196, 198, 199, 201, 202, 203, 204, 208, 209, 210, 211, 212, 213, 216, 217, 218, 219, 220, 221, 222, 223, 224, 225, 226, 227, 228, 229, 230, 231, 232, 268, 273, 276, 281, 282, 283, 284, 285, 286, 291, 295, 304, 305, 309, 310, 311, 312, 317, 434, 435, 436, 437, 438, 440, 441, 442, 443, 444, 445, 446, 447, 448, 449, 451, 452, 453, 454, 455, 457, 458, 460, 461, 467, 473, 505, 507, 508, 510, 512, 513, 514, 518, 532, 533, 534, 535, 536, 537, 539, 541, 544, 545, 546, 547, 549, 550, 563, 565, 566, 568, 570, 571, 572, 573, 574, 575, 576, 578, 579, 580, 582, 583, 584, 594, 595, 596, 597, 603, 610, 631, 632, 636, 637, 638, 639, 640, 641, 645, 650, 653, 654, 665, 668, 670, 677, 678, 682, 685, 686, 688, 692, 693, 694, 698, 699, 700, 702, 703, 704, 709, 710, 711, 714, 715, 716, 717, 718, 721, 725, 726, 727, 728, 729, 733, 734, 735, 743, 751, 753, 755, 756, 757, 769, 770, 773, 774, 776, 780, 781, 782, 784, 785, 786, 787, 788, 790, 793, 795, 796, 797, 798, 801, 802, 803, 804, 805, 806, 807, 808, 809, 811, 812, 813, 814, 815, 823, 824, 825, 826, 828, 829, 830, 831, 832, 835, 836, 837, 839, 840, 841, 842, 843, 845, 846, 847, 858, 859, 860, 861, 862, 865, 871, 872, 873, 875, 876, 877, 878, 879, 880, 881, 884, 885, 888, 907, 908, 911, 913, 914, 915, 916, 917, 918, 919, 921, 922, 923, 924, 925, 926, 927, 928, 929, 931, 932, 933, 936, 937, 939, 948, 949, 950, 951, 964, 966, 967, 970, 971, 973, 974, 975, 976, 977, 981, 985, 986, 987, 988, 990, 992, 993, 994, 995, 1002, 1003, 1004, 1010, 1011, 1012, 1013, 1014, 1017, 1018, 1020, 1021, 1022, 1023, 1024, 1027, 1028, 1029, 1030, 1032, 1034, 1035, 1037, 1038, 1039, 1041, 1043, 1044, 1045, 1048, 1049, 1050, 1051, 1052, 1054, 1055, 1056, 1057, 1058, 1059, 1062, 1064, 1065, 1070, 1072], "summary": {"covered_lines": 373, "num_statements": 597, "percent_covered": 58.47554038680318, "percent_covered_display": "58", "missing_lines": 224, "excluded_lines": 0, "percent_statements_covered": 62.47906197654942, "percent_statements_covered_display": "62", "num_branches": 282, "num_partial_branches": 47, "covered_branches": 141, "missing_branches": 141, "percent_branches_covered": 50.0, "percent_branches_covered_display": "50"}, "missing_lines": [148, 149, 156, 169, 170, 206, 235, 238, 239, 240, 241, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 253, 256, 257, 258, 259, 262, 263, 264, 271, 279, 287, 289, 292, 293, 306, 307, 314, 315, 316, 320, 323, 324, 326, 327, 329, 332, 333, 335, 336, 337, 338, 339, 340, 342, 343, 344, 345, 348, 350, 351, 353, 355, 357, 361, 362, 365, 367, 368, 371, 372, 374, 375, 376, 377, 378, 381, 382, 383, 384, 385, 388, 389, 390, 392, 393, 394, 396, 397, 398, 403, 404, 405, 406, 407, 408, 416, 417, 420, 423, 424, 426, 427, 428, 429, 431, 463, 464, 468, 469, 470, 471, 476, 477, 478, 480, 481, 482, 483, 484, 485, 486, 487, 489, 490, 492, 493, 496, 498, 499, 500, 501, 502, 516, 521, 522, 523, 524, 525, 530, 552, 553, 554, 555, 556, 557, 558, 559, 560, 586, 588, 589, 591, 592, 598, 599, 600, 601, 605, 606, 607, 608, 618, 619, 622, 623, 624, 626, 627, 634, 642, 643, 647, 651, 655, 656, 657, 658, 661, 662, 663, 705, 712, 713, 719, 791, 816, 817, 818, 820, 821, 848, 851, 854, 855, 856, 866, 867, 868, 869, 940, 941, 942, 943, 945, 946, 953, 954, 956, 957, 958, 961, 978, 979, 980, 982, 983, 984, 996, 997, 1008, 1060, 1067], "excluded_lines": [], "start_line": 131, "executed_branches": [[140, 142], [140, 160], [145, 146], [152, 153], [152, 172], [155, 158], [160, 164], [160, 172], [168, 172], [172, 173], [172, 176], [176, 177], [176, 188], [203, 204], [268, 273], [276, 281], [281, 282], [283, 284], [285, 286], [291, 295], [295, 304], [305, 309], [309, 310], [437, 438], [437, 440], [443, 444], [443, 467], [446, 447], [446, 473], [448, 449], [448, 451], [453, 454], [453, 457], [454, 453], [454, 455], [507, 508], [507, 518], [508, 510], [508, 512], [512, 513], [518, 532], [532, 533], [532, 534], [534, 535], [534, 536], [536, 537], [536, 539], [544, 545], [545, 546], [545, 549], [565, 566], [565, 568], [572, 573], [572, 594], [575, 576], [576, 578], [578, 579], [578, 582], [582, 583], [594, 595], [594, 603], [631, 632], [638, 639], [650, 653], [654, -653], [668, 670], [668, 678], [678, 682], [678, 688], [698, 699], [698, 703], [699, 700], [699, 702], [703, 704], [709, 710], [709, 714], [714, 715], [714, 716], [716, 717], [716, 718], [718, 721], [726, 727], [726, 729], [753, 755], [753, 773], [784, 785], [784, 790], [786, 787], [790, 793], [805, 806], [805, 811], [807, 808], [812, 813], [830, 831], [839, 840], [839, 845], [841, 842], [847, 858], [858, 859], [858, 865], [860, 861], [865, 871], [877, 878], [877, 884], [879, 880], [917, 918], [925, 926], [925, 931], [927, 928], [936, 937], [936, 990], [939, 948], [948, 949], [964, 966], [970, 936], [970, 971], [975, 976], [987, 988], [1002, 1003], [1020, 1021], [1020, 1037], [1022, 1023], [1022, 1072], [1023, 1024], [1023, 1027], [1028, 1029], [1028, 1032], [1037, 1038], [1037, 1048], [1039, 1041], [1039, 1072], [1043, 1044], [1043, 1072], [1048, 1049], [1048, 1070], [1050, 1051], [1054, 1055], [1054, 1064], [1056, 1057], [1059, 1062], [1064, 1065]], "missing_branches": [[145, 148], [155, 156], [168, 169], [203, 206], [238, 239], [238, 240], [262, -255], [262, 263], [263, -255], [263, 264], [268, 271], [276, 279], [281, 291], [283, 285], [285, 287], [287, 281], [287, 289], [291, 292], [295, 317], [305, 306], [309, 314], [323, 324], [323, 326], [326, 327], [326, 329], [333, 335], [333, 338], [335, 336], [335, 342], [336, 337], [336, 342], [338, 339], [338, 342], [339, 340], [339, 342], [342, 343], [342, 371], [344, 345], [344, 348], [350, 351], [350, 353], [355, -331], [355, 357], [361, 362], [361, 365], [365, -331], [365, 367], [371, 372], [371, 374], [375, 376], [375, 377], [377, -331], [377, 378], [384, -380], [384, 385], [388, 389], [388, 420], [389, 390], [389, 392], [393, 394], [393, 396], [396, 397], [396, 416], [398, -387], [398, 403], [403, -387], [403, 404], [404, 403], [404, 405], [406, 403], [406, 407], [428, 429], [428, 431], [477, 478], [477, 480], [483, 484], [483, 496], [486, 487], [486, 502], [512, 516], [518, 521], [522, 523], [522, 524], [524, 525], [524, 530], [544, 552], [553, 554], [553, 560], [554, 555], [554, 556], [556, 557], [556, 558], [558, 553], [558, 559], [575, 610], [576, 586], [582, 584], [622, 623], [622, 626], [631, 634], [638, 647], [650, 651], [654, 655], [656, 657], [656, 661], [661, -653], [661, 662], [703, 705], [718, 719], [786, 788], [790, 791], [807, 809], [812, 820], [830, 832], [841, 843], [847, 848], [855, 856], [855, 858], [860, 862], [865, 866], [867, 868], [867, 869], [879, 881], [917, 919], [927, 929], [939, 940], [948, 956], [956, 957], [956, 961], [964, 936], [975, 990], [979, 980], [979, 990], [983, 984], [983, 990], [987, 990], [1002, 1008], [1050, 1054], [1056, 1072], [1059, 1060], [1064, 1067]]}, "": {"executed_lines": [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 14, 15, 16, 18, 43, 45, 47, 49, 69, 81, 84, 88, 89, 93, 96, 100, 108, 119, 126, 131, 133, 234, 237, 255, 267, 275, 319, 331, 380, 387, 422, 433, 475, 504, 543, 562, 617, 621, 629, 649, 667, 690, 696, 707, 723, 731, 778, 1016, 1074, 1075, 1084, 1086, 1087, 1088, 1091, 1093, 1094, 1095, 1096, 1097, 1098, 1099, 1100, 1101, 1102, 1103, 1104, 1105, 1108, 1109, 1110, 1111], "summary": {"covered_lines": 84, "num_statements": 86, "percent_covered": 95.74468085106383, "percent_covered_display": "96", "missing_lines": 2, "excluded_lines": 0, "percent_statements_covered": 97.67441860465117, "percent_statements_covered_display": "98", "num_branches": 8, "num_partial_branches": 2, "covered_branches": 6, "missing_branches": 2, "percent_branches_covered": 75.0, "percent_branches_covered_display": "75"}, "missing_lines": [1106, 1107], "excluded_lines": [], "start_line": 1, "executed_branches": [[1086, 1087], [1086, 1091], [1095, 1096], [1099, 1100], [1099, 1102], [1105, 1108]], "missing_branches": [[1095, 1111], [1105, 1106]]}}}, "custom_components/goecharger_api2/pygoecharger_ha/const.py": {"executed_lines": [1, 2, 6, 11, 12, 13, 14, 16, 17, 18, 19, 22, 23, 24, 25, 26, 29, 31, 32, 34, 37, 39, 40, 41, 46, 47, 48, 51, 52, 53, 56, 57, 58, 60, 61, 62, 63, 64, 65, 66, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 90, 111], "summary": {"covered_lines": 61, "num_statements": 61, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "executed_branches": [], "missing_branches": [], "functions": {"": {"executed_lines": [1, 2, 6, 11, 12, 13, 14, 16, 17, 18, 19, 22, 23, 24, 25, 26, 29, 31, 32, 34, 37, 39, 40, 41, 46, 47, 48, 51, 52, 53, 56, 57, 58, 60, 61, 62, 63, 64, 65, 66, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 90, 111], "summary": {"covered_lines": 61, "num_statements": 61, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 1, "executed_branches": [], "missing_branches": []}}, "classes": {"INTG_TYPE": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 0, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 56, "executed_branches": [], "missing_branches": []}, "CAR_VALUES": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 0, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 60, "executed_branches": [], "missing_branches": []}, "CT_VALUES": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 0, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 70, "executed_branches": [], "missing_branches": []}, "": {"executed_lines": [1, 2, 6, 11, 12, 13, 14, 16, 17, 18, 19, 22, 23, 24, 25, 26, 29, 31, 32, 34, 37, 39, 40, 41, 46, 47, 48, 51, 52, 53, 56, 57, 58, 60, 61, 62, 63, 64, 65, 66, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 90, 111], "summary": {"covered_lines": 61, "num_statements": 61, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 1, "executed_branches": [], "missing_branches": []}}}, "custom_components/goecharger_api2/pygoecharger_ha/keys.py": {"executed_lines": [1, 2, 3, 9, 11, 13, 14, 15, 16, 17, 20, 23, 24, 27, 29, 32, 36, 38, 40, 42, 44, 46, 48, 50, 52, 54, 56, 58, 60, 62, 64, 66, 68, 70, 72, 74, 76, 78, 80, 82, 84, 95, 98, 100, 102, 104, 107, 109, 111, 113, 115, 117, 119, 121, 123, 125, 127, 129, 131, 133, 135, 137, 139, 141, 143, 145, 147, 149, 151, 153, 155, 157, 159, 161, 163, 165, 167, 169, 171, 173, 175, 179, 181, 183, 185, 187, 189, 191, 193, 195, 197, 199, 201, 203, 205, 207, 209, 211, 213, 215, 217, 219, 221, 223, 225, 227, 229, 232, 234, 236, 238, 240, 242, 244, 246, 248, 250, 252, 254, 256, 258, 260, 262, 264, 266, 268, 270, 272, 274, 276, 278, 280, 282, 284, 286, 288, 290, 292, 294, 296, 298, 300, 302, 304, 306, 308, 310, 312, 314, 316, 318, 320, 322, 324, 326, 328, 330, 332, 334, 336, 338, 340, 342, 344, 346, 350, 353, 354, 355, 359, 361, 366, 372, 378, 384, 390, 396, 402, 409, 411, 413, 415, 426, 428, 430, 432, 434, 436, 438, 440, 442, 444, 446, 448, 450, 452, 454, 456, 458, 460, 462, 464, 466, 523, 525, 527, 529, 531, 533, 535, 537, 539, 541, 543, 545, 547, 549, 551, 560, 562, 564, 566, 568, 570, 664, 668, 670, 673, 689, 691, 693, 695, 697, 699, 701, 703, 705, 707, 737, 739, 743, 753, 767, 785, 787, 789, 791, 793, 805, 807, 809, 811, 813], "summary": {"covered_lines": 253, "num_statements": 255, "percent_covered": 99.2156862745098, "percent_covered_display": "99", "missing_lines": 2, "excluded_lines": 0, "percent_statements_covered": 99.2156862745098, "percent_statements_covered_display": "99", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [30, 33], "excluded_lines": [], "executed_branches": [], "missing_branches": [], "functions": {"Tag.__hash__": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 1, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 1, "excluded_lines": 0, "percent_statements_covered": 0.0, "percent_statements_covered_display": "0", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [30], "excluded_lines": [], "start_line": 29, "executed_branches": [], "missing_branches": []}, "Tag.__str__": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 1, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 1, "excluded_lines": 0, "percent_statements_covered": 0.0, "percent_statements_covered_display": "0", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [33], "excluded_lines": [], "start_line": 32, "executed_branches": [], "missing_branches": []}, "": {"executed_lines": [1, 2, 3, 9, 11, 13, 14, 15, 16, 17, 20, 23, 24, 27, 29, 32, 36, 38, 40, 42, 44, 46, 48, 50, 52, 54, 56, 58, 60, 62, 64, 66, 68, 70, 72, 74, 76, 78, 80, 82, 84, 95, 98, 100, 102, 104, 107, 109, 111, 113, 115, 117, 119, 121, 123, 125, 127, 129, 131, 133, 135, 137, 139, 141, 143, 145, 147, 149, 151, 153, 155, 157, 159, 161, 163, 165, 167, 169, 171, 173, 175, 179, 181, 183, 185, 187, 189, 191, 193, 195, 197, 199, 201, 203, 205, 207, 209, 211, 213, 215, 217, 219, 221, 223, 225, 227, 229, 232, 234, 236, 238, 240, 242, 244, 246, 248, 250, 252, 254, 256, 258, 260, 262, 264, 266, 268, 270, 272, 274, 276, 278, 280, 282, 284, 286, 288, 290, 292, 294, 296, 298, 300, 302, 304, 306, 308, 310, 312, 314, 316, 318, 320, 322, 324, 326, 328, 330, 332, 334, 336, 338, 340, 342, 344, 346, 350, 353, 354, 355, 359, 361, 366, 372, 378, 384, 390, 396, 402, 409, 411, 413, 415, 426, 428, 430, 432, 434, 436, 438, 440, 442, 444, 446, 448, 450, 452, 454, 456, 458, 460, 462, 464, 466, 523, 525, 527, 529, 531, 533, 535, 537, 539, 541, 543, 545, 547, 549, 551, 560, 562, 564, 566, 568, 570, 664, 668, 670, 673, 689, 691, 693, 695, 697, 699, 701, 703, 705, 707, 737, 739, 743, 753, 767, 785, 787, 789, 791, 793, 805, 807, 809, 811, 813], "summary": {"covered_lines": 253, "num_statements": 253, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 1, "executed_branches": [], "missing_branches": []}}, "classes": {"CAT": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 0, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 13, "executed_branches": [], "missing_branches": []}, "ApiKey": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 0, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 20, "executed_branches": [], "missing_branches": []}, "Tag": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 2, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 2, "excluded_lines": 0, "percent_statements_covered": 0.0, "percent_statements_covered_display": "0", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [30, 33], "excluded_lines": [], "start_line": 27, "executed_branches": [], "missing_branches": []}, "": {"executed_lines": [1, 2, 3, 9, 11, 13, 14, 15, 16, 17, 20, 23, 24, 27, 29, 32, 36, 38, 40, 42, 44, 46, 48, 50, 52, 54, 56, 58, 60, 62, 64, 66, 68, 70, 72, 74, 76, 78, 80, 82, 84, 95, 98, 100, 102, 104, 107, 109, 111, 113, 115, 117, 119, 121, 123, 125, 127, 129, 131, 133, 135, 137, 139, 141, 143, 145, 147, 149, 151, 153, 155, 157, 159, 161, 163, 165, 167, 169, 171, 173, 175, 179, 181, 183, 185, 187, 189, 191, 193, 195, 197, 199, 201, 203, 205, 207, 209, 211, 213, 215, 217, 219, 221, 223, 225, 227, 229, 232, 234, 236, 238, 240, 242, 244, 246, 248, 250, 252, 254, 256, 258, 260, 262, 264, 266, 268, 270, 272, 274, 276, 278, 280, 282, 284, 286, 288, 290, 292, 294, 296, 298, 300, 302, 304, 306, 308, 310, 312, 314, 316, 318, 320, 322, 324, 326, 328, 330, 332, 334, 336, 338, 340, 342, 344, 346, 350, 353, 354, 355, 359, 361, 366, 372, 378, 384, 390, 396, 402, 409, 411, 413, 415, 426, 428, 430, 432, 434, 436, 438, 440, 442, 444, 446, 448, 450, 452, 454, 456, 458, 460, 462, 464, 466, 523, 525, 527, 529, 531, 533, 535, 537, 539, 541, 543, 545, 547, 549, 551, 560, 562, 564, 566, 568, 570, 664, 668, 670, 673, 689, 691, 693, 695, 697, 699, 701, 703, 705, 707, 737, 739, 743, 753, 767, 785, 787, 789, 791, 793, 805, 807, 809, 811, 813], "summary": {"covered_lines": 253, "num_statements": 253, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 1, "executed_branches": [], "missing_branches": []}}}, "custom_components/goecharger_api2/select.py": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 100, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 100, "excluded_lines": 0, "percent_statements_covered": 0.0, "percent_statements_covered_display": "0", "num_branches": 56, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 56, "percent_branches_covered": 0.0, "percent_branches_covered_display": "0"}, "missing_lines": [1, 3, 4, 5, 6, 7, 9, 10, 12, 13, 14, 16, 19, 20, 21, 22, 24, 25, 26, 27, 28, 30, 31, 32, 34, 37, 38, 39, 40, 43, 59, 61, 62, 63, 64, 65, 66, 67, 68, 70, 71, 73, 74, 76, 79, 82, 83, 84, 85, 87, 89, 90, 92, 93, 95, 96, 97, 98, 99, 101, 102, 104, 105, 107, 108, 109, 111, 113, 116, 117, 118, 119, 120, 122, 124, 126, 127, 129, 130, 133, 135, 165, 167, 169, 172, 173, 175, 178, 179, 181, 184, 185, 187, 190, 191, 193, 196, 198, 199, 200], "excluded_lines": [], "executed_branches": [], "missing_branches": [[24, 25], [24, 30], [25, 26], [25, 34], [26, 25], [26, 27], [30, 31], [30, 34], [39, 40], [39, 59], [65, 66], [65, 79], [66, 67], [66, 73], [67, 68], [67, 70], [73, 74], [73, 76], [79, 82], [79, 89], [82, 83], [82, 84], [84, 85], [84, 87], [89, 90], [89, 92], [92, 93], [92, 99], [104, 105], [104, 129], [105, 107], [105, 126], [108, 109], [108, 111], [111, 113], [111, 122], [116, 117], [116, 122], [117, 118], [117, 119], [119, 116], [119, 120], [129, 130], [129, 133], [133, 135], [133, 198], [165, 167], [165, 169], [172, 173], [172, 175], [178, 179], [178, 181], [184, 185], [184, 187], [190, 191], [190, 193]], "functions": {"async_setup_entry": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 12, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 12, "excluded_lines": 0, "percent_statements_covered": 0.0, "percent_statements_covered_display": "0", "num_branches": 8, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 8, "percent_branches_covered": 0.0, "percent_branches_covered_display": "0"}, "missing_lines": [20, 21, 22, 24, 25, 26, 27, 28, 30, 31, 32, 34], "excluded_lines": [], "start_line": 19, "executed_branches": [], "missing_branches": [[24, 25], [24, 30], [25, 26], [25, 34], [26, 25], [26, 27], [30, 31], [30, 34]]}, "GoeChargerSelect.__init__": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 4, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 4, "excluded_lines": 0, "percent_statements_covered": 0.0, "percent_statements_covered_display": "0", "num_branches": 2, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 2, "percent_branches_covered": 0.0, "percent_branches_covered_display": "0"}, "missing_lines": [39, 40, 43, 59], "excluded_lines": [], "start_line": 38, "executed_branches": [], "missing_branches": [[39, 40], [39, 59]]}, "GoeChargerSelect.current_option": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 26, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 26, "excluded_lines": 0, "percent_statements_covered": 0.0, "percent_statements_covered_display": "0", "num_branches": 18, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 18, "percent_branches_covered": 0.0, "percent_branches_covered_display": "0"}, "missing_lines": [63, 64, 65, 66, 67, 68, 70, 71, 73, 74, 76, 79, 82, 83, 84, 85, 87, 89, 90, 92, 93, 95, 96, 97, 98, 99], "excluded_lines": [], "start_line": 62, "executed_branches": [], "missing_branches": [[65, 66], [65, 79], [66, 67], [66, 73], [67, 68], [67, 70], [73, 74], [73, 76], [79, 82], [79, 89], [82, 83], [82, 84], [84, 85], [84, 87], [89, 90], [89, 92], [92, 93], [92, 99]]}, "GoeChargerSelect.async_select_option": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 40, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 40, "excluded_lines": 0, "percent_statements_covered": 0.0, "percent_statements_covered_display": "0", "num_branches": 28, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 28, "percent_branches_covered": 0.0, "percent_branches_covered_display": "0"}, "missing_lines": [102, 104, 105, 107, 108, 109, 111, 113, 116, 117, 118, 119, 120, 122, 124, 126, 127, 129, 130, 133, 135, 165, 167, 169, 172, 173, 175, 178, 179, 181, 184, 185, 187, 190, 191, 193, 196, 198, 199, 200], "excluded_lines": [], "start_line": 101, "executed_branches": [], "missing_branches": [[104, 105], [104, 129], [105, 107], [105, 126], [108, 109], [108, 111], [111, 113], [111, 122], [116, 117], [116, 122], [117, 118], [117, 119], [119, 116], [119, 120], [129, 130], [129, 133], [133, 135], [133, 198], [165, 167], [165, 169], [172, 173], [172, 175], [178, 179], [178, 181], [184, 185], [184, 187], [190, 191], [190, 193]]}, "": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 18, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 18, "excluded_lines": 0, "percent_statements_covered": 0.0, "percent_statements_covered_display": "0", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [1, 3, 4, 5, 6, 7, 9, 10, 12, 13, 14, 16, 19, 37, 38, 61, 62, 101], "excluded_lines": [], "start_line": 1, "executed_branches": [], "missing_branches": []}}, "classes": {"GoeChargerSelect": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 70, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 70, "excluded_lines": 0, "percent_statements_covered": 0.0, "percent_statements_covered_display": "0", "num_branches": 48, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 48, "percent_branches_covered": 0.0, "percent_branches_covered_display": "0"}, "missing_lines": [39, 40, 43, 59, 63, 64, 65, 66, 67, 68, 70, 71, 73, 74, 76, 79, 82, 83, 84, 85, 87, 89, 90, 92, 93, 95, 96, 97, 98, 99, 102, 104, 105, 107, 108, 109, 111, 113, 116, 117, 118, 119, 120, 122, 124, 126, 127, 129, 130, 133, 135, 165, 167, 169, 172, 173, 175, 178, 179, 181, 184, 185, 187, 190, 191, 193, 196, 198, 199, 200], "excluded_lines": [], "start_line": 37, "executed_branches": [], "missing_branches": [[39, 40], [39, 59], [65, 66], [65, 79], [66, 67], [66, 73], [67, 68], [67, 70], [73, 74], [73, 76], [79, 82], [79, 89], [82, 83], [82, 84], [84, 85], [84, 87], [89, 90], [89, 92], [92, 93], [92, 99], [104, 105], [104, 129], [105, 107], [105, 126], [108, 109], [108, 111], [111, 113], [111, 122], [116, 117], [116, 122], [117, 118], [117, 119], [119, 116], [119, 120], [129, 130], [129, 133], [133, 135], [133, 198], [165, 167], [165, 169], [172, 173], [172, 175], [178, 179], [178, 181], [184, 185], [184, 187], [190, 191], [190, 193]]}, "": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 30, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 30, "excluded_lines": 0, "percent_statements_covered": 0.0, "percent_statements_covered_display": "0", "num_branches": 8, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 8, "percent_branches_covered": 0.0, "percent_branches_covered_display": "0"}, "missing_lines": [1, 3, 4, 5, 6, 7, 9, 10, 12, 13, 14, 16, 19, 20, 21, 22, 24, 25, 26, 27, 28, 30, 31, 32, 34, 37, 38, 61, 62, 101], "excluded_lines": [], "start_line": 1, "executed_branches": [], "missing_branches": [[24, 25], [24, 30], [25, 26], [25, 34], [26, 25], [26, 27], [30, 31], [30, 34]]}}}, "custom_components/goecharger_api2/sensor.py": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 109, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 109, "excluded_lines": 0, "percent_statements_covered": 0.0, "percent_statements_covered_display": "0", "num_branches": 60, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 60, "percent_branches_covered": 0.0, "percent_branches_covered_display": "0"}, "missing_lines": [1, 2, 3, 4, 6, 7, 8, 9, 10, 11, 13, 14, 15, 16, 18, 21, 22, 23, 24, 26, 27, 28, 29, 30, 32, 33, 34, 36, 38, 39, 40, 42, 44, 45, 47, 48, 49, 50, 51, 52, 54, 55, 57, 58, 59, 60, 61, 63, 65, 66, 67, 68, 69, 70, 71, 72, 77, 78, 81, 82, 83, 84, 85, 86, 88, 89, 91, 93, 94, 95, 96, 99, 100, 101, 102, 103, 105, 107, 109, 111, 112, 114, 115, 116, 118, 122, 125, 126, 127, 128, 130, 131, 133, 134, 135, 136, 137, 138, 139, 140, 141, 143, 144, 145, 147, 148, 149, 150, 153], "excluded_lines": [], "executed_branches": [], "missing_branches": [[26, 27], [26, 32], [27, 28], [27, 36], [28, 27], [28, 29], [32, 33], [32, 36], [57, 58], [57, 63], [59, 60], [59, 61], [69, 70], [69, 111], [70, 71], [70, 94], [77, 78], [77, 93], [81, 82], [81, 83], [83, 84], [83, 85], [85, 86], [85, 88], [88, 89], [88, 91], [94, 95], [94, 96], [96, 99], [96, 109], [100, 101], [100, 107], [102, 103], [102, 105], [111, 112], [111, 114], [114, 115], [114, 122], [115, 116], [115, 118], [122, 125], [122, 133], [125, 126], [125, 130], [127, 128], [127, 130], [130, 131], [130, 133], [133, 134], [133, 135], [135, 136], [135, 137], [137, 138], [137, 153], [138, 139], [138, 140], [140, 141], [140, 153], [144, 145], [144, 147]], "functions": {"async_setup_entry": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 12, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 12, "excluded_lines": 0, "percent_statements_covered": 0.0, "percent_statements_covered_display": "0", "num_branches": 8, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 8, "percent_branches_covered": 0.0, "percent_branches_covered_display": "0"}, "missing_lines": [22, 23, 24, 26, 27, 28, 29, 30, 32, 33, 34, 36], "excluded_lines": [], "start_line": 21, "executed_branches": [], "missing_branches": [[26, 27], [26, 32], [27, 28], [27, 36], [28, 27], [28, 29], [32, 33], [32, 36]]}, "GoeChargerSensor.__init__": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 1, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 1, "excluded_lines": 0, "percent_statements_covered": 0.0, "percent_statements_covered_display": "0", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [45], "excluded_lines": [], "start_line": 44, "executed_branches": [], "missing_branches": []}, "GoeChargerSensor._camel_to_snake": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 4, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 4, "excluded_lines": 0, "percent_statements_covered": 0.0, "percent_statements_covered_display": "0", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [49, 50, 51, 52], "excluded_lines": [], "start_line": 48, "executed_branches": [], "missing_branches": []}, "GoeChargerSensor.state": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 6, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 6, "excluded_lines": 0, "percent_statements_covered": 0.0, "percent_statements_covered_display": "0", "num_branches": 4, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 4, "percent_branches_covered": 0.0, "percent_branches_covered_display": "0"}, "missing_lines": [57, 58, 59, 60, 61, 63], "excluded_lines": [], "start_line": 55, "executed_branches": [], "missing_branches": [[57, 58], [57, 63], [59, 60], [59, 61]]}, "GoeChargerSensor.native_value": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 59, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 59, "excluded_lines": 0, "percent_statements_covered": 0.0, "percent_statements_covered_display": "0", "num_branches": 48, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 48, "percent_branches_covered": 0.0, "percent_branches_covered_display": "0"}, "missing_lines": [67, 68, 69, 70, 71, 72, 77, 78, 81, 82, 83, 84, 85, 86, 88, 89, 91, 93, 94, 95, 96, 99, 100, 101, 102, 103, 105, 107, 109, 111, 112, 114, 115, 116, 118, 122, 125, 126, 127, 128, 130, 131, 133, 134, 135, 136, 137, 138, 139, 140, 141, 143, 144, 145, 147, 148, 149, 150, 153], "excluded_lines": [], "start_line": 66, "executed_branches": [], "missing_branches": [[69, 70], [69, 111], [70, 71], [70, 94], [77, 78], [77, 93], [81, 82], [81, 83], [83, 84], [83, 85], [85, 86], [85, 88], [88, 89], [88, 91], [94, 95], [94, 96], [96, 99], [96, 109], [100, 101], [100, 107], [102, 103], [102, 105], [111, 112], [111, 114], [114, 115], [114, 122], [115, 116], [115, 118], [122, 125], [122, 133], [125, 126], [125, 130], [127, 128], [127, 130], [130, 131], [130, 133], [133, 134], [133, 135], [135, 136], [135, 137], [137, 138], [137, 153], [138, 139], [138, 140], [140, 141], [140, 153], [144, 145], [144, 147]]}, "": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 27, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 27, "excluded_lines": 0, "percent_statements_covered": 0.0, "percent_statements_covered_display": "0", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [1, 2, 3, 4, 6, 7, 8, 9, 10, 11, 13, 14, 15, 16, 18, 21, 38, 39, 40, 42, 44, 47, 48, 54, 55, 65, 66], "excluded_lines": [], "start_line": 1, "executed_branches": [], "missing_branches": []}}, "classes": {"GoeChargerSensor": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 70, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 70, "excluded_lines": 0, "percent_statements_covered": 0.0, "percent_statements_covered_display": "0", "num_branches": 52, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 52, "percent_branches_covered": 0.0, "percent_branches_covered_display": "0"}, "missing_lines": [45, 49, 50, 51, 52, 57, 58, 59, 60, 61, 63, 67, 68, 69, 70, 71, 72, 77, 78, 81, 82, 83, 84, 85, 86, 88, 89, 91, 93, 94, 95, 96, 99, 100, 101, 102, 103, 105, 107, 109, 111, 112, 114, 115, 116, 118, 122, 125, 126, 127, 128, 130, 131, 133, 134, 135, 136, 137, 138, 139, 140, 141, 143, 144, 145, 147, 148, 149, 150, 153], "excluded_lines": [], "start_line": 42, "executed_branches": [], "missing_branches": [[57, 58], [57, 63], [59, 60], [59, 61], [69, 70], [69, 111], [70, 71], [70, 94], [77, 78], [77, 93], [81, 82], [81, 83], [83, 84], [83, 85], [85, 86], [85, 88], [88, 89], [88, 91], [94, 95], [94, 96], [96, 99], [96, 109], [100, 101], [100, 107], [102, 103], [102, 105], [111, 112], [111, 114], [114, 115], [114, 122], [115, 116], [115, 118], [122, 125], [122, 133], [125, 126], [125, 130], [127, 128], [127, 130], [130, 131], [130, 133], [133, 134], [133, 135], [135, 136], [135, 137], [137, 138], [137, 153], [138, 139], [138, 140], [140, 141], [140, 153], [144, 145], [144, 147]]}, "": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 39, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 39, "excluded_lines": 0, "percent_statements_covered": 0.0, "percent_statements_covered_display": "0", "num_branches": 8, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 8, "percent_branches_covered": 0.0, "percent_branches_covered_display": "0"}, "missing_lines": [1, 2, 3, 4, 6, 7, 8, 9, 10, 11, 13, 14, 15, 16, 18, 21, 22, 23, 24, 26, 27, 28, 29, 30, 32, 33, 34, 36, 38, 39, 40, 42, 44, 47, 48, 54, 55, 65, 66], "excluded_lines": [], "start_line": 1, "executed_branches": [], "missing_branches": [[26, 27], [26, 32], [27, 28], [27, 36], [28, 27], [28, 29], [32, 33], [32, 36]]}}}, "custom_components/goecharger_api2/service.py": {"executed_lines": [1, 2, 3, 4, 6, 8, 9, 10, 11, 13, 14, 15, 17, 19, 21, 23, 25, 26, 30, 32, 33, 35, 37, 38, 39, 41, 42, 44, 45, 49, 50, 51, 53, 55, 56, 57, 60, 71, 72, 73, 74, 75, 76, 77, 78, 80, 85, 86, 87, 88, 89, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 115, 117, 118, 119, 132, 133, 134, 140, 142, 143, 144, 147, 149, 150, 151, 159], "summary": {"covered_lines": 79, "num_statements": 104, "percent_covered": 66.43835616438356, "percent_covered_display": "66", "missing_lines": 25, "excluded_lines": 0, "percent_statements_covered": 75.96153846153847, "percent_statements_covered_display": "76", "num_branches": 42, "num_partial_branches": 10, "covered_branches": 18, "missing_branches": 24, "percent_branches_covered": 42.857142857142854, "percent_branches_covered_display": "43"}, "missing_lines": [24, 27, 28, 62, 63, 64, 65, 66, 67, 69, 95, 96, 97, 99, 100, 126, 128, 129, 130, 137, 138, 153, 154, 156, 157], "excluded_lines": [], "executed_branches": [[23, 25], [37, -35], [37, 38], [60, 71], [74, 75], [75, 76], [75, 77], [77, 78], [77, 80], [88, 89], [103, 104], [110, 111], [118, 119], [133, 134], [143, 144], [143, 147], [149, 150], [150, 151]], "missing_branches": [[23, 24], [60, 62], [63, 64], [63, 71], [64, 65], [64, 71], [66, 67], [66, 69], [74, 99], [88, 99], [96, 97], [96, 99], [99, -55], [99, 100], [103, 137], [110, 126], [118, -102], [129, -102], [129, 130], [133, -102], [137, -102], [137, 138], [149, 156], [150, 153]], "functions": {"_is_finite_number": {"executed_lines": [23, 25, 26], "summary": {"covered_lines": 3, "num_statements": 6, "percent_covered": 50.0, "percent_covered_display": "50", "missing_lines": 3, "excluded_lines": 0, "percent_statements_covered": 50.0, "percent_statements_covered_display": "50", "num_branches": 2, "num_partial_branches": 1, "covered_branches": 1, "missing_branches": 1, "percent_branches_covered": 50.0, "percent_branches_covered_display": "50"}, "missing_lines": [24, 27, 28], "excluded_lines": [], "start_line": 21, "executed_branches": [[23, 25]], "missing_branches": [[23, 24]]}, "add_config_entry_to_service": {"executed_lines": [32, 33], "summary": {"covered_lines": 2, "num_statements": 2, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 30, "executed_branches": [], "missing_branches": []}, "remove_config_entry_from_service": {"executed_lines": [37, 38, 39], "summary": {"covered_lines": 3, "num_statements": 3, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 2, "num_partial_branches": 0, "covered_branches": 2, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 35, "executed_branches": [[37, -35], [37, 38]], "missing_branches": []}, "GoeChargerApiV2Service.__init__": {"executed_lines": [44, 45, 49, 50, 51, 53], "summary": {"covered_lines": 6, "num_statements": 6, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 42, "executed_branches": [], "missing_branches": []}, "GoeChargerApiV2Service.set_pv_data": {"executed_lines": [56, 57, 60, 71, 72, 73, 74, 75, 76, 77, 78, 80, 85, 86, 87, 88, 89], "summary": {"covered_lines": 17, "num_statements": 29, "percent_covered": 48.97959183673469, "percent_covered_display": "49", "missing_lines": 12, "excluded_lines": 0, "percent_statements_covered": 58.62068965517241, "percent_statements_covered_display": "59", "num_branches": 20, "num_partial_branches": 3, "covered_branches": 7, "missing_branches": 13, "percent_branches_covered": 35.0, "percent_branches_covered_display": "35"}, "missing_lines": [62, 63, 64, 65, 66, 67, 69, 95, 96, 97, 99, 100], "excluded_lines": [], "start_line": 55, "executed_branches": [[60, 71], [74, 75], [75, 76], [75, 77], [77, 78], [77, 80], [88, 89]], "missing_branches": [[60, 62], [63, 64], [63, 71], [64, 65], [64, 71], [66, 67], [66, 69], [74, 99], [88, 99], [96, 97], [96, 99], [99, -55], [99, 100]]}, "GoeChargerApiV2Service.stop_charging": {"executed_lines": [103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 115, 117, 118, 119, 132, 133, 134], "summary": {"covered_lines": 18, "num_statements": 24, "percent_covered": 61.111111111111114, "percent_covered_display": "61", "missing_lines": 6, "excluded_lines": 0, "percent_statements_covered": 75.0, "percent_statements_covered_display": "75", "num_branches": 12, "num_partial_branches": 4, "covered_branches": 4, "missing_branches": 8, "percent_branches_covered": 33.333333333333336, "percent_branches_covered_display": "33"}, "missing_lines": [126, 128, 129, 130, 137, 138], "excluded_lines": [], "start_line": 102, "executed_branches": [[103, 104], [110, 111], [118, 119], [133, 134]], "missing_branches": [[103, 137], [110, 126], [118, -102], [129, -102], [129, 130], [133, -102], [137, -102], [137, 138]]}, "GoeChargerApiV2Service.get_coordinator": {"executed_lines": [142, 143, 144, 147, 149, 150, 151, 159], "summary": {"covered_lines": 8, "num_statements": 12, "percent_covered": 66.66666666666667, "percent_covered_display": "67", "missing_lines": 4, "excluded_lines": 0, "percent_statements_covered": 66.66666666666667, "percent_statements_covered_display": "67", "num_branches": 6, "num_partial_branches": 2, "covered_branches": 4, "missing_branches": 2, "percent_branches_covered": 66.66666666666667, "percent_branches_covered_display": "67"}, "missing_lines": [153, 154, 156, 157], "excluded_lines": [], "start_line": 140, "executed_branches": [[143, 144], [143, 147], [149, 150], [150, 151]], "missing_branches": [[149, 156], [150, 153]]}, "": {"executed_lines": [1, 2, 3, 4, 6, 8, 9, 10, 11, 13, 14, 15, 17, 19, 21, 30, 35, 41, 42, 55, 102, 140], "summary": {"covered_lines": 22, "num_statements": 22, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 1, "executed_branches": [], "missing_branches": []}}, "classes": {"GoeChargerApiV2Service": {"executed_lines": [44, 45, 49, 50, 51, 53, 56, 57, 60, 71, 72, 73, 74, 75, 76, 77, 78, 80, 85, 86, 87, 88, 89, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 115, 117, 118, 119, 132, 133, 134, 142, 143, 144, 147, 149, 150, 151, 159], "summary": {"covered_lines": 49, "num_statements": 71, "percent_covered": 58.71559633027523, "percent_covered_display": "59", "missing_lines": 22, "excluded_lines": 0, "percent_statements_covered": 69.01408450704226, "percent_statements_covered_display": "69", "num_branches": 38, "num_partial_branches": 9, "covered_branches": 15, "missing_branches": 23, "percent_branches_covered": 39.473684210526315, "percent_branches_covered_display": "39"}, "missing_lines": [62, 63, 64, 65, 66, 67, 69, 95, 96, 97, 99, 100, 126, 128, 129, 130, 137, 138, 153, 154, 156, 157], "excluded_lines": [], "start_line": 41, "executed_branches": [[60, 71], [74, 75], [75, 76], [75, 77], [77, 78], [77, 80], [88, 89], [103, 104], [110, 111], [118, 119], [133, 134], [143, 144], [143, 147], [149, 150], [150, 151]], "missing_branches": [[60, 62], [63, 64], [63, 71], [64, 65], [64, 71], [66, 67], [66, 69], [74, 99], [88, 99], [96, 97], [96, 99], [99, -55], [99, 100], [103, 137], [110, 126], [118, -102], [129, -102], [129, 130], [133, -102], [137, -102], [137, 138], [149, 156], [150, 153]]}, "": {"executed_lines": [1, 2, 3, 4, 6, 8, 9, 10, 11, 13, 14, 15, 17, 19, 21, 23, 25, 26, 30, 32, 33, 35, 37, 38, 39, 41, 42, 55, 102, 140], "summary": {"covered_lines": 30, "num_statements": 33, "percent_covered": 89.1891891891892, "percent_covered_display": "89", "missing_lines": 3, "excluded_lines": 0, "percent_statements_covered": 90.9090909090909, "percent_statements_covered_display": "91", "num_branches": 4, "num_partial_branches": 1, "covered_branches": 3, "missing_branches": 1, "percent_branches_covered": 75.0, "percent_branches_covered_display": "75"}, "missing_lines": [24, 27, 28], "excluded_lines": [], "start_line": 1, "executed_branches": [[23, 25], [37, -35], [37, 38]], "missing_branches": [[23, 24]]}}}, "custom_components/goecharger_api2/switch.py": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 101, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 101, "excluded_lines": 0, "percent_statements_covered": 0.0, "percent_statements_covered_display": "0", "num_branches": 48, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 48, "percent_branches_covered": 0.0, "percent_branches_covered_display": "0"}, "missing_lines": [1, 2, 4, 5, 6, 7, 8, 10, 11, 12, 14, 17, 18, 19, 20, 22, 23, 24, 25, 26, 28, 29, 30, 32, 35, 36, 37, 38, 40, 42, 43, 44, 46, 47, 48, 50, 52, 53, 55, 56, 58, 60, 61, 63, 64, 66, 68, 69, 70, 72, 73, 74, 76, 78, 79, 81, 82, 84, 86, 87, 88, 89, 91, 92, 93, 94, 95, 96, 97, 98, 100, 102, 103, 104, 105, 107, 108, 109, 110, 111, 113, 114, 115, 116, 118, 119, 121, 122, 124, 126, 128, 130, 131, 133, 134, 135, 137, 138, 140, 141, 143], "excluded_lines": [], "executed_branches": [], "missing_branches": [[22, 23], [22, 28], [23, 24], [23, 32], [24, 23], [24, 25], [28, 29], [28, 32], [43, 44], [43, 55], [44, 46], [44, 52], [47, 48], [47, 50], [55, 56], [55, 58], [69, 70], [69, 81], [70, 72], [70, 78], [73, 74], [73, 76], [81, 82], [81, 84], [95, 96], [95, 113], [96, 97], [96, 102], [97, 98], [97, 100], [102, 103], [102, 104], [104, 105], [104, 113], [113, 114], [113, 128], [114, 115], [114, 119], [115, 116], [115, 118], [119, 121], [119, 126], [121, 122], [121, 124], [133, 134], [133, 135], [140, 141], [140, 143]], "functions": {"async_setup_entry": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 12, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 12, "excluded_lines": 0, "percent_statements_covered": 0.0, "percent_statements_covered_display": "0", "num_branches": 8, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 8, "percent_branches_covered": 0.0, "percent_branches_covered_display": "0"}, "missing_lines": [18, 19, 20, 22, 23, 24, 25, 26, 28, 29, 30, 32], "excluded_lines": [], "start_line": 17, "executed_branches": [], "missing_branches": [[22, 23], [22, 28], [23, 24], [23, 32], [24, 23], [24, 25], [28, 29], [28, 32]]}, "GoeChargerSwitch.__init__": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 2, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 2, "excluded_lines": 0, "percent_statements_covered": 0.0, "percent_statements_covered_display": "0", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [37, 38], "excluded_lines": [], "start_line": 36, "executed_branches": [], "missing_branches": []}, "GoeChargerSwitch.async_turn_on": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 16, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 16, "excluded_lines": 0, "percent_statements_covered": 0.0, "percent_statements_covered_display": "0", "num_branches": 8, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 8, "percent_branches_covered": 0.0, "percent_branches_covered_display": "0"}, "missing_lines": [42, 43, 44, 46, 47, 48, 50, 52, 53, 55, 56, 58, 60, 61, 63, 64], "excluded_lines": [], "start_line": 40, "executed_branches": [], "missing_branches": [[43, 44], [43, 55], [44, 46], [44, 52], [47, 48], [47, 50], [55, 56], [55, 58]]}, "GoeChargerSwitch.async_turn_off": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 16, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 16, "excluded_lines": 0, "percent_statements_covered": 0.0, "percent_statements_covered_display": "0", "num_branches": 8, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 8, "percent_branches_covered": 0.0, "percent_branches_covered_display": "0"}, "missing_lines": [68, 69, 70, 72, 73, 74, 76, 78, 79, 81, 82, 84, 86, 87, 88, 89], "excluded_lines": [], "start_line": 66, "executed_branches": [], "missing_branches": [[69, 70], [69, 81], [70, 72], [70, 78], [73, 74], [73, 76], [81, 82], [81, 84]]}, "GoeChargerSwitch.is_on": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 27, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 27, "excluded_lines": 0, "percent_statements_covered": 0.0, "percent_statements_covered_display": "0", "num_branches": 20, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 20, "percent_branches_covered": 0.0, "percent_branches_covered_display": "0"}, "missing_lines": [93, 94, 95, 96, 97, 98, 100, 102, 103, 104, 105, 107, 108, 109, 110, 111, 113, 114, 115, 116, 118, 119, 121, 122, 124, 126, 128], "excluded_lines": [], "start_line": 92, "executed_branches": [], "missing_branches": [[95, 96], [95, 113], [96, 97], [96, 102], [97, 98], [97, 100], [102, 103], [102, 104], [104, 105], [104, 113], [113, 114], [113, 128], [114, 115], [114, 119], [115, 116], [115, 118], [119, 121], [119, 126], [121, 122], [121, 124]]}, "GoeChargerSwitch.state": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 3, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 3, "excluded_lines": 0, "percent_statements_covered": 0.0, "percent_statements_covered_display": "0", "num_branches": 2, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 2, "percent_branches_covered": 0.0, "percent_branches_covered_display": "0"}, "missing_lines": [133, 134, 135], "excluded_lines": [], "start_line": 131, "executed_branches": [], "missing_branches": [[133, 134], [133, 135]]}, "GoeChargerSwitch.icon": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 3, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 3, "excluded_lines": 0, "percent_statements_covered": 0.0, "percent_statements_covered_display": "0", "num_branches": 2, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 2, "percent_branches_covered": 0.0, "percent_branches_covered_display": "0"}, "missing_lines": [140, 141, 143], "excluded_lines": [], "start_line": 138, "executed_branches": [], "missing_branches": [[140, 141], [140, 143]]}, "": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 22, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 22, "excluded_lines": 0, "percent_statements_covered": 0.0, "percent_statements_covered_display": "0", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [1, 2, 4, 5, 6, 7, 8, 10, 11, 12, 14, 17, 35, 36, 40, 66, 91, 92, 130, 131, 137, 138], "excluded_lines": [], "start_line": 1, "executed_branches": [], "missing_branches": []}}, "classes": {"GoeChargerSwitch": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 67, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 67, "excluded_lines": 0, "percent_statements_covered": 0.0, "percent_statements_covered_display": "0", "num_branches": 40, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 40, "percent_branches_covered": 0.0, "percent_branches_covered_display": "0"}, "missing_lines": [37, 38, 42, 43, 44, 46, 47, 48, 50, 52, 53, 55, 56, 58, 60, 61, 63, 64, 68, 69, 70, 72, 73, 74, 76, 78, 79, 81, 82, 84, 86, 87, 88, 89, 93, 94, 95, 96, 97, 98, 100, 102, 103, 104, 105, 107, 108, 109, 110, 111, 113, 114, 115, 116, 118, 119, 121, 122, 124, 126, 128, 133, 134, 135, 140, 141, 143], "excluded_lines": [], "start_line": 35, "executed_branches": [], "missing_branches": [[43, 44], [43, 55], [44, 46], [44, 52], [47, 48], [47, 50], [55, 56], [55, 58], [69, 70], [69, 81], [70, 72], [70, 78], [73, 74], [73, 76], [81, 82], [81, 84], [95, 96], [95, 113], [96, 97], [96, 102], [97, 98], [97, 100], [102, 103], [102, 104], [104, 105], [104, 113], [113, 114], [113, 128], [114, 115], [114, 119], [115, 116], [115, 118], [119, 121], [119, 126], [121, 122], [121, 124], [133, 134], [133, 135], [140, 141], [140, 143]]}, "": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 34, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 34, "excluded_lines": 0, "percent_statements_covered": 0.0, "percent_statements_covered_display": "0", "num_branches": 8, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 8, "percent_branches_covered": 0.0, "percent_branches_covered_display": "0"}, "missing_lines": [1, 2, 4, 5, 6, 7, 8, 10, 11, 12, 14, 17, 18, 19, 20, 22, 23, 24, 25, 26, 28, 29, 30, 32, 35, 36, 40, 66, 91, 92, 130, 131, 137, 138], "excluded_lines": [], "start_line": 1, "executed_branches": [], "missing_branches": [[22, 23], [22, 28], [23, 24], [23, 32], [24, 23], [24, 25], [28, 29], [28, 32]]}}}}, "totals": {"covered_lines": 1397, "num_statements": 2529, "percent_covered": 48.97131266299623, "percent_covered_display": "49", "missing_lines": 1132, "excluded_lines": 0, "percent_statements_covered": 55.23922499011467, "percent_statements_covered_display": "55", "num_branches": 922, "num_partial_branches": 105, "covered_branches": 293, "missing_branches": 629, "percent_branches_covered": 31.77874186550976, "percent_branches_covered_display": "32"}} \ No newline at end of file diff --git a/tests/reports/2026-10-29/coverage-blocking.json b/tests/reports/2026-10-29/coverage-blocking.json new file mode 100644 index 0000000..179221e --- /dev/null +++ b/tests/reports/2026-10-29/coverage-blocking.json @@ -0,0 +1 @@ +{"meta": {"format": 3, "version": "7.16.2", "timestamp": "2026-10-29T11:48:45.708953", "branch_coverage": true, "show_contexts": false}, "files": {"custom_components/goecharger_api2/__init__.py": {"executed_lines": [1, 2, 3, 4, 5, 6, 7, 9, 10, 11, 12, 23, 24, 25, 30, 31, 32, 33, 34, 35, 36, 38, 45, 46, 60, 61, 63, 65, 66, 67, 68, 69, 70, 71, 73, 74, 75, 76, 77, 78, 81, 82, 85, 86, 87, 88, 89, 90, 91, 92, 93, 97, 98, 100, 103, 105, 108, 109, 110, 111, 112, 113, 116, 117, 118, 120, 122, 123, 124, 125, 126, 127, 128, 129, 131, 132, 133, 135, 136, 137, 138, 140, 141, 144, 145, 148, 149, 150, 151, 153, 154, 155, 156, 158, 162, 165, 166, 173, 175, 176, 177, 179, 180, 182, 184, 187, 188, 190, 191, 192, 193, 194, 195, 196, 197, 198, 201, 202, 203, 204, 206, 209, 210, 211, 214, 215, 216, 217, 218, 219, 220, 221, 224, 225, 226, 227, 228, 230, 231, 232, 233, 234, 236, 238, 240, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 254, 255, 257, 258, 259, 260, 261, 262, 269, 270, 271, 272, 273, 275, 276, 278, 279, 280, 281, 283, 284, 285, 286, 287, 288, 289, 290, 291, 294, 296, 297, 298, 300, 301, 302, 303, 304, 306, 307, 310, 311, 312, 314, 315, 316, 325, 326, 336, 337, 339, 340, 341, 343, 346, 347, 348, 349, 350, 351, 352, 353, 354, 356, 358, 359, 360, 361, 362, 363, 368, 371, 374, 375, 377, 378, 379, 380, 381, 382, 383, 384, 386, 387, 388, 391, 392, 393, 394, 396, 398, 403, 404, 406, 410, 411, 412, 413, 416, 417, 418, 419, 423, 424, 425, 428, 431, 432, 433, 435, 437, 438, 442, 445, 446, 448, 452, 453, 454, 456, 457, 458, 460, 463, 464, 465, 468, 470, 471, 472, 473, 474, 475, 476, 478, 479, 481, 482, 484, 486, 487, 493, 494, 495, 496, 498, 499, 500, 501, 502, 503, 504, 505, 506, 508, 509, 510, 511, 512, 513, 514, 515, 516, 518, 519, 520, 521, 524, 525, 526, 528, 529, 530, 531, 532, 533, 534, 535, 536, 537, 538, 540, 542, 544, 545, 546, 547, 549, 550, 551, 553, 554, 555, 557, 558, 559, 561, 562, 565, 567, 569, 570, 572, 574, 575, 577, 578, 579, 580, 582, 583, 584, 586, 587, 588, 591, 595, 596, 597, 598, 599, 600, 601, 602, 604, 606, 608, 609, 613, 614, 615, 616, 617, 618, 619, 621, 622, 623, 624, 625, 631, 633, 634, 635, 636, 638, 639, 640, 642, 643, 644, 645, 646, 647, 648, 649, 651, 652, 653, 654, 656, 657, 658, 659, 660, 661, 663, 664, 665, 666, 667, 668, 669, 670, 672, 673, 677, 678, 679, 684, 685, 686, 688, 692, 693, 694, 696, 700, 701, 702, 703, 704, 707, 710, 711, 712, 713, 714, 715, 716, 719, 720, 721, 723, 725, 727, 729, 730, 731, 732, 733, 734, 736, 737, 738, 739, 740, 742, 743, 745, 750, 751, 752, 753, 754, 755, 756, 757, 758, 759, 760, 761, 763, 768, 769, 771, 772, 773, 774, 776, 778, 779, 780, 783, 784, 785, 787, 788, 789, 791, 792, 794, 797, 799, 800, 801, 803, 805, 806, 808, 811, 812, 813, 814, 815, 817, 819, 834, 836, 837, 838, 839, 840, 842, 843, 845, 846, 848, 849, 852, 855, 858, 859, 861, 863, 864, 866, 867, 870, 873, 874, 875, 877, 878, 879, 880, 881, 882, 883, 885, 886, 887, 888, 889, 890, 892, 894, 895, 897, 898, 900, 907, 911, 912, 915, 916, 920, 921, 923, 925], "summary": {"covered_lines": 575, "num_statements": 604, "percent_covered": 93.33333333333333, "percent_covered_display": "93", "missing_lines": 29, "excluded_lines": 0, "percent_statements_covered": 95.19867549668874, "percent_statements_covered_display": "95", "num_branches": 266, "num_partial_branches": 19, "covered_branches": 237, "missing_branches": 29, "percent_branches_covered": 89.09774436090225, "percent_branches_covered_display": "89"}, "missing_lines": [80, 94, 95, 364, 390, 449, 610, 611, 637, 674, 675, 680, 681, 682, 909, 913, 918, 931, 932, 933, 935, 936, 938, 939, 940, 943, 944, 945, 947], "excluded_lines": [], "executed_branches": [[74, 75], [74, 85], [75, 76], [77, 78], [85, 86], [85, 100], [89, 90], [91, 92], [91, 97], [109, 110], [109, 116], [117, 118], [117, 120], [124, 125], [124, 135], [127, 128], [127, 144], [137, 138], [137, 140], [140, 141], [140, 144], [148, 149], [148, 165], [149, 150], [149, 151], [153, 154], [153, 162], [165, 166], [165, 173], [173, 175], [173, 182], [175, 176], [175, 179], [190, 191], [190, 206], [191, 192], [191, 201], [201, 202], [201, 206], [218, 219], [220, 221], [225, 226], [225, 242], [226, 225], [226, 227], [227, 225], [227, 228], [228, 225], [228, 230], [230, 231], [230, 240], [231, 232], [231, 238], [232, 233], [232, 236], [242, -214], [242, 243], [246, 247], [246, 257], [247, 248], [247, 254], [249, 250], [249, 251], [254, 246], [254, 255], [260, 261], [260, 283], [262, 269], [262, 273], [269, 260], [269, 270], [271, 272], [273, 260], [273, 275], [276, 260], [276, 278], [278, 276], [278, 279], [280, 281], [284, -214], [284, 285], [285, 286], [285, 288], [311, 312], [311, 314], [314, 315], [314, 325], [340, 341], [340, 343], [353, 354], [353, 356], [363, 368], [379, -377], [379, 380], [380, 381], [382, 383], [382, 386], [386, 387], [387, 388], [391, -377], [391, 392], [393, 394], [393, 396], [411, 412], [411, 416], [417, 418], [417, 423], [432, 433], [432, 435], [448, 452], [463, 464], [463, 468], [470, 471], [470, 478], [472, 473], [472, 478], [494, -493], [494, 495], [499, -498], [499, 500], [509, 510], [509, 518], [514, 515], [519, -508], [519, 520], [532, 533], [550, -549], [550, 551], [562, 565], [562, 567], [569, 570], [569, 572], [575, 577], [575, 586], [577, 578], [577, 582], [582, 583], [582, 586], [586, -561], [586, 587], [587, -561], [587, 588], [597, 598], [597, 604], [599, 600], [609, 613], [613, 614], [613, 617], [617, 618], [617, 621], [623, 624], [643, 644], [643, 647], [647, 648], [647, 651], [664, 665], [664, 668], [668, 669], [668, 672], [685, 686], [685, 688], [688, 692], [688, 700], [692, 693], [692, 696], [700, 701], [700, 707], [702, 703], [702, 707], [711, 712], [711, 715], [713, 714], [715, 716], [715, 719], [720, 721], [720, 723], [727, 729], [727, 797], [730, 731], [730, 736], [731, 732], [731, 742], [732, 733], [732, 734], [736, 737], [736, 742], [738, 739], [738, 740], [742, 743], [742, 745], [752, 753], [752, 778], [754, 755], [754, 776], [758, 759], [758, 763], [773, 774], [773, 778], [778, 779], [778, 783], [783, 784], [783, 787], [791, 792], [791, 794], [800, 801], [800, 803], [805, 806], [805, 808], [811, 812], [811, 817], [813, 814], [813, 819], [843, 845], [843, 852], [845, 846], [845, 848], [848, 849], [848, 861], [852, 855], [852, 858], [858, 859], [858, 861], [873, 874], [873, 885], [874, 875], [874, 877], [877, 878], [877, 881], [881, 882], [885, 886], [885, 887], [887, 888], [887, 889], [889, 890], [889, 892], [897, 898], [897, 900]], "missing_branches": [[75, 85], [77, 80], [89, 97], [218, -214], [220, -214], [271, 260], [280, 276], [363, 364], [380, -377], [386, -377], [387, 390], [448, 449], [514, -508], [532, 534], [599, -591], [609, 610], [623, 631], [713, 715], [881, 894], [932, 933], [932, 935], [935, 936], [935, 938], [939, 940], [939, 943], [943, 944], [943, 947], [944, 945], [944, 947]], "functions": {"async_migrate_entry": {"executed_lines": [74, 75, 76, 77, 78, 81, 82, 85, 86, 87, 88, 89, 90, 91, 92, 93, 97, 98, 100], "summary": {"covered_lines": 19, "num_statements": 22, "percent_covered": 82.3529411764706, "percent_covered_display": "82", "missing_lines": 3, "excluded_lines": 0, "percent_statements_covered": 86.36363636363636, "percent_statements_covered_display": "86", "num_branches": 12, "num_partial_branches": 3, "covered_branches": 9, "missing_branches": 3, "percent_branches_covered": 75.0, "percent_branches_covered_display": "75"}, "missing_lines": [80, 94, 95], "excluded_lines": [], "start_line": 73, "executed_branches": [[74, 75], [74, 85], [75, 76], [77, 78], [85, 86], [85, 100], [89, 90], [91, 92], [91, 97]], "missing_branches": [[75, 85], [77, 80], [89, 97]]}, "async_setup": {"executed_lines": [105], "summary": {"covered_lines": 1, "num_statements": 1, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 103, "executed_branches": [], "missing_branches": []}, "async_setup_entry": {"executed_lines": [109, 110, 111, 112, 113, 116, 117, 118, 120, 122, 123, 124, 125, 126, 127, 128, 129, 131, 132, 133, 135, 136, 137, 138, 140, 141, 144, 145, 148, 149, 150, 151, 153, 154, 155, 156, 158, 162, 165, 166, 173, 175, 176, 177, 179, 180, 182, 184], "summary": {"covered_lines": 48, "num_statements": 48, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 24, "num_partial_branches": 0, "covered_branches": 24, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 108, "executed_branches": [[109, 110], [109, 116], [117, 118], [117, 120], [124, 125], [124, 135], [127, 128], [127, 144], [137, 138], [137, 140], [140, 141], [140, 144], [148, 149], [148, 165], [149, 150], [149, 151], [153, 154], [153, 162], [165, 166], [165, 173], [173, 175], [173, 182], [175, 176], [175, 179]], "missing_branches": []}, "async_unload_entry": {"executed_lines": [188, 190, 191, 192, 193, 194, 195, 196, 197, 198, 201, 202, 203, 204, 206], "summary": {"covered_lines": 15, "num_statements": 15, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 6, "num_partial_branches": 0, "covered_branches": 6, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 187, "executed_branches": [[190, 191], [190, 206], [191, 192], [191, 201], [201, 202], [201, 206]], "missing_branches": []}, "entry_update_listener": {"executed_lines": [210, 211], "summary": {"covered_lines": 2, "num_statements": 2, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 209, "executed_branches": [], "missing_branches": []}, "check_and_write_to_16a": {"executed_lines": [216, 217, 218, 219, 220, 221, 224, 225, 226, 227, 228, 230, 231, 232, 233, 234, 236, 238, 240, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 254, 255, 257, 258, 259, 260, 261, 262, 269, 270, 271, 272, 273, 275, 276, 278, 279, 280, 281, 283, 284, 285, 286, 287, 288, 289, 290, 291], "summary": {"covered_lines": 58, "num_statements": 58, "percent_covered": 96.22641509433963, "percent_covered_display": "96", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 48, "num_partial_branches": 4, "covered_branches": 44, "missing_branches": 4, "percent_branches_covered": 91.66666666666667, "percent_branches_covered_display": "92"}, "missing_lines": [], "excluded_lines": [], "start_line": 215, "executed_branches": [[218, 219], [220, 221], [225, 226], [225, 242], [226, 225], [226, 227], [227, 225], [227, 228], [228, 225], [228, 230], [230, 231], [230, 240], [231, 232], [231, 238], [232, 233], [232, 236], [242, -214], [242, 243], [246, 247], [246, 257], [247, 248], [247, 254], [249, 250], [249, 251], [254, 246], [254, 255], [260, 261], [260, 283], [262, 269], [262, 273], [269, 260], [269, 270], [271, 272], [273, 260], [273, 275], [276, 260], [276, 278], [278, 276], [278, 279], [280, 281], [284, -214], [284, 285], [285, 286], [285, 288]], "missing_branches": [[218, -214], [220, -214], [271, 260], [280, 276]]}, "GoeChargerDataUpdateCoordinator.__init__": {"executed_lines": [301, 302, 303, 304, 306, 307, 310, 311, 312, 314, 315, 316, 325, 326, 336, 337, 339, 340, 341, 343, 346, 347, 348, 349, 350, 351, 352, 353, 354, 356, 358, 359, 360, 361, 362, 363, 368, 371, 374, 375], "summary": {"covered_lines": 40, "num_statements": 41, "percent_covered": 96.07843137254902, "percent_covered_display": "96", "missing_lines": 1, "excluded_lines": 0, "percent_statements_covered": 97.5609756097561, "percent_statements_covered_display": "98", "num_branches": 10, "num_partial_branches": 1, "covered_branches": 9, "missing_branches": 1, "percent_branches_covered": 90.0, "percent_branches_covered_display": "90"}, "missing_lines": [364], "excluded_lines": [], "start_line": 300, "executed_branches": [[311, 312], [311, 314], [314, 315], [314, 325], [340, 341], [340, 343], [353, 354], [353, 356], [363, 368]], "missing_branches": [[363, 364]]}, "GoeChargerDataUpdateCoordinator.call_later_update_device_registry": {"executed_lines": [378, 379, 380, 381, 382, 383, 384, 386, 387, 388, 391, 392, 393, 394, 396, 398], "summary": {"covered_lines": 16, "num_statements": 17, "percent_covered": 87.09677419354838, "percent_covered_display": "87", "missing_lines": 1, "excluded_lines": 0, "percent_statements_covered": 94.11764705882354, "percent_statements_covered_display": "94", "num_branches": 14, "num_partial_branches": 3, "covered_branches": 11, "missing_branches": 3, "percent_branches_covered": 78.57142857142857, "percent_branches_covered_display": "79"}, "missing_lines": [390], "excluded_lines": [], "start_line": 377, "executed_branches": [[379, -377], [379, 380], [380, 381], [382, 383], [382, 386], [386, 387], [387, 388], [391, -377], [391, 392], [393, 394], [393, 396]], "missing_branches": [[380, -377], [386, -377], [387, 390]]}, "GoeChargerDataUpdateCoordinator.cards_as_object_present": {"executed_lines": [404], "summary": {"covered_lines": 1, "num_statements": 1, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 403, "executed_branches": [], "missing_branches": []}, "GoeChargerDataUpdateCoordinator.force_async_update_now": {"executed_lines": [410, 411, 412, 413, 416, 417, 418, 419, 423, 424, 425, 428, 431, 432, 433, 435], "summary": {"covered_lines": 16, "num_statements": 16, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 6, "num_partial_branches": 0, "covered_branches": 6, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 406, "executed_branches": [[411, 412], [411, 416], [417, 418], [417, 423], [432, 433], [432, 435]], "missing_branches": []}, "GoeChargerDataUpdateCoordinator.start_websocket_and_wait_for_first_data": {"executed_lines": [438, 442, 445, 446, 448, 452, 453, 454, 456, 457, 458, 460, 463, 464, 465, 468, 470, 471, 472, 473, 474, 475, 476, 478, 479, 481, 482], "summary": {"covered_lines": 27, "num_statements": 28, "percent_covered": 94.44444444444444, "percent_covered_display": "94", "missing_lines": 1, "excluded_lines": 0, "percent_statements_covered": 96.42857142857143, "percent_statements_covered_display": "96", "num_branches": 8, "num_partial_branches": 1, "covered_branches": 7, "missing_branches": 1, "percent_branches_covered": 87.5, "percent_branches_covered_display": "88"}, "missing_lines": [449], "excluded_lines": [], "start_line": 437, "executed_branches": [[448, 452], [463, 464], [463, 468], [470, 471], [470, 478], [472, 473], [472, 478]], "missing_branches": [[448, 449]]}, "GoeChargerDataUpdateCoordinator.start_watchdog": {"executed_lines": [486, 487], "summary": {"covered_lines": 2, "num_statements": 2, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 484, "executed_branches": [], "missing_branches": []}, "GoeChargerDataUpdateCoordinator.stop_watchdog": {"executed_lines": [494, 495, 496], "summary": {"covered_lines": 3, "num_statements": 3, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 2, "num_partial_branches": 0, "covered_branches": 2, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 493, "executed_branches": [[494, -493], [494, 495]], "missing_branches": []}, "GoeChargerDataUpdateCoordinator._check_for_ws_task_and_cancel_if_running": {"executed_lines": [499, 500, 501, 502, 503, 504, 505, 506], "summary": {"covered_lines": 8, "num_statements": 8, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 2, "num_partial_branches": 0, "covered_branches": 2, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 498, "executed_branches": [[499, -498], [499, 500]], "missing_branches": []}, "GoeChargerDataUpdateCoordinator._async_watchdog_check": {"executed_lines": [509, 510, 511, 512, 513, 514, 515, 516, 518, 519, 520, 521], "summary": {"covered_lines": 12, "num_statements": 12, "percent_covered": 94.44444444444444, "percent_covered_display": "94", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 6, "num_partial_branches": 1, "covered_branches": 5, "missing_branches": 1, "percent_branches_covered": 83.33333333333333, "percent_branches_covered_display": "83"}, "missing_lines": [], "excluded_lines": [], "start_line": 508, "executed_branches": [[509, 510], [509, 518], [514, 515], [519, -508], [519, 520]], "missing_branches": [[514, -508]]}, "GoeChargerDataUpdateCoordinator.__call__": {"executed_lines": [525, 526], "summary": {"covered_lines": 2, "num_statements": 2, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 524, "executed_branches": [], "missing_branches": []}, "GoeChargerDataUpdateCoordinator.clear_data": {"executed_lines": [529, 530, 531, 532, 533, 534, 535, 536, 537, 538], "summary": {"covered_lines": 10, "num_statements": 10, "percent_covered": 91.66666666666667, "percent_covered_display": "92", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 2, "num_partial_branches": 1, "covered_branches": 1, "missing_branches": 1, "percent_branches_covered": 50.0, "percent_branches_covered_display": "50"}, "missing_lines": [], "excluded_lines": [], "start_line": 528, "executed_branches": [[532, 533]], "missing_branches": [[532, 534]]}, "GoeChargerDataUpdateCoordinator.trigger_restart_delayed": {"executed_lines": [542, 544, 545, 546, 547], "summary": {"covered_lines": 5, "num_statements": 5, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 540, "executed_branches": [], "missing_branches": []}, "GoeChargerDataUpdateCoordinator.cancel_existing_debouncer": {"executed_lines": [550, 551], "summary": {"covered_lines": 2, "num_statements": 2, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 2, "num_partial_branches": 0, "covered_branches": 2, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 549, "executed_branches": [[550, -549], [550, 551]], "missing_branches": []}, "GoeChargerDataUpdateCoordinator.request_update_in_sec": {"executed_lines": [554, 555], "summary": {"covered_lines": 2, "num_statements": 2, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 553, "executed_branches": [], "missing_branches": []}, "GoeChargerDataUpdateCoordinator._debounce_coordinator_update": {"executed_lines": [558, 559], "summary": {"covered_lines": 2, "num_statements": 2, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 557, "executed_branches": [], "missing_branches": []}, "GoeChargerDataUpdateCoordinator.handle_write_result": {"executed_lines": [562, 565, 567, 569, 570, 572, 574, 575, 577, 578, 579, 580, 582, 583, 584, 586, 587, 588], "summary": {"covered_lines": 18, "num_statements": 18, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 14, "num_partial_branches": 0, "covered_branches": 14, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 561, "executed_branches": [[562, 565], [562, 567], [569, 570], [569, 572], [575, 577], [575, 586], [577, 578], [577, 582], [582, 583], [582, 586], [586, -561], [586, 587], [587, -561], [587, 588]], "missing_branches": []}, "GoeChargerDataUpdateCoordinator._handle_client_connection_error": {"executed_lines": [595, 596, 597, 598, 599, 600, 601, 602, 604], "summary": {"covered_lines": 9, "num_statements": 9, "percent_covered": 92.3076923076923, "percent_covered_display": "92", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 4, "num_partial_branches": 1, "covered_branches": 3, "missing_branches": 1, "percent_branches_covered": 75.0, "percent_branches_covered_display": "75"}, "missing_lines": [], "excluded_lines": [], "start_line": 591, "executed_branches": [[597, 598], [597, 604], [599, 600]], "missing_branches": [[599, -591]]}, "GoeChargerDataUpdateCoordinator._async_update_data": {"executed_lines": [608, 609, 613, 614, 615, 616, 617, 618, 619, 621, 622, 623, 624, 625, 631, 633, 634, 635, 636, 638, 639, 640], "summary": {"covered_lines": 22, "num_statements": 25, "percent_covered": 84.84848484848484, "percent_covered_display": "85", "missing_lines": 3, "excluded_lines": 0, "percent_statements_covered": 88.0, "percent_statements_covered_display": "88", "num_branches": 8, "num_partial_branches": 2, "covered_branches": 6, "missing_branches": 2, "percent_branches_covered": 75.0, "percent_branches_covered_display": "75"}, "missing_lines": [610, 611, 637], "excluded_lines": [], "start_line": 606, "executed_branches": [[609, 613], [613, 614], [613, 617], [617, 618], [617, 621], [623, 624]], "missing_branches": [[609, 610], [623, 631]]}, "GoeChargerDataUpdateCoordinator.async_write_key": {"executed_lines": [643, 644, 645, 646, 647, 648, 649, 651, 652, 653, 654, 656, 657, 658, 659, 660, 661], "summary": {"covered_lines": 17, "num_statements": 17, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 4, "num_partial_branches": 0, "covered_branches": 4, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 642, "executed_branches": [[643, 644], [643, 647], [647, 648], [647, 651]], "missing_branches": []}, "GoeChargerDataUpdateCoordinator.async_write_multiple_keys": {"executed_lines": [664, 665, 666, 667, 668, 669, 670, 672, 673, 677, 678, 679], "summary": {"covered_lines": 12, "num_statements": 17, "percent_covered": 76.19047619047619, "percent_covered_display": "76", "missing_lines": 5, "excluded_lines": 0, "percent_statements_covered": 70.58823529411765, "percent_statements_covered_display": "71", "num_branches": 4, "num_partial_branches": 0, "covered_branches": 4, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [674, 675, 680, 681, 682], "excluded_lines": [], "start_line": 663, "executed_branches": [[664, 665], [664, 668], [668, 669], [668, 672]], "missing_branches": []}, "GoeChargerDataUpdateCoordinator.read_versions": {"executed_lines": [685, 686, 688, 692, 693, 694, 696, 700, 701, 702, 703, 704, 707, 710, 711, 712, 713, 714, 715, 716, 719, 720, 721, 723, 725, 727, 729, 730, 731, 732, 733, 734, 736, 737, 738, 739, 740, 742, 743, 745, 750, 751, 752, 753, 754, 755, 756, 757, 758, 759, 760, 761, 763, 768, 769, 771, 772, 773, 774, 776, 778, 779, 780, 783, 784, 785, 787, 788, 789, 791, 792, 794, 797, 799, 800, 801, 803, 805, 806, 808, 811, 812, 813, 814, 815, 817, 819, 834], "summary": {"covered_lines": 88, "num_statements": 88, "percent_covered": 99.29577464788733, "percent_covered_display": "99", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 54, "num_partial_branches": 1, "covered_branches": 53, "missing_branches": 1, "percent_branches_covered": 98.14814814814815, "percent_branches_covered_display": "98"}, "missing_lines": [], "excluded_lines": [], "start_line": 684, "executed_branches": [[685, 686], [685, 688], [688, 692], [688, 700], [692, 693], [692, 696], [700, 701], [700, 707], [702, 703], [702, 707], [711, 712], [711, 715], [713, 714], [715, 716], [715, 719], [720, 721], [720, 723], [727, 729], [727, 797], [730, 731], [730, 736], [731, 732], [731, 742], [732, 733], [732, 734], [736, 737], [736, 742], [738, 739], [738, 740], [742, 743], [742, 745], [752, 753], [752, 778], [754, 755], [754, 776], [758, 759], [758, 763], [773, 774], [773, 778], [778, 779], [778, 783], [783, 784], [783, 787], [791, 792], [791, 794], [800, 801], [800, 803], [805, 806], [805, 808], [811, 812], [811, 817], [813, 814], [813, 819]], "missing_branches": [[713, 715]]}, "GoeChargerDataUpdateCoordinator.check_for_16a_limit": {"executed_lines": [837, 838, 839, 840], "summary": {"covered_lines": 4, "num_statements": 4, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 836, "executed_branches": [], "missing_branches": []}, "GoeChargerDataUpdateCoordinator.is_valid_charger_entity": {"executed_lines": [843, 845, 846, 848, 849, 852, 855, 858, 859, 861], "summary": {"covered_lines": 10, "num_statements": 10, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 10, "num_partial_branches": 0, "covered_branches": 10, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 842, "executed_branches": [[843, 845], [843, 852], [845, 846], [845, 848], [848, 849], [848, 861], [852, 855], [852, 858], [858, 859], [858, 861]], "missing_branches": []}, "GoeChargerBaseEntity.__init__": {"executed_lines": [867, 870, 873, 874, 875, 877, 878, 879, 880, 881, 882, 883, 885, 886, 887, 888, 889, 890, 892, 894, 895, 897, 898, 900], "summary": {"covered_lines": 24, "num_statements": 24, "percent_covered": 97.5, "percent_covered_display": "98", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 16, "num_partial_branches": 1, "covered_branches": 15, "missing_branches": 1, "percent_branches_covered": 93.75, "percent_branches_covered_display": "94"}, "missing_lines": [], "excluded_lines": [], "start_line": 866, "executed_branches": [[873, 874], [873, 885], [874, 875], [874, 877], [877, 878], [877, 881], [881, 882], [885, 886], [885, 887], [887, 888], [887, 889], [889, 890], [889, 892], [897, 898], [897, 900]], "missing_branches": [[881, 894]]}, "GoeChargerBaseEntity._name_internal": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 1, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 1, "excluded_lines": 0, "percent_statements_covered": 0.0, "percent_statements_covered_display": "0", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [909], "excluded_lines": [], "start_line": 907, "executed_branches": [], "missing_branches": []}, "GoeChargerBaseEntity.device_info": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 1, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 1, "excluded_lines": 0, "percent_statements_covered": 0.0, "percent_statements_covered_display": "0", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [913], "excluded_lines": [], "start_line": 912, "executed_branches": [], "missing_branches": []}, "GoeChargerBaseEntity.available": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 1, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 1, "excluded_lines": 0, "percent_statements_covered": 0.0, "percent_statements_covered_display": "0", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [918], "excluded_lines": [], "start_line": 916, "executed_branches": [], "missing_branches": []}, "GoeChargerBaseEntity.unique_id": {"executed_lines": [923], "summary": {"covered_lines": 1, "num_statements": 1, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 921, "executed_branches": [], "missing_branches": []}, "GoeChargerBaseEntity._friendly_name_internal": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 12, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 12, "excluded_lines": 0, "percent_statements_covered": 0.0, "percent_statements_covered_display": "0", "num_branches": 10, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 10, "percent_branches_covered": 0.0, "percent_branches_covered_display": "0"}, "missing_lines": [931, 932, 933, 935, 936, 938, 939, 940, 943, 944, 945, 947], "excluded_lines": [], "start_line": 925, "executed_branches": [], "missing_branches": [[932, 933], [932, 935], [935, 936], [935, 938], [939, 940], [939, 943], [943, 944], [943, 947], [944, 945], [944, 947]]}, "": {"executed_lines": [1, 2, 3, 4, 5, 6, 7, 9, 10, 11, 12, 23, 24, 25, 30, 31, 32, 33, 34, 35, 36, 38, 45, 46, 60, 61, 63, 65, 66, 67, 68, 69, 70, 71, 73, 103, 108, 187, 209, 214, 215, 294, 296, 297, 298, 300, 377, 403, 406, 437, 484, 493, 498, 508, 524, 528, 540, 549, 553, 557, 561, 591, 606, 642, 663, 684, 836, 842, 863, 864, 866, 907, 911, 912, 915, 916, 920, 921, 925], "summary": {"covered_lines": 79, "num_statements": 79, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 1, "executed_branches": [], "missing_branches": []}}, "classes": {"GoeChargerDataUpdateCoordinator": {"executed_lines": [301, 302, 303, 304, 306, 307, 310, 311, 312, 314, 315, 316, 325, 326, 336, 337, 339, 340, 341, 343, 346, 347, 348, 349, 350, 351, 352, 353, 354, 356, 358, 359, 360, 361, 362, 363, 368, 371, 374, 375, 378, 379, 380, 381, 382, 383, 384, 386, 387, 388, 391, 392, 393, 394, 396, 398, 404, 410, 411, 412, 413, 416, 417, 418, 419, 423, 424, 425, 428, 431, 432, 433, 435, 438, 442, 445, 446, 448, 452, 453, 454, 456, 457, 458, 460, 463, 464, 465, 468, 470, 471, 472, 473, 474, 475, 476, 478, 479, 481, 482, 486, 487, 494, 495, 496, 499, 500, 501, 502, 503, 504, 505, 506, 509, 510, 511, 512, 513, 514, 515, 516, 518, 519, 520, 521, 525, 526, 529, 530, 531, 532, 533, 534, 535, 536, 537, 538, 542, 544, 545, 546, 547, 550, 551, 554, 555, 558, 559, 562, 565, 567, 569, 570, 572, 574, 575, 577, 578, 579, 580, 582, 583, 584, 586, 587, 588, 595, 596, 597, 598, 599, 600, 601, 602, 604, 608, 609, 613, 614, 615, 616, 617, 618, 619, 621, 622, 623, 624, 625, 631, 633, 634, 635, 636, 638, 639, 640, 643, 644, 645, 646, 647, 648, 649, 651, 652, 653, 654, 656, 657, 658, 659, 660, 661, 664, 665, 666, 667, 668, 669, 670, 672, 673, 677, 678, 679, 685, 686, 688, 692, 693, 694, 696, 700, 701, 702, 703, 704, 707, 710, 711, 712, 713, 714, 715, 716, 719, 720, 721, 723, 725, 727, 729, 730, 731, 732, 733, 734, 736, 737, 738, 739, 740, 742, 743, 745, 750, 751, 752, 753, 754, 755, 756, 757, 758, 759, 760, 761, 763, 768, 769, 771, 772, 773, 774, 776, 778, 779, 780, 783, 784, 785, 787, 788, 789, 791, 792, 794, 797, 799, 800, 801, 803, 805, 806, 808, 811, 812, 813, 814, 815, 817, 819, 834, 837, 838, 839, 840, 843, 845, 846, 848, 849, 852, 855, 858, 859, 861], "summary": {"covered_lines": 328, "num_statements": 339, "percent_covered": 95.50102249488752, "percent_covered_display": "96", "missing_lines": 11, "excluded_lines": 0, "percent_statements_covered": 96.7551622418879, "percent_statements_covered_display": "97", "num_branches": 150, "num_partial_branches": 11, "covered_branches": 139, "missing_branches": 11, "percent_branches_covered": 92.66666666666667, "percent_branches_covered_display": "93"}, "missing_lines": [364, 390, 449, 610, 611, 637, 674, 675, 680, 681, 682], "excluded_lines": [], "start_line": 294, "executed_branches": [[311, 312], [311, 314], [314, 315], [314, 325], [340, 341], [340, 343], [353, 354], [353, 356], [363, 368], [379, -377], [379, 380], [380, 381], [382, 383], [382, 386], [386, 387], [387, 388], [391, -377], [391, 392], [393, 394], [393, 396], [411, 412], [411, 416], [417, 418], [417, 423], [432, 433], [432, 435], [448, 452], [463, 464], [463, 468], [470, 471], [470, 478], [472, 473], [472, 478], [494, -493], [494, 495], [499, -498], [499, 500], [509, 510], [509, 518], [514, 515], [519, -508], [519, 520], [532, 533], [550, -549], [550, 551], [562, 565], [562, 567], [569, 570], [569, 572], [575, 577], [575, 586], [577, 578], [577, 582], [582, 583], [582, 586], [586, -561], [586, 587], [587, -561], [587, 588], [597, 598], [597, 604], [599, 600], [609, 613], [613, 614], [613, 617], [617, 618], [617, 621], [623, 624], [643, 644], [643, 647], [647, 648], [647, 651], [664, 665], [664, 668], [668, 669], [668, 672], [685, 686], [685, 688], [688, 692], [688, 700], [692, 693], [692, 696], [700, 701], [700, 707], [702, 703], [702, 707], [711, 712], [711, 715], [713, 714], [715, 716], [715, 719], [720, 721], [720, 723], [727, 729], [727, 797], [730, 731], [730, 736], [731, 732], [731, 742], [732, 733], [732, 734], [736, 737], [736, 742], [738, 739], [738, 740], [742, 743], [742, 745], [752, 753], [752, 778], [754, 755], [754, 776], [758, 759], [758, 763], [773, 774], [773, 778], [778, 779], [778, 783], [783, 784], [783, 787], [791, 792], [791, 794], [800, 801], [800, 803], [805, 806], [805, 808], [811, 812], [811, 817], [813, 814], [813, 819], [843, 845], [843, 852], [845, 846], [845, 848], [848, 849], [848, 861], [852, 855], [852, 858], [858, 859], [858, 861]], "missing_branches": [[363, 364], [380, -377], [386, -377], [387, 390], [448, 449], [514, -508], [532, 534], [599, -591], [609, 610], [623, 631], [713, 715]]}, "GoeChargerBaseEntity": {"executed_lines": [867, 870, 873, 874, 875, 877, 878, 879, 880, 881, 882, 883, 885, 886, 887, 888, 889, 890, 892, 894, 895, 897, 898, 900, 923], "summary": {"covered_lines": 25, "num_statements": 40, "percent_covered": 60.60606060606061, "percent_covered_display": "61", "missing_lines": 15, "excluded_lines": 0, "percent_statements_covered": 62.5, "percent_statements_covered_display": "62", "num_branches": 26, "num_partial_branches": 1, "covered_branches": 15, "missing_branches": 11, "percent_branches_covered": 57.69230769230769, "percent_branches_covered_display": "58"}, "missing_lines": [909, 913, 918, 931, 932, 933, 935, 936, 938, 939, 940, 943, 944, 945, 947], "excluded_lines": [], "start_line": 863, "executed_branches": [[873, 874], [873, 885], [874, 875], [874, 877], [877, 878], [877, 881], [881, 882], [885, 886], [885, 887], [887, 888], [887, 889], [889, 890], [889, 892], [897, 898], [897, 900]], "missing_branches": [[881, 894], [932, 933], [932, 935], [935, 936], [935, 938], [939, 940], [939, 943], [943, 944], [943, 947], [944, 945], [944, 947]]}, "": {"executed_lines": [1, 2, 3, 4, 5, 6, 7, 9, 10, 11, 12, 23, 24, 25, 30, 31, 32, 33, 34, 35, 36, 38, 45, 46, 60, 61, 63, 65, 66, 67, 68, 69, 70, 71, 73, 74, 75, 76, 77, 78, 81, 82, 85, 86, 87, 88, 89, 90, 91, 92, 93, 97, 98, 100, 103, 105, 108, 109, 110, 111, 112, 113, 116, 117, 118, 120, 122, 123, 124, 125, 126, 127, 128, 129, 131, 132, 133, 135, 136, 137, 138, 140, 141, 144, 145, 148, 149, 150, 151, 153, 154, 155, 156, 158, 162, 165, 166, 173, 175, 176, 177, 179, 180, 182, 184, 187, 188, 190, 191, 192, 193, 194, 195, 196, 197, 198, 201, 202, 203, 204, 206, 209, 210, 211, 214, 215, 216, 217, 218, 219, 220, 221, 224, 225, 226, 227, 228, 230, 231, 232, 233, 234, 236, 238, 240, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 254, 255, 257, 258, 259, 260, 261, 262, 269, 270, 271, 272, 273, 275, 276, 278, 279, 280, 281, 283, 284, 285, 286, 287, 288, 289, 290, 291, 294, 296, 297, 298, 300, 377, 403, 406, 437, 484, 493, 498, 508, 524, 528, 540, 549, 553, 557, 561, 591, 606, 642, 663, 684, 836, 842, 863, 864, 866, 907, 911, 912, 915, 916, 920, 921, 925], "summary": {"covered_lines": 222, "num_statements": 225, "percent_covered": 96.82539682539682, "percent_covered_display": "97", "missing_lines": 3, "excluded_lines": 0, "percent_statements_covered": 98.66666666666667, "percent_statements_covered_display": "99", "num_branches": 90, "num_partial_branches": 7, "covered_branches": 83, "missing_branches": 7, "percent_branches_covered": 92.22222222222223, "percent_branches_covered_display": "92"}, "missing_lines": [80, 94, 95], "excluded_lines": [], "start_line": 1, "executed_branches": [[74, 75], [74, 85], [75, 76], [77, 78], [85, 86], [85, 100], [89, 90], [91, 92], [91, 97], [109, 110], [109, 116], [117, 118], [117, 120], [124, 125], [124, 135], [127, 128], [127, 144], [137, 138], [137, 140], [140, 141], [140, 144], [148, 149], [148, 165], [149, 150], [149, 151], [153, 154], [153, 162], [165, 166], [165, 173], [173, 175], [173, 182], [175, 176], [175, 179], [190, 191], [190, 206], [191, 192], [191, 201], [201, 202], [201, 206], [218, 219], [220, 221], [225, 226], [225, 242], [226, 225], [226, 227], [227, 225], [227, 228], [228, 225], [228, 230], [230, 231], [230, 240], [231, 232], [231, 238], [232, 233], [232, 236], [242, -214], [242, 243], [246, 247], [246, 257], [247, 248], [247, 254], [249, 250], [249, 251], [254, 246], [254, 255], [260, 261], [260, 283], [262, 269], [262, 273], [269, 260], [269, 270], [271, 272], [273, 260], [273, 275], [276, 260], [276, 278], [278, 276], [278, 279], [280, 281], [284, -214], [284, 285], [285, 286], [285, 288]], "missing_branches": [[75, 85], [77, 80], [89, 97], [218, -214], [220, -214], [271, 260], [280, 276]]}}}, "custom_components/goecharger_api2/binary_sensor.py": {"executed_lines": [1, 3, 4, 5, 6, 7, 9, 10, 11, 12, 15, 18, 36, 37, 38, 39, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 53, 54, 56, 58, 61, 62, 63, 64, 66, 67, 68, 69, 70, 73, 74, 75, 76, 77, 78, 80, 81, 83, 84, 86, 88, 90, 92, 93, 95, 96, 98], "summary": {"covered_lines": 58, "num_statements": 71, "percent_covered": 77.14285714285714, "percent_covered_display": "77", "missing_lines": 13, "excluded_lines": 0, "percent_statements_covered": 81.69014084507042, "percent_statements_covered_display": "82", "num_branches": 34, "num_partial_branches": 3, "covered_branches": 23, "missing_branches": 11, "percent_branches_covered": 67.6470588235294, "percent_branches_covered_display": "68"}, "missing_lines": [19, 20, 21, 23, 24, 25, 26, 27, 29, 30, 31, 33, 72], "excluded_lines": [], "executed_branches": [[45, 46], [46, 47], [46, 61], [47, 48], [47, 51], [51, 53], [51, 58], [53, 54], [53, 56], [61, 62], [63, 64], [63, 80], [67, 68], [67, 69], [69, 70], [80, 81], [80, 90], [81, 83], [81, 88], [83, 84], [83, 86], [95, 96], [95, 98]], "missing_branches": [[23, 24], [23, 29], [24, 25], [24, 33], [25, 24], [25, 26], [29, 30], [29, 33], [45, 80], [61, 63], [69, 72]], "functions": {"async_setup_entry": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 12, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 12, "excluded_lines": 0, "percent_statements_covered": 0.0, "percent_statements_covered_display": "0", "num_branches": 8, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 8, "percent_branches_covered": 0.0, "percent_branches_covered_display": "0"}, "missing_lines": [19, 20, 21, 23, 24, 25, 26, 27, 29, 30, 31, 33], "excluded_lines": [], "start_line": 18, "executed_branches": [], "missing_branches": [[23, 24], [23, 29], [24, 25], [24, 33], [25, 24], [25, 26], [29, 30], [29, 33]]}, "GoeChargerApiV2BinarySensor.__init__": {"executed_lines": [38, 39], "summary": {"covered_lines": 2, "num_statements": 2, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 37, "executed_branches": [], "missing_branches": []}, "GoeChargerApiV2BinarySensor.is_on": {"executed_lines": [43, 44, 45, 46, 47, 48, 49, 50, 51, 53, 54, 56, 58, 61, 62, 63, 64, 66, 67, 68, 69, 70, 73, 74, 75, 76, 77, 78, 80, 81, 83, 84, 86, 88, 90], "summary": {"covered_lines": 35, "num_statements": 36, "percent_covered": 93.33333333333333, "percent_covered_display": "93", "missing_lines": 1, "excluded_lines": 0, "percent_statements_covered": 97.22222222222223, "percent_statements_covered_display": "97", "num_branches": 24, "num_partial_branches": 3, "covered_branches": 21, "missing_branches": 3, "percent_branches_covered": 87.5, "percent_branches_covered_display": "88"}, "missing_lines": [72], "excluded_lines": [], "start_line": 42, "executed_branches": [[45, 46], [46, 47], [46, 61], [47, 48], [47, 51], [51, 53], [51, 58], [53, 54], [53, 56], [61, 62], [63, 64], [63, 80], [67, 68], [67, 69], [69, 70], [80, 81], [80, 90], [81, 83], [81, 88], [83, 84], [83, 86]], "missing_branches": [[45, 80], [61, 63], [69, 72]]}, "GoeChargerApiV2BinarySensor.icon": {"executed_lines": [95, 96, 98], "summary": {"covered_lines": 3, "num_statements": 3, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 2, "num_partial_branches": 0, "covered_branches": 2, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 93, "executed_branches": [[95, 96], [95, 98]], "missing_branches": []}, "": {"executed_lines": [1, 3, 4, 5, 6, 7, 9, 10, 11, 12, 15, 18, 36, 37, 41, 42, 92, 93], "summary": {"covered_lines": 18, "num_statements": 18, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 1, "executed_branches": [], "missing_branches": []}}, "classes": {"GoeChargerApiV2BinarySensor": {"executed_lines": [38, 39, 43, 44, 45, 46, 47, 48, 49, 50, 51, 53, 54, 56, 58, 61, 62, 63, 64, 66, 67, 68, 69, 70, 73, 74, 75, 76, 77, 78, 80, 81, 83, 84, 86, 88, 90, 95, 96, 98], "summary": {"covered_lines": 40, "num_statements": 41, "percent_covered": 94.02985074626865, "percent_covered_display": "94", "missing_lines": 1, "excluded_lines": 0, "percent_statements_covered": 97.5609756097561, "percent_statements_covered_display": "98", "num_branches": 26, "num_partial_branches": 3, "covered_branches": 23, "missing_branches": 3, "percent_branches_covered": 88.46153846153847, "percent_branches_covered_display": "88"}, "missing_lines": [72], "excluded_lines": [], "start_line": 36, "executed_branches": [[45, 46], [46, 47], [46, 61], [47, 48], [47, 51], [51, 53], [51, 58], [53, 54], [53, 56], [61, 62], [63, 64], [63, 80], [67, 68], [67, 69], [69, 70], [80, 81], [80, 90], [81, 83], [81, 88], [83, 84], [83, 86], [95, 96], [95, 98]], "missing_branches": [[45, 80], [61, 63], [69, 72]]}, "": {"executed_lines": [1, 3, 4, 5, 6, 7, 9, 10, 11, 12, 15, 18, 36, 37, 41, 42, 92, 93], "summary": {"covered_lines": 18, "num_statements": 30, "percent_covered": 47.36842105263158, "percent_covered_display": "47", "missing_lines": 12, "excluded_lines": 0, "percent_statements_covered": 60.0, "percent_statements_covered_display": "60", "num_branches": 8, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 8, "percent_branches_covered": 0.0, "percent_branches_covered_display": "0"}, "missing_lines": [19, 20, 21, 23, 24, 25, 26, 27, 29, 30, 31, 33], "excluded_lines": [], "start_line": 1, "executed_branches": [], "missing_branches": [[23, 24], [23, 29], [24, 25], [24, 33], [25, 24], [25, 26], [29, 30], [29, 33]]}}}, "custom_components/goecharger_api2/button.py": {"executed_lines": [1, 3, 4, 5, 6, 7, 9, 10, 11, 13, 16, 34, 35, 36, 38, 39, 40, 41, 42], "summary": {"covered_lines": 19, "num_statements": 31, "percent_covered": 48.717948717948715, "percent_covered_display": "49", "missing_lines": 12, "excluded_lines": 0, "percent_statements_covered": 61.29032258064516, "percent_statements_covered_display": "61", "num_branches": 8, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 8, "percent_branches_covered": 0.0, "percent_branches_covered_display": "0"}, "missing_lines": [17, 18, 19, 21, 22, 23, 24, 25, 27, 28, 29, 31], "excluded_lines": [], "executed_branches": [], "missing_branches": [[21, 22], [21, 27], [22, 23], [22, 31], [23, 22], [23, 24], [27, 28], [27, 31]], "functions": {"async_setup_entry": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 12, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 12, "excluded_lines": 0, "percent_statements_covered": 0.0, "percent_statements_covered_display": "0", "num_branches": 8, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 8, "percent_branches_covered": 0.0, "percent_branches_covered_display": "0"}, "missing_lines": [17, 18, 19, 21, 22, 23, 24, 25, 27, 28, 29, 31], "excluded_lines": [], "start_line": 16, "executed_branches": [], "missing_branches": [[21, 22], [21, 27], [22, 23], [22, 31], [23, 22], [23, 24], [27, 28], [27, 31]]}, "GoeChargerApiV2Button.__init__": {"executed_lines": [36], "summary": {"covered_lines": 1, "num_statements": 1, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 35, "executed_branches": [], "missing_branches": []}, "GoeChargerApiV2Button.async_press": {"executed_lines": [39, 40, 41, 42], "summary": {"covered_lines": 4, "num_statements": 4, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 38, "executed_branches": [], "missing_branches": []}, "": {"executed_lines": [1, 3, 4, 5, 6, 7, 9, 10, 11, 13, 16, 34, 35, 38], "summary": {"covered_lines": 14, "num_statements": 14, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 1, "executed_branches": [], "missing_branches": []}}, "classes": {"GoeChargerApiV2Button": {"executed_lines": [36, 39, 40, 41, 42], "summary": {"covered_lines": 5, "num_statements": 5, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 34, "executed_branches": [], "missing_branches": []}, "": {"executed_lines": [1, 3, 4, 5, 6, 7, 9, 10, 11, 13, 16, 34, 35, 38], "summary": {"covered_lines": 14, "num_statements": 26, "percent_covered": 41.1764705882353, "percent_covered_display": "41", "missing_lines": 12, "excluded_lines": 0, "percent_statements_covered": 53.84615384615385, "percent_statements_covered_display": "54", "num_branches": 8, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 8, "percent_branches_covered": 0.0, "percent_branches_covered_display": "0"}, "missing_lines": [17, 18, 19, 21, 22, 23, 24, 25, 27, 28, 29, 31], "excluded_lines": [], "start_line": 1, "executed_branches": [], "missing_branches": [[21, 22], [21, 27], [22, 23], [22, 31], [23, 22], [23, 24], [27, 28], [27, 31]]}}}, "custom_components/goecharger_api2/config_flow.py": {"executed_lines": [1, 2, 3, 4, 6, 7, 8, 9, 10, 21, 22, 24, 33, 39, 41, 43, 44, 47, 50, 51, 52, 54, 56, 58, 59, 60, 62, 63, 64, 66, 67, 68, 70, 71, 72, 74, 75, 76, 78, 79, 80, 81, 82, 83, 84, 85, 87, 88, 89, 90, 91, 92, 93, 94, 96, 97, 98, 99, 101, 102, 105, 107, 111, 129, 131, 137, 138, 139, 140, 141, 143, 144, 145, 146, 147, 148, 149, 151, 152, 154, 155, 156, 158, 160, 161, 163, 172, 196, 198, 204, 205, 207, 208, 209, 211, 212, 213, 214, 215, 216, 217, 219, 220, 224, 225, 226, 228, 230, 231, 233, 242, 268, 271, 275, 276, 277, 278, 279, 280, 281, 282, 284, 285, 289, 290, 306, 307, 308, 310, 311, 312, 316, 318, 319, 324, 325, 329, 331, 332, 333, 334, 340, 341, 342, 343, 344, 345, 347, 349, 351, 352, 353, 354, 357, 358, 359, 360, 366, 367, 368, 370, 371, 372, 376, 378, 379, 380, 381, 382, 385], "summary": {"covered_lines": 170, "num_statements": 178, "percent_covered": 95.04504504504504, "percent_covered_display": "95", "missing_lines": 8, "excluded_lines": 0, "percent_statements_covered": 95.50561797752809, "percent_statements_covered_display": "96", "num_branches": 44, "num_partial_branches": 3, "covered_branches": 41, "missing_branches": 3, "percent_branches_covered": 93.18181818181819, "percent_branches_covered_display": "93"}, "missing_lines": [142, 210, 320, 321, 326, 327, 383, 384], "excluded_lines": [], "executed_branches": [[78, 79], [78, 87], [98, 99], [98, 107], [101, 102], [101, 105], [137, 138], [137, 163], [140, 141], [140, 160], [141, 143], [147, 148], [147, 151], [155, 156], [155, 158], [204, 205], [204, 233], [208, 209], [208, 230], [209, 211], [215, 216], [215, 219], [225, 226], [225, 228], [280, 281], [280, 284], [284, 285], [341, 342], [341, 349], [343, 344], [343, 347], [349, 351], [349, 366], [353, 354], [353, 357], [359, 360], [359, 366], [367, 368], [367, 385], [371, 372], [371, 376]], "missing_branches": [[141, 142], [209, 210], [284, 316]], "functions": {"GoeChargerApiV2FlowHandler.__init__": {"executed_lines": [56, 58, 59, 60, 62, 63, 64, 66, 67, 68, 70, 71, 72], "summary": {"covered_lines": 13, "num_statements": 13, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 54, "executed_branches": [], "missing_branches": []}, "GoeChargerApiV2FlowHandler.async_step_reconfigure": {"executed_lines": [75, 76, 78, 79, 80, 81, 82, 83, 84, 85, 87, 88, 89, 90, 91, 92, 93, 94], "summary": {"covered_lines": 18, "num_statements": 18, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 2, "num_partial_branches": 0, "covered_branches": 2, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 74, "executed_branches": [[78, 79], [78, 87]], "missing_branches": []}, "GoeChargerApiV2FlowHandler.async_step_user": {"executed_lines": [97, 98, 99, 101, 102, 105, 107, 111], "summary": {"covered_lines": 8, "num_statements": 8, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 4, "num_partial_branches": 0, "covered_branches": 4, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 96, "executed_branches": [[98, 99], [98, 107], [101, 102], [101, 105]], "missing_branches": []}, "GoeChargerApiV2FlowHandler.async_step_user_lan": {"executed_lines": [131, 137, 138, 139, 140, 141, 143, 144, 145, 146, 147, 148, 149, 151, 152, 154, 155, 156, 158, 160, 161, 163, 172], "summary": {"covered_lines": 23, "num_statements": 24, "percent_covered": 94.11764705882354, "percent_covered_display": "94", "missing_lines": 1, "excluded_lines": 0, "percent_statements_covered": 95.83333333333333, "percent_statements_covered_display": "96", "num_branches": 10, "num_partial_branches": 1, "covered_branches": 9, "missing_branches": 1, "percent_branches_covered": 90.0, "percent_branches_covered_display": "90"}, "missing_lines": [142], "excluded_lines": [], "start_line": 129, "executed_branches": [[137, 138], [137, 163], [140, 141], [140, 160], [141, 143], [147, 148], [147, 151], [155, 156], [155, 158]], "missing_branches": [[141, 142]]}, "GoeChargerApiV2FlowHandler.async_step_user_wan": {"executed_lines": [198, 204, 205, 207, 208, 209, 211, 212, 213, 214, 215, 216, 217, 219, 220, 224, 225, 226, 228, 230, 231, 233, 242], "summary": {"covered_lines": 23, "num_statements": 24, "percent_covered": 94.11764705882354, "percent_covered_display": "94", "missing_lines": 1, "excluded_lines": 0, "percent_statements_covered": 95.83333333333333, "percent_statements_covered_display": "96", "num_branches": 10, "num_partial_branches": 1, "covered_branches": 9, "missing_branches": 1, "percent_branches_covered": 90.0, "percent_branches_covered_display": "90"}, "missing_lines": [210], "excluded_lines": [], "start_line": 196, "executed_branches": [[204, 205], [204, 233], [208, 209], [208, 230], [209, 211], [215, 216], [215, 219], [225, 226], [225, 228]], "missing_branches": [[209, 210]]}, "GoeChargerApiV2FlowHandler._check_ws_connection_and_flags": {"executed_lines": [271, 275, 276, 277, 278, 279, 280, 281, 282, 284, 285, 289, 290, 306, 307, 308, 310, 311, 312, 316, 318, 319, 324, 325, 329], "summary": {"covered_lines": 25, "num_statements": 29, "percent_covered": 84.84848484848484, "percent_covered_display": "85", "missing_lines": 4, "excluded_lines": 0, "percent_statements_covered": 86.20689655172414, "percent_statements_covered_display": "86", "num_branches": 4, "num_partial_branches": 1, "covered_branches": 3, "missing_branches": 1, "percent_branches_covered": 75.0, "percent_branches_covered_display": "75"}, "missing_lines": [320, 321, 326, 327], "excluded_lines": [], "start_line": 268, "executed_branches": [[280, 281], [280, 284], [284, 285]], "missing_branches": [[284, 316]]}, "GoeChargerApiV2FlowHandler._test_host": {"executed_lines": [332, 333, 334, 340, 341, 342, 343, 344, 345, 347, 349, 351, 352, 353, 354, 357, 358, 359, 360, 366, 367, 368, 370, 371, 372, 376, 378, 379, 380, 381, 382, 385], "summary": {"covered_lines": 32, "num_statements": 34, "percent_covered": 95.83333333333333, "percent_covered_display": "96", "missing_lines": 2, "excluded_lines": 0, "percent_statements_covered": 94.11764705882354, "percent_statements_covered_display": "94", "num_branches": 14, "num_partial_branches": 0, "covered_branches": 14, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [383, 384], "excluded_lines": [], "start_line": 331, "executed_branches": [[341, 342], [341, 349], [343, 344], [343, 347], [349, 351], [349, 366], [353, 354], [353, 357], [359, 360], [359, 366], [367, 368], [367, 385], [371, 372], [371, 376]], "missing_branches": []}, "": {"executed_lines": [1, 2, 3, 4, 6, 7, 8, 9, 10, 21, 22, 24, 33, 39, 41, 43, 44, 47, 50, 51, 52, 54, 74, 96, 129, 196, 268, 331], "summary": {"covered_lines": 28, "num_statements": 28, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 1, "executed_branches": [], "missing_branches": []}}, "classes": {"GoeChargerApiV2FlowHandler": {"executed_lines": [56, 58, 59, 60, 62, 63, 64, 66, 67, 68, 70, 71, 72, 75, 76, 78, 79, 80, 81, 82, 83, 84, 85, 87, 88, 89, 90, 91, 92, 93, 94, 97, 98, 99, 101, 102, 105, 107, 111, 131, 137, 138, 139, 140, 141, 143, 144, 145, 146, 147, 148, 149, 151, 152, 154, 155, 156, 158, 160, 161, 163, 172, 198, 204, 205, 207, 208, 209, 211, 212, 213, 214, 215, 216, 217, 219, 220, 224, 225, 226, 228, 230, 231, 233, 242, 271, 275, 276, 277, 278, 279, 280, 281, 282, 284, 285, 289, 290, 306, 307, 308, 310, 311, 312, 316, 318, 319, 324, 325, 329, 332, 333, 334, 340, 341, 342, 343, 344, 345, 347, 349, 351, 352, 353, 354, 357, 358, 359, 360, 366, 367, 368, 370, 371, 372, 376, 378, 379, 380, 381, 382, 385], "summary": {"covered_lines": 142, "num_statements": 150, "percent_covered": 94.3298969072165, "percent_covered_display": "94", "missing_lines": 8, "excluded_lines": 0, "percent_statements_covered": 94.66666666666667, "percent_statements_covered_display": "95", "num_branches": 44, "num_partial_branches": 3, "covered_branches": 41, "missing_branches": 3, "percent_branches_covered": 93.18181818181819, "percent_branches_covered_display": "93"}, "missing_lines": [142, 210, 320, 321, 326, 327, 383, 384], "excluded_lines": [], "start_line": 47, "executed_branches": [[78, 79], [78, 87], [98, 99], [98, 107], [101, 102], [101, 105], [137, 138], [137, 163], [140, 141], [140, 160], [141, 143], [147, 148], [147, 151], [155, 156], [155, 158], [204, 205], [204, 233], [208, 209], [208, 230], [209, 211], [215, 216], [215, 219], [225, 226], [225, 228], [280, 281], [280, 284], [284, 285], [341, 342], [341, 349], [343, 344], [343, 347], [349, 351], [349, 366], [353, 354], [353, 357], [359, 360], [359, 366], [367, 368], [367, 385], [371, 372], [371, 376]], "missing_branches": [[141, 142], [209, 210], [284, 316]]}, "": {"executed_lines": [1, 2, 3, 4, 6, 7, 8, 9, 10, 21, 22, 24, 33, 39, 41, 43, 44, 47, 50, 51, 52, 54, 74, 96, 129, 196, 268, 331], "summary": {"covered_lines": 28, "num_statements": 28, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 1, "executed_branches": [], "missing_branches": []}}}, "custom_components/goecharger_api2/const.py": {"executed_lines": [1, 2, 4, 5, 6, 7, 8, 9, 10, 16, 17, 20, 21, 22, 23, 25, 26, 28, 37, 38, 39, 40, 42, 43, 45, 46, 47, 48, 49, 52, 53, 54, 57, 58, 59, 60, 61, 62, 63, 66, 67, 68, 71, 72, 73, 74, 75, 76, 77, 80, 81, 82, 83, 84, 87, 89, 216, 242, 710, 837, 1953, 2086, 2097, 2315, 2317, 2327, 2329, 2385, 3242], "summary": {"covered_lines": 69, "num_statements": 69, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "executed_branches": [], "missing_branches": [], "functions": {"": {"executed_lines": [1, 2, 4, 5, 6, 7, 8, 9, 10, 16, 17, 20, 21, 22, 23, 25, 26, 28, 37, 38, 39, 40, 42, 43, 45, 46, 47, 48, 49, 52, 53, 54, 57, 58, 59, 60, 61, 62, 63, 66, 67, 68, 71, 72, 73, 74, 75, 76, 77, 80, 81, 82, 83, 84, 87, 89, 216, 242, 710, 837, 1953, 2086, 2097, 2315, 2317, 2327, 2329, 2385, 3242], "summary": {"covered_lines": 69, "num_statements": 69, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 1, "executed_branches": [], "missing_branches": []}}, "classes": {"ExtBinarySensorEntityDescription": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 0, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 46, "executed_branches": [], "missing_branches": []}, "ExtButtonEntityDescription": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 0, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 53, "executed_branches": [], "missing_branches": []}, "ExtNumberEntityDescription": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 0, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 58, "executed_branches": [], "missing_branches": []}, "ExtSelectEntityDescription": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 0, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 67, "executed_branches": [], "missing_branches": []}, "ExtSensorEntityDescription": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 0, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 72, "executed_branches": [], "missing_branches": []}, "ExtSwitchEntityDescription": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 0, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 81, "executed_branches": [], "missing_branches": []}, "": {"executed_lines": [1, 2, 4, 5, 6, 7, 8, 9, 10, 16, 17, 20, 21, 22, 23, 25, 26, 28, 37, 38, 39, 40, 42, 43, 45, 46, 47, 48, 49, 52, 53, 54, 57, 58, 59, 60, 61, 62, 63, 66, 67, 68, 71, 72, 73, 74, 75, 76, 77, 80, 81, 82, 83, 84, 87, 89, 216, 242, 710, 837, 1953, 2086, 2097, 2315, 2317, 2327, 2329, 2385, 3242], "summary": {"covered_lines": 69, "num_statements": 69, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 1, "executed_branches": [], "missing_branches": []}}}, "custom_components/goecharger_api2/entity.py": {"executed_lines": [1, 3, 4, 5, 7, 9, 13, 16], "summary": {"covered_lines": 8, "num_statements": 31, "percent_covered": 17.77777777777778, "percent_covered_display": "18", "missing_lines": 23, "excluded_lines": 0, "percent_statements_covered": 25.806451612903224, "percent_statements_covered_display": "26", "num_branches": 14, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 14, "percent_branches_covered": 0.0, "percent_branches_covered_display": "0"}, "missing_lines": [20, 22, 23, 26, 27, 30, 33, 34, 35, 36, 38, 39, 40, 41, 42, 44, 45, 46, 49, 50, 51, 52, 54], "excluded_lines": [], "executed_branches": [], "missing_branches": [[22, 23], [22, 26], [26, 27], [26, 30], [33, 34], [33, 54], [38, 39], [38, 44], [39, 38], [39, 40], [44, 45], [44, 49], [49, 50], [49, 54]], "functions": {"CustomFriendlyNameEntity._Entity__async_calculate_state": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 23, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 23, "excluded_lines": 0, "percent_statements_covered": 0.0, "percent_statements_covered_display": "0", "num_branches": 14, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 14, "percent_branches_covered": 0.0, "percent_branches_covered_display": "0"}, "missing_lines": [20, 22, 23, 26, 27, 30, 33, 34, 35, 36, 38, 39, 40, 41, 42, 44, 45, 46, 49, 50, 51, 52, 54], "excluded_lines": [], "start_line": 16, "executed_branches": [], "missing_branches": [[22, 23], [22, 26], [26, 27], [26, 30], [33, 34], [33, 54], [38, 39], [38, 44], [39, 38], [39, 40], [44, 45], [44, 49], [49, 50], [49, 54]]}, "": {"executed_lines": [1, 3, 4, 5, 7, 9, 13, 16], "summary": {"covered_lines": 8, "num_statements": 8, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 1, "executed_branches": [], "missing_branches": []}}, "classes": {"CustomFriendlyNameEntity": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 23, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 23, "excluded_lines": 0, "percent_statements_covered": 0.0, "percent_statements_covered_display": "0", "num_branches": 14, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 14, "percent_branches_covered": 0.0, "percent_branches_covered_display": "0"}, "missing_lines": [20, 22, 23, 26, 27, 30, 33, 34, 35, 36, 38, 39, 40, 41, 42, 44, 45, 46, 49, 50, 51, 52, 54], "excluded_lines": [], "start_line": 13, "executed_branches": [], "missing_branches": [[22, 23], [22, 26], [26, 27], [26, 30], [33, 34], [33, 54], [38, 39], [38, 44], [39, 38], [39, 40], [44, 45], [44, 49], [49, 50], [49, 54]]}, "": {"executed_lines": [1, 3, 4, 5, 7, 9, 13, 16], "summary": {"covered_lines": 8, "num_statements": 8, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 1, "executed_branches": [], "missing_branches": []}}}, "custom_components/goecharger_api2/number.py": {"executed_lines": [1, 2, 4, 10, 11, 12, 13, 15, 16, 17, 19, 21, 46, 47, 48, 50, 60, 61, 62, 63, 64, 65, 66, 68, 70, 71, 72, 73, 74, 76, 77, 78, 80, 81, 83, 84, 87, 89, 91, 93, 94, 96, 98, 99, 100, 103, 104, 105, 108, 110, 113, 114, 115, 118, 119, 120, 123, 125, 126, 128, 129, 130, 131, 133, 134, 135, 137, 138, 139, 140, 142, 143, 144, 146, 147, 148], "summary": {"covered_lines": 76, "num_statements": 94, "percent_covered": 77.46478873239437, "percent_covered_display": "77", "missing_lines": 18, "excluded_lines": 0, "percent_statements_covered": 80.85106382978724, "percent_statements_covered_display": "81", "num_branches": 48, "num_partial_branches": 2, "covered_branches": 34, "missing_branches": 14, "percent_branches_covered": 70.83333333333333, "percent_branches_covered_display": "71"}, "missing_lines": [22, 23, 24, 26, 27, 28, 31, 32, 34, 36, 37, 39, 40, 41, 43, 52, 57, 58], "excluded_lines": [], "executed_branches": [[64, 65], [65, 66], [65, 68], [70, 71], [70, 72], [72, 73], [72, 77], [73, 74], [73, 76], [77, 78], [77, 87], [94, 96], [94, 113], [98, 99], [98, 100], [100, 103], [100, 104], [104, 105], [104, 108], [113, 114], [113, 115], [115, 118], [115, 119], [119, 120], [119, 123], [129, -128], [129, 130], [131, -128], [131, 133], [138, 139], [138, 146], [143, 144], [146, -128], [146, 147]], "missing_branches": [[26, 27], [26, 39], [27, 28], [27, 43], [28, 31], [28, 36], [31, 32], [31, 34], [39, 40], [39, 43], [57, -50], [57, 58], [64, 70], [143, 146]], "functions": {"async_setup_entry": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 15, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 15, "excluded_lines": 0, "percent_statements_covered": 0.0, "percent_statements_covered_display": "0", "num_branches": 10, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 10, "percent_branches_covered": 0.0, "percent_branches_covered_display": "0"}, "missing_lines": [22, 23, 24, 26, 27, 28, 31, 32, 34, 36, 37, 39, 40, 41, 43], "excluded_lines": [], "start_line": 21, "executed_branches": [], "missing_branches": [[26, 27], [26, 39], [27, 28], [27, 43], [28, 31], [28, 36], [31, 32], [31, 34], [39, 40], [39, 43]]}, "GoeChargerNumber.__init__": {"executed_lines": [48], "summary": {"covered_lines": 1, "num_statements": 1, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 47, "executed_branches": [], "missing_branches": []}, "GoeChargerNumber.async_added_to_hass": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 3, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 3, "excluded_lines": 0, "percent_statements_covered": 0.0, "percent_statements_covered_display": "0", "num_branches": 2, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 2, "percent_branches_covered": 0.0, "percent_branches_covered_display": "0"}, "missing_lines": [52, 57, 58], "excluded_lines": [], "start_line": 50, "executed_branches": [], "missing_branches": [[57, -50], [57, 58]]}, "GoeChargerNumber.native_value": {"executed_lines": [62, 63, 64, 65, 66, 68, 70, 71, 72, 73, 74, 76, 77, 78, 80, 81, 83, 84, 87, 89], "summary": {"covered_lines": 20, "num_statements": 20, "percent_covered": 96.875, "percent_covered_display": "97", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 12, "num_partial_branches": 1, "covered_branches": 11, "missing_branches": 1, "percent_branches_covered": 91.66666666666667, "percent_branches_covered_display": "92"}, "missing_lines": [], "excluded_lines": [], "start_line": 61, "executed_branches": [[64, 65], [65, 66], [65, 68], [70, 71], [70, 72], [72, 73], [72, 77], [73, 74], [73, 76], [77, 78], [77, 87]], "missing_branches": [[64, 70]]}, "GoeChargerNumber.async_set_native_value": {"executed_lines": [93, 94, 96, 98, 99, 100, 103, 104, 105, 108, 110, 113, 114, 115, 118, 119, 120, 123, 125, 126], "summary": {"covered_lines": 20, "num_statements": 20, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 14, "num_partial_branches": 0, "covered_branches": 14, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 91, "executed_branches": [[94, 96], [94, 113], [98, 99], [98, 100], [100, 103], [100, 104], [104, 105], [104, 108], [113, 114], [113, 115], [115, 118], [115, 119], [119, 120], [119, 123]], "missing_branches": []}, "GoeChargerNumber.check_amp_min_max": {"executed_lines": [129, 130, 131, 133, 134, 135, 137, 138, 139, 140, 142, 143, 144, 146, 147, 148], "summary": {"covered_lines": 16, "num_statements": 16, "percent_covered": 96.15384615384616, "percent_covered_display": "96", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 10, "num_partial_branches": 1, "covered_branches": 9, "missing_branches": 1, "percent_branches_covered": 90.0, "percent_branches_covered_display": "90"}, "missing_lines": [], "excluded_lines": [], "start_line": 128, "executed_branches": [[129, -128], [129, 130], [131, -128], [131, 133], [138, 139], [138, 146], [143, 144], [146, -128], [146, 147]], "missing_branches": [[143, 146]]}, "": {"executed_lines": [1, 2, 4, 10, 11, 12, 13, 15, 16, 17, 19, 21, 46, 47, 50, 60, 61, 91, 128], "summary": {"covered_lines": 19, "num_statements": 19, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 1, "executed_branches": [], "missing_branches": []}}, "classes": {"GoeChargerNumber": {"executed_lines": [48, 62, 63, 64, 65, 66, 68, 70, 71, 72, 73, 74, 76, 77, 78, 80, 81, 83, 84, 87, 89, 93, 94, 96, 98, 99, 100, 103, 104, 105, 108, 110, 113, 114, 115, 118, 119, 120, 123, 125, 126, 129, 130, 131, 133, 134, 135, 137, 138, 139, 140, 142, 143, 144, 146, 147, 148], "summary": {"covered_lines": 57, "num_statements": 60, "percent_covered": 92.85714285714286, "percent_covered_display": "93", "missing_lines": 3, "excluded_lines": 0, "percent_statements_covered": 95.0, "percent_statements_covered_display": "95", "num_branches": 38, "num_partial_branches": 2, "covered_branches": 34, "missing_branches": 4, "percent_branches_covered": 89.47368421052632, "percent_branches_covered_display": "89"}, "missing_lines": [52, 57, 58], "excluded_lines": [], "start_line": 46, "executed_branches": [[64, 65], [65, 66], [65, 68], [70, 71], [70, 72], [72, 73], [72, 77], [73, 74], [73, 76], [77, 78], [77, 87], [94, 96], [94, 113], [98, 99], [98, 100], [100, 103], [100, 104], [104, 105], [104, 108], [113, 114], [113, 115], [115, 118], [115, 119], [119, 120], [119, 123], [129, -128], [129, 130], [131, -128], [131, 133], [138, 139], [138, 146], [143, 144], [146, -128], [146, 147]], "missing_branches": [[57, -50], [57, 58], [64, 70], [143, 146]]}, "": {"executed_lines": [1, 2, 4, 10, 11, 12, 13, 15, 16, 17, 19, 21, 46, 47, 50, 60, 61, 91, 128], "summary": {"covered_lines": 19, "num_statements": 34, "percent_covered": 43.18181818181818, "percent_covered_display": "43", "missing_lines": 15, "excluded_lines": 0, "percent_statements_covered": 55.88235294117647, "percent_statements_covered_display": "56", "num_branches": 10, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 10, "percent_branches_covered": 0.0, "percent_branches_covered_display": "0"}, "missing_lines": [22, 23, 24, 26, 27, 28, 31, 32, 34, 36, 37, 39, 40, 41, 43], "excluded_lines": [], "start_line": 1, "executed_branches": [], "missing_branches": [[26, 27], [26, 39], [27, 28], [27, 43], [28, 31], [28, 36], [31, 32], [31, 34], [39, 40], [39, 43]]}}}, "custom_components/goecharger_api2/pygoecharger_ha/__init__.py": {"executed_lines": [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 14, 15, 16, 18, 43, 45, 47, 49, 50, 51, 52, 53, 56, 59, 62, 64, 66, 67, 69, 71, 72, 73, 76, 77, 79, 81, 82, 84, 85, 86, 88, 89, 90, 91, 93, 96, 97, 98, 100, 102, 106, 108, 110, 111, 112, 113, 114, 117, 119, 121, 122, 123, 124, 126, 127, 128, 131, 133, 137, 138, 140, 142, 145, 146, 148, 149, 152, 153, 155, 156, 158, 160, 164, 165, 168, 169, 170, 172, 173, 174, 176, 177, 178, 179, 180, 181, 182, 183, 184, 185, 186, 188, 189, 190, 191, 192, 194, 195, 196, 198, 199, 201, 202, 203, 204, 206, 208, 209, 210, 211, 212, 213, 216, 217, 218, 219, 220, 221, 222, 223, 224, 225, 226, 227, 228, 229, 230, 231, 232, 234, 235, 237, 238, 239, 240, 241, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 253, 255, 256, 257, 258, 259, 262, 263, 264, 267, 268, 271, 273, 275, 276, 279, 281, 282, 283, 284, 285, 286, 287, 289, 291, 292, 293, 295, 304, 305, 306, 307, 309, 310, 311, 312, 314, 315, 316, 317, 319, 320, 323, 324, 326, 327, 329, 331, 332, 333, 335, 336, 337, 338, 339, 340, 342, 343, 344, 345, 348, 350, 351, 353, 355, 357, 361, 362, 365, 367, 368, 371, 372, 374, 375, 376, 377, 378, 380, 381, 382, 383, 384, 385, 387, 388, 389, 390, 392, 393, 394, 396, 397, 398, 403, 404, 405, 406, 407, 408, 416, 417, 420, 422, 423, 424, 426, 427, 428, 429, 431, 433, 434, 435, 436, 437, 438, 440, 441, 442, 443, 444, 445, 446, 447, 448, 449, 451, 452, 453, 454, 455, 457, 458, 460, 461, 467, 473, 475, 476, 477, 478, 480, 481, 482, 483, 484, 485, 486, 487, 489, 490, 496, 502, 504, 505, 507, 508, 510, 512, 513, 514, 516, 518, 521, 522, 523, 524, 525, 530, 532, 533, 534, 535, 536, 537, 539, 541, 543, 544, 545, 546, 547, 549, 550, 552, 553, 554, 555, 556, 557, 558, 559, 560, 562, 563, 565, 566, 568, 570, 571, 572, 573, 574, 575, 576, 578, 579, 580, 582, 583, 584, 586, 588, 589, 594, 595, 596, 597, 603, 610, 617, 618, 619, 621, 622, 623, 624, 626, 627, 629, 631, 632, 634, 636, 637, 638, 639, 640, 641, 642, 643, 645, 647, 649, 650, 651, 653, 654, 655, 656, 657, 658, 661, 662, 663, 665, 667, 668, 670, 677, 678, 682, 685, 686, 688, 690, 692, 693, 694, 696, 698, 699, 700, 702, 703, 704, 707, 709, 710, 711, 712, 713, 714, 715, 716, 717, 718, 719, 721, 723, 725, 726, 727, 728, 729, 731, 733, 734, 735, 743, 751, 753, 755, 756, 757, 769, 770, 773, 774, 776, 778, 780, 781, 782, 784, 785, 786, 787, 788, 790, 791, 793, 795, 796, 797, 798, 801, 802, 803, 804, 805, 806, 807, 808, 809, 811, 812, 813, 814, 815, 816, 817, 818, 820, 821, 823, 824, 825, 826, 828, 829, 830, 831, 832, 835, 836, 837, 839, 840, 841, 842, 843, 845, 846, 847, 848, 851, 854, 855, 856, 858, 859, 860, 861, 862, 865, 871, 872, 873, 875, 876, 877, 878, 879, 880, 881, 884, 885, 888, 907, 908, 911, 913, 914, 915, 916, 917, 918, 919, 921, 922, 923, 924, 925, 926, 927, 928, 929, 931, 932, 933, 936, 937, 939, 940, 941, 942, 943, 945, 946, 948, 949, 950, 951, 953, 954, 956, 957, 958, 961, 964, 966, 967, 970, 971, 973, 974, 975, 976, 977, 978, 979, 980, 981, 982, 983, 984, 985, 986, 987, 988, 990, 992, 993, 994, 995, 1002, 1003, 1004, 1008, 1010, 1011, 1012, 1013, 1014, 1016, 1017, 1018, 1020, 1021, 1022, 1023, 1024, 1027, 1028, 1029, 1030, 1032, 1034, 1035, 1037, 1038, 1039, 1041, 1043, 1044, 1045, 1048, 1049, 1050, 1051, 1052, 1054, 1055, 1056, 1057, 1058, 1059, 1062, 1064, 1065, 1070, 1072, 1074, 1075, 1084, 1086, 1087, 1088, 1091, 1093, 1094, 1095, 1096, 1097, 1098, 1099, 1100, 1101, 1102, 1103, 1104, 1105, 1108, 1109, 1110, 1111], "summary": {"covered_lines": 687, "num_statements": 721, "percent_covered": 93.93939393939394, "percent_covered_display": "94", "missing_lines": 34, "excluded_lines": 0, "percent_statements_covered": 95.28432732316227, "percent_statements_covered_display": "95", "num_branches": 302, "num_partial_branches": 26, "covered_branches": 274, "missing_branches": 28, "percent_branches_covered": 90.72847682119205, "percent_branches_covered_display": "91"}, "missing_lines": [116, 463, 464, 468, 469, 470, 471, 492, 493, 498, 499, 500, 501, 591, 592, 598, 599, 600, 601, 605, 606, 607, 608, 705, 866, 867, 868, 869, 996, 997, 1060, 1067, 1106, 1107], "excluded_lines": [], "executed_branches": [[51, 52], [52, 53], [52, 56], [71, 72], [71, 76], [111, 112], [111, 117], [113, 114], [123, -119], [123, 124], [140, 142], [140, 160], [145, 146], [145, 148], [152, 153], [152, 172], [155, 156], [155, 158], [160, 164], [160, 172], [168, 169], [168, 172], [172, 173], [172, 176], [176, 177], [176, 188], [203, 204], [203, 206], [238, 239], [238, 240], [262, -255], [262, 263], [263, -255], [263, 264], [268, 271], [268, 273], [276, 279], [276, 281], [281, 282], [281, 291], [283, 284], [283, 285], [285, 286], [285, 287], [287, 281], [287, 289], [291, 292], [291, 295], [295, 304], [295, 317], [305, 306], [305, 309], [309, 310], [309, 314], [323, 324], [323, 326], [326, 327], [326, 329], [333, 335], [333, 338], [335, 336], [335, 342], [336, 337], [336, 342], [338, 339], [339, 340], [339, 342], [342, 343], [342, 371], [344, 345], [344, 348], [350, 351], [350, 353], [355, -331], [355, 357], [361, 362], [361, 365], [365, -331], [365, 367], [371, 372], [371, 374], [375, 376], [375, 377], [377, 378], [384, -380], [384, 385], [388, 389], [388, 420], [389, 390], [389, 392], [393, 394], [393, 396], [396, 397], [396, 416], [398, -387], [398, 403], [403, -387], [403, 404], [404, 405], [406, 403], [406, 407], [428, 429], [437, 438], [437, 440], [443, 444], [443, 467], [446, 447], [446, 473], [448, 449], [448, 451], [453, 454], [453, 457], [454, 453], [454, 455], [477, 478], [477, 480], [483, 484], [483, 496], [486, 487], [486, 502], [507, 508], [507, 518], [508, 510], [508, 512], [512, 513], [512, 516], [518, 521], [518, 532], [522, 523], [522, 524], [524, 525], [524, 530], [532, 533], [532, 534], [534, 535], [534, 536], [536, 537], [536, 539], [544, 545], [544, 552], [545, 546], [545, 549], [553, 554], [553, 560], [554, 555], [554, 556], [556, 557], [556, 558], [558, 553], [558, 559], [565, 566], [565, 568], [572, 573], [572, 594], [575, 576], [575, 610], [576, 578], [576, 586], [578, 579], [578, 582], [582, 583], [582, 584], [594, 595], [594, 603], [622, 623], [622, 626], [631, 632], [631, 634], [638, 639], [638, 647], [650, 651], [650, 653], [654, -653], [654, 655], [656, 657], [656, 661], [661, 662], [668, 670], [668, 678], [678, 682], [678, 688], [698, 699], [698, 703], [699, 700], [699, 702], [703, 704], [709, 710], [709, 714], [714, 715], [714, 716], [716, 717], [716, 718], [718, 719], [718, 721], [726, 727], [726, 729], [753, 755], [753, 773], [784, 785], [784, 790], [786, 787], [790, 791], [790, 793], [805, 806], [805, 811], [807, 808], [812, 813], [812, 820], [830, 831], [839, 840], [839, 845], [841, 842], [847, 848], [847, 858], [855, 856], [855, 858], [858, 859], [858, 865], [860, 861], [865, 871], [877, 878], [877, 884], [879, 880], [917, 918], [925, 926], [925, 931], [927, 928], [936, 937], [936, 990], [939, 940], [939, 948], [948, 949], [948, 956], [956, 957], [956, 961], [964, 936], [964, 966], [970, 936], [970, 971], [975, 976], [975, 990], [979, 980], [983, 984], [987, 988], [1002, 1003], [1002, 1008], [1020, 1021], [1020, 1037], [1022, 1023], [1022, 1072], [1023, 1024], [1023, 1027], [1028, 1029], [1028, 1032], [1037, 1038], [1037, 1048], [1039, 1041], [1039, 1072], [1043, 1044], [1043, 1072], [1048, 1049], [1048, 1070], [1050, 1051], [1054, 1055], [1054, 1064], [1056, 1057], [1059, 1062], [1064, 1065], [1086, 1087], [1086, 1091], [1095, 1096], [1099, 1100], [1099, 1102], [1105, 1108]], "missing_branches": [[51, 66], [113, 116], [338, 342], [377, -331], [404, 403], [428, 431], [661, -653], [703, 705], [786, 788], [807, 809], [830, 832], [841, 843], [860, 862], [865, 866], [867, 868], [867, 869], [879, 881], [917, 919], [927, 929], [979, 990], [983, 990], [987, 990], [1050, 1054], [1056, 1072], [1059, 1060], [1064, 1067], [1095, 1111], [1105, 1106]], "functions": {"TargetedEvent.__init__": {"executed_lines": [50, 51, 52, 53, 56, 59, 62, 64, 66, 67], "summary": {"covered_lines": 10, "num_statements": 10, "percent_covered": 92.85714285714286, "percent_covered_display": "93", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 4, "num_partial_branches": 1, "covered_branches": 3, "missing_branches": 1, "percent_branches_covered": 75.0, "percent_branches_covered_display": "75"}, "missing_lines": [], "excluded_lines": [], "start_line": 49, "executed_branches": [[51, 52], [52, 53], [52, 56]], "missing_branches": [[51, 66]]}, "TargetedEvent.check_and_set": {"executed_lines": [71, 72, 73, 76, 77, 79], "summary": {"covered_lines": 6, "num_statements": 6, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 2, "num_partial_branches": 0, "covered_branches": 2, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 69, "executed_branches": [[71, 72], [71, 76]], "missing_branches": []}, "TargetedEvent.has_errors": {"executed_lines": [82], "summary": {"covered_lines": 1, "num_statements": 1, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 81, "executed_branches": [], "missing_branches": []}, "TargetedEvent.set_error": {"executed_lines": [85, 86], "summary": {"covered_lines": 2, "num_statements": 2, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 84, "executed_branches": [], "missing_branches": []}, "NonNullDict.none_null_get": {"executed_lines": [90, 91], "summary": {"covered_lines": 2, "num_statements": 2, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 89, "executed_branches": [], "missing_branches": []}, "ExpiringStore.__init__": {"executed_lines": [97, 98], "summary": {"covered_lines": 2, "num_statements": 2, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 96, "executed_branches": [], "missing_branches": []}, "ExpiringStore.set": {"executed_lines": [102, 106], "summary": {"covered_lines": 2, "num_statements": 2, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 100, "executed_branches": [], "missing_branches": []}, "ExpiringStore.get": {"executed_lines": [110, 111, 112, 113, 114, 117], "summary": {"covered_lines": 6, "num_statements": 7, "percent_covered": 81.81818181818181, "percent_covered_display": "82", "missing_lines": 1, "excluded_lines": 0, "percent_statements_covered": 85.71428571428571, "percent_statements_covered_display": "86", "num_branches": 4, "num_partial_branches": 1, "covered_branches": 3, "missing_branches": 1, "percent_branches_covered": 75.0, "percent_branches_covered_display": "75"}, "missing_lines": [116], "excluded_lines": [], "start_line": 108, "executed_branches": [[111, 112], [111, 117], [113, 114]], "missing_branches": [[113, 116]]}, "ExpiringStore._cleanup": {"executed_lines": [121, 122, 123, 124], "summary": {"covered_lines": 4, "num_statements": 4, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 2, "num_partial_branches": 0, "covered_branches": 2, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 119, "executed_branches": [[123, -119], [123, 124]], "missing_branches": []}, "ExpiringStore.__len__": {"executed_lines": [127, 128], "summary": {"covered_lines": 2, "num_statements": 2, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 126, "executed_branches": [], "missing_branches": []}, "GoeChargerApiV2Bridge.__init__": {"executed_lines": [137, 138, 140, 142, 145, 146, 148, 149, 152, 153, 155, 156, 158, 160, 164, 165, 168, 169, 170, 172, 173, 174, 176, 177, 178, 179, 180, 181, 182, 183, 184, 185, 186, 188, 189, 190, 191, 192, 194, 195, 196, 198, 199, 201, 202, 203, 204, 206, 208, 209, 210, 211, 212, 213, 216, 217, 218, 219, 220, 221, 222, 223, 224, 225, 226, 227, 228, 229, 230, 231, 232], "summary": {"covered_lines": 71, "num_statements": 71, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 18, "num_partial_branches": 0, "covered_branches": 18, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 133, "executed_branches": [[140, 142], [140, 160], [145, 146], [145, 148], [152, 153], [152, 172], [155, 156], [155, 158], [160, 164], [160, 172], [168, 169], [168, 172], [172, 173], [172, 176], [176, 177], [176, 188], [203, 204], [203, 206]], "missing_branches": []}, "GoeChargerApiV2Bridge.available_fields": {"executed_lines": [235], "summary": {"covered_lines": 1, "num_statements": 1, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 234, "executed_branches": [], "missing_branches": []}, "GoeChargerApiV2Bridge.clear_data": {"executed_lines": [238, 239, 240, 241, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 253], "summary": {"covered_lines": 16, "num_statements": 16, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 2, "num_partial_branches": 0, "covered_branches": 2, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 237, "executed_branches": [[238, 239], [238, 240]], "missing_branches": []}, "GoeChargerApiV2Bridge.reset_stored_update_ts": {"executed_lines": [256, 257, 258, 259, 262, 263, 264], "summary": {"covered_lines": 7, "num_statements": 7, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 4, "num_partial_branches": 0, "covered_branches": 4, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 255, "executed_branches": [[262, -255], [262, 263], [263, -255], [263, 264]], "missing_branches": []}, "GoeChargerApiV2Bridge.read_system": {"executed_lines": [268, 271, 273], "summary": {"covered_lines": 3, "num_statements": 3, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 2, "num_partial_branches": 0, "covered_branches": 2, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 267, "executed_branches": [[268, 271], [268, 273]], "missing_branches": []}, "GoeChargerApiV2Bridge.read_versions": {"executed_lines": [276, 279, 281, 282, 283, 284, 285, 286, 287, 289, 291, 292, 293, 295, 304, 305, 306, 307, 309, 310, 311, 312, 314, 315, 316, 317], "summary": {"covered_lines": 26, "num_statements": 26, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 18, "num_partial_branches": 0, "covered_branches": 18, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 275, "executed_branches": [[276, 279], [276, 281], [281, 282], [281, 291], [283, 284], [283, 285], [285, 286], [285, 287], [287, 281], [287, 289], [291, 292], [291, 295], [295, 304], [295, 317], [305, 306], [305, 309], [309, 310], [309, 314]], "missing_branches": []}, "GoeChargerApiV2Bridge.read_all": {"executed_lines": [320, 323, 324, 326, 327, 329], "summary": {"covered_lines": 6, "num_statements": 6, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 4, "num_partial_branches": 0, "covered_branches": 4, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 319, "executed_branches": [[323, 324], [323, 326], [326, 327], [326, 329]], "missing_branches": []}, "GoeChargerApiV2Bridge.read_all_states": {"executed_lines": [332, 333, 335, 336, 337, 338, 339, 340, 342, 343, 344, 345, 348, 350, 351, 353, 355, 357, 361, 362, 365, 367, 368, 371, 372, 374, 375, 376, 377, 378], "summary": {"covered_lines": 30, "num_statements": 30, "percent_covered": 96.55172413793103, "percent_covered_display": "97", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 28, "num_partial_branches": 2, "covered_branches": 26, "missing_branches": 2, "percent_branches_covered": 92.85714285714286, "percent_branches_covered_display": "93"}, "missing_lines": [], "excluded_lines": [], "start_line": 331, "executed_branches": [[333, 335], [333, 338], [335, 336], [335, 342], [336, 337], [336, 342], [338, 339], [339, 340], [339, 342], [342, 343], [342, 371], [344, 345], [344, 348], [350, 351], [350, 353], [355, -331], [355, 357], [361, 362], [361, 365], [365, -331], [365, 367], [371, 372], [371, 374], [375, 376], [375, 377], [377, 378]], "missing_branches": [[338, 342], [377, -331]]}, "GoeChargerApiV2Bridge.force_config_update": {"executed_lines": [381, 382, 383, 384, 385], "summary": {"covered_lines": 5, "num_statements": 5, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 2, "num_partial_branches": 0, "covered_branches": 2, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 380, "executed_branches": [[384, -380], [384, 385]], "missing_branches": []}, "GoeChargerApiV2Bridge.read_all_config": {"executed_lines": [388, 389, 390, 392, 393, 394, 396, 397, 398, 403, 404, 405, 406, 407, 408, 416, 417, 420], "summary": {"covered_lines": 18, "num_statements": 18, "percent_covered": 97.05882352941177, "percent_covered_display": "97", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 16, "num_partial_branches": 1, "covered_branches": 15, "missing_branches": 1, "percent_branches_covered": 93.75, "percent_branches_covered_display": "94"}, "missing_lines": [], "excluded_lines": [], "start_line": 387, "executed_branches": [[388, 389], [388, 420], [389, 390], [389, 392], [393, 394], [393, 396], [396, 397], [396, 416], [398, -387], [398, 403], [403, -387], [403, 404], [404, 405], [406, 403], [406, 407]], "missing_branches": [[404, 403]]}, "GoeChargerApiV2Bridge._read_filtered_data_from_ws_source": {"executed_lines": [423, 424, 426, 427, 428, 429, 431], "summary": {"covered_lines": 7, "num_statements": 7, "percent_covered": 88.88888888888889, "percent_covered_display": "89", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 2, "num_partial_branches": 1, "covered_branches": 1, "missing_branches": 1, "percent_branches_covered": 50.0, "percent_branches_covered_display": "50"}, "missing_lines": [], "excluded_lines": [], "start_line": 422, "executed_branches": [[428, 429]], "missing_branches": [[428, 431]]}, "GoeChargerApiV2Bridge._read_filtered_data": {"executed_lines": [434, 435, 436, 437, 438, 440, 441, 442, 443, 444, 445, 446, 447, 448, 449, 451, 452, 453, 454, 455, 457, 458, 460, 461, 467, 473], "summary": {"covered_lines": 26, "num_statements": 32, "percent_covered": 86.36363636363636, "percent_covered_display": "86", "missing_lines": 6, "excluded_lines": 0, "percent_statements_covered": 81.25, "percent_statements_covered_display": "81", "num_branches": 12, "num_partial_branches": 0, "covered_branches": 12, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [463, 464, 468, 469, 470, 471], "excluded_lines": [], "start_line": 433, "executed_branches": [[437, 438], [437, 440], [443, 444], [443, 467], [446, 447], [446, 473], [448, 449], [448, 451], [453, 454], [453, 457], [454, 453], [454, 455]], "missing_branches": []}, "GoeChargerApiV2Bridge._read_all_data": {"executed_lines": [476, 477, 478, 480, 481, 482, 483, 484, 485, 486, 487, 489, 490, 496, 502], "summary": {"covered_lines": 15, "num_statements": 21, "percent_covered": 77.77777777777777, "percent_covered_display": "78", "missing_lines": 6, "excluded_lines": 0, "percent_statements_covered": 71.42857142857143, "percent_statements_covered_display": "71", "num_branches": 6, "num_partial_branches": 0, "covered_branches": 6, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [492, 493, 498, 499, 500, 501], "excluded_lines": [], "start_line": 475, "executed_branches": [[477, 478], [477, 480], [483, 484], [483, 496], [486, 487], [486, 502]], "missing_branches": []}, "GoeChargerApiV2Bridge.write_value_to_key": {"executed_lines": [505, 507, 508, 510, 512, 513, 514, 516, 518, 521, 522, 523, 524, 525, 530, 532, 533, 534, 535, 536, 537, 539, 541], "summary": {"covered_lines": 23, "num_statements": 23, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 18, "num_partial_branches": 0, "covered_branches": 18, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 504, "executed_branches": [[507, 508], [507, 518], [508, 510], [508, 512], [512, 513], [512, 516], [518, 521], [518, 532], [522, 523], [522, 524], [524, 525], [524, 530], [532, 533], [532, 534], [534, 535], [534, 536], [536, 537], [536, 539]], "missing_branches": []}, "GoeChargerApiV2Bridge.write_multiple_values_to_keys": {"executed_lines": [544, 545, 546, 547, 549, 550, 552, 553, 554, 555, 556, 557, 558, 559, 560], "summary": {"covered_lines": 15, "num_statements": 15, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 12, "num_partial_branches": 0, "covered_branches": 12, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 543, "executed_branches": [[544, 545], [544, 552], [545, 546], [545, 549], [553, 554], [553, 560], [554, 555], [554, 556], [556, 557], [556, 558], [558, 553], [558, 559]], "missing_branches": []}, "GoeChargerApiV2Bridge._write_values_int": {"executed_lines": [563, 565, 566, 568, 570, 571, 572, 573, 574, 575, 576, 578, 579, 580, 582, 583, 584, 586, 588, 589, 594, 595, 596, 597, 603, 610], "summary": {"covered_lines": 26, "num_statements": 36, "percent_covered": 80.0, "percent_covered_display": "80", "missing_lines": 10, "excluded_lines": 0, "percent_statements_covered": 72.22222222222223, "percent_statements_covered_display": "72", "num_branches": 14, "num_partial_branches": 0, "covered_branches": 14, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [591, 592, 598, 599, 600, 601, 605, 606, 607, 608], "excluded_lines": [], "start_line": 562, "executed_branches": [[565, 566], [565, 568], [572, 573], [572, 594], [575, 576], [575, 610], [576, 578], [576, 586], [578, 579], [578, 582], [582, 583], [582, 584], [594, 595], [594, 603]], "missing_branches": []}, "GoeChargerApiV2Bridge.ws_set_coordinator": {"executed_lines": [618, 619], "summary": {"covered_lines": 2, "num_statements": 2, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 617, "executed_branches": [], "missing_branches": []}, "GoeChargerApiV2Bridge.ws_check_last_update": {"executed_lines": [622, 623, 624, 626, 627], "summary": {"covered_lines": 5, "num_statements": 5, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 2, "num_partial_branches": 0, "covered_branches": 2, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 621, "executed_branches": [[622, 623], [622, 626]], "missing_branches": []}, "GoeChargerApiV2Bridge.ws_close": {"executed_lines": [631, 632, 634, 636, 637, 638, 639, 640, 641, 642, 643, 645, 647], "summary": {"covered_lines": 13, "num_statements": 13, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 4, "num_partial_branches": 0, "covered_branches": 4, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 629, "executed_branches": [[631, 632], [631, 634], [638, 639], [638, 647]], "missing_branches": []}, "GoeChargerApiV2Bridge._ws_notify_for_new_data": {"executed_lines": [650, 651, 653, 665], "summary": {"covered_lines": 4, "num_statements": 4, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 2, "num_partial_branches": 0, "covered_branches": 2, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 649, "executed_branches": [[650, 651], [650, 653]], "missing_branches": []}, "GoeChargerApiV2Bridge._ws_notify_for_new_data._ws_debounce_coordinator_update": {"executed_lines": [654, 655, 656, 657, 658, 661, 662, 663], "summary": {"covered_lines": 8, "num_statements": 8, "percent_covered": 92.85714285714286, "percent_covered_display": "93", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 6, "num_partial_branches": 1, "covered_branches": 5, "missing_branches": 1, "percent_branches_covered": 83.33333333333333, "percent_branches_covered_display": "83"}, "missing_lines": [], "excluded_lines": [], "start_line": 653, "executed_branches": [[654, -653], [654, 655], [656, 657], [656, 661], [661, 662]], "missing_branches": [[661, -653]]}, "GoeChargerApiV2Bridge._ws_compute_hashed_password": {"executed_lines": [668, 670, 677, 678, 682, 685, 686, 688], "summary": {"covered_lines": 8, "num_statements": 8, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 4, "num_partial_branches": 0, "covered_branches": 4, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 667, "executed_branches": [[668, 670], [668, 678], [678, 682], [678, 688]], "missing_branches": []}, "GoeChargerApiV2Bridge._ws_compute_auth_hash": {"executed_lines": [692, 693, 694], "summary": {"covered_lines": 3, "num_statements": 3, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 690, "executed_branches": [], "missing_branches": []}, "GoeChargerApiV2Bridge._ws_decode_message": {"executed_lines": [698, 699, 700, 702, 703, 704], "summary": {"covered_lines": 6, "num_statements": 7, "percent_covered": 84.61538461538461, "percent_covered_display": "85", "missing_lines": 1, "excluded_lines": 0, "percent_statements_covered": 85.71428571428571, "percent_statements_covered_display": "86", "num_branches": 6, "num_partial_branches": 1, "covered_branches": 5, "missing_branches": 1, "percent_branches_covered": 83.33333333333333, "percent_branches_covered_display": "83"}, "missing_lines": [705], "excluded_lines": [], "start_line": 696, "executed_branches": [[698, 699], [698, 703], [699, 700], [699, 702], [703, 704]], "missing_branches": [[703, 705]]}, "GoeChargerApiV2Bridge._ws_normalize_value": {"executed_lines": [709, 710, 711, 712, 713, 714, 715, 716, 717, 718, 719, 721], "summary": {"covered_lines": 12, "num_statements": 12, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 8, "num_partial_branches": 0, "covered_branches": 8, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 707, "executed_branches": [[709, 710], [709, 714], [714, 715], [714, 716], [716, 717], [716, 718], [718, 719], [718, 721]], "missing_branches": []}, "GoeChargerApiV2Bridge._ws_normalize_dict": {"executed_lines": [725, 726, 727, 728, 729], "summary": {"covered_lines": 5, "num_statements": 5, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 2, "num_partial_branches": 0, "covered_branches": 2, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 723, "executed_branches": [[726, 727], [726, 729]], "missing_branches": []}, "GoeChargerApiV2Bridge._ws_send_command": {"executed_lines": [733, 734, 735, 743, 751, 753, 755, 756, 757, 769, 770, 773, 774, 776], "summary": {"covered_lines": 14, "num_statements": 14, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 2, "num_partial_branches": 0, "covered_branches": 2, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 731, "executed_branches": [[753, 755], [753, 773]], "missing_branches": []}, "GoeChargerApiV2Bridge.ws_connect": {"executed_lines": [780, 781, 782, 784, 785, 786, 787, 788, 790, 791, 793, 795, 796, 797, 798, 801, 802, 803, 804, 805, 806, 807, 808, 809, 811, 812, 813, 814, 815, 816, 817, 818, 820, 821, 823, 824, 825, 826, 828, 829, 830, 831, 832, 835, 836, 837, 839, 840, 841, 842, 843, 845, 846, 847, 848, 851, 854, 855, 856, 858, 859, 860, 861, 862, 865, 871, 872, 873, 875, 876, 877, 878, 879, 880, 881, 884, 885, 888, 907, 908, 911, 913, 914, 915, 916, 917, 918, 919, 921, 922, 923, 924, 925, 926, 927, 928, 929, 931, 932, 933, 936, 937, 939, 940, 941, 942, 943, 945, 946, 948, 949, 950, 951, 953, 954, 956, 957, 958, 961, 964, 966, 967, 970, 971, 973, 974, 975, 976, 977, 978, 979, 980, 981, 982, 983, 984, 985, 986, 987, 988, 990, 992, 993, 994, 995, 1002, 1003, 1004, 1008, 1010, 1011, 1012, 1013, 1014], "summary": {"covered_lines": 154, "num_statements": 160, "percent_covered": 90.990990990991, "percent_covered_display": "91", "missing_lines": 6, "excluded_lines": 0, "percent_statements_covered": 96.25, "percent_statements_covered_display": "96", "num_branches": 62, "num_partial_branches": 12, "covered_branches": 48, "missing_branches": 14, "percent_branches_covered": 77.41935483870968, "percent_branches_covered_display": "77"}, "missing_lines": [866, 867, 868, 869, 996, 997], "excluded_lines": [], "start_line": 778, "executed_branches": [[784, 785], [784, 790], [786, 787], [790, 791], [790, 793], [805, 806], [805, 811], [807, 808], [812, 813], [812, 820], [830, 831], [839, 840], [839, 845], [841, 842], [847, 848], [847, 858], [855, 856], [855, 858], [858, 859], [858, 865], [860, 861], [865, 871], [877, 878], [877, 884], [879, 880], [917, 918], [925, 926], [925, 931], [927, 928], [936, 937], [936, 990], [939, 940], [939, 948], [948, 949], [948, 956], [956, 957], [956, 961], [964, 936], [964, 966], [970, 936], [970, 971], [975, 976], [975, 990], [979, 980], [983, 984], [987, 988], [1002, 1003], [1002, 1008]], "missing_branches": [[786, 788], [807, 809], [830, 832], [841, 843], [860, 862], [865, 866], [867, 868], [867, 869], [879, 881], [917, 919], [927, 929], [979, 990], [983, 990], [987, 990]]}, "GoeChargerApiV2Bridge.extract_ws_message_data": {"executed_lines": [1017, 1018, 1020, 1021, 1022, 1023, 1024, 1027, 1028, 1029, 1030, 1032, 1034, 1035, 1037, 1038, 1039, 1041, 1043, 1044, 1045, 1048, 1049, 1050, 1051, 1052, 1054, 1055, 1056, 1057, 1058, 1059, 1062, 1064, 1065, 1070, 1072], "summary": {"covered_lines": 37, "num_statements": 39, "percent_covered": 90.76923076923077, "percent_covered_display": "91", "missing_lines": 2, "excluded_lines": 0, "percent_statements_covered": 94.87179487179488, "percent_statements_covered_display": "95", "num_branches": 26, "num_partial_branches": 4, "covered_branches": 22, "missing_branches": 4, "percent_branches_covered": 84.61538461538461, "percent_branches_covered_display": "85"}, "missing_lines": [1060, 1067], "excluded_lines": [], "start_line": 1016, "executed_branches": [[1020, 1021], [1020, 1037], [1022, 1023], [1022, 1072], [1023, 1024], [1023, 1027], [1028, 1029], [1028, 1032], [1037, 1038], [1037, 1048], [1039, 1041], [1039, 1072], [1043, 1044], [1043, 1072], [1048, 1049], [1048, 1070], [1050, 1051], [1054, 1055], [1054, 1064], [1056, 1057], [1059, 1062], [1064, 1065]], "missing_branches": [[1050, 1054], [1056, 1072], [1059, 1060], [1064, 1067]]}, "bcryptjs_base64_encode_serial": {"executed_lines": [1084, 1086, 1087, 1088, 1091, 1093, 1094, 1095, 1096, 1097, 1098, 1099, 1100, 1101, 1102, 1103, 1104, 1105, 1108, 1109, 1110, 1111], "summary": {"covered_lines": 22, "num_statements": 24, "percent_covered": 87.5, "percent_covered_display": "88", "missing_lines": 2, "excluded_lines": 0, "percent_statements_covered": 91.66666666666667, "percent_statements_covered_display": "92", "num_branches": 8, "num_partial_branches": 2, "covered_branches": 6, "missing_branches": 2, "percent_branches_covered": 75.0, "percent_branches_covered_display": "75"}, "missing_lines": [1106, 1107], "excluded_lines": [], "start_line": 1075, "executed_branches": [[1086, 1087], [1086, 1091], [1095, 1096], [1099, 1100], [1099, 1102], [1105, 1108]], "missing_branches": [[1095, 1111], [1105, 1106]]}, "": {"executed_lines": [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 14, 15, 16, 18, 43, 45, 47, 49, 69, 81, 84, 88, 89, 93, 96, 100, 108, 119, 126, 131, 133, 234, 237, 255, 267, 275, 319, 331, 380, 387, 422, 433, 475, 504, 543, 562, 617, 621, 629, 649, 667, 690, 696, 707, 723, 731, 778, 1016, 1074, 1075], "summary": {"covered_lines": 62, "num_statements": 62, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 1, "executed_branches": [], "missing_branches": []}}, "classes": {"TargetedEvent": {"executed_lines": [50, 51, 52, 53, 56, 59, 62, 64, 66, 67, 71, 72, 73, 76, 77, 79, 82, 85, 86], "summary": {"covered_lines": 19, "num_statements": 19, "percent_covered": 96.0, "percent_covered_display": "96", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 6, "num_partial_branches": 1, "covered_branches": 5, "missing_branches": 1, "percent_branches_covered": 83.33333333333333, "percent_branches_covered_display": "83"}, "missing_lines": [], "excluded_lines": [], "start_line": 47, "executed_branches": [[51, 52], [52, 53], [52, 56], [71, 72], [71, 76]], "missing_branches": [[51, 66]]}, "NonNullDict": {"executed_lines": [90, 91], "summary": {"covered_lines": 2, "num_statements": 2, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 88, "executed_branches": [], "missing_branches": []}, "ExpiringStore": {"executed_lines": [97, 98, 102, 106, 110, 111, 112, 113, 114, 117, 121, 122, 123, 124, 127, 128], "summary": {"covered_lines": 16, "num_statements": 17, "percent_covered": 91.30434782608695, "percent_covered_display": "91", "missing_lines": 1, "excluded_lines": 0, "percent_statements_covered": 94.11764705882354, "percent_statements_covered_display": "94", "num_branches": 6, "num_partial_branches": 1, "covered_branches": 5, "missing_branches": 1, "percent_branches_covered": 83.33333333333333, "percent_branches_covered_display": "83"}, "missing_lines": [116], "excluded_lines": [], "start_line": 93, "executed_branches": [[111, 112], [111, 117], [113, 114], [123, -119], [123, 124]], "missing_branches": [[113, 116]]}, "GoeChargerApiV2Bridge": {"executed_lines": [137, 138, 140, 142, 145, 146, 148, 149, 152, 153, 155, 156, 158, 160, 164, 165, 168, 169, 170, 172, 173, 174, 176, 177, 178, 179, 180, 181, 182, 183, 184, 185, 186, 188, 189, 190, 191, 192, 194, 195, 196, 198, 199, 201, 202, 203, 204, 206, 208, 209, 210, 211, 212, 213, 216, 217, 218, 219, 220, 221, 222, 223, 224, 225, 226, 227, 228, 229, 230, 231, 232, 235, 238, 239, 240, 241, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 253, 256, 257, 258, 259, 262, 263, 264, 268, 271, 273, 276, 279, 281, 282, 283, 284, 285, 286, 287, 289, 291, 292, 293, 295, 304, 305, 306, 307, 309, 310, 311, 312, 314, 315, 316, 317, 320, 323, 324, 326, 327, 329, 332, 333, 335, 336, 337, 338, 339, 340, 342, 343, 344, 345, 348, 350, 351, 353, 355, 357, 361, 362, 365, 367, 368, 371, 372, 374, 375, 376, 377, 378, 381, 382, 383, 384, 385, 388, 389, 390, 392, 393, 394, 396, 397, 398, 403, 404, 405, 406, 407, 408, 416, 417, 420, 423, 424, 426, 427, 428, 429, 431, 434, 435, 436, 437, 438, 440, 441, 442, 443, 444, 445, 446, 447, 448, 449, 451, 452, 453, 454, 455, 457, 458, 460, 461, 467, 473, 476, 477, 478, 480, 481, 482, 483, 484, 485, 486, 487, 489, 490, 496, 502, 505, 507, 508, 510, 512, 513, 514, 516, 518, 521, 522, 523, 524, 525, 530, 532, 533, 534, 535, 536, 537, 539, 541, 544, 545, 546, 547, 549, 550, 552, 553, 554, 555, 556, 557, 558, 559, 560, 563, 565, 566, 568, 570, 571, 572, 573, 574, 575, 576, 578, 579, 580, 582, 583, 584, 586, 588, 589, 594, 595, 596, 597, 603, 610, 618, 619, 622, 623, 624, 626, 627, 631, 632, 634, 636, 637, 638, 639, 640, 641, 642, 643, 645, 647, 650, 651, 653, 654, 655, 656, 657, 658, 661, 662, 663, 665, 668, 670, 677, 678, 682, 685, 686, 688, 692, 693, 694, 698, 699, 700, 702, 703, 704, 709, 710, 711, 712, 713, 714, 715, 716, 717, 718, 719, 721, 725, 726, 727, 728, 729, 733, 734, 735, 743, 751, 753, 755, 756, 757, 769, 770, 773, 774, 776, 780, 781, 782, 784, 785, 786, 787, 788, 790, 791, 793, 795, 796, 797, 798, 801, 802, 803, 804, 805, 806, 807, 808, 809, 811, 812, 813, 814, 815, 816, 817, 818, 820, 821, 823, 824, 825, 826, 828, 829, 830, 831, 832, 835, 836, 837, 839, 840, 841, 842, 843, 845, 846, 847, 848, 851, 854, 855, 856, 858, 859, 860, 861, 862, 865, 871, 872, 873, 875, 876, 877, 878, 879, 880, 881, 884, 885, 888, 907, 908, 911, 913, 914, 915, 916, 917, 918, 919, 921, 922, 923, 924, 925, 926, 927, 928, 929, 931, 932, 933, 936, 937, 939, 940, 941, 942, 943, 945, 946, 948, 949, 950, 951, 953, 954, 956, 957, 958, 961, 964, 966, 967, 970, 971, 973, 974, 975, 976, 977, 978, 979, 980, 981, 982, 983, 984, 985, 986, 987, 988, 990, 992, 993, 994, 995, 1002, 1003, 1004, 1008, 1010, 1011, 1012, 1013, 1014, 1017, 1018, 1020, 1021, 1022, 1023, 1024, 1027, 1028, 1029, 1030, 1032, 1034, 1035, 1037, 1038, 1039, 1041, 1043, 1044, 1045, 1048, 1049, 1050, 1051, 1052, 1054, 1055, 1056, 1057, 1058, 1059, 1062, 1064, 1065, 1070, 1072], "summary": {"covered_lines": 566, "num_statements": 597, "percent_covered": 93.7428896473265, "percent_covered_display": "94", "missing_lines": 31, "excluded_lines": 0, "percent_statements_covered": 94.80737018425461, "percent_statements_covered_display": "95", "num_branches": 282, "num_partial_branches": 22, "covered_branches": 258, "missing_branches": 24, "percent_branches_covered": 91.48936170212765, "percent_branches_covered_display": "91"}, "missing_lines": [463, 464, 468, 469, 470, 471, 492, 493, 498, 499, 500, 501, 591, 592, 598, 599, 600, 601, 605, 606, 607, 608, 705, 866, 867, 868, 869, 996, 997, 1060, 1067], "excluded_lines": [], "start_line": 131, "executed_branches": [[140, 142], [140, 160], [145, 146], [145, 148], [152, 153], [152, 172], [155, 156], [155, 158], [160, 164], [160, 172], [168, 169], [168, 172], [172, 173], [172, 176], [176, 177], [176, 188], [203, 204], [203, 206], [238, 239], [238, 240], [262, -255], [262, 263], [263, -255], [263, 264], [268, 271], [268, 273], [276, 279], [276, 281], [281, 282], [281, 291], [283, 284], [283, 285], [285, 286], [285, 287], [287, 281], [287, 289], [291, 292], [291, 295], [295, 304], [295, 317], [305, 306], [305, 309], [309, 310], [309, 314], [323, 324], [323, 326], [326, 327], [326, 329], [333, 335], [333, 338], [335, 336], [335, 342], [336, 337], [336, 342], [338, 339], [339, 340], [339, 342], [342, 343], [342, 371], [344, 345], [344, 348], [350, 351], [350, 353], [355, -331], [355, 357], [361, 362], [361, 365], [365, -331], [365, 367], [371, 372], [371, 374], [375, 376], [375, 377], [377, 378], [384, -380], [384, 385], [388, 389], [388, 420], [389, 390], [389, 392], [393, 394], [393, 396], [396, 397], [396, 416], [398, -387], [398, 403], [403, -387], [403, 404], [404, 405], [406, 403], [406, 407], [428, 429], [437, 438], [437, 440], [443, 444], [443, 467], [446, 447], [446, 473], [448, 449], [448, 451], [453, 454], [453, 457], [454, 453], [454, 455], [477, 478], [477, 480], [483, 484], [483, 496], [486, 487], [486, 502], [507, 508], [507, 518], [508, 510], [508, 512], [512, 513], [512, 516], [518, 521], [518, 532], [522, 523], [522, 524], [524, 525], [524, 530], [532, 533], [532, 534], [534, 535], [534, 536], [536, 537], [536, 539], [544, 545], [544, 552], [545, 546], [545, 549], [553, 554], [553, 560], [554, 555], [554, 556], [556, 557], [556, 558], [558, 553], [558, 559], [565, 566], [565, 568], [572, 573], [572, 594], [575, 576], [575, 610], [576, 578], [576, 586], [578, 579], [578, 582], [582, 583], [582, 584], [594, 595], [594, 603], [622, 623], [622, 626], [631, 632], [631, 634], [638, 639], [638, 647], [650, 651], [650, 653], [654, -653], [654, 655], [656, 657], [656, 661], [661, 662], [668, 670], [668, 678], [678, 682], [678, 688], [698, 699], [698, 703], [699, 700], [699, 702], [703, 704], [709, 710], [709, 714], [714, 715], [714, 716], [716, 717], [716, 718], [718, 719], [718, 721], [726, 727], [726, 729], [753, 755], [753, 773], [784, 785], [784, 790], [786, 787], [790, 791], [790, 793], [805, 806], [805, 811], [807, 808], [812, 813], [812, 820], [830, 831], [839, 840], [839, 845], [841, 842], [847, 848], [847, 858], [855, 856], [855, 858], [858, 859], [858, 865], [860, 861], [865, 871], [877, 878], [877, 884], [879, 880], [917, 918], [925, 926], [925, 931], [927, 928], [936, 937], [936, 990], [939, 940], [939, 948], [948, 949], [948, 956], [956, 957], [956, 961], [964, 936], [964, 966], [970, 936], [970, 971], [975, 976], [975, 990], [979, 980], [983, 984], [987, 988], [1002, 1003], [1002, 1008], [1020, 1021], [1020, 1037], [1022, 1023], [1022, 1072], [1023, 1024], [1023, 1027], [1028, 1029], [1028, 1032], [1037, 1038], [1037, 1048], [1039, 1041], [1039, 1072], [1043, 1044], [1043, 1072], [1048, 1049], [1048, 1070], [1050, 1051], [1054, 1055], [1054, 1064], [1056, 1057], [1059, 1062], [1064, 1065]], "missing_branches": [[338, 342], [377, -331], [404, 403], [428, 431], [661, -653], [703, 705], [786, 788], [807, 809], [830, 832], [841, 843], [860, 862], [865, 866], [867, 868], [867, 869], [879, 881], [917, 919], [927, 929], [979, 990], [983, 990], [987, 990], [1050, 1054], [1056, 1072], [1059, 1060], [1064, 1067]]}, "": {"executed_lines": [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 14, 15, 16, 18, 43, 45, 47, 49, 69, 81, 84, 88, 89, 93, 96, 100, 108, 119, 126, 131, 133, 234, 237, 255, 267, 275, 319, 331, 380, 387, 422, 433, 475, 504, 543, 562, 617, 621, 629, 649, 667, 690, 696, 707, 723, 731, 778, 1016, 1074, 1075, 1084, 1086, 1087, 1088, 1091, 1093, 1094, 1095, 1096, 1097, 1098, 1099, 1100, 1101, 1102, 1103, 1104, 1105, 1108, 1109, 1110, 1111], "summary": {"covered_lines": 84, "num_statements": 86, "percent_covered": 95.74468085106383, "percent_covered_display": "96", "missing_lines": 2, "excluded_lines": 0, "percent_statements_covered": 97.67441860465117, "percent_statements_covered_display": "98", "num_branches": 8, "num_partial_branches": 2, "covered_branches": 6, "missing_branches": 2, "percent_branches_covered": 75.0, "percent_branches_covered_display": "75"}, "missing_lines": [1106, 1107], "excluded_lines": [], "start_line": 1, "executed_branches": [[1086, 1087], [1086, 1091], [1095, 1096], [1099, 1100], [1099, 1102], [1105, 1108]], "missing_branches": [[1095, 1111], [1105, 1106]]}}}, "custom_components/goecharger_api2/pygoecharger_ha/const.py": {"executed_lines": [1, 2, 6, 11, 12, 13, 14, 16, 17, 18, 19, 22, 23, 24, 25, 26, 29, 31, 32, 34, 37, 39, 40, 41, 46, 47, 48, 51, 52, 53, 56, 57, 58, 60, 61, 62, 63, 64, 65, 66, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 90, 111], "summary": {"covered_lines": 61, "num_statements": 61, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "executed_branches": [], "missing_branches": [], "functions": {"": {"executed_lines": [1, 2, 6, 11, 12, 13, 14, 16, 17, 18, 19, 22, 23, 24, 25, 26, 29, 31, 32, 34, 37, 39, 40, 41, 46, 47, 48, 51, 52, 53, 56, 57, 58, 60, 61, 62, 63, 64, 65, 66, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 90, 111], "summary": {"covered_lines": 61, "num_statements": 61, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 1, "executed_branches": [], "missing_branches": []}}, "classes": {"INTG_TYPE": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 0, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 56, "executed_branches": [], "missing_branches": []}, "CAR_VALUES": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 0, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 60, "executed_branches": [], "missing_branches": []}, "CT_VALUES": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 0, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 70, "executed_branches": [], "missing_branches": []}, "": {"executed_lines": [1, 2, 6, 11, 12, 13, 14, 16, 17, 18, 19, 22, 23, 24, 25, 26, 29, 31, 32, 34, 37, 39, 40, 41, 46, 47, 48, 51, 52, 53, 56, 57, 58, 60, 61, 62, 63, 64, 65, 66, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 90, 111], "summary": {"covered_lines": 61, "num_statements": 61, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 1, "executed_branches": [], "missing_branches": []}}}, "custom_components/goecharger_api2/pygoecharger_ha/keys.py": {"executed_lines": [1, 2, 3, 9, 11, 13, 14, 15, 16, 17, 20, 23, 24, 27, 29, 32, 36, 38, 40, 42, 44, 46, 48, 50, 52, 54, 56, 58, 60, 62, 64, 66, 68, 70, 72, 74, 76, 78, 80, 82, 84, 95, 98, 100, 102, 104, 107, 109, 111, 113, 115, 117, 119, 121, 123, 125, 127, 129, 131, 133, 135, 137, 139, 141, 143, 145, 147, 149, 151, 153, 155, 157, 159, 161, 163, 165, 167, 169, 171, 173, 175, 179, 181, 183, 185, 187, 189, 191, 193, 195, 197, 199, 201, 203, 205, 207, 209, 211, 213, 215, 217, 219, 221, 223, 225, 227, 229, 232, 234, 236, 238, 240, 242, 244, 246, 248, 250, 252, 254, 256, 258, 260, 262, 264, 266, 268, 270, 272, 274, 276, 278, 280, 282, 284, 286, 288, 290, 292, 294, 296, 298, 300, 302, 304, 306, 308, 310, 312, 314, 316, 318, 320, 322, 324, 326, 328, 330, 332, 334, 336, 338, 340, 342, 344, 346, 350, 353, 354, 355, 359, 361, 366, 372, 378, 384, 390, 396, 402, 409, 411, 413, 415, 426, 428, 430, 432, 434, 436, 438, 440, 442, 444, 446, 448, 450, 452, 454, 456, 458, 460, 462, 464, 466, 523, 525, 527, 529, 531, 533, 535, 537, 539, 541, 543, 545, 547, 549, 551, 560, 562, 564, 566, 568, 570, 664, 668, 670, 673, 689, 691, 693, 695, 697, 699, 701, 703, 705, 707, 737, 739, 743, 753, 767, 785, 787, 789, 791, 793, 805, 807, 809, 811, 813], "summary": {"covered_lines": 253, "num_statements": 255, "percent_covered": 99.2156862745098, "percent_covered_display": "99", "missing_lines": 2, "excluded_lines": 0, "percent_statements_covered": 99.2156862745098, "percent_statements_covered_display": "99", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [30, 33], "excluded_lines": [], "executed_branches": [], "missing_branches": [], "functions": {"Tag.__hash__": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 1, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 1, "excluded_lines": 0, "percent_statements_covered": 0.0, "percent_statements_covered_display": "0", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [30], "excluded_lines": [], "start_line": 29, "executed_branches": [], "missing_branches": []}, "Tag.__str__": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 1, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 1, "excluded_lines": 0, "percent_statements_covered": 0.0, "percent_statements_covered_display": "0", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [33], "excluded_lines": [], "start_line": 32, "executed_branches": [], "missing_branches": []}, "": {"executed_lines": [1, 2, 3, 9, 11, 13, 14, 15, 16, 17, 20, 23, 24, 27, 29, 32, 36, 38, 40, 42, 44, 46, 48, 50, 52, 54, 56, 58, 60, 62, 64, 66, 68, 70, 72, 74, 76, 78, 80, 82, 84, 95, 98, 100, 102, 104, 107, 109, 111, 113, 115, 117, 119, 121, 123, 125, 127, 129, 131, 133, 135, 137, 139, 141, 143, 145, 147, 149, 151, 153, 155, 157, 159, 161, 163, 165, 167, 169, 171, 173, 175, 179, 181, 183, 185, 187, 189, 191, 193, 195, 197, 199, 201, 203, 205, 207, 209, 211, 213, 215, 217, 219, 221, 223, 225, 227, 229, 232, 234, 236, 238, 240, 242, 244, 246, 248, 250, 252, 254, 256, 258, 260, 262, 264, 266, 268, 270, 272, 274, 276, 278, 280, 282, 284, 286, 288, 290, 292, 294, 296, 298, 300, 302, 304, 306, 308, 310, 312, 314, 316, 318, 320, 322, 324, 326, 328, 330, 332, 334, 336, 338, 340, 342, 344, 346, 350, 353, 354, 355, 359, 361, 366, 372, 378, 384, 390, 396, 402, 409, 411, 413, 415, 426, 428, 430, 432, 434, 436, 438, 440, 442, 444, 446, 448, 450, 452, 454, 456, 458, 460, 462, 464, 466, 523, 525, 527, 529, 531, 533, 535, 537, 539, 541, 543, 545, 547, 549, 551, 560, 562, 564, 566, 568, 570, 664, 668, 670, 673, 689, 691, 693, 695, 697, 699, 701, 703, 705, 707, 737, 739, 743, 753, 767, 785, 787, 789, 791, 793, 805, 807, 809, 811, 813], "summary": {"covered_lines": 253, "num_statements": 253, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 1, "executed_branches": [], "missing_branches": []}}, "classes": {"CAT": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 0, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 13, "executed_branches": [], "missing_branches": []}, "ApiKey": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 0, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 20, "executed_branches": [], "missing_branches": []}, "Tag": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 2, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 2, "excluded_lines": 0, "percent_statements_covered": 0.0, "percent_statements_covered_display": "0", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [30, 33], "excluded_lines": [], "start_line": 27, "executed_branches": [], "missing_branches": []}, "": {"executed_lines": [1, 2, 3, 9, 11, 13, 14, 15, 16, 17, 20, 23, 24, 27, 29, 32, 36, 38, 40, 42, 44, 46, 48, 50, 52, 54, 56, 58, 60, 62, 64, 66, 68, 70, 72, 74, 76, 78, 80, 82, 84, 95, 98, 100, 102, 104, 107, 109, 111, 113, 115, 117, 119, 121, 123, 125, 127, 129, 131, 133, 135, 137, 139, 141, 143, 145, 147, 149, 151, 153, 155, 157, 159, 161, 163, 165, 167, 169, 171, 173, 175, 179, 181, 183, 185, 187, 189, 191, 193, 195, 197, 199, 201, 203, 205, 207, 209, 211, 213, 215, 217, 219, 221, 223, 225, 227, 229, 232, 234, 236, 238, 240, 242, 244, 246, 248, 250, 252, 254, 256, 258, 260, 262, 264, 266, 268, 270, 272, 274, 276, 278, 280, 282, 284, 286, 288, 290, 292, 294, 296, 298, 300, 302, 304, 306, 308, 310, 312, 314, 316, 318, 320, 322, 324, 326, 328, 330, 332, 334, 336, 338, 340, 342, 344, 346, 350, 353, 354, 355, 359, 361, 366, 372, 378, 384, 390, 396, 402, 409, 411, 413, 415, 426, 428, 430, 432, 434, 436, 438, 440, 442, 444, 446, 448, 450, 452, 454, 456, 458, 460, 462, 464, 466, 523, 525, 527, 529, 531, 533, 535, 537, 539, 541, 543, 545, 547, 549, 551, 560, 562, 564, 566, 568, 570, 664, 668, 670, 673, 689, 691, 693, 695, 697, 699, 701, 703, 705, 707, 737, 739, 743, 753, 767, 785, 787, 789, 791, 793, 805, 807, 809, 811, 813], "summary": {"covered_lines": 253, "num_statements": 253, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 1, "executed_branches": [], "missing_branches": []}}}, "custom_components/goecharger_api2/select.py": {"executed_lines": [1, 3, 4, 5, 6, 7, 9, 10, 12, 13, 14, 16, 19, 37, 38, 39, 40, 43, 59, 61, 62, 63, 64, 65, 66, 67, 68, 70, 71, 73, 74, 76, 79, 82, 83, 84, 85, 87, 89, 90, 92, 93, 95, 96, 97, 98, 99, 101, 102, 104, 105, 107, 108, 109, 111, 113, 116, 117, 118, 119, 120, 122, 124, 126, 127, 129, 130, 133, 135, 165, 167, 169, 172, 173, 175, 178, 179, 181, 184, 185, 187, 190, 191, 193, 196, 198, 199, 200], "summary": {"covered_lines": 88, "num_statements": 100, "percent_covered": 86.53846153846153, "percent_covered_display": "87", "missing_lines": 12, "excluded_lines": 0, "percent_statements_covered": 88.0, "percent_statements_covered_display": "88", "num_branches": 56, "num_partial_branches": 1, "covered_branches": 47, "missing_branches": 9, "percent_branches_covered": 83.92857142857143, "percent_branches_covered_display": "84"}, "missing_lines": [20, 21, 22, 24, 25, 26, 27, 28, 30, 31, 32, 34], "excluded_lines": [], "executed_branches": [[39, 40], [39, 59], [65, 66], [66, 67], [66, 73], [67, 68], [67, 70], [73, 74], [73, 76], [79, 82], [79, 89], [82, 83], [82, 84], [84, 85], [84, 87], [89, 90], [89, 92], [92, 93], [92, 99], [104, 105], [104, 129], [105, 107], [105, 126], [108, 109], [108, 111], [111, 113], [111, 122], [116, 117], [116, 122], [117, 118], [117, 119], [119, 116], [119, 120], [129, 130], [129, 133], [133, 135], [133, 198], [165, 167], [165, 169], [172, 173], [172, 175], [178, 179], [178, 181], [184, 185], [184, 187], [190, 191], [190, 193]], "missing_branches": [[24, 25], [24, 30], [25, 26], [25, 34], [26, 25], [26, 27], [30, 31], [30, 34], [65, 79]], "functions": {"async_setup_entry": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 12, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 12, "excluded_lines": 0, "percent_statements_covered": 0.0, "percent_statements_covered_display": "0", "num_branches": 8, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 8, "percent_branches_covered": 0.0, "percent_branches_covered_display": "0"}, "missing_lines": [20, 21, 22, 24, 25, 26, 27, 28, 30, 31, 32, 34], "excluded_lines": [], "start_line": 19, "executed_branches": [], "missing_branches": [[24, 25], [24, 30], [25, 26], [25, 34], [26, 25], [26, 27], [30, 31], [30, 34]]}, "GoeChargerSelect.__init__": {"executed_lines": [39, 40, 43, 59], "summary": {"covered_lines": 4, "num_statements": 4, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 2, "num_partial_branches": 0, "covered_branches": 2, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 38, "executed_branches": [[39, 40], [39, 59]], "missing_branches": []}, "GoeChargerSelect.current_option": {"executed_lines": [63, 64, 65, 66, 67, 68, 70, 71, 73, 74, 76, 79, 82, 83, 84, 85, 87, 89, 90, 92, 93, 95, 96, 97, 98, 99], "summary": {"covered_lines": 26, "num_statements": 26, "percent_covered": 97.72727272727273, "percent_covered_display": "98", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 18, "num_partial_branches": 1, "covered_branches": 17, "missing_branches": 1, "percent_branches_covered": 94.44444444444444, "percent_branches_covered_display": "94"}, "missing_lines": [], "excluded_lines": [], "start_line": 62, "executed_branches": [[65, 66], [66, 67], [66, 73], [67, 68], [67, 70], [73, 74], [73, 76], [79, 82], [79, 89], [82, 83], [82, 84], [84, 85], [84, 87], [89, 90], [89, 92], [92, 93], [92, 99]], "missing_branches": [[65, 79]]}, "GoeChargerSelect.async_select_option": {"executed_lines": [102, 104, 105, 107, 108, 109, 111, 113, 116, 117, 118, 119, 120, 122, 124, 126, 127, 129, 130, 133, 135, 165, 167, 169, 172, 173, 175, 178, 179, 181, 184, 185, 187, 190, 191, 193, 196, 198, 199, 200], "summary": {"covered_lines": 40, "num_statements": 40, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 28, "num_partial_branches": 0, "covered_branches": 28, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 101, "executed_branches": [[104, 105], [104, 129], [105, 107], [105, 126], [108, 109], [108, 111], [111, 113], [111, 122], [116, 117], [116, 122], [117, 118], [117, 119], [119, 116], [119, 120], [129, 130], [129, 133], [133, 135], [133, 198], [165, 167], [165, 169], [172, 173], [172, 175], [178, 179], [178, 181], [184, 185], [184, 187], [190, 191], [190, 193]], "missing_branches": []}, "": {"executed_lines": [1, 3, 4, 5, 6, 7, 9, 10, 12, 13, 14, 16, 19, 37, 38, 61, 62, 101], "summary": {"covered_lines": 18, "num_statements": 18, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 1, "executed_branches": [], "missing_branches": []}}, "classes": {"GoeChargerSelect": {"executed_lines": [39, 40, 43, 59, 63, 64, 65, 66, 67, 68, 70, 71, 73, 74, 76, 79, 82, 83, 84, 85, 87, 89, 90, 92, 93, 95, 96, 97, 98, 99, 102, 104, 105, 107, 108, 109, 111, 113, 116, 117, 118, 119, 120, 122, 124, 126, 127, 129, 130, 133, 135, 165, 167, 169, 172, 173, 175, 178, 179, 181, 184, 185, 187, 190, 191, 193, 196, 198, 199, 200], "summary": {"covered_lines": 70, "num_statements": 70, "percent_covered": 99.15254237288136, "percent_covered_display": "99", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 48, "num_partial_branches": 1, "covered_branches": 47, "missing_branches": 1, "percent_branches_covered": 97.91666666666667, "percent_branches_covered_display": "98"}, "missing_lines": [], "excluded_lines": [], "start_line": 37, "executed_branches": [[39, 40], [39, 59], [65, 66], [66, 67], [66, 73], [67, 68], [67, 70], [73, 74], [73, 76], [79, 82], [79, 89], [82, 83], [82, 84], [84, 85], [84, 87], [89, 90], [89, 92], [92, 93], [92, 99], [104, 105], [104, 129], [105, 107], [105, 126], [108, 109], [108, 111], [111, 113], [111, 122], [116, 117], [116, 122], [117, 118], [117, 119], [119, 116], [119, 120], [129, 130], [129, 133], [133, 135], [133, 198], [165, 167], [165, 169], [172, 173], [172, 175], [178, 179], [178, 181], [184, 185], [184, 187], [190, 191], [190, 193]], "missing_branches": [[65, 79]]}, "": {"executed_lines": [1, 3, 4, 5, 6, 7, 9, 10, 12, 13, 14, 16, 19, 37, 38, 61, 62, 101], "summary": {"covered_lines": 18, "num_statements": 30, "percent_covered": 47.36842105263158, "percent_covered_display": "47", "missing_lines": 12, "excluded_lines": 0, "percent_statements_covered": 60.0, "percent_statements_covered_display": "60", "num_branches": 8, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 8, "percent_branches_covered": 0.0, "percent_branches_covered_display": "0"}, "missing_lines": [20, 21, 22, 24, 25, 26, 27, 28, 30, 31, 32, 34], "excluded_lines": [], "start_line": 1, "executed_branches": [], "missing_branches": [[24, 25], [24, 30], [25, 26], [25, 34], [26, 25], [26, 27], [30, 31], [30, 34]]}}}, "custom_components/goecharger_api2/sensor.py": {"executed_lines": [1, 2, 3, 4, 6, 7, 8, 9, 10, 11, 13, 14, 15, 16, 18, 21, 38, 39, 40, 42, 44, 45, 47, 48, 49, 50, 51, 52, 54, 55, 57, 58, 59, 60, 61, 65, 66, 67, 68, 69, 70, 71, 72, 77, 78, 81, 82, 83, 84, 85, 86, 88, 89, 91, 93, 94, 95, 96, 99, 100, 101, 102, 103, 105, 107, 109, 111, 112, 114, 115, 116, 118, 122, 125, 126, 127, 128, 130, 131, 133, 134, 135, 136, 137, 138, 139, 140, 141, 143, 144, 147, 148, 149, 150, 153], "summary": {"covered_lines": 95, "num_statements": 109, "percent_covered": 85.20710059171597, "percent_covered_display": "85", "missing_lines": 14, "excluded_lines": 0, "percent_statements_covered": 87.1559633027523, "percent_statements_covered_display": "87", "num_branches": 60, "num_partial_branches": 3, "covered_branches": 49, "missing_branches": 11, "percent_branches_covered": 81.66666666666667, "percent_branches_covered_display": "82"}, "missing_lines": [22, 23, 24, 26, 27, 28, 29, 30, 32, 33, 34, 36, 63, 145], "excluded_lines": [], "executed_branches": [[57, 58], [59, 60], [59, 61], [69, 70], [69, 111], [70, 71], [70, 94], [77, 78], [77, 93], [81, 82], [81, 83], [83, 84], [83, 85], [85, 86], [85, 88], [88, 89], [88, 91], [94, 95], [94, 96], [96, 99], [96, 109], [100, 101], [100, 107], [102, 103], [102, 105], [111, 112], [111, 114], [114, 115], [114, 122], [115, 116], [115, 118], [122, 125], [122, 133], [125, 126], [125, 130], [127, 128], [127, 130], [130, 131], [130, 133], [133, 134], [133, 135], [135, 136], [135, 137], [137, 138], [137, 153], [138, 139], [138, 140], [140, 141], [144, 147]], "missing_branches": [[26, 27], [26, 32], [27, 28], [27, 36], [28, 27], [28, 29], [32, 33], [32, 36], [57, 63], [140, 153], [144, 145]], "functions": {"async_setup_entry": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 12, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 12, "excluded_lines": 0, "percent_statements_covered": 0.0, "percent_statements_covered_display": "0", "num_branches": 8, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 8, "percent_branches_covered": 0.0, "percent_branches_covered_display": "0"}, "missing_lines": [22, 23, 24, 26, 27, 28, 29, 30, 32, 33, 34, 36], "excluded_lines": [], "start_line": 21, "executed_branches": [], "missing_branches": [[26, 27], [26, 32], [27, 28], [27, 36], [28, 27], [28, 29], [32, 33], [32, 36]]}, "GoeChargerSensor.__init__": {"executed_lines": [45], "summary": {"covered_lines": 1, "num_statements": 1, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 44, "executed_branches": [], "missing_branches": []}, "GoeChargerSensor._camel_to_snake": {"executed_lines": [49, 50, 51, 52], "summary": {"covered_lines": 4, "num_statements": 4, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 48, "executed_branches": [], "missing_branches": []}, "GoeChargerSensor.state": {"executed_lines": [57, 58, 59, 60, 61], "summary": {"covered_lines": 5, "num_statements": 6, "percent_covered": 80.0, "percent_covered_display": "80", "missing_lines": 1, "excluded_lines": 0, "percent_statements_covered": 83.33333333333333, "percent_statements_covered_display": "83", "num_branches": 4, "num_partial_branches": 1, "covered_branches": 3, "missing_branches": 1, "percent_branches_covered": 75.0, "percent_branches_covered_display": "75"}, "missing_lines": [63], "excluded_lines": [], "start_line": 55, "executed_branches": [[57, 58], [59, 60], [59, 61]], "missing_branches": [[57, 63]]}, "GoeChargerSensor.native_value": {"executed_lines": [67, 68, 69, 70, 71, 72, 77, 78, 81, 82, 83, 84, 85, 86, 88, 89, 91, 93, 94, 95, 96, 99, 100, 101, 102, 103, 105, 107, 109, 111, 112, 114, 115, 116, 118, 122, 125, 126, 127, 128, 130, 131, 133, 134, 135, 136, 137, 138, 139, 140, 141, 143, 144, 147, 148, 149, 150, 153], "summary": {"covered_lines": 58, "num_statements": 59, "percent_covered": 97.19626168224299, "percent_covered_display": "97", "missing_lines": 1, "excluded_lines": 0, "percent_statements_covered": 98.30508474576271, "percent_statements_covered_display": "98", "num_branches": 48, "num_partial_branches": 2, "covered_branches": 46, "missing_branches": 2, "percent_branches_covered": 95.83333333333333, "percent_branches_covered_display": "96"}, "missing_lines": [145], "excluded_lines": [], "start_line": 66, "executed_branches": [[69, 70], [69, 111], [70, 71], [70, 94], [77, 78], [77, 93], [81, 82], [81, 83], [83, 84], [83, 85], [85, 86], [85, 88], [88, 89], [88, 91], [94, 95], [94, 96], [96, 99], [96, 109], [100, 101], [100, 107], [102, 103], [102, 105], [111, 112], [111, 114], [114, 115], [114, 122], [115, 116], [115, 118], [122, 125], [122, 133], [125, 126], [125, 130], [127, 128], [127, 130], [130, 131], [130, 133], [133, 134], [133, 135], [135, 136], [135, 137], [137, 138], [137, 153], [138, 139], [138, 140], [140, 141], [144, 147]], "missing_branches": [[140, 153], [144, 145]]}, "": {"executed_lines": [1, 2, 3, 4, 6, 7, 8, 9, 10, 11, 13, 14, 15, 16, 18, 21, 38, 39, 40, 42, 44, 47, 48, 54, 55, 65, 66], "summary": {"covered_lines": 27, "num_statements": 27, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 1, "executed_branches": [], "missing_branches": []}}, "classes": {"GoeChargerSensor": {"executed_lines": [45, 49, 50, 51, 52, 57, 58, 59, 60, 61, 67, 68, 69, 70, 71, 72, 77, 78, 81, 82, 83, 84, 85, 86, 88, 89, 91, 93, 94, 95, 96, 99, 100, 101, 102, 103, 105, 107, 109, 111, 112, 114, 115, 116, 118, 122, 125, 126, 127, 128, 130, 131, 133, 134, 135, 136, 137, 138, 139, 140, 141, 143, 144, 147, 148, 149, 150, 153], "summary": {"covered_lines": 68, "num_statements": 70, "percent_covered": 95.90163934426229, "percent_covered_display": "96", "missing_lines": 2, "excluded_lines": 0, "percent_statements_covered": 97.14285714285714, "percent_statements_covered_display": "97", "num_branches": 52, "num_partial_branches": 3, "covered_branches": 49, "missing_branches": 3, "percent_branches_covered": 94.23076923076923, "percent_branches_covered_display": "94"}, "missing_lines": [63, 145], "excluded_lines": [], "start_line": 42, "executed_branches": [[57, 58], [59, 60], [59, 61], [69, 70], [69, 111], [70, 71], [70, 94], [77, 78], [77, 93], [81, 82], [81, 83], [83, 84], [83, 85], [85, 86], [85, 88], [88, 89], [88, 91], [94, 95], [94, 96], [96, 99], [96, 109], [100, 101], [100, 107], [102, 103], [102, 105], [111, 112], [111, 114], [114, 115], [114, 122], [115, 116], [115, 118], [122, 125], [122, 133], [125, 126], [125, 130], [127, 128], [127, 130], [130, 131], [130, 133], [133, 134], [133, 135], [135, 136], [135, 137], [137, 138], [137, 153], [138, 139], [138, 140], [140, 141], [144, 147]], "missing_branches": [[57, 63], [140, 153], [144, 145]]}, "": {"executed_lines": [1, 2, 3, 4, 6, 7, 8, 9, 10, 11, 13, 14, 15, 16, 18, 21, 38, 39, 40, 42, 44, 47, 48, 54, 55, 65, 66], "summary": {"covered_lines": 27, "num_statements": 39, "percent_covered": 57.4468085106383, "percent_covered_display": "57", "missing_lines": 12, "excluded_lines": 0, "percent_statements_covered": 69.23076923076923, "percent_statements_covered_display": "69", "num_branches": 8, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 8, "percent_branches_covered": 0.0, "percent_branches_covered_display": "0"}, "missing_lines": [22, 23, 24, 26, 27, 28, 29, 30, 32, 33, 34, 36], "excluded_lines": [], "start_line": 1, "executed_branches": [], "missing_branches": [[26, 27], [26, 32], [27, 28], [27, 36], [28, 27], [28, 29], [32, 33], [32, 36]]}}}, "custom_components/goecharger_api2/service.py": {"executed_lines": [1, 2, 3, 4, 6, 8, 9, 10, 11, 13, 14, 15, 17, 19, 21, 23, 25, 26, 30, 32, 33, 35, 37, 38, 39, 41, 42, 44, 45, 49, 50, 51, 53, 55, 56, 57, 60, 62, 63, 64, 65, 66, 67, 69, 71, 72, 73, 74, 75, 76, 77, 78, 80, 85, 86, 87, 88, 89, 95, 96, 97, 99, 100, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 115, 117, 118, 119, 126, 128, 129, 130, 132, 133, 134, 137, 138, 140, 142, 143, 144, 147, 149, 150, 151, 153, 154, 156, 157, 159], "summary": {"covered_lines": 101, "num_statements": 104, "percent_covered": 97.26027397260275, "percent_covered_display": "97", "missing_lines": 3, "excluded_lines": 0, "percent_statements_covered": 97.11538461538461, "percent_statements_covered_display": "97", "num_branches": 42, "num_partial_branches": 1, "covered_branches": 41, "missing_branches": 1, "percent_branches_covered": 97.61904761904762, "percent_branches_covered_display": "98"}, "missing_lines": [24, 27, 28], "excluded_lines": [], "executed_branches": [[23, 25], [37, -35], [37, 38], [60, 62], [60, 71], [63, 64], [63, 71], [64, 65], [64, 71], [66, 67], [66, 69], [74, 75], [74, 99], [75, 76], [75, 77], [77, 78], [77, 80], [88, 89], [88, 99], [96, 97], [96, 99], [99, -55], [99, 100], [103, 104], [103, 137], [110, 111], [110, 126], [118, -102], [118, 119], [129, -102], [129, 130], [133, -102], [133, 134], [137, -102], [137, 138], [143, 144], [143, 147], [149, 150], [149, 156], [150, 151], [150, 153]], "missing_branches": [[23, 24]], "functions": {"_is_finite_number": {"executed_lines": [23, 25, 26], "summary": {"covered_lines": 3, "num_statements": 6, "percent_covered": 50.0, "percent_covered_display": "50", "missing_lines": 3, "excluded_lines": 0, "percent_statements_covered": 50.0, "percent_statements_covered_display": "50", "num_branches": 2, "num_partial_branches": 1, "covered_branches": 1, "missing_branches": 1, "percent_branches_covered": 50.0, "percent_branches_covered_display": "50"}, "missing_lines": [24, 27, 28], "excluded_lines": [], "start_line": 21, "executed_branches": [[23, 25]], "missing_branches": [[23, 24]]}, "add_config_entry_to_service": {"executed_lines": [32, 33], "summary": {"covered_lines": 2, "num_statements": 2, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 30, "executed_branches": [], "missing_branches": []}, "remove_config_entry_from_service": {"executed_lines": [37, 38, 39], "summary": {"covered_lines": 3, "num_statements": 3, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 2, "num_partial_branches": 0, "covered_branches": 2, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 35, "executed_branches": [[37, -35], [37, 38]], "missing_branches": []}, "GoeChargerApiV2Service.__init__": {"executed_lines": [44, 45, 49, 50, 51, 53], "summary": {"covered_lines": 6, "num_statements": 6, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 42, "executed_branches": [], "missing_branches": []}, "GoeChargerApiV2Service.set_pv_data": {"executed_lines": [56, 57, 60, 62, 63, 64, 65, 66, 67, 69, 71, 72, 73, 74, 75, 76, 77, 78, 80, 85, 86, 87, 88, 89, 95, 96, 97, 99, 100], "summary": {"covered_lines": 29, "num_statements": 29, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 20, "num_partial_branches": 0, "covered_branches": 20, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 55, "executed_branches": [[60, 62], [60, 71], [63, 64], [63, 71], [64, 65], [64, 71], [66, 67], [66, 69], [74, 75], [74, 99], [75, 76], [75, 77], [77, 78], [77, 80], [88, 89], [88, 99], [96, 97], [96, 99], [99, -55], [99, 100]], "missing_branches": []}, "GoeChargerApiV2Service.stop_charging": {"executed_lines": [103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 115, 117, 118, 119, 126, 128, 129, 130, 132, 133, 134, 137, 138], "summary": {"covered_lines": 24, "num_statements": 24, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 12, "num_partial_branches": 0, "covered_branches": 12, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 102, "executed_branches": [[103, 104], [103, 137], [110, 111], [110, 126], [118, -102], [118, 119], [129, -102], [129, 130], [133, -102], [133, 134], [137, -102], [137, 138]], "missing_branches": []}, "GoeChargerApiV2Service.get_coordinator": {"executed_lines": [142, 143, 144, 147, 149, 150, 151, 153, 154, 156, 157, 159], "summary": {"covered_lines": 12, "num_statements": 12, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 6, "num_partial_branches": 0, "covered_branches": 6, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 140, "executed_branches": [[143, 144], [143, 147], [149, 150], [149, 156], [150, 151], [150, 153]], "missing_branches": []}, "": {"executed_lines": [1, 2, 3, 4, 6, 8, 9, 10, 11, 13, 14, 15, 17, 19, 21, 30, 35, 41, 42, 55, 102, 140], "summary": {"covered_lines": 22, "num_statements": 22, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 1, "executed_branches": [], "missing_branches": []}}, "classes": {"GoeChargerApiV2Service": {"executed_lines": [44, 45, 49, 50, 51, 53, 56, 57, 60, 62, 63, 64, 65, 66, 67, 69, 71, 72, 73, 74, 75, 76, 77, 78, 80, 85, 86, 87, 88, 89, 95, 96, 97, 99, 100, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 115, 117, 118, 119, 126, 128, 129, 130, 132, 133, 134, 137, 138, 142, 143, 144, 147, 149, 150, 151, 153, 154, 156, 157, 159], "summary": {"covered_lines": 71, "num_statements": 71, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 38, "num_partial_branches": 0, "covered_branches": 38, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 41, "executed_branches": [[60, 62], [60, 71], [63, 64], [63, 71], [64, 65], [64, 71], [66, 67], [66, 69], [74, 75], [74, 99], [75, 76], [75, 77], [77, 78], [77, 80], [88, 89], [88, 99], [96, 97], [96, 99], [99, -55], [99, 100], [103, 104], [103, 137], [110, 111], [110, 126], [118, -102], [118, 119], [129, -102], [129, 130], [133, -102], [133, 134], [137, -102], [137, 138], [143, 144], [143, 147], [149, 150], [149, 156], [150, 151], [150, 153]], "missing_branches": []}, "": {"executed_lines": [1, 2, 3, 4, 6, 8, 9, 10, 11, 13, 14, 15, 17, 19, 21, 23, 25, 26, 30, 32, 33, 35, 37, 38, 39, 41, 42, 55, 102, 140], "summary": {"covered_lines": 30, "num_statements": 33, "percent_covered": 89.1891891891892, "percent_covered_display": "89", "missing_lines": 3, "excluded_lines": 0, "percent_statements_covered": 90.9090909090909, "percent_statements_covered_display": "91", "num_branches": 4, "num_partial_branches": 1, "covered_branches": 3, "missing_branches": 1, "percent_branches_covered": 75.0, "percent_branches_covered_display": "75"}, "missing_lines": [24, 27, 28], "excluded_lines": [], "start_line": 1, "executed_branches": [[23, 25], [37, -35], [37, 38]], "missing_branches": [[23, 24]]}}}, "custom_components/goecharger_api2/switch.py": {"executed_lines": [1, 2, 4, 5, 6, 7, 8, 10, 11, 12, 14, 17, 35, 36, 37, 38, 40, 42, 43, 44, 46, 47, 48, 50, 52, 53, 55, 56, 58, 60, 61, 63, 64, 66, 68, 69, 70, 72, 73, 74, 76, 78, 79, 81, 82, 84, 86, 87, 88, 89, 91, 92, 93, 94, 95, 96, 97, 98, 100, 102, 103, 104, 105, 107, 108, 109, 110, 111, 113, 114, 115, 116, 118, 119, 121, 122, 124, 126, 128, 130, 131, 133, 134, 135, 137, 138, 140, 141, 143], "summary": {"covered_lines": 89, "num_statements": 101, "percent_covered": 85.23489932885906, "percent_covered_display": "85", "missing_lines": 12, "excluded_lines": 0, "percent_statements_covered": 88.11881188118812, "percent_statements_covered_display": "88", "num_branches": 48, "num_partial_branches": 2, "covered_branches": 38, "missing_branches": 10, "percent_branches_covered": 79.16666666666667, "percent_branches_covered_display": "79"}, "missing_lines": [18, 19, 20, 22, 23, 24, 25, 26, 28, 29, 30, 32], "excluded_lines": [], "executed_branches": [[43, 44], [43, 55], [44, 46], [44, 52], [47, 48], [47, 50], [55, 56], [55, 58], [69, 70], [69, 81], [70, 72], [70, 78], [73, 74], [73, 76], [81, 82], [81, 84], [95, 96], [96, 97], [96, 102], [97, 98], [97, 100], [102, 103], [104, 105], [104, 113], [113, 114], [113, 128], [114, 115], [114, 119], [115, 116], [115, 118], [119, 121], [119, 126], [121, 122], [121, 124], [133, 134], [133, 135], [140, 141], [140, 143]], "missing_branches": [[22, 23], [22, 28], [23, 24], [23, 32], [24, 23], [24, 25], [28, 29], [28, 32], [95, 113], [102, 104]], "functions": {"async_setup_entry": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 12, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 12, "excluded_lines": 0, "percent_statements_covered": 0.0, "percent_statements_covered_display": "0", "num_branches": 8, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 8, "percent_branches_covered": 0.0, "percent_branches_covered_display": "0"}, "missing_lines": [18, 19, 20, 22, 23, 24, 25, 26, 28, 29, 30, 32], "excluded_lines": [], "start_line": 17, "executed_branches": [], "missing_branches": [[22, 23], [22, 28], [23, 24], [23, 32], [24, 23], [24, 25], [28, 29], [28, 32]]}, "GoeChargerSwitch.__init__": {"executed_lines": [37, 38], "summary": {"covered_lines": 2, "num_statements": 2, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 36, "executed_branches": [], "missing_branches": []}, "GoeChargerSwitch.async_turn_on": {"executed_lines": [42, 43, 44, 46, 47, 48, 50, 52, 53, 55, 56, 58, 60, 61, 63, 64], "summary": {"covered_lines": 16, "num_statements": 16, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 8, "num_partial_branches": 0, "covered_branches": 8, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 40, "executed_branches": [[43, 44], [43, 55], [44, 46], [44, 52], [47, 48], [47, 50], [55, 56], [55, 58]], "missing_branches": []}, "GoeChargerSwitch.async_turn_off": {"executed_lines": [68, 69, 70, 72, 73, 74, 76, 78, 79, 81, 82, 84, 86, 87, 88, 89], "summary": {"covered_lines": 16, "num_statements": 16, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 8, "num_partial_branches": 0, "covered_branches": 8, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 66, "executed_branches": [[69, 70], [69, 81], [70, 72], [70, 78], [73, 74], [73, 76], [81, 82], [81, 84]], "missing_branches": []}, "GoeChargerSwitch.is_on": {"executed_lines": [93, 94, 95, 96, 97, 98, 100, 102, 103, 104, 105, 107, 108, 109, 110, 111, 113, 114, 115, 116, 118, 119, 121, 122, 124, 126, 128], "summary": {"covered_lines": 27, "num_statements": 27, "percent_covered": 95.74468085106383, "percent_covered_display": "96", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 20, "num_partial_branches": 2, "covered_branches": 18, "missing_branches": 2, "percent_branches_covered": 90.0, "percent_branches_covered_display": "90"}, "missing_lines": [], "excluded_lines": [], "start_line": 92, "executed_branches": [[95, 96], [96, 97], [96, 102], [97, 98], [97, 100], [102, 103], [104, 105], [104, 113], [113, 114], [113, 128], [114, 115], [114, 119], [115, 116], [115, 118], [119, 121], [119, 126], [121, 122], [121, 124]], "missing_branches": [[95, 113], [102, 104]]}, "GoeChargerSwitch.state": {"executed_lines": [133, 134, 135], "summary": {"covered_lines": 3, "num_statements": 3, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 2, "num_partial_branches": 0, "covered_branches": 2, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 131, "executed_branches": [[133, 134], [133, 135]], "missing_branches": []}, "GoeChargerSwitch.icon": {"executed_lines": [140, 141, 143], "summary": {"covered_lines": 3, "num_statements": 3, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 2, "num_partial_branches": 0, "covered_branches": 2, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 138, "executed_branches": [[140, 141], [140, 143]], "missing_branches": []}, "": {"executed_lines": [1, 2, 4, 5, 6, 7, 8, 10, 11, 12, 14, 17, 35, 36, 40, 66, 91, 92, 130, 131, 137, 138], "summary": {"covered_lines": 22, "num_statements": 22, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 1, "executed_branches": [], "missing_branches": []}}, "classes": {"GoeChargerSwitch": {"executed_lines": [37, 38, 42, 43, 44, 46, 47, 48, 50, 52, 53, 55, 56, 58, 60, 61, 63, 64, 68, 69, 70, 72, 73, 74, 76, 78, 79, 81, 82, 84, 86, 87, 88, 89, 93, 94, 95, 96, 97, 98, 100, 102, 103, 104, 105, 107, 108, 109, 110, 111, 113, 114, 115, 116, 118, 119, 121, 122, 124, 126, 128, 133, 134, 135, 140, 141, 143], "summary": {"covered_lines": 67, "num_statements": 67, "percent_covered": 98.13084112149532, "percent_covered_display": "98", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 40, "num_partial_branches": 2, "covered_branches": 38, "missing_branches": 2, "percent_branches_covered": 95.0, "percent_branches_covered_display": "95"}, "missing_lines": [], "excluded_lines": [], "start_line": 35, "executed_branches": [[43, 44], [43, 55], [44, 46], [44, 52], [47, 48], [47, 50], [55, 56], [55, 58], [69, 70], [69, 81], [70, 72], [70, 78], [73, 74], [73, 76], [81, 82], [81, 84], [95, 96], [96, 97], [96, 102], [97, 98], [97, 100], [102, 103], [104, 105], [104, 113], [113, 114], [113, 128], [114, 115], [114, 119], [115, 116], [115, 118], [119, 121], [119, 126], [121, 122], [121, 124], [133, 134], [133, 135], [140, 141], [140, 143]], "missing_branches": [[95, 113], [102, 104]]}, "": {"executed_lines": [1, 2, 4, 5, 6, 7, 8, 10, 11, 12, 14, 17, 35, 36, 40, 66, 91, 92, 130, 131, 137, 138], "summary": {"covered_lines": 22, "num_statements": 34, "percent_covered": 52.38095238095238, "percent_covered_display": "52", "missing_lines": 12, "excluded_lines": 0, "percent_statements_covered": 64.70588235294117, "percent_statements_covered_display": "65", "num_branches": 8, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 8, "percent_branches_covered": 0.0, "percent_branches_covered_display": "0"}, "missing_lines": [18, 19, 20, 22, 23, 24, 25, 26, 28, 29, 30, 32], "excluded_lines": [], "start_line": 1, "executed_branches": [], "missing_branches": [[22, 23], [22, 28], [23, 24], [23, 32], [24, 23], [24, 25], [28, 29], [28, 32]]}}}}, "totals": {"covered_lines": 2349, "num_statements": 2529, "percent_covered": 90.78527962909301, "percent_covered_display": "91", "missing_lines": 180, "excluded_lines": 0, "percent_statements_covered": 92.88256227758008, "percent_statements_covered_display": "93", "num_branches": 922, "num_partial_branches": 60, "covered_branches": 784, "missing_branches": 138, "percent_branches_covered": 85.03253796095444, "percent_branches_covered_display": "85"}} \ No newline at end of file diff --git a/tests/reports/2026-10-29/junit-all.xml b/tests/reports/2026-10-29/junit-all.xml new file mode 100644 index 0000000..4aeaa40 --- /dev/null +++ b/tests/reports/2026-10-29/junit-all.xml @@ -0,0 +1,416 @@ +key = 'cco' + + @pytest.mark.parametrize("key", _cases("writeable")) + def test_write_flag_matches_documentation(key): + documented = DOC[key]["rw"] in ("R/W", "W") +> assert TAGS[key].writeable is documented, f"documented {DOC[key]['rw']!r}, keys.py writeable={TAGS[key].writeable}" +E AssertionError: documented 'R/W', keys.py writeable=False +E assert False is True +E + where False = <Tag.CCO: ApiKey(key='cco', cat=<CAT.CONFIG: 'CONF'>, writeable=False, writeonly=False)>.writeable + +tests/integration/test_api_contract.py:101: AssertionErrorkey = 'di1' + + @pytest.mark.parametrize("key", _cases("writeable")) + def test_write_flag_matches_documentation(key): + documented = DOC[key]["rw"] in ("R/W", "W") +> assert TAGS[key].writeable is documented, f"documented {DOC[key]['rw']!r}, keys.py writeable={TAGS[key].writeable}" +E AssertionError: documented 'R/W', keys.py writeable=False +E assert False is True +E + where False = <Tag.DI1: ApiKey(key='di1', cat=<CAT.STATUS: 'STAT'>, writeable=False, writeonly=False)>.writeable + +tests/integration/test_api_contract.py:101: AssertionErrorkey = 'esk' + + @pytest.mark.parametrize("key", _cases("writeable")) + def test_write_flag_matches_documentation(key): + documented = DOC[key]["rw"] in ("R/W", "W") +> assert TAGS[key].writeable is documented, f"documented {DOC[key]['rw']!r}, keys.py writeable={TAGS[key].writeable}" +E AssertionError: documented 'R/W', keys.py writeable=False +E assert False is True +E + where False = <Tag.ESK: ApiKey(key='esk', cat=<CAT.CONFIG: 'CONF'>, writeable=False, writeonly=False)>.writeable + +tests/integration/test_api_contract.py:101: AssertionErrorkey = 'frm' + + @pytest.mark.parametrize("key", _cases("writeable")) + def test_write_flag_matches_documentation(key): + documented = DOC[key]["rw"] in ("R/W", "W") +> assert TAGS[key].writeable is documented, f"documented {DOC[key]['rw']!r}, keys.py writeable={TAGS[key].writeable}" +E AssertionError: documented 'R', keys.py writeable=True +E assert True is False +E + where True = <Tag.FRM: ApiKey(key='frm', cat=<CAT.CONFIG: 'CONF'>, writeable=True, writeonly=False)>.writeable + +tests/integration/test_api_contract.py:101: AssertionErrorkey = 'rdbf' + + @pytest.mark.parametrize("key", _cases("writeable")) + def test_write_flag_matches_documentation(key): + documented = DOC[key]["rw"] in ("R/W", "W") +> assert TAGS[key].writeable is documented, f"documented {DOC[key]['rw']!r}, keys.py writeable={TAGS[key].writeable}" +E AssertionError: documented 'R/W', keys.py writeable=False +E assert False is True +E + where False = <Tag.RDBF: ApiKey(key='rdbf', cat=<CAT.CONFIG: 'CONF'>, writeable=False, writeonly=False)>.writeable + +tests/integration/test_api_contract.py:101: AssertionErrorkey = 'rdbs' + + @pytest.mark.parametrize("key", _cases("writeable")) + def test_write_flag_matches_documentation(key): + documented = DOC[key]["rw"] in ("R/W", "W") +> assert TAGS[key].writeable is documented, f"documented {DOC[key]['rw']!r}, keys.py writeable={TAGS[key].writeable}" +E AssertionError: documented 'R/W', keys.py writeable=False +E assert False is True +E + where False = <Tag.RDBS: ApiKey(key='rdbs', cat=<CAT.CONFIG: 'CONF'>, writeable=False, writeonly=False)>.writeable + +tests/integration/test_api_contract.py:101: AssertionErrorkey = 'rdef' + + @pytest.mark.parametrize("key", _cases("writeable")) + def test_write_flag_matches_documentation(key): + documented = DOC[key]["rw"] in ("R/W", "W") +> assert TAGS[key].writeable is documented, f"documented {DOC[key]['rw']!r}, keys.py writeable={TAGS[key].writeable}" +E AssertionError: documented 'R/W', keys.py writeable=False +E assert False is True +E + where False = <Tag.RDEF: ApiKey(key='rdef', cat=<CAT.CONFIG: 'CONF'>, writeable=False, writeonly=False)>.writeable + +tests/integration/test_api_contract.py:101: AssertionErrorkey = 'rdes' + + @pytest.mark.parametrize("key", _cases("writeable")) + def test_write_flag_matches_documentation(key): + documented = DOC[key]["rw"] in ("R/W", "W") +> assert TAGS[key].writeable is documented, f"documented {DOC[key]['rw']!r}, keys.py writeable={TAGS[key].writeable}" +E AssertionError: documented 'R/W', keys.py writeable=False +E assert False is True +E + where False = <Tag.RDES: ApiKey(key='rdes', cat=<CAT.CONFIG: 'CONF'>, writeable=False, writeonly=False)>.writeable + +tests/integration/test_api_contract.py:101: AssertionErrorkey = 'rdpl' + + @pytest.mark.parametrize("key", _cases("writeable")) + def test_write_flag_matches_documentation(key): + documented = DOC[key]["rw"] in ("R/W", "W") +> assert TAGS[key].writeable is documented, f"documented {DOC[key]['rw']!r}, keys.py writeable={TAGS[key].writeable}" +E AssertionError: documented 'R/W', keys.py writeable=False +E assert False is True +E + where False = <Tag.RDPL: ApiKey(key='rdpl', cat=<CAT.CONFIG: 'CONF'>, writeable=False, writeonly=False)>.writeable + +tests/integration/test_api_contract.py:101: AssertionErrorkey = 'rdre' + + @pytest.mark.parametrize("key", _cases("writeable")) + def test_write_flag_matches_documentation(key): + documented = DOC[key]["rw"] in ("R/W", "W") +> assert TAGS[key].writeable is documented, f"documented {DOC[key]['rw']!r}, keys.py writeable={TAGS[key].writeable}" +E AssertionError: documented 'R/W', keys.py writeable=False +E assert False is True +E + where False = <Tag.RDRE: ApiKey(key='rdre', cat=<CAT.CONFIG: 'CONF'>, writeable=False, writeonly=False)>.writeable + +tests/integration/test_api_contract.py:101: AssertionErrorkey = 'deltaa' + + @pytest.mark.parametrize("key", _cases("category")) + def test_category_matches_documentation(key): +> assert TAGS[key].cat == CATEGORY[DOC[key]["category"]], \ + f"documented {DOC[key]['category']!r}, keys.py {TAGS[key].cat.name}" +E AssertionError: documented 'Other', keys.py STATUS +E assert <CAT.STATUS: 'STAT'> == <CAT.OTHER: 'OTHE'> +E + where <CAT.STATUS: 'STAT'> = <Tag.DELTAA: ApiKey(key='deltaa', cat=<CAT.STATUS: 'STAT'>, writeable=False, writeonly=False)>.cat + +tests/integration/test_api_contract.py:106: AssertionErrorkey = 'di1' + + @pytest.mark.parametrize("key", _cases("category")) + def test_category_matches_documentation(key): +> assert TAGS[key].cat == CATEGORY[DOC[key]["category"]], \ + f"documented {DOC[key]['category']!r}, keys.py {TAGS[key].cat.name}" +E AssertionError: documented 'Config', keys.py STATUS +E assert <CAT.STATUS: 'STAT'> == <CAT.CONFIG: 'CONF'> +E + where <CAT.STATUS: 'STAT'> = <Tag.DI1: ApiKey(key='di1', cat=<CAT.STATUS: 'STAT'>, writeable=False, writeonly=False)>.cat + +tests/integration/test_api_contract.py:106: AssertionErrorkey = 'ids' + + @pytest.mark.parametrize("key", _cases("category")) + def test_category_matches_documentation(key): +> assert TAGS[key].cat == CATEGORY[DOC[key]["category"]], \ + f"documented {DOC[key]['category']!r}, keys.py {TAGS[key].cat.name}" +E AssertionError: documented 'Other', keys.py STATUS +E assert <CAT.STATUS: 'STAT'> == <CAT.OTHER: 'OTHE'> +E + where <CAT.STATUS: 'STAT'> = <Tag.IDS: ApiKey(key='ids', cat=<CAT.STATUS: 'STAT'>, writeable=True, writeonly=True)>.cat + +tests/integration/test_api_contract.py:106: AssertionErrorkey = 'lcs' + + @pytest.mark.parametrize("key", _cases("category")) + def test_category_matches_documentation(key): +> assert TAGS[key].cat == CATEGORY[DOC[key]["category"]], \ + f"documented {DOC[key]['category']!r}, keys.py {TAGS[key].cat.name}" +E AssertionError: documented 'Status', keys.py CONFIG +E assert <CAT.CONFIG: 'CONF'> == <CAT.STATUS: 'STAT'> +E + where <CAT.CONFIG: 'CONF'> = <Tag.LCS: ApiKey(key='lcs', cat=<CAT.CONFIG: 'CONF'>, writeable=False, writeonly=False)>.cat + +tests/integration/test_api_contract.py:106: AssertionErrorhass = <HomeAssistant RUNNING> +aioclient_mock = <pytest_homeassistant_custom_component.test_util.aiohttp.AiohttpClientMocker object at 0x10dd2cc50> + + @pytest.mark.known_bug(( + "http-en.md: the answer holds 'true for success, or a string error message' per key. The bridge " + "tests the value for truthiness, so an error string counts as success. Not verified on a real charger.")) + async def test_write_rejected_by_charger_returns_err(hass, aioclient_mock): + aioclient_mock.get(LAN_SET, json={"amp": "value out of range"}) + result = await _lan(hass).write_value_to_key("amp", 99) +> assert result == {"err": {"amp": "value out of range"}} +E AssertionError: assert {'amp': 99} == {'err': {'amp...ut of range'}} +E +E Left contains 1 more item: +E {'amp': 99} +E Right contains 1 more item: +E {'err': {'amp': 'value out of range'}} +E Use -v to get more diff + +tests/integration/test_bridge_http.py:104: AssertionErrorhass = <HomeAssistant RUNNING> +aioclient_mock = <pytest_homeassistant_custom_component.test_util.aiohttp.AiohttpClientMocker object at 0x10dfaf390> + + @pytest.mark.known_bug(( + "Official cloud docs: /api/set answers HTTP 202 on success; the bridge only accepts 200. " + "Not verified against the real cloud. Remove the marker when the behaviour is clarified.")) + async def test_cloud_write_accepts_http_202(hass, aioclient_mock): + aioclient_mock.get("https://001234.api.v3.go-e.io/api/set", status=202, json={"amp": True}) +> assert await _cloud(hass).write_value_to_key("amp", 10) == {"amp": 10} +E AssertionError: assert {} == {'amp': 10} +E +E Right contains 1 more item: +E {'amp': 10} +E Use -v to get more diff + +tests/integration/test_bridge_http.py:127: AssertionErrorsvc = (<custom_components.goecharger_api2.service.GoeChargerApiV2Service object at 0x10f9e6e90>, namespace(intg_type='charger', mode='lan', name='test charger', _serial='123456', data={}, async_write_key=<AsyncMock id='4544635168'>)) +bad = nan + + @pytest.mark.known_bug("service.py checks pgrid with isinstance(Number) only; NaN and inf pass") + @pytest.mark.parametrize("bad", [math.nan, math.inf, -math.inf]) + async def test_non_finite_grid_value_is_rejected(svc, bad): + service, coordinator = svc + result = await service.set_pv_data(_call(pgrid=bad, ppv=1500)) +> assert "error" in result +E AssertionError: assert 'error' in {'date': '11:48:18.464227', 'response': {'ids': True}, 'success': 'true'} + +tests/integration/test_service.py:41: AssertionErrorsvc = (<custom_components.goecharger_api2.service.GoeChargerApiV2Service object at 0x10f9e5e50>, namespace(intg_type='charger', mode='lan', name='test charger', _serial='123456', data={}, async_write_key=<AsyncMock id='4532624688'>)) +bad = inf + + @pytest.mark.known_bug("service.py checks pgrid with isinstance(Number) only; NaN and inf pass") + @pytest.mark.parametrize("bad", [math.nan, math.inf, -math.inf]) + async def test_non_finite_grid_value_is_rejected(svc, bad): + service, coordinator = svc + result = await service.set_pv_data(_call(pgrid=bad, ppv=1500)) +> assert "error" in result +E AssertionError: assert 'error' in {'date': '11:48:18.477353', 'response': {'ids': True}, 'success': 'true'} + +tests/integration/test_service.py:41: AssertionErrorsvc = (<custom_components.goecharger_api2.service.GoeChargerApiV2Service object at 0x10dc3d6e0>, namespace(intg_type='charger', mode='lan', name='test charger', _serial='123456', data={}, async_write_key=<AsyncMock id='4550494928'>)) +bad = -inf + + @pytest.mark.known_bug("service.py checks pgrid with isinstance(Number) only; NaN and inf pass") + @pytest.mark.parametrize("bad", [math.nan, math.inf, -math.inf]) + async def test_non_finite_grid_value_is_rejected(svc, bad): + service, coordinator = svc + result = await service.set_pv_data(_call(pgrid=bad, ppv=1500)) +> assert "error" in result +E AssertionError: assert 'error' in {'date': '11:48:18.491423', 'response': {'ids': True}, 'success': 'true'} + +tests/integration/test_service.py:41: AssertionErrorhass = <HomeAssistant RUNNING> +config_entry = <ConfigEntry entry_id=01M63MCRDA7KCWJETHAYFQX6ZB version=1 domain=goecharger_api2 title=go-e 123456 state=ConfigEntryState.NOT_LOADED unique_id=None> + + async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry): + if DOMAIN not in hass.data: + the_integration = await async_get_integration(hass, DOMAIN) + intg_version = the_integration.version if the_integration is not None else "UNKNOWN" + _LOGGER.info(STARTUP_MESSAGE % intg_version) + hass.data.setdefault(DOMAIN, {"manifest_version": intg_version}) + + # ok starting the init sequence... + lang = hass.config.language.lower() + if lang in TRANSLATIONS: + lang_map = TRANSLATIONS[lang] + else: + lang_map = TRANSLATIONS["en"] + + coordinator = GoeChargerDataUpdateCoordinator(hass, config_entry) + a_pwd = config_entry.data.get(CONF_PASSWORD, None) + if a_pwd is not None and len(a_pwd.strip()) > 0: + use_websocket = True + try: + if not await coordinator.start_websocket_and_wait_for_first_data(is_integration_init=True): + _LOGGER.warning(f"async_setup_entry(): The coordinator.start_websocket_and_wait_for_first_data() as returned FALSE") +> raise ConfigEntryNotReady(lang_map["coord_no_device_data"]) +E homeassistant.exceptions.ConfigEntryNotReady: Coordinator could not fetch essential information from your configured device. Please check your Home Assistant system log for possible error messages. + +custom_components/goecharger_api2/__init__.py:129: ConfigEntryNotReady + +During handling of the above exception, another exception occurred: + +hass = <HomeAssistant RUNNING> +setup_patches = <integration.test_services_and_setup.SimpleNamespace_ object at 0x10eb0bdf0> + + @pytest.mark.known_bug("the specific message 'could not fetch essential information' is swallowed by the broad except and replaced by the generic websocket message (__init__.py:131)") + async def test_missing_device_data_is_reported_with_its_own_message(hass, setup_patches): + from homeassistant.exceptions import ConfigEntryNotReady + entry = make_entry(hass, password="secret") + setup_patches.ws_start.return_value = False + with pytest.raises(ConfigEntryNotReady, match="could not fetch essential"): +> await _setup(hass, entry) + +tests/integration/test_services_and_setup.py:303: +_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ +tests/integration/test_services_and_setup.py:228: in _setup + result = await async_setup_entry(hass, entry) + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ + +hass = <HomeAssistant RUNNING> +config_entry = <ConfigEntry entry_id=01M63MCRDA7KCWJETHAYFQX6ZB version=1 domain=goecharger_api2 title=go-e 123456 state=ConfigEntryState.NOT_LOADED unique_id=None> + + async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry): + if DOMAIN not in hass.data: + the_integration = await async_get_integration(hass, DOMAIN) + intg_version = the_integration.version if the_integration is not None else "UNKNOWN" + _LOGGER.info(STARTUP_MESSAGE % intg_version) + hass.data.setdefault(DOMAIN, {"manifest_version": intg_version}) + + # ok starting the init sequence... + lang = hass.config.language.lower() + if lang in TRANSLATIONS: + lang_map = TRANSLATIONS[lang] + else: + lang_map = TRANSLATIONS["en"] + + coordinator = GoeChargerDataUpdateCoordinator(hass, config_entry) + a_pwd = config_entry.data.get(CONF_PASSWORD, None) + if a_pwd is not None and len(a_pwd.strip()) > 0: + use_websocket = True + try: + if not await coordinator.start_websocket_and_wait_for_first_data(is_integration_init=True): + _LOGGER.warning(f"async_setup_entry(): The coordinator.start_websocket_and_wait_for_first_data() as returned FALSE") + raise ConfigEntryNotReady(lang_map["coord_no_device_data"]) + + except BaseException as exc: + _LOGGER.error(f"async_setup_entry(): Error starting websocket connection: {type(exc).__name__} - {exc}", stack_info=True) +> raise ConfigEntryNotReady(lang_map["websocket_start_failed"]) +E homeassistant.exceptions.ConfigEntryNotReady: The Websocket could not be started within one minute. Please check your Home Assistant system log for possible error messages. + +custom_components/goecharger_api2/__init__.py:133: ConfigEntryNotReady + +During handling of the above exception, another exception occurred: + +hass = <HomeAssistant RUNNING> +setup_patches = <integration.test_services_and_setup.SimpleNamespace_ object at 0x10eb0bdf0> + + @pytest.mark.known_bug("the specific message 'could not fetch essential information' is swallowed by the broad except and replaced by the generic websocket message (__init__.py:131)") + async def test_missing_device_data_is_reported_with_its_own_message(hass, setup_patches): + from homeassistant.exceptions import ConfigEntryNotReady + entry = make_entry(hass, password="secret") + setup_patches.ws_start.return_value = False +> with pytest.raises(ConfigEntryNotReady, match="could not fetch essential"): + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +E AssertionError: Regex pattern did not match. +E Expected regex: 'could not fetch essential' +E Actual message: 'The Websocket could not be started within one minute. Please check your Home Assistant system log for possible error messages.' + +tests/integration/test_services_and_setup.py:302: AssertionErrorhass = <HomeAssistant RUNNING> + + @pytest.mark.known_bug("stop_charging does not reset _stop_in_progress when the first write fails (service.py)") + async def test_failed_lock_write_blocks_later_stop_requests(hass): + """a ValueError on the first write leaves _stop_in_progress True forever.""" + coordinator = _coordinator(hass, _entry(hass), websocket=True) + service = GoeChargerApiV2Service(hass, coordinator._config_entry, coordinator) + coordinator.async_write_key = AsyncMock(side_effect=ValueError("charger unreachable")) + await service.stop_charging(_call()) +> assert service._stop_in_progress is False +E assert True is False +E + where True = <custom_components.goecharger_api2.service.GoeChargerApiV2Service object at 0x10f3fe930>._stop_in_progress + +tests/integration/test_services_and_unload.py:88: AssertionErrorhass = <HomeAssistant RUNNING> + + @pytest.mark.known_bug("stop_charging does not reset _stop_in_progress when the unlock write fails (service.py)") + async def test_failed_unlock_write_leaves_flag_stuck(hass): + """if frc=0 cannot be written after the wait, the charger stays locked + and the flag blocks every later stop_charging call.""" + coordinator = _coordinator(hass, _entry(hass), websocket=True) + service = GoeChargerApiV2Service(hass, coordinator._config_entry, coordinator) + coordinator.async_write_key = AsyncMock(side_effect=[{Tag.FRC.key: 1}, ValueError("down")]) + with patch(SERVICE_SLEEP, AsyncMock()): + await service.stop_charging(_call()) +> assert service._stop_in_progress is False +E assert True is False +E + where True = <custom_components.goecharger_api2.service.GoeChargerApiV2Service object at 0x10f7b6db0>._stop_in_progress + +tests/integration/test_services_and_unload.py:100: AssertionErrorhass = <HomeAssistant RUNNING> +caplog = <_pytest.logging.LogCaptureFixture object at 0x10dc3d6e0> + + @pytest.mark.known_bug("write_multiple_values_to_keys returns {} over WebSocket; handle_write_result only skips on None") + async def test_websocket_multi_write_logs_no_error_on_success(hass, caplog): + """a successful multi-key write in WebSocket mode is logged as an ERROR.""" + coordinator = _coordinator(hass, _entry(hass), websocket=True) + with patch(BRIDGE_SLEEP, AsyncMock()), caplog.at_level(logging.ERROR): + await coordinator.async_write_multiple_keys({"ct": "1", "mca": 6}, "ct", "default") + assert [k for k, _ in _sent(coordinator)] == ["ct", "mca"] + errors = [r.message for r in caplog.records if r.levelno >= logging.ERROR] +> assert errors == [] +E assert ['handle_writ...sult was: {}'] == [] +E +E Left contains one more item: "handle_write_result() could not write multiple value: 'default' to: ct result was: {}" +E Use -v to get more diff + +tests/integration/test_services_and_unload.py:113: AssertionErrorhass = <HomeAssistant RUNNING> + + @pytest.mark.known_bug("a reloaded entry is appended behind the others, so the default charger changes") + async def test_default_charger_does_not_change_after_reload(hass): + """without configid the first registered charger is used. After A is reloaded it + is appended behind B, so the default silently switches from A to B.""" + a = _coordinator(hass, _entry(hass, "111111"), websocket=False) + b = _coordinator(hass, _entry(hass, "222222"), websocket=False) + service = GoeChargerApiV2Service(hass, a._config_entry, a) + add_config_entry_to_service(b._config_entry, b) + _, before = service.get_coordinator(_call()) + + remove_config_entry_from_service(a._config_entry) + add_config_entry_to_service(a._config_entry, a) + _, after = service.get_coordinator(_call()) +> assert after is before +E assert <custom_components.goecharger_api2.GoeChargerDataUpdateCoordinator object at 0x10ffb11d0> is <custom_components.goecharger_api2.GoeChargerDataUpdateCoordinator object at 0x10ffb0e10> + +tests/integration/test_services_and_unload.py:162: AssertionErrorhass = <HomeAssistant RUNNING> + + @pytest.mark.known_bug("ACTIVE_CHARGER_COUNTER is raised for chargers only but lowered for every entry") + async def test_unloading_a_controller_keeps_the_charger_services(hass): + """the active-charger counter is only raised for chargers but lowered for every + entry, so unloading a controller removes the services of a still-running charger.""" + controller = _entry(hass, "999999", intg_type="controller") + hass.data[DOMAIN] = {controller.entry_id: MagicMock(), ACTIVE_CHARGER_COUNTER: 1} # one charger + _register_services(hass) + with patch.object(hass.config_entries, "async_unload_platforms", AsyncMock(return_value=True)): + await async_unload_entry(hass, controller) +> assert hass.services.has_service(DOMAIN, SERVICE_SET_PV_DATA) +E AssertionError: assert False +E + where False = has_service('goecharger_api2', 'set_pv_data') +E + where has_service = <homeassistant.core.ServiceRegistry object at 0x1100c3af0>.has_service +E + where <homeassistant.core.ServiceRegistry object at 0x1100c3af0> = <HomeAssistant RUNNING>.services + +tests/integration/test_services_and_unload.py:183: AssertionErrorhass = <HomeAssistant RUNNING> + + @pytest.mark.known_bug("ACTIVE_CHARGER_COUNTER key does not exist when only controllers are configured") + async def test_unloading_a_controller_without_any_charger_does_not_raise(hass): + """with only controllers configured the counter key does not exist.""" + controller = _entry(hass, "999999", intg_type="controller") + hass.data[DOMAIN] = {controller.entry_id: MagicMock()} + with patch.object(hass.config_entries, "async_unload_platforms", AsyncMock(return_value=True)): +> assert await async_unload_entry(hass, controller) is True + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +tests/integration/test_services_and_unload.py:192: +_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ + +hass = <HomeAssistant RUNNING> +config_entry = <ConfigEntry entry_id=01M63MCRT0KH5RDX17D1NGX058 version=1 domain=goecharger_api2 title=go-e 999999 state=ConfigEntryState.NOT_LOADED unique_id=None> + + async def async_unload_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> bool: + unload_ok = await hass.config_entries.async_unload_platforms(config_entry, PLATFORMS) + + if unload_ok: + if DOMAIN in hass.data and config_entry.entry_id in hass.data[DOMAIN]: + coordinator = hass.data[DOMAIN][config_entry.entry_id] + coordinator.cancel_existing_debouncer() + coordinator.stop_watchdog() + coordinator.clear_data() + hass.data[DOMAIN].pop(config_entry.entry_id) +> hass.data[DOMAIN][ACTIVE_CHARGER_COUNTER] = hass.data[DOMAIN][ACTIVE_CHARGER_COUNTER] - 1 + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +E KeyError: 'active_charger_counter' + +custom_components/goecharger_api2/__init__.py:197: KeyError \ No newline at end of file diff --git a/tests/reports/2026-10-29/pathtrace-all.json b/tests/reports/2026-10-29/pathtrace-all.json new file mode 100644 index 0000000..be8abfc --- /dev/null +++ b/tests/reports/2026-10-29/pathtrace-all.json @@ -0,0 +1,638 @@ +{ + "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/pygoecharger_ha/const.py::INTG_TYPE": { + "calls": 1, + "distinct_paths": 1 + }, + "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/pygoecharger_ha/const.py::CAR_VALUES": { + "calls": 1, + "distinct_paths": 1 + }, + "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/pygoecharger_ha/const.py::CT_VALUES": { + "calls": 1, + "distinct_paths": 1 + }, + "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/pygoecharger_ha/const.py::": { + "calls": 1, + "distinct_paths": 1 + }, + "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/pygoecharger_ha/keys.py::CAT": { + "calls": 1, + "distinct_paths": 1 + }, + "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/pygoecharger_ha/keys.py::ApiKey": { + "calls": 1, + "distinct_paths": 1 + }, + "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/pygoecharger_ha/keys.py::ApiKey.__annotate__": { + "calls": 2, + "distinct_paths": 2 + }, + "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/pygoecharger_ha/keys.py::Tag": { + "calls": 1, + "distinct_paths": 1 + }, + "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/pygoecharger_ha/keys.py::": { + "calls": 1, + "distinct_paths": 1 + }, + "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/pygoecharger_ha/__init__.py::TargetedEvent": { + "calls": 1, + "distinct_paths": 1 + }, + "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/pygoecharger_ha/__init__.py::NonNullDict": { + "calls": 1, + "distinct_paths": 1 + }, + "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/pygoecharger_ha/__init__.py::ExpiringStore": { + "calls": 1, + "distinct_paths": 1 + }, + "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/pygoecharger_ha/__init__.py::GoeChargerApiV2Bridge": { + "calls": 1, + "distinct_paths": 1 + }, + "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/pygoecharger_ha/__init__.py::": { + "calls": 1, + "distinct_paths": 1 + }, + "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/const.py::ExtBinarySensorEntityDescription": { + "calls": 1, + "distinct_paths": 1 + }, + "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/const.py::ExtBinarySensorEntityDescription.__annotate__": { + "calls": 1, + "distinct_paths": 1 + }, + "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/const.py::ExtButtonEntityDescription": { + "calls": 1, + "distinct_paths": 1 + }, + "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/const.py::ExtButtonEntityDescription.__annotate__": { + "calls": 1, + "distinct_paths": 1 + }, + "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/const.py::ExtNumberEntityDescription": { + "calls": 1, + "distinct_paths": 1 + }, + "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/const.py::ExtNumberEntityDescription.__annotate__": { + "calls": 1, + "distinct_paths": 1 + }, + "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/const.py::ExtSelectEntityDescription": { + "calls": 1, + "distinct_paths": 1 + }, + "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/const.py::ExtSelectEntityDescription.__annotate__": { + "calls": 1, + "distinct_paths": 1 + }, + "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/const.py::ExtSensorEntityDescription": { + "calls": 1, + "distinct_paths": 1 + }, + "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/const.py::ExtSensorEntityDescription.__annotate__": { + "calls": 1, + "distinct_paths": 1 + }, + "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/const.py::ExtSwitchEntityDescription": { + "calls": 1, + "distinct_paths": 1 + }, + "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/const.py::ExtSwitchEntityDescription.__annotate__": { + "calls": 1, + "distinct_paths": 1 + }, + "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/const.py::": { + "calls": 1, + "distinct_paths": 1 + }, + "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/entity.py::CustomFriendlyNameEntity": { + "calls": 1, + "distinct_paths": 1 + }, + "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/entity.py::": { + "calls": 1, + "distinct_paths": 1 + }, + "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/service.py::GoeChargerApiV2Service": { + "calls": 1, + "distinct_paths": 1 + }, + "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/service.py::": { + "calls": 1, + "distinct_paths": 1 + }, + "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/__init__.py::GoeChargerDataUpdateCoordinator": { + "calls": 1, + "distinct_paths": 1 + }, + "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/__init__.py::GoeChargerBaseEntity": { + "calls": 1, + "distinct_paths": 1 + }, + "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/__init__.py::": { + "calls": 1, + "distinct_paths": 1 + }, + "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/config_flow.py::GoeChargerApiV2FlowHandler": { + "calls": 1, + "distinct_paths": 1 + }, + "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/config_flow.py::": { + "calls": 1, + "distinct_paths": 1 + }, + "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/binary_sensor.py::GoeChargerApiV2BinarySensor": { + "calls": 1, + "distinct_paths": 1 + }, + "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/binary_sensor.py::": { + "calls": 1, + "distinct_paths": 1 + }, + "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/button.py::GoeChargerApiV2Button": { + "calls": 1, + "distinct_paths": 1 + }, + "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/button.py::": { + "calls": 1, + "distinct_paths": 1 + }, + "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/select.py::GoeChargerSelect": { + "calls": 1, + "distinct_paths": 1 + }, + "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/select.py::": { + "calls": 1, + "distinct_paths": 1 + }, + "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/switch.py::GoeChargerSwitch": { + "calls": 1, + "distinct_paths": 1 + }, + "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/switch.py::": { + "calls": 1, + "distinct_paths": 1 + }, + "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/number.py::GoeChargerNumber": { + "calls": 1, + "distinct_paths": 1 + }, + "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/number.py::": { + "calls": 1, + "distinct_paths": 1 + }, + "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/sensor.py::GoeChargerSensor": { + "calls": 1, + "distinct_paths": 1 + }, + "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/sensor.py::": { + "calls": 1, + "distinct_paths": 1 + }, + "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/pygoecharger_ha/__init__.py::GoeChargerApiV2Bridge.__init__": { + "calls": 466, + "distinct_paths": 11 + }, + "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/pygoecharger_ha/__init__.py::ExpiringStore.__init__": { + "calls": 466, + "distinct_paths": 1 + }, + "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/pygoecharger_ha/__init__.py::GoeChargerApiV2Bridge._write_values_int": { + "calls": 30, + "distinct_paths": 11 + }, + "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/pygoecharger_ha/__init__.py::GoeChargerApiV2Bridge._read_filtered_data": { + "calls": 57, + "distinct_paths": 10 + }, + "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/pygoecharger_ha/__init__.py::NonNullDict.none_null_get": { + "calls": 399, + "distinct_paths": 1 + }, + "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/pygoecharger_ha/__init__.py::GoeChargerApiV2Bridge.write_value_to_key": { + "calls": 36, + "distinct_paths": 11 + }, + "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/pygoecharger_ha/__init__.py::GoeChargerApiV2Bridge.read_all_states": { + "calls": 17, + "distinct_paths": 11 + }, + "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/pygoecharger_ha/__init__.py::GoeChargerApiV2Bridge._read_filtered_data_from_ws_source": { + "calls": 16, + "distinct_paths": 1 + }, + "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/pygoecharger_ha/__init__.py::GoeChargerApiV2Bridge.read_all_config": { + "calls": 8, + "distinct_paths": 5 + }, + "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/pygoecharger_ha/__init__.py::GoeChargerApiV2Bridge.force_config_update": { + "calls": 2, + "distinct_paths": 2 + }, + "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/pygoecharger_ha/__init__.py::GoeChargerApiV2Bridge.read_all": { + "calls": 4, + "distinct_paths": 3 + }, + "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/pygoecharger_ha/__init__.py::GoeChargerApiV2Bridge.read_versions": { + "calls": 18, + "distinct_paths": 9 + }, + "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/pygoecharger_ha/__init__.py::GoeChargerApiV2Bridge.read_system": { + "calls": 20, + "distinct_paths": 3 + }, + "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/pygoecharger_ha/__init__.py::GoeChargerApiV2Bridge._read_all_data": { + "calls": 6, + "distinct_paths": 5 + }, + "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/pygoecharger_ha/__init__.py::GoeChargerApiV2Bridge.clear_data": { + "calls": 4, + "distinct_paths": 2 + }, + "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/pygoecharger_ha/__init__.py::GoeChargerApiV2Bridge.available_fields": { + "calls": 1, + "distinct_paths": 1 + }, + "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/pygoecharger_ha/__init__.py::GoeChargerApiV2Bridge.reset_stored_update_ts": { + "calls": 3, + "distinct_paths": 3 + }, + "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/pygoecharger_ha/__init__.py::GoeChargerApiV2Bridge._ws_notify_for_new_data": { + "calls": 15, + "distinct_paths": 2 + }, + "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/pygoecharger_ha/__init__.py::GoeChargerApiV2Bridge._ws_notify_for_new_data.._ws_debounce_coordinator_update": { + "calls": 12, + "distinct_paths": 4 + }, + "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/pygoecharger_ha/__init__.py::GoeChargerApiV2Bridge.ws_check_last_update": { + "calls": 6, + "distinct_paths": 2 + }, + "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/pygoecharger_ha/__init__.py::GoeChargerApiV2Bridge.ws_close": { + "calls": 25, + "distinct_paths": 3 + }, + "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/pygoecharger_ha/__init__.py::ExpiringStore._cleanup": { + "calls": 22, + "distinct_paths": 2 + }, + "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/pygoecharger_ha/__init__.py::ExpiringStore.set": { + "calls": 14, + "distinct_paths": 1 + }, + "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/pygoecharger_ha/__init__.py::ExpiringStore.get": { + "calls": 6, + "distinct_paths": 2 + }, + "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/pygoecharger_ha/__init__.py::ExpiringStore.__len__": { + "calls": 2, + "distinct_paths": 1 + }, + "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/pygoecharger_ha/__init__.py::GoeChargerApiV2Bridge._ws_send_command": { + "calls": 11, + "distinct_paths": 2 + }, + "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/pygoecharger_ha/__init__.py::GoeChargerApiV2Bridge.write_multiple_values_to_keys": { + "calls": 3, + "distinct_paths": 2 + }, + "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/config_flow.py::GoeChargerApiV2FlowHandler.__init__": { + "calls": 25, + "distinct_paths": 1 + }, + "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/config_flow.py::GoeChargerApiV2FlowHandler.async_step_user": { + "calls": 33, + "distinct_paths": 3 + }, + "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/config_flow.py::GoeChargerApiV2FlowHandler.async_step_user_lan": { + "calls": 27, + "distinct_paths": 5 + }, + "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/config_flow.py::GoeChargerApiV2FlowHandler._test_host": { + "calls": 18, + "distinct_paths": 7 + }, + "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/__init__.py::async_setup": { + "calls": 11, + "distinct_paths": 1 + }, + "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/__init__.py::async_unload_entry": { + "calls": 14, + "distinct_paths": 5 + }, + "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/config_flow.py::GoeChargerApiV2FlowHandler.async_step_user_wan": { + "calls": 11, + "distinct_paths": 5 + }, + "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/config_flow.py::GoeChargerApiV2FlowHandler.async_step_reconfigure": { + "calls": 4, + "distinct_paths": 2 + }, + "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/pygoecharger_ha/__init__.py::TargetedEvent.__init__": { + "calls": 30, + "distinct_paths": 2 + }, + "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/pygoecharger_ha/__init__.py::TargetedEvent.has_errors": { + "calls": 21, + "distinct_paths": 1 + }, + "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/config_flow.py::GoeChargerApiV2FlowHandler._check_ws_connection_and_flags": { + "calls": 4, + "distinct_paths": 4 + }, + "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/pygoecharger_ha/__init__.py::TargetedEvent.set_error": { + "calls": 15, + "distinct_paths": 1 + }, + "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/__init__.py::GoeChargerDataUpdateCoordinator.__init__": { + "calls": 286, + "distinct_paths": 5 + }, + "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/__init__.py::GoeChargerDataUpdateCoordinator._handle_client_connection_error": { + "calls": 15, + "distinct_paths": 2 + }, + "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/__init__.py::GoeChargerDataUpdateCoordinator.async_write_key": { + "calls": 7, + "distinct_paths": 5 + }, + "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/__init__.py::GoeChargerDataUpdateCoordinator.async_write_multiple_keys": { + "calls": 4, + "distinct_paths": 4 + }, + "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/__init__.py::GoeChargerDataUpdateCoordinator._async_update_data": { + "calls": 19, + "distinct_paths": 5 + }, + "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/__init__.py::async_setup_entry": { + "calls": 17, + "distinct_paths": 11 + }, + "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/__init__.py::GoeChargerDataUpdateCoordinator.handle_write_result": { + "calls": 11, + "distinct_paths": 7 + }, + "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/__init__.py::check_and_write_to_16a": { + "calls": 8, + "distinct_paths": 8 + }, + "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/__init__.py::GoeChargerDataUpdateCoordinator.check_for_16a_limit": { + "calls": 1, + "distinct_paths": 1 + }, + "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/__init__.py::GoeChargerDataUpdateCoordinator.read_versions": { + "calls": 28, + "distinct_paths": 21 + }, + "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/__init__.py::GoeChargerDataUpdateCoordinator.read_versions..": { + "calls": 26, + "distinct_paths": 2 + }, + "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/__init__.py::GoeChargerDataUpdateCoordinator.is_valid_charger_entity": { + "calls": 14, + "distinct_paths": 7 + }, + "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/pygoecharger_ha/__init__.py::GoeChargerApiV2Bridge.ws_set_coordinator": { + "calls": 7, + "distinct_paths": 1 + }, + "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/__init__.py::GoeChargerDataUpdateCoordinator.start_websocket_and_wait_for_first_data": { + "calls": 6, + "distinct_paths": 6 + }, + "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/__init__.py::GoeChargerDataUpdateCoordinator.force_async_update_now": { + "calls": 4, + "distinct_paths": 4 + }, + "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/__init__.py::GoeChargerDataUpdateCoordinator._check_for_ws_task_and_cancel_if_running": { + "calls": 10, + "distinct_paths": 3 + }, + "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/__init__.py::GoeChargerDataUpdateCoordinator._async_watchdog_check": { + "calls": 4, + "distinct_paths": 3 + }, + "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/__init__.py::GoeChargerDataUpdateCoordinator.start_watchdog": { + "calls": 1, + "distinct_paths": 1 + }, + "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/__init__.py::GoeChargerDataUpdateCoordinator.stop_watchdog": { + "calls": 4, + "distinct_paths": 2 + }, + "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/__init__.py::GoeChargerDataUpdateCoordinator.call_later_update_device_registry": { + "calls": 5, + "distinct_paths": 5 + }, + "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/__init__.py::GoeChargerDataUpdateCoordinator.cancel_existing_debouncer": { + "calls": 9, + "distinct_paths": 2 + }, + "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/__init__.py::GoeChargerDataUpdateCoordinator.request_update_in_sec": { + "calls": 3, + "distinct_paths": 1 + }, + "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/__init__.py::GoeChargerDataUpdateCoordinator._debounce_coordinator_update": { + "calls": 2, + "distinct_paths": 2 + }, + "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/__init__.py::GoeChargerDataUpdateCoordinator.clear_data": { + "calls": 3, + "distinct_paths": 1 + }, + "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/__init__.py::GoeChargerDataUpdateCoordinator.trigger_restart_delayed": { + "calls": 1, + "distinct_paths": 1 + }, + "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/__init__.py::GoeChargerDataUpdateCoordinator.__call__": { + "calls": 1, + "distinct_paths": 1 + }, + "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/__init__.py::GoeChargerDataUpdateCoordinator.cards_as_object_present": { + "calls": 7, + "distinct_paths": 1 + }, + "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/__init__.py::GoeChargerBaseEntity.__init__": { + "calls": 1908, + "distinct_paths": 13 + }, + "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/__init__.py::GoeChargerBaseEntity.unique_id": { + "calls": 467, + "distinct_paths": 1 + }, + "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/__init__.py::async_migrate_entry": { + "calls": 3, + "distinct_paths": 3 + }, + "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/select.py::GoeChargerSelect.__init__": { + "calls": 27, + "distinct_paths": 2 + }, + "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/select.py::GoeChargerSelect.async_select_option": { + "calls": 15, + "distinct_paths": 13 + }, + "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/switch.py::GoeChargerSwitch.__init__": { + "calls": 26, + "distinct_paths": 1 + }, + "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/switch.py::GoeChargerSwitch.async_turn_on": { + "calls": 6, + "distinct_paths": 6 + }, + "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/switch.py::GoeChargerSwitch.async_turn_off": { + "calls": 6, + "distinct_paths": 6 + }, + "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/switch.py::GoeChargerSwitch.is_on": { + "calls": 18, + "distinct_paths": 11 + }, + "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/switch.py::GoeChargerSwitch.state": { + "calls": 5, + "distinct_paths": 2 + }, + "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/switch.py::GoeChargerSwitch.icon": { + "calls": 2, + "distinct_paths": 2 + }, + "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/binary_sensor.py::GoeChargerApiV2BinarySensor.__init__": { + "calls": 17, + "distinct_paths": 1 + }, + "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/binary_sensor.py::GoeChargerApiV2BinarySensor.is_on": { + "calls": 18, + "distinct_paths": 13 + }, + "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/binary_sensor.py::GoeChargerApiV2BinarySensor.icon": { + "calls": 2, + "distinct_paths": 2 + }, + "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/button.py::GoeChargerApiV2Button.__init__": { + "calls": 2, + "distinct_paths": 1 + }, + "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/button.py::GoeChargerApiV2Button.async_press": { + "calls": 2, + "distinct_paths": 2 + }, + "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/sensor.py::GoeChargerSensor.__init__": { + "calls": 33, + "distinct_paths": 1 + }, + "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/sensor.py::GoeChargerSensor.native_value": { + "calls": 33, + "distinct_paths": 28 + }, + "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/sensor.py::GoeChargerSensor._camel_to_snake": { + "calls": 3, + "distinct_paths": 1 + }, + "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/sensor.py::GoeChargerSensor.state": { + "calls": 2, + "distinct_paths": 2 + }, + "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/number.py::GoeChargerNumber.__init__": { + "calls": 26, + "distinct_paths": 1 + }, + "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/number.py::GoeChargerNumber.check_amp_min_max": { + "calls": 11, + "distinct_paths": 4 + }, + "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/number.py::GoeChargerNumber.native_value": { + "calls": 9, + "distinct_paths": 8 + }, + "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/number.py::GoeChargerNumber.async_set_native_value": { + "calls": 11, + "distinct_paths": 9 + }, + "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/select.py::GoeChargerSelect.current_option": { + "calls": 11, + "distinct_paths": 11 + }, + "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/service.py::GoeChargerApiV2Service.__init__": { + "calls": 46, + "distinct_paths": 1 + }, + "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/service.py::GoeChargerApiV2Service.get_coordinator": { + "calls": 38, + "distinct_paths": 5 + }, + "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/service.py::_is_finite_number": { + "calls": 28, + "distinct_paths": 1 + }, + "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/service.py::GoeChargerApiV2Service.set_pv_data": { + "calls": 19, + "distinct_paths": 11 + }, + "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/service.py::GoeChargerApiV2Service.stop_charging": { + "calls": 11, + "distinct_paths": 9 + }, + "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/service.py::remove_config_entry_from_service": { + "calls": 6, + "distinct_paths": 2 + }, + "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/service.py::add_config_entry_to_service": { + "calls": 7, + "distinct_paths": 1 + }, + "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/__init__.py::entry_update_listener": { + "calls": 1, + "distinct_paths": 1 + }, + "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/pygoecharger_ha/__init__.py::GoeChargerApiV2Bridge._ws_decode_message": { + "calls": 88, + "distinct_paths": 4 + }, + "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/pygoecharger_ha/__init__.py::GoeChargerApiV2Bridge._ws_normalize_value": { + "calls": 271, + "distinct_paths": 6 + }, + "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/pygoecharger_ha/__init__.py::GoeChargerApiV2Bridge._ws_normalize_dict": { + "calls": 86, + "distinct_paths": 2 + }, + "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/pygoecharger_ha/__init__.py::GoeChargerApiV2Bridge._ws_compute_hashed_password": { + "calls": 31, + "distinct_paths": 3 + }, + "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/pygoecharger_ha/__init__.py::GoeChargerApiV2Bridge._ws_compute_auth_hash": { + "calls": 26, + "distinct_paths": 1 + }, + "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/pygoecharger_ha/__init__.py::GoeChargerApiV2Bridge.extract_ws_message_data": { + "calls": 25, + "distinct_paths": 11 + }, + "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/pygoecharger_ha/__init__.py::GoeChargerApiV2Bridge.ws_connect": { + "calls": 36, + "distinct_paths": 27 + }, + "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/pygoecharger_ha/__init__.py::bcryptjs_base64_encode_serial": { + "calls": 8, + "distinct_paths": 2 + }, + "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/pygoecharger_ha/__init__.py::GoeChargerApiV2Bridge.ws_connect..": { + "calls": 3, + "distinct_paths": 2 + }, + "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/pygoecharger_ha/__init__.py::TargetedEvent.check_and_set..": { + "calls": 5, + "distinct_paths": 2 + }, + "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/pygoecharger_ha/__init__.py::TargetedEvent.check_and_set": { + "calls": 5, + "distinct_paths": 2 + }, + "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/pygoecharger_ha/__init__.py::GoeChargerApiV2Bridge._ws_normalize_value..": { + "calls": 1, + "distinct_paths": 1 + } +} \ No newline at end of file diff --git a/tests/reports/2026-10-29/pathtrace-baseline.json b/tests/reports/2026-10-29/pathtrace-baseline.json new file mode 100644 index 0000000..1669d25 --- /dev/null +++ b/tests/reports/2026-10-29/pathtrace-baseline.json @@ -0,0 +1,366 @@ +{ + "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/pygoecharger_ha/const.py::INTG_TYPE": { + "calls": 1, + "distinct_paths": 1 + }, + "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/pygoecharger_ha/const.py::CAR_VALUES": { + "calls": 1, + "distinct_paths": 1 + }, + "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/pygoecharger_ha/const.py::CT_VALUES": { + "calls": 1, + "distinct_paths": 1 + }, + "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/pygoecharger_ha/const.py::": { + "calls": 1, + "distinct_paths": 1 + }, + "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/pygoecharger_ha/keys.py::CAT": { + "calls": 1, + "distinct_paths": 1 + }, + "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/pygoecharger_ha/keys.py::ApiKey": { + "calls": 1, + "distinct_paths": 1 + }, + "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/pygoecharger_ha/keys.py::ApiKey.__annotate__": { + "calls": 2, + "distinct_paths": 2 + }, + "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/pygoecharger_ha/keys.py::Tag": { + "calls": 1, + "distinct_paths": 1 + }, + "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/pygoecharger_ha/keys.py::": { + "calls": 1, + "distinct_paths": 1 + }, + "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/pygoecharger_ha/__init__.py::TargetedEvent": { + "calls": 1, + "distinct_paths": 1 + }, + "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/pygoecharger_ha/__init__.py::NonNullDict": { + "calls": 1, + "distinct_paths": 1 + }, + "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/pygoecharger_ha/__init__.py::ExpiringStore": { + "calls": 1, + "distinct_paths": 1 + }, + "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/pygoecharger_ha/__init__.py::GoeChargerApiV2Bridge": { + "calls": 1, + "distinct_paths": 1 + }, + "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/pygoecharger_ha/__init__.py::": { + "calls": 1, + "distinct_paths": 1 + }, + "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/const.py::ExtBinarySensorEntityDescription": { + "calls": 1, + "distinct_paths": 1 + }, + "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/const.py::ExtBinarySensorEntityDescription.__annotate__": { + "calls": 1, + "distinct_paths": 1 + }, + "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/const.py::ExtButtonEntityDescription": { + "calls": 1, + "distinct_paths": 1 + }, + "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/const.py::ExtButtonEntityDescription.__annotate__": { + "calls": 1, + "distinct_paths": 1 + }, + "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/const.py::ExtNumberEntityDescription": { + "calls": 1, + "distinct_paths": 1 + }, + "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/const.py::ExtNumberEntityDescription.__annotate__": { + "calls": 1, + "distinct_paths": 1 + }, + "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/const.py::ExtSelectEntityDescription": { + "calls": 1, + "distinct_paths": 1 + }, + "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/const.py::ExtSelectEntityDescription.__annotate__": { + "calls": 1, + "distinct_paths": 1 + }, + "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/const.py::ExtSensorEntityDescription": { + "calls": 1, + "distinct_paths": 1 + }, + "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/const.py::ExtSensorEntityDescription.__annotate__": { + "calls": 1, + "distinct_paths": 1 + }, + "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/const.py::ExtSwitchEntityDescription": { + "calls": 1, + "distinct_paths": 1 + }, + "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/const.py::ExtSwitchEntityDescription.__annotate__": { + "calls": 1, + "distinct_paths": 1 + }, + "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/const.py::": { + "calls": 1, + "distinct_paths": 1 + }, + "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/entity.py::CustomFriendlyNameEntity": { + "calls": 1, + "distinct_paths": 1 + }, + "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/entity.py::": { + "calls": 1, + "distinct_paths": 1 + }, + "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/service.py::GoeChargerApiV2Service": { + "calls": 1, + "distinct_paths": 1 + }, + "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/service.py::": { + "calls": 1, + "distinct_paths": 1 + }, + "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/__init__.py::GoeChargerDataUpdateCoordinator": { + "calls": 1, + "distinct_paths": 1 + }, + "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/__init__.py::GoeChargerBaseEntity": { + "calls": 1, + "distinct_paths": 1 + }, + "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/__init__.py::": { + "calls": 1, + "distinct_paths": 1 + }, + "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/config_flow.py::GoeChargerApiV2FlowHandler": { + "calls": 1, + "distinct_paths": 1 + }, + "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/config_flow.py::": { + "calls": 1, + "distinct_paths": 1 + }, + "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/pygoecharger_ha/__init__.py::GoeChargerApiV2Bridge.__init__": { + "calls": 114, + "distinct_paths": 5 + }, + "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/pygoecharger_ha/__init__.py::ExpiringStore.__init__": { + "calls": 113, + "distinct_paths": 1 + }, + "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/pygoecharger_ha/__init__.py::GoeChargerApiV2Bridge._write_values_int": { + "calls": 14, + "distinct_paths": 5 + }, + "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/pygoecharger_ha/__init__.py::GoeChargerApiV2Bridge._read_filtered_data": { + "calls": 17, + "distinct_paths": 8 + }, + "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/pygoecharger_ha/__init__.py::NonNullDict.none_null_get": { + "calls": 154, + "distinct_paths": 1 + }, + "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/pygoecharger_ha/__init__.py::GoeChargerApiV2Bridge.write_value_to_key": { + "calls": 17, + "distinct_paths": 6 + }, + "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/config_flow.py::GoeChargerApiV2FlowHandler.__init__": { + "calls": 8, + "distinct_paths": 1 + }, + "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/config_flow.py::GoeChargerApiV2FlowHandler.async_step_user": { + "calls": 16, + "distinct_paths": 3 + }, + "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/config_flow.py::GoeChargerApiV2FlowHandler.async_step_user_lan": { + "calls": 14, + "distinct_paths": 3 + }, + "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/pygoecharger_ha/__init__.py::GoeChargerApiV2Bridge.read_system": { + "calls": 8, + "distinct_paths": 2 + }, + "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/pygoecharger_ha/__init__.py::GoeChargerApiV2Bridge.read_versions": { + "calls": 1, + "distinct_paths": 1 + }, + "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/config_flow.py::GoeChargerApiV2FlowHandler._test_host": { + "calls": 8, + "distinct_paths": 4 + }, + "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/__init__.py::async_setup": { + "calls": 1, + "distinct_paths": 1 + }, + "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/__init__.py::async_unload_entry": { + "calls": 3, + "distinct_paths": 3 + }, + "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/config_flow.py::GoeChargerApiV2FlowHandler.async_step_user_wan": { + "calls": 2, + "distinct_paths": 2 + }, + "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/__init__.py::GoeChargerDataUpdateCoordinator.__init__": { + "calls": 49, + "distinct_paths": 4 + }, + "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/__init__.py::GoeChargerDataUpdateCoordinator._handle_client_connection_error": { + "calls": 15, + "distinct_paths": 2 + }, + "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/__init__.py::GoeChargerDataUpdateCoordinator.async_write_key": { + "calls": 7, + "distinct_paths": 5 + }, + "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/__init__.py::GoeChargerDataUpdateCoordinator.async_write_multiple_keys": { + "calls": 4, + "distinct_paths": 4 + }, + "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/__init__.py::GoeChargerDataUpdateCoordinator._async_update_data": { + "calls": 19, + "distinct_paths": 5 + }, + "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/__init__.py::async_setup_entry": { + "calls": 4, + "distinct_paths": 4 + }, + "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/__init__.py::GoeChargerBaseEntity.__init__": { + "calls": 1777, + "distinct_paths": 12 + }, + "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/__init__.py::GoeChargerBaseEntity.unique_id": { + "calls": 467, + "distinct_paths": 1 + }, + "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/__init__.py::async_migrate_entry": { + "calls": 3, + "distinct_paths": 3 + }, + "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/service.py::GoeChargerApiV2Service.__init__": { + "calls": 13, + "distinct_paths": 1 + }, + "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/service.py::GoeChargerApiV2Service.get_coordinator": { + "calls": 14, + "distinct_paths": 2 + }, + "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/service.py::_is_finite_number": { + "calls": 14, + "distinct_paths": 1 + }, + "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/service.py::GoeChargerApiV2Service.set_pv_data": { + "calls": 7, + "distinct_paths": 2 + }, + "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/pygoecharger_ha/__init__.py::ExpiringStore._cleanup": { + "calls": 12, + "distinct_paths": 1 + }, + "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/pygoecharger_ha/__init__.py::ExpiringStore.set": { + "calls": 10, + "distinct_paths": 1 + }, + "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/pygoecharger_ha/__init__.py::GoeChargerApiV2Bridge._ws_send_command": { + "calls": 9, + "distinct_paths": 2 + }, + "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/__init__.py::GoeChargerDataUpdateCoordinator.handle_write_result": { + "calls": 4, + "distinct_paths": 2 + }, + "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/service.py::GoeChargerApiV2Service.stop_charging": { + "calls": 3, + "distinct_paths": 3 + }, + "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/pygoecharger_ha/__init__.py::GoeChargerApiV2Bridge.write_multiple_values_to_keys": { + "calls": 1, + "distinct_paths": 1 + }, + "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/service.py::add_config_entry_to_service": { + "calls": 5, + "distinct_paths": 1 + }, + "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/service.py::remove_config_entry_from_service": { + "calls": 3, + "distinct_paths": 2 + }, + "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/__init__.py::GoeChargerDataUpdateCoordinator.read_versions..": { + "calls": 10, + "distinct_paths": 2 + }, + "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/__init__.py::GoeChargerDataUpdateCoordinator.read_versions": { + "calls": 10, + "distinct_paths": 7 + }, + "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/pygoecharger_ha/__init__.py::TargetedEvent.__init__": { + "calls": 16, + "distinct_paths": 2 + }, + "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/pygoecharger_ha/__init__.py::GoeChargerApiV2Bridge._ws_decode_message": { + "calls": 26, + "distinct_paths": 3 + }, + "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/pygoecharger_ha/__init__.py::GoeChargerApiV2Bridge._ws_normalize_value": { + "calls": 80, + "distinct_paths": 4 + }, + "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/pygoecharger_ha/__init__.py::GoeChargerApiV2Bridge._ws_normalize_dict": { + "calls": 24, + "distinct_paths": 2 + }, + "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/pygoecharger_ha/__init__.py::GoeChargerApiV2Bridge._ws_compute_hashed_password": { + "calls": 11, + "distinct_paths": 3 + }, + "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/pygoecharger_ha/__init__.py::GoeChargerApiV2Bridge._ws_compute_auth_hash": { + "calls": 6, + "distinct_paths": 1 + }, + "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/pygoecharger_ha/__init__.py::GoeChargerApiV2Bridge.extract_ws_message_data": { + "calls": 13, + "distinct_paths": 10 + }, + "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/pygoecharger_ha/__init__.py::GoeChargerApiV2Bridge._ws_notify_for_new_data": { + "calls": 2, + "distinct_paths": 1 + }, + "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/pygoecharger_ha/__init__.py::TargetedEvent.check_and_set..": { + "calls": 3, + "distinct_paths": 2 + }, + "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/pygoecharger_ha/__init__.py::TargetedEvent.check_and_set": { + "calls": 3, + "distinct_paths": 2 + }, + "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/pygoecharger_ha/__init__.py::GoeChargerApiV2Bridge.ws_close": { + "calls": 2, + "distinct_paths": 1 + }, + "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/pygoecharger_ha/__init__.py::GoeChargerApiV2Bridge.ws_connect": { + "calls": 13, + "distinct_paths": 11 + }, + "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/pygoecharger_ha/__init__.py::TargetedEvent.has_errors": { + "calls": 11, + "distinct_paths": 1 + }, + "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/pygoecharger_ha/__init__.py::GoeChargerApiV2Bridge._ws_notify_for_new_data.._ws_debounce_coordinator_update": { + "calls": 2, + "distinct_paths": 1 + }, + "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/pygoecharger_ha/__init__.py::TargetedEvent.set_error": { + "calls": 11, + "distinct_paths": 1 + }, + "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/pygoecharger_ha/__init__.py::ExpiringStore.get": { + "calls": 2, + "distinct_paths": 2 + }, + "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/pygoecharger_ha/__init__.py::bcryptjs_base64_encode_serial": { + "calls": 5, + "distinct_paths": 2 + } +} \ No newline at end of file diff --git a/tests/reports/2026-10-29/testreport.md b/tests/reports/2026-10-29/testreport.md new file mode 100644 index 0000000..5de3007 --- /dev/null +++ b/tests/reports/2026-10-29/testreport.md @@ -0,0 +1,269 @@ +# Testreport und Testabdeckung: `ha-goecharger-api2` + +| | | +|---|---| +| **Datum** | 2026-10-29 | +| **Geprüfter Code** | `marq24/ha-goecharger-api2` `main`, Commit `98af3e0` | +| **Tests** | Fork `OliEder/ha-goecharger-api2`, Branch `tests/regression-suite`, Commit `1d50118+local` | +| **Umgebung** | Python 3.14.3, Home Assistant 2026.10.4, coverage 7.16.2, radon 6.0.1 | +| **Rohdaten** | `coverage-all.json`, `coverage-blocking.json`, `coverage-baseline.json`, `junit-all.xml`, `pathtrace-all.json`, `pathtrace-baseline.json` in diesem Ordner (der HTML-Bericht ist nicht eingecheckt und lässt sich mit `tests/tools/measure.sh` neu erzeugen) | + +## 1. Zusammenfassung + +| Kennzahl | Wert | +|---|---| +| Tests gesammelt / bestanden / fehlgeschlagen | 911 / 885 / 26 (alle beabsichtigt: 12 `known_bug`, 14 `needs_decision`) | +| Blockierende Menge (ohne die beiden Marker) | 885 von 885 bestanden | +| **Zeilenabdeckung** (alle Tests) | **93.0 %** (2351 von 2529 Anweisungen) | +| **Zweigabdeckung** (alle Tests) | **85.0 %** (784 von 922 Zweigen) | +| **Pfadabdeckung** (obere Schranke der Basispfade, alle Funktionen) | **61 %** (484 von 794) | +| **Pfadabdeckung** (obere Schranke, nur die vier Kernmodule) | **62 %** (357 von 580) | +| Funktionen: nie ausgeführt / mit zu wenigen Pfaden / Pfadzahl nicht widerlegt | 14 / 58 / 48 (von 120) | + +**Entwicklung gegenüber dem ersten Report (gleicher Tag, Stand vor dem Ausbau der Tests)** + +| Kennzahl | Vorher | Jetzt | +|---|---|---| +| Verhaltens- und Fehlertests (ohne Vertragsprüfungen je Schlüssel) | 125 | 491 | +| Zeilenabdeckung | 55.2 % | 93.0 % | +| Zweigabdeckung | 31.8 % | 85.0 % | +| Pfadabdeckung (obere Schranke), alle Funktionen | 20 % | 61 % | +| Pfadabdeckung (obere Schranke), Kernmodule | 27 % | 62 % | + +**Was das bedeutet:** Die Tests prüfen jetzt auch die Plattformen (Wertdarstellung, Schreiben) und die Laufzeitpfade des Koordinators. Zeilen- und Zweigabdeckung sind hoch, die Pfadabdeckung ist deutlich niedriger, weil sie verlangt, dass jede Funktion **viele verschiedene Kombinationen** von Entscheidungen durchläuft. Die Pfadzahl ist eine obere Schranke und kein Beleg für Vollständigkeit (Abschnitt 2). + +## 2. Begriffe und Messverfahren (wichtig für die Einordnung) + +| Kennzahl | Bedeutung | Gemessen mit | +|---|---|---| +| Zeilenabdeckung | Anteil der ausgeführten Anweisungen | `coverage.py` | +| Zweigabdeckung | Anteil der genommenen Verzweigungsausgänge (`if`/`else`, Schleife betreten/übersprungen, `try`/`except`). Entspricht der Entscheidungsabdeckung. | `coverage.py --cov-branch` | +| Pfadabdeckung | Anteil der möglichen Ausführungswege durch eine Funktion. **Wird von `coverage.py` nicht gemessen** und ist bei Schleifen und vielen Entscheidungen praktisch nicht vollständig erreichbar (Zahl der Pfade wächst exponentiell). | siehe unten | + +**Wie die Pfadzahlen entstehen.** Ein eigenes Test-Plugin (`tests/tools/pathtracer.py`, nutzt `sys.monitoring`) zeichnet für **jeden Aufruf** einer Funktion die Folge der ausgeführten Zeilen auf. Schleifen werden verkürzt: eine Zeile zählt je Aufruf höchstens zweimal. Danach zählt es, wie viele **verschiedene** Pfade je Funktion im Testlauf vorkamen. + +Diese Zahl wird mit der **zyklomatischen Komplexität** (CC, `radon`) verglichen. Die CC ist die Zahl der linear unabhängigen Basispfade einer Funktion. Daraus folgt eine **sichere Aussage in eine Richtung**: +- Wurden weniger verschiedene Pfade beobachtet als CC, sind **sicher nicht** alle Basispfade getestet. +- Wurden mindestens CC Pfade beobachtet, ist das **kein Beleg** für vollständige Abdeckung. Die Pfade können sich in denselben Entscheidungen ähneln. +- Die Kennzahl „Pfadabdeckung (obere Schranke)“ summiert je Funktion `min(beobachtete Pfade, CC)` und teilt durch die Summe der CC. Die tatsächliche Basispfadabdeckung ist **höchstens so hoch**. + +Grenzen: Die Messung ist empirisch und heuristisch. Verschachtelte Funktionen werden der umgebenden Funktion zugerechnet oder nicht erfasst. Nur eine Tracing-Variante (Zeilenfolgen) wurde verwendet. + +## 3. Testergebnisse + +| Datei | Tests | fehlgeschlagen | Zeit | +|---|---|---|---| +| `test_api_contract.py` | 420 | 14 | 5.77 s | +| `test_bridge.py` | 3 | 0 | 0.06 s | +| `test_bridge_http.py` | 20 | 2 | 0.29 s | +| `test_bridge_polling.py` | 47 | 0 | 0.63 s | +| `test_bridge_write_paths.py` | 21 | 0 | 0.44 s | +| `test_config_flow_failures.py` | 8 | 0 | 0.17 s | +| `test_config_flow_paths.py` | 17 | 0 | 0.32 s | +| `test_coordinator_errors.py` | 2 | 0 | 0.03 s | +| `test_coordinator_failures.py` | 14 | 0 | 0.23 s | +| `test_coordinator_runtime.py` | 76 | 0 | 1.12 s | +| `test_entity_ids.py` | 10 | 0 | 0.16 s | +| `test_migration.py` | 3 | 0 | 0.04 s | +| `test_platform_actions.py` | 60 | 0 | 0.89 s | +| `test_platform_values.py` | 72 | 0 | 1.04 s | +| `test_service.py` | 7 | 3 | 0.10 s | +| `test_services_and_setup.py` | 39 | 1 | 0.68 s | +| `test_services_and_unload.py` | 10 | 6 | 0.22 s | +| `test_versions_16a.py` | 10 | 0 | 0.20 s | +| `test_ws_connect_paths.py` | 34 | 0 | 2.54 s | +| `test_ws_login_failures.py` | 13 | 0 | 0.96 s | +| `test_ws_protocol.py` | 25 | 0 | 0.58 s | + +Hinweis: `test_api_contract.py` enthält 420 parametrisierte Vertragsprüfungen (je Schlüssel). Verhaltens- und Fehlerpfadtests insgesamt: 491. + +**Gesamtdauer** 18.619 s. + +## 4. Abdeckung je Datei + +| Datei | Anweisungen | Zeilenabdeckung | Zweige | Zweigabdeckung | CC (Summe) | Pfade (obere Schranke) | +|---|---|---|---|---|---|---| +| `__init__.py` | 604 | 96 % | 266 | 89 % | 224 | 59 % | +| `binary_sensor.py` | 71 | 82 % | 34 | 68 % | 27 | 59 % | +| `button.py` | 31 | 61 % | 8 | 0 % | 8 | 38 % | +| `config_flow.py` | 178 | 96 % | 44 | 93 % | 42 | 64 % | +| `const.py` | 69 | 100 % | 0 | – | 0 | – | +| `entity.py` | 31 | 26 % | 14 | 0 % | 11 | 0 % | +| `number.py` | 94 | 81 % | 48 | 71 % | 50 | 44 % | +| `pygoecharger_ha/__init__.py` | 721 | 95 % | 302 | 91 % | 280 | 60 % | +| `pygoecharger_ha/const.py` | 61 | 100 % | 0 | – | 0 | – | +| `pygoecharger_ha/keys.py` | 255 | 99 % | 0 | – | 2 | 0 % | +| `select.py` | 100 | 88 % | 56 | 84 % | 36 | 72 % | +| `sensor.py` | 109 | 87 % | 60 | 82 % | 42 | 76 % | +| `service.py` | 104 | 97 % | 42 | 98 % | 34 | 85 % | +| `switch.py` | 101 | 88 % | 48 | 79 % | 38 | 74 % | + +**Vergleich:** Nur die blockierenden Tests: Zeilen 92.9 %, Zweige 85.0 %. Die beabsichtigt roten Tests (bekannte Fehler und Abweichungen) verändern die Abdeckung kaum (0.1 Prozentpunkte bei den Zeilen). + + +## 5. Pfade nach Kritikalität + +**Maßstab.** Kritikalität ist die Auswirkung eines falschen Pfades auf den Nutzer, nicht die Häufigkeit. Die Einstufung gilt für eine ganze Funktion und damit für alle ihre Pfade. Die Zuordnung steht in `tests/tools/criticality.py` (mit Begründung je Funktion) und ist eine Einschätzung, keine Messung. + +| Klasse | Bedeutung | Beispiele | +|---|---|---| +| **K1 kritisch** | Ändert den Charger, seine Grenzen oder die HA-Konfiguration; ein falscher Pfad kann den Charger sperren, falsche Werte senden, Geräte löschen oder Dienste entfernen | `write_value_to_key`, `stop_charging`, `set_pv_data`, `check_and_write_to_16a`, `async_migrate_entry` | +| **K2 hoch** | Verbindung, Anmeldung, Erholung, Start; ein falscher Pfad macht die Integration unverfügbar oder zeigt alte Daten | `ws_connect`, `_async_update_data`, `async_setup_entry`, Config-Flow | +| **K3 mittel** | Korrektheit dessen, was angezeigt oder erkannt wird, ohne Schreiben | Wertdarstellung der Entitäten, `is_valid_charger_entity` | +| **K4 niedrig** | Hilfsfunktionen, Namen, Symbole, Logging | `_ws_normalize_value`, `unique_id`, Anzeigename | + +| Klasse | Funktionen | CC (Basispfade) | Pfade vorher | Pfade jetzt (obere Schranke) | Zeilen | Zweige | nie ausgeführt | +|---|---|---|---|---|---|---|---| +| **K1 kritisch** | 24 | 195 | 22 % | **66 %** (128 von 195) | 96 % | 96 % | 0 | +| **K2 hoch** | 38 | 335 | 24 % | **60 %** (202 von 335) | 96 % | 91 % | 0 | +| **K3 mittel** | 33 | 171 | 1 % | **64 %** (109 von 171) | 78 % | 72 % | 6 | +| **K4 niedrig** | 25 | 93 | 34 % | **48 %** (45 von 93) | 71 % | 57 % | 8 | + +Lesehilfe: „Pfade jetzt“ ist die obere Schranke der Basispfadabdeckung (Abschnitt 2). Die Spalten „Zeilen“ und „Zweige“ stehen für dieselben Funktionen und liegen viel höher; der Unterschied zeigt, wie viel strenger die Pfadabdeckung ist. + +### 5.1 Offene Lücken in K1 und K2 + +Funktionen, bei denen **sicher** Basispfade fehlen (beobachtete Pfade unter der CC), geordnet nach Klasse und Zahl der fehlenden Pfade. „Fehlerpfad“ markiert nicht genommene Zweige, deren Zeile nach einem Fehlerfall aussieht (Schlüsselwörter wie `except`, `raise`, `error`, `warning`); das ist eine Heuristik. + +**K1 kritisch** + +| Funktion | CC | Pfade | fehlen mind. | Aufrufe | Warum kritisch | Nicht genommene Zweige | +|---|---|---|---|---|---|---| +| `check_and_write_to_16a` | 29 | 8 | 21 | 8 | schreibt beim Start Stromgrenzen an den Charger | Z.218 `if hass is not None:` (Normalpfad)
Z.220 `if a_entity_reg is not None:` (Normalpfad)
Z.271 `if a_res_key not in keys_to_patch:` (Normalpfad)
Z.280 `if a_res_key not in keys_to_patch:` (Normalpfad) | +| `async_migrate_entry` | 11 | 3 | 8 | 3 | löscht Geräte und damit Entitäten | Z.75 `if config_entry.data is not None and len(confi` (Normalpfad)
Z.77 `if config_entry.options is not None and len(co` (Normalpfad)
Z.89 `if a_device_reg is not None:` (Normalpfad) | +| `GoeChargerNumber.async_set_native_value` | 17 | 9 | 8 | 11 | schreibt Zahlenwerte (Ströme, Grenzen) | – | +| `GoeChargerApiV2Bridge._write_values_int` | 18 | 11 | 7 | 30 | wertet die Antwort des Chargers auf Schreibbefehle aus | – | +| `GoeChargerApiV2Bridge.write_multiple_values_to_keys` | 8 | 2 | 6 | 3 | schreibt mehrere Werte (Fahrzeugauswahl) | – | +| `GoeChargerNumber.check_amp_min_max` | 9 | 4 | 5 | 11 | löst automatisch das Setzen von amp aus | Z.143 `if amp_value_exceeds_limit:` (Normalpfad) | +| `GoeChargerSelect.async_select_option` | 16 | 13 | 3 | 15 | schreibt Auswahl, inklusive Fahrzeugprofil mit mehreren Werten | – | +| `GoeChargerApiV2Service.set_pv_data` | 14 | 11 | 3 | 19 | sendet PV-Daten; steuert das PV-Laden | – | +| `GoeChargerDataUpdateCoordinator.handle_write_result` | 9 | 7 | 2 | 11 | übernimmt das Schreibergebnis in die Daten | – | +| `_is_finite_number` | 3 | 1 | 2 | 28 | verhindert ungültige Werte an den Charger | Z.23 `if not isinstance(value, Number):` (Normalpfad) | +| `GoeChargerDataUpdateCoordinator.async_write_multiple_keys` | 5 | 4 | 1 | 4 | Mehrfach-Schreibpfad mit Fehlerpause | – | +| `GoeChargerApiV2Bridge.write_value_to_key` | 12 | 11 | 1 | 36 | schreibt einen Wert an den Charger (HTTP oder WebSocket) | – | + +**K2 hoch** + +| Funktion | CC | Pfade | fehlen mind. | Aufrufe | Warum kritisch | Nicht genommene Zweige | +|---|---|---|---|---|---|---| +| `GoeChargerApiV2Bridge.ws_connect` | 61 | 27 | 34 | 36 | Anmeldung und Nachrichtenschleife der WebSocket-Verbindung | Z.786 `if a_event is not None and not a_event.is_set(` (Normalpfad)
Z.807 `if a_event is not None and not a_event.is_set(` (Normalpfad)
Z.830 `if 'type' in self._ws_device_info:` (Normalpfad)
Z.841 `if a_event is not None and not a_event.is_set(` (Normalpfad) | +| `GoeChargerDataUpdateCoordinator.read_versions` | 37 | 21 | 16 | 28 | erkennt Gerätetyp, 16-A-Limit und Karten | Z.713 `if lc_model_type is not None:` (Normalpfad) | +| `GoeChargerApiV2Bridge.read_all_states` | 22 | 11 | 11 | 17 | Statuslesen mit Hibernation | Z.338 `elif self.isController:` (Normalpfad)
Z.377 `if len(self._states) > 0:` (Normalpfad) | +| `GoeChargerApiV2Bridge.extract_ws_message_data` | 20 | 11 | 9 | 25 | verarbeitet alle Daten und Antworten vom WebSocket | Z.1050 `if 'requestId' in data:` (Normalpfad)
Z.1056 `if status_data and len(status_data) > 0:` (Normalpfad)
Z.1059 `if the_request_payload is not None:` (Normalpfad)
Z.1064 `if the_request_payload is not None:` (Fehlerpfad) | +| `GoeChargerApiV2FlowHandler._test_host` | 14 | 7 | 7 | 18 | prüft Zugang beim Einrichten | – | +| `GoeChargerApiV2Bridge.read_all_config` | 11 | 5 | 6 | 8 | Konfigurationslesen mit Wiederholungs-Pause | Z.404 `if a_key in self._ws_states:` (Normalpfad) | +| `async_setup_entry` | 16 | 11 | 5 | 17 | Start der Integration | – | +| `GoeChargerDataUpdateCoordinator._async_update_data` | 10 | 5 | 5 | 19 | Lesezyklus mit Fehlerpause | Z.609 `if self.bridge.ws_connected and self._force_cl` (Normalpfad)
Z.623 `if new_data is not None and len(new_data) > 0:` (Normalpfad) | +| `GoeChargerApiV2Bridge.read_versions` | 14 | 9 | 5 | 18 | liest Geräte- und Versionsdaten beim Start | – | +| `GoeChargerApiV2Bridge.__init__` | 15 | 11 | 4 | 466 | bildet Adressen für LAN, Cloud und WebSocket | – | +| `GoeChargerApiV2Bridge._read_all_data` | 9 | 5 | 4 | 6 | Lesen aller Daten per HTTP | – | +| `bcryptjs_base64_encode_serial` | 6 | 2 | 4 | 8 | Salz für die bcrypt-Anmeldung | Z.1095 `while i < length:` (Normalpfad)
Z.1105 `if i >= length:` (Normalpfad) | +| `GoeChargerApiV2FlowHandler.async_step_user_wan` | 8 | 5 | 3 | 11 | Einrichtung Cloud | Z.209 `if len(a_user_pwd) == 0 and CONF_PASSWORD in u` (Normalpfad) | +| `GoeChargerApiV2FlowHandler._check_ws_connection_and_flags` | 7 | 4 | 3 | 4 | prüft WebSocket beim Einrichten | Z.284 `if ready_event.is_set():` (Normalpfad) | + + +## 6. Kritische Funktionen (nach Komplexität) + +Kernmodule (`__init__.py` mit Koordinator, `pygoecharger_ha/__init__.py` mit Bridge, `service.py`, `config_flow.py`), sortiert nach Komplexität, gezeigt ab CC 4. „Pfade“ = beobachtete verschiedene Pfade, „CC“ = Basispfade. + +| Funktion | CC | Aufrufe | Pfade | Zeilen | Zweige | Bewertung | +|---|---|---|---|---|---|---| +| `GoeChargerApiV2Bridge.ws_connect` (Bridge) | 61 | 36 | 27 | 96 % | 77 % | es fehlen mindestens 34 Basispfade | +| `GoeChargerDataUpdateCoordinator.read_versions` (Koordinator) | 37 | 28 | 21 | 100 % | 98 % | es fehlen mindestens 16 Basispfade | +| `check_and_write_to_16a` (Setup) | 29 | 8 | 8 | 100 % | 92 % | es fehlen mindestens 21 Basispfade | +| `GoeChargerApiV2Bridge.read_all_states` (Bridge) | 22 | 17 | 11 | 100 % | 93 % | es fehlen mindestens 11 Basispfade | +| `GoeChargerApiV2Bridge.extract_ws_message_data` (Bridge) | 20 | 25 | 11 | 95 % | 85 % | es fehlen mindestens 9 Basispfade | +| `GoeChargerApiV2Bridge._write_values_int` (Bridge) | 18 | 30 | 11 | 72 % | 100 % | es fehlen mindestens 7 Basispfade | +| `async_setup_entry` (Setup) | 16 | 17 | 11 | 100 % | 100 % | es fehlen mindestens 5 Basispfade | +| `GoeChargerApiV2Bridge.__init__` (Bridge) | 15 | 466 | 11 | 100 % | 100 % | es fehlen mindestens 4 Basispfade | +| `GoeChargerApiV2FlowHandler._test_host` (Flow) | 14 | 18 | 7 | 94 % | 100 % | es fehlen mindestens 7 Basispfade | +| `GoeChargerApiV2Bridge.read_versions` (Bridge) | 14 | 18 | 9 | 100 % | 100 % | es fehlen mindestens 5 Basispfade | +| `GoeChargerApiV2Service.set_pv_data` (Dienst) | 14 | 19 | 11 | 100 % | 100 % | es fehlen mindestens 3 Basispfade | +| `GoeChargerBaseEntity.__init__` (Entität) | 13 | 1908 | 13 | 100 % | 94 % | Pfadzahl nicht widerlegt | +| `GoeChargerApiV2Bridge._read_filtered_data` (Bridge) | 12 | 57 | 10 | 81 % | 100 % | es fehlen mindestens 2 Basispfade | +| `GoeChargerApiV2Bridge.write_value_to_key` (Bridge) | 12 | 36 | 11 | 100 % | 100 % | es fehlen mindestens 1 Basispfade | +| `async_migrate_entry` (Setup) | 11 | 3 | 3 | 86 % | 75 % | es fehlen mindestens 8 Basispfade | +| `GoeChargerDataUpdateCoordinator.call_later_update_device_registry` (Koordinator) | 11 | 5 | 5 | 94 % | 79 % | es fehlen mindestens 6 Basispfade | +| `GoeChargerDataUpdateCoordinator.is_valid_charger_entity` (Koordinator) | 11 | 14 | 7 | 100 % | 100 % | es fehlen mindestens 4 Basispfade | +| `GoeChargerApiV2Bridge.read_all_config` (Bridge) | 11 | 8 | 5 | 100 % | 94 % | es fehlen mindestens 6 Basispfade | +| `GoeChargerDataUpdateCoordinator._async_update_data` (Koordinator) | 10 | 19 | 5 | 88 % | 75 % | es fehlen mindestens 5 Basispfade | +| `GoeChargerBaseEntity._friendly_name_internal` (Entität) | 10 | 0 | 0 | 0 % | 0 % | nie ausgeführt | +| `GoeChargerDataUpdateCoordinator.__init__` (Koordinator) | 9 | 286 | 5 | 98 % | 90 % | es fehlen mindestens 4 Basispfade | +| `GoeChargerDataUpdateCoordinator.handle_write_result` (Koordinator) | 9 | 11 | 7 | 100 % | 100 % | es fehlen mindestens 2 Basispfade | +| `GoeChargerApiV2Bridge._read_all_data` (Bridge) | 9 | 6 | 5 | 71 % | 100 % | es fehlen mindestens 4 Basispfade | +| `GoeChargerApiV2Bridge._ws_normalize_value` (Bridge) | 9 | 271 | 6 | 100 % | 100 % | es fehlen mindestens 3 Basispfade | +| `GoeChargerApiV2FlowHandler.async_step_user_wan` (Flow) | 8 | 11 | 5 | 96 % | 90 % | es fehlen mindestens 3 Basispfade | +| `GoeChargerApiV2Bridge.write_multiple_values_to_keys` (Bridge) | 8 | 3 | 2 | 100 % | 100 % | es fehlen mindestens 6 Basispfade | +| `GoeChargerApiV2Service.stop_charging` (Dienst) | 8 | 11 | 9 | 100 % | 100 % | Pfadzahl nicht widerlegt | +| `GoeChargerDataUpdateCoordinator.start_websocket_and_wait_for_first_data` (Koordinator) | 7 | 6 | 6 | 96 % | 88 % | es fehlen mindestens 1 Basispfade | +| `GoeChargerApiV2FlowHandler.async_step_user_lan` (Flow) | 7 | 27 | 5 | 96 % | 90 % | es fehlen mindestens 2 Basispfade | +| `GoeChargerApiV2FlowHandler._check_ws_connection_and_flags` (Flow) | 7 | 4 | 4 | 86 % | 75 % | es fehlen mindestens 3 Basispfade | +| `bcryptjs_base64_encode_serial` (Bridge) | 6 | 8 | 2 | 92 % | 75 % | es fehlen mindestens 4 Basispfade | +| `async_unload_entry` (Setup) | 5 | 14 | 5 | 100 % | 100 % | Pfadzahl nicht widerlegt | +| `GoeChargerDataUpdateCoordinator.async_write_key` (Koordinator) | 5 | 7 | 5 | 100 % | 100 % | Pfadzahl nicht widerlegt | +| `GoeChargerDataUpdateCoordinator.async_write_multiple_keys` (Koordinator) | 5 | 4 | 4 | 82 % | 100 % | es fehlen mindestens 1 Basispfade | +| `TargetedEvent.check_and_set` (Bridge) | 5 | 5 | 2 | 100 % | 100 % | es fehlen mindestens 3 Basispfade | +| `GoeChargerApiV2Service.get_coordinator` (Dienst) | 5 | 38 | 5 | 100 % | 100 % | Pfadzahl nicht widerlegt | +| `GoeChargerDataUpdateCoordinator.force_async_update_now` (Koordinator) | 4 | 4 | 4 | 100 % | 100 % | Pfadzahl nicht widerlegt | +| `GoeChargerDataUpdateCoordinator._check_for_ws_task_and_cancel_if_running` (Koordinator) | 4 | 10 | 3 | 100 % | 100 % | es fehlen mindestens 1 Basispfade | +| `GoeChargerDataUpdateCoordinator._async_watchdog_check` (Koordinator) | 4 | 4 | 3 | 100 % | 83 % | es fehlen mindestens 1 Basispfade | +| `TargetedEvent.__init__` (Bridge) | 4 | 30 | 2 | 100 % | 75 % | es fehlen mindestens 2 Basispfade | +| `ExpiringStore._cleanup` (Bridge) | 4 | 22 | 2 | 100 % | 100 % | es fehlen mindestens 2 Basispfade | +| `GoeChargerApiV2Bridge.ws_close` (Bridge) | 4 | 25 | 3 | 100 % | 100 % | es fehlen mindestens 1 Basispfade | +| `GoeChargerApiV2Bridge._ws_decode_message` (Bridge) | 4 | 88 | 4 | 86 % | 83 % | Pfadzahl nicht widerlegt | + +### 6.1 Nicht genommene Zweige in den wichtigsten Pfaden +Je Funktion die Zeilen, an denen mindestens ein Ausgang nie genommen wurde (bis zu sechs, Auswahl der Funktionen mit CC ab 8). + +**`GoeChargerApiV2Bridge.ws_connect`** (pygoecharger_ha/__init__.py): Z. 786 `if a_event is not None and not a_event.is_set():`; Z. 807 `if a_event is not None and not a_event.is_set():`; Z. 830 `if 'type' in self._ws_device_info:`; Z. 841 `if a_event is not None and not a_event.is_set():`; Z. 860 `if a_event is not None and not a_event.is_set():`; Z. 865 `if not hasattr(self, 'access_password') or not self.access_password:` + +**`GoeChargerDataUpdateCoordinator.read_versions`** (__init__.py): Z. 713 `if lc_model_type is not None:` + +**`check_and_write_to_16a`** (__init__.py): Z. 218 `if hass is not None:`; Z. 220 `if a_entity_reg is not None:`; Z. 271 `if a_res_key not in keys_to_patch:`; Z. 280 `if a_res_key not in keys_to_patch:` + +**`GoeChargerApiV2Bridge.read_all_states`** (pygoecharger_ha/__init__.py): Z. 338 `elif self.isController:`; Z. 377 `if len(self._states) > 0:` + +**`GoeChargerApiV2Bridge.extract_ws_message_data`** (pygoecharger_ha/__init__.py): Z. 1050 `if 'requestId' in data:`; Z. 1056 `if status_data and len(status_data) > 0:`; Z. 1059 `if the_request_payload is not None:`; Z. 1064 `if the_request_payload is not None:` + +**`GoeChargerBaseEntity.__init__`** (__init__.py): Z. 881 `elif len(description.tuple_idx) > 0:` + +**`async_migrate_entry`** (__init__.py): Z. 75 `if config_entry.data is not None and len(config_entry.data) > 0:`; Z. 77 `if config_entry.options is not None and len(config_entry.options):`; Z. 89 `if a_device_reg is not None:` + +**`GoeChargerDataUpdateCoordinator.call_later_update_device_registry`** (__init__.py): Z. 380 `if self.hass is not None:`; Z. 386 `if a_device_reg is not None:`; Z. 387 `if hasattr(a_device_reg, "async_get_device_by_identifier"):` + +**`GoeChargerApiV2Bridge.read_all_config`** (pygoecharger_ha/__init__.py): Z. 404 `if a_key in self._ws_states:` + +**`GoeChargerDataUpdateCoordinator._async_update_data`** (__init__.py): Z. 609 `if self.bridge.ws_connected and self._force_classic_requests is False:`; Z. 623 `if new_data is not None and len(new_data) > 0:` + +**`GoeChargerDataUpdateCoordinator.__init__`** (__init__.py): Z. 363 `if config_entry.data.get(CONF_DELAY, False):` + +**`GoeChargerApiV2FlowHandler.async_step_user_wan`** (config_flow.py): Z. 209 `if len(a_user_pwd) == 0 and CONF_PASSWORD in user_input:` + +## 7. Nicht ausgeführte Funktionen im Kern + +Funktionen der Kernmodule, die kein Test erreicht. + +| Funktion | CC | +|---|---| +| `GoeChargerBaseEntity._friendly_name_internal` (__init__.py) | 10 | +| `GoeChargerBaseEntity._name_internal` (__init__.py) | 1 | +| `GoeChargerBaseEntity.device_info` (__init__.py) | 1 | +| `GoeChargerBaseEntity.available` (__init__.py) | 1 | + +## 8. Plattformen und Hilfsdateien + +Die Plattformen sind jetzt getestet (Wertdarstellung und Schreiben der Entitäten). Nicht ausgeführt bleiben das Anlegen der Entitäten (`async_setup_entry` je Plattform) und der Anzeigename in `entity.py`: `binary_sensor.py: async_setup_entry`, `button.py: async_setup_entry`, `entity.py: CustomFriendlyNameEntity._Entity__async_calculate_state`, `number.py: async_setup_entry`, `number.py: GoeChargerNumber.async_added_to_hass`, `select.py: async_setup_entry`, `sensor.py: async_setup_entry`, `switch.py: async_setup_entry`. + +## 9. Einordnung + +- **Pfadabdeckung:** Vollständige Pfadabdeckung ist hier weder erreichbar noch ein sinnvolles Ziel. Mit 98 von 580 Basispfaden der Kernmodule (17 %) bestimmen `ws_connect` (CC 61) und `read_versions` des Koordinators (CC 37) die Kennzahl; für sie wurden 27 und 21 verschiedene Pfade beobachtet. Funktionen mit sehr hoher CC sind in dieser Form schwer vollständig zu testen. Das ist ein Hinweis auf die Struktur des Codes, kein Vorwurf an die Tests. +- **Das Ziel von 60 % ist erreicht** (alle Funktionen 61 %, Kernmodule 62 %), aber die Zahl ist eine **obere Schranke** und lässt sich durch Tests mit nur leicht verschiedenen Eingaben steigern, ohne dass die Prüfung besser wird. Deshalb gelten die Mutationsprüfungen als Gegenprobe: 33 gezielte Code-Änderungen, alle wurden von den Tests erkannt (eine Lücke wurde erst nach einem zusätzlichen Test geschlossen). +- **Zeilen- und Zweigabdeckung sagen nichts über die Qualität der Prüfungen.** Alle Tests laufen mit Attrappen und sagen nichts darüber, ob der echte Charger sich so verhält. + +## 10. Empfehlungen (nach Wirkung) + +1. K1 und K2 zuerst: die Tabellen in Abschnitt 5.1 listen, wo noch Basispfade sicher fehlen. Dort stehen auch nicht genommene Fehlerpfade. +2. Fehlende Tests für bekannte Risiken: R20 (alte Werte nach fehlgeschlagenem Lesen, Cloud-Betrieb) und R7 (Konfigurationsintervall). +3. Lesenden Abgleich am echten Charger durchführen; erst er belegt, dass die Attrappen dem Gerät entsprechen. +4. Eine Untergrenze für die Abdeckung in den Lauf einbauen (zum Beispiel Zweigabdeckung der Kernmodule), damit sie nicht unbemerkt sinkt. +5. `ws_connect` und `read_versions` zerlegen, wenn sie später geändert werden; erst dann lassen sich ihre Pfade sinnvoll einzeln prüfen. diff --git a/tests/tools/README.md b/tests/tools/README.md new file mode 100644 index 0000000..2f81ade --- /dev/null +++ b/tests/tools/README.md @@ -0,0 +1,27 @@ +# Test tools + +| File | Purpose | +|---|---| +| `pathtracer.py` | pytest plugin (`-p pathtracer`) that records which distinct execution paths each function takes (needs Python 3.12+, uses `sys.monitoring`). Empirical, not a proof of path coverage. | +| `criticality.py` | Criticality class (K1 to K4) of every function, with a reason. A judgement, not a measurement. | +| `pathmetric.py` | Prints the path coverage (upper bound of basis paths) per criticality class from a path-trace file. | +| `build_report.py` | Builds `testreport.md` from coverage, junit and path-trace data. | +| `measure.sh` | Runs everything and builds the report. | +| `release_check.sh` | Tests a release (or branch) of the upstream integration with the tests of this branch. Used by the workflow `.github/workflows/regression-on-release.yaml`. | + +Needs `pytest-cov` and `radon` in addition to `requirements_dev.txt`. + +## Testing every new upstream release automatically + +`.github/workflows/regression-on-release.yaml` checks every three hours for a new release of `marq24/ha-goecharger-api2`. A new release is cloned fresh, the tests of this branch are copied into the clone, and the blocking tests run (`release_check.sh`). The result appears in the job summary; a red run means a blocking test failed. After the run the tag `tested/` is set in the fork, so a release is tested once. The summary also lists known-bug tests that now pass, which means the bug looks fixed in that version. + +Things to know: +- GitHub cannot start a workflow in a fork when another repository publishes a release. The workflow therefore polls. +- Scheduled workflows only run from the **default branch**. Either make `tests/regression-suite` the default branch of the fork, or copy the workflow file to `main`. Actions must be enabled for the fork (Actions tab). +- Nothing is rebased or merged by the workflow. It always tests a fresh clone of the upstream version, so the test branch does not have to follow upstream. The last step tries to fast-forward the fork's `main` to upstream and may fail harmlessly. +- The tests follow upstream `main`. An older release can fail a test that pins behaviour introduced later (for example the 16 A rule since commit `98af3e0` fails on release 2026.10.8), and a release that lacks functions the tests import cannot be collected. The summary says which case happened. +- Run it by hand: `tests/tools/release_check.sh [tag-or-branch]` (needs the test dependencies and the GitHub CLI for the latest release). + +## Home Assistant version of the tests + +`requirements_dev.txt` pins `pytest-homeassistant-custom-component` (currently 0.13.375, which brings Home Assistant 2026.10.4). A red run is therefore a change in the code under test, not a new Home Assistant. To see what a Home Assistant update breaks, start `regression-on-release.yaml` by hand with `home_assistant = latest`. To move the pin on purpose: run the suite with the new version, fix what broke, then raise the number in `requirements_dev.txt`. The tests run against the newest Home Assistant only; the minimum version named in `hacs.json` (2023.8.0) is not tested. diff --git a/tests/tools/build_report.py b/tests/tools/build_report.py new file mode 100644 index 0000000..39fb992 --- /dev/null +++ b/tests/tools/build_report.py @@ -0,0 +1,294 @@ +"""Builds testreport.md from coverage JSON, junit XML, pathtrace JSON and radon complexity. + +Usage: python build_report.py +""" +import json +import os +import subprocess +import sys +import xml.etree.ElementTree as ET +from collections import defaultdict +from datetime import date +from pathlib import Path + +sys.path.insert(0, str(Path(__file__).parent)) +from criticality import classify # noqa: E402 + +report_dir, checkout, src_commit, tests_commit = Path(sys.argv[1]), Path(sys.argv[2]).resolve(), sys.argv[3], sys.argv[4] +PKG = "custom_components/goecharger_api2/" +CORE = ["__init__.py", "pygoecharger_ha/__init__.py", "service.py", "config_flow.py"] +PLATFORMS = ["binary_sensor.py", "button.py", "number.py", "select.py", "sensor.py", "switch.py", "entity.py"] + + +def rel(path): + return path.split(PKG)[-1] + + +cov = json.loads((report_dir / "coverage-all.json").read_text()) +cov_block = json.loads((report_dir / "coverage-blocking.json").read_text())["totals"] +def load_trace(name): + raw = json.loads((report_dir / name).read_text()) + return {(rel(k.split("::")[0]), k.split("::")[1]): v for k, v in raw.items()} + + +trace = load_trace("pathtrace-all.json") +base_trace = load_trace("pathtrace-baseline.json") +base_cov = json.loads((report_dir / "coverage-baseline.json").read_text())["totals"] +sources = {rel(p): (checkout / p).read_text().splitlines() if (checkout / p).exists() else + (checkout / PKG / rel(p)).read_text().splitlines() for p in cov["files"]} + +radon = json.loads(subprocess.run(["radon", "cc", "-s", "-j", str(checkout / PKG)], capture_output=True, text=True).stdout) +cc = {} +for file, blocks in radon.items(): + for block in blocks: + entries = [block] + block.get("methods", []) if block["type"] == "class" else [block] + for e in entries: + if e["type"] in ("function", "method"): + name = f"{e['classname']}.{e['name']}" if e.get("classname") else e["name"] + cc[(rel(file), name)] = e["complexity"] + +funcs = [] +for path, data in cov["files"].items(): + f = rel(path) + for qual, fd in data["functions"].items(): + if not qual or (f, qual) not in cc: + continue + s = fd["summary"] + n_paths = trace.get((f, qual), {}).get("distinct_paths", 0) + calls = trace.get((f, qual), {}).get("calls", 0) + base_paths = base_trace.get((f, qual), {}).get("distinct_paths", 0) + funcs.append(dict(file=f, name=qual, cc=cc[(f, qual)], stm=s["num_statements"], base_paths=base_paths, + klass=classify(f, qual)[0], why=classify(f, qual)[1], covered_lines=s["covered_lines"], + cov_branches=s["covered_branches"], + line=s["percent_statements_covered"], br=s["num_branches"], brc=s["covered_branches"], + paths=n_paths, calls=calls, missing=fd["missing_branches"])) + + +def pct(a, b): + return f"{100 * a / b:.0f} %" if b else "–" + + +def path_bound(rows): + total = sum(r["cc"] for r in rows) + return sum(min(r["paths"], r["cc"]) for r in rows), total + + +# ---------------------------------------------------------------------------------------- results +junit = ET.parse(report_dir / "junit-all.xml").getroot() +suite = junit if junit.tag == "testsuite" else junit.find("testsuite") +by_file = defaultdict(lambda: [0, 0, 0.0]) +for tc in suite.iter("testcase"): + name = tc.get("classname").split(".")[-1] + by_file[name][0] += 1 + by_file[name][2] += float(tc.get("time")) + if tc.find("failure") is not None or tc.find("error") is not None: + by_file[name][1] += 1 + +t = cov["totals"] +core_rows = [r for r in funcs if r["file"] in CORE] +all_cov, all_cc = path_bound(funcs) +core_cov, core_cc = path_bound(core_rows) +never = [r for r in funcs if r["calls"] == 0] +short = [r for r in funcs if r["calls"] > 0 and r["paths"] < r["cc"]] +enough = [r for r in funcs if r["calls"] > 0 and r["paths"] >= r["cc"]] + +failures = int(suite.get("failures")) + int(suite.get("errors")) +passed = int(suite.get("tests")) - failures +NEW_BEHAVIOUR = int(suite.get("tests")) - by_file["test_api_contract"][0] +BASE_ALL = pct(sum(min(r["base_paths"], r["cc"]) for r in funcs), all_cc) +BASE_CORE = pct(sum(min(r["base_paths"], r["cc"]) for r in core_rows), core_cc) +out = [] +w = out.append +ERROR_WORDS = ("except", "raise", "error", "warning", "fail", "invalid", "timeout", "denied", "unavailable") + + +def crit_section(): + names = ["K1 kritisch", "K2 hoch", "K3 mittel", "K4 niedrig"] + w("""## 5. Pfade nach Kritikalität + +**Maßstab.** Kritikalität ist die Auswirkung eines falschen Pfades auf den Nutzer, nicht die Häufigkeit. Die Einstufung gilt für eine ganze Funktion und damit für alle ihre Pfade. Die Zuordnung steht in `tests/tools/criticality.py` (mit Begründung je Funktion) und ist eine Einschätzung, keine Messung. + +| Klasse | Bedeutung | Beispiele | +|---|---|---| +| **K1 kritisch** | Ändert den Charger, seine Grenzen oder die HA-Konfiguration; ein falscher Pfad kann den Charger sperren, falsche Werte senden, Geräte löschen oder Dienste entfernen | `write_value_to_key`, `stop_charging`, `set_pv_data`, `check_and_write_to_16a`, `async_migrate_entry` | +| **K2 hoch** | Verbindung, Anmeldung, Erholung, Start; ein falscher Pfad macht die Integration unverfügbar oder zeigt alte Daten | `ws_connect`, `_async_update_data`, `async_setup_entry`, Config-Flow | +| **K3 mittel** | Korrektheit dessen, was angezeigt oder erkannt wird, ohne Schreiben | Wertdarstellung der Entitäten, `is_valid_charger_entity` | +| **K4 niedrig** | Hilfsfunktionen, Namen, Symbole, Logging | `_ws_normalize_value`, `unique_id`, Anzeigename | + +| Klasse | Funktionen | CC (Basispfade) | Pfade vorher | Pfade jetzt (obere Schranke) | Zeilen | Zweige | nie ausgeführt | +|---|---|---|---|---|---|---|---|""") + for k in names: + rows = [r for r in funcs if r["klass"] == k] + cc_sum = sum(r["cc"] for r in rows) + now = sum(min(r["paths"], r["cc"]) for r in rows) + before = sum(min(r["base_paths"], r["cc"]) for r in rows) + stm = sum(r["stm"] for r in rows) + w(f"| **{k}** | {len(rows)} | {cc_sum} | {pct(before, cc_sum)} | **{pct(now, cc_sum)}** ({now} von {cc_sum}) | " + f"{pct(sum(r['covered_lines'] for r in rows), stm)} | {pct(sum(r['cov_branches'] for r in rows), sum(r['br'] for r in rows))} | " + f"{sum(1 for r in rows if r['calls'] == 0)} |") + w(""" +Lesehilfe: „Pfade jetzt“ ist die obere Schranke der Basispfadabdeckung (Abschnitt 2). Die Spalten „Zeilen“ und „Zweige“ stehen für dieselben Funktionen und liegen viel höher; der Unterschied zeigt, wie viel strenger die Pfadabdeckung ist. + +### 5.1 Offene Lücken in K1 und K2 + +Funktionen, bei denen **sicher** Basispfade fehlen (beobachtete Pfade unter der CC), geordnet nach Klasse und Zahl der fehlenden Pfade. „Fehlerpfad“ markiert nicht genommene Zweige, deren Zeile nach einem Fehlerfall aussieht (Schlüsselwörter wie `except`, `raise`, `error`, `warning`); das ist eine Heuristik. +""") + for k in names[:2]: + rows = sorted((r for r in funcs if r["klass"] == k and r["paths"] < r["cc"]), + key=lambda r: -(r["cc"] - r["paths"])) + w(f"**{k}**\n\n| Funktion | CC | Pfade | fehlen mind. | Aufrufe | Warum kritisch | Nicht genommene Zweige |\n|---|---|---|---|---|---|---|") + for r in rows[:14]: + src = sources[r["file"]] + parts = [] + for frm, to in sorted({(m[0], m[1]) for m in r["missing"] if m[0] > 0})[:4]: + text = src[frm - 1].strip() + target = src[to - 1].strip() if to > 0 else "" + kind = "Fehlerpfad" if any(wd in (text + " " + target).lower() for wd in ERROR_WORDS) else "Normalpfad" + parts.append(f"Z.{frm} `{text[:46]}` ({kind})") + how = "nie ausgeführt" if r["calls"] == 0 else str(r["calls"]) + w(f"| `{r['name']}` | {r['cc']} | {r['paths']} | {r['cc'] - r['paths']} | {how} | {r['why']} | {'
'.join(parts) if parts else '–'} |") + w("") +w(f"""# Testreport und Testabdeckung: `ha-goecharger-api2` + +| | | +|---|---| +| **Datum** | {date.today().isoformat()} | +| **Geprüfter Code** | `marq24/ha-goecharger-api2` `{os.environ.get('SOURCE_REF', 'main')}`, Commit `{src_commit}` | +| **Tests** | Fork `OliEder/ha-goecharger-api2`, Branch `tests/regression-suite`, Commit `{tests_commit}` | +| **Umgebung** | Python 3.14.3, Home Assistant 2026.10.4, coverage {cov['meta']['version']}, radon 6.0.1 | +| **Rohdaten** | `coverage-all.json`, `coverage-blocking.json`, `junit-all.xml`, `pathtrace-all.json`, HTML-Bericht in `html-all/index.html` | + +## 1. Zusammenfassung + +| Kennzahl | Wert | +|---|---| +| Tests gesammelt / bestanden / fehlgeschlagen | {suite.get('tests')} / {passed} / {failures} (alle beabsichtigt: {failures - 14} `known_bug`, 14 `needs_decision`) | +| Blockierende Menge (ohne die beiden Marker) | {passed} von {passed} bestanden | +| **Zeilenabdeckung** (alle Tests) | **{t['percent_statements_covered']:.1f} %** ({t['covered_lines']} von {t['num_statements']} Anweisungen) | +| **Zweigabdeckung** (alle Tests) | **{t['percent_branches_covered']:.1f} %** ({t['covered_branches']} von {t['num_branches']} Zweigen) | +| **Pfadabdeckung** (obere Schranke der Basispfade, alle Funktionen) | **{pct(all_cov, all_cc)}** ({all_cov} von {all_cc}) | +| **Pfadabdeckung** (obere Schranke, nur die vier Kernmodule) | **{pct(core_cov, core_cc)}** ({core_cov} von {core_cc}) | +| Funktionen: nie ausgeführt / mit zu wenigen Pfaden / Pfadzahl nicht widerlegt | {len(never)} / {len(short)} / {len(enough)} (von {len(funcs)}) | + +**Entwicklung gegenüber dem ersten Report (gleicher Tag, Stand vor dem Ausbau der Tests)** + +| Kennzahl | Vorher | Jetzt | +|---|---|---| +| Verhaltens- und Fehlertests (ohne Vertragsprüfungen je Schlüssel) | 125 | {NEW_BEHAVIOUR} | +| Zeilenabdeckung | {base_cov['percent_statements_covered']:.1f} % | {t['percent_statements_covered']:.1f} % | +| Zweigabdeckung | {base_cov['percent_branches_covered']:.1f} % | {t['percent_branches_covered']:.1f} % | +| Pfadabdeckung (obere Schranke), alle Funktionen | {BASE_ALL} | {pct(all_cov, all_cc)} | +| Pfadabdeckung (obere Schranke), Kernmodule | {BASE_CORE} | {pct(core_cov, core_cc)} | + +**Was das bedeutet:** Die Tests prüfen jetzt auch die Plattformen (Wertdarstellung, Schreiben) und die Laufzeitpfade des Koordinators. Zeilen- und Zweigabdeckung sind hoch, die Pfadabdeckung ist deutlich niedriger, weil sie verlangt, dass jede Funktion **viele verschiedene Kombinationen** von Entscheidungen durchläuft. Die Pfadzahl ist eine obere Schranke und kein Beleg für Vollständigkeit (Abschnitt 2). + +## 2. Begriffe und Messverfahren (wichtig für die Einordnung) + +| Kennzahl | Bedeutung | Gemessen mit | +|---|---|---| +| Zeilenabdeckung | Anteil der ausgeführten Anweisungen | `coverage.py` | +| Zweigabdeckung | Anteil der genommenen Verzweigungsausgänge (`if`/`else`, Schleife betreten/übersprungen, `try`/`except`). Entspricht der Entscheidungsabdeckung. | `coverage.py --cov-branch` | +| Pfadabdeckung | Anteil der möglichen Ausführungswege durch eine Funktion. **Wird von `coverage.py` nicht gemessen** und ist bei Schleifen und vielen Entscheidungen praktisch nicht vollständig erreichbar (Zahl der Pfade wächst exponentiell). | siehe unten | + +**Wie die Pfadzahlen entstehen.** Ein eigenes Test-Plugin (`tests/tools/pathtracer.py`, nutzt `sys.monitoring`) zeichnet für **jeden Aufruf** einer Funktion die Folge der ausgeführten Zeilen auf. Schleifen werden verkürzt: eine Zeile zählt je Aufruf höchstens zweimal. Danach zählt es, wie viele **verschiedene** Pfade je Funktion im Testlauf vorkamen. + +Diese Zahl wird mit der **zyklomatischen Komplexität** (CC, `radon`) verglichen. Die CC ist die Zahl der linear unabhängigen Basispfade einer Funktion. Daraus folgt eine **sichere Aussage in eine Richtung**: +- Wurden weniger verschiedene Pfade beobachtet als CC, sind **sicher nicht** alle Basispfade getestet. +- Wurden mindestens CC Pfade beobachtet, ist das **kein Beleg** für vollständige Abdeckung. Die Pfade können sich in denselben Entscheidungen ähneln. +- Die Kennzahl „Pfadabdeckung (obere Schranke)“ summiert je Funktion `min(beobachtete Pfade, CC)` und teilt durch die Summe der CC. Die tatsächliche Basispfadabdeckung ist **höchstens so hoch**. + +Grenzen: Die Messung ist empirisch und heuristisch. Verschachtelte Funktionen werden der umgebenden Funktion zugerechnet oder nicht erfasst. Nur eine Tracing-Variante (Zeilenfolgen) wurde verwendet. + +## 3. Testergebnisse + +| Datei | Tests | fehlgeschlagen | Zeit | +|---|---|---|---|""") +for name, (n, fail, tm) in sorted(by_file.items()): + w(f"| `{name}.py` | {n} | {fail} | {tm:.2f} s |") +w(f""" +Hinweis: `test_api_contract.py` enthält 420 parametrisierte Vertragsprüfungen (je Schlüssel). Verhaltens- und Fehlerpfadtests insgesamt: {int(suite.get('tests')) - by_file['test_api_contract'][0]}. + +**Gesamtdauer** {suite.get('time')} s. + +## 4. Abdeckung je Datei + +| Datei | Anweisungen | Zeilenabdeckung | Zweige | Zweigabdeckung | CC (Summe) | Pfade (obere Schranke) | +|---|---|---|---|---|---|---|""") +for path, data in sorted(cov["files"].items(), key=lambda kv: rel(kv[0])): + f = rel(path) + s = data["summary"] + rows = [r for r in funcs if r["file"] == f] + c, total = path_bound(rows) + w(f"| `{f}` | {s['num_statements']} | {s['percent_statements_covered']:.0f} % | {s['num_branches']} | " + f"{pct(s['covered_branches'], s['num_branches'])} | {total} | {pct(c, total)} |") +w(f""" +**Vergleich:** Nur die blockierenden Tests: Zeilen {cov_block['percent_statements_covered']:.1f} %, Zweige {cov_block['percent_branches_covered']:.1f} %. Die beabsichtigt roten Tests (bekannte Fehler und Abweichungen) verändern die Abdeckung kaum ({t['percent_statements_covered'] - cov_block['percent_statements_covered']:.1f} Prozentpunkte bei den Zeilen). + +""") +crit_section() +w(f""" +## 6. Kritische Funktionen (nach Komplexität) + +Kernmodule (`__init__.py` mit Koordinator, `pygoecharger_ha/__init__.py` mit Bridge, `service.py`, `config_flow.py`), sortiert nach Komplexität, gezeigt ab CC 4. „Pfade“ = beobachtete verschiedene Pfade, „CC“ = Basispfade. + +| Funktion | CC | Aufrufe | Pfade | Zeilen | Zweige | Bewertung | +|---|---|---|---|---|---|---|""") +for r in sorted(core_rows, key=lambda r: -r["cc"]): + if r["cc"] < 4: + continue + verdict = "nie ausgeführt" if r["calls"] == 0 else (f"es fehlen mindestens {r['cc'] - r['paths']} Basispfade" if r["paths"] < r["cc"] else "Pfadzahl nicht widerlegt") + if r["file"] == "__init__.py": + short_name = ("Entität" if r["name"].startswith("GoeChargerBaseEntity") else + "Koordinator" if r["name"].startswith("GoeChargerDataUpdateCoordinator") else "Setup") + else: + short_name = {"pygoecharger_ha/__init__.py": "Bridge", "service.py": "Dienst", "config_flow.py": "Flow"}[r["file"]] + w(f"| `{r['name']}` ({short_name}) | {r['cc']} | {r['calls']} | {r['paths']} | {r['line']:.0f} % | {pct(r['brc'], r['br'])} | {verdict} |") +w(""" +### 6.1 Nicht genommene Zweige in den wichtigsten Pfaden +Je Funktion die Zeilen, an denen mindestens ein Ausgang nie genommen wurde (bis zu sechs, Auswahl der Funktionen mit CC ab 8). +""") +for r in sorted(core_rows, key=lambda r: -r["cc"]): + if r["cc"] < 8 or not r["missing"] or r["calls"] == 0: + continue + lines = sorted({m[0] for m in r["missing"] if m[0] > 0})[:6] + src = sources[r["file"]] + w(f"**`{r['name']}`** ({r['file']}): " + "; ".join(f"Z. {n} `{src[n - 1].strip()[:70]}`" for n in lines) + "\n") + +w("""## 7. Nicht ausgeführte Funktionen im Kern + +Funktionen der Kernmodule, die kein Test erreicht. +""") +rows = [r for r in core_rows if r["calls"] == 0] +w("| Funktion | CC |\n|---|---|") +for r in sorted(rows, key=lambda r: -r["cc"]): + w(f"| `{r['name']}` ({r['file']}) | {r['cc']} |") +WS = ("pygoecharger_ha/__init__.py", "GoeChargerApiV2Bridge.ws_connect") +RV = ("__init__.py", "GoeChargerDataUpdateCoordinator.read_versions") +WS_CC, RV_CC = cc[WS], cc[RV] +WS_N, RV_N = trace.get(WS, {}).get("distinct_paths", 0), trace.get(RV, {}).get("distinct_paths", 0) +BIG2 = WS_CC + RV_CC +BIG2_PCT = round(100 * BIG2 / core_cc) +ALL_PCT, CORE_PCT = pct(all_cov, all_cc), pct(core_cov, core_cc) +PLATFORM_GAPS = ", ".join(f"`{r['file']}: {r['name']}`" for r in funcs if r['file'] in PLATFORMS and r['calls'] == 0) or "keine" +w(f""" +## 8. Plattformen und Hilfsdateien + +Die Plattformen sind jetzt getestet (Wertdarstellung und Schreiben der Entitäten). Nicht ausgeführt bleiben das Anlegen der Entitäten (`async_setup_entry` je Plattform) und der Anzeigename in `entity.py`: {PLATFORM_GAPS}. + +## 9. Einordnung + +- **Pfadabdeckung:** Vollständige Pfadabdeckung ist hier weder erreichbar noch ein sinnvolles Ziel. Mit {BIG2} von {core_cc} Basispfaden der Kernmodule ({BIG2_PCT} %) bestimmen `ws_connect` (CC {WS_CC}) und `read_versions` des Koordinators (CC {RV_CC}) die Kennzahl; für sie wurden {WS_N} und {RV_N} verschiedene Pfade beobachtet. Funktionen mit sehr hoher CC sind in dieser Form schwer vollständig zu testen. Das ist ein Hinweis auf die Struktur des Codes, kein Vorwurf an die Tests. +- **Das Ziel von 60 % ist erreicht** (alle Funktionen {ALL_PCT}, Kernmodule {CORE_PCT}), aber die Zahl ist eine **obere Schranke** und lässt sich durch Tests mit nur leicht verschiedenen Eingaben steigern, ohne dass die Prüfung besser wird. Deshalb gelten die Mutationsprüfungen als Gegenprobe: 33 gezielte Code-Änderungen, alle wurden von den Tests erkannt (eine Lücke wurde erst nach einem zusätzlichen Test geschlossen). +- **Zeilen- und Zweigabdeckung sagen nichts über die Qualität der Prüfungen.** Alle Tests laufen mit Attrappen und sagen nichts darüber, ob der echte Charger sich so verhält. + +## 10. Empfehlungen (nach Wirkung) + +1. K1 und K2 zuerst: die Tabellen in Abschnitt 5.1 listen, wo noch Basispfade sicher fehlen. Dort stehen auch nicht genommene Fehlerpfade. +2. Fehlende Tests für bekannte Risiken: R20 (alte Werte nach fehlgeschlagenem Lesen, Cloud-Betrieb) und R7 (Konfigurationsintervall). +3. Lesenden Abgleich am echten Charger durchführen; erst er belegt, dass die Attrappen dem Gerät entsprechen. +4. Eine Untergrenze für die Abdeckung in den Lauf einbauen (zum Beispiel Zweigabdeckung der Kernmodule), damit sie nicht unbemerkt sinkt. +5. `ws_connect` und `read_versions` zerlegen, wenn sie später geändert werden; erst dann lassen sich ihre Pfade sinnvoll einzeln prüfen. +""") +(report_dir / "testreport.md").write_text("\n".join(out)) +print("written", report_dir / "testreport.md", len("\n".join(out).splitlines()), "lines") +print("all:", all_cov, all_cc, " core:", core_cov, core_cc, " never/short/enough:", len(never), len(short), len(enough)) diff --git a/tests/tools/build_site.py b/tests/tools/build_site.py new file mode 100644 index 0000000..6119638 --- /dev/null +++ b/tests/tools/build_site.py @@ -0,0 +1,98 @@ +"""Builds the static site for GitHub Pages from the latest results. + +Usage: python build_site.py --report

\ + --testcases [--regression ] [--docs ] +Needs the package `markdown`. Every input is optional except the site directory; a missing part is left out. +""" +import argparse +import html +import re +import shutil +from datetime import datetime, timezone +from pathlib import Path + +import markdown + +CSS = """ +:root{--bg:#fff;--fg:#1f2328;--muted:#59636e;--line:#d1d9e0;--head:#f6f8fa;--link:#0969da} +@media (prefers-color-scheme:dark){:root{--bg:#0d1117;--fg:#e6edf3;--muted:#9198a1;--line:#3d444d;--head:#151b23;--link:#4493f8}} +body{margin:0;background:var(--bg);color:var(--fg);font:16px/1.55 system-ui,sans-serif} +main{max-width:60rem;margin:0 auto;padding:1rem 16px 4rem} +nav{border-bottom:1px solid var(--line);background:var(--head)} +nav div{max-width:60rem;margin:0 auto;padding:.7rem 16px;display:flex;gap:1.2rem;flex-wrap:wrap} +a{color:var(--link)}h1,h2,h3{line-height:1.25} +table{border-collapse:collapse;display:block;overflow-x:auto;max-width:100%} +th,td{border:1px solid var(--line);padding:.3rem .6rem;text-align:left;vertical-align:top} +th{background:var(--head)}code{background:var(--head);padding:.1rem .3rem;border-radius:4px} +footer{color:var(--muted);font-size:.9rem;margin-top:3rem} +""" +PAGES = [("index.html", "Übersicht"), ("testreport.html", "Testbericht"), ("testcases.html", "Testfälle"), + ("testkonzept.html", "Testkonzept"), ("arc42.html", "Architektur (arc42)"), + ("coverage/index.html", "Abdeckung im Detail")] +# the diagrams of the arc42 document are Mermaid; the library is loaded from a CDN with a fixed version +MERMAID = ('') +DOC_FILES = {"arc42.html": ("Architektur (arc42)", "arc42-ha-goecharger-api2.md"), + "testkonzept.html": ("Testkonzept", "testkonzept-ha-goecharger-api2.md")} + + +def render(site, name, title, body_md, available, toc_levels=False): + nav = "".join(f'{label}' for href, label in PAGES if href in available) + md = markdown.Markdown(extensions=["tables", "fenced_code", "toc"]) + body = md.convert(body_md) + mermaid = '' in body + if mermaid: + body = re.sub(r'
(.*?)
', r'
\1
', body, flags=re.S) + if toc_levels: + entries = [t for top in md.toc_tokens for t in (top["children"] if top["level"] == 1 else [top])] + items = "".join(f'
  • {html.escape(t["name"])}
  • ' for t in entries) + body = body.replace("", f"
    Inhalt
      {items}
    ", 1) + stamp = datetime.now(timezone.utc).strftime("%Y-%m-%d %H:%M UTC") + (site / name).write_text( + f'' + f"{html.escape(title)}" + f"
    {body}{MERMAID if mermaid else ''}
    Erzeugt am {stamp}. Die Tests wurden mit KI-Unterstützung (Claude Code) erstellt und von Oliver geprüft. " + f'Quelle: Fork OliEder/ha-goecharger-api2.' + f"
    ") + + +def main(): + ap = argparse.ArgumentParser() + ap.add_argument("site") + ap.add_argument("--report") + ap.add_argument("--testcases") + ap.add_argument("--regression") + ap.add_argument("--docs") + a = ap.parse_args() + site = Path(a.site) + site.mkdir(parents=True, exist_ok=True) + pages = {} + if a.report and (Path(a.report) / "testreport.md").exists(): + pages["testreport.html"] = ("Testbericht", (Path(a.report) / "testreport.md").read_text()) + if (Path(a.report) / "html-all").exists(): + shutil.copytree(Path(a.report) / "html-all", site / "coverage", dirs_exist_ok=True) + if a.testcases and Path(a.testcases).exists(): + pages["testcases.html"] = ("Testfälle", Path(a.testcases).read_text()) + for name, (title, filename) in DOC_FILES.items(): + if a.docs and (Path(a.docs) / filename).exists(): + pages[name] = (title, (Path(a.docs) / filename).read_text(), True) + index = ["# Regressionstests für ha-goecharger-api2", "", + "Tests und Berichte zur Home-Assistant-Integration [marq24/ha-goecharger-api2](https://github.com/marq24/ha-goecharger-api2) " + "(go-eCharger). Die Tests laufen in einem Fork gegen den Code des Original-Repos und ändern dieses nicht.", ""] + if a.regression and Path(a.regression).exists(): + index += ["## Letzter Lauf gegen eine Version des Original-Repos", "", Path(a.regression).read_text().replace("\n## ", "\n### ") + .replace("## Regression", "### Regression", 1), ""] + index += ["## Seiten", "", "- [Testbericht](testreport.html): Zeilen-, Zweig- und Pfadabdeckung, Kritikalität", + "- [Testfälle](testcases.html): jede Testfunktion in einer Zeile", + "- [Abdeckung im Detail](coverage/index.html): Zeile für Zeile", + "- [Testkonzept](testkonzept.html): Ziele, Methoden, Kennzahlen und ihre Grenzen", + "- [Architektur (arc42)](arc42.html): Aufbau der Integration, Risiken, Entscheidungen"] + available = set(pages) | {"index.html"} | ({"coverage/index.html"} if (site / "coverage").exists() else set()) + for name, (title, body, *toc) in pages.items(): + render(site, name, title, body, available, bool(toc)) + render(site, "index.html", "Regressionstests", "\n".join(index), available) + (site / ".nojekyll").write_text("") + print("site written to", site) + + +main() diff --git a/tests/tools/build_testcases.py b/tests/tools/build_testcases.py new file mode 100644 index 0000000..bccf86a --- /dev/null +++ b/tests/tools/build_testcases.py @@ -0,0 +1,55 @@ +"""Builds testcases.md: every test function on one line, with the number of cases and the result. + +Usage: python build_testcases.py + +The text of a line is the first line of the docstring of the test function; without a docstring it is the +function name written as a sentence. Parametrized tests are one line with the number of cases. A case that +fails on purpose (marker known_bug or needs_decision) is counted as "red on purpose". +""" +import ast +import sys +import xml.etree.ElementTree as ET +from collections import OrderedDict +from pathlib import Path + +junit, tests_dir, output = Path(sys.argv[1]), Path(sys.argv[2]), Path(sys.argv[3]) + + +def sentence(name): + text = name.removeprefix("test_").replace("_", " ") + return text[:1].upper() + text[1:] + + +results = OrderedDict() # (module, function) -> [total, red, skipped] +for case in ET.parse(junit).getroot().iter("testcase"): + function = case.get("name").split("[")[0] + entry = results.setdefault((case.get("classname").split(".")[-1], function), [0, 0, 0]) + entry[0] += 1 + if case.find("failure") is not None or case.find("error") is not None: + entry[1] += 1 + elif case.find("skipped") is not None: + entry[2] += 1 + +lines = ["# Testfälle", "", + "Eine Zeile je Testfunktion. Der Text ist die erste Zeile der Beschreibung im Code oder der Funktionsname als Satz. " + "„Rot“ bedeutet: das Ergebnis ist absichtlich rot (bekannter Fehler oder offene Entscheidung), nicht blockierend für die Freigabe.", ""] +total = red = skipped = 0 +for path in sorted(tests_dir.glob("test_*.py")): + tree = ast.parse(path.read_text()) + module_doc = (ast.get_docstring(tree) or path.stem).splitlines()[0] + rows = [] + for node in tree.body: + if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)) and node.name.startswith("test_"): + n, r, s = results.get((path.stem, node.name), [0, 0, 0]) + if n == 0: + continue + doc = (ast.get_docstring(node) or "").splitlines() + text = (doc[0] if doc else sentence(node.name)).replace("|", "\\|") + status = "rot" if r == n else (f"{r} rot" if r else ("übersprungen" if s == n else "grün")) + rows.append(f"| {text} | {n} | {status} |") + total, red, skipped = total + n, red + r, skipped + s + if rows: + lines += [f"## `{path.name}`", "", module_doc, "", "| Prüft | Fälle | Ergebnis |", "|---|---|---|", *rows, ""] +lines.insert(3, f"**{total} Fälle**, davon {red} absichtlich rot und {skipped} übersprungen.\n") +output.write_text("\n".join(lines) + "\n") +print(f"{total} cases in {output}") diff --git a/tests/tools/criticality.py b/tests/tools/criticality.py new file mode 100644 index 0000000..7fda285 --- /dev/null +++ b/tests/tools/criticality.py @@ -0,0 +1,115 @@ +"""Criticality of functions (and with them of their paths) of the integration. + +Rubric: criticality = impact of a wrong path on the user. Probability is not rated separately; it is +covered by looking at which functions run often (the report shows call counts). + +K1 critical : changes the charger, its limits or the HA configuration; a wrong path can lock the charger, + send wrong values, delete devices/entities or switch off the services. +K2 high : connection, login, recovery and start of the integration; a wrong path makes the integration + unavailable or shows stale data. +K3 medium : correctness of what is shown or detected (values, units, device type), without writing. +K4 low : helpers, naming, icons, logging, constructors of small helper classes. +""" +import re + +K1, K2, K3, K4 = "K1 kritisch", "K2 hoch", "K3 mittel", "K4 niedrig" + +# exact assignments: (file, qualified name without class prefix handling) -> (class, reason) +EXACT = { + # bridge: writing + ("pygoecharger_ha/__init__.py", "GoeChargerApiV2Bridge.write_value_to_key"): (K1, "schreibt einen Wert an den Charger (HTTP oder WebSocket)"), + ("pygoecharger_ha/__init__.py", "GoeChargerApiV2Bridge.write_multiple_values_to_keys"): (K1, "schreibt mehrere Werte (Fahrzeugauswahl)"), + ("pygoecharger_ha/__init__.py", "GoeChargerApiV2Bridge._write_values_int"): (K1, "wertet die Antwort des Chargers auf Schreibbefehle aus"), + ("pygoecharger_ha/__init__.py", "GoeChargerApiV2Bridge._ws_send_command"): (K1, "sendet und signiert Befehle per WebSocket"), + # coordinator: writing and limits + ("__init__.py", "GoeChargerDataUpdateCoordinator.async_write_key"): (K1, "zentraler Schreibpfad mit Fehlerpause"), + ("__init__.py", "GoeChargerDataUpdateCoordinator.async_write_multiple_keys"): (K1, "Mehrfach-Schreibpfad mit Fehlerpause"), + ("__init__.py", "GoeChargerDataUpdateCoordinator.handle_write_result"): (K1, "übernimmt das Schreibergebnis in die Daten"), + ("__init__.py", "check_and_write_to_16a"): (K1, "schreibt beim Start Stromgrenzen an den Charger"), + ("__init__.py", "GoeChargerDataUpdateCoordinator.check_for_16a_limit"): (K1, "startet das Zurückschreiben der 16-A-Grenze"), + ("__init__.py", "async_migrate_entry"): (K1, "löscht Geräte und damit Entitäten"), + ("__init__.py", "async_unload_entry"): (K1, "entfernt die Dienste set_pv_data und stop_charging"), + # services + ("service.py", "GoeChargerApiV2Service.set_pv_data"): (K1, "sendet PV-Daten; steuert das PV-Laden"), + ("service.py", "GoeChargerApiV2Service.stop_charging"): (K1, "sperrt und entsperrt den Charger"), + ("service.py", "GoeChargerApiV2Service.get_coordinator"): (K1, "wählt den Charger für Dienste"), + ("service.py", "_is_finite_number"): (K1, "verhindert ungültige Werte an den Charger"), + ("service.py", "add_config_entry_to_service"): (K1, "Register der Charger für die Dienste"), + ("service.py", "remove_config_entry_from_service"): (K1, "Register der Charger für die Dienste"), + ("service.py", "GoeChargerApiV2Service.__init__"): (K1, "Register der Charger für die Dienste"), + # entities that write + ("number.py", "GoeChargerNumber.async_set_native_value"): (K1, "schreibt Zahlenwerte (Ströme, Grenzen)"), + ("number.py", "GoeChargerNumber.check_amp_min_max"): (K1, "löst automatisch das Setzen von amp aus"), + ("select.py", "GoeChargerSelect.async_select_option"): (K1, "schreibt Auswahl, inklusive Fahrzeugprofil mit mehreren Werten"), + ("switch.py", "GoeChargerSwitch.async_turn_on"): (K1, "schaltet Einstellungen am Charger"), + ("switch.py", "GoeChargerSwitch.async_turn_off"): (K1, "schaltet Einstellungen am Charger"), + ("button.py", "GoeChargerApiV2Button.async_press"): (K1, "löst Aktionen am Charger aus"), + # connection, login, recovery, start + ("pygoecharger_ha/__init__.py", "GoeChargerApiV2Bridge.ws_connect"): (K2, "Anmeldung und Nachrichtenschleife der WebSocket-Verbindung"), + ("pygoecharger_ha/__init__.py", "GoeChargerApiV2Bridge.extract_ws_message_data"): (K2, "verarbeitet alle Daten und Antworten vom WebSocket"), + ("pygoecharger_ha/__init__.py", "GoeChargerApiV2Bridge._read_filtered_data"): (K2, "zentraler Lesepfad im Polling"), + ("pygoecharger_ha/__init__.py", "GoeChargerApiV2Bridge._read_all_data"): (K2, "Lesen aller Daten per HTTP"), + ("pygoecharger_ha/__init__.py", "GoeChargerApiV2Bridge.read_all_states"): (K2, "Statuslesen mit Hibernation"), + ("pygoecharger_ha/__init__.py", "GoeChargerApiV2Bridge.read_all_config"): (K2, "Konfigurationslesen mit Wiederholungs-Pause"), + ("pygoecharger_ha/__init__.py", "GoeChargerApiV2Bridge.read_all"): (K2, "Lesezyklus"), + ("pygoecharger_ha/__init__.py", "GoeChargerApiV2Bridge.read_versions"): (K2, "liest Geräte- und Versionsdaten beim Start"), + ("pygoecharger_ha/__init__.py", "GoeChargerApiV2Bridge.read_system"): (K2, "liest Systemdaten"), + ("pygoecharger_ha/__init__.py", "GoeChargerApiV2Bridge.__init__"): (K2, "bildet Adressen für LAN, Cloud und WebSocket"), + ("pygoecharger_ha/__init__.py", "GoeChargerApiV2Bridge._ws_compute_hashed_password"): (K2, "Passwort-Hash der Anmeldung"), + ("pygoecharger_ha/__init__.py", "GoeChargerApiV2Bridge._ws_compute_auth_hash"): (K2, "Antwort auf die Anmelde-Aufforderung"), + ("pygoecharger_ha/__init__.py", "bcryptjs_base64_encode_serial"): (K2, "Salz für die bcrypt-Anmeldung"), + ("pygoecharger_ha/__init__.py", "GoeChargerApiV2Bridge._ws_decode_message"): (K2, "liest WebSocket-Nachrichten"), + ("pygoecharger_ha/__init__.py", "GoeChargerApiV2Bridge.ws_close"): (K2, "schließt die Verbindung sauber"), + ("pygoecharger_ha/__init__.py", "GoeChargerApiV2Bridge.ws_check_last_update"): (K2, "erkennt eine tote Verbindung"), + ("pygoecharger_ha/__init__.py", "GoeChargerApiV2Bridge._ws_notify_for_new_data"): (K2, "gibt neue Daten an HA weiter"), + ("pygoecharger_ha/__init__.py", "TargetedEvent.check_and_set"): (K2, "meldet, dass die Startdaten vollständig sind"), + ("pygoecharger_ha/__init__.py", "TargetedEvent.__init__"): (K2, "legt die Pflichtschlüssel für den Start fest"), + ("__init__.py", "async_setup_entry"): (K2, "Start der Integration"), + ("__init__.py", "GoeChargerDataUpdateCoordinator._async_update_data"): (K2, "Lesezyklus mit Fehlerpause"), + ("__init__.py", "GoeChargerDataUpdateCoordinator._handle_client_connection_error"): (K2, "Fehlerzähler und Neustart"), + ("__init__.py", "GoeChargerDataUpdateCoordinator.start_websocket_and_wait_for_first_data"): (K2, "Start der WebSocket-Verbindung"), + ("__init__.py", "GoeChargerDataUpdateCoordinator.force_async_update_now"): (K2, "erzwingt neue WebSocket-Verbindung"), + ("__init__.py", "GoeChargerDataUpdateCoordinator._async_watchdog_check"): (K2, "Wiederverbindung"), + ("__init__.py", "GoeChargerDataUpdateCoordinator._check_for_ws_task_and_cancel_if_running"): (K2, "Wiederverbindung"), + ("__init__.py", "GoeChargerDataUpdateCoordinator.start_watchdog"): (K2, "Wiederverbindung"), + ("__init__.py", "GoeChargerDataUpdateCoordinator.stop_watchdog"): (K2, "Wiederverbindung"), + ("__init__.py", "GoeChargerDataUpdateCoordinator.trigger_restart_delayed"): (K2, "Neustart nach Dauerfehlern"), + ("__init__.py", "GoeChargerDataUpdateCoordinator.clear_data"): (K2, "Aufräumen beim Entladen"), + ("__init__.py", "GoeChargerDataUpdateCoordinator.read_versions"): (K2, "erkennt Gerätetyp, 16-A-Limit und Karten"), + ("__init__.py", "entry_update_listener"): (K2, "lädt den Entry bei Änderungen neu"), + ("config_flow.py", "GoeChargerApiV2FlowHandler._test_host"): (K2, "prüft Zugang beim Einrichten"), + ("config_flow.py", "GoeChargerApiV2FlowHandler._check_ws_connection_and_flags"): (K2, "prüft WebSocket beim Einrichten"), + ("config_flow.py", "GoeChargerApiV2FlowHandler.async_step_user_lan"): (K2, "Einrichtung lokal"), + ("config_flow.py", "GoeChargerApiV2FlowHandler.async_step_user_wan"): (K2, "Einrichtung Cloud"), + ("config_flow.py", "GoeChargerApiV2FlowHandler.async_step_reconfigure"): (K2, "Umkonfigurieren"), + ("config_flow.py", "GoeChargerApiV2FlowHandler.async_step_user"): (K2, "Einrichtung, Auswahl LAN oder Cloud"), +} + +# defaults by file for everything not listed +FILE_DEFAULT = { + "sensor.py": (K3, "Wertdarstellung"), + "binary_sensor.py": (K3, "Wertdarstellung"), + "number.py": (K3, "Wertdarstellung"), + "select.py": (K3, "Wertdarstellung"), + "switch.py": (K3, "Wertdarstellung"), + "button.py": (K4, "Hilfsfunktion"), + "entity.py": (K4, "Anzeigename"), + "pygoecharger_ha/keys.py": (K4, "Hilfsfunktion"), +} +NAME_DEFAULT = [ + (re.compile(r"is_valid_charger_entity|cards_as_object_present"), (K3, "bestimmt, welche Entitäten es gibt")), + (re.compile(r"_read_filtered_data_from_ws_source|none_null_get|available_fields"), (K3, "liest aus dem Datenspeicher")), + (re.compile(r"clear_data|reset_stored_update_ts|force_config_update|ws_set_coordinator|cancel_existing_debouncer|request_update_in_sec|_debounce"), (K3, "Datenspeicher und Aktualisierung")), + (re.compile(r"_ws_normalize|ExpiringStore|\.get$|\.set$|_cleanup|__len__|__call__|device_info|unique_id|available$|_name_internal|_friendly_name|call_later_update_device_registry|async_setup$|has_errors|set_error|__hash__|__str__"), (K4, "Hilfsfunktion oder Anzeigename")), +] + + +def classify(file: str, qual: str): + if (file, qual) in EXACT: + return EXACT[(file, qual)] + if file in FILE_DEFAULT: + return FILE_DEFAULT[file] + for pattern, value in NAME_DEFAULT: + if pattern.search(qual): + return value + return (K4, "Hilfsfunktion") diff --git a/tests/tools/measure.sh b/tests/tools/measure.sh new file mode 100755 index 0000000..1e5ac5f --- /dev/null +++ b/tests/tools/measure.sh @@ -0,0 +1,27 @@ +#!/bin/sh +# Measures coverage and path coverage of the integration with the tests in tests/integration. +# Usage (from the repository root, with the test dependencies installed): +# tests/tools/measure.sh [report-dir] +# Optional environment: TESTS_COMMIT (commit of the tests, default: HEAD), SOURCE_REF (name of the measured version in the report). +# Writes coverage, junit and path-trace data and builds testreport.md in the report directory. +set -e +ROOT="$(cd "$(dirname "$0")/../.." && pwd)" +cd "$ROOT" +OUT="${1:-$ROOT/tests/reports/$(date +%Y-%m-%d)}" +mkdir -p "$OUT" +COMMIT="$(git rev-parse --short HEAD)" + +python -m pytest tests/integration -p no:cacheprovider -q \ + --cov=custom_components.goecharger_api2 --cov-branch \ + --cov-report=json:"$OUT/coverage-all.json" --cov-report=html:"$OUT/html-all" \ + --junitxml="$OUT/junit-all.xml" || true +python -m pytest tests/integration -p no:cacheprovider -q -m "not known_bug and not needs_decision" \ + --cov=custom_components.goecharger_api2 --cov-branch \ + --cov-report=json:"$OUT/coverage-blocking.json" --junitxml="$OUT/junit-blocking.xml" +PYTHONPATH="$ROOT/tests/tools" PATHTRACE_OUT="$OUT/pathtrace-all.json" \ + python -m pytest tests/integration -p pathtracer -p no:cacheprovider -q || true + +# the report compares with a baseline; without one, the current data is used as baseline +[ -f "$OUT/coverage-baseline.json" ] || cp "$OUT/coverage-all.json" "$OUT/coverage-baseline.json" +[ -f "$OUT/pathtrace-baseline.json" ] || cp "$OUT/pathtrace-all.json" "$OUT/pathtrace-baseline.json" +python tests/tools/build_report.py "$OUT" "$ROOT" "$COMMIT" "${TESTS_COMMIT:-$COMMIT}" diff --git a/tests/tools/pathmetric.py b/tests/tools/pathmetric.py new file mode 100644 index 0000000..57a9935 --- /dev/null +++ b/tests/tools/pathmetric.py @@ -0,0 +1,40 @@ +"""Quick metric: path coverage (upper bound of basis paths) from a pathtrace JSON, per criticality. + +Usage: python pathmetric.py +""" +import json +import subprocess +import sys +from collections import defaultdict +from pathlib import Path + +sys.path.insert(0, str(Path(__file__).parent)) +from criticality import classify # noqa: E402 + +PKG = "custom_components/goecharger_api2/" +CORE = ["__init__.py", "pygoecharger_ha/__init__.py", "service.py", "config_flow.py"] +trace_file, checkout = sys.argv[1], Path(sys.argv[2]).resolve() + + +def rel(p): + return p.split(PKG)[-1] + + +trace = {(rel(k.split("::")[0]), k.split("::")[1]): v for k, v in json.load(open(trace_file)).items()} +radon = json.loads(subprocess.run(["radon", "cc", "-s", "-j", str(checkout / PKG)], capture_output=True, text=True).stdout) +cc = {} +for file, blocks in radon.items(): + for b in blocks: + for e in ([b] + b.get("methods", []) if b["type"] == "class" else [b]): + if e["type"] in ("function", "method"): + cc[(rel(file), f"{e['classname']}.{e['name']}" if e.get("classname") else e["name"])] = e["complexity"] +tot = defaultdict(lambda: [0, 0]) +for (f, q), c in cc.items(): + n = trace.get((f, q), {}).get("distinct_paths", 0) + k = classify(f, q)[0] + for key in ("ALL", "CORE" if f in CORE else "PLAT", k): + tot[key][0] += min(n, c) + tot[key][1] += c +for key in ("ALL", "CORE", "PLAT", "K1 kritisch", "K2 hoch", "K3 mittel", "K4 niedrig"): + a, b = tot[key] + print(f"{key:12s} {a:4d}/{b:4d} = {100 * a / b:5.1f} %") diff --git a/tests/tools/pathtracer.py b/tests/tools/pathtracer.py new file mode 100644 index 0000000..20320a8 --- /dev/null +++ b/tests/tools/pathtracer.py @@ -0,0 +1,72 @@ +"""pytest plugin: records which distinct execution paths each function of the integration takes. + +A path is the sequence of executed source lines of ONE call of a function (a coroutine's call spans +all its resumptions). Loops are shortened: a line counts at most twice per call. The result is an +empirical number of distinct paths seen during the test run; it is NOT a proof of path coverage. +Use: PYTHONPATH=tools PATHTRACE_OUT=out.json python -m pytest -p pathtracer ... +Needs Python 3.12+ (sys.monitoring). +""" +import json +import os +import sys +from collections import defaultdict + +mon = sys.monitoring +TOOL = 3 +PKG = "/custom_components/goecharger_api2/" +E = mon.events +active = {} # frame -> [code, lines, counts] +paths = defaultdict(set) # (file, qualname) -> {tuple of lines} +calls = defaultdict(int) + + +def _relevant(code): + return PKG in code.co_filename + + +def _start(code, offset): + if not _relevant(code): + return mon.DISABLE + active[sys._getframe(1)] = [code, [], {}] + calls[(code.co_filename, code.co_qualname)] += 1 + + +def _line(code, line): + if not _relevant(code): + return mon.DISABLE + entry = active.get(sys._getframe(1)) + if entry is None: + return + seen = entry[2].get(line, 0) + entry[2][line] = seen + 1 + if seen < 2: + entry[1].append(line) + + +def _finish(marker): + def callback(code, offset, *args): + if not _relevant(code): + return None # PY_UNWIND events cannot be disabled + entry = active.pop(sys._getframe(1), None) + if entry is not None: + paths[(code.co_filename, code.co_qualname)].add(tuple(entry[1]) + (marker,)) + return callback + + +def pytest_configure(config): + mon.use_tool_id(TOOL, "pathtracer") + mon.register_callback(TOOL, E.PY_START, _start) + mon.register_callback(TOOL, E.LINE, _line) + mon.register_callback(TOOL, E.PY_RETURN, _finish("R")) + mon.register_callback(TOOL, E.PY_UNWIND, _finish("X")) + mon.set_events(TOOL, E.PY_START | E.LINE | E.PY_RETURN | E.PY_UNWIND) + + +def pytest_sessionfinish(session, exitstatus): + mon.set_events(TOOL, 0) + out = os.environ.get("PATHTRACE_OUT", "pathtrace.json") + data = {f"{file}::{qual}": {"calls": calls[(file, qual)], "distinct_paths": len(p)} + for (file, qual), p in paths.items()} + for (file, qual), n in calls.items(): + data.setdefault(f"{file}::{qual}", {"calls": n, "distinct_paths": 0}) + json.dump(data, open(out, "w"), indent=1) diff --git a/tests/tools/release_check.sh b/tests/tools/release_check.sh new file mode 100755 index 0000000..8e4cbd5 --- /dev/null +++ b/tests/tools/release_check.sh @@ -0,0 +1,85 @@ +#!/bin/sh +# Tests a version of the upstream integration with the tests of this branch. +# +# Usage: tests/tools/release_check.sh [ref] [work-dir] +# ref tag or branch of marq24/ha-goecharger-api2 (default: the latest release) +# work-dir directory for the clone and the results (default: a temporary directory) +# Environment: PYTHON (default: python), UPSTREAM (default: https://github.com/marq24/ha-goecharger-api2) +# +# The upstream source is cloned into /src and only the directory tests/integration of this +# branch is copied into it (no rebase, no merge, nothing is pushed anywhere). Two runs follow: +# 1. the blocking tests (everything except the markers known_bug and needs_decision) +# 2. the tests marked known_bug or needs_decision. They fail on purpose as long as a bug exists; +# a test that PASSES here means the bug looks fixed in this version (remove its marker). +# Exit code: 0 when the blocking tests pass, 1 otherwise. A summary goes to /summary.md and, +# when running in GitHub Actions, to the job summary. +set -u +HERE="$(cd "$(dirname "$0")/../.." && pwd)" +PYTHON="${PYTHON:-python}" +UPSTREAM="${UPSTREAM:-https://github.com/marq24/ha-goecharger-api2}" +REF="${1:-}" +WORK="${2:-$(mktemp -d)}" + +if [ -z "$REF" ]; then + REF="$(gh api repos/marq24/ha-goecharger-api2/releases/latest --jq .tag_name)" || { echo "cannot determine the latest release"; exit 2; } +fi +mkdir -p "$WORK" +rm -rf "$WORK/src" +git -c advice.detachedHead=false clone --quiet --depth 1 --branch "$REF" "$UPSTREAM" "$WORK/src" || { echo "cannot clone $REF"; exit 2; } +SHA="$(git -C "$WORK/src" rev-parse --short HEAD)" +mkdir -p "$WORK/src/tests" +cp -R "$HERE/tests/integration" "$WORK/src/tests/" +find "$WORK/src/tests" -name __pycache__ -prune -exec rm -rf {} + + +cd "$WORK/src" || exit 2 +"$PYTHON" -m pytest tests/integration -p no:cacheprovider -q -m "not known_bug and not needs_decision" \ + --junitxml="$WORK/blocking.xml" > "$WORK/blocking.txt" 2>&1 +BLOCKING=$? +"$PYTHON" -m pytest tests/integration -p no:cacheprovider -q -m "known_bug or needs_decision" \ + --junitxml="$WORK/known.xml" > "$WORK/known.txt" 2>&1 + +"$PYTHON" - "$WORK" "$REF" "$SHA" <<'PY' +import sys +import xml.etree.ElementTree as ET + +work, ref, sha = sys.argv[1:4] + + +def read(name): + root = ET.parse(f"{work}/{name}.xml").getroot() + suite = root if root.tag == "testsuite" else root.find("testsuite") + cases = [] + for tc in suite.iter("testcase"): + failed = tc.find("failure") is not None or tc.find("error") is not None + cases.append((tc.get("classname").split(".")[-1] + "::" + tc.get("name"), failed)) + return cases + + +blocking, known = read("blocking"), read("known") +raw = open(f"{work}/blocking.txt").read() +if "ERROR collecting" in raw or "ImportError" in raw: + text = "\n".join([f"## Regression tests for upstream `{ref}` (`{sha}`)", "", + "**The tests could not be collected.** This version probably lacks functions the tests import " + "(the tests follow upstream `main`; for example `add_config_entry_to_service` exists since 2026.10.8).", + "", "```", "\n".join(raw.strip().splitlines()[-12:]), "```", ""]) + open(f"{work}/summary.md", "w").write(text + "\n") + print(text) + sys.exit(0) +failing = [n for n, f in blocking if f] +fixed = [n for n, f in known if not f] +still = [n for n, f in known if f] +lines = [f"## Regression tests for upstream `{ref}` (`{sha}`)", "", + f"| | |", "|---|---|", + f"| Blocking tests | **{len(blocking) - len(failing)} of {len(blocking)} passed** |", + f"| Known bugs / open decisions still failing | {len(still)} |", + f"| Known-bug tests that now PASS (bug looks fixed) | **{len(fixed)}** |", ""] +if failing: + lines += ["### Blocking tests that fail (new regression or version-specific behaviour)", ""] + [f"- `{n}`" for n in failing] + [""] +if fixed: + lines += ["### Known-bug tests that pass in this version (remove the marker)", ""] + [f"- `{n}`" for n in fixed] + [""] +text = "\n".join(lines) +open(f"{work}/summary.md", "w").write(text + "\n") +print(text) +PY +[ -n "${GITHUB_STEP_SUMMARY:-}" ] && cat "$WORK/summary.md" >> "$GITHUB_STEP_SUMMARY" +[ "$BLOCKING" -eq 0 ] && exit 0 || exit 1 From 9e7d62a2bd07c346040d575a0781fd0463e439de Mon Sep 17 00:00:00 2001 From: marq24 Date: Thu, 29 Oct 2026 20:08:54 +0100 Subject: [PATCH 17/24] - minor adjustments from `tcl` changes to tests - removed the tests\reports --- README.md | 2 +- custom_components/goecharger_api2/__init__.py | 2 +- .../goecharger_api2/pygoecharger_ha/keys.py | 2 +- custom_components/goecharger_api2/service.py | 8 +- .../integration/contract/classification.json | 4 - tests/integration/snapshots/entity_ids.json | 2 + tests/integration/test_platform_values.py | 4 +- tests/reports/2026-10-29/coverage-all.json | 1 - .../reports/2026-10-29/coverage-baseline.json | 1 - .../reports/2026-10-29/coverage-blocking.json | 1 - tests/reports/2026-10-29/junit-all.xml | 416 ------------ tests/reports/2026-10-29/pathtrace-all.json | 638 ------------------ .../2026-10-29/pathtrace-baseline.json | 366 ---------- tests/reports/2026-10-29/testreport.md | 269 -------- 14 files changed, 11 insertions(+), 1705 deletions(-) delete mode 100644 tests/reports/2026-10-29/coverage-all.json delete mode 100644 tests/reports/2026-10-29/coverage-baseline.json delete mode 100644 tests/reports/2026-10-29/coverage-blocking.json delete mode 100644 tests/reports/2026-10-29/junit-all.xml delete mode 100644 tests/reports/2026-10-29/pathtrace-all.json delete mode 100644 tests/reports/2026-10-29/pathtrace-baseline.json delete mode 100644 tests/reports/2026-10-29/testreport.md diff --git a/README.md b/README.md index 3390848..c5fa1f2 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,7 @@ So it's either enabled HTTP v2 API or password to use the websocket [[see instru > [!IMPORTANT] -> __Please note__, _that this integration is __not official__, nor is it supported by the go-e manufacture from Austrian. I (marq24) am not affiliated with go-e.com in any way. This integration is based on the go-e API and the available go-e API documentation. (and some reverse engineering)_. +> __Please note__, _that this integration is __not official__, nor is it supported by the go-e manufacture from Austria. I (marq24) am not affiliated with go-e.com in any way. This integration is based on the go-e API and the available go-e API documentation. (and some reverse engineering)_. [![hacs_badge][hacsbadge]][hacs] [![hainstall][hainstallbadge]][hainstall] [![Wero][werobadge]][wero] [![wise][wisebadge]][wise] [![Revolut][revolutbadge]][revolut] [![PayPal][paypalbadge]][paypal] [![github][ghsbadge]][ghs] [![BuyMeCoffee][buymecoffeebadge]][buymecoffee] diff --git a/custom_components/goecharger_api2/__init__.py b/custom_components/goecharger_api2/__init__.py index 7be1c18..82eb70a 100644 --- a/custom_components/goecharger_api2/__init__.py +++ b/custom_components/goecharger_api2/__init__.py @@ -566,7 +566,7 @@ async def _debounce_coordinator_update(self, seconds: float): await self.async_refresh() def handle_write_result(self, a_type, value, key, result, entity): - if result is None and self.bridge.ws_connected: + if self.bridge.ws_connected and (result is None or len(result) == 0): # when using websocket - the writing result will be handled # already in the ws-response processor... return diff --git a/custom_components/goecharger_api2/pygoecharger_ha/keys.py b/custom_components/goecharger_api2/pygoecharger_ha/keys.py index 1c96cf8..05cfec8 100644 --- a/custom_components/goecharger_api2/pygoecharger_ha/keys.py +++ b/custom_components/goecharger_api2/pygoecharger_ha/keys.py @@ -702,7 +702,7 @@ def __getattr__(self, name): # tab | R | TYPE | Status | time at boot in utc in milliseconds, add rbt to get to current utc time # tcl | R/W | TYPE | Config | temporary current limit (does not change the user current limit, will be reset after 10min if not updated regularly) - TCL = ApiKey(key="tcl", cat=CAT.STATUS, writeable=True) + TCL = ApiKey(key="tcl", cat=CAT.CONFIG, writeable=True) # tsi | R | TYPE | Status | transaction start rfidid (only available when sendRfid) # tzt | R/W | TYPE | Config | timezone type, freetext string for app selection # ufa | R/W | TYPE | Config | Underfrequency Control activation threshold diff --git a/custom_components/goecharger_api2/service.py b/custom_components/goecharger_api2/service.py index 67514b5..20ae18f 100644 --- a/custom_components/goecharger_api2/service.py +++ b/custom_components/goecharger_api2/service.py @@ -110,13 +110,13 @@ async def stop_charging(self, call: ServiceCall): self._stop_in_progress = True _LOGGER.debug(f"Service-stop_charging(): Provided config_id: {config_id} -> using {coordinator.name} serial: {coordinator._serial}") try: - resp = await coordinator.async_write_key(Tag.FRC.key, 1) + resp = await coordinator.async_write_key(Tag.FRC.key, 1)#, require_ack=True) if Tag.FRC.key in resp: _LOGGER.debug(f"Service-stop_charging(): STOP_CHARGING: waiting for 5 minutes...") await asyncio.sleep(300) _LOGGER.debug(f"Service-stop_charging(): STOP_CHARGING: 5 minutes are over... disable charging LOCK again") - resp = await coordinator.async_write_key(Tag.FRC.key, 0) + resp = await coordinator.async_write_key(Tag.FRC.key, 0)#, require_ack=True) self._stop_in_progress = False if call.return_response: @@ -128,7 +128,6 @@ async def stop_charging(self, call: ServiceCall): else: _LOGGER.debug(f"response does not contain {Tag.FRC.key}: {resp}") - self._stop_in_progress = False if call.return_response: return {"error": "A STOP_CHARGING request could not be send", "date": str(datetime.datetime.now().time())} @@ -137,7 +136,8 @@ async def stop_charging(self, call: ServiceCall): _LOGGER.debug(f"Service-stop_charging(): Error {type(exc).__name__}: {exc}") if call.return_response: return {"error": str(exc), "date": str(datetime.datetime.now().time())} - + finally: + self._stop_in_progress = False else: if call.return_response: return {"error": "A STOP_CHARGING request is already in progress", "date": str(datetime.datetime.now().time())} diff --git a/tests/integration/contract/classification.json b/tests/integration/contract/classification.json index aa9626d..672619e 100644 --- a/tests/integration/contract/classification.json +++ b/tests/integration/contract/classification.json @@ -269,10 +269,6 @@ "group": "current limits and load balancing", "decision": "open" }, - "tcl": { - "group": "current limits and load balancing", - "decision": "open" - }, "dsrc": { "group": "data sources", "decision": "open" diff --git a/tests/integration/snapshots/entity_ids.json b/tests/integration/snapshots/entity_ids.json index 4433b30..3bca5a9 100644 --- a/tests/integration/snapshots/entity_ids.json +++ b/tests/integration/snapshots/entity_ids.json @@ -54,6 +54,7 @@ "number.goe_123456_sh", "number.goe_123456_spl3", "number.goe_123456_sumd", + "number.goe_123456_tcl", "number.goe_123456_zfo", "select.goe_123456_bac", "select.goe_123456_ct", @@ -244,6 +245,7 @@ "number.goe_wan_123456_sh", "number.goe_wan_123456_spl3", "number.goe_wan_123456_sumd", + "number.goe_wan_123456_tcl", "number.goe_wan_123456_zfo", "select.goe_wan_123456_bac", "select.goe_wan_123456_ct", diff --git a/tests/integration/test_platform_values.py b/tests/integration/test_platform_values.py index f21ce67..2ab2e64 100644 --- a/tests/integration/test_platform_values.py +++ b/tests/integration/test_platform_values.py @@ -169,7 +169,7 @@ async def test_number_set_value_writes_the_converted_value(hass, data, descripti number, coordinator = _number(hass, data, **description) coordinator.async_write_key = AsyncMock() await number.async_set_native_value(value) - coordinator.async_write_key.assert_awaited_once_with(expected[0], expected[1], number) + coordinator.async_write_key.assert_awaited_once_with(expected[0], expected[1], number, require_ack=False) IDX_WRITE_CASES = [ @@ -186,7 +186,7 @@ async def test_number_set_value_writes_the_whole_list(hass, description, value, number, coordinator = _number(hass, {"lop": [1, 2, 3]}, key="lop", **description) coordinator.async_write_key = AsyncMock() await number.async_set_native_value(value) - coordinator.async_write_key.assert_awaited_once_with("lop", expected, number) + coordinator.async_write_key.assert_awaited_once_with("lop", expected, number, require_ack=False) async def test_number_set_value_swallows_write_errors(hass): diff --git a/tests/reports/2026-10-29/coverage-all.json b/tests/reports/2026-10-29/coverage-all.json deleted file mode 100644 index c1e3c2d..0000000 --- a/tests/reports/2026-10-29/coverage-all.json +++ /dev/null @@ -1 +0,0 @@ -{"meta": {"format": 3, "version": "7.16.2", "timestamp": "2026-10-29T11:48:24.962084", "branch_coverage": true, "show_contexts": false}, "files": {"custom_components/goecharger_api2/__init__.py": {"executed_lines": [1, 2, 3, 4, 5, 6, 7, 9, 10, 11, 12, 23, 24, 25, 30, 31, 32, 33, 34, 35, 36, 38, 45, 46, 60, 61, 63, 65, 66, 67, 68, 69, 70, 71, 73, 74, 75, 76, 77, 78, 81, 82, 85, 86, 87, 88, 89, 90, 91, 92, 93, 97, 98, 100, 103, 105, 108, 109, 110, 111, 112, 113, 116, 117, 118, 120, 122, 123, 124, 125, 126, 127, 128, 129, 131, 132, 133, 135, 136, 137, 138, 140, 141, 144, 145, 148, 149, 150, 151, 153, 154, 155, 156, 158, 162, 165, 166, 173, 175, 176, 177, 179, 180, 182, 184, 187, 188, 190, 191, 192, 193, 194, 195, 196, 197, 198, 201, 202, 203, 204, 206, 209, 210, 211, 214, 215, 216, 217, 218, 219, 220, 221, 224, 225, 226, 227, 228, 230, 231, 232, 233, 234, 236, 238, 240, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 254, 255, 257, 258, 259, 260, 261, 262, 269, 270, 271, 272, 273, 275, 276, 278, 279, 280, 281, 283, 284, 285, 286, 287, 288, 289, 290, 291, 294, 296, 297, 298, 300, 301, 302, 303, 304, 306, 307, 310, 311, 312, 314, 315, 316, 325, 326, 336, 337, 339, 340, 341, 343, 346, 347, 348, 349, 350, 351, 352, 353, 354, 356, 358, 359, 360, 361, 362, 363, 368, 371, 374, 375, 377, 378, 379, 380, 381, 382, 383, 384, 386, 387, 388, 391, 392, 393, 394, 396, 398, 403, 404, 406, 410, 411, 412, 413, 416, 417, 418, 419, 423, 424, 425, 428, 431, 432, 433, 435, 437, 438, 442, 445, 446, 448, 452, 453, 454, 456, 457, 458, 460, 463, 464, 465, 468, 470, 471, 472, 473, 474, 475, 476, 478, 479, 481, 482, 484, 486, 487, 493, 494, 495, 496, 498, 499, 500, 501, 502, 503, 504, 505, 506, 508, 509, 510, 511, 512, 513, 514, 515, 516, 518, 519, 520, 521, 524, 525, 526, 528, 529, 530, 531, 532, 533, 534, 535, 536, 537, 538, 540, 542, 544, 545, 546, 547, 549, 550, 551, 553, 554, 555, 557, 558, 559, 561, 562, 565, 567, 569, 570, 572, 574, 575, 577, 578, 579, 580, 582, 583, 584, 586, 587, 588, 591, 595, 596, 597, 598, 599, 600, 601, 602, 604, 606, 608, 609, 613, 614, 615, 616, 617, 618, 619, 621, 622, 623, 624, 625, 631, 633, 634, 635, 636, 638, 639, 640, 642, 643, 644, 645, 646, 647, 648, 649, 651, 652, 653, 654, 656, 657, 658, 659, 660, 661, 663, 664, 665, 666, 667, 668, 669, 670, 672, 673, 674, 675, 677, 678, 679, 684, 685, 686, 688, 692, 693, 694, 696, 700, 701, 702, 703, 704, 707, 710, 711, 712, 713, 714, 715, 716, 719, 720, 721, 723, 725, 727, 729, 730, 731, 732, 733, 734, 736, 737, 738, 739, 740, 742, 743, 745, 750, 751, 752, 753, 754, 755, 756, 757, 758, 759, 760, 761, 763, 768, 769, 771, 772, 773, 774, 776, 778, 779, 780, 783, 784, 785, 787, 788, 789, 791, 792, 794, 797, 799, 800, 801, 803, 805, 806, 808, 811, 812, 813, 814, 815, 817, 819, 834, 836, 837, 838, 839, 840, 842, 843, 845, 846, 848, 849, 852, 855, 858, 859, 861, 863, 864, 866, 867, 870, 873, 874, 875, 877, 878, 879, 880, 881, 882, 883, 885, 886, 887, 888, 889, 890, 892, 894, 895, 897, 898, 900, 907, 911, 912, 915, 916, 920, 921, 923, 925], "summary": {"covered_lines": 577, "num_statements": 604, "percent_covered": 93.5632183908046, "percent_covered_display": "94", "missing_lines": 27, "excluded_lines": 0, "percent_statements_covered": 95.52980132450331, "percent_statements_covered_display": "96", "num_branches": 266, "num_partial_branches": 19, "covered_branches": 237, "missing_branches": 29, "percent_branches_covered": 89.09774436090225, "percent_branches_covered_display": "89"}, "missing_lines": [80, 94, 95, 364, 390, 449, 610, 611, 637, 680, 681, 682, 909, 913, 918, 931, 932, 933, 935, 936, 938, 939, 940, 943, 944, 945, 947], "excluded_lines": [], "executed_branches": [[74, 75], [74, 85], [75, 76], [77, 78], [85, 86], [85, 100], [89, 90], [91, 92], [91, 97], [109, 110], [109, 116], [117, 118], [117, 120], [124, 125], [124, 135], [127, 128], [127, 144], [137, 138], [137, 140], [140, 141], [140, 144], [148, 149], [148, 165], [149, 150], [149, 151], [153, 154], [153, 162], [165, 166], [165, 173], [173, 175], [173, 182], [175, 176], [175, 179], [190, 191], [190, 206], [191, 192], [191, 201], [201, 202], [201, 206], [218, 219], [220, 221], [225, 226], [225, 242], [226, 225], [226, 227], [227, 225], [227, 228], [228, 225], [228, 230], [230, 231], [230, 240], [231, 232], [231, 238], [232, 233], [232, 236], [242, -214], [242, 243], [246, 247], [246, 257], [247, 248], [247, 254], [249, 250], [249, 251], [254, 246], [254, 255], [260, 261], [260, 283], [262, 269], [262, 273], [269, 260], [269, 270], [271, 272], [273, 260], [273, 275], [276, 260], [276, 278], [278, 276], [278, 279], [280, 281], [284, -214], [284, 285], [285, 286], [285, 288], [311, 312], [311, 314], [314, 315], [314, 325], [340, 341], [340, 343], [353, 354], [353, 356], [363, 368], [379, -377], [379, 380], [380, 381], [382, 383], [382, 386], [386, 387], [387, 388], [391, -377], [391, 392], [393, 394], [393, 396], [411, 412], [411, 416], [417, 418], [417, 423], [432, 433], [432, 435], [448, 452], [463, 464], [463, 468], [470, 471], [470, 478], [472, 473], [472, 478], [494, -493], [494, 495], [499, -498], [499, 500], [509, 510], [509, 518], [514, 515], [519, -508], [519, 520], [532, 533], [550, -549], [550, 551], [562, 565], [562, 567], [569, 570], [569, 572], [575, 577], [575, 586], [577, 578], [577, 582], [582, 583], [582, 586], [586, -561], [586, 587], [587, -561], [587, 588], [597, 598], [597, 604], [599, 600], [609, 613], [613, 614], [613, 617], [617, 618], [617, 621], [623, 624], [643, 644], [643, 647], [647, 648], [647, 651], [664, 665], [664, 668], [668, 669], [668, 672], [685, 686], [685, 688], [688, 692], [688, 700], [692, 693], [692, 696], [700, 701], [700, 707], [702, 703], [702, 707], [711, 712], [711, 715], [713, 714], [715, 716], [715, 719], [720, 721], [720, 723], [727, 729], [727, 797], [730, 731], [730, 736], [731, 732], [731, 742], [732, 733], [732, 734], [736, 737], [736, 742], [738, 739], [738, 740], [742, 743], [742, 745], [752, 753], [752, 778], [754, 755], [754, 776], [758, 759], [758, 763], [773, 774], [773, 778], [778, 779], [778, 783], [783, 784], [783, 787], [791, 792], [791, 794], [800, 801], [800, 803], [805, 806], [805, 808], [811, 812], [811, 817], [813, 814], [813, 819], [843, 845], [843, 852], [845, 846], [845, 848], [848, 849], [848, 861], [852, 855], [852, 858], [858, 859], [858, 861], [873, 874], [873, 885], [874, 875], [874, 877], [877, 878], [877, 881], [881, 882], [885, 886], [885, 887], [887, 888], [887, 889], [889, 890], [889, 892], [897, 898], [897, 900]], "missing_branches": [[75, 85], [77, 80], [89, 97], [218, -214], [220, -214], [271, 260], [280, 276], [363, 364], [380, -377], [386, -377], [387, 390], [448, 449], [514, -508], [532, 534], [599, -591], [609, 610], [623, 631], [713, 715], [881, 894], [932, 933], [932, 935], [935, 936], [935, 938], [939, 940], [939, 943], [943, 944], [943, 947], [944, 945], [944, 947]], "functions": {"async_migrate_entry": {"executed_lines": [74, 75, 76, 77, 78, 81, 82, 85, 86, 87, 88, 89, 90, 91, 92, 93, 97, 98, 100], "summary": {"covered_lines": 19, "num_statements": 22, "percent_covered": 82.3529411764706, "percent_covered_display": "82", "missing_lines": 3, "excluded_lines": 0, "percent_statements_covered": 86.36363636363636, "percent_statements_covered_display": "86", "num_branches": 12, "num_partial_branches": 3, "covered_branches": 9, "missing_branches": 3, "percent_branches_covered": 75.0, "percent_branches_covered_display": "75"}, "missing_lines": [80, 94, 95], "excluded_lines": [], "start_line": 73, "executed_branches": [[74, 75], [74, 85], [75, 76], [77, 78], [85, 86], [85, 100], [89, 90], [91, 92], [91, 97]], "missing_branches": [[75, 85], [77, 80], [89, 97]]}, "async_setup": {"executed_lines": [105], "summary": {"covered_lines": 1, "num_statements": 1, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 103, "executed_branches": [], "missing_branches": []}, "async_setup_entry": {"executed_lines": [109, 110, 111, 112, 113, 116, 117, 118, 120, 122, 123, 124, 125, 126, 127, 128, 129, 131, 132, 133, 135, 136, 137, 138, 140, 141, 144, 145, 148, 149, 150, 151, 153, 154, 155, 156, 158, 162, 165, 166, 173, 175, 176, 177, 179, 180, 182, 184], "summary": {"covered_lines": 48, "num_statements": 48, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 24, "num_partial_branches": 0, "covered_branches": 24, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 108, "executed_branches": [[109, 110], [109, 116], [117, 118], [117, 120], [124, 125], [124, 135], [127, 128], [127, 144], [137, 138], [137, 140], [140, 141], [140, 144], [148, 149], [148, 165], [149, 150], [149, 151], [153, 154], [153, 162], [165, 166], [165, 173], [173, 175], [173, 182], [175, 176], [175, 179]], "missing_branches": []}, "async_unload_entry": {"executed_lines": [188, 190, 191, 192, 193, 194, 195, 196, 197, 198, 201, 202, 203, 204, 206], "summary": {"covered_lines": 15, "num_statements": 15, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 6, "num_partial_branches": 0, "covered_branches": 6, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 187, "executed_branches": [[190, 191], [190, 206], [191, 192], [191, 201], [201, 202], [201, 206]], "missing_branches": []}, "entry_update_listener": {"executed_lines": [210, 211], "summary": {"covered_lines": 2, "num_statements": 2, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 209, "executed_branches": [], "missing_branches": []}, "check_and_write_to_16a": {"executed_lines": [216, 217, 218, 219, 220, 221, 224, 225, 226, 227, 228, 230, 231, 232, 233, 234, 236, 238, 240, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 254, 255, 257, 258, 259, 260, 261, 262, 269, 270, 271, 272, 273, 275, 276, 278, 279, 280, 281, 283, 284, 285, 286, 287, 288, 289, 290, 291], "summary": {"covered_lines": 58, "num_statements": 58, "percent_covered": 96.22641509433963, "percent_covered_display": "96", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 48, "num_partial_branches": 4, "covered_branches": 44, "missing_branches": 4, "percent_branches_covered": 91.66666666666667, "percent_branches_covered_display": "92"}, "missing_lines": [], "excluded_lines": [], "start_line": 215, "executed_branches": [[218, 219], [220, 221], [225, 226], [225, 242], [226, 225], [226, 227], [227, 225], [227, 228], [228, 225], [228, 230], [230, 231], [230, 240], [231, 232], [231, 238], [232, 233], [232, 236], [242, -214], [242, 243], [246, 247], [246, 257], [247, 248], [247, 254], [249, 250], [249, 251], [254, 246], [254, 255], [260, 261], [260, 283], [262, 269], [262, 273], [269, 260], [269, 270], [271, 272], [273, 260], [273, 275], [276, 260], [276, 278], [278, 276], [278, 279], [280, 281], [284, -214], [284, 285], [285, 286], [285, 288]], "missing_branches": [[218, -214], [220, -214], [271, 260], [280, 276]]}, "GoeChargerDataUpdateCoordinator.__init__": {"executed_lines": [301, 302, 303, 304, 306, 307, 310, 311, 312, 314, 315, 316, 325, 326, 336, 337, 339, 340, 341, 343, 346, 347, 348, 349, 350, 351, 352, 353, 354, 356, 358, 359, 360, 361, 362, 363, 368, 371, 374, 375], "summary": {"covered_lines": 40, "num_statements": 41, "percent_covered": 96.07843137254902, "percent_covered_display": "96", "missing_lines": 1, "excluded_lines": 0, "percent_statements_covered": 97.5609756097561, "percent_statements_covered_display": "98", "num_branches": 10, "num_partial_branches": 1, "covered_branches": 9, "missing_branches": 1, "percent_branches_covered": 90.0, "percent_branches_covered_display": "90"}, "missing_lines": [364], "excluded_lines": [], "start_line": 300, "executed_branches": [[311, 312], [311, 314], [314, 315], [314, 325], [340, 341], [340, 343], [353, 354], [353, 356], [363, 368]], "missing_branches": [[363, 364]]}, "GoeChargerDataUpdateCoordinator.call_later_update_device_registry": {"executed_lines": [378, 379, 380, 381, 382, 383, 384, 386, 387, 388, 391, 392, 393, 394, 396, 398], "summary": {"covered_lines": 16, "num_statements": 17, "percent_covered": 87.09677419354838, "percent_covered_display": "87", "missing_lines": 1, "excluded_lines": 0, "percent_statements_covered": 94.11764705882354, "percent_statements_covered_display": "94", "num_branches": 14, "num_partial_branches": 3, "covered_branches": 11, "missing_branches": 3, "percent_branches_covered": 78.57142857142857, "percent_branches_covered_display": "79"}, "missing_lines": [390], "excluded_lines": [], "start_line": 377, "executed_branches": [[379, -377], [379, 380], [380, 381], [382, 383], [382, 386], [386, 387], [387, 388], [391, -377], [391, 392], [393, 394], [393, 396]], "missing_branches": [[380, -377], [386, -377], [387, 390]]}, "GoeChargerDataUpdateCoordinator.cards_as_object_present": {"executed_lines": [404], "summary": {"covered_lines": 1, "num_statements": 1, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 403, "executed_branches": [], "missing_branches": []}, "GoeChargerDataUpdateCoordinator.force_async_update_now": {"executed_lines": [410, 411, 412, 413, 416, 417, 418, 419, 423, 424, 425, 428, 431, 432, 433, 435], "summary": {"covered_lines": 16, "num_statements": 16, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 6, "num_partial_branches": 0, "covered_branches": 6, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 406, "executed_branches": [[411, 412], [411, 416], [417, 418], [417, 423], [432, 433], [432, 435]], "missing_branches": []}, "GoeChargerDataUpdateCoordinator.start_websocket_and_wait_for_first_data": {"executed_lines": [438, 442, 445, 446, 448, 452, 453, 454, 456, 457, 458, 460, 463, 464, 465, 468, 470, 471, 472, 473, 474, 475, 476, 478, 479, 481, 482], "summary": {"covered_lines": 27, "num_statements": 28, "percent_covered": 94.44444444444444, "percent_covered_display": "94", "missing_lines": 1, "excluded_lines": 0, "percent_statements_covered": 96.42857142857143, "percent_statements_covered_display": "96", "num_branches": 8, "num_partial_branches": 1, "covered_branches": 7, "missing_branches": 1, "percent_branches_covered": 87.5, "percent_branches_covered_display": "88"}, "missing_lines": [449], "excluded_lines": [], "start_line": 437, "executed_branches": [[448, 452], [463, 464], [463, 468], [470, 471], [470, 478], [472, 473], [472, 478]], "missing_branches": [[448, 449]]}, "GoeChargerDataUpdateCoordinator.start_watchdog": {"executed_lines": [486, 487], "summary": {"covered_lines": 2, "num_statements": 2, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 484, "executed_branches": [], "missing_branches": []}, "GoeChargerDataUpdateCoordinator.stop_watchdog": {"executed_lines": [494, 495, 496], "summary": {"covered_lines": 3, "num_statements": 3, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 2, "num_partial_branches": 0, "covered_branches": 2, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 493, "executed_branches": [[494, -493], [494, 495]], "missing_branches": []}, "GoeChargerDataUpdateCoordinator._check_for_ws_task_and_cancel_if_running": {"executed_lines": [499, 500, 501, 502, 503, 504, 505, 506], "summary": {"covered_lines": 8, "num_statements": 8, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 2, "num_partial_branches": 0, "covered_branches": 2, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 498, "executed_branches": [[499, -498], [499, 500]], "missing_branches": []}, "GoeChargerDataUpdateCoordinator._async_watchdog_check": {"executed_lines": [509, 510, 511, 512, 513, 514, 515, 516, 518, 519, 520, 521], "summary": {"covered_lines": 12, "num_statements": 12, "percent_covered": 94.44444444444444, "percent_covered_display": "94", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 6, "num_partial_branches": 1, "covered_branches": 5, "missing_branches": 1, "percent_branches_covered": 83.33333333333333, "percent_branches_covered_display": "83"}, "missing_lines": [], "excluded_lines": [], "start_line": 508, "executed_branches": [[509, 510], [509, 518], [514, 515], [519, -508], [519, 520]], "missing_branches": [[514, -508]]}, "GoeChargerDataUpdateCoordinator.__call__": {"executed_lines": [525, 526], "summary": {"covered_lines": 2, "num_statements": 2, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 524, "executed_branches": [], "missing_branches": []}, "GoeChargerDataUpdateCoordinator.clear_data": {"executed_lines": [529, 530, 531, 532, 533, 534, 535, 536, 537, 538], "summary": {"covered_lines": 10, "num_statements": 10, "percent_covered": 91.66666666666667, "percent_covered_display": "92", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 2, "num_partial_branches": 1, "covered_branches": 1, "missing_branches": 1, "percent_branches_covered": 50.0, "percent_branches_covered_display": "50"}, "missing_lines": [], "excluded_lines": [], "start_line": 528, "executed_branches": [[532, 533]], "missing_branches": [[532, 534]]}, "GoeChargerDataUpdateCoordinator.trigger_restart_delayed": {"executed_lines": [542, 544, 545, 546, 547], "summary": {"covered_lines": 5, "num_statements": 5, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 540, "executed_branches": [], "missing_branches": []}, "GoeChargerDataUpdateCoordinator.cancel_existing_debouncer": {"executed_lines": [550, 551], "summary": {"covered_lines": 2, "num_statements": 2, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 2, "num_partial_branches": 0, "covered_branches": 2, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 549, "executed_branches": [[550, -549], [550, 551]], "missing_branches": []}, "GoeChargerDataUpdateCoordinator.request_update_in_sec": {"executed_lines": [554, 555], "summary": {"covered_lines": 2, "num_statements": 2, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 553, "executed_branches": [], "missing_branches": []}, "GoeChargerDataUpdateCoordinator._debounce_coordinator_update": {"executed_lines": [558, 559], "summary": {"covered_lines": 2, "num_statements": 2, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 557, "executed_branches": [], "missing_branches": []}, "GoeChargerDataUpdateCoordinator.handle_write_result": {"executed_lines": [562, 565, 567, 569, 570, 572, 574, 575, 577, 578, 579, 580, 582, 583, 584, 586, 587, 588], "summary": {"covered_lines": 18, "num_statements": 18, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 14, "num_partial_branches": 0, "covered_branches": 14, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 561, "executed_branches": [[562, 565], [562, 567], [569, 570], [569, 572], [575, 577], [575, 586], [577, 578], [577, 582], [582, 583], [582, 586], [586, -561], [586, 587], [587, -561], [587, 588]], "missing_branches": []}, "GoeChargerDataUpdateCoordinator._handle_client_connection_error": {"executed_lines": [595, 596, 597, 598, 599, 600, 601, 602, 604], "summary": {"covered_lines": 9, "num_statements": 9, "percent_covered": 92.3076923076923, "percent_covered_display": "92", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 4, "num_partial_branches": 1, "covered_branches": 3, "missing_branches": 1, "percent_branches_covered": 75.0, "percent_branches_covered_display": "75"}, "missing_lines": [], "excluded_lines": [], "start_line": 591, "executed_branches": [[597, 598], [597, 604], [599, 600]], "missing_branches": [[599, -591]]}, "GoeChargerDataUpdateCoordinator._async_update_data": {"executed_lines": [608, 609, 613, 614, 615, 616, 617, 618, 619, 621, 622, 623, 624, 625, 631, 633, 634, 635, 636, 638, 639, 640], "summary": {"covered_lines": 22, "num_statements": 25, "percent_covered": 84.84848484848484, "percent_covered_display": "85", "missing_lines": 3, "excluded_lines": 0, "percent_statements_covered": 88.0, "percent_statements_covered_display": "88", "num_branches": 8, "num_partial_branches": 2, "covered_branches": 6, "missing_branches": 2, "percent_branches_covered": 75.0, "percent_branches_covered_display": "75"}, "missing_lines": [610, 611, 637], "excluded_lines": [], "start_line": 606, "executed_branches": [[609, 613], [613, 614], [613, 617], [617, 618], [617, 621], [623, 624]], "missing_branches": [[609, 610], [623, 631]]}, "GoeChargerDataUpdateCoordinator.async_write_key": {"executed_lines": [643, 644, 645, 646, 647, 648, 649, 651, 652, 653, 654, 656, 657, 658, 659, 660, 661], "summary": {"covered_lines": 17, "num_statements": 17, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 4, "num_partial_branches": 0, "covered_branches": 4, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 642, "executed_branches": [[643, 644], [643, 647], [647, 648], [647, 651]], "missing_branches": []}, "GoeChargerDataUpdateCoordinator.async_write_multiple_keys": {"executed_lines": [664, 665, 666, 667, 668, 669, 670, 672, 673, 674, 675, 677, 678, 679], "summary": {"covered_lines": 14, "num_statements": 17, "percent_covered": 85.71428571428571, "percent_covered_display": "86", "missing_lines": 3, "excluded_lines": 0, "percent_statements_covered": 82.3529411764706, "percent_statements_covered_display": "82", "num_branches": 4, "num_partial_branches": 0, "covered_branches": 4, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [680, 681, 682], "excluded_lines": [], "start_line": 663, "executed_branches": [[664, 665], [664, 668], [668, 669], [668, 672]], "missing_branches": []}, "GoeChargerDataUpdateCoordinator.read_versions": {"executed_lines": [685, 686, 688, 692, 693, 694, 696, 700, 701, 702, 703, 704, 707, 710, 711, 712, 713, 714, 715, 716, 719, 720, 721, 723, 725, 727, 729, 730, 731, 732, 733, 734, 736, 737, 738, 739, 740, 742, 743, 745, 750, 751, 752, 753, 754, 755, 756, 757, 758, 759, 760, 761, 763, 768, 769, 771, 772, 773, 774, 776, 778, 779, 780, 783, 784, 785, 787, 788, 789, 791, 792, 794, 797, 799, 800, 801, 803, 805, 806, 808, 811, 812, 813, 814, 815, 817, 819, 834], "summary": {"covered_lines": 88, "num_statements": 88, "percent_covered": 99.29577464788733, "percent_covered_display": "99", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 54, "num_partial_branches": 1, "covered_branches": 53, "missing_branches": 1, "percent_branches_covered": 98.14814814814815, "percent_branches_covered_display": "98"}, "missing_lines": [], "excluded_lines": [], "start_line": 684, "executed_branches": [[685, 686], [685, 688], [688, 692], [688, 700], [692, 693], [692, 696], [700, 701], [700, 707], [702, 703], [702, 707], [711, 712], [711, 715], [713, 714], [715, 716], [715, 719], [720, 721], [720, 723], [727, 729], [727, 797], [730, 731], [730, 736], [731, 732], [731, 742], [732, 733], [732, 734], [736, 737], [736, 742], [738, 739], [738, 740], [742, 743], [742, 745], [752, 753], [752, 778], [754, 755], [754, 776], [758, 759], [758, 763], [773, 774], [773, 778], [778, 779], [778, 783], [783, 784], [783, 787], [791, 792], [791, 794], [800, 801], [800, 803], [805, 806], [805, 808], [811, 812], [811, 817], [813, 814], [813, 819]], "missing_branches": [[713, 715]]}, "GoeChargerDataUpdateCoordinator.check_for_16a_limit": {"executed_lines": [837, 838, 839, 840], "summary": {"covered_lines": 4, "num_statements": 4, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 836, "executed_branches": [], "missing_branches": []}, "GoeChargerDataUpdateCoordinator.is_valid_charger_entity": {"executed_lines": [843, 845, 846, 848, 849, 852, 855, 858, 859, 861], "summary": {"covered_lines": 10, "num_statements": 10, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 10, "num_partial_branches": 0, "covered_branches": 10, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 842, "executed_branches": [[843, 845], [843, 852], [845, 846], [845, 848], [848, 849], [848, 861], [852, 855], [852, 858], [858, 859], [858, 861]], "missing_branches": []}, "GoeChargerBaseEntity.__init__": {"executed_lines": [867, 870, 873, 874, 875, 877, 878, 879, 880, 881, 882, 883, 885, 886, 887, 888, 889, 890, 892, 894, 895, 897, 898, 900], "summary": {"covered_lines": 24, "num_statements": 24, "percent_covered": 97.5, "percent_covered_display": "98", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 16, "num_partial_branches": 1, "covered_branches": 15, "missing_branches": 1, "percent_branches_covered": 93.75, "percent_branches_covered_display": "94"}, "missing_lines": [], "excluded_lines": [], "start_line": 866, "executed_branches": [[873, 874], [873, 885], [874, 875], [874, 877], [877, 878], [877, 881], [881, 882], [885, 886], [885, 887], [887, 888], [887, 889], [889, 890], [889, 892], [897, 898], [897, 900]], "missing_branches": [[881, 894]]}, "GoeChargerBaseEntity._name_internal": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 1, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 1, "excluded_lines": 0, "percent_statements_covered": 0.0, "percent_statements_covered_display": "0", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [909], "excluded_lines": [], "start_line": 907, "executed_branches": [], "missing_branches": []}, "GoeChargerBaseEntity.device_info": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 1, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 1, "excluded_lines": 0, "percent_statements_covered": 0.0, "percent_statements_covered_display": "0", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [913], "excluded_lines": [], "start_line": 912, "executed_branches": [], "missing_branches": []}, "GoeChargerBaseEntity.available": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 1, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 1, "excluded_lines": 0, "percent_statements_covered": 0.0, "percent_statements_covered_display": "0", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [918], "excluded_lines": [], "start_line": 916, "executed_branches": [], "missing_branches": []}, "GoeChargerBaseEntity.unique_id": {"executed_lines": [923], "summary": {"covered_lines": 1, "num_statements": 1, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 921, "executed_branches": [], "missing_branches": []}, "GoeChargerBaseEntity._friendly_name_internal": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 12, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 12, "excluded_lines": 0, "percent_statements_covered": 0.0, "percent_statements_covered_display": "0", "num_branches": 10, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 10, "percent_branches_covered": 0.0, "percent_branches_covered_display": "0"}, "missing_lines": [931, 932, 933, 935, 936, 938, 939, 940, 943, 944, 945, 947], "excluded_lines": [], "start_line": 925, "executed_branches": [], "missing_branches": [[932, 933], [932, 935], [935, 936], [935, 938], [939, 940], [939, 943], [943, 944], [943, 947], [944, 945], [944, 947]]}, "": {"executed_lines": [1, 2, 3, 4, 5, 6, 7, 9, 10, 11, 12, 23, 24, 25, 30, 31, 32, 33, 34, 35, 36, 38, 45, 46, 60, 61, 63, 65, 66, 67, 68, 69, 70, 71, 73, 103, 108, 187, 209, 214, 215, 294, 296, 297, 298, 300, 377, 403, 406, 437, 484, 493, 498, 508, 524, 528, 540, 549, 553, 557, 561, 591, 606, 642, 663, 684, 836, 842, 863, 864, 866, 907, 911, 912, 915, 916, 920, 921, 925], "summary": {"covered_lines": 79, "num_statements": 79, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 1, "executed_branches": [], "missing_branches": []}}, "classes": {"GoeChargerDataUpdateCoordinator": {"executed_lines": [301, 302, 303, 304, 306, 307, 310, 311, 312, 314, 315, 316, 325, 326, 336, 337, 339, 340, 341, 343, 346, 347, 348, 349, 350, 351, 352, 353, 354, 356, 358, 359, 360, 361, 362, 363, 368, 371, 374, 375, 378, 379, 380, 381, 382, 383, 384, 386, 387, 388, 391, 392, 393, 394, 396, 398, 404, 410, 411, 412, 413, 416, 417, 418, 419, 423, 424, 425, 428, 431, 432, 433, 435, 438, 442, 445, 446, 448, 452, 453, 454, 456, 457, 458, 460, 463, 464, 465, 468, 470, 471, 472, 473, 474, 475, 476, 478, 479, 481, 482, 486, 487, 494, 495, 496, 499, 500, 501, 502, 503, 504, 505, 506, 509, 510, 511, 512, 513, 514, 515, 516, 518, 519, 520, 521, 525, 526, 529, 530, 531, 532, 533, 534, 535, 536, 537, 538, 542, 544, 545, 546, 547, 550, 551, 554, 555, 558, 559, 562, 565, 567, 569, 570, 572, 574, 575, 577, 578, 579, 580, 582, 583, 584, 586, 587, 588, 595, 596, 597, 598, 599, 600, 601, 602, 604, 608, 609, 613, 614, 615, 616, 617, 618, 619, 621, 622, 623, 624, 625, 631, 633, 634, 635, 636, 638, 639, 640, 643, 644, 645, 646, 647, 648, 649, 651, 652, 653, 654, 656, 657, 658, 659, 660, 661, 664, 665, 666, 667, 668, 669, 670, 672, 673, 674, 675, 677, 678, 679, 685, 686, 688, 692, 693, 694, 696, 700, 701, 702, 703, 704, 707, 710, 711, 712, 713, 714, 715, 716, 719, 720, 721, 723, 725, 727, 729, 730, 731, 732, 733, 734, 736, 737, 738, 739, 740, 742, 743, 745, 750, 751, 752, 753, 754, 755, 756, 757, 758, 759, 760, 761, 763, 768, 769, 771, 772, 773, 774, 776, 778, 779, 780, 783, 784, 785, 787, 788, 789, 791, 792, 794, 797, 799, 800, 801, 803, 805, 806, 808, 811, 812, 813, 814, 815, 817, 819, 834, 837, 838, 839, 840, 843, 845, 846, 848, 849, 852, 855, 858, 859, 861], "summary": {"covered_lines": 330, "num_statements": 339, "percent_covered": 95.91002044989776, "percent_covered_display": "96", "missing_lines": 9, "excluded_lines": 0, "percent_statements_covered": 97.34513274336283, "percent_statements_covered_display": "97", "num_branches": 150, "num_partial_branches": 11, "covered_branches": 139, "missing_branches": 11, "percent_branches_covered": 92.66666666666667, "percent_branches_covered_display": "93"}, "missing_lines": [364, 390, 449, 610, 611, 637, 680, 681, 682], "excluded_lines": [], "start_line": 294, "executed_branches": [[311, 312], [311, 314], [314, 315], [314, 325], [340, 341], [340, 343], [353, 354], [353, 356], [363, 368], [379, -377], [379, 380], [380, 381], [382, 383], [382, 386], [386, 387], [387, 388], [391, -377], [391, 392], [393, 394], [393, 396], [411, 412], [411, 416], [417, 418], [417, 423], [432, 433], [432, 435], [448, 452], [463, 464], [463, 468], [470, 471], [470, 478], [472, 473], [472, 478], [494, -493], [494, 495], [499, -498], [499, 500], [509, 510], [509, 518], [514, 515], [519, -508], [519, 520], [532, 533], [550, -549], [550, 551], [562, 565], [562, 567], [569, 570], [569, 572], [575, 577], [575, 586], [577, 578], [577, 582], [582, 583], [582, 586], [586, -561], [586, 587], [587, -561], [587, 588], [597, 598], [597, 604], [599, 600], [609, 613], [613, 614], [613, 617], [617, 618], [617, 621], [623, 624], [643, 644], [643, 647], [647, 648], [647, 651], [664, 665], [664, 668], [668, 669], [668, 672], [685, 686], [685, 688], [688, 692], [688, 700], [692, 693], [692, 696], [700, 701], [700, 707], [702, 703], [702, 707], [711, 712], [711, 715], [713, 714], [715, 716], [715, 719], [720, 721], [720, 723], [727, 729], [727, 797], [730, 731], [730, 736], [731, 732], [731, 742], [732, 733], [732, 734], [736, 737], [736, 742], [738, 739], [738, 740], [742, 743], [742, 745], [752, 753], [752, 778], [754, 755], [754, 776], [758, 759], [758, 763], [773, 774], [773, 778], [778, 779], [778, 783], [783, 784], [783, 787], [791, 792], [791, 794], [800, 801], [800, 803], [805, 806], [805, 808], [811, 812], [811, 817], [813, 814], [813, 819], [843, 845], [843, 852], [845, 846], [845, 848], [848, 849], [848, 861], [852, 855], [852, 858], [858, 859], [858, 861]], "missing_branches": [[363, 364], [380, -377], [386, -377], [387, 390], [448, 449], [514, -508], [532, 534], [599, -591], [609, 610], [623, 631], [713, 715]]}, "GoeChargerBaseEntity": {"executed_lines": [867, 870, 873, 874, 875, 877, 878, 879, 880, 881, 882, 883, 885, 886, 887, 888, 889, 890, 892, 894, 895, 897, 898, 900, 923], "summary": {"covered_lines": 25, "num_statements": 40, "percent_covered": 60.60606060606061, "percent_covered_display": "61", "missing_lines": 15, "excluded_lines": 0, "percent_statements_covered": 62.5, "percent_statements_covered_display": "62", "num_branches": 26, "num_partial_branches": 1, "covered_branches": 15, "missing_branches": 11, "percent_branches_covered": 57.69230769230769, "percent_branches_covered_display": "58"}, "missing_lines": [909, 913, 918, 931, 932, 933, 935, 936, 938, 939, 940, 943, 944, 945, 947], "excluded_lines": [], "start_line": 863, "executed_branches": [[873, 874], [873, 885], [874, 875], [874, 877], [877, 878], [877, 881], [881, 882], [885, 886], [885, 887], [887, 888], [887, 889], [889, 890], [889, 892], [897, 898], [897, 900]], "missing_branches": [[881, 894], [932, 933], [932, 935], [935, 936], [935, 938], [939, 940], [939, 943], [943, 944], [943, 947], [944, 945], [944, 947]]}, "": {"executed_lines": [1, 2, 3, 4, 5, 6, 7, 9, 10, 11, 12, 23, 24, 25, 30, 31, 32, 33, 34, 35, 36, 38, 45, 46, 60, 61, 63, 65, 66, 67, 68, 69, 70, 71, 73, 74, 75, 76, 77, 78, 81, 82, 85, 86, 87, 88, 89, 90, 91, 92, 93, 97, 98, 100, 103, 105, 108, 109, 110, 111, 112, 113, 116, 117, 118, 120, 122, 123, 124, 125, 126, 127, 128, 129, 131, 132, 133, 135, 136, 137, 138, 140, 141, 144, 145, 148, 149, 150, 151, 153, 154, 155, 156, 158, 162, 165, 166, 173, 175, 176, 177, 179, 180, 182, 184, 187, 188, 190, 191, 192, 193, 194, 195, 196, 197, 198, 201, 202, 203, 204, 206, 209, 210, 211, 214, 215, 216, 217, 218, 219, 220, 221, 224, 225, 226, 227, 228, 230, 231, 232, 233, 234, 236, 238, 240, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 254, 255, 257, 258, 259, 260, 261, 262, 269, 270, 271, 272, 273, 275, 276, 278, 279, 280, 281, 283, 284, 285, 286, 287, 288, 289, 290, 291, 294, 296, 297, 298, 300, 377, 403, 406, 437, 484, 493, 498, 508, 524, 528, 540, 549, 553, 557, 561, 591, 606, 642, 663, 684, 836, 842, 863, 864, 866, 907, 911, 912, 915, 916, 920, 921, 925], "summary": {"covered_lines": 222, "num_statements": 225, "percent_covered": 96.82539682539682, "percent_covered_display": "97", "missing_lines": 3, "excluded_lines": 0, "percent_statements_covered": 98.66666666666667, "percent_statements_covered_display": "99", "num_branches": 90, "num_partial_branches": 7, "covered_branches": 83, "missing_branches": 7, "percent_branches_covered": 92.22222222222223, "percent_branches_covered_display": "92"}, "missing_lines": [80, 94, 95], "excluded_lines": [], "start_line": 1, "executed_branches": [[74, 75], [74, 85], [75, 76], [77, 78], [85, 86], [85, 100], [89, 90], [91, 92], [91, 97], [109, 110], [109, 116], [117, 118], [117, 120], [124, 125], [124, 135], [127, 128], [127, 144], [137, 138], [137, 140], [140, 141], [140, 144], [148, 149], [148, 165], [149, 150], [149, 151], [153, 154], [153, 162], [165, 166], [165, 173], [173, 175], [173, 182], [175, 176], [175, 179], [190, 191], [190, 206], [191, 192], [191, 201], [201, 202], [201, 206], [218, 219], [220, 221], [225, 226], [225, 242], [226, 225], [226, 227], [227, 225], [227, 228], [228, 225], [228, 230], [230, 231], [230, 240], [231, 232], [231, 238], [232, 233], [232, 236], [242, -214], [242, 243], [246, 247], [246, 257], [247, 248], [247, 254], [249, 250], [249, 251], [254, 246], [254, 255], [260, 261], [260, 283], [262, 269], [262, 273], [269, 260], [269, 270], [271, 272], [273, 260], [273, 275], [276, 260], [276, 278], [278, 276], [278, 279], [280, 281], [284, -214], [284, 285], [285, 286], [285, 288]], "missing_branches": [[75, 85], [77, 80], [89, 97], [218, -214], [220, -214], [271, 260], [280, 276]]}}}, "custom_components/goecharger_api2/binary_sensor.py": {"executed_lines": [1, 3, 4, 5, 6, 7, 9, 10, 11, 12, 15, 18, 36, 37, 38, 39, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 53, 54, 56, 58, 61, 62, 63, 64, 66, 67, 68, 69, 70, 73, 74, 75, 76, 77, 78, 80, 81, 83, 84, 86, 88, 90, 92, 93, 95, 96, 98], "summary": {"covered_lines": 58, "num_statements": 71, "percent_covered": 77.14285714285714, "percent_covered_display": "77", "missing_lines": 13, "excluded_lines": 0, "percent_statements_covered": 81.69014084507042, "percent_statements_covered_display": "82", "num_branches": 34, "num_partial_branches": 3, "covered_branches": 23, "missing_branches": 11, "percent_branches_covered": 67.6470588235294, "percent_branches_covered_display": "68"}, "missing_lines": [19, 20, 21, 23, 24, 25, 26, 27, 29, 30, 31, 33, 72], "excluded_lines": [], "executed_branches": [[45, 46], [46, 47], [46, 61], [47, 48], [47, 51], [51, 53], [51, 58], [53, 54], [53, 56], [61, 62], [63, 64], [63, 80], [67, 68], [67, 69], [69, 70], [80, 81], [80, 90], [81, 83], [81, 88], [83, 84], [83, 86], [95, 96], [95, 98]], "missing_branches": [[23, 24], [23, 29], [24, 25], [24, 33], [25, 24], [25, 26], [29, 30], [29, 33], [45, 80], [61, 63], [69, 72]], "functions": {"async_setup_entry": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 12, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 12, "excluded_lines": 0, "percent_statements_covered": 0.0, "percent_statements_covered_display": "0", "num_branches": 8, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 8, "percent_branches_covered": 0.0, "percent_branches_covered_display": "0"}, "missing_lines": [19, 20, 21, 23, 24, 25, 26, 27, 29, 30, 31, 33], "excluded_lines": [], "start_line": 18, "executed_branches": [], "missing_branches": [[23, 24], [23, 29], [24, 25], [24, 33], [25, 24], [25, 26], [29, 30], [29, 33]]}, "GoeChargerApiV2BinarySensor.__init__": {"executed_lines": [38, 39], "summary": {"covered_lines": 2, "num_statements": 2, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 37, "executed_branches": [], "missing_branches": []}, "GoeChargerApiV2BinarySensor.is_on": {"executed_lines": [43, 44, 45, 46, 47, 48, 49, 50, 51, 53, 54, 56, 58, 61, 62, 63, 64, 66, 67, 68, 69, 70, 73, 74, 75, 76, 77, 78, 80, 81, 83, 84, 86, 88, 90], "summary": {"covered_lines": 35, "num_statements": 36, "percent_covered": 93.33333333333333, "percent_covered_display": "93", "missing_lines": 1, "excluded_lines": 0, "percent_statements_covered": 97.22222222222223, "percent_statements_covered_display": "97", "num_branches": 24, "num_partial_branches": 3, "covered_branches": 21, "missing_branches": 3, "percent_branches_covered": 87.5, "percent_branches_covered_display": "88"}, "missing_lines": [72], "excluded_lines": [], "start_line": 42, "executed_branches": [[45, 46], [46, 47], [46, 61], [47, 48], [47, 51], [51, 53], [51, 58], [53, 54], [53, 56], [61, 62], [63, 64], [63, 80], [67, 68], [67, 69], [69, 70], [80, 81], [80, 90], [81, 83], [81, 88], [83, 84], [83, 86]], "missing_branches": [[45, 80], [61, 63], [69, 72]]}, "GoeChargerApiV2BinarySensor.icon": {"executed_lines": [95, 96, 98], "summary": {"covered_lines": 3, "num_statements": 3, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 2, "num_partial_branches": 0, "covered_branches": 2, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 93, "executed_branches": [[95, 96], [95, 98]], "missing_branches": []}, "": {"executed_lines": [1, 3, 4, 5, 6, 7, 9, 10, 11, 12, 15, 18, 36, 37, 41, 42, 92, 93], "summary": {"covered_lines": 18, "num_statements": 18, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 1, "executed_branches": [], "missing_branches": []}}, "classes": {"GoeChargerApiV2BinarySensor": {"executed_lines": [38, 39, 43, 44, 45, 46, 47, 48, 49, 50, 51, 53, 54, 56, 58, 61, 62, 63, 64, 66, 67, 68, 69, 70, 73, 74, 75, 76, 77, 78, 80, 81, 83, 84, 86, 88, 90, 95, 96, 98], "summary": {"covered_lines": 40, "num_statements": 41, "percent_covered": 94.02985074626865, "percent_covered_display": "94", "missing_lines": 1, "excluded_lines": 0, "percent_statements_covered": 97.5609756097561, "percent_statements_covered_display": "98", "num_branches": 26, "num_partial_branches": 3, "covered_branches": 23, "missing_branches": 3, "percent_branches_covered": 88.46153846153847, "percent_branches_covered_display": "88"}, "missing_lines": [72], "excluded_lines": [], "start_line": 36, "executed_branches": [[45, 46], [46, 47], [46, 61], [47, 48], [47, 51], [51, 53], [51, 58], [53, 54], [53, 56], [61, 62], [63, 64], [63, 80], [67, 68], [67, 69], [69, 70], [80, 81], [80, 90], [81, 83], [81, 88], [83, 84], [83, 86], [95, 96], [95, 98]], "missing_branches": [[45, 80], [61, 63], [69, 72]]}, "": {"executed_lines": [1, 3, 4, 5, 6, 7, 9, 10, 11, 12, 15, 18, 36, 37, 41, 42, 92, 93], "summary": {"covered_lines": 18, "num_statements": 30, "percent_covered": 47.36842105263158, "percent_covered_display": "47", "missing_lines": 12, "excluded_lines": 0, "percent_statements_covered": 60.0, "percent_statements_covered_display": "60", "num_branches": 8, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 8, "percent_branches_covered": 0.0, "percent_branches_covered_display": "0"}, "missing_lines": [19, 20, 21, 23, 24, 25, 26, 27, 29, 30, 31, 33], "excluded_lines": [], "start_line": 1, "executed_branches": [], "missing_branches": [[23, 24], [23, 29], [24, 25], [24, 33], [25, 24], [25, 26], [29, 30], [29, 33]]}}}, "custom_components/goecharger_api2/button.py": {"executed_lines": [1, 3, 4, 5, 6, 7, 9, 10, 11, 13, 16, 34, 35, 36, 38, 39, 40, 41, 42], "summary": {"covered_lines": 19, "num_statements": 31, "percent_covered": 48.717948717948715, "percent_covered_display": "49", "missing_lines": 12, "excluded_lines": 0, "percent_statements_covered": 61.29032258064516, "percent_statements_covered_display": "61", "num_branches": 8, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 8, "percent_branches_covered": 0.0, "percent_branches_covered_display": "0"}, "missing_lines": [17, 18, 19, 21, 22, 23, 24, 25, 27, 28, 29, 31], "excluded_lines": [], "executed_branches": [], "missing_branches": [[21, 22], [21, 27], [22, 23], [22, 31], [23, 22], [23, 24], [27, 28], [27, 31]], "functions": {"async_setup_entry": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 12, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 12, "excluded_lines": 0, "percent_statements_covered": 0.0, "percent_statements_covered_display": "0", "num_branches": 8, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 8, "percent_branches_covered": 0.0, "percent_branches_covered_display": "0"}, "missing_lines": [17, 18, 19, 21, 22, 23, 24, 25, 27, 28, 29, 31], "excluded_lines": [], "start_line": 16, "executed_branches": [], "missing_branches": [[21, 22], [21, 27], [22, 23], [22, 31], [23, 22], [23, 24], [27, 28], [27, 31]]}, "GoeChargerApiV2Button.__init__": {"executed_lines": [36], "summary": {"covered_lines": 1, "num_statements": 1, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 35, "executed_branches": [], "missing_branches": []}, "GoeChargerApiV2Button.async_press": {"executed_lines": [39, 40, 41, 42], "summary": {"covered_lines": 4, "num_statements": 4, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 38, "executed_branches": [], "missing_branches": []}, "": {"executed_lines": [1, 3, 4, 5, 6, 7, 9, 10, 11, 13, 16, 34, 35, 38], "summary": {"covered_lines": 14, "num_statements": 14, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 1, "executed_branches": [], "missing_branches": []}}, "classes": {"GoeChargerApiV2Button": {"executed_lines": [36, 39, 40, 41, 42], "summary": {"covered_lines": 5, "num_statements": 5, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 34, "executed_branches": [], "missing_branches": []}, "": {"executed_lines": [1, 3, 4, 5, 6, 7, 9, 10, 11, 13, 16, 34, 35, 38], "summary": {"covered_lines": 14, "num_statements": 26, "percent_covered": 41.1764705882353, "percent_covered_display": "41", "missing_lines": 12, "excluded_lines": 0, "percent_statements_covered": 53.84615384615385, "percent_statements_covered_display": "54", "num_branches": 8, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 8, "percent_branches_covered": 0.0, "percent_branches_covered_display": "0"}, "missing_lines": [17, 18, 19, 21, 22, 23, 24, 25, 27, 28, 29, 31], "excluded_lines": [], "start_line": 1, "executed_branches": [], "missing_branches": [[21, 22], [21, 27], [22, 23], [22, 31], [23, 22], [23, 24], [27, 28], [27, 31]]}}}, "custom_components/goecharger_api2/config_flow.py": {"executed_lines": [1, 2, 3, 4, 6, 7, 8, 9, 10, 21, 22, 24, 33, 39, 41, 43, 44, 47, 50, 51, 52, 54, 56, 58, 59, 60, 62, 63, 64, 66, 67, 68, 70, 71, 72, 74, 75, 76, 78, 79, 80, 81, 82, 83, 84, 85, 87, 88, 89, 90, 91, 92, 93, 94, 96, 97, 98, 99, 101, 102, 105, 107, 111, 129, 131, 137, 138, 139, 140, 141, 143, 144, 145, 146, 147, 148, 149, 151, 152, 154, 155, 156, 158, 160, 161, 163, 172, 196, 198, 204, 205, 207, 208, 209, 211, 212, 213, 214, 215, 216, 217, 219, 220, 224, 225, 226, 228, 230, 231, 233, 242, 268, 271, 275, 276, 277, 278, 279, 280, 281, 282, 284, 285, 289, 290, 306, 307, 308, 310, 311, 312, 316, 318, 319, 324, 325, 329, 331, 332, 333, 334, 340, 341, 342, 343, 344, 345, 347, 349, 351, 352, 353, 354, 357, 358, 359, 360, 366, 367, 368, 370, 371, 372, 376, 378, 379, 380, 381, 382, 385], "summary": {"covered_lines": 170, "num_statements": 178, "percent_covered": 95.04504504504504, "percent_covered_display": "95", "missing_lines": 8, "excluded_lines": 0, "percent_statements_covered": 95.50561797752809, "percent_statements_covered_display": "96", "num_branches": 44, "num_partial_branches": 3, "covered_branches": 41, "missing_branches": 3, "percent_branches_covered": 93.18181818181819, "percent_branches_covered_display": "93"}, "missing_lines": [142, 210, 320, 321, 326, 327, 383, 384], "excluded_lines": [], "executed_branches": [[78, 79], [78, 87], [98, 99], [98, 107], [101, 102], [101, 105], [137, 138], [137, 163], [140, 141], [140, 160], [141, 143], [147, 148], [147, 151], [155, 156], [155, 158], [204, 205], [204, 233], [208, 209], [208, 230], [209, 211], [215, 216], [215, 219], [225, 226], [225, 228], [280, 281], [280, 284], [284, 285], [341, 342], [341, 349], [343, 344], [343, 347], [349, 351], [349, 366], [353, 354], [353, 357], [359, 360], [359, 366], [367, 368], [367, 385], [371, 372], [371, 376]], "missing_branches": [[141, 142], [209, 210], [284, 316]], "functions": {"GoeChargerApiV2FlowHandler.__init__": {"executed_lines": [56, 58, 59, 60, 62, 63, 64, 66, 67, 68, 70, 71, 72], "summary": {"covered_lines": 13, "num_statements": 13, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 54, "executed_branches": [], "missing_branches": []}, "GoeChargerApiV2FlowHandler.async_step_reconfigure": {"executed_lines": [75, 76, 78, 79, 80, 81, 82, 83, 84, 85, 87, 88, 89, 90, 91, 92, 93, 94], "summary": {"covered_lines": 18, "num_statements": 18, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 2, "num_partial_branches": 0, "covered_branches": 2, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 74, "executed_branches": [[78, 79], [78, 87]], "missing_branches": []}, "GoeChargerApiV2FlowHandler.async_step_user": {"executed_lines": [97, 98, 99, 101, 102, 105, 107, 111], "summary": {"covered_lines": 8, "num_statements": 8, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 4, "num_partial_branches": 0, "covered_branches": 4, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 96, "executed_branches": [[98, 99], [98, 107], [101, 102], [101, 105]], "missing_branches": []}, "GoeChargerApiV2FlowHandler.async_step_user_lan": {"executed_lines": [131, 137, 138, 139, 140, 141, 143, 144, 145, 146, 147, 148, 149, 151, 152, 154, 155, 156, 158, 160, 161, 163, 172], "summary": {"covered_lines": 23, "num_statements": 24, "percent_covered": 94.11764705882354, "percent_covered_display": "94", "missing_lines": 1, "excluded_lines": 0, "percent_statements_covered": 95.83333333333333, "percent_statements_covered_display": "96", "num_branches": 10, "num_partial_branches": 1, "covered_branches": 9, "missing_branches": 1, "percent_branches_covered": 90.0, "percent_branches_covered_display": "90"}, "missing_lines": [142], "excluded_lines": [], "start_line": 129, "executed_branches": [[137, 138], [137, 163], [140, 141], [140, 160], [141, 143], [147, 148], [147, 151], [155, 156], [155, 158]], "missing_branches": [[141, 142]]}, "GoeChargerApiV2FlowHandler.async_step_user_wan": {"executed_lines": [198, 204, 205, 207, 208, 209, 211, 212, 213, 214, 215, 216, 217, 219, 220, 224, 225, 226, 228, 230, 231, 233, 242], "summary": {"covered_lines": 23, "num_statements": 24, "percent_covered": 94.11764705882354, "percent_covered_display": "94", "missing_lines": 1, "excluded_lines": 0, "percent_statements_covered": 95.83333333333333, "percent_statements_covered_display": "96", "num_branches": 10, "num_partial_branches": 1, "covered_branches": 9, "missing_branches": 1, "percent_branches_covered": 90.0, "percent_branches_covered_display": "90"}, "missing_lines": [210], "excluded_lines": [], "start_line": 196, "executed_branches": [[204, 205], [204, 233], [208, 209], [208, 230], [209, 211], [215, 216], [215, 219], [225, 226], [225, 228]], "missing_branches": [[209, 210]]}, "GoeChargerApiV2FlowHandler._check_ws_connection_and_flags": {"executed_lines": [271, 275, 276, 277, 278, 279, 280, 281, 282, 284, 285, 289, 290, 306, 307, 308, 310, 311, 312, 316, 318, 319, 324, 325, 329], "summary": {"covered_lines": 25, "num_statements": 29, "percent_covered": 84.84848484848484, "percent_covered_display": "85", "missing_lines": 4, "excluded_lines": 0, "percent_statements_covered": 86.20689655172414, "percent_statements_covered_display": "86", "num_branches": 4, "num_partial_branches": 1, "covered_branches": 3, "missing_branches": 1, "percent_branches_covered": 75.0, "percent_branches_covered_display": "75"}, "missing_lines": [320, 321, 326, 327], "excluded_lines": [], "start_line": 268, "executed_branches": [[280, 281], [280, 284], [284, 285]], "missing_branches": [[284, 316]]}, "GoeChargerApiV2FlowHandler._test_host": {"executed_lines": [332, 333, 334, 340, 341, 342, 343, 344, 345, 347, 349, 351, 352, 353, 354, 357, 358, 359, 360, 366, 367, 368, 370, 371, 372, 376, 378, 379, 380, 381, 382, 385], "summary": {"covered_lines": 32, "num_statements": 34, "percent_covered": 95.83333333333333, "percent_covered_display": "96", "missing_lines": 2, "excluded_lines": 0, "percent_statements_covered": 94.11764705882354, "percent_statements_covered_display": "94", "num_branches": 14, "num_partial_branches": 0, "covered_branches": 14, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [383, 384], "excluded_lines": [], "start_line": 331, "executed_branches": [[341, 342], [341, 349], [343, 344], [343, 347], [349, 351], [349, 366], [353, 354], [353, 357], [359, 360], [359, 366], [367, 368], [367, 385], [371, 372], [371, 376]], "missing_branches": []}, "": {"executed_lines": [1, 2, 3, 4, 6, 7, 8, 9, 10, 21, 22, 24, 33, 39, 41, 43, 44, 47, 50, 51, 52, 54, 74, 96, 129, 196, 268, 331], "summary": {"covered_lines": 28, "num_statements": 28, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 1, "executed_branches": [], "missing_branches": []}}, "classes": {"GoeChargerApiV2FlowHandler": {"executed_lines": [56, 58, 59, 60, 62, 63, 64, 66, 67, 68, 70, 71, 72, 75, 76, 78, 79, 80, 81, 82, 83, 84, 85, 87, 88, 89, 90, 91, 92, 93, 94, 97, 98, 99, 101, 102, 105, 107, 111, 131, 137, 138, 139, 140, 141, 143, 144, 145, 146, 147, 148, 149, 151, 152, 154, 155, 156, 158, 160, 161, 163, 172, 198, 204, 205, 207, 208, 209, 211, 212, 213, 214, 215, 216, 217, 219, 220, 224, 225, 226, 228, 230, 231, 233, 242, 271, 275, 276, 277, 278, 279, 280, 281, 282, 284, 285, 289, 290, 306, 307, 308, 310, 311, 312, 316, 318, 319, 324, 325, 329, 332, 333, 334, 340, 341, 342, 343, 344, 345, 347, 349, 351, 352, 353, 354, 357, 358, 359, 360, 366, 367, 368, 370, 371, 372, 376, 378, 379, 380, 381, 382, 385], "summary": {"covered_lines": 142, "num_statements": 150, "percent_covered": 94.3298969072165, "percent_covered_display": "94", "missing_lines": 8, "excluded_lines": 0, "percent_statements_covered": 94.66666666666667, "percent_statements_covered_display": "95", "num_branches": 44, "num_partial_branches": 3, "covered_branches": 41, "missing_branches": 3, "percent_branches_covered": 93.18181818181819, "percent_branches_covered_display": "93"}, "missing_lines": [142, 210, 320, 321, 326, 327, 383, 384], "excluded_lines": [], "start_line": 47, "executed_branches": [[78, 79], [78, 87], [98, 99], [98, 107], [101, 102], [101, 105], [137, 138], [137, 163], [140, 141], [140, 160], [141, 143], [147, 148], [147, 151], [155, 156], [155, 158], [204, 205], [204, 233], [208, 209], [208, 230], [209, 211], [215, 216], [215, 219], [225, 226], [225, 228], [280, 281], [280, 284], [284, 285], [341, 342], [341, 349], [343, 344], [343, 347], [349, 351], [349, 366], [353, 354], [353, 357], [359, 360], [359, 366], [367, 368], [367, 385], [371, 372], [371, 376]], "missing_branches": [[141, 142], [209, 210], [284, 316]]}, "": {"executed_lines": [1, 2, 3, 4, 6, 7, 8, 9, 10, 21, 22, 24, 33, 39, 41, 43, 44, 47, 50, 51, 52, 54, 74, 96, 129, 196, 268, 331], "summary": {"covered_lines": 28, "num_statements": 28, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 1, "executed_branches": [], "missing_branches": []}}}, "custom_components/goecharger_api2/const.py": {"executed_lines": [1, 2, 4, 5, 6, 7, 8, 9, 10, 16, 17, 20, 21, 22, 23, 25, 26, 28, 37, 38, 39, 40, 42, 43, 45, 46, 47, 48, 49, 52, 53, 54, 57, 58, 59, 60, 61, 62, 63, 66, 67, 68, 71, 72, 73, 74, 75, 76, 77, 80, 81, 82, 83, 84, 87, 89, 216, 242, 710, 837, 1953, 2086, 2097, 2315, 2317, 2327, 2329, 2385, 3242], "summary": {"covered_lines": 69, "num_statements": 69, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "executed_branches": [], "missing_branches": [], "functions": {"": {"executed_lines": [1, 2, 4, 5, 6, 7, 8, 9, 10, 16, 17, 20, 21, 22, 23, 25, 26, 28, 37, 38, 39, 40, 42, 43, 45, 46, 47, 48, 49, 52, 53, 54, 57, 58, 59, 60, 61, 62, 63, 66, 67, 68, 71, 72, 73, 74, 75, 76, 77, 80, 81, 82, 83, 84, 87, 89, 216, 242, 710, 837, 1953, 2086, 2097, 2315, 2317, 2327, 2329, 2385, 3242], "summary": {"covered_lines": 69, "num_statements": 69, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 1, "executed_branches": [], "missing_branches": []}}, "classes": {"ExtBinarySensorEntityDescription": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 0, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 46, "executed_branches": [], "missing_branches": []}, "ExtButtonEntityDescription": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 0, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 53, "executed_branches": [], "missing_branches": []}, "ExtNumberEntityDescription": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 0, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 58, "executed_branches": [], "missing_branches": []}, "ExtSelectEntityDescription": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 0, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 67, "executed_branches": [], "missing_branches": []}, "ExtSensorEntityDescription": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 0, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 72, "executed_branches": [], "missing_branches": []}, "ExtSwitchEntityDescription": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 0, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 81, "executed_branches": [], "missing_branches": []}, "": {"executed_lines": [1, 2, 4, 5, 6, 7, 8, 9, 10, 16, 17, 20, 21, 22, 23, 25, 26, 28, 37, 38, 39, 40, 42, 43, 45, 46, 47, 48, 49, 52, 53, 54, 57, 58, 59, 60, 61, 62, 63, 66, 67, 68, 71, 72, 73, 74, 75, 76, 77, 80, 81, 82, 83, 84, 87, 89, 216, 242, 710, 837, 1953, 2086, 2097, 2315, 2317, 2327, 2329, 2385, 3242], "summary": {"covered_lines": 69, "num_statements": 69, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 1, "executed_branches": [], "missing_branches": []}}}, "custom_components/goecharger_api2/entity.py": {"executed_lines": [1, 3, 4, 5, 7, 9, 13, 16], "summary": {"covered_lines": 8, "num_statements": 31, "percent_covered": 17.77777777777778, "percent_covered_display": "18", "missing_lines": 23, "excluded_lines": 0, "percent_statements_covered": 25.806451612903224, "percent_statements_covered_display": "26", "num_branches": 14, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 14, "percent_branches_covered": 0.0, "percent_branches_covered_display": "0"}, "missing_lines": [20, 22, 23, 26, 27, 30, 33, 34, 35, 36, 38, 39, 40, 41, 42, 44, 45, 46, 49, 50, 51, 52, 54], "excluded_lines": [], "executed_branches": [], "missing_branches": [[22, 23], [22, 26], [26, 27], [26, 30], [33, 34], [33, 54], [38, 39], [38, 44], [39, 38], [39, 40], [44, 45], [44, 49], [49, 50], [49, 54]], "functions": {"CustomFriendlyNameEntity._Entity__async_calculate_state": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 23, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 23, "excluded_lines": 0, "percent_statements_covered": 0.0, "percent_statements_covered_display": "0", "num_branches": 14, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 14, "percent_branches_covered": 0.0, "percent_branches_covered_display": "0"}, "missing_lines": [20, 22, 23, 26, 27, 30, 33, 34, 35, 36, 38, 39, 40, 41, 42, 44, 45, 46, 49, 50, 51, 52, 54], "excluded_lines": [], "start_line": 16, "executed_branches": [], "missing_branches": [[22, 23], [22, 26], [26, 27], [26, 30], [33, 34], [33, 54], [38, 39], [38, 44], [39, 38], [39, 40], [44, 45], [44, 49], [49, 50], [49, 54]]}, "": {"executed_lines": [1, 3, 4, 5, 7, 9, 13, 16], "summary": {"covered_lines": 8, "num_statements": 8, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 1, "executed_branches": [], "missing_branches": []}}, "classes": {"CustomFriendlyNameEntity": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 23, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 23, "excluded_lines": 0, "percent_statements_covered": 0.0, "percent_statements_covered_display": "0", "num_branches": 14, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 14, "percent_branches_covered": 0.0, "percent_branches_covered_display": "0"}, "missing_lines": [20, 22, 23, 26, 27, 30, 33, 34, 35, 36, 38, 39, 40, 41, 42, 44, 45, 46, 49, 50, 51, 52, 54], "excluded_lines": [], "start_line": 13, "executed_branches": [], "missing_branches": [[22, 23], [22, 26], [26, 27], [26, 30], [33, 34], [33, 54], [38, 39], [38, 44], [39, 38], [39, 40], [44, 45], [44, 49], [49, 50], [49, 54]]}, "": {"executed_lines": [1, 3, 4, 5, 7, 9, 13, 16], "summary": {"covered_lines": 8, "num_statements": 8, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 1, "executed_branches": [], "missing_branches": []}}}, "custom_components/goecharger_api2/number.py": {"executed_lines": [1, 2, 4, 10, 11, 12, 13, 15, 16, 17, 19, 21, 46, 47, 48, 50, 60, 61, 62, 63, 64, 65, 66, 68, 70, 71, 72, 73, 74, 76, 77, 78, 80, 81, 83, 84, 87, 89, 91, 93, 94, 96, 98, 99, 100, 103, 104, 105, 108, 110, 113, 114, 115, 118, 119, 120, 123, 125, 126, 128, 129, 130, 131, 133, 134, 135, 137, 138, 139, 140, 142, 143, 144, 146, 147, 148], "summary": {"covered_lines": 76, "num_statements": 94, "percent_covered": 77.46478873239437, "percent_covered_display": "77", "missing_lines": 18, "excluded_lines": 0, "percent_statements_covered": 80.85106382978724, "percent_statements_covered_display": "81", "num_branches": 48, "num_partial_branches": 2, "covered_branches": 34, "missing_branches": 14, "percent_branches_covered": 70.83333333333333, "percent_branches_covered_display": "71"}, "missing_lines": [22, 23, 24, 26, 27, 28, 31, 32, 34, 36, 37, 39, 40, 41, 43, 52, 57, 58], "excluded_lines": [], "executed_branches": [[64, 65], [65, 66], [65, 68], [70, 71], [70, 72], [72, 73], [72, 77], [73, 74], [73, 76], [77, 78], [77, 87], [94, 96], [94, 113], [98, 99], [98, 100], [100, 103], [100, 104], [104, 105], [104, 108], [113, 114], [113, 115], [115, 118], [115, 119], [119, 120], [119, 123], [129, -128], [129, 130], [131, -128], [131, 133], [138, 139], [138, 146], [143, 144], [146, -128], [146, 147]], "missing_branches": [[26, 27], [26, 39], [27, 28], [27, 43], [28, 31], [28, 36], [31, 32], [31, 34], [39, 40], [39, 43], [57, -50], [57, 58], [64, 70], [143, 146]], "functions": {"async_setup_entry": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 15, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 15, "excluded_lines": 0, "percent_statements_covered": 0.0, "percent_statements_covered_display": "0", "num_branches": 10, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 10, "percent_branches_covered": 0.0, "percent_branches_covered_display": "0"}, "missing_lines": [22, 23, 24, 26, 27, 28, 31, 32, 34, 36, 37, 39, 40, 41, 43], "excluded_lines": [], "start_line": 21, "executed_branches": [], "missing_branches": [[26, 27], [26, 39], [27, 28], [27, 43], [28, 31], [28, 36], [31, 32], [31, 34], [39, 40], [39, 43]]}, "GoeChargerNumber.__init__": {"executed_lines": [48], "summary": {"covered_lines": 1, "num_statements": 1, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 47, "executed_branches": [], "missing_branches": []}, "GoeChargerNumber.async_added_to_hass": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 3, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 3, "excluded_lines": 0, "percent_statements_covered": 0.0, "percent_statements_covered_display": "0", "num_branches": 2, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 2, "percent_branches_covered": 0.0, "percent_branches_covered_display": "0"}, "missing_lines": [52, 57, 58], "excluded_lines": [], "start_line": 50, "executed_branches": [], "missing_branches": [[57, -50], [57, 58]]}, "GoeChargerNumber.native_value": {"executed_lines": [62, 63, 64, 65, 66, 68, 70, 71, 72, 73, 74, 76, 77, 78, 80, 81, 83, 84, 87, 89], "summary": {"covered_lines": 20, "num_statements": 20, "percent_covered": 96.875, "percent_covered_display": "97", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 12, "num_partial_branches": 1, "covered_branches": 11, "missing_branches": 1, "percent_branches_covered": 91.66666666666667, "percent_branches_covered_display": "92"}, "missing_lines": [], "excluded_lines": [], "start_line": 61, "executed_branches": [[64, 65], [65, 66], [65, 68], [70, 71], [70, 72], [72, 73], [72, 77], [73, 74], [73, 76], [77, 78], [77, 87]], "missing_branches": [[64, 70]]}, "GoeChargerNumber.async_set_native_value": {"executed_lines": [93, 94, 96, 98, 99, 100, 103, 104, 105, 108, 110, 113, 114, 115, 118, 119, 120, 123, 125, 126], "summary": {"covered_lines": 20, "num_statements": 20, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 14, "num_partial_branches": 0, "covered_branches": 14, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 91, "executed_branches": [[94, 96], [94, 113], [98, 99], [98, 100], [100, 103], [100, 104], [104, 105], [104, 108], [113, 114], [113, 115], [115, 118], [115, 119], [119, 120], [119, 123]], "missing_branches": []}, "GoeChargerNumber.check_amp_min_max": {"executed_lines": [129, 130, 131, 133, 134, 135, 137, 138, 139, 140, 142, 143, 144, 146, 147, 148], "summary": {"covered_lines": 16, "num_statements": 16, "percent_covered": 96.15384615384616, "percent_covered_display": "96", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 10, "num_partial_branches": 1, "covered_branches": 9, "missing_branches": 1, "percent_branches_covered": 90.0, "percent_branches_covered_display": "90"}, "missing_lines": [], "excluded_lines": [], "start_line": 128, "executed_branches": [[129, -128], [129, 130], [131, -128], [131, 133], [138, 139], [138, 146], [143, 144], [146, -128], [146, 147]], "missing_branches": [[143, 146]]}, "": {"executed_lines": [1, 2, 4, 10, 11, 12, 13, 15, 16, 17, 19, 21, 46, 47, 50, 60, 61, 91, 128], "summary": {"covered_lines": 19, "num_statements": 19, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 1, "executed_branches": [], "missing_branches": []}}, "classes": {"GoeChargerNumber": {"executed_lines": [48, 62, 63, 64, 65, 66, 68, 70, 71, 72, 73, 74, 76, 77, 78, 80, 81, 83, 84, 87, 89, 93, 94, 96, 98, 99, 100, 103, 104, 105, 108, 110, 113, 114, 115, 118, 119, 120, 123, 125, 126, 129, 130, 131, 133, 134, 135, 137, 138, 139, 140, 142, 143, 144, 146, 147, 148], "summary": {"covered_lines": 57, "num_statements": 60, "percent_covered": 92.85714285714286, "percent_covered_display": "93", "missing_lines": 3, "excluded_lines": 0, "percent_statements_covered": 95.0, "percent_statements_covered_display": "95", "num_branches": 38, "num_partial_branches": 2, "covered_branches": 34, "missing_branches": 4, "percent_branches_covered": 89.47368421052632, "percent_branches_covered_display": "89"}, "missing_lines": [52, 57, 58], "excluded_lines": [], "start_line": 46, "executed_branches": [[64, 65], [65, 66], [65, 68], [70, 71], [70, 72], [72, 73], [72, 77], [73, 74], [73, 76], [77, 78], [77, 87], [94, 96], [94, 113], [98, 99], [98, 100], [100, 103], [100, 104], [104, 105], [104, 108], [113, 114], [113, 115], [115, 118], [115, 119], [119, 120], [119, 123], [129, -128], [129, 130], [131, -128], [131, 133], [138, 139], [138, 146], [143, 144], [146, -128], [146, 147]], "missing_branches": [[57, -50], [57, 58], [64, 70], [143, 146]]}, "": {"executed_lines": [1, 2, 4, 10, 11, 12, 13, 15, 16, 17, 19, 21, 46, 47, 50, 60, 61, 91, 128], "summary": {"covered_lines": 19, "num_statements": 34, "percent_covered": 43.18181818181818, "percent_covered_display": "43", "missing_lines": 15, "excluded_lines": 0, "percent_statements_covered": 55.88235294117647, "percent_statements_covered_display": "56", "num_branches": 10, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 10, "percent_branches_covered": 0.0, "percent_branches_covered_display": "0"}, "missing_lines": [22, 23, 24, 26, 27, 28, 31, 32, 34, 36, 37, 39, 40, 41, 43], "excluded_lines": [], "start_line": 1, "executed_branches": [], "missing_branches": [[26, 27], [26, 39], [27, 28], [27, 43], [28, 31], [28, 36], [31, 32], [31, 34], [39, 40], [39, 43]]}}}, "custom_components/goecharger_api2/pygoecharger_ha/__init__.py": {"executed_lines": [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 14, 15, 16, 18, 43, 45, 47, 49, 50, 51, 52, 53, 56, 59, 62, 64, 66, 67, 69, 71, 72, 73, 76, 77, 79, 81, 82, 84, 85, 86, 88, 89, 90, 91, 93, 96, 97, 98, 100, 102, 106, 108, 110, 111, 112, 113, 114, 117, 119, 121, 122, 123, 124, 126, 127, 128, 131, 133, 137, 138, 140, 142, 145, 146, 148, 149, 152, 153, 155, 156, 158, 160, 164, 165, 168, 169, 170, 172, 173, 174, 176, 177, 178, 179, 180, 181, 182, 183, 184, 185, 186, 188, 189, 190, 191, 192, 194, 195, 196, 198, 199, 201, 202, 203, 204, 206, 208, 209, 210, 211, 212, 213, 216, 217, 218, 219, 220, 221, 222, 223, 224, 225, 226, 227, 228, 229, 230, 231, 232, 234, 235, 237, 238, 239, 240, 241, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 253, 255, 256, 257, 258, 259, 262, 263, 264, 267, 268, 271, 273, 275, 276, 279, 281, 282, 283, 284, 285, 286, 287, 289, 291, 292, 293, 295, 304, 305, 306, 307, 309, 310, 311, 312, 314, 315, 316, 317, 319, 320, 323, 324, 326, 327, 329, 331, 332, 333, 335, 336, 337, 338, 339, 340, 342, 343, 344, 345, 348, 350, 351, 353, 355, 357, 361, 362, 365, 367, 368, 371, 372, 374, 375, 376, 377, 378, 380, 381, 382, 383, 384, 385, 387, 388, 389, 390, 392, 393, 394, 396, 397, 398, 403, 404, 405, 406, 407, 408, 416, 417, 420, 422, 423, 424, 426, 427, 428, 429, 431, 433, 434, 435, 436, 437, 438, 440, 441, 442, 443, 444, 445, 446, 447, 448, 449, 451, 452, 453, 454, 455, 457, 458, 460, 461, 467, 473, 475, 476, 477, 478, 480, 481, 482, 483, 484, 485, 486, 487, 489, 490, 496, 502, 504, 505, 507, 508, 510, 512, 513, 514, 516, 518, 521, 522, 523, 524, 525, 530, 532, 533, 534, 535, 536, 537, 539, 541, 543, 544, 545, 546, 547, 549, 550, 552, 553, 554, 555, 556, 557, 558, 559, 560, 562, 563, 565, 566, 568, 570, 571, 572, 573, 574, 575, 576, 578, 579, 580, 582, 583, 584, 586, 588, 589, 594, 595, 596, 597, 603, 610, 617, 618, 619, 621, 622, 623, 624, 626, 627, 629, 631, 632, 634, 636, 637, 638, 639, 640, 641, 642, 643, 645, 647, 649, 650, 651, 653, 654, 655, 656, 657, 658, 661, 662, 663, 665, 667, 668, 670, 677, 678, 682, 685, 686, 688, 690, 692, 693, 694, 696, 698, 699, 700, 702, 703, 704, 707, 709, 710, 711, 712, 713, 714, 715, 716, 717, 718, 719, 721, 723, 725, 726, 727, 728, 729, 731, 733, 734, 735, 743, 751, 753, 755, 756, 757, 769, 770, 773, 774, 776, 778, 780, 781, 782, 784, 785, 786, 787, 788, 790, 791, 793, 795, 796, 797, 798, 801, 802, 803, 804, 805, 806, 807, 808, 809, 811, 812, 813, 814, 815, 816, 817, 818, 820, 821, 823, 824, 825, 826, 828, 829, 830, 831, 832, 835, 836, 837, 839, 840, 841, 842, 843, 845, 846, 847, 848, 851, 854, 855, 856, 858, 859, 860, 861, 862, 865, 871, 872, 873, 875, 876, 877, 878, 879, 880, 881, 884, 885, 888, 907, 908, 911, 913, 914, 915, 916, 917, 918, 919, 921, 922, 923, 924, 925, 926, 927, 928, 929, 931, 932, 933, 936, 937, 939, 940, 941, 942, 943, 945, 946, 948, 949, 950, 951, 953, 954, 956, 957, 958, 961, 964, 966, 967, 970, 971, 973, 974, 975, 976, 977, 978, 979, 980, 981, 982, 983, 984, 985, 986, 987, 988, 990, 992, 993, 994, 995, 1002, 1003, 1004, 1008, 1010, 1011, 1012, 1013, 1014, 1016, 1017, 1018, 1020, 1021, 1022, 1023, 1024, 1027, 1028, 1029, 1030, 1032, 1034, 1035, 1037, 1038, 1039, 1041, 1043, 1044, 1045, 1048, 1049, 1050, 1051, 1052, 1054, 1055, 1056, 1057, 1058, 1059, 1062, 1064, 1065, 1070, 1072, 1074, 1075, 1084, 1086, 1087, 1088, 1091, 1093, 1094, 1095, 1096, 1097, 1098, 1099, 1100, 1101, 1102, 1103, 1104, 1105, 1108, 1109, 1110, 1111], "summary": {"covered_lines": 687, "num_statements": 721, "percent_covered": 93.93939393939394, "percent_covered_display": "94", "missing_lines": 34, "excluded_lines": 0, "percent_statements_covered": 95.28432732316227, "percent_statements_covered_display": "95", "num_branches": 302, "num_partial_branches": 26, "covered_branches": 274, "missing_branches": 28, "percent_branches_covered": 90.72847682119205, "percent_branches_covered_display": "91"}, "missing_lines": [116, 463, 464, 468, 469, 470, 471, 492, 493, 498, 499, 500, 501, 591, 592, 598, 599, 600, 601, 605, 606, 607, 608, 705, 866, 867, 868, 869, 996, 997, 1060, 1067, 1106, 1107], "excluded_lines": [], "executed_branches": [[51, 52], [52, 53], [52, 56], [71, 72], [71, 76], [111, 112], [111, 117], [113, 114], [123, -119], [123, 124], [140, 142], [140, 160], [145, 146], [145, 148], [152, 153], [152, 172], [155, 156], [155, 158], [160, 164], [160, 172], [168, 169], [168, 172], [172, 173], [172, 176], [176, 177], [176, 188], [203, 204], [203, 206], [238, 239], [238, 240], [262, -255], [262, 263], [263, -255], [263, 264], [268, 271], [268, 273], [276, 279], [276, 281], [281, 282], [281, 291], [283, 284], [283, 285], [285, 286], [285, 287], [287, 281], [287, 289], [291, 292], [291, 295], [295, 304], [295, 317], [305, 306], [305, 309], [309, 310], [309, 314], [323, 324], [323, 326], [326, 327], [326, 329], [333, 335], [333, 338], [335, 336], [335, 342], [336, 337], [336, 342], [338, 339], [339, 340], [339, 342], [342, 343], [342, 371], [344, 345], [344, 348], [350, 351], [350, 353], [355, -331], [355, 357], [361, 362], [361, 365], [365, -331], [365, 367], [371, 372], [371, 374], [375, 376], [375, 377], [377, 378], [384, -380], [384, 385], [388, 389], [388, 420], [389, 390], [389, 392], [393, 394], [393, 396], [396, 397], [396, 416], [398, -387], [398, 403], [403, -387], [403, 404], [404, 405], [406, 403], [406, 407], [428, 429], [437, 438], [437, 440], [443, 444], [443, 467], [446, 447], [446, 473], [448, 449], [448, 451], [453, 454], [453, 457], [454, 453], [454, 455], [477, 478], [477, 480], [483, 484], [483, 496], [486, 487], [486, 502], [507, 508], [507, 518], [508, 510], [508, 512], [512, 513], [512, 516], [518, 521], [518, 532], [522, 523], [522, 524], [524, 525], [524, 530], [532, 533], [532, 534], [534, 535], [534, 536], [536, 537], [536, 539], [544, 545], [544, 552], [545, 546], [545, 549], [553, 554], [553, 560], [554, 555], [554, 556], [556, 557], [556, 558], [558, 553], [558, 559], [565, 566], [565, 568], [572, 573], [572, 594], [575, 576], [575, 610], [576, 578], [576, 586], [578, 579], [578, 582], [582, 583], [582, 584], [594, 595], [594, 603], [622, 623], [622, 626], [631, 632], [631, 634], [638, 639], [638, 647], [650, 651], [650, 653], [654, -653], [654, 655], [656, 657], [656, 661], [661, 662], [668, 670], [668, 678], [678, 682], [678, 688], [698, 699], [698, 703], [699, 700], [699, 702], [703, 704], [709, 710], [709, 714], [714, 715], [714, 716], [716, 717], [716, 718], [718, 719], [718, 721], [726, 727], [726, 729], [753, 755], [753, 773], [784, 785], [784, 790], [786, 787], [790, 791], [790, 793], [805, 806], [805, 811], [807, 808], [812, 813], [812, 820], [830, 831], [839, 840], [839, 845], [841, 842], [847, 848], [847, 858], [855, 856], [855, 858], [858, 859], [858, 865], [860, 861], [865, 871], [877, 878], [877, 884], [879, 880], [917, 918], [925, 926], [925, 931], [927, 928], [936, 937], [936, 990], [939, 940], [939, 948], [948, 949], [948, 956], [956, 957], [956, 961], [964, 936], [964, 966], [970, 936], [970, 971], [975, 976], [975, 990], [979, 980], [983, 984], [987, 988], [1002, 1003], [1002, 1008], [1020, 1021], [1020, 1037], [1022, 1023], [1022, 1072], [1023, 1024], [1023, 1027], [1028, 1029], [1028, 1032], [1037, 1038], [1037, 1048], [1039, 1041], [1039, 1072], [1043, 1044], [1043, 1072], [1048, 1049], [1048, 1070], [1050, 1051], [1054, 1055], [1054, 1064], [1056, 1057], [1059, 1062], [1064, 1065], [1086, 1087], [1086, 1091], [1095, 1096], [1099, 1100], [1099, 1102], [1105, 1108]], "missing_branches": [[51, 66], [113, 116], [338, 342], [377, -331], [404, 403], [428, 431], [661, -653], [703, 705], [786, 788], [807, 809], [830, 832], [841, 843], [860, 862], [865, 866], [867, 868], [867, 869], [879, 881], [917, 919], [927, 929], [979, 990], [983, 990], [987, 990], [1050, 1054], [1056, 1072], [1059, 1060], [1064, 1067], [1095, 1111], [1105, 1106]], "functions": {"TargetedEvent.__init__": {"executed_lines": [50, 51, 52, 53, 56, 59, 62, 64, 66, 67], "summary": {"covered_lines": 10, "num_statements": 10, "percent_covered": 92.85714285714286, "percent_covered_display": "93", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 4, "num_partial_branches": 1, "covered_branches": 3, "missing_branches": 1, "percent_branches_covered": 75.0, "percent_branches_covered_display": "75"}, "missing_lines": [], "excluded_lines": [], "start_line": 49, "executed_branches": [[51, 52], [52, 53], [52, 56]], "missing_branches": [[51, 66]]}, "TargetedEvent.check_and_set": {"executed_lines": [71, 72, 73, 76, 77, 79], "summary": {"covered_lines": 6, "num_statements": 6, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 2, "num_partial_branches": 0, "covered_branches": 2, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 69, "executed_branches": [[71, 72], [71, 76]], "missing_branches": []}, "TargetedEvent.has_errors": {"executed_lines": [82], "summary": {"covered_lines": 1, "num_statements": 1, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 81, "executed_branches": [], "missing_branches": []}, "TargetedEvent.set_error": {"executed_lines": [85, 86], "summary": {"covered_lines": 2, "num_statements": 2, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 84, "executed_branches": [], "missing_branches": []}, "NonNullDict.none_null_get": {"executed_lines": [90, 91], "summary": {"covered_lines": 2, "num_statements": 2, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 89, "executed_branches": [], "missing_branches": []}, "ExpiringStore.__init__": {"executed_lines": [97, 98], "summary": {"covered_lines": 2, "num_statements": 2, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 96, "executed_branches": [], "missing_branches": []}, "ExpiringStore.set": {"executed_lines": [102, 106], "summary": {"covered_lines": 2, "num_statements": 2, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 100, "executed_branches": [], "missing_branches": []}, "ExpiringStore.get": {"executed_lines": [110, 111, 112, 113, 114, 117], "summary": {"covered_lines": 6, "num_statements": 7, "percent_covered": 81.81818181818181, "percent_covered_display": "82", "missing_lines": 1, "excluded_lines": 0, "percent_statements_covered": 85.71428571428571, "percent_statements_covered_display": "86", "num_branches": 4, "num_partial_branches": 1, "covered_branches": 3, "missing_branches": 1, "percent_branches_covered": 75.0, "percent_branches_covered_display": "75"}, "missing_lines": [116], "excluded_lines": [], "start_line": 108, "executed_branches": [[111, 112], [111, 117], [113, 114]], "missing_branches": [[113, 116]]}, "ExpiringStore._cleanup": {"executed_lines": [121, 122, 123, 124], "summary": {"covered_lines": 4, "num_statements": 4, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 2, "num_partial_branches": 0, "covered_branches": 2, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 119, "executed_branches": [[123, -119], [123, 124]], "missing_branches": []}, "ExpiringStore.__len__": {"executed_lines": [127, 128], "summary": {"covered_lines": 2, "num_statements": 2, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 126, "executed_branches": [], "missing_branches": []}, "GoeChargerApiV2Bridge.__init__": {"executed_lines": [137, 138, 140, 142, 145, 146, 148, 149, 152, 153, 155, 156, 158, 160, 164, 165, 168, 169, 170, 172, 173, 174, 176, 177, 178, 179, 180, 181, 182, 183, 184, 185, 186, 188, 189, 190, 191, 192, 194, 195, 196, 198, 199, 201, 202, 203, 204, 206, 208, 209, 210, 211, 212, 213, 216, 217, 218, 219, 220, 221, 222, 223, 224, 225, 226, 227, 228, 229, 230, 231, 232], "summary": {"covered_lines": 71, "num_statements": 71, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 18, "num_partial_branches": 0, "covered_branches": 18, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 133, "executed_branches": [[140, 142], [140, 160], [145, 146], [145, 148], [152, 153], [152, 172], [155, 156], [155, 158], [160, 164], [160, 172], [168, 169], [168, 172], [172, 173], [172, 176], [176, 177], [176, 188], [203, 204], [203, 206]], "missing_branches": []}, "GoeChargerApiV2Bridge.available_fields": {"executed_lines": [235], "summary": {"covered_lines": 1, "num_statements": 1, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 234, "executed_branches": [], "missing_branches": []}, "GoeChargerApiV2Bridge.clear_data": {"executed_lines": [238, 239, 240, 241, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 253], "summary": {"covered_lines": 16, "num_statements": 16, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 2, "num_partial_branches": 0, "covered_branches": 2, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 237, "executed_branches": [[238, 239], [238, 240]], "missing_branches": []}, "GoeChargerApiV2Bridge.reset_stored_update_ts": {"executed_lines": [256, 257, 258, 259, 262, 263, 264], "summary": {"covered_lines": 7, "num_statements": 7, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 4, "num_partial_branches": 0, "covered_branches": 4, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 255, "executed_branches": [[262, -255], [262, 263], [263, -255], [263, 264]], "missing_branches": []}, "GoeChargerApiV2Bridge.read_system": {"executed_lines": [268, 271, 273], "summary": {"covered_lines": 3, "num_statements": 3, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 2, "num_partial_branches": 0, "covered_branches": 2, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 267, "executed_branches": [[268, 271], [268, 273]], "missing_branches": []}, "GoeChargerApiV2Bridge.read_versions": {"executed_lines": [276, 279, 281, 282, 283, 284, 285, 286, 287, 289, 291, 292, 293, 295, 304, 305, 306, 307, 309, 310, 311, 312, 314, 315, 316, 317], "summary": {"covered_lines": 26, "num_statements": 26, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 18, "num_partial_branches": 0, "covered_branches": 18, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 275, "executed_branches": [[276, 279], [276, 281], [281, 282], [281, 291], [283, 284], [283, 285], [285, 286], [285, 287], [287, 281], [287, 289], [291, 292], [291, 295], [295, 304], [295, 317], [305, 306], [305, 309], [309, 310], [309, 314]], "missing_branches": []}, "GoeChargerApiV2Bridge.read_all": {"executed_lines": [320, 323, 324, 326, 327, 329], "summary": {"covered_lines": 6, "num_statements": 6, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 4, "num_partial_branches": 0, "covered_branches": 4, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 319, "executed_branches": [[323, 324], [323, 326], [326, 327], [326, 329]], "missing_branches": []}, "GoeChargerApiV2Bridge.read_all_states": {"executed_lines": [332, 333, 335, 336, 337, 338, 339, 340, 342, 343, 344, 345, 348, 350, 351, 353, 355, 357, 361, 362, 365, 367, 368, 371, 372, 374, 375, 376, 377, 378], "summary": {"covered_lines": 30, "num_statements": 30, "percent_covered": 96.55172413793103, "percent_covered_display": "97", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 28, "num_partial_branches": 2, "covered_branches": 26, "missing_branches": 2, "percent_branches_covered": 92.85714285714286, "percent_branches_covered_display": "93"}, "missing_lines": [], "excluded_lines": [], "start_line": 331, "executed_branches": [[333, 335], [333, 338], [335, 336], [335, 342], [336, 337], [336, 342], [338, 339], [339, 340], [339, 342], [342, 343], [342, 371], [344, 345], [344, 348], [350, 351], [350, 353], [355, -331], [355, 357], [361, 362], [361, 365], [365, -331], [365, 367], [371, 372], [371, 374], [375, 376], [375, 377], [377, 378]], "missing_branches": [[338, 342], [377, -331]]}, "GoeChargerApiV2Bridge.force_config_update": {"executed_lines": [381, 382, 383, 384, 385], "summary": {"covered_lines": 5, "num_statements": 5, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 2, "num_partial_branches": 0, "covered_branches": 2, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 380, "executed_branches": [[384, -380], [384, 385]], "missing_branches": []}, "GoeChargerApiV2Bridge.read_all_config": {"executed_lines": [388, 389, 390, 392, 393, 394, 396, 397, 398, 403, 404, 405, 406, 407, 408, 416, 417, 420], "summary": {"covered_lines": 18, "num_statements": 18, "percent_covered": 97.05882352941177, "percent_covered_display": "97", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 16, "num_partial_branches": 1, "covered_branches": 15, "missing_branches": 1, "percent_branches_covered": 93.75, "percent_branches_covered_display": "94"}, "missing_lines": [], "excluded_lines": [], "start_line": 387, "executed_branches": [[388, 389], [388, 420], [389, 390], [389, 392], [393, 394], [393, 396], [396, 397], [396, 416], [398, -387], [398, 403], [403, -387], [403, 404], [404, 405], [406, 403], [406, 407]], "missing_branches": [[404, 403]]}, "GoeChargerApiV2Bridge._read_filtered_data_from_ws_source": {"executed_lines": [423, 424, 426, 427, 428, 429, 431], "summary": {"covered_lines": 7, "num_statements": 7, "percent_covered": 88.88888888888889, "percent_covered_display": "89", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 2, "num_partial_branches": 1, "covered_branches": 1, "missing_branches": 1, "percent_branches_covered": 50.0, "percent_branches_covered_display": "50"}, "missing_lines": [], "excluded_lines": [], "start_line": 422, "executed_branches": [[428, 429]], "missing_branches": [[428, 431]]}, "GoeChargerApiV2Bridge._read_filtered_data": {"executed_lines": [434, 435, 436, 437, 438, 440, 441, 442, 443, 444, 445, 446, 447, 448, 449, 451, 452, 453, 454, 455, 457, 458, 460, 461, 467, 473], "summary": {"covered_lines": 26, "num_statements": 32, "percent_covered": 86.36363636363636, "percent_covered_display": "86", "missing_lines": 6, "excluded_lines": 0, "percent_statements_covered": 81.25, "percent_statements_covered_display": "81", "num_branches": 12, "num_partial_branches": 0, "covered_branches": 12, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [463, 464, 468, 469, 470, 471], "excluded_lines": [], "start_line": 433, "executed_branches": [[437, 438], [437, 440], [443, 444], [443, 467], [446, 447], [446, 473], [448, 449], [448, 451], [453, 454], [453, 457], [454, 453], [454, 455]], "missing_branches": []}, "GoeChargerApiV2Bridge._read_all_data": {"executed_lines": [476, 477, 478, 480, 481, 482, 483, 484, 485, 486, 487, 489, 490, 496, 502], "summary": {"covered_lines": 15, "num_statements": 21, "percent_covered": 77.77777777777777, "percent_covered_display": "78", "missing_lines": 6, "excluded_lines": 0, "percent_statements_covered": 71.42857142857143, "percent_statements_covered_display": "71", "num_branches": 6, "num_partial_branches": 0, "covered_branches": 6, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [492, 493, 498, 499, 500, 501], "excluded_lines": [], "start_line": 475, "executed_branches": [[477, 478], [477, 480], [483, 484], [483, 496], [486, 487], [486, 502]], "missing_branches": []}, "GoeChargerApiV2Bridge.write_value_to_key": {"executed_lines": [505, 507, 508, 510, 512, 513, 514, 516, 518, 521, 522, 523, 524, 525, 530, 532, 533, 534, 535, 536, 537, 539, 541], "summary": {"covered_lines": 23, "num_statements": 23, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 18, "num_partial_branches": 0, "covered_branches": 18, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 504, "executed_branches": [[507, 508], [507, 518], [508, 510], [508, 512], [512, 513], [512, 516], [518, 521], [518, 532], [522, 523], [522, 524], [524, 525], [524, 530], [532, 533], [532, 534], [534, 535], [534, 536], [536, 537], [536, 539]], "missing_branches": []}, "GoeChargerApiV2Bridge.write_multiple_values_to_keys": {"executed_lines": [544, 545, 546, 547, 549, 550, 552, 553, 554, 555, 556, 557, 558, 559, 560], "summary": {"covered_lines": 15, "num_statements": 15, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 12, "num_partial_branches": 0, "covered_branches": 12, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 543, "executed_branches": [[544, 545], [544, 552], [545, 546], [545, 549], [553, 554], [553, 560], [554, 555], [554, 556], [556, 557], [556, 558], [558, 553], [558, 559]], "missing_branches": []}, "GoeChargerApiV2Bridge._write_values_int": {"executed_lines": [563, 565, 566, 568, 570, 571, 572, 573, 574, 575, 576, 578, 579, 580, 582, 583, 584, 586, 588, 589, 594, 595, 596, 597, 603, 610], "summary": {"covered_lines": 26, "num_statements": 36, "percent_covered": 80.0, "percent_covered_display": "80", "missing_lines": 10, "excluded_lines": 0, "percent_statements_covered": 72.22222222222223, "percent_statements_covered_display": "72", "num_branches": 14, "num_partial_branches": 0, "covered_branches": 14, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [591, 592, 598, 599, 600, 601, 605, 606, 607, 608], "excluded_lines": [], "start_line": 562, "executed_branches": [[565, 566], [565, 568], [572, 573], [572, 594], [575, 576], [575, 610], [576, 578], [576, 586], [578, 579], [578, 582], [582, 583], [582, 584], [594, 595], [594, 603]], "missing_branches": []}, "GoeChargerApiV2Bridge.ws_set_coordinator": {"executed_lines": [618, 619], "summary": {"covered_lines": 2, "num_statements": 2, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 617, "executed_branches": [], "missing_branches": []}, "GoeChargerApiV2Bridge.ws_check_last_update": {"executed_lines": [622, 623, 624, 626, 627], "summary": {"covered_lines": 5, "num_statements": 5, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 2, "num_partial_branches": 0, "covered_branches": 2, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 621, "executed_branches": [[622, 623], [622, 626]], "missing_branches": []}, "GoeChargerApiV2Bridge.ws_close": {"executed_lines": [631, 632, 634, 636, 637, 638, 639, 640, 641, 642, 643, 645, 647], "summary": {"covered_lines": 13, "num_statements": 13, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 4, "num_partial_branches": 0, "covered_branches": 4, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 629, "executed_branches": [[631, 632], [631, 634], [638, 639], [638, 647]], "missing_branches": []}, "GoeChargerApiV2Bridge._ws_notify_for_new_data": {"executed_lines": [650, 651, 653, 665], "summary": {"covered_lines": 4, "num_statements": 4, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 2, "num_partial_branches": 0, "covered_branches": 2, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 649, "executed_branches": [[650, 651], [650, 653]], "missing_branches": []}, "GoeChargerApiV2Bridge._ws_notify_for_new_data._ws_debounce_coordinator_update": {"executed_lines": [654, 655, 656, 657, 658, 661, 662, 663], "summary": {"covered_lines": 8, "num_statements": 8, "percent_covered": 92.85714285714286, "percent_covered_display": "93", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 6, "num_partial_branches": 1, "covered_branches": 5, "missing_branches": 1, "percent_branches_covered": 83.33333333333333, "percent_branches_covered_display": "83"}, "missing_lines": [], "excluded_lines": [], "start_line": 653, "executed_branches": [[654, -653], [654, 655], [656, 657], [656, 661], [661, 662]], "missing_branches": [[661, -653]]}, "GoeChargerApiV2Bridge._ws_compute_hashed_password": {"executed_lines": [668, 670, 677, 678, 682, 685, 686, 688], "summary": {"covered_lines": 8, "num_statements": 8, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 4, "num_partial_branches": 0, "covered_branches": 4, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 667, "executed_branches": [[668, 670], [668, 678], [678, 682], [678, 688]], "missing_branches": []}, "GoeChargerApiV2Bridge._ws_compute_auth_hash": {"executed_lines": [692, 693, 694], "summary": {"covered_lines": 3, "num_statements": 3, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 690, "executed_branches": [], "missing_branches": []}, "GoeChargerApiV2Bridge._ws_decode_message": {"executed_lines": [698, 699, 700, 702, 703, 704], "summary": {"covered_lines": 6, "num_statements": 7, "percent_covered": 84.61538461538461, "percent_covered_display": "85", "missing_lines": 1, "excluded_lines": 0, "percent_statements_covered": 85.71428571428571, "percent_statements_covered_display": "86", "num_branches": 6, "num_partial_branches": 1, "covered_branches": 5, "missing_branches": 1, "percent_branches_covered": 83.33333333333333, "percent_branches_covered_display": "83"}, "missing_lines": [705], "excluded_lines": [], "start_line": 696, "executed_branches": [[698, 699], [698, 703], [699, 700], [699, 702], [703, 704]], "missing_branches": [[703, 705]]}, "GoeChargerApiV2Bridge._ws_normalize_value": {"executed_lines": [709, 710, 711, 712, 713, 714, 715, 716, 717, 718, 719, 721], "summary": {"covered_lines": 12, "num_statements": 12, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 8, "num_partial_branches": 0, "covered_branches": 8, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 707, "executed_branches": [[709, 710], [709, 714], [714, 715], [714, 716], [716, 717], [716, 718], [718, 719], [718, 721]], "missing_branches": []}, "GoeChargerApiV2Bridge._ws_normalize_dict": {"executed_lines": [725, 726, 727, 728, 729], "summary": {"covered_lines": 5, "num_statements": 5, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 2, "num_partial_branches": 0, "covered_branches": 2, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 723, "executed_branches": [[726, 727], [726, 729]], "missing_branches": []}, "GoeChargerApiV2Bridge._ws_send_command": {"executed_lines": [733, 734, 735, 743, 751, 753, 755, 756, 757, 769, 770, 773, 774, 776], "summary": {"covered_lines": 14, "num_statements": 14, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 2, "num_partial_branches": 0, "covered_branches": 2, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 731, "executed_branches": [[753, 755], [753, 773]], "missing_branches": []}, "GoeChargerApiV2Bridge.ws_connect": {"executed_lines": [780, 781, 782, 784, 785, 786, 787, 788, 790, 791, 793, 795, 796, 797, 798, 801, 802, 803, 804, 805, 806, 807, 808, 809, 811, 812, 813, 814, 815, 816, 817, 818, 820, 821, 823, 824, 825, 826, 828, 829, 830, 831, 832, 835, 836, 837, 839, 840, 841, 842, 843, 845, 846, 847, 848, 851, 854, 855, 856, 858, 859, 860, 861, 862, 865, 871, 872, 873, 875, 876, 877, 878, 879, 880, 881, 884, 885, 888, 907, 908, 911, 913, 914, 915, 916, 917, 918, 919, 921, 922, 923, 924, 925, 926, 927, 928, 929, 931, 932, 933, 936, 937, 939, 940, 941, 942, 943, 945, 946, 948, 949, 950, 951, 953, 954, 956, 957, 958, 961, 964, 966, 967, 970, 971, 973, 974, 975, 976, 977, 978, 979, 980, 981, 982, 983, 984, 985, 986, 987, 988, 990, 992, 993, 994, 995, 1002, 1003, 1004, 1008, 1010, 1011, 1012, 1013, 1014], "summary": {"covered_lines": 154, "num_statements": 160, "percent_covered": 90.990990990991, "percent_covered_display": "91", "missing_lines": 6, "excluded_lines": 0, "percent_statements_covered": 96.25, "percent_statements_covered_display": "96", "num_branches": 62, "num_partial_branches": 12, "covered_branches": 48, "missing_branches": 14, "percent_branches_covered": 77.41935483870968, "percent_branches_covered_display": "77"}, "missing_lines": [866, 867, 868, 869, 996, 997], "excluded_lines": [], "start_line": 778, "executed_branches": [[784, 785], [784, 790], [786, 787], [790, 791], [790, 793], [805, 806], [805, 811], [807, 808], [812, 813], [812, 820], [830, 831], [839, 840], [839, 845], [841, 842], [847, 848], [847, 858], [855, 856], [855, 858], [858, 859], [858, 865], [860, 861], [865, 871], [877, 878], [877, 884], [879, 880], [917, 918], [925, 926], [925, 931], [927, 928], [936, 937], [936, 990], [939, 940], [939, 948], [948, 949], [948, 956], [956, 957], [956, 961], [964, 936], [964, 966], [970, 936], [970, 971], [975, 976], [975, 990], [979, 980], [983, 984], [987, 988], [1002, 1003], [1002, 1008]], "missing_branches": [[786, 788], [807, 809], [830, 832], [841, 843], [860, 862], [865, 866], [867, 868], [867, 869], [879, 881], [917, 919], [927, 929], [979, 990], [983, 990], [987, 990]]}, "GoeChargerApiV2Bridge.extract_ws_message_data": {"executed_lines": [1017, 1018, 1020, 1021, 1022, 1023, 1024, 1027, 1028, 1029, 1030, 1032, 1034, 1035, 1037, 1038, 1039, 1041, 1043, 1044, 1045, 1048, 1049, 1050, 1051, 1052, 1054, 1055, 1056, 1057, 1058, 1059, 1062, 1064, 1065, 1070, 1072], "summary": {"covered_lines": 37, "num_statements": 39, "percent_covered": 90.76923076923077, "percent_covered_display": "91", "missing_lines": 2, "excluded_lines": 0, "percent_statements_covered": 94.87179487179488, "percent_statements_covered_display": "95", "num_branches": 26, "num_partial_branches": 4, "covered_branches": 22, "missing_branches": 4, "percent_branches_covered": 84.61538461538461, "percent_branches_covered_display": "85"}, "missing_lines": [1060, 1067], "excluded_lines": [], "start_line": 1016, "executed_branches": [[1020, 1021], [1020, 1037], [1022, 1023], [1022, 1072], [1023, 1024], [1023, 1027], [1028, 1029], [1028, 1032], [1037, 1038], [1037, 1048], [1039, 1041], [1039, 1072], [1043, 1044], [1043, 1072], [1048, 1049], [1048, 1070], [1050, 1051], [1054, 1055], [1054, 1064], [1056, 1057], [1059, 1062], [1064, 1065]], "missing_branches": [[1050, 1054], [1056, 1072], [1059, 1060], [1064, 1067]]}, "bcryptjs_base64_encode_serial": {"executed_lines": [1084, 1086, 1087, 1088, 1091, 1093, 1094, 1095, 1096, 1097, 1098, 1099, 1100, 1101, 1102, 1103, 1104, 1105, 1108, 1109, 1110, 1111], "summary": {"covered_lines": 22, "num_statements": 24, "percent_covered": 87.5, "percent_covered_display": "88", "missing_lines": 2, "excluded_lines": 0, "percent_statements_covered": 91.66666666666667, "percent_statements_covered_display": "92", "num_branches": 8, "num_partial_branches": 2, "covered_branches": 6, "missing_branches": 2, "percent_branches_covered": 75.0, "percent_branches_covered_display": "75"}, "missing_lines": [1106, 1107], "excluded_lines": [], "start_line": 1075, "executed_branches": [[1086, 1087], [1086, 1091], [1095, 1096], [1099, 1100], [1099, 1102], [1105, 1108]], "missing_branches": [[1095, 1111], [1105, 1106]]}, "": {"executed_lines": [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 14, 15, 16, 18, 43, 45, 47, 49, 69, 81, 84, 88, 89, 93, 96, 100, 108, 119, 126, 131, 133, 234, 237, 255, 267, 275, 319, 331, 380, 387, 422, 433, 475, 504, 543, 562, 617, 621, 629, 649, 667, 690, 696, 707, 723, 731, 778, 1016, 1074, 1075], "summary": {"covered_lines": 62, "num_statements": 62, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 1, "executed_branches": [], "missing_branches": []}}, "classes": {"TargetedEvent": {"executed_lines": [50, 51, 52, 53, 56, 59, 62, 64, 66, 67, 71, 72, 73, 76, 77, 79, 82, 85, 86], "summary": {"covered_lines": 19, "num_statements": 19, "percent_covered": 96.0, "percent_covered_display": "96", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 6, "num_partial_branches": 1, "covered_branches": 5, "missing_branches": 1, "percent_branches_covered": 83.33333333333333, "percent_branches_covered_display": "83"}, "missing_lines": [], "excluded_lines": [], "start_line": 47, "executed_branches": [[51, 52], [52, 53], [52, 56], [71, 72], [71, 76]], "missing_branches": [[51, 66]]}, "NonNullDict": {"executed_lines": [90, 91], "summary": {"covered_lines": 2, "num_statements": 2, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 88, "executed_branches": [], "missing_branches": []}, "ExpiringStore": {"executed_lines": [97, 98, 102, 106, 110, 111, 112, 113, 114, 117, 121, 122, 123, 124, 127, 128], "summary": {"covered_lines": 16, "num_statements": 17, "percent_covered": 91.30434782608695, "percent_covered_display": "91", "missing_lines": 1, "excluded_lines": 0, "percent_statements_covered": 94.11764705882354, "percent_statements_covered_display": "94", "num_branches": 6, "num_partial_branches": 1, "covered_branches": 5, "missing_branches": 1, "percent_branches_covered": 83.33333333333333, "percent_branches_covered_display": "83"}, "missing_lines": [116], "excluded_lines": [], "start_line": 93, "executed_branches": [[111, 112], [111, 117], [113, 114], [123, -119], [123, 124]], "missing_branches": [[113, 116]]}, "GoeChargerApiV2Bridge": {"executed_lines": [137, 138, 140, 142, 145, 146, 148, 149, 152, 153, 155, 156, 158, 160, 164, 165, 168, 169, 170, 172, 173, 174, 176, 177, 178, 179, 180, 181, 182, 183, 184, 185, 186, 188, 189, 190, 191, 192, 194, 195, 196, 198, 199, 201, 202, 203, 204, 206, 208, 209, 210, 211, 212, 213, 216, 217, 218, 219, 220, 221, 222, 223, 224, 225, 226, 227, 228, 229, 230, 231, 232, 235, 238, 239, 240, 241, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 253, 256, 257, 258, 259, 262, 263, 264, 268, 271, 273, 276, 279, 281, 282, 283, 284, 285, 286, 287, 289, 291, 292, 293, 295, 304, 305, 306, 307, 309, 310, 311, 312, 314, 315, 316, 317, 320, 323, 324, 326, 327, 329, 332, 333, 335, 336, 337, 338, 339, 340, 342, 343, 344, 345, 348, 350, 351, 353, 355, 357, 361, 362, 365, 367, 368, 371, 372, 374, 375, 376, 377, 378, 381, 382, 383, 384, 385, 388, 389, 390, 392, 393, 394, 396, 397, 398, 403, 404, 405, 406, 407, 408, 416, 417, 420, 423, 424, 426, 427, 428, 429, 431, 434, 435, 436, 437, 438, 440, 441, 442, 443, 444, 445, 446, 447, 448, 449, 451, 452, 453, 454, 455, 457, 458, 460, 461, 467, 473, 476, 477, 478, 480, 481, 482, 483, 484, 485, 486, 487, 489, 490, 496, 502, 505, 507, 508, 510, 512, 513, 514, 516, 518, 521, 522, 523, 524, 525, 530, 532, 533, 534, 535, 536, 537, 539, 541, 544, 545, 546, 547, 549, 550, 552, 553, 554, 555, 556, 557, 558, 559, 560, 563, 565, 566, 568, 570, 571, 572, 573, 574, 575, 576, 578, 579, 580, 582, 583, 584, 586, 588, 589, 594, 595, 596, 597, 603, 610, 618, 619, 622, 623, 624, 626, 627, 631, 632, 634, 636, 637, 638, 639, 640, 641, 642, 643, 645, 647, 650, 651, 653, 654, 655, 656, 657, 658, 661, 662, 663, 665, 668, 670, 677, 678, 682, 685, 686, 688, 692, 693, 694, 698, 699, 700, 702, 703, 704, 709, 710, 711, 712, 713, 714, 715, 716, 717, 718, 719, 721, 725, 726, 727, 728, 729, 733, 734, 735, 743, 751, 753, 755, 756, 757, 769, 770, 773, 774, 776, 780, 781, 782, 784, 785, 786, 787, 788, 790, 791, 793, 795, 796, 797, 798, 801, 802, 803, 804, 805, 806, 807, 808, 809, 811, 812, 813, 814, 815, 816, 817, 818, 820, 821, 823, 824, 825, 826, 828, 829, 830, 831, 832, 835, 836, 837, 839, 840, 841, 842, 843, 845, 846, 847, 848, 851, 854, 855, 856, 858, 859, 860, 861, 862, 865, 871, 872, 873, 875, 876, 877, 878, 879, 880, 881, 884, 885, 888, 907, 908, 911, 913, 914, 915, 916, 917, 918, 919, 921, 922, 923, 924, 925, 926, 927, 928, 929, 931, 932, 933, 936, 937, 939, 940, 941, 942, 943, 945, 946, 948, 949, 950, 951, 953, 954, 956, 957, 958, 961, 964, 966, 967, 970, 971, 973, 974, 975, 976, 977, 978, 979, 980, 981, 982, 983, 984, 985, 986, 987, 988, 990, 992, 993, 994, 995, 1002, 1003, 1004, 1008, 1010, 1011, 1012, 1013, 1014, 1017, 1018, 1020, 1021, 1022, 1023, 1024, 1027, 1028, 1029, 1030, 1032, 1034, 1035, 1037, 1038, 1039, 1041, 1043, 1044, 1045, 1048, 1049, 1050, 1051, 1052, 1054, 1055, 1056, 1057, 1058, 1059, 1062, 1064, 1065, 1070, 1072], "summary": {"covered_lines": 566, "num_statements": 597, "percent_covered": 93.7428896473265, "percent_covered_display": "94", "missing_lines": 31, "excluded_lines": 0, "percent_statements_covered": 94.80737018425461, "percent_statements_covered_display": "95", "num_branches": 282, "num_partial_branches": 22, "covered_branches": 258, "missing_branches": 24, "percent_branches_covered": 91.48936170212765, "percent_branches_covered_display": "91"}, "missing_lines": [463, 464, 468, 469, 470, 471, 492, 493, 498, 499, 500, 501, 591, 592, 598, 599, 600, 601, 605, 606, 607, 608, 705, 866, 867, 868, 869, 996, 997, 1060, 1067], "excluded_lines": [], "start_line": 131, "executed_branches": [[140, 142], [140, 160], [145, 146], [145, 148], [152, 153], [152, 172], [155, 156], [155, 158], [160, 164], [160, 172], [168, 169], [168, 172], [172, 173], [172, 176], [176, 177], [176, 188], [203, 204], [203, 206], [238, 239], [238, 240], [262, -255], [262, 263], [263, -255], [263, 264], [268, 271], [268, 273], [276, 279], [276, 281], [281, 282], [281, 291], [283, 284], [283, 285], [285, 286], [285, 287], [287, 281], [287, 289], [291, 292], [291, 295], [295, 304], [295, 317], [305, 306], [305, 309], [309, 310], [309, 314], [323, 324], [323, 326], [326, 327], [326, 329], [333, 335], [333, 338], [335, 336], [335, 342], [336, 337], [336, 342], [338, 339], [339, 340], [339, 342], [342, 343], [342, 371], [344, 345], [344, 348], [350, 351], [350, 353], [355, -331], [355, 357], [361, 362], [361, 365], [365, -331], [365, 367], [371, 372], [371, 374], [375, 376], [375, 377], [377, 378], [384, -380], [384, 385], [388, 389], [388, 420], [389, 390], [389, 392], [393, 394], [393, 396], [396, 397], [396, 416], [398, -387], [398, 403], [403, -387], [403, 404], [404, 405], [406, 403], [406, 407], [428, 429], [437, 438], [437, 440], [443, 444], [443, 467], [446, 447], [446, 473], [448, 449], [448, 451], [453, 454], [453, 457], [454, 453], [454, 455], [477, 478], [477, 480], [483, 484], [483, 496], [486, 487], [486, 502], [507, 508], [507, 518], [508, 510], [508, 512], [512, 513], [512, 516], [518, 521], [518, 532], [522, 523], [522, 524], [524, 525], [524, 530], [532, 533], [532, 534], [534, 535], [534, 536], [536, 537], [536, 539], [544, 545], [544, 552], [545, 546], [545, 549], [553, 554], [553, 560], [554, 555], [554, 556], [556, 557], [556, 558], [558, 553], [558, 559], [565, 566], [565, 568], [572, 573], [572, 594], [575, 576], [575, 610], [576, 578], [576, 586], [578, 579], [578, 582], [582, 583], [582, 584], [594, 595], [594, 603], [622, 623], [622, 626], [631, 632], [631, 634], [638, 639], [638, 647], [650, 651], [650, 653], [654, -653], [654, 655], [656, 657], [656, 661], [661, 662], [668, 670], [668, 678], [678, 682], [678, 688], [698, 699], [698, 703], [699, 700], [699, 702], [703, 704], [709, 710], [709, 714], [714, 715], [714, 716], [716, 717], [716, 718], [718, 719], [718, 721], [726, 727], [726, 729], [753, 755], [753, 773], [784, 785], [784, 790], [786, 787], [790, 791], [790, 793], [805, 806], [805, 811], [807, 808], [812, 813], [812, 820], [830, 831], [839, 840], [839, 845], [841, 842], [847, 848], [847, 858], [855, 856], [855, 858], [858, 859], [858, 865], [860, 861], [865, 871], [877, 878], [877, 884], [879, 880], [917, 918], [925, 926], [925, 931], [927, 928], [936, 937], [936, 990], [939, 940], [939, 948], [948, 949], [948, 956], [956, 957], [956, 961], [964, 936], [964, 966], [970, 936], [970, 971], [975, 976], [975, 990], [979, 980], [983, 984], [987, 988], [1002, 1003], [1002, 1008], [1020, 1021], [1020, 1037], [1022, 1023], [1022, 1072], [1023, 1024], [1023, 1027], [1028, 1029], [1028, 1032], [1037, 1038], [1037, 1048], [1039, 1041], [1039, 1072], [1043, 1044], [1043, 1072], [1048, 1049], [1048, 1070], [1050, 1051], [1054, 1055], [1054, 1064], [1056, 1057], [1059, 1062], [1064, 1065]], "missing_branches": [[338, 342], [377, -331], [404, 403], [428, 431], [661, -653], [703, 705], [786, 788], [807, 809], [830, 832], [841, 843], [860, 862], [865, 866], [867, 868], [867, 869], [879, 881], [917, 919], [927, 929], [979, 990], [983, 990], [987, 990], [1050, 1054], [1056, 1072], [1059, 1060], [1064, 1067]]}, "": {"executed_lines": [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 14, 15, 16, 18, 43, 45, 47, 49, 69, 81, 84, 88, 89, 93, 96, 100, 108, 119, 126, 131, 133, 234, 237, 255, 267, 275, 319, 331, 380, 387, 422, 433, 475, 504, 543, 562, 617, 621, 629, 649, 667, 690, 696, 707, 723, 731, 778, 1016, 1074, 1075, 1084, 1086, 1087, 1088, 1091, 1093, 1094, 1095, 1096, 1097, 1098, 1099, 1100, 1101, 1102, 1103, 1104, 1105, 1108, 1109, 1110, 1111], "summary": {"covered_lines": 84, "num_statements": 86, "percent_covered": 95.74468085106383, "percent_covered_display": "96", "missing_lines": 2, "excluded_lines": 0, "percent_statements_covered": 97.67441860465117, "percent_statements_covered_display": "98", "num_branches": 8, "num_partial_branches": 2, "covered_branches": 6, "missing_branches": 2, "percent_branches_covered": 75.0, "percent_branches_covered_display": "75"}, "missing_lines": [1106, 1107], "excluded_lines": [], "start_line": 1, "executed_branches": [[1086, 1087], [1086, 1091], [1095, 1096], [1099, 1100], [1099, 1102], [1105, 1108]], "missing_branches": [[1095, 1111], [1105, 1106]]}}}, "custom_components/goecharger_api2/pygoecharger_ha/const.py": {"executed_lines": [1, 2, 6, 11, 12, 13, 14, 16, 17, 18, 19, 22, 23, 24, 25, 26, 29, 31, 32, 34, 37, 39, 40, 41, 46, 47, 48, 51, 52, 53, 56, 57, 58, 60, 61, 62, 63, 64, 65, 66, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 90, 111], "summary": {"covered_lines": 61, "num_statements": 61, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "executed_branches": [], "missing_branches": [], "functions": {"": {"executed_lines": [1, 2, 6, 11, 12, 13, 14, 16, 17, 18, 19, 22, 23, 24, 25, 26, 29, 31, 32, 34, 37, 39, 40, 41, 46, 47, 48, 51, 52, 53, 56, 57, 58, 60, 61, 62, 63, 64, 65, 66, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 90, 111], "summary": {"covered_lines": 61, "num_statements": 61, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 1, "executed_branches": [], "missing_branches": []}}, "classes": {"INTG_TYPE": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 0, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 56, "executed_branches": [], "missing_branches": []}, "CAR_VALUES": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 0, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 60, "executed_branches": [], "missing_branches": []}, "CT_VALUES": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 0, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 70, "executed_branches": [], "missing_branches": []}, "": {"executed_lines": [1, 2, 6, 11, 12, 13, 14, 16, 17, 18, 19, 22, 23, 24, 25, 26, 29, 31, 32, 34, 37, 39, 40, 41, 46, 47, 48, 51, 52, 53, 56, 57, 58, 60, 61, 62, 63, 64, 65, 66, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 90, 111], "summary": {"covered_lines": 61, "num_statements": 61, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 1, "executed_branches": [], "missing_branches": []}}}, "custom_components/goecharger_api2/pygoecharger_ha/keys.py": {"executed_lines": [1, 2, 3, 9, 11, 13, 14, 15, 16, 17, 20, 23, 24, 27, 29, 32, 36, 38, 40, 42, 44, 46, 48, 50, 52, 54, 56, 58, 60, 62, 64, 66, 68, 70, 72, 74, 76, 78, 80, 82, 84, 95, 98, 100, 102, 104, 107, 109, 111, 113, 115, 117, 119, 121, 123, 125, 127, 129, 131, 133, 135, 137, 139, 141, 143, 145, 147, 149, 151, 153, 155, 157, 159, 161, 163, 165, 167, 169, 171, 173, 175, 179, 181, 183, 185, 187, 189, 191, 193, 195, 197, 199, 201, 203, 205, 207, 209, 211, 213, 215, 217, 219, 221, 223, 225, 227, 229, 232, 234, 236, 238, 240, 242, 244, 246, 248, 250, 252, 254, 256, 258, 260, 262, 264, 266, 268, 270, 272, 274, 276, 278, 280, 282, 284, 286, 288, 290, 292, 294, 296, 298, 300, 302, 304, 306, 308, 310, 312, 314, 316, 318, 320, 322, 324, 326, 328, 330, 332, 334, 336, 338, 340, 342, 344, 346, 350, 353, 354, 355, 359, 361, 366, 372, 378, 384, 390, 396, 402, 409, 411, 413, 415, 426, 428, 430, 432, 434, 436, 438, 440, 442, 444, 446, 448, 450, 452, 454, 456, 458, 460, 462, 464, 466, 523, 525, 527, 529, 531, 533, 535, 537, 539, 541, 543, 545, 547, 549, 551, 560, 562, 564, 566, 568, 570, 664, 668, 670, 673, 689, 691, 693, 695, 697, 699, 701, 703, 705, 707, 737, 739, 743, 753, 767, 785, 787, 789, 791, 793, 805, 807, 809, 811, 813], "summary": {"covered_lines": 253, "num_statements": 255, "percent_covered": 99.2156862745098, "percent_covered_display": "99", "missing_lines": 2, "excluded_lines": 0, "percent_statements_covered": 99.2156862745098, "percent_statements_covered_display": "99", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [30, 33], "excluded_lines": [], "executed_branches": [], "missing_branches": [], "functions": {"Tag.__hash__": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 1, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 1, "excluded_lines": 0, "percent_statements_covered": 0.0, "percent_statements_covered_display": "0", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [30], "excluded_lines": [], "start_line": 29, "executed_branches": [], "missing_branches": []}, "Tag.__str__": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 1, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 1, "excluded_lines": 0, "percent_statements_covered": 0.0, "percent_statements_covered_display": "0", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [33], "excluded_lines": [], "start_line": 32, "executed_branches": [], "missing_branches": []}, "": {"executed_lines": [1, 2, 3, 9, 11, 13, 14, 15, 16, 17, 20, 23, 24, 27, 29, 32, 36, 38, 40, 42, 44, 46, 48, 50, 52, 54, 56, 58, 60, 62, 64, 66, 68, 70, 72, 74, 76, 78, 80, 82, 84, 95, 98, 100, 102, 104, 107, 109, 111, 113, 115, 117, 119, 121, 123, 125, 127, 129, 131, 133, 135, 137, 139, 141, 143, 145, 147, 149, 151, 153, 155, 157, 159, 161, 163, 165, 167, 169, 171, 173, 175, 179, 181, 183, 185, 187, 189, 191, 193, 195, 197, 199, 201, 203, 205, 207, 209, 211, 213, 215, 217, 219, 221, 223, 225, 227, 229, 232, 234, 236, 238, 240, 242, 244, 246, 248, 250, 252, 254, 256, 258, 260, 262, 264, 266, 268, 270, 272, 274, 276, 278, 280, 282, 284, 286, 288, 290, 292, 294, 296, 298, 300, 302, 304, 306, 308, 310, 312, 314, 316, 318, 320, 322, 324, 326, 328, 330, 332, 334, 336, 338, 340, 342, 344, 346, 350, 353, 354, 355, 359, 361, 366, 372, 378, 384, 390, 396, 402, 409, 411, 413, 415, 426, 428, 430, 432, 434, 436, 438, 440, 442, 444, 446, 448, 450, 452, 454, 456, 458, 460, 462, 464, 466, 523, 525, 527, 529, 531, 533, 535, 537, 539, 541, 543, 545, 547, 549, 551, 560, 562, 564, 566, 568, 570, 664, 668, 670, 673, 689, 691, 693, 695, 697, 699, 701, 703, 705, 707, 737, 739, 743, 753, 767, 785, 787, 789, 791, 793, 805, 807, 809, 811, 813], "summary": {"covered_lines": 253, "num_statements": 253, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 1, "executed_branches": [], "missing_branches": []}}, "classes": {"CAT": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 0, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 13, "executed_branches": [], "missing_branches": []}, "ApiKey": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 0, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 20, "executed_branches": [], "missing_branches": []}, "Tag": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 2, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 2, "excluded_lines": 0, "percent_statements_covered": 0.0, "percent_statements_covered_display": "0", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [30, 33], "excluded_lines": [], "start_line": 27, "executed_branches": [], "missing_branches": []}, "": {"executed_lines": [1, 2, 3, 9, 11, 13, 14, 15, 16, 17, 20, 23, 24, 27, 29, 32, 36, 38, 40, 42, 44, 46, 48, 50, 52, 54, 56, 58, 60, 62, 64, 66, 68, 70, 72, 74, 76, 78, 80, 82, 84, 95, 98, 100, 102, 104, 107, 109, 111, 113, 115, 117, 119, 121, 123, 125, 127, 129, 131, 133, 135, 137, 139, 141, 143, 145, 147, 149, 151, 153, 155, 157, 159, 161, 163, 165, 167, 169, 171, 173, 175, 179, 181, 183, 185, 187, 189, 191, 193, 195, 197, 199, 201, 203, 205, 207, 209, 211, 213, 215, 217, 219, 221, 223, 225, 227, 229, 232, 234, 236, 238, 240, 242, 244, 246, 248, 250, 252, 254, 256, 258, 260, 262, 264, 266, 268, 270, 272, 274, 276, 278, 280, 282, 284, 286, 288, 290, 292, 294, 296, 298, 300, 302, 304, 306, 308, 310, 312, 314, 316, 318, 320, 322, 324, 326, 328, 330, 332, 334, 336, 338, 340, 342, 344, 346, 350, 353, 354, 355, 359, 361, 366, 372, 378, 384, 390, 396, 402, 409, 411, 413, 415, 426, 428, 430, 432, 434, 436, 438, 440, 442, 444, 446, 448, 450, 452, 454, 456, 458, 460, 462, 464, 466, 523, 525, 527, 529, 531, 533, 535, 537, 539, 541, 543, 545, 547, 549, 551, 560, 562, 564, 566, 568, 570, 664, 668, 670, 673, 689, 691, 693, 695, 697, 699, 701, 703, 705, 707, 737, 739, 743, 753, 767, 785, 787, 789, 791, 793, 805, 807, 809, 811, 813], "summary": {"covered_lines": 253, "num_statements": 253, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 1, "executed_branches": [], "missing_branches": []}}}, "custom_components/goecharger_api2/select.py": {"executed_lines": [1, 3, 4, 5, 6, 7, 9, 10, 12, 13, 14, 16, 19, 37, 38, 39, 40, 43, 59, 61, 62, 63, 64, 65, 66, 67, 68, 70, 71, 73, 74, 76, 79, 82, 83, 84, 85, 87, 89, 90, 92, 93, 95, 96, 97, 98, 99, 101, 102, 104, 105, 107, 108, 109, 111, 113, 116, 117, 118, 119, 120, 122, 124, 126, 127, 129, 130, 133, 135, 165, 167, 169, 172, 173, 175, 178, 179, 181, 184, 185, 187, 190, 191, 193, 196, 198, 199, 200], "summary": {"covered_lines": 88, "num_statements": 100, "percent_covered": 86.53846153846153, "percent_covered_display": "87", "missing_lines": 12, "excluded_lines": 0, "percent_statements_covered": 88.0, "percent_statements_covered_display": "88", "num_branches": 56, "num_partial_branches": 1, "covered_branches": 47, "missing_branches": 9, "percent_branches_covered": 83.92857142857143, "percent_branches_covered_display": "84"}, "missing_lines": [20, 21, 22, 24, 25, 26, 27, 28, 30, 31, 32, 34], "excluded_lines": [], "executed_branches": [[39, 40], [39, 59], [65, 66], [66, 67], [66, 73], [67, 68], [67, 70], [73, 74], [73, 76], [79, 82], [79, 89], [82, 83], [82, 84], [84, 85], [84, 87], [89, 90], [89, 92], [92, 93], [92, 99], [104, 105], [104, 129], [105, 107], [105, 126], [108, 109], [108, 111], [111, 113], [111, 122], [116, 117], [116, 122], [117, 118], [117, 119], [119, 116], [119, 120], [129, 130], [129, 133], [133, 135], [133, 198], [165, 167], [165, 169], [172, 173], [172, 175], [178, 179], [178, 181], [184, 185], [184, 187], [190, 191], [190, 193]], "missing_branches": [[24, 25], [24, 30], [25, 26], [25, 34], [26, 25], [26, 27], [30, 31], [30, 34], [65, 79]], "functions": {"async_setup_entry": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 12, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 12, "excluded_lines": 0, "percent_statements_covered": 0.0, "percent_statements_covered_display": "0", "num_branches": 8, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 8, "percent_branches_covered": 0.0, "percent_branches_covered_display": "0"}, "missing_lines": [20, 21, 22, 24, 25, 26, 27, 28, 30, 31, 32, 34], "excluded_lines": [], "start_line": 19, "executed_branches": [], "missing_branches": [[24, 25], [24, 30], [25, 26], [25, 34], [26, 25], [26, 27], [30, 31], [30, 34]]}, "GoeChargerSelect.__init__": {"executed_lines": [39, 40, 43, 59], "summary": {"covered_lines": 4, "num_statements": 4, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 2, "num_partial_branches": 0, "covered_branches": 2, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 38, "executed_branches": [[39, 40], [39, 59]], "missing_branches": []}, "GoeChargerSelect.current_option": {"executed_lines": [63, 64, 65, 66, 67, 68, 70, 71, 73, 74, 76, 79, 82, 83, 84, 85, 87, 89, 90, 92, 93, 95, 96, 97, 98, 99], "summary": {"covered_lines": 26, "num_statements": 26, "percent_covered": 97.72727272727273, "percent_covered_display": "98", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 18, "num_partial_branches": 1, "covered_branches": 17, "missing_branches": 1, "percent_branches_covered": 94.44444444444444, "percent_branches_covered_display": "94"}, "missing_lines": [], "excluded_lines": [], "start_line": 62, "executed_branches": [[65, 66], [66, 67], [66, 73], [67, 68], [67, 70], [73, 74], [73, 76], [79, 82], [79, 89], [82, 83], [82, 84], [84, 85], [84, 87], [89, 90], [89, 92], [92, 93], [92, 99]], "missing_branches": [[65, 79]]}, "GoeChargerSelect.async_select_option": {"executed_lines": [102, 104, 105, 107, 108, 109, 111, 113, 116, 117, 118, 119, 120, 122, 124, 126, 127, 129, 130, 133, 135, 165, 167, 169, 172, 173, 175, 178, 179, 181, 184, 185, 187, 190, 191, 193, 196, 198, 199, 200], "summary": {"covered_lines": 40, "num_statements": 40, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 28, "num_partial_branches": 0, "covered_branches": 28, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 101, "executed_branches": [[104, 105], [104, 129], [105, 107], [105, 126], [108, 109], [108, 111], [111, 113], [111, 122], [116, 117], [116, 122], [117, 118], [117, 119], [119, 116], [119, 120], [129, 130], [129, 133], [133, 135], [133, 198], [165, 167], [165, 169], [172, 173], [172, 175], [178, 179], [178, 181], [184, 185], [184, 187], [190, 191], [190, 193]], "missing_branches": []}, "": {"executed_lines": [1, 3, 4, 5, 6, 7, 9, 10, 12, 13, 14, 16, 19, 37, 38, 61, 62, 101], "summary": {"covered_lines": 18, "num_statements": 18, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 1, "executed_branches": [], "missing_branches": []}}, "classes": {"GoeChargerSelect": {"executed_lines": [39, 40, 43, 59, 63, 64, 65, 66, 67, 68, 70, 71, 73, 74, 76, 79, 82, 83, 84, 85, 87, 89, 90, 92, 93, 95, 96, 97, 98, 99, 102, 104, 105, 107, 108, 109, 111, 113, 116, 117, 118, 119, 120, 122, 124, 126, 127, 129, 130, 133, 135, 165, 167, 169, 172, 173, 175, 178, 179, 181, 184, 185, 187, 190, 191, 193, 196, 198, 199, 200], "summary": {"covered_lines": 70, "num_statements": 70, "percent_covered": 99.15254237288136, "percent_covered_display": "99", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 48, "num_partial_branches": 1, "covered_branches": 47, "missing_branches": 1, "percent_branches_covered": 97.91666666666667, "percent_branches_covered_display": "98"}, "missing_lines": [], "excluded_lines": [], "start_line": 37, "executed_branches": [[39, 40], [39, 59], [65, 66], [66, 67], [66, 73], [67, 68], [67, 70], [73, 74], [73, 76], [79, 82], [79, 89], [82, 83], [82, 84], [84, 85], [84, 87], [89, 90], [89, 92], [92, 93], [92, 99], [104, 105], [104, 129], [105, 107], [105, 126], [108, 109], [108, 111], [111, 113], [111, 122], [116, 117], [116, 122], [117, 118], [117, 119], [119, 116], [119, 120], [129, 130], [129, 133], [133, 135], [133, 198], [165, 167], [165, 169], [172, 173], [172, 175], [178, 179], [178, 181], [184, 185], [184, 187], [190, 191], [190, 193]], "missing_branches": [[65, 79]]}, "": {"executed_lines": [1, 3, 4, 5, 6, 7, 9, 10, 12, 13, 14, 16, 19, 37, 38, 61, 62, 101], "summary": {"covered_lines": 18, "num_statements": 30, "percent_covered": 47.36842105263158, "percent_covered_display": "47", "missing_lines": 12, "excluded_lines": 0, "percent_statements_covered": 60.0, "percent_statements_covered_display": "60", "num_branches": 8, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 8, "percent_branches_covered": 0.0, "percent_branches_covered_display": "0"}, "missing_lines": [20, 21, 22, 24, 25, 26, 27, 28, 30, 31, 32, 34], "excluded_lines": [], "start_line": 1, "executed_branches": [], "missing_branches": [[24, 25], [24, 30], [25, 26], [25, 34], [26, 25], [26, 27], [30, 31], [30, 34]]}}}, "custom_components/goecharger_api2/sensor.py": {"executed_lines": [1, 2, 3, 4, 6, 7, 8, 9, 10, 11, 13, 14, 15, 16, 18, 21, 38, 39, 40, 42, 44, 45, 47, 48, 49, 50, 51, 52, 54, 55, 57, 58, 59, 60, 61, 65, 66, 67, 68, 69, 70, 71, 72, 77, 78, 81, 82, 83, 84, 85, 86, 88, 89, 91, 93, 94, 95, 96, 99, 100, 101, 102, 103, 105, 107, 109, 111, 112, 114, 115, 116, 118, 122, 125, 126, 127, 128, 130, 131, 133, 134, 135, 136, 137, 138, 139, 140, 141, 143, 144, 147, 148, 149, 150, 153], "summary": {"covered_lines": 95, "num_statements": 109, "percent_covered": 85.20710059171597, "percent_covered_display": "85", "missing_lines": 14, "excluded_lines": 0, "percent_statements_covered": 87.1559633027523, "percent_statements_covered_display": "87", "num_branches": 60, "num_partial_branches": 3, "covered_branches": 49, "missing_branches": 11, "percent_branches_covered": 81.66666666666667, "percent_branches_covered_display": "82"}, "missing_lines": [22, 23, 24, 26, 27, 28, 29, 30, 32, 33, 34, 36, 63, 145], "excluded_lines": [], "executed_branches": [[57, 58], [59, 60], [59, 61], [69, 70], [69, 111], [70, 71], [70, 94], [77, 78], [77, 93], [81, 82], [81, 83], [83, 84], [83, 85], [85, 86], [85, 88], [88, 89], [88, 91], [94, 95], [94, 96], [96, 99], [96, 109], [100, 101], [100, 107], [102, 103], [102, 105], [111, 112], [111, 114], [114, 115], [114, 122], [115, 116], [115, 118], [122, 125], [122, 133], [125, 126], [125, 130], [127, 128], [127, 130], [130, 131], [130, 133], [133, 134], [133, 135], [135, 136], [135, 137], [137, 138], [137, 153], [138, 139], [138, 140], [140, 141], [144, 147]], "missing_branches": [[26, 27], [26, 32], [27, 28], [27, 36], [28, 27], [28, 29], [32, 33], [32, 36], [57, 63], [140, 153], [144, 145]], "functions": {"async_setup_entry": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 12, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 12, "excluded_lines": 0, "percent_statements_covered": 0.0, "percent_statements_covered_display": "0", "num_branches": 8, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 8, "percent_branches_covered": 0.0, "percent_branches_covered_display": "0"}, "missing_lines": [22, 23, 24, 26, 27, 28, 29, 30, 32, 33, 34, 36], "excluded_lines": [], "start_line": 21, "executed_branches": [], "missing_branches": [[26, 27], [26, 32], [27, 28], [27, 36], [28, 27], [28, 29], [32, 33], [32, 36]]}, "GoeChargerSensor.__init__": {"executed_lines": [45], "summary": {"covered_lines": 1, "num_statements": 1, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 44, "executed_branches": [], "missing_branches": []}, "GoeChargerSensor._camel_to_snake": {"executed_lines": [49, 50, 51, 52], "summary": {"covered_lines": 4, "num_statements": 4, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 48, "executed_branches": [], "missing_branches": []}, "GoeChargerSensor.state": {"executed_lines": [57, 58, 59, 60, 61], "summary": {"covered_lines": 5, "num_statements": 6, "percent_covered": 80.0, "percent_covered_display": "80", "missing_lines": 1, "excluded_lines": 0, "percent_statements_covered": 83.33333333333333, "percent_statements_covered_display": "83", "num_branches": 4, "num_partial_branches": 1, "covered_branches": 3, "missing_branches": 1, "percent_branches_covered": 75.0, "percent_branches_covered_display": "75"}, "missing_lines": [63], "excluded_lines": [], "start_line": 55, "executed_branches": [[57, 58], [59, 60], [59, 61]], "missing_branches": [[57, 63]]}, "GoeChargerSensor.native_value": {"executed_lines": [67, 68, 69, 70, 71, 72, 77, 78, 81, 82, 83, 84, 85, 86, 88, 89, 91, 93, 94, 95, 96, 99, 100, 101, 102, 103, 105, 107, 109, 111, 112, 114, 115, 116, 118, 122, 125, 126, 127, 128, 130, 131, 133, 134, 135, 136, 137, 138, 139, 140, 141, 143, 144, 147, 148, 149, 150, 153], "summary": {"covered_lines": 58, "num_statements": 59, "percent_covered": 97.19626168224299, "percent_covered_display": "97", "missing_lines": 1, "excluded_lines": 0, "percent_statements_covered": 98.30508474576271, "percent_statements_covered_display": "98", "num_branches": 48, "num_partial_branches": 2, "covered_branches": 46, "missing_branches": 2, "percent_branches_covered": 95.83333333333333, "percent_branches_covered_display": "96"}, "missing_lines": [145], "excluded_lines": [], "start_line": 66, "executed_branches": [[69, 70], [69, 111], [70, 71], [70, 94], [77, 78], [77, 93], [81, 82], [81, 83], [83, 84], [83, 85], [85, 86], [85, 88], [88, 89], [88, 91], [94, 95], [94, 96], [96, 99], [96, 109], [100, 101], [100, 107], [102, 103], [102, 105], [111, 112], [111, 114], [114, 115], [114, 122], [115, 116], [115, 118], [122, 125], [122, 133], [125, 126], [125, 130], [127, 128], [127, 130], [130, 131], [130, 133], [133, 134], [133, 135], [135, 136], [135, 137], [137, 138], [137, 153], [138, 139], [138, 140], [140, 141], [144, 147]], "missing_branches": [[140, 153], [144, 145]]}, "": {"executed_lines": [1, 2, 3, 4, 6, 7, 8, 9, 10, 11, 13, 14, 15, 16, 18, 21, 38, 39, 40, 42, 44, 47, 48, 54, 55, 65, 66], "summary": {"covered_lines": 27, "num_statements": 27, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 1, "executed_branches": [], "missing_branches": []}}, "classes": {"GoeChargerSensor": {"executed_lines": [45, 49, 50, 51, 52, 57, 58, 59, 60, 61, 67, 68, 69, 70, 71, 72, 77, 78, 81, 82, 83, 84, 85, 86, 88, 89, 91, 93, 94, 95, 96, 99, 100, 101, 102, 103, 105, 107, 109, 111, 112, 114, 115, 116, 118, 122, 125, 126, 127, 128, 130, 131, 133, 134, 135, 136, 137, 138, 139, 140, 141, 143, 144, 147, 148, 149, 150, 153], "summary": {"covered_lines": 68, "num_statements": 70, "percent_covered": 95.90163934426229, "percent_covered_display": "96", "missing_lines": 2, "excluded_lines": 0, "percent_statements_covered": 97.14285714285714, "percent_statements_covered_display": "97", "num_branches": 52, "num_partial_branches": 3, "covered_branches": 49, "missing_branches": 3, "percent_branches_covered": 94.23076923076923, "percent_branches_covered_display": "94"}, "missing_lines": [63, 145], "excluded_lines": [], "start_line": 42, "executed_branches": [[57, 58], [59, 60], [59, 61], [69, 70], [69, 111], [70, 71], [70, 94], [77, 78], [77, 93], [81, 82], [81, 83], [83, 84], [83, 85], [85, 86], [85, 88], [88, 89], [88, 91], [94, 95], [94, 96], [96, 99], [96, 109], [100, 101], [100, 107], [102, 103], [102, 105], [111, 112], [111, 114], [114, 115], [114, 122], [115, 116], [115, 118], [122, 125], [122, 133], [125, 126], [125, 130], [127, 128], [127, 130], [130, 131], [130, 133], [133, 134], [133, 135], [135, 136], [135, 137], [137, 138], [137, 153], [138, 139], [138, 140], [140, 141], [144, 147]], "missing_branches": [[57, 63], [140, 153], [144, 145]]}, "": {"executed_lines": [1, 2, 3, 4, 6, 7, 8, 9, 10, 11, 13, 14, 15, 16, 18, 21, 38, 39, 40, 42, 44, 47, 48, 54, 55, 65, 66], "summary": {"covered_lines": 27, "num_statements": 39, "percent_covered": 57.4468085106383, "percent_covered_display": "57", "missing_lines": 12, "excluded_lines": 0, "percent_statements_covered": 69.23076923076923, "percent_statements_covered_display": "69", "num_branches": 8, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 8, "percent_branches_covered": 0.0, "percent_branches_covered_display": "0"}, "missing_lines": [22, 23, 24, 26, 27, 28, 29, 30, 32, 33, 34, 36], "excluded_lines": [], "start_line": 1, "executed_branches": [], "missing_branches": [[26, 27], [26, 32], [27, 28], [27, 36], [28, 27], [28, 29], [32, 33], [32, 36]]}}}, "custom_components/goecharger_api2/service.py": {"executed_lines": [1, 2, 3, 4, 6, 8, 9, 10, 11, 13, 14, 15, 17, 19, 21, 23, 25, 26, 30, 32, 33, 35, 37, 38, 39, 41, 42, 44, 45, 49, 50, 51, 53, 55, 56, 57, 60, 62, 63, 64, 65, 66, 67, 69, 71, 72, 73, 74, 75, 76, 77, 78, 80, 85, 86, 87, 88, 89, 95, 96, 97, 99, 100, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 115, 117, 118, 119, 126, 128, 129, 130, 132, 133, 134, 137, 138, 140, 142, 143, 144, 147, 149, 150, 151, 153, 154, 156, 157, 159], "summary": {"covered_lines": 101, "num_statements": 104, "percent_covered": 97.26027397260275, "percent_covered_display": "97", "missing_lines": 3, "excluded_lines": 0, "percent_statements_covered": 97.11538461538461, "percent_statements_covered_display": "97", "num_branches": 42, "num_partial_branches": 1, "covered_branches": 41, "missing_branches": 1, "percent_branches_covered": 97.61904761904762, "percent_branches_covered_display": "98"}, "missing_lines": [24, 27, 28], "excluded_lines": [], "executed_branches": [[23, 25], [37, -35], [37, 38], [60, 62], [60, 71], [63, 64], [63, 71], [64, 65], [64, 71], [66, 67], [66, 69], [74, 75], [74, 99], [75, 76], [75, 77], [77, 78], [77, 80], [88, 89], [88, 99], [96, 97], [96, 99], [99, -55], [99, 100], [103, 104], [103, 137], [110, 111], [110, 126], [118, -102], [118, 119], [129, -102], [129, 130], [133, -102], [133, 134], [137, -102], [137, 138], [143, 144], [143, 147], [149, 150], [149, 156], [150, 151], [150, 153]], "missing_branches": [[23, 24]], "functions": {"_is_finite_number": {"executed_lines": [23, 25, 26], "summary": {"covered_lines": 3, "num_statements": 6, "percent_covered": 50.0, "percent_covered_display": "50", "missing_lines": 3, "excluded_lines": 0, "percent_statements_covered": 50.0, "percent_statements_covered_display": "50", "num_branches": 2, "num_partial_branches": 1, "covered_branches": 1, "missing_branches": 1, "percent_branches_covered": 50.0, "percent_branches_covered_display": "50"}, "missing_lines": [24, 27, 28], "excluded_lines": [], "start_line": 21, "executed_branches": [[23, 25]], "missing_branches": [[23, 24]]}, "add_config_entry_to_service": {"executed_lines": [32, 33], "summary": {"covered_lines": 2, "num_statements": 2, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 30, "executed_branches": [], "missing_branches": []}, "remove_config_entry_from_service": {"executed_lines": [37, 38, 39], "summary": {"covered_lines": 3, "num_statements": 3, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 2, "num_partial_branches": 0, "covered_branches": 2, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 35, "executed_branches": [[37, -35], [37, 38]], "missing_branches": []}, "GoeChargerApiV2Service.__init__": {"executed_lines": [44, 45, 49, 50, 51, 53], "summary": {"covered_lines": 6, "num_statements": 6, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 42, "executed_branches": [], "missing_branches": []}, "GoeChargerApiV2Service.set_pv_data": {"executed_lines": [56, 57, 60, 62, 63, 64, 65, 66, 67, 69, 71, 72, 73, 74, 75, 76, 77, 78, 80, 85, 86, 87, 88, 89, 95, 96, 97, 99, 100], "summary": {"covered_lines": 29, "num_statements": 29, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 20, "num_partial_branches": 0, "covered_branches": 20, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 55, "executed_branches": [[60, 62], [60, 71], [63, 64], [63, 71], [64, 65], [64, 71], [66, 67], [66, 69], [74, 75], [74, 99], [75, 76], [75, 77], [77, 78], [77, 80], [88, 89], [88, 99], [96, 97], [96, 99], [99, -55], [99, 100]], "missing_branches": []}, "GoeChargerApiV2Service.stop_charging": {"executed_lines": [103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 115, 117, 118, 119, 126, 128, 129, 130, 132, 133, 134, 137, 138], "summary": {"covered_lines": 24, "num_statements": 24, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 12, "num_partial_branches": 0, "covered_branches": 12, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 102, "executed_branches": [[103, 104], [103, 137], [110, 111], [110, 126], [118, -102], [118, 119], [129, -102], [129, 130], [133, -102], [133, 134], [137, -102], [137, 138]], "missing_branches": []}, "GoeChargerApiV2Service.get_coordinator": {"executed_lines": [142, 143, 144, 147, 149, 150, 151, 153, 154, 156, 157, 159], "summary": {"covered_lines": 12, "num_statements": 12, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 6, "num_partial_branches": 0, "covered_branches": 6, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 140, "executed_branches": [[143, 144], [143, 147], [149, 150], [149, 156], [150, 151], [150, 153]], "missing_branches": []}, "": {"executed_lines": [1, 2, 3, 4, 6, 8, 9, 10, 11, 13, 14, 15, 17, 19, 21, 30, 35, 41, 42, 55, 102, 140], "summary": {"covered_lines": 22, "num_statements": 22, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 1, "executed_branches": [], "missing_branches": []}}, "classes": {"GoeChargerApiV2Service": {"executed_lines": [44, 45, 49, 50, 51, 53, 56, 57, 60, 62, 63, 64, 65, 66, 67, 69, 71, 72, 73, 74, 75, 76, 77, 78, 80, 85, 86, 87, 88, 89, 95, 96, 97, 99, 100, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 115, 117, 118, 119, 126, 128, 129, 130, 132, 133, 134, 137, 138, 142, 143, 144, 147, 149, 150, 151, 153, 154, 156, 157, 159], "summary": {"covered_lines": 71, "num_statements": 71, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 38, "num_partial_branches": 0, "covered_branches": 38, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 41, "executed_branches": [[60, 62], [60, 71], [63, 64], [63, 71], [64, 65], [64, 71], [66, 67], [66, 69], [74, 75], [74, 99], [75, 76], [75, 77], [77, 78], [77, 80], [88, 89], [88, 99], [96, 97], [96, 99], [99, -55], [99, 100], [103, 104], [103, 137], [110, 111], [110, 126], [118, -102], [118, 119], [129, -102], [129, 130], [133, -102], [133, 134], [137, -102], [137, 138], [143, 144], [143, 147], [149, 150], [149, 156], [150, 151], [150, 153]], "missing_branches": []}, "": {"executed_lines": [1, 2, 3, 4, 6, 8, 9, 10, 11, 13, 14, 15, 17, 19, 21, 23, 25, 26, 30, 32, 33, 35, 37, 38, 39, 41, 42, 55, 102, 140], "summary": {"covered_lines": 30, "num_statements": 33, "percent_covered": 89.1891891891892, "percent_covered_display": "89", "missing_lines": 3, "excluded_lines": 0, "percent_statements_covered": 90.9090909090909, "percent_statements_covered_display": "91", "num_branches": 4, "num_partial_branches": 1, "covered_branches": 3, "missing_branches": 1, "percent_branches_covered": 75.0, "percent_branches_covered_display": "75"}, "missing_lines": [24, 27, 28], "excluded_lines": [], "start_line": 1, "executed_branches": [[23, 25], [37, -35], [37, 38]], "missing_branches": [[23, 24]]}}}, "custom_components/goecharger_api2/switch.py": {"executed_lines": [1, 2, 4, 5, 6, 7, 8, 10, 11, 12, 14, 17, 35, 36, 37, 38, 40, 42, 43, 44, 46, 47, 48, 50, 52, 53, 55, 56, 58, 60, 61, 63, 64, 66, 68, 69, 70, 72, 73, 74, 76, 78, 79, 81, 82, 84, 86, 87, 88, 89, 91, 92, 93, 94, 95, 96, 97, 98, 100, 102, 103, 104, 105, 107, 108, 109, 110, 111, 113, 114, 115, 116, 118, 119, 121, 122, 124, 126, 128, 130, 131, 133, 134, 135, 137, 138, 140, 141, 143], "summary": {"covered_lines": 89, "num_statements": 101, "percent_covered": 85.23489932885906, "percent_covered_display": "85", "missing_lines": 12, "excluded_lines": 0, "percent_statements_covered": 88.11881188118812, "percent_statements_covered_display": "88", "num_branches": 48, "num_partial_branches": 2, "covered_branches": 38, "missing_branches": 10, "percent_branches_covered": 79.16666666666667, "percent_branches_covered_display": "79"}, "missing_lines": [18, 19, 20, 22, 23, 24, 25, 26, 28, 29, 30, 32], "excluded_lines": [], "executed_branches": [[43, 44], [43, 55], [44, 46], [44, 52], [47, 48], [47, 50], [55, 56], [55, 58], [69, 70], [69, 81], [70, 72], [70, 78], [73, 74], [73, 76], [81, 82], [81, 84], [95, 96], [96, 97], [96, 102], [97, 98], [97, 100], [102, 103], [104, 105], [104, 113], [113, 114], [113, 128], [114, 115], [114, 119], [115, 116], [115, 118], [119, 121], [119, 126], [121, 122], [121, 124], [133, 134], [133, 135], [140, 141], [140, 143]], "missing_branches": [[22, 23], [22, 28], [23, 24], [23, 32], [24, 23], [24, 25], [28, 29], [28, 32], [95, 113], [102, 104]], "functions": {"async_setup_entry": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 12, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 12, "excluded_lines": 0, "percent_statements_covered": 0.0, "percent_statements_covered_display": "0", "num_branches": 8, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 8, "percent_branches_covered": 0.0, "percent_branches_covered_display": "0"}, "missing_lines": [18, 19, 20, 22, 23, 24, 25, 26, 28, 29, 30, 32], "excluded_lines": [], "start_line": 17, "executed_branches": [], "missing_branches": [[22, 23], [22, 28], [23, 24], [23, 32], [24, 23], [24, 25], [28, 29], [28, 32]]}, "GoeChargerSwitch.__init__": {"executed_lines": [37, 38], "summary": {"covered_lines": 2, "num_statements": 2, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 36, "executed_branches": [], "missing_branches": []}, "GoeChargerSwitch.async_turn_on": {"executed_lines": [42, 43, 44, 46, 47, 48, 50, 52, 53, 55, 56, 58, 60, 61, 63, 64], "summary": {"covered_lines": 16, "num_statements": 16, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 8, "num_partial_branches": 0, "covered_branches": 8, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 40, "executed_branches": [[43, 44], [43, 55], [44, 46], [44, 52], [47, 48], [47, 50], [55, 56], [55, 58]], "missing_branches": []}, "GoeChargerSwitch.async_turn_off": {"executed_lines": [68, 69, 70, 72, 73, 74, 76, 78, 79, 81, 82, 84, 86, 87, 88, 89], "summary": {"covered_lines": 16, "num_statements": 16, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 8, "num_partial_branches": 0, "covered_branches": 8, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 66, "executed_branches": [[69, 70], [69, 81], [70, 72], [70, 78], [73, 74], [73, 76], [81, 82], [81, 84]], "missing_branches": []}, "GoeChargerSwitch.is_on": {"executed_lines": [93, 94, 95, 96, 97, 98, 100, 102, 103, 104, 105, 107, 108, 109, 110, 111, 113, 114, 115, 116, 118, 119, 121, 122, 124, 126, 128], "summary": {"covered_lines": 27, "num_statements": 27, "percent_covered": 95.74468085106383, "percent_covered_display": "96", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 20, "num_partial_branches": 2, "covered_branches": 18, "missing_branches": 2, "percent_branches_covered": 90.0, "percent_branches_covered_display": "90"}, "missing_lines": [], "excluded_lines": [], "start_line": 92, "executed_branches": [[95, 96], [96, 97], [96, 102], [97, 98], [97, 100], [102, 103], [104, 105], [104, 113], [113, 114], [113, 128], [114, 115], [114, 119], [115, 116], [115, 118], [119, 121], [119, 126], [121, 122], [121, 124]], "missing_branches": [[95, 113], [102, 104]]}, "GoeChargerSwitch.state": {"executed_lines": [133, 134, 135], "summary": {"covered_lines": 3, "num_statements": 3, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 2, "num_partial_branches": 0, "covered_branches": 2, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 131, "executed_branches": [[133, 134], [133, 135]], "missing_branches": []}, "GoeChargerSwitch.icon": {"executed_lines": [140, 141, 143], "summary": {"covered_lines": 3, "num_statements": 3, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 2, "num_partial_branches": 0, "covered_branches": 2, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 138, "executed_branches": [[140, 141], [140, 143]], "missing_branches": []}, "": {"executed_lines": [1, 2, 4, 5, 6, 7, 8, 10, 11, 12, 14, 17, 35, 36, 40, 66, 91, 92, 130, 131, 137, 138], "summary": {"covered_lines": 22, "num_statements": 22, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 1, "executed_branches": [], "missing_branches": []}}, "classes": {"GoeChargerSwitch": {"executed_lines": [37, 38, 42, 43, 44, 46, 47, 48, 50, 52, 53, 55, 56, 58, 60, 61, 63, 64, 68, 69, 70, 72, 73, 74, 76, 78, 79, 81, 82, 84, 86, 87, 88, 89, 93, 94, 95, 96, 97, 98, 100, 102, 103, 104, 105, 107, 108, 109, 110, 111, 113, 114, 115, 116, 118, 119, 121, 122, 124, 126, 128, 133, 134, 135, 140, 141, 143], "summary": {"covered_lines": 67, "num_statements": 67, "percent_covered": 98.13084112149532, "percent_covered_display": "98", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 40, "num_partial_branches": 2, "covered_branches": 38, "missing_branches": 2, "percent_branches_covered": 95.0, "percent_branches_covered_display": "95"}, "missing_lines": [], "excluded_lines": [], "start_line": 35, "executed_branches": [[43, 44], [43, 55], [44, 46], [44, 52], [47, 48], [47, 50], [55, 56], [55, 58], [69, 70], [69, 81], [70, 72], [70, 78], [73, 74], [73, 76], [81, 82], [81, 84], [95, 96], [96, 97], [96, 102], [97, 98], [97, 100], [102, 103], [104, 105], [104, 113], [113, 114], [113, 128], [114, 115], [114, 119], [115, 116], [115, 118], [119, 121], [119, 126], [121, 122], [121, 124], [133, 134], [133, 135], [140, 141], [140, 143]], "missing_branches": [[95, 113], [102, 104]]}, "": {"executed_lines": [1, 2, 4, 5, 6, 7, 8, 10, 11, 12, 14, 17, 35, 36, 40, 66, 91, 92, 130, 131, 137, 138], "summary": {"covered_lines": 22, "num_statements": 34, "percent_covered": 52.38095238095238, "percent_covered_display": "52", "missing_lines": 12, "excluded_lines": 0, "percent_statements_covered": 64.70588235294117, "percent_statements_covered_display": "65", "num_branches": 8, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 8, "percent_branches_covered": 0.0, "percent_branches_covered_display": "0"}, "missing_lines": [18, 19, 20, 22, 23, 24, 25, 26, 28, 29, 30, 32], "excluded_lines": [], "start_line": 1, "executed_branches": [], "missing_branches": [[22, 23], [22, 28], [23, 24], [23, 32], [24, 23], [24, 25], [28, 29], [28, 32]]}}}}, "totals": {"covered_lines": 2351, "num_statements": 2529, "percent_covered": 90.84323384526225, "percent_covered_display": "91", "missing_lines": 178, "excluded_lines": 0, "percent_statements_covered": 92.9616449189403, "percent_statements_covered_display": "93", "num_branches": 922, "num_partial_branches": 60, "covered_branches": 784, "missing_branches": 138, "percent_branches_covered": 85.03253796095444, "percent_branches_covered_display": "85"}} \ No newline at end of file diff --git a/tests/reports/2026-10-29/coverage-baseline.json b/tests/reports/2026-10-29/coverage-baseline.json deleted file mode 100644 index f259c51..0000000 --- a/tests/reports/2026-10-29/coverage-baseline.json +++ /dev/null @@ -1 +0,0 @@ -{"meta": {"format": 3, "version": "7.16.2", "timestamp": "2026-10-29T11:24:54.047351", "branch_coverage": true, "show_contexts": false}, "files": {"custom_components/goecharger_api2/__init__.py": {"executed_lines": [1, 2, 3, 4, 5, 6, 7, 9, 10, 11, 12, 23, 24, 25, 30, 31, 32, 33, 34, 35, 36, 38, 45, 46, 60, 61, 63, 65, 66, 67, 68, 69, 70, 71, 73, 74, 75, 76, 77, 78, 81, 82, 85, 86, 87, 88, 89, 90, 91, 92, 93, 97, 98, 100, 103, 105, 108, 109, 110, 111, 112, 113, 116, 117, 118, 122, 123, 124, 125, 126, 127, 128, 129, 131, 132, 133, 135, 136, 137, 138, 140, 141, 187, 188, 190, 191, 192, 193, 194, 195, 196, 197, 198, 201, 202, 203, 204, 206, 209, 214, 215, 294, 296, 297, 298, 300, 301, 302, 303, 304, 306, 307, 310, 311, 312, 314, 315, 316, 325, 326, 336, 337, 339, 340, 341, 346, 347, 348, 349, 350, 351, 352, 353, 354, 356, 358, 359, 360, 361, 362, 363, 368, 371, 374, 375, 377, 403, 406, 437, 484, 493, 498, 508, 524, 528, 540, 549, 553, 557, 561, 562, 567, 569, 570, 572, 574, 575, 577, 582, 586, 587, 591, 595, 596, 597, 598, 599, 600, 601, 602, 604, 606, 608, 609, 613, 614, 615, 616, 617, 618, 619, 621, 622, 623, 624, 625, 631, 633, 634, 635, 636, 638, 639, 640, 642, 643, 644, 645, 646, 647, 648, 649, 651, 652, 653, 654, 656, 657, 658, 659, 660, 661, 663, 664, 665, 666, 667, 668, 669, 670, 672, 673, 674, 675, 677, 678, 679, 684, 685, 688, 692, 696, 700, 707, 710, 711, 712, 713, 714, 715, 719, 720, 721, 723, 725, 727, 729, 730, 736, 737, 738, 740, 742, 743, 750, 751, 752, 753, 754, 755, 756, 757, 758, 759, 760, 761, 763, 768, 769, 778, 779, 780, 783, 784, 785, 787, 788, 789, 791, 792, 794, 799, 800, 803, 805, 806, 808, 811, 812, 813, 819, 834, 836, 842, 863, 864, 866, 867, 870, 873, 874, 875, 877, 878, 879, 880, 885, 886, 887, 888, 889, 890, 892, 894, 895, 897, 898, 900, 907, 911, 912, 915, 916, 920, 921, 923, 925], "summary": {"covered_lines": 337, "num_statements": 604, "percent_covered": 50.0, "percent_covered_display": "50", "missing_lines": 267, "excluded_lines": 0, "percent_statements_covered": 55.794701986754966, "percent_statements_covered_display": "56", "num_branches": 266, "num_partial_branches": 36, "covered_branches": 98, "missing_branches": 168, "percent_branches_covered": 36.8421052631579, "percent_branches_covered_display": "37"}, "missing_lines": [80, 94, 95, 120, 144, 145, 148, 149, 150, 151, 153, 154, 155, 156, 158, 162, 165, 166, 173, 175, 176, 177, 179, 180, 182, 184, 210, 211, 216, 217, 218, 219, 220, 221, 224, 225, 226, 227, 228, 230, 231, 232, 233, 234, 236, 238, 240, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 254, 255, 257, 258, 259, 260, 261, 262, 269, 270, 271, 272, 273, 275, 276, 278, 279, 280, 281, 283, 284, 285, 286, 287, 288, 289, 290, 291, 343, 364, 378, 379, 380, 381, 382, 383, 384, 386, 387, 388, 390, 391, 392, 393, 394, 396, 398, 404, 410, 411, 412, 413, 416, 417, 418, 419, 423, 424, 425, 428, 431, 432, 433, 435, 438, 442, 445, 446, 448, 449, 452, 453, 454, 456, 457, 458, 460, 463, 464, 465, 468, 470, 471, 472, 473, 474, 475, 476, 478, 479, 481, 482, 486, 487, 494, 495, 496, 499, 500, 501, 502, 503, 504, 505, 506, 509, 510, 511, 512, 513, 514, 515, 516, 518, 519, 520, 521, 525, 526, 529, 530, 531, 532, 533, 534, 535, 536, 537, 538, 542, 544, 545, 546, 547, 550, 551, 554, 555, 558, 559, 565, 578, 579, 580, 583, 584, 588, 610, 611, 637, 680, 681, 682, 686, 693, 694, 701, 702, 703, 704, 716, 731, 732, 733, 734, 739, 745, 771, 772, 773, 774, 776, 797, 801, 814, 815, 817, 837, 838, 839, 840, 843, 845, 846, 848, 849, 852, 855, 858, 859, 861, 881, 882, 883, 909, 913, 918, 931, 932, 933, 935, 936, 938, 939, 940, 943, 944, 945, 947], "excluded_lines": [], "executed_branches": [[74, 75], [74, 85], [75, 76], [77, 78], [85, 86], [85, 100], [89, 90], [91, 92], [91, 97], [109, 110], [117, 118], [124, 125], [124, 135], [127, 128], [137, 138], [137, 140], [140, 141], [190, 191], [191, 192], [191, 201], [201, 202], [311, 312], [311, 314], [314, 315], [314, 325], [340, 341], [353, 354], [353, 356], [363, 368], [562, 567], [569, 570], [569, 572], [575, 577], [577, 582], [582, 586], [586, 587], [587, -561], [597, 598], [597, 604], [599, 600], [609, 613], [613, 614], [613, 617], [617, 618], [617, 621], [623, 624], [643, 644], [643, 647], [647, 648], [647, 651], [664, 665], [664, 668], [668, 669], [668, 672], [685, 688], [688, 692], [692, 696], [700, 707], [711, 712], [713, 714], [715, 719], [720, 721], [720, 723], [727, 729], [730, 736], [736, 737], [736, 742], [738, 740], [742, 743], [752, 753], [752, 778], [754, 755], [758, 759], [758, 763], [778, 779], [778, 783], [783, 784], [783, 787], [791, 792], [791, 794], [800, 803], [805, 806], [805, 808], [811, 812], [813, 819], [873, 874], [873, 885], [874, 875], [874, 877], [877, 878], [885, 886], [885, 887], [887, 888], [887, 889], [889, 890], [889, 892], [897, 898], [897, 900]], "missing_branches": [[75, 85], [77, 80], [89, 97], [109, 116], [117, 120], [127, 144], [140, 144], [148, 149], [148, 165], [149, 150], [149, 151], [153, 154], [153, 162], [165, 166], [165, 173], [173, 175], [173, 182], [175, 176], [175, 179], [190, 206], [201, 206], [218, -214], [218, 219], [220, -214], [220, 221], [225, 226], [225, 242], [226, 225], [226, 227], [227, 225], [227, 228], [228, 225], [228, 230], [230, 231], [230, 240], [231, 232], [231, 238], [232, 233], [232, 236], [242, -214], [242, 243], [246, 247], [246, 257], [247, 248], [247, 254], [249, 250], [249, 251], [254, 246], [254, 255], [260, 261], [260, 283], [262, 269], [262, 273], [269, 260], [269, 270], [271, 260], [271, 272], [273, 260], [273, 275], [276, 260], [276, 278], [278, 276], [278, 279], [280, 276], [280, 281], [284, -214], [284, 285], [285, 286], [285, 288], [340, 343], [363, 364], [379, -377], [379, 380], [380, -377], [380, 381], [382, 383], [382, 386], [386, -377], [386, 387], [387, 388], [387, 390], [391, -377], [391, 392], [393, 394], [393, 396], [411, 412], [411, 416], [417, 418], [417, 423], [432, 433], [432, 435], [448, 449], [448, 452], [463, 464], [463, 468], [470, 471], [470, 478], [472, 473], [472, 478], [494, -493], [494, 495], [499, -498], [499, 500], [509, 510], [509, 518], [514, -508], [514, 515], [519, -508], [519, 520], [532, 533], [532, 534], [550, -549], [550, 551], [562, 565], [575, 586], [577, 578], [582, 583], [586, -561], [587, 588], [599, -591], [609, 610], [623, 631], [685, 686], [688, 700], [692, 693], [700, 701], [702, 703], [702, 707], [711, 715], [713, 715], [715, 716], [727, 797], [730, 731], [731, 732], [731, 742], [732, 733], [732, 734], [738, 739], [742, 745], [754, 776], [773, 774], [773, 778], [800, 801], [811, 817], [813, 814], [843, 845], [843, 852], [845, 846], [845, 848], [848, 849], [848, 861], [852, 855], [852, 858], [858, 859], [858, 861], [877, 881], [881, 882], [881, 894], [932, 933], [932, 935], [935, 936], [935, 938], [939, 940], [939, 943], [943, 944], [943, 947], [944, 945], [944, 947]], "functions": {"async_migrate_entry": {"executed_lines": [74, 75, 76, 77, 78, 81, 82, 85, 86, 87, 88, 89, 90, 91, 92, 93, 97, 98, 100], "summary": {"covered_lines": 19, "num_statements": 22, "percent_covered": 82.3529411764706, "percent_covered_display": "82", "missing_lines": 3, "excluded_lines": 0, "percent_statements_covered": 86.36363636363636, "percent_statements_covered_display": "86", "num_branches": 12, "num_partial_branches": 3, "covered_branches": 9, "missing_branches": 3, "percent_branches_covered": 75.0, "percent_branches_covered_display": "75"}, "missing_lines": [80, 94, 95], "excluded_lines": [], "start_line": 73, "executed_branches": [[74, 75], [74, 85], [75, 76], [77, 78], [85, 86], [85, 100], [89, 90], [91, 92], [91, 97]], "missing_branches": [[75, 85], [77, 80], [89, 97]]}, "async_setup": {"executed_lines": [105], "summary": {"covered_lines": 1, "num_statements": 1, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 103, "executed_branches": [], "missing_branches": []}, "async_setup_entry": {"executed_lines": [109, 110, 111, 112, 113, 116, 117, 118, 122, 123, 124, 125, 126, 127, 128, 129, 131, 132, 133, 135, 136, 137, 138, 140, 141], "summary": {"covered_lines": 25, "num_statements": 48, "percent_covered": 45.833333333333336, "percent_covered_display": "46", "missing_lines": 23, "excluded_lines": 0, "percent_statements_covered": 52.083333333333336, "percent_statements_covered_display": "52", "num_branches": 24, "num_partial_branches": 4, "covered_branches": 8, "missing_branches": 16, "percent_branches_covered": 33.333333333333336, "percent_branches_covered_display": "33"}, "missing_lines": [120, 144, 145, 148, 149, 150, 151, 153, 154, 155, 156, 158, 162, 165, 166, 173, 175, 176, 177, 179, 180, 182, 184], "excluded_lines": [], "start_line": 108, "executed_branches": [[109, 110], [117, 118], [124, 125], [124, 135], [127, 128], [137, 138], [137, 140], [140, 141]], "missing_branches": [[109, 116], [117, 120], [127, 144], [140, 144], [148, 149], [148, 165], [149, 150], [149, 151], [153, 154], [153, 162], [165, 166], [165, 173], [173, 175], [173, 182], [175, 176], [175, 179]]}, "async_unload_entry": {"executed_lines": [188, 190, 191, 192, 193, 194, 195, 196, 197, 198, 201, 202, 203, 204, 206], "summary": {"covered_lines": 15, "num_statements": 15, "percent_covered": 90.47619047619048, "percent_covered_display": "90", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 6, "num_partial_branches": 2, "covered_branches": 4, "missing_branches": 2, "percent_branches_covered": 66.66666666666667, "percent_branches_covered_display": "67"}, "missing_lines": [], "excluded_lines": [], "start_line": 187, "executed_branches": [[190, 191], [191, 192], [191, 201], [201, 202]], "missing_branches": [[190, 206], [201, 206]]}, "entry_update_listener": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 2, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 2, "excluded_lines": 0, "percent_statements_covered": 0.0, "percent_statements_covered_display": "0", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [210, 211], "excluded_lines": [], "start_line": 209, "executed_branches": [], "missing_branches": []}, "check_and_write_to_16a": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 58, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 58, "excluded_lines": 0, "percent_statements_covered": 0.0, "percent_statements_covered_display": "0", "num_branches": 48, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 48, "percent_branches_covered": 0.0, "percent_branches_covered_display": "0"}, "missing_lines": [216, 217, 218, 219, 220, 221, 224, 225, 226, 227, 228, 230, 231, 232, 233, 234, 236, 238, 240, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 254, 255, 257, 258, 259, 260, 261, 262, 269, 270, 271, 272, 273, 275, 276, 278, 279, 280, 281, 283, 284, 285, 286, 287, 288, 289, 290, 291], "excluded_lines": [], "start_line": 215, "executed_branches": [], "missing_branches": [[218, -214], [218, 219], [220, -214], [220, 221], [225, 226], [225, 242], [226, 225], [226, 227], [227, 225], [227, 228], [228, 225], [228, 230], [230, 231], [230, 240], [231, 232], [231, 238], [232, 233], [232, 236], [242, -214], [242, 243], [246, 247], [246, 257], [247, 248], [247, 254], [249, 250], [249, 251], [254, 246], [254, 255], [260, 261], [260, 283], [262, 269], [262, 273], [269, 260], [269, 270], [271, 260], [271, 272], [273, 260], [273, 275], [276, 260], [276, 278], [278, 276], [278, 279], [280, 276], [280, 281], [284, -214], [284, 285], [285, 286], [285, 288]]}, "GoeChargerDataUpdateCoordinator.__init__": {"executed_lines": [301, 302, 303, 304, 306, 307, 310, 311, 312, 314, 315, 316, 325, 326, 336, 337, 339, 340, 341, 346, 347, 348, 349, 350, 351, 352, 353, 354, 356, 358, 359, 360, 361, 362, 363, 368, 371, 374, 375], "summary": {"covered_lines": 39, "num_statements": 41, "percent_covered": 92.15686274509804, "percent_covered_display": "92", "missing_lines": 2, "excluded_lines": 0, "percent_statements_covered": 95.1219512195122, "percent_statements_covered_display": "95", "num_branches": 10, "num_partial_branches": 2, "covered_branches": 8, "missing_branches": 2, "percent_branches_covered": 80.0, "percent_branches_covered_display": "80"}, "missing_lines": [343, 364], "excluded_lines": [], "start_line": 300, "executed_branches": [[311, 312], [311, 314], [314, 315], [314, 325], [340, 341], [353, 354], [353, 356], [363, 368]], "missing_branches": [[340, 343], [363, 364]]}, "GoeChargerDataUpdateCoordinator.call_later_update_device_registry": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 17, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 17, "excluded_lines": 0, "percent_statements_covered": 0.0, "percent_statements_covered_display": "0", "num_branches": 14, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 14, "percent_branches_covered": 0.0, "percent_branches_covered_display": "0"}, "missing_lines": [378, 379, 380, 381, 382, 383, 384, 386, 387, 388, 390, 391, 392, 393, 394, 396, 398], "excluded_lines": [], "start_line": 377, "executed_branches": [], "missing_branches": [[379, -377], [379, 380], [380, -377], [380, 381], [382, 383], [382, 386], [386, -377], [386, 387], [387, 388], [387, 390], [391, -377], [391, 392], [393, 394], [393, 396]]}, "GoeChargerDataUpdateCoordinator.cards_as_object_present": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 1, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 1, "excluded_lines": 0, "percent_statements_covered": 0.0, "percent_statements_covered_display": "0", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [404], "excluded_lines": [], "start_line": 403, "executed_branches": [], "missing_branches": []}, "GoeChargerDataUpdateCoordinator.force_async_update_now": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 16, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 16, "excluded_lines": 0, "percent_statements_covered": 0.0, "percent_statements_covered_display": "0", "num_branches": 6, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 6, "percent_branches_covered": 0.0, "percent_branches_covered_display": "0"}, "missing_lines": [410, 411, 412, 413, 416, 417, 418, 419, 423, 424, 425, 428, 431, 432, 433, 435], "excluded_lines": [], "start_line": 406, "executed_branches": [], "missing_branches": [[411, 412], [411, 416], [417, 418], [417, 423], [432, 433], [432, 435]]}, "GoeChargerDataUpdateCoordinator.start_websocket_and_wait_for_first_data": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 28, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 28, "excluded_lines": 0, "percent_statements_covered": 0.0, "percent_statements_covered_display": "0", "num_branches": 8, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 8, "percent_branches_covered": 0.0, "percent_branches_covered_display": "0"}, "missing_lines": [438, 442, 445, 446, 448, 449, 452, 453, 454, 456, 457, 458, 460, 463, 464, 465, 468, 470, 471, 472, 473, 474, 475, 476, 478, 479, 481, 482], "excluded_lines": [], "start_line": 437, "executed_branches": [], "missing_branches": [[448, 449], [448, 452], [463, 464], [463, 468], [470, 471], [470, 478], [472, 473], [472, 478]]}, "GoeChargerDataUpdateCoordinator.start_watchdog": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 2, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 2, "excluded_lines": 0, "percent_statements_covered": 0.0, "percent_statements_covered_display": "0", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [486, 487], "excluded_lines": [], "start_line": 484, "executed_branches": [], "missing_branches": []}, "GoeChargerDataUpdateCoordinator.stop_watchdog": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 3, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 3, "excluded_lines": 0, "percent_statements_covered": 0.0, "percent_statements_covered_display": "0", "num_branches": 2, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 2, "percent_branches_covered": 0.0, "percent_branches_covered_display": "0"}, "missing_lines": [494, 495, 496], "excluded_lines": [], "start_line": 493, "executed_branches": [], "missing_branches": [[494, -493], [494, 495]]}, "GoeChargerDataUpdateCoordinator._check_for_ws_task_and_cancel_if_running": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 8, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 8, "excluded_lines": 0, "percent_statements_covered": 0.0, "percent_statements_covered_display": "0", "num_branches": 2, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 2, "percent_branches_covered": 0.0, "percent_branches_covered_display": "0"}, "missing_lines": [499, 500, 501, 502, 503, 504, 505, 506], "excluded_lines": [], "start_line": 498, "executed_branches": [], "missing_branches": [[499, -498], [499, 500]]}, "GoeChargerDataUpdateCoordinator._async_watchdog_check": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 12, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 12, "excluded_lines": 0, "percent_statements_covered": 0.0, "percent_statements_covered_display": "0", "num_branches": 6, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 6, "percent_branches_covered": 0.0, "percent_branches_covered_display": "0"}, "missing_lines": [509, 510, 511, 512, 513, 514, 515, 516, 518, 519, 520, 521], "excluded_lines": [], "start_line": 508, "executed_branches": [], "missing_branches": [[509, 510], [509, 518], [514, -508], [514, 515], [519, -508], [519, 520]]}, "GoeChargerDataUpdateCoordinator.__call__": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 2, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 2, "excluded_lines": 0, "percent_statements_covered": 0.0, "percent_statements_covered_display": "0", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [525, 526], "excluded_lines": [], "start_line": 524, "executed_branches": [], "missing_branches": []}, "GoeChargerDataUpdateCoordinator.clear_data": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 10, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 10, "excluded_lines": 0, "percent_statements_covered": 0.0, "percent_statements_covered_display": "0", "num_branches": 2, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 2, "percent_branches_covered": 0.0, "percent_branches_covered_display": "0"}, "missing_lines": [529, 530, 531, 532, 533, 534, 535, 536, 537, 538], "excluded_lines": [], "start_line": 528, "executed_branches": [], "missing_branches": [[532, 533], [532, 534]]}, "GoeChargerDataUpdateCoordinator.trigger_restart_delayed": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 5, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 5, "excluded_lines": 0, "percent_statements_covered": 0.0, "percent_statements_covered_display": "0", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [542, 544, 545, 546, 547], "excluded_lines": [], "start_line": 540, "executed_branches": [], "missing_branches": []}, "GoeChargerDataUpdateCoordinator.cancel_existing_debouncer": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 2, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 2, "excluded_lines": 0, "percent_statements_covered": 0.0, "percent_statements_covered_display": "0", "num_branches": 2, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 2, "percent_branches_covered": 0.0, "percent_branches_covered_display": "0"}, "missing_lines": [550, 551], "excluded_lines": [], "start_line": 549, "executed_branches": [], "missing_branches": [[550, -549], [550, 551]]}, "GoeChargerDataUpdateCoordinator.request_update_in_sec": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 2, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 2, "excluded_lines": 0, "percent_statements_covered": 0.0, "percent_statements_covered_display": "0", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [554, 555], "excluded_lines": [], "start_line": 553, "executed_branches": [], "missing_branches": []}, "GoeChargerDataUpdateCoordinator._debounce_coordinator_update": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 2, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 2, "excluded_lines": 0, "percent_statements_covered": 0.0, "percent_statements_covered_display": "0", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [558, 559], "excluded_lines": [], "start_line": 557, "executed_branches": [], "missing_branches": []}, "GoeChargerDataUpdateCoordinator.handle_write_result": {"executed_lines": [562, 567, 569, 570, 572, 574, 575, 577, 582, 586, 587], "summary": {"covered_lines": 11, "num_statements": 18, "percent_covered": 59.375, "percent_covered_display": "59", "missing_lines": 7, "excluded_lines": 0, "percent_statements_covered": 61.111111111111114, "percent_statements_covered_display": "61", "num_branches": 14, "num_partial_branches": 6, "covered_branches": 8, "missing_branches": 6, "percent_branches_covered": 57.142857142857146, "percent_branches_covered_display": "57"}, "missing_lines": [565, 578, 579, 580, 583, 584, 588], "excluded_lines": [], "start_line": 561, "executed_branches": [[562, 567], [569, 570], [569, 572], [575, 577], [577, 582], [582, 586], [586, 587], [587, -561]], "missing_branches": [[562, 565], [575, 586], [577, 578], [582, 583], [586, -561], [587, 588]]}, "GoeChargerDataUpdateCoordinator._handle_client_connection_error": {"executed_lines": [595, 596, 597, 598, 599, 600, 601, 602, 604], "summary": {"covered_lines": 9, "num_statements": 9, "percent_covered": 92.3076923076923, "percent_covered_display": "92", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 4, "num_partial_branches": 1, "covered_branches": 3, "missing_branches": 1, "percent_branches_covered": 75.0, "percent_branches_covered_display": "75"}, "missing_lines": [], "excluded_lines": [], "start_line": 591, "executed_branches": [[597, 598], [597, 604], [599, 600]], "missing_branches": [[599, -591]]}, "GoeChargerDataUpdateCoordinator._async_update_data": {"executed_lines": [608, 609, 613, 614, 615, 616, 617, 618, 619, 621, 622, 623, 624, 625, 631, 633, 634, 635, 636, 638, 639, 640], "summary": {"covered_lines": 22, "num_statements": 25, "percent_covered": 84.84848484848484, "percent_covered_display": "85", "missing_lines": 3, "excluded_lines": 0, "percent_statements_covered": 88.0, "percent_statements_covered_display": "88", "num_branches": 8, "num_partial_branches": 2, "covered_branches": 6, "missing_branches": 2, "percent_branches_covered": 75.0, "percent_branches_covered_display": "75"}, "missing_lines": [610, 611, 637], "excluded_lines": [], "start_line": 606, "executed_branches": [[609, 613], [613, 614], [613, 617], [617, 618], [617, 621], [623, 624]], "missing_branches": [[609, 610], [623, 631]]}, "GoeChargerDataUpdateCoordinator.async_write_key": {"executed_lines": [643, 644, 645, 646, 647, 648, 649, 651, 652, 653, 654, 656, 657, 658, 659, 660, 661], "summary": {"covered_lines": 17, "num_statements": 17, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 4, "num_partial_branches": 0, "covered_branches": 4, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 642, "executed_branches": [[643, 644], [643, 647], [647, 648], [647, 651]], "missing_branches": []}, "GoeChargerDataUpdateCoordinator.async_write_multiple_keys": {"executed_lines": [664, 665, 666, 667, 668, 669, 670, 672, 673, 674, 675, 677, 678, 679], "summary": {"covered_lines": 14, "num_statements": 17, "percent_covered": 85.71428571428571, "percent_covered_display": "86", "missing_lines": 3, "excluded_lines": 0, "percent_statements_covered": 82.3529411764706, "percent_statements_covered_display": "82", "num_branches": 4, "num_partial_branches": 0, "covered_branches": 4, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [680, 681, 682], "excluded_lines": [], "start_line": 663, "executed_branches": [[664, 665], [664, 668], [668, 669], [668, 672]], "missing_branches": []}, "GoeChargerDataUpdateCoordinator.read_versions": {"executed_lines": [685, 688, 692, 696, 700, 707, 710, 711, 712, 713, 714, 715, 719, 720, 721, 723, 725, 727, 729, 730, 736, 737, 738, 740, 742, 743, 750, 751, 752, 753, 754, 755, 756, 757, 758, 759, 760, 761, 763, 768, 769, 778, 779, 780, 783, 784, 785, 787, 788, 789, 791, 792, 794, 799, 800, 803, 805, 806, 808, 811, 812, 813, 819, 834], "summary": {"covered_lines": 64, "num_statements": 88, "percent_covered": 66.90140845070422, "percent_covered_display": "67", "missing_lines": 24, "excluded_lines": 0, "percent_statements_covered": 72.72727272727273, "percent_statements_covered_display": "73", "num_branches": 54, "num_partial_branches": 15, "covered_branches": 31, "missing_branches": 23, "percent_branches_covered": 57.407407407407405, "percent_branches_covered_display": "57"}, "missing_lines": [686, 693, 694, 701, 702, 703, 704, 716, 731, 732, 733, 734, 739, 745, 771, 772, 773, 774, 776, 797, 801, 814, 815, 817], "excluded_lines": [], "start_line": 684, "executed_branches": [[685, 688], [688, 692], [692, 696], [700, 707], [711, 712], [713, 714], [715, 719], [720, 721], [720, 723], [727, 729], [730, 736], [736, 737], [736, 742], [738, 740], [742, 743], [752, 753], [752, 778], [754, 755], [758, 759], [758, 763], [778, 779], [778, 783], [783, 784], [783, 787], [791, 792], [791, 794], [800, 803], [805, 806], [805, 808], [811, 812], [813, 819]], "missing_branches": [[685, 686], [688, 700], [692, 693], [700, 701], [702, 703], [702, 707], [711, 715], [713, 715], [715, 716], [727, 797], [730, 731], [731, 732], [731, 742], [732, 733], [732, 734], [738, 739], [742, 745], [754, 776], [773, 774], [773, 778], [800, 801], [811, 817], [813, 814]]}, "GoeChargerDataUpdateCoordinator.check_for_16a_limit": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 4, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 4, "excluded_lines": 0, "percent_statements_covered": 0.0, "percent_statements_covered_display": "0", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [837, 838, 839, 840], "excluded_lines": [], "start_line": 836, "executed_branches": [], "missing_branches": []}, "GoeChargerDataUpdateCoordinator.is_valid_charger_entity": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 10, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 10, "excluded_lines": 0, "percent_statements_covered": 0.0, "percent_statements_covered_display": "0", "num_branches": 10, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 10, "percent_branches_covered": 0.0, "percent_branches_covered_display": "0"}, "missing_lines": [843, 845, 846, 848, 849, 852, 855, 858, 859, 861], "excluded_lines": [], "start_line": 842, "executed_branches": [], "missing_branches": [[843, 845], [843, 852], [845, 846], [845, 848], [848, 849], [848, 861], [852, 855], [852, 858], [858, 859], [858, 861]]}, "GoeChargerBaseEntity.__init__": {"executed_lines": [867, 870, 873, 874, 875, 877, 878, 879, 880, 885, 886, 887, 888, 889, 890, 892, 894, 895, 897, 898, 900], "summary": {"covered_lines": 21, "num_statements": 24, "percent_covered": 85.0, "percent_covered_display": "85", "missing_lines": 3, "excluded_lines": 0, "percent_statements_covered": 87.5, "percent_statements_covered_display": "88", "num_branches": 16, "num_partial_branches": 1, "covered_branches": 13, "missing_branches": 3, "percent_branches_covered": 81.25, "percent_branches_covered_display": "81"}, "missing_lines": [881, 882, 883], "excluded_lines": [], "start_line": 866, "executed_branches": [[873, 874], [873, 885], [874, 875], [874, 877], [877, 878], [885, 886], [885, 887], [887, 888], [887, 889], [889, 890], [889, 892], [897, 898], [897, 900]], "missing_branches": [[877, 881], [881, 882], [881, 894]]}, "GoeChargerBaseEntity._name_internal": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 1, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 1, "excluded_lines": 0, "percent_statements_covered": 0.0, "percent_statements_covered_display": "0", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [909], "excluded_lines": [], "start_line": 907, "executed_branches": [], "missing_branches": []}, "GoeChargerBaseEntity.device_info": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 1, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 1, "excluded_lines": 0, "percent_statements_covered": 0.0, "percent_statements_covered_display": "0", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [913], "excluded_lines": [], "start_line": 912, "executed_branches": [], "missing_branches": []}, "GoeChargerBaseEntity.available": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 1, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 1, "excluded_lines": 0, "percent_statements_covered": 0.0, "percent_statements_covered_display": "0", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [918], "excluded_lines": [], "start_line": 916, "executed_branches": [], "missing_branches": []}, "GoeChargerBaseEntity.unique_id": {"executed_lines": [923], "summary": {"covered_lines": 1, "num_statements": 1, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 921, "executed_branches": [], "missing_branches": []}, "GoeChargerBaseEntity._friendly_name_internal": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 12, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 12, "excluded_lines": 0, "percent_statements_covered": 0.0, "percent_statements_covered_display": "0", "num_branches": 10, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 10, "percent_branches_covered": 0.0, "percent_branches_covered_display": "0"}, "missing_lines": [931, 932, 933, 935, 936, 938, 939, 940, 943, 944, 945, 947], "excluded_lines": [], "start_line": 925, "executed_branches": [], "missing_branches": [[932, 933], [932, 935], [935, 936], [935, 938], [939, 940], [939, 943], [943, 944], [943, 947], [944, 945], [944, 947]]}, "": {"executed_lines": [1, 2, 3, 4, 5, 6, 7, 9, 10, 11, 12, 23, 24, 25, 30, 31, 32, 33, 34, 35, 36, 38, 45, 46, 60, 61, 63, 65, 66, 67, 68, 69, 70, 71, 73, 103, 108, 187, 209, 214, 215, 294, 296, 297, 298, 300, 377, 403, 406, 437, 484, 493, 498, 508, 524, 528, 540, 549, 553, 557, 561, 591, 606, 642, 663, 684, 836, 842, 863, 864, 866, 907, 911, 912, 915, 916, 920, 921, 925], "summary": {"covered_lines": 79, "num_statements": 79, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 1, "executed_branches": [], "missing_branches": []}}, "classes": {"GoeChargerDataUpdateCoordinator": {"executed_lines": [301, 302, 303, 304, 306, 307, 310, 311, 312, 314, 315, 316, 325, 326, 336, 337, 339, 340, 341, 346, 347, 348, 349, 350, 351, 352, 353, 354, 356, 358, 359, 360, 361, 362, 363, 368, 371, 374, 375, 562, 567, 569, 570, 572, 574, 575, 577, 582, 586, 587, 595, 596, 597, 598, 599, 600, 601, 602, 604, 608, 609, 613, 614, 615, 616, 617, 618, 619, 621, 622, 623, 624, 625, 631, 633, 634, 635, 636, 638, 639, 640, 643, 644, 645, 646, 647, 648, 649, 651, 652, 653, 654, 656, 657, 658, 659, 660, 661, 664, 665, 666, 667, 668, 669, 670, 672, 673, 674, 675, 677, 678, 679, 685, 688, 692, 696, 700, 707, 710, 711, 712, 713, 714, 715, 719, 720, 721, 723, 725, 727, 729, 730, 736, 737, 738, 740, 742, 743, 750, 751, 752, 753, 754, 755, 756, 757, 758, 759, 760, 761, 763, 768, 769, 778, 779, 780, 783, 784, 785, 787, 788, 789, 791, 792, 794, 799, 800, 803, 805, 806, 808, 811, 812, 813, 819, 834], "summary": {"covered_lines": 176, "num_statements": 339, "percent_covered": 49.079754601226995, "percent_covered_display": "49", "missing_lines": 163, "excluded_lines": 0, "percent_statements_covered": 51.91740412979351, "percent_statements_covered_display": "52", "num_branches": 150, "num_partial_branches": 26, "covered_branches": 64, "missing_branches": 86, "percent_branches_covered": 42.666666666666664, "percent_branches_covered_display": "43"}, "missing_lines": [343, 364, 378, 379, 380, 381, 382, 383, 384, 386, 387, 388, 390, 391, 392, 393, 394, 396, 398, 404, 410, 411, 412, 413, 416, 417, 418, 419, 423, 424, 425, 428, 431, 432, 433, 435, 438, 442, 445, 446, 448, 449, 452, 453, 454, 456, 457, 458, 460, 463, 464, 465, 468, 470, 471, 472, 473, 474, 475, 476, 478, 479, 481, 482, 486, 487, 494, 495, 496, 499, 500, 501, 502, 503, 504, 505, 506, 509, 510, 511, 512, 513, 514, 515, 516, 518, 519, 520, 521, 525, 526, 529, 530, 531, 532, 533, 534, 535, 536, 537, 538, 542, 544, 545, 546, 547, 550, 551, 554, 555, 558, 559, 565, 578, 579, 580, 583, 584, 588, 610, 611, 637, 680, 681, 682, 686, 693, 694, 701, 702, 703, 704, 716, 731, 732, 733, 734, 739, 745, 771, 772, 773, 774, 776, 797, 801, 814, 815, 817, 837, 838, 839, 840, 843, 845, 846, 848, 849, 852, 855, 858, 859, 861], "excluded_lines": [], "start_line": 294, "executed_branches": [[311, 312], [311, 314], [314, 315], [314, 325], [340, 341], [353, 354], [353, 356], [363, 368], [562, 567], [569, 570], [569, 572], [575, 577], [577, 582], [582, 586], [586, 587], [587, -561], [597, 598], [597, 604], [599, 600], [609, 613], [613, 614], [613, 617], [617, 618], [617, 621], [623, 624], [643, 644], [643, 647], [647, 648], [647, 651], [664, 665], [664, 668], [668, 669], [668, 672], [685, 688], [688, 692], [692, 696], [700, 707], [711, 712], [713, 714], [715, 719], [720, 721], [720, 723], [727, 729], [730, 736], [736, 737], [736, 742], [738, 740], [742, 743], [752, 753], [752, 778], [754, 755], [758, 759], [758, 763], [778, 779], [778, 783], [783, 784], [783, 787], [791, 792], [791, 794], [800, 803], [805, 806], [805, 808], [811, 812], [813, 819]], "missing_branches": [[340, 343], [363, 364], [379, -377], [379, 380], [380, -377], [380, 381], [382, 383], [382, 386], [386, -377], [386, 387], [387, 388], [387, 390], [391, -377], [391, 392], [393, 394], [393, 396], [411, 412], [411, 416], [417, 418], [417, 423], [432, 433], [432, 435], [448, 449], [448, 452], [463, 464], [463, 468], [470, 471], [470, 478], [472, 473], [472, 478], [494, -493], [494, 495], [499, -498], [499, 500], [509, 510], [509, 518], [514, -508], [514, 515], [519, -508], [519, 520], [532, 533], [532, 534], [550, -549], [550, 551], [562, 565], [575, 586], [577, 578], [582, 583], [586, -561], [587, 588], [599, -591], [609, 610], [623, 631], [685, 686], [688, 700], [692, 693], [700, 701], [702, 703], [702, 707], [711, 715], [713, 715], [715, 716], [727, 797], [730, 731], [731, 732], [731, 742], [732, 733], [732, 734], [738, 739], [742, 745], [754, 776], [773, 774], [773, 778], [800, 801], [811, 817], [813, 814], [843, 845], [843, 852], [845, 846], [845, 848], [848, 849], [848, 861], [852, 855], [852, 858], [858, 859], [858, 861]]}, "GoeChargerBaseEntity": {"executed_lines": [867, 870, 873, 874, 875, 877, 878, 879, 880, 885, 886, 887, 888, 889, 890, 892, 894, 895, 897, 898, 900, 923], "summary": {"covered_lines": 22, "num_statements": 40, "percent_covered": 53.03030303030303, "percent_covered_display": "53", "missing_lines": 18, "excluded_lines": 0, "percent_statements_covered": 55.0, "percent_statements_covered_display": "55", "num_branches": 26, "num_partial_branches": 1, "covered_branches": 13, "missing_branches": 13, "percent_branches_covered": 50.0, "percent_branches_covered_display": "50"}, "missing_lines": [881, 882, 883, 909, 913, 918, 931, 932, 933, 935, 936, 938, 939, 940, 943, 944, 945, 947], "excluded_lines": [], "start_line": 863, "executed_branches": [[873, 874], [873, 885], [874, 875], [874, 877], [877, 878], [885, 886], [885, 887], [887, 888], [887, 889], [889, 890], [889, 892], [897, 898], [897, 900]], "missing_branches": [[877, 881], [881, 882], [881, 894], [932, 933], [932, 935], [935, 936], [935, 938], [939, 940], [939, 943], [943, 944], [943, 947], [944, 945], [944, 947]]}, "": {"executed_lines": [1, 2, 3, 4, 5, 6, 7, 9, 10, 11, 12, 23, 24, 25, 30, 31, 32, 33, 34, 35, 36, 38, 45, 46, 60, 61, 63, 65, 66, 67, 68, 69, 70, 71, 73, 74, 75, 76, 77, 78, 81, 82, 85, 86, 87, 88, 89, 90, 91, 92, 93, 97, 98, 100, 103, 105, 108, 109, 110, 111, 112, 113, 116, 117, 118, 122, 123, 124, 125, 126, 127, 128, 129, 131, 132, 133, 135, 136, 137, 138, 140, 141, 187, 188, 190, 191, 192, 193, 194, 195, 196, 197, 198, 201, 202, 203, 204, 206, 209, 214, 215, 294, 296, 297, 298, 300, 377, 403, 406, 437, 484, 493, 498, 508, 524, 528, 540, 549, 553, 557, 561, 591, 606, 642, 663, 684, 836, 842, 863, 864, 866, 907, 911, 912, 915, 916, 920, 921, 925], "summary": {"covered_lines": 139, "num_statements": 225, "percent_covered": 50.79365079365079, "percent_covered_display": "51", "missing_lines": 86, "excluded_lines": 0, "percent_statements_covered": 61.77777777777778, "percent_statements_covered_display": "62", "num_branches": 90, "num_partial_branches": 9, "covered_branches": 21, "missing_branches": 69, "percent_branches_covered": 23.333333333333332, "percent_branches_covered_display": "23"}, "missing_lines": [80, 94, 95, 120, 144, 145, 148, 149, 150, 151, 153, 154, 155, 156, 158, 162, 165, 166, 173, 175, 176, 177, 179, 180, 182, 184, 210, 211, 216, 217, 218, 219, 220, 221, 224, 225, 226, 227, 228, 230, 231, 232, 233, 234, 236, 238, 240, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 254, 255, 257, 258, 259, 260, 261, 262, 269, 270, 271, 272, 273, 275, 276, 278, 279, 280, 281, 283, 284, 285, 286, 287, 288, 289, 290, 291], "excluded_lines": [], "start_line": 1, "executed_branches": [[74, 75], [74, 85], [75, 76], [77, 78], [85, 86], [85, 100], [89, 90], [91, 92], [91, 97], [109, 110], [117, 118], [124, 125], [124, 135], [127, 128], [137, 138], [137, 140], [140, 141], [190, 191], [191, 192], [191, 201], [201, 202]], "missing_branches": [[75, 85], [77, 80], [89, 97], [109, 116], [117, 120], [127, 144], [140, 144], [148, 149], [148, 165], [149, 150], [149, 151], [153, 154], [153, 162], [165, 166], [165, 173], [173, 175], [173, 182], [175, 176], [175, 179], [190, 206], [201, 206], [218, -214], [218, 219], [220, -214], [220, 221], [225, 226], [225, 242], [226, 225], [226, 227], [227, 225], [227, 228], [228, 225], [228, 230], [230, 231], [230, 240], [231, 232], [231, 238], [232, 233], [232, 236], [242, -214], [242, 243], [246, 247], [246, 257], [247, 248], [247, 254], [249, 250], [249, 251], [254, 246], [254, 255], [260, 261], [260, 283], [262, 269], [262, 273], [269, 260], [269, 270], [271, 260], [271, 272], [273, 260], [273, 275], [276, 260], [276, 278], [278, 276], [278, 279], [280, 276], [280, 281], [284, -214], [284, 285], [285, 286], [285, 288]]}}}, "custom_components/goecharger_api2/binary_sensor.py": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 71, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 71, "excluded_lines": 0, "percent_statements_covered": 0.0, "percent_statements_covered_display": "0", "num_branches": 34, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 34, "percent_branches_covered": 0.0, "percent_branches_covered_display": "0"}, "missing_lines": [1, 3, 4, 5, 6, 7, 9, 10, 11, 12, 15, 18, 19, 20, 21, 23, 24, 25, 26, 27, 29, 30, 31, 33, 36, 37, 38, 39, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 53, 54, 56, 58, 61, 62, 63, 64, 66, 67, 68, 69, 70, 72, 73, 74, 75, 76, 77, 78, 80, 81, 83, 84, 86, 88, 90, 92, 93, 95, 96, 98], "excluded_lines": [], "executed_branches": [], "missing_branches": [[23, 24], [23, 29], [24, 25], [24, 33], [25, 24], [25, 26], [29, 30], [29, 33], [45, 46], [45, 80], [46, 47], [46, 61], [47, 48], [47, 51], [51, 53], [51, 58], [53, 54], [53, 56], [61, 62], [61, 63], [63, 64], [63, 80], [67, 68], [67, 69], [69, 70], [69, 72], [80, 81], [80, 90], [81, 83], [81, 88], [83, 84], [83, 86], [95, 96], [95, 98]], "functions": {"async_setup_entry": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 12, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 12, "excluded_lines": 0, "percent_statements_covered": 0.0, "percent_statements_covered_display": "0", "num_branches": 8, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 8, "percent_branches_covered": 0.0, "percent_branches_covered_display": "0"}, "missing_lines": [19, 20, 21, 23, 24, 25, 26, 27, 29, 30, 31, 33], "excluded_lines": [], "start_line": 18, "executed_branches": [], "missing_branches": [[23, 24], [23, 29], [24, 25], [24, 33], [25, 24], [25, 26], [29, 30], [29, 33]]}, "GoeChargerApiV2BinarySensor.__init__": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 2, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 2, "excluded_lines": 0, "percent_statements_covered": 0.0, "percent_statements_covered_display": "0", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [38, 39], "excluded_lines": [], "start_line": 37, "executed_branches": [], "missing_branches": []}, "GoeChargerApiV2BinarySensor.is_on": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 36, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 36, "excluded_lines": 0, "percent_statements_covered": 0.0, "percent_statements_covered_display": "0", "num_branches": 24, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 24, "percent_branches_covered": 0.0, "percent_branches_covered_display": "0"}, "missing_lines": [43, 44, 45, 46, 47, 48, 49, 50, 51, 53, 54, 56, 58, 61, 62, 63, 64, 66, 67, 68, 69, 70, 72, 73, 74, 75, 76, 77, 78, 80, 81, 83, 84, 86, 88, 90], "excluded_lines": [], "start_line": 42, "executed_branches": [], "missing_branches": [[45, 46], [45, 80], [46, 47], [46, 61], [47, 48], [47, 51], [51, 53], [51, 58], [53, 54], [53, 56], [61, 62], [61, 63], [63, 64], [63, 80], [67, 68], [67, 69], [69, 70], [69, 72], [80, 81], [80, 90], [81, 83], [81, 88], [83, 84], [83, 86]]}, "GoeChargerApiV2BinarySensor.icon": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 3, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 3, "excluded_lines": 0, "percent_statements_covered": 0.0, "percent_statements_covered_display": "0", "num_branches": 2, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 2, "percent_branches_covered": 0.0, "percent_branches_covered_display": "0"}, "missing_lines": [95, 96, 98], "excluded_lines": [], "start_line": 93, "executed_branches": [], "missing_branches": [[95, 96], [95, 98]]}, "": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 18, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 18, "excluded_lines": 0, "percent_statements_covered": 0.0, "percent_statements_covered_display": "0", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [1, 3, 4, 5, 6, 7, 9, 10, 11, 12, 15, 18, 36, 37, 41, 42, 92, 93], "excluded_lines": [], "start_line": 1, "executed_branches": [], "missing_branches": []}}, "classes": {"GoeChargerApiV2BinarySensor": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 41, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 41, "excluded_lines": 0, "percent_statements_covered": 0.0, "percent_statements_covered_display": "0", "num_branches": 26, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 26, "percent_branches_covered": 0.0, "percent_branches_covered_display": "0"}, "missing_lines": [38, 39, 43, 44, 45, 46, 47, 48, 49, 50, 51, 53, 54, 56, 58, 61, 62, 63, 64, 66, 67, 68, 69, 70, 72, 73, 74, 75, 76, 77, 78, 80, 81, 83, 84, 86, 88, 90, 95, 96, 98], "excluded_lines": [], "start_line": 36, "executed_branches": [], "missing_branches": [[45, 46], [45, 80], [46, 47], [46, 61], [47, 48], [47, 51], [51, 53], [51, 58], [53, 54], [53, 56], [61, 62], [61, 63], [63, 64], [63, 80], [67, 68], [67, 69], [69, 70], [69, 72], [80, 81], [80, 90], [81, 83], [81, 88], [83, 84], [83, 86], [95, 96], [95, 98]]}, "": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 30, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 30, "excluded_lines": 0, "percent_statements_covered": 0.0, "percent_statements_covered_display": "0", "num_branches": 8, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 8, "percent_branches_covered": 0.0, "percent_branches_covered_display": "0"}, "missing_lines": [1, 3, 4, 5, 6, 7, 9, 10, 11, 12, 15, 18, 19, 20, 21, 23, 24, 25, 26, 27, 29, 30, 31, 33, 36, 37, 41, 42, 92, 93], "excluded_lines": [], "start_line": 1, "executed_branches": [], "missing_branches": [[23, 24], [23, 29], [24, 25], [24, 33], [25, 24], [25, 26], [29, 30], [29, 33]]}}}, "custom_components/goecharger_api2/button.py": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 31, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 31, "excluded_lines": 0, "percent_statements_covered": 0.0, "percent_statements_covered_display": "0", "num_branches": 8, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 8, "percent_branches_covered": 0.0, "percent_branches_covered_display": "0"}, "missing_lines": [1, 3, 4, 5, 6, 7, 9, 10, 11, 13, 16, 17, 18, 19, 21, 22, 23, 24, 25, 27, 28, 29, 31, 34, 35, 36, 38, 39, 40, 41, 42], "excluded_lines": [], "executed_branches": [], "missing_branches": [[21, 22], [21, 27], [22, 23], [22, 31], [23, 22], [23, 24], [27, 28], [27, 31]], "functions": {"async_setup_entry": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 12, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 12, "excluded_lines": 0, "percent_statements_covered": 0.0, "percent_statements_covered_display": "0", "num_branches": 8, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 8, "percent_branches_covered": 0.0, "percent_branches_covered_display": "0"}, "missing_lines": [17, 18, 19, 21, 22, 23, 24, 25, 27, 28, 29, 31], "excluded_lines": [], "start_line": 16, "executed_branches": [], "missing_branches": [[21, 22], [21, 27], [22, 23], [22, 31], [23, 22], [23, 24], [27, 28], [27, 31]]}, "GoeChargerApiV2Button.__init__": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 1, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 1, "excluded_lines": 0, "percent_statements_covered": 0.0, "percent_statements_covered_display": "0", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [36], "excluded_lines": [], "start_line": 35, "executed_branches": [], "missing_branches": []}, "GoeChargerApiV2Button.async_press": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 4, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 4, "excluded_lines": 0, "percent_statements_covered": 0.0, "percent_statements_covered_display": "0", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [39, 40, 41, 42], "excluded_lines": [], "start_line": 38, "executed_branches": [], "missing_branches": []}, "": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 14, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 14, "excluded_lines": 0, "percent_statements_covered": 0.0, "percent_statements_covered_display": "0", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [1, 3, 4, 5, 6, 7, 9, 10, 11, 13, 16, 34, 35, 38], "excluded_lines": [], "start_line": 1, "executed_branches": [], "missing_branches": []}}, "classes": {"GoeChargerApiV2Button": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 5, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 5, "excluded_lines": 0, "percent_statements_covered": 0.0, "percent_statements_covered_display": "0", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [36, 39, 40, 41, 42], "excluded_lines": [], "start_line": 34, "executed_branches": [], "missing_branches": []}, "": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 26, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 26, "excluded_lines": 0, "percent_statements_covered": 0.0, "percent_statements_covered_display": "0", "num_branches": 8, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 8, "percent_branches_covered": 0.0, "percent_branches_covered_display": "0"}, "missing_lines": [1, 3, 4, 5, 6, 7, 9, 10, 11, 13, 16, 17, 18, 19, 21, 22, 23, 24, 25, 27, 28, 29, 31, 34, 35, 38], "excluded_lines": [], "start_line": 1, "executed_branches": [], "missing_branches": [[21, 22], [21, 27], [22, 23], [22, 31], [23, 22], [23, 24], [27, 28], [27, 31]]}}}, "custom_components/goecharger_api2/config_flow.py": {"executed_lines": [1, 2, 3, 4, 6, 7, 8, 9, 10, 21, 22, 24, 33, 39, 41, 43, 44, 47, 50, 51, 52, 54, 56, 58, 59, 60, 62, 63, 64, 66, 67, 68, 70, 71, 72, 74, 96, 97, 98, 99, 101, 102, 105, 107, 111, 129, 131, 137, 138, 139, 140, 141, 143, 144, 145, 146, 147, 148, 149, 154, 155, 158, 160, 161, 163, 172, 196, 198, 204, 205, 207, 208, 230, 231, 233, 242, 268, 331, 332, 333, 334, 340, 341, 342, 343, 347, 349, 366, 367, 368, 370, 371, 372, 378, 379, 380, 381, 382, 385], "summary": {"covered_lines": 99, "num_statements": 178, "percent_covered": 54.054054054054056, "percent_covered_display": "54", "missing_lines": 79, "excluded_lines": 0, "percent_statements_covered": 55.61797752808989, "percent_statements_covered_display": "56", "num_branches": 44, "num_partial_branches": 7, "covered_branches": 21, "missing_branches": 23, "percent_branches_covered": 47.72727272727273, "percent_branches_covered_display": "48"}, "missing_lines": [75, 76, 78, 79, 80, 81, 82, 83, 84, 85, 87, 88, 89, 90, 91, 92, 93, 94, 142, 151, 152, 156, 209, 210, 211, 212, 213, 214, 215, 216, 217, 219, 220, 224, 225, 226, 228, 271, 275, 276, 277, 278, 279, 280, 281, 282, 284, 285, 289, 290, 306, 307, 308, 310, 311, 312, 316, 318, 319, 320, 321, 324, 325, 326, 327, 329, 344, 345, 351, 352, 353, 354, 357, 358, 359, 360, 376, 383, 384], "excluded_lines": [], "executed_branches": [[98, 99], [98, 107], [101, 102], [101, 105], [137, 138], [137, 163], [140, 141], [140, 160], [141, 143], [147, 148], [155, 158], [204, 205], [204, 233], [208, 230], [341, 342], [341, 349], [343, 347], [349, 366], [367, 368], [367, 385], [371, 372]], "missing_branches": [[78, 79], [78, 87], [141, 142], [147, 151], [155, 156], [208, 209], [209, 210], [209, 211], [215, 216], [215, 219], [225, 226], [225, 228], [280, 281], [280, 284], [284, 285], [284, 316], [343, 344], [349, 351], [353, 354], [353, 357], [359, 360], [359, 366], [371, 376]], "functions": {"GoeChargerApiV2FlowHandler.__init__": {"executed_lines": [56, 58, 59, 60, 62, 63, 64, 66, 67, 68, 70, 71, 72], "summary": {"covered_lines": 13, "num_statements": 13, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 54, "executed_branches": [], "missing_branches": []}, "GoeChargerApiV2FlowHandler.async_step_reconfigure": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 18, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 18, "excluded_lines": 0, "percent_statements_covered": 0.0, "percent_statements_covered_display": "0", "num_branches": 2, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 2, "percent_branches_covered": 0.0, "percent_branches_covered_display": "0"}, "missing_lines": [75, 76, 78, 79, 80, 81, 82, 83, 84, 85, 87, 88, 89, 90, 91, 92, 93, 94], "excluded_lines": [], "start_line": 74, "executed_branches": [], "missing_branches": [[78, 79], [78, 87]]}, "GoeChargerApiV2FlowHandler.async_step_user": {"executed_lines": [97, 98, 99, 101, 102, 105, 107, 111], "summary": {"covered_lines": 8, "num_statements": 8, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 4, "num_partial_branches": 0, "covered_branches": 4, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 96, "executed_branches": [[98, 99], [98, 107], [101, 102], [101, 105]], "missing_branches": []}, "GoeChargerApiV2FlowHandler.async_step_user_lan": {"executed_lines": [131, 137, 138, 139, 140, 141, 143, 144, 145, 146, 147, 148, 149, 154, 155, 158, 160, 161, 163, 172], "summary": {"covered_lines": 20, "num_statements": 24, "percent_covered": 79.41176470588235, "percent_covered_display": "79", "missing_lines": 4, "excluded_lines": 0, "percent_statements_covered": 83.33333333333333, "percent_statements_covered_display": "83", "num_branches": 10, "num_partial_branches": 3, "covered_branches": 7, "missing_branches": 3, "percent_branches_covered": 70.0, "percent_branches_covered_display": "70"}, "missing_lines": [142, 151, 152, 156], "excluded_lines": [], "start_line": 129, "executed_branches": [[137, 138], [137, 163], [140, 141], [140, 160], [141, 143], [147, 148], [155, 158]], "missing_branches": [[141, 142], [147, 151], [155, 156]]}, "GoeChargerApiV2FlowHandler.async_step_user_wan": {"executed_lines": [198, 204, 205, 207, 208, 230, 231, 233, 242], "summary": {"covered_lines": 9, "num_statements": 24, "percent_covered": 35.294117647058826, "percent_covered_display": "35", "missing_lines": 15, "excluded_lines": 0, "percent_statements_covered": 37.5, "percent_statements_covered_display": "38", "num_branches": 10, "num_partial_branches": 1, "covered_branches": 3, "missing_branches": 7, "percent_branches_covered": 30.0, "percent_branches_covered_display": "30"}, "missing_lines": [209, 210, 211, 212, 213, 214, 215, 216, 217, 219, 220, 224, 225, 226, 228], "excluded_lines": [], "start_line": 196, "executed_branches": [[204, 205], [204, 233], [208, 230]], "missing_branches": [[208, 209], [209, 210], [209, 211], [215, 216], [215, 219], [225, 226], [225, 228]]}, "GoeChargerApiV2FlowHandler._check_ws_connection_and_flags": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 29, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 29, "excluded_lines": 0, "percent_statements_covered": 0.0, "percent_statements_covered_display": "0", "num_branches": 4, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 4, "percent_branches_covered": 0.0, "percent_branches_covered_display": "0"}, "missing_lines": [271, 275, 276, 277, 278, 279, 280, 281, 282, 284, 285, 289, 290, 306, 307, 308, 310, 311, 312, 316, 318, 319, 320, 321, 324, 325, 326, 327, 329], "excluded_lines": [], "start_line": 268, "executed_branches": [], "missing_branches": [[280, 281], [280, 284], [284, 285], [284, 316]]}, "GoeChargerApiV2FlowHandler._test_host": {"executed_lines": [332, 333, 334, 340, 341, 342, 343, 347, 349, 366, 367, 368, 370, 371, 372, 378, 379, 380, 381, 382, 385], "summary": {"covered_lines": 21, "num_statements": 34, "percent_covered": 58.333333333333336, "percent_covered_display": "58", "missing_lines": 13, "excluded_lines": 0, "percent_statements_covered": 61.76470588235294, "percent_statements_covered_display": "62", "num_branches": 14, "num_partial_branches": 3, "covered_branches": 7, "missing_branches": 7, "percent_branches_covered": 50.0, "percent_branches_covered_display": "50"}, "missing_lines": [344, 345, 351, 352, 353, 354, 357, 358, 359, 360, 376, 383, 384], "excluded_lines": [], "start_line": 331, "executed_branches": [[341, 342], [341, 349], [343, 347], [349, 366], [367, 368], [367, 385], [371, 372]], "missing_branches": [[343, 344], [349, 351], [353, 354], [353, 357], [359, 360], [359, 366], [371, 376]]}, "": {"executed_lines": [1, 2, 3, 4, 6, 7, 8, 9, 10, 21, 22, 24, 33, 39, 41, 43, 44, 47, 50, 51, 52, 54, 74, 96, 129, 196, 268, 331], "summary": {"covered_lines": 28, "num_statements": 28, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 1, "executed_branches": [], "missing_branches": []}}, "classes": {"GoeChargerApiV2FlowHandler": {"executed_lines": [56, 58, 59, 60, 62, 63, 64, 66, 67, 68, 70, 71, 72, 97, 98, 99, 101, 102, 105, 107, 111, 131, 137, 138, 139, 140, 141, 143, 144, 145, 146, 147, 148, 149, 154, 155, 158, 160, 161, 163, 172, 198, 204, 205, 207, 208, 230, 231, 233, 242, 332, 333, 334, 340, 341, 342, 343, 347, 349, 366, 367, 368, 370, 371, 372, 378, 379, 380, 381, 382, 385], "summary": {"covered_lines": 71, "num_statements": 150, "percent_covered": 47.422680412371136, "percent_covered_display": "47", "missing_lines": 79, "excluded_lines": 0, "percent_statements_covered": 47.333333333333336, "percent_statements_covered_display": "47", "num_branches": 44, "num_partial_branches": 7, "covered_branches": 21, "missing_branches": 23, "percent_branches_covered": 47.72727272727273, "percent_branches_covered_display": "48"}, "missing_lines": [75, 76, 78, 79, 80, 81, 82, 83, 84, 85, 87, 88, 89, 90, 91, 92, 93, 94, 142, 151, 152, 156, 209, 210, 211, 212, 213, 214, 215, 216, 217, 219, 220, 224, 225, 226, 228, 271, 275, 276, 277, 278, 279, 280, 281, 282, 284, 285, 289, 290, 306, 307, 308, 310, 311, 312, 316, 318, 319, 320, 321, 324, 325, 326, 327, 329, 344, 345, 351, 352, 353, 354, 357, 358, 359, 360, 376, 383, 384], "excluded_lines": [], "start_line": 47, "executed_branches": [[98, 99], [98, 107], [101, 102], [101, 105], [137, 138], [137, 163], [140, 141], [140, 160], [141, 143], [147, 148], [155, 158], [204, 205], [204, 233], [208, 230], [341, 342], [341, 349], [343, 347], [349, 366], [367, 368], [367, 385], [371, 372]], "missing_branches": [[78, 79], [78, 87], [141, 142], [147, 151], [155, 156], [208, 209], [209, 210], [209, 211], [215, 216], [215, 219], [225, 226], [225, 228], [280, 281], [280, 284], [284, 285], [284, 316], [343, 344], [349, 351], [353, 354], [353, 357], [359, 360], [359, 366], [371, 376]]}, "": {"executed_lines": [1, 2, 3, 4, 6, 7, 8, 9, 10, 21, 22, 24, 33, 39, 41, 43, 44, 47, 50, 51, 52, 54, 74, 96, 129, 196, 268, 331], "summary": {"covered_lines": 28, "num_statements": 28, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 1, "executed_branches": [], "missing_branches": []}}}, "custom_components/goecharger_api2/const.py": {"executed_lines": [1, 2, 4, 5, 6, 7, 8, 9, 10, 16, 17, 20, 21, 22, 23, 25, 26, 28, 37, 38, 39, 40, 42, 43, 45, 46, 47, 48, 49, 52, 53, 54, 57, 58, 59, 60, 61, 62, 63, 66, 67, 68, 71, 72, 73, 74, 75, 76, 77, 80, 81, 82, 83, 84, 87, 89, 216, 242, 710, 837, 1953, 2086, 2097, 2315, 2317, 2327, 2329, 2385, 3242], "summary": {"covered_lines": 69, "num_statements": 69, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "executed_branches": [], "missing_branches": [], "functions": {"": {"executed_lines": [1, 2, 4, 5, 6, 7, 8, 9, 10, 16, 17, 20, 21, 22, 23, 25, 26, 28, 37, 38, 39, 40, 42, 43, 45, 46, 47, 48, 49, 52, 53, 54, 57, 58, 59, 60, 61, 62, 63, 66, 67, 68, 71, 72, 73, 74, 75, 76, 77, 80, 81, 82, 83, 84, 87, 89, 216, 242, 710, 837, 1953, 2086, 2097, 2315, 2317, 2327, 2329, 2385, 3242], "summary": {"covered_lines": 69, "num_statements": 69, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 1, "executed_branches": [], "missing_branches": []}}, "classes": {"ExtBinarySensorEntityDescription": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 0, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 46, "executed_branches": [], "missing_branches": []}, "ExtButtonEntityDescription": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 0, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 53, "executed_branches": [], "missing_branches": []}, "ExtNumberEntityDescription": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 0, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 58, "executed_branches": [], "missing_branches": []}, "ExtSelectEntityDescription": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 0, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 67, "executed_branches": [], "missing_branches": []}, "ExtSensorEntityDescription": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 0, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 72, "executed_branches": [], "missing_branches": []}, "ExtSwitchEntityDescription": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 0, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 81, "executed_branches": [], "missing_branches": []}, "": {"executed_lines": [1, 2, 4, 5, 6, 7, 8, 9, 10, 16, 17, 20, 21, 22, 23, 25, 26, 28, 37, 38, 39, 40, 42, 43, 45, 46, 47, 48, 49, 52, 53, 54, 57, 58, 59, 60, 61, 62, 63, 66, 67, 68, 71, 72, 73, 74, 75, 76, 77, 80, 81, 82, 83, 84, 87, 89, 216, 242, 710, 837, 1953, 2086, 2097, 2315, 2317, 2327, 2329, 2385, 3242], "summary": {"covered_lines": 69, "num_statements": 69, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 1, "executed_branches": [], "missing_branches": []}}}, "custom_components/goecharger_api2/entity.py": {"executed_lines": [1, 3, 4, 5, 7, 9, 13, 16], "summary": {"covered_lines": 8, "num_statements": 31, "percent_covered": 17.77777777777778, "percent_covered_display": "18", "missing_lines": 23, "excluded_lines": 0, "percent_statements_covered": 25.806451612903224, "percent_statements_covered_display": "26", "num_branches": 14, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 14, "percent_branches_covered": 0.0, "percent_branches_covered_display": "0"}, "missing_lines": [20, 22, 23, 26, 27, 30, 33, 34, 35, 36, 38, 39, 40, 41, 42, 44, 45, 46, 49, 50, 51, 52, 54], "excluded_lines": [], "executed_branches": [], "missing_branches": [[22, 23], [22, 26], [26, 27], [26, 30], [33, 34], [33, 54], [38, 39], [38, 44], [39, 38], [39, 40], [44, 45], [44, 49], [49, 50], [49, 54]], "functions": {"CustomFriendlyNameEntity._Entity__async_calculate_state": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 23, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 23, "excluded_lines": 0, "percent_statements_covered": 0.0, "percent_statements_covered_display": "0", "num_branches": 14, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 14, "percent_branches_covered": 0.0, "percent_branches_covered_display": "0"}, "missing_lines": [20, 22, 23, 26, 27, 30, 33, 34, 35, 36, 38, 39, 40, 41, 42, 44, 45, 46, 49, 50, 51, 52, 54], "excluded_lines": [], "start_line": 16, "executed_branches": [], "missing_branches": [[22, 23], [22, 26], [26, 27], [26, 30], [33, 34], [33, 54], [38, 39], [38, 44], [39, 38], [39, 40], [44, 45], [44, 49], [49, 50], [49, 54]]}, "": {"executed_lines": [1, 3, 4, 5, 7, 9, 13, 16], "summary": {"covered_lines": 8, "num_statements": 8, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 1, "executed_branches": [], "missing_branches": []}}, "classes": {"CustomFriendlyNameEntity": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 23, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 23, "excluded_lines": 0, "percent_statements_covered": 0.0, "percent_statements_covered_display": "0", "num_branches": 14, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 14, "percent_branches_covered": 0.0, "percent_branches_covered_display": "0"}, "missing_lines": [20, 22, 23, 26, 27, 30, 33, 34, 35, 36, 38, 39, 40, 41, 42, 44, 45, 46, 49, 50, 51, 52, 54], "excluded_lines": [], "start_line": 13, "executed_branches": [], "missing_branches": [[22, 23], [22, 26], [26, 27], [26, 30], [33, 34], [33, 54], [38, 39], [38, 44], [39, 38], [39, 40], [44, 45], [44, 49], [49, 50], [49, 54]]}, "": {"executed_lines": [1, 3, 4, 5, 7, 9, 13, 16], "summary": {"covered_lines": 8, "num_statements": 8, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 1, "executed_branches": [], "missing_branches": []}}}, "custom_components/goecharger_api2/number.py": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 94, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 94, "excluded_lines": 0, "percent_statements_covered": 0.0, "percent_statements_covered_display": "0", "num_branches": 48, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 48, "percent_branches_covered": 0.0, "percent_branches_covered_display": "0"}, "missing_lines": [1, 2, 4, 10, 11, 12, 13, 15, 16, 17, 19, 21, 22, 23, 24, 26, 27, 28, 31, 32, 34, 36, 37, 39, 40, 41, 43, 46, 47, 48, 50, 52, 57, 58, 60, 61, 62, 63, 64, 65, 66, 68, 70, 71, 72, 73, 74, 76, 77, 78, 80, 81, 83, 84, 87, 89, 91, 93, 94, 96, 98, 99, 100, 103, 104, 105, 108, 110, 113, 114, 115, 118, 119, 120, 123, 125, 126, 128, 129, 130, 131, 133, 134, 135, 137, 138, 139, 140, 142, 143, 144, 146, 147, 148], "excluded_lines": [], "executed_branches": [], "missing_branches": [[26, 27], [26, 39], [27, 28], [27, 43], [28, 31], [28, 36], [31, 32], [31, 34], [39, 40], [39, 43], [57, -50], [57, 58], [64, 65], [64, 70], [65, 66], [65, 68], [70, 71], [70, 72], [72, 73], [72, 77], [73, 74], [73, 76], [77, 78], [77, 87], [94, 96], [94, 113], [98, 99], [98, 100], [100, 103], [100, 104], [104, 105], [104, 108], [113, 114], [113, 115], [115, 118], [115, 119], [119, 120], [119, 123], [129, -128], [129, 130], [131, -128], [131, 133], [138, 139], [138, 146], [143, 144], [143, 146], [146, -128], [146, 147]], "functions": {"async_setup_entry": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 15, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 15, "excluded_lines": 0, "percent_statements_covered": 0.0, "percent_statements_covered_display": "0", "num_branches": 10, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 10, "percent_branches_covered": 0.0, "percent_branches_covered_display": "0"}, "missing_lines": [22, 23, 24, 26, 27, 28, 31, 32, 34, 36, 37, 39, 40, 41, 43], "excluded_lines": [], "start_line": 21, "executed_branches": [], "missing_branches": [[26, 27], [26, 39], [27, 28], [27, 43], [28, 31], [28, 36], [31, 32], [31, 34], [39, 40], [39, 43]]}, "GoeChargerNumber.__init__": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 1, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 1, "excluded_lines": 0, "percent_statements_covered": 0.0, "percent_statements_covered_display": "0", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [48], "excluded_lines": [], "start_line": 47, "executed_branches": [], "missing_branches": []}, "GoeChargerNumber.async_added_to_hass": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 3, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 3, "excluded_lines": 0, "percent_statements_covered": 0.0, "percent_statements_covered_display": "0", "num_branches": 2, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 2, "percent_branches_covered": 0.0, "percent_branches_covered_display": "0"}, "missing_lines": [52, 57, 58], "excluded_lines": [], "start_line": 50, "executed_branches": [], "missing_branches": [[57, -50], [57, 58]]}, "GoeChargerNumber.native_value": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 20, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 20, "excluded_lines": 0, "percent_statements_covered": 0.0, "percent_statements_covered_display": "0", "num_branches": 12, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 12, "percent_branches_covered": 0.0, "percent_branches_covered_display": "0"}, "missing_lines": [62, 63, 64, 65, 66, 68, 70, 71, 72, 73, 74, 76, 77, 78, 80, 81, 83, 84, 87, 89], "excluded_lines": [], "start_line": 61, "executed_branches": [], "missing_branches": [[64, 65], [64, 70], [65, 66], [65, 68], [70, 71], [70, 72], [72, 73], [72, 77], [73, 74], [73, 76], [77, 78], [77, 87]]}, "GoeChargerNumber.async_set_native_value": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 20, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 20, "excluded_lines": 0, "percent_statements_covered": 0.0, "percent_statements_covered_display": "0", "num_branches": 14, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 14, "percent_branches_covered": 0.0, "percent_branches_covered_display": "0"}, "missing_lines": [93, 94, 96, 98, 99, 100, 103, 104, 105, 108, 110, 113, 114, 115, 118, 119, 120, 123, 125, 126], "excluded_lines": [], "start_line": 91, "executed_branches": [], "missing_branches": [[94, 96], [94, 113], [98, 99], [98, 100], [100, 103], [100, 104], [104, 105], [104, 108], [113, 114], [113, 115], [115, 118], [115, 119], [119, 120], [119, 123]]}, "GoeChargerNumber.check_amp_min_max": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 16, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 16, "excluded_lines": 0, "percent_statements_covered": 0.0, "percent_statements_covered_display": "0", "num_branches": 10, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 10, "percent_branches_covered": 0.0, "percent_branches_covered_display": "0"}, "missing_lines": [129, 130, 131, 133, 134, 135, 137, 138, 139, 140, 142, 143, 144, 146, 147, 148], "excluded_lines": [], "start_line": 128, "executed_branches": [], "missing_branches": [[129, -128], [129, 130], [131, -128], [131, 133], [138, 139], [138, 146], [143, 144], [143, 146], [146, -128], [146, 147]]}, "": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 19, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 19, "excluded_lines": 0, "percent_statements_covered": 0.0, "percent_statements_covered_display": "0", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [1, 2, 4, 10, 11, 12, 13, 15, 16, 17, 19, 21, 46, 47, 50, 60, 61, 91, 128], "excluded_lines": [], "start_line": 1, "executed_branches": [], "missing_branches": []}}, "classes": {"GoeChargerNumber": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 60, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 60, "excluded_lines": 0, "percent_statements_covered": 0.0, "percent_statements_covered_display": "0", "num_branches": 38, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 38, "percent_branches_covered": 0.0, "percent_branches_covered_display": "0"}, "missing_lines": [48, 52, 57, 58, 62, 63, 64, 65, 66, 68, 70, 71, 72, 73, 74, 76, 77, 78, 80, 81, 83, 84, 87, 89, 93, 94, 96, 98, 99, 100, 103, 104, 105, 108, 110, 113, 114, 115, 118, 119, 120, 123, 125, 126, 129, 130, 131, 133, 134, 135, 137, 138, 139, 140, 142, 143, 144, 146, 147, 148], "excluded_lines": [], "start_line": 46, "executed_branches": [], "missing_branches": [[57, -50], [57, 58], [64, 65], [64, 70], [65, 66], [65, 68], [70, 71], [70, 72], [72, 73], [72, 77], [73, 74], [73, 76], [77, 78], [77, 87], [94, 96], [94, 113], [98, 99], [98, 100], [100, 103], [100, 104], [104, 105], [104, 108], [113, 114], [113, 115], [115, 118], [115, 119], [119, 120], [119, 123], [129, -128], [129, 130], [131, -128], [131, 133], [138, 139], [138, 146], [143, 144], [143, 146], [146, -128], [146, 147]]}, "": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 34, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 34, "excluded_lines": 0, "percent_statements_covered": 0.0, "percent_statements_covered_display": "0", "num_branches": 10, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 10, "percent_branches_covered": 0.0, "percent_branches_covered_display": "0"}, "missing_lines": [1, 2, 4, 10, 11, 12, 13, 15, 16, 17, 19, 21, 22, 23, 24, 26, 27, 28, 31, 32, 34, 36, 37, 39, 40, 41, 43, 46, 47, 50, 60, 61, 91, 128], "excluded_lines": [], "start_line": 1, "executed_branches": [], "missing_branches": [[26, 27], [26, 39], [27, 28], [27, 43], [28, 31], [28, 36], [31, 32], [31, 34], [39, 40], [39, 43]]}}}, "custom_components/goecharger_api2/pygoecharger_ha/__init__.py": {"executed_lines": [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 14, 15, 16, 18, 43, 45, 47, 49, 50, 51, 52, 53, 56, 59, 62, 64, 66, 67, 69, 71, 72, 73, 76, 77, 79, 81, 82, 84, 85, 86, 88, 89, 90, 91, 93, 96, 97, 98, 100, 102, 106, 108, 110, 111, 112, 113, 114, 117, 119, 121, 122, 123, 126, 131, 133, 137, 138, 140, 142, 145, 146, 152, 153, 155, 158, 160, 164, 165, 168, 172, 173, 174, 176, 177, 178, 179, 180, 181, 182, 183, 184, 185, 186, 188, 189, 190, 191, 192, 194, 195, 196, 198, 199, 201, 202, 203, 204, 208, 209, 210, 211, 212, 213, 216, 217, 218, 219, 220, 221, 222, 223, 224, 225, 226, 227, 228, 229, 230, 231, 232, 234, 237, 255, 267, 268, 273, 275, 276, 281, 282, 283, 284, 285, 286, 291, 295, 304, 305, 309, 310, 311, 312, 317, 319, 331, 380, 387, 422, 433, 434, 435, 436, 437, 438, 440, 441, 442, 443, 444, 445, 446, 447, 448, 449, 451, 452, 453, 454, 455, 457, 458, 460, 461, 467, 473, 475, 504, 505, 507, 508, 510, 512, 513, 514, 518, 532, 533, 534, 535, 536, 537, 539, 541, 543, 544, 545, 546, 547, 549, 550, 562, 563, 565, 566, 568, 570, 571, 572, 573, 574, 575, 576, 578, 579, 580, 582, 583, 584, 594, 595, 596, 597, 603, 610, 617, 621, 629, 631, 632, 636, 637, 638, 639, 640, 641, 645, 649, 650, 653, 654, 665, 667, 668, 670, 677, 678, 682, 685, 686, 688, 690, 692, 693, 694, 696, 698, 699, 700, 702, 703, 704, 707, 709, 710, 711, 714, 715, 716, 717, 718, 721, 723, 725, 726, 727, 728, 729, 731, 733, 734, 735, 743, 751, 753, 755, 756, 757, 769, 770, 773, 774, 776, 778, 780, 781, 782, 784, 785, 786, 787, 788, 790, 793, 795, 796, 797, 798, 801, 802, 803, 804, 805, 806, 807, 808, 809, 811, 812, 813, 814, 815, 823, 824, 825, 826, 828, 829, 830, 831, 832, 835, 836, 837, 839, 840, 841, 842, 843, 845, 846, 847, 858, 859, 860, 861, 862, 865, 871, 872, 873, 875, 876, 877, 878, 879, 880, 881, 884, 885, 888, 907, 908, 911, 913, 914, 915, 916, 917, 918, 919, 921, 922, 923, 924, 925, 926, 927, 928, 929, 931, 932, 933, 936, 937, 939, 948, 949, 950, 951, 964, 966, 967, 970, 971, 973, 974, 975, 976, 977, 981, 985, 986, 987, 988, 990, 992, 993, 994, 995, 1002, 1003, 1004, 1010, 1011, 1012, 1013, 1014, 1016, 1017, 1018, 1020, 1021, 1022, 1023, 1024, 1027, 1028, 1029, 1030, 1032, 1034, 1035, 1037, 1038, 1039, 1041, 1043, 1044, 1045, 1048, 1049, 1050, 1051, 1052, 1054, 1055, 1056, 1057, 1058, 1059, 1062, 1064, 1065, 1070, 1072, 1074, 1075, 1084, 1086, 1087, 1088, 1091, 1093, 1094, 1095, 1096, 1097, 1098, 1099, 1100, 1101, 1102, 1103, 1104, 1105, 1108, 1109, 1110, 1111], "summary": {"covered_lines": 491, "num_statements": 721, "percent_covered": 63.24535679374389, "percent_covered_display": "63", "missing_lines": 230, "excluded_lines": 0, "percent_statements_covered": 68.0998613037448, "percent_statements_covered_display": "68", "num_branches": 302, "num_partial_branches": 52, "covered_branches": 156, "missing_branches": 146, "percent_branches_covered": 51.65562913907285, "percent_branches_covered_display": "52"}, "missing_lines": [116, 124, 127, 128, 148, 149, 156, 169, 170, 206, 235, 238, 239, 240, 241, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 253, 256, 257, 258, 259, 262, 263, 264, 271, 279, 287, 289, 292, 293, 306, 307, 314, 315, 316, 320, 323, 324, 326, 327, 329, 332, 333, 335, 336, 337, 338, 339, 340, 342, 343, 344, 345, 348, 350, 351, 353, 355, 357, 361, 362, 365, 367, 368, 371, 372, 374, 375, 376, 377, 378, 381, 382, 383, 384, 385, 388, 389, 390, 392, 393, 394, 396, 397, 398, 403, 404, 405, 406, 407, 408, 416, 417, 420, 423, 424, 426, 427, 428, 429, 431, 463, 464, 468, 469, 470, 471, 476, 477, 478, 480, 481, 482, 483, 484, 485, 486, 487, 489, 490, 492, 493, 496, 498, 499, 500, 501, 502, 516, 521, 522, 523, 524, 525, 530, 552, 553, 554, 555, 556, 557, 558, 559, 560, 586, 588, 589, 591, 592, 598, 599, 600, 601, 605, 606, 607, 608, 618, 619, 622, 623, 624, 626, 627, 634, 642, 643, 647, 651, 655, 656, 657, 658, 661, 662, 663, 705, 712, 713, 719, 791, 816, 817, 818, 820, 821, 848, 851, 854, 855, 856, 866, 867, 868, 869, 940, 941, 942, 943, 945, 946, 953, 954, 956, 957, 958, 961, 978, 979, 980, 982, 983, 984, 996, 997, 1008, 1060, 1067, 1106, 1107], "excluded_lines": [], "executed_branches": [[51, 52], [52, 53], [52, 56], [71, 72], [71, 76], [111, 112], [111, 117], [113, 114], [123, -119], [140, 142], [140, 160], [145, 146], [152, 153], [152, 172], [155, 158], [160, 164], [160, 172], [168, 172], [172, 173], [172, 176], [176, 177], [176, 188], [203, 204], [268, 273], [276, 281], [281, 282], [283, 284], [285, 286], [291, 295], [295, 304], [305, 309], [309, 310], [437, 438], [437, 440], [443, 444], [443, 467], [446, 447], [446, 473], [448, 449], [448, 451], [453, 454], [453, 457], [454, 453], [454, 455], [507, 508], [507, 518], [508, 510], [508, 512], [512, 513], [518, 532], [532, 533], [532, 534], [534, 535], [534, 536], [536, 537], [536, 539], [544, 545], [545, 546], [545, 549], [565, 566], [565, 568], [572, 573], [572, 594], [575, 576], [576, 578], [578, 579], [578, 582], [582, 583], [594, 595], [594, 603], [631, 632], [638, 639], [650, 653], [654, -653], [668, 670], [668, 678], [678, 682], [678, 688], [698, 699], [698, 703], [699, 700], [699, 702], [703, 704], [709, 710], [709, 714], [714, 715], [714, 716], [716, 717], [716, 718], [718, 721], [726, 727], [726, 729], [753, 755], [753, 773], [784, 785], [784, 790], [786, 787], [790, 793], [805, 806], [805, 811], [807, 808], [812, 813], [830, 831], [839, 840], [839, 845], [841, 842], [847, 858], [858, 859], [858, 865], [860, 861], [865, 871], [877, 878], [877, 884], [879, 880], [917, 918], [925, 926], [925, 931], [927, 928], [936, 937], [936, 990], [939, 948], [948, 949], [964, 966], [970, 936], [970, 971], [975, 976], [987, 988], [1002, 1003], [1020, 1021], [1020, 1037], [1022, 1023], [1022, 1072], [1023, 1024], [1023, 1027], [1028, 1029], [1028, 1032], [1037, 1038], [1037, 1048], [1039, 1041], [1039, 1072], [1043, 1044], [1043, 1072], [1048, 1049], [1048, 1070], [1050, 1051], [1054, 1055], [1054, 1064], [1056, 1057], [1059, 1062], [1064, 1065], [1086, 1087], [1086, 1091], [1095, 1096], [1099, 1100], [1099, 1102], [1105, 1108]], "missing_branches": [[51, 66], [113, 116], [123, 124], [145, 148], [155, 156], [168, 169], [203, 206], [238, 239], [238, 240], [262, -255], [262, 263], [263, -255], [263, 264], [268, 271], [276, 279], [281, 291], [283, 285], [285, 287], [287, 281], [287, 289], [291, 292], [295, 317], [305, 306], [309, 314], [323, 324], [323, 326], [326, 327], [326, 329], [333, 335], [333, 338], [335, 336], [335, 342], [336, 337], [336, 342], [338, 339], [338, 342], [339, 340], [339, 342], [342, 343], [342, 371], [344, 345], [344, 348], [350, 351], [350, 353], [355, -331], [355, 357], [361, 362], [361, 365], [365, -331], [365, 367], [371, 372], [371, 374], [375, 376], [375, 377], [377, -331], [377, 378], [384, -380], [384, 385], [388, 389], [388, 420], [389, 390], [389, 392], [393, 394], [393, 396], [396, 397], [396, 416], [398, -387], [398, 403], [403, -387], [403, 404], [404, 403], [404, 405], [406, 403], [406, 407], [428, 429], [428, 431], [477, 478], [477, 480], [483, 484], [483, 496], [486, 487], [486, 502], [512, 516], [518, 521], [522, 523], [522, 524], [524, 525], [524, 530], [544, 552], [553, 554], [553, 560], [554, 555], [554, 556], [556, 557], [556, 558], [558, 553], [558, 559], [575, 610], [576, 586], [582, 584], [622, 623], [622, 626], [631, 634], [638, 647], [650, 651], [654, 655], [656, 657], [656, 661], [661, -653], [661, 662], [703, 705], [718, 719], [786, 788], [790, 791], [807, 809], [812, 820], [830, 832], [841, 843], [847, 848], [855, 856], [855, 858], [860, 862], [865, 866], [867, 868], [867, 869], [879, 881], [917, 919], [927, 929], [939, 940], [948, 956], [956, 957], [956, 961], [964, 936], [975, 990], [979, 980], [979, 990], [983, 984], [983, 990], [987, 990], [1002, 1008], [1050, 1054], [1056, 1072], [1059, 1060], [1064, 1067], [1095, 1111], [1105, 1106]], "functions": {"TargetedEvent.__init__": {"executed_lines": [50, 51, 52, 53, 56, 59, 62, 64, 66, 67], "summary": {"covered_lines": 10, "num_statements": 10, "percent_covered": 92.85714285714286, "percent_covered_display": "93", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 4, "num_partial_branches": 1, "covered_branches": 3, "missing_branches": 1, "percent_branches_covered": 75.0, "percent_branches_covered_display": "75"}, "missing_lines": [], "excluded_lines": [], "start_line": 49, "executed_branches": [[51, 52], [52, 53], [52, 56]], "missing_branches": [[51, 66]]}, "TargetedEvent.check_and_set": {"executed_lines": [71, 72, 73, 76, 77, 79], "summary": {"covered_lines": 6, "num_statements": 6, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 2, "num_partial_branches": 0, "covered_branches": 2, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 69, "executed_branches": [[71, 72], [71, 76]], "missing_branches": []}, "TargetedEvent.has_errors": {"executed_lines": [82], "summary": {"covered_lines": 1, "num_statements": 1, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 81, "executed_branches": [], "missing_branches": []}, "TargetedEvent.set_error": {"executed_lines": [85, 86], "summary": {"covered_lines": 2, "num_statements": 2, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 84, "executed_branches": [], "missing_branches": []}, "NonNullDict.none_null_get": {"executed_lines": [90, 91], "summary": {"covered_lines": 2, "num_statements": 2, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 89, "executed_branches": [], "missing_branches": []}, "ExpiringStore.__init__": {"executed_lines": [97, 98], "summary": {"covered_lines": 2, "num_statements": 2, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 96, "executed_branches": [], "missing_branches": []}, "ExpiringStore.set": {"executed_lines": [102, 106], "summary": {"covered_lines": 2, "num_statements": 2, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 100, "executed_branches": [], "missing_branches": []}, "ExpiringStore.get": {"executed_lines": [110, 111, 112, 113, 114, 117], "summary": {"covered_lines": 6, "num_statements": 7, "percent_covered": 81.81818181818181, "percent_covered_display": "82", "missing_lines": 1, "excluded_lines": 0, "percent_statements_covered": 85.71428571428571, "percent_statements_covered_display": "86", "num_branches": 4, "num_partial_branches": 1, "covered_branches": 3, "missing_branches": 1, "percent_branches_covered": 75.0, "percent_branches_covered_display": "75"}, "missing_lines": [116], "excluded_lines": [], "start_line": 108, "executed_branches": [[111, 112], [111, 117], [113, 114]], "missing_branches": [[113, 116]]}, "ExpiringStore._cleanup": {"executed_lines": [121, 122, 123], "summary": {"covered_lines": 3, "num_statements": 4, "percent_covered": 66.66666666666667, "percent_covered_display": "67", "missing_lines": 1, "excluded_lines": 0, "percent_statements_covered": 75.0, "percent_statements_covered_display": "75", "num_branches": 2, "num_partial_branches": 1, "covered_branches": 1, "missing_branches": 1, "percent_branches_covered": 50.0, "percent_branches_covered_display": "50"}, "missing_lines": [124], "excluded_lines": [], "start_line": 119, "executed_branches": [[123, -119]], "missing_branches": [[123, 124]]}, "ExpiringStore.__len__": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 2, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 2, "excluded_lines": 0, "percent_statements_covered": 0.0, "percent_statements_covered_display": "0", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [127, 128], "excluded_lines": [], "start_line": 126, "executed_branches": [], "missing_branches": []}, "GoeChargerApiV2Bridge.__init__": {"executed_lines": [137, 138, 140, 142, 145, 146, 152, 153, 155, 158, 160, 164, 165, 168, 172, 173, 174, 176, 177, 178, 179, 180, 181, 182, 183, 184, 185, 186, 188, 189, 190, 191, 192, 194, 195, 196, 198, 199, 201, 202, 203, 204, 208, 209, 210, 211, 212, 213, 216, 217, 218, 219, 220, 221, 222, 223, 224, 225, 226, 227, 228, 229, 230, 231, 232], "summary": {"covered_lines": 65, "num_statements": 71, "percent_covered": 88.76404494382022, "percent_covered_display": "89", "missing_lines": 6, "excluded_lines": 0, "percent_statements_covered": 91.54929577464789, "percent_statements_covered_display": "92", "num_branches": 18, "num_partial_branches": 4, "covered_branches": 14, "missing_branches": 4, "percent_branches_covered": 77.77777777777777, "percent_branches_covered_display": "78"}, "missing_lines": [148, 149, 156, 169, 170, 206], "excluded_lines": [], "start_line": 133, "executed_branches": [[140, 142], [140, 160], [145, 146], [152, 153], [152, 172], [155, 158], [160, 164], [160, 172], [168, 172], [172, 173], [172, 176], [176, 177], [176, 188], [203, 204]], "missing_branches": [[145, 148], [155, 156], [168, 169], [203, 206]]}, "GoeChargerApiV2Bridge.available_fields": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 1, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 1, "excluded_lines": 0, "percent_statements_covered": 0.0, "percent_statements_covered_display": "0", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [235], "excluded_lines": [], "start_line": 234, "executed_branches": [], "missing_branches": []}, "GoeChargerApiV2Bridge.clear_data": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 16, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 16, "excluded_lines": 0, "percent_statements_covered": 0.0, "percent_statements_covered_display": "0", "num_branches": 2, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 2, "percent_branches_covered": 0.0, "percent_branches_covered_display": "0"}, "missing_lines": [238, 239, 240, 241, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 253], "excluded_lines": [], "start_line": 237, "executed_branches": [], "missing_branches": [[238, 239], [238, 240]]}, "GoeChargerApiV2Bridge.reset_stored_update_ts": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 7, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 7, "excluded_lines": 0, "percent_statements_covered": 0.0, "percent_statements_covered_display": "0", "num_branches": 4, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 4, "percent_branches_covered": 0.0, "percent_branches_covered_display": "0"}, "missing_lines": [256, 257, 258, 259, 262, 263, 264], "excluded_lines": [], "start_line": 255, "executed_branches": [], "missing_branches": [[262, -255], [262, 263], [263, -255], [263, 264]]}, "GoeChargerApiV2Bridge.read_system": {"executed_lines": [268, 273], "summary": {"covered_lines": 2, "num_statements": 3, "percent_covered": 60.0, "percent_covered_display": "60", "missing_lines": 1, "excluded_lines": 0, "percent_statements_covered": 66.66666666666667, "percent_statements_covered_display": "67", "num_branches": 2, "num_partial_branches": 1, "covered_branches": 1, "missing_branches": 1, "percent_branches_covered": 50.0, "percent_branches_covered_display": "50"}, "missing_lines": [271], "excluded_lines": [], "start_line": 267, "executed_branches": [[268, 273]], "missing_branches": [[268, 271]]}, "GoeChargerApiV2Bridge.read_versions": {"executed_lines": [276, 281, 282, 283, 284, 285, 286, 291, 295, 304, 305, 309, 310, 311, 312, 317], "summary": {"covered_lines": 16, "num_statements": 26, "percent_covered": 54.54545454545455, "percent_covered_display": "55", "missing_lines": 10, "excluded_lines": 0, "percent_statements_covered": 61.53846153846154, "percent_statements_covered_display": "62", "num_branches": 18, "num_partial_branches": 8, "covered_branches": 8, "missing_branches": 10, "percent_branches_covered": 44.44444444444444, "percent_branches_covered_display": "44"}, "missing_lines": [279, 287, 289, 292, 293, 306, 307, 314, 315, 316], "excluded_lines": [], "start_line": 275, "executed_branches": [[276, 281], [281, 282], [283, 284], [285, 286], [291, 295], [295, 304], [305, 309], [309, 310]], "missing_branches": [[276, 279], [281, 291], [283, 285], [285, 287], [287, 281], [287, 289], [291, 292], [295, 317], [305, 306], [309, 314]]}, "GoeChargerApiV2Bridge.read_all": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 6, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 6, "excluded_lines": 0, "percent_statements_covered": 0.0, "percent_statements_covered_display": "0", "num_branches": 4, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 4, "percent_branches_covered": 0.0, "percent_branches_covered_display": "0"}, "missing_lines": [320, 323, 324, 326, 327, 329], "excluded_lines": [], "start_line": 319, "executed_branches": [], "missing_branches": [[323, 324], [323, 326], [326, 327], [326, 329]]}, "GoeChargerApiV2Bridge.read_all_states": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 30, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 30, "excluded_lines": 0, "percent_statements_covered": 0.0, "percent_statements_covered_display": "0", "num_branches": 28, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 28, "percent_branches_covered": 0.0, "percent_branches_covered_display": "0"}, "missing_lines": [332, 333, 335, 336, 337, 338, 339, 340, 342, 343, 344, 345, 348, 350, 351, 353, 355, 357, 361, 362, 365, 367, 368, 371, 372, 374, 375, 376, 377, 378], "excluded_lines": [], "start_line": 331, "executed_branches": [], "missing_branches": [[333, 335], [333, 338], [335, 336], [335, 342], [336, 337], [336, 342], [338, 339], [338, 342], [339, 340], [339, 342], [342, 343], [342, 371], [344, 345], [344, 348], [350, 351], [350, 353], [355, -331], [355, 357], [361, 362], [361, 365], [365, -331], [365, 367], [371, 372], [371, 374], [375, 376], [375, 377], [377, -331], [377, 378]]}, "GoeChargerApiV2Bridge.force_config_update": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 5, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 5, "excluded_lines": 0, "percent_statements_covered": 0.0, "percent_statements_covered_display": "0", "num_branches": 2, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 2, "percent_branches_covered": 0.0, "percent_branches_covered_display": "0"}, "missing_lines": [381, 382, 383, 384, 385], "excluded_lines": [], "start_line": 380, "executed_branches": [], "missing_branches": [[384, -380], [384, 385]]}, "GoeChargerApiV2Bridge.read_all_config": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 18, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 18, "excluded_lines": 0, "percent_statements_covered": 0.0, "percent_statements_covered_display": "0", "num_branches": 16, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 16, "percent_branches_covered": 0.0, "percent_branches_covered_display": "0"}, "missing_lines": [388, 389, 390, 392, 393, 394, 396, 397, 398, 403, 404, 405, 406, 407, 408, 416, 417, 420], "excluded_lines": [], "start_line": 387, "executed_branches": [], "missing_branches": [[388, 389], [388, 420], [389, 390], [389, 392], [393, 394], [393, 396], [396, 397], [396, 416], [398, -387], [398, 403], [403, -387], [403, 404], [404, 403], [404, 405], [406, 403], [406, 407]]}, "GoeChargerApiV2Bridge._read_filtered_data_from_ws_source": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 7, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 7, "excluded_lines": 0, "percent_statements_covered": 0.0, "percent_statements_covered_display": "0", "num_branches": 2, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 2, "percent_branches_covered": 0.0, "percent_branches_covered_display": "0"}, "missing_lines": [423, 424, 426, 427, 428, 429, 431], "excluded_lines": [], "start_line": 422, "executed_branches": [], "missing_branches": [[428, 429], [428, 431]]}, "GoeChargerApiV2Bridge._read_filtered_data": {"executed_lines": [434, 435, 436, 437, 438, 440, 441, 442, 443, 444, 445, 446, 447, 448, 449, 451, 452, 453, 454, 455, 457, 458, 460, 461, 467, 473], "summary": {"covered_lines": 26, "num_statements": 32, "percent_covered": 86.36363636363636, "percent_covered_display": "86", "missing_lines": 6, "excluded_lines": 0, "percent_statements_covered": 81.25, "percent_statements_covered_display": "81", "num_branches": 12, "num_partial_branches": 0, "covered_branches": 12, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [463, 464, 468, 469, 470, 471], "excluded_lines": [], "start_line": 433, "executed_branches": [[437, 438], [437, 440], [443, 444], [443, 467], [446, 447], [446, 473], [448, 449], [448, 451], [453, 454], [453, 457], [454, 453], [454, 455]], "missing_branches": []}, "GoeChargerApiV2Bridge._read_all_data": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 21, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 21, "excluded_lines": 0, "percent_statements_covered": 0.0, "percent_statements_covered_display": "0", "num_branches": 6, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 6, "percent_branches_covered": 0.0, "percent_branches_covered_display": "0"}, "missing_lines": [476, 477, 478, 480, 481, 482, 483, 484, 485, 486, 487, 489, 490, 492, 493, 496, 498, 499, 500, 501, 502], "excluded_lines": [], "start_line": 475, "executed_branches": [], "missing_branches": [[477, 478], [477, 480], [483, 484], [483, 496], [486, 487], [486, 502]]}, "GoeChargerApiV2Bridge.write_value_to_key": {"executed_lines": [505, 507, 508, 510, 512, 513, 514, 518, 532, 533, 534, 535, 536, 537, 539, 541], "summary": {"covered_lines": 16, "num_statements": 23, "percent_covered": 68.29268292682927, "percent_covered_display": "68", "missing_lines": 7, "excluded_lines": 0, "percent_statements_covered": 69.56521739130434, "percent_statements_covered_display": "70", "num_branches": 18, "num_partial_branches": 2, "covered_branches": 12, "missing_branches": 6, "percent_branches_covered": 66.66666666666667, "percent_branches_covered_display": "67"}, "missing_lines": [516, 521, 522, 523, 524, 525, 530], "excluded_lines": [], "start_line": 504, "executed_branches": [[507, 508], [507, 518], [508, 510], [508, 512], [512, 513], [518, 532], [532, 533], [532, 534], [534, 535], [534, 536], [536, 537], [536, 539]], "missing_branches": [[512, 516], [518, 521], [522, 523], [522, 524], [524, 525], [524, 530]]}, "GoeChargerApiV2Bridge.write_multiple_values_to_keys": {"executed_lines": [544, 545, 546, 547, 549, 550], "summary": {"covered_lines": 6, "num_statements": 15, "percent_covered": 33.333333333333336, "percent_covered_display": "33", "missing_lines": 9, "excluded_lines": 0, "percent_statements_covered": 40.0, "percent_statements_covered_display": "40", "num_branches": 12, "num_partial_branches": 1, "covered_branches": 3, "missing_branches": 9, "percent_branches_covered": 25.0, "percent_branches_covered_display": "25"}, "missing_lines": [552, 553, 554, 555, 556, 557, 558, 559, 560], "excluded_lines": [], "start_line": 543, "executed_branches": [[544, 545], [545, 546], [545, 549]], "missing_branches": [[544, 552], [553, 554], [553, 560], [554, 555], [554, 556], [556, 557], [556, 558], [558, 553], [558, 559]]}, "GoeChargerApiV2Bridge._write_values_int": {"executed_lines": [563, 565, 566, 568, 570, 571, 572, 573, 574, 575, 576, 578, 579, 580, 582, 583, 584, 594, 595, 596, 597, 603, 610], "summary": {"covered_lines": 23, "num_statements": 36, "percent_covered": 68.0, "percent_covered_display": "68", "missing_lines": 13, "excluded_lines": 0, "percent_statements_covered": 63.888888888888886, "percent_statements_covered_display": "64", "num_branches": 14, "num_partial_branches": 3, "covered_branches": 11, "missing_branches": 3, "percent_branches_covered": 78.57142857142857, "percent_branches_covered_display": "79"}, "missing_lines": [586, 588, 589, 591, 592, 598, 599, 600, 601, 605, 606, 607, 608], "excluded_lines": [], "start_line": 562, "executed_branches": [[565, 566], [565, 568], [572, 573], [572, 594], [575, 576], [576, 578], [578, 579], [578, 582], [582, 583], [594, 595], [594, 603]], "missing_branches": [[575, 610], [576, 586], [582, 584]]}, "GoeChargerApiV2Bridge.ws_set_coordinator": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 2, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 2, "excluded_lines": 0, "percent_statements_covered": 0.0, "percent_statements_covered_display": "0", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [618, 619], "excluded_lines": [], "start_line": 617, "executed_branches": [], "missing_branches": []}, "GoeChargerApiV2Bridge.ws_check_last_update": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 5, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 5, "excluded_lines": 0, "percent_statements_covered": 0.0, "percent_statements_covered_display": "0", "num_branches": 2, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 2, "percent_branches_covered": 0.0, "percent_branches_covered_display": "0"}, "missing_lines": [622, 623, 624, 626, 627], "excluded_lines": [], "start_line": 621, "executed_branches": [], "missing_branches": [[622, 623], [622, 626]]}, "GoeChargerApiV2Bridge.ws_close": {"executed_lines": [631, 632, 636, 637, 638, 639, 640, 641, 645], "summary": {"covered_lines": 9, "num_statements": 13, "percent_covered": 64.70588235294117, "percent_covered_display": "65", "missing_lines": 4, "excluded_lines": 0, "percent_statements_covered": 69.23076923076923, "percent_statements_covered_display": "69", "num_branches": 4, "num_partial_branches": 2, "covered_branches": 2, "missing_branches": 2, "percent_branches_covered": 50.0, "percent_branches_covered_display": "50"}, "missing_lines": [634, 642, 643, 647], "excluded_lines": [], "start_line": 629, "executed_branches": [[631, 632], [638, 639]], "missing_branches": [[631, 634], [638, 647]]}, "GoeChargerApiV2Bridge._ws_notify_for_new_data": {"executed_lines": [650, 653, 665], "summary": {"covered_lines": 3, "num_statements": 4, "percent_covered": 66.66666666666667, "percent_covered_display": "67", "missing_lines": 1, "excluded_lines": 0, "percent_statements_covered": 75.0, "percent_statements_covered_display": "75", "num_branches": 2, "num_partial_branches": 1, "covered_branches": 1, "missing_branches": 1, "percent_branches_covered": 50.0, "percent_branches_covered_display": "50"}, "missing_lines": [651], "excluded_lines": [], "start_line": 649, "executed_branches": [[650, 653]], "missing_branches": [[650, 651]]}, "GoeChargerApiV2Bridge._ws_notify_for_new_data._ws_debounce_coordinator_update": {"executed_lines": [654], "summary": {"covered_lines": 1, "num_statements": 8, "percent_covered": 14.285714285714286, "percent_covered_display": "14", "missing_lines": 7, "excluded_lines": 0, "percent_statements_covered": 12.5, "percent_statements_covered_display": "12", "num_branches": 6, "num_partial_branches": 1, "covered_branches": 1, "missing_branches": 5, "percent_branches_covered": 16.666666666666668, "percent_branches_covered_display": "17"}, "missing_lines": [655, 656, 657, 658, 661, 662, 663], "excluded_lines": [], "start_line": 653, "executed_branches": [[654, -653]], "missing_branches": [[654, 655], [656, 657], [656, 661], [661, -653], [661, 662]]}, "GoeChargerApiV2Bridge._ws_compute_hashed_password": {"executed_lines": [668, 670, 677, 678, 682, 685, 686, 688], "summary": {"covered_lines": 8, "num_statements": 8, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 4, "num_partial_branches": 0, "covered_branches": 4, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 667, "executed_branches": [[668, 670], [668, 678], [678, 682], [678, 688]], "missing_branches": []}, "GoeChargerApiV2Bridge._ws_compute_auth_hash": {"executed_lines": [692, 693, 694], "summary": {"covered_lines": 3, "num_statements": 3, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 690, "executed_branches": [], "missing_branches": []}, "GoeChargerApiV2Bridge._ws_decode_message": {"executed_lines": [698, 699, 700, 702, 703, 704], "summary": {"covered_lines": 6, "num_statements": 7, "percent_covered": 84.61538461538461, "percent_covered_display": "85", "missing_lines": 1, "excluded_lines": 0, "percent_statements_covered": 85.71428571428571, "percent_statements_covered_display": "86", "num_branches": 6, "num_partial_branches": 1, "covered_branches": 5, "missing_branches": 1, "percent_branches_covered": 83.33333333333333, "percent_branches_covered_display": "83"}, "missing_lines": [705], "excluded_lines": [], "start_line": 696, "executed_branches": [[698, 699], [698, 703], [699, 700], [699, 702], [703, 704]], "missing_branches": [[703, 705]]}, "GoeChargerApiV2Bridge._ws_normalize_value": {"executed_lines": [709, 710, 711, 714, 715, 716, 717, 718, 721], "summary": {"covered_lines": 9, "num_statements": 12, "percent_covered": 80.0, "percent_covered_display": "80", "missing_lines": 3, "excluded_lines": 0, "percent_statements_covered": 75.0, "percent_statements_covered_display": "75", "num_branches": 8, "num_partial_branches": 1, "covered_branches": 7, "missing_branches": 1, "percent_branches_covered": 87.5, "percent_branches_covered_display": "88"}, "missing_lines": [712, 713, 719], "excluded_lines": [], "start_line": 707, "executed_branches": [[709, 710], [709, 714], [714, 715], [714, 716], [716, 717], [716, 718], [718, 721]], "missing_branches": [[718, 719]]}, "GoeChargerApiV2Bridge._ws_normalize_dict": {"executed_lines": [725, 726, 727, 728, 729], "summary": {"covered_lines": 5, "num_statements": 5, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 2, "num_partial_branches": 0, "covered_branches": 2, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 723, "executed_branches": [[726, 727], [726, 729]], "missing_branches": []}, "GoeChargerApiV2Bridge._ws_send_command": {"executed_lines": [733, 734, 735, 743, 751, 753, 755, 756, 757, 769, 770, 773, 774, 776], "summary": {"covered_lines": 14, "num_statements": 14, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 2, "num_partial_branches": 0, "covered_branches": 2, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 731, "executed_branches": [[753, 755], [753, 773]], "missing_branches": []}, "GoeChargerApiV2Bridge.ws_connect": {"executed_lines": [780, 781, 782, 784, 785, 786, 787, 788, 790, 793, 795, 796, 797, 798, 801, 802, 803, 804, 805, 806, 807, 808, 809, 811, 812, 813, 814, 815, 823, 824, 825, 826, 828, 829, 830, 831, 832, 835, 836, 837, 839, 840, 841, 842, 843, 845, 846, 847, 858, 859, 860, 861, 862, 865, 871, 872, 873, 875, 876, 877, 878, 879, 880, 881, 884, 885, 888, 907, 908, 911, 913, 914, 915, 916, 917, 918, 919, 921, 922, 923, 924, 925, 926, 927, 928, 929, 931, 932, 933, 936, 937, 939, 948, 949, 950, 951, 964, 966, 967, 970, 971, 973, 974, 975, 976, 977, 981, 985, 986, 987, 988, 990, 992, 993, 994, 995, 1002, 1003, 1004, 1010, 1011, 1012, 1013, 1014], "summary": {"covered_lines": 124, "num_statements": 160, "percent_covered": 71.17117117117117, "percent_covered_display": "71", "missing_lines": 36, "excluded_lines": 0, "percent_statements_covered": 77.5, "percent_statements_covered_display": "78", "num_branches": 62, "num_partial_branches": 18, "covered_branches": 34, "missing_branches": 28, "percent_branches_covered": 54.83870967741935, "percent_branches_covered_display": "55"}, "missing_lines": [791, 816, 817, 818, 820, 821, 848, 851, 854, 855, 856, 866, 867, 868, 869, 940, 941, 942, 943, 945, 946, 953, 954, 956, 957, 958, 961, 978, 979, 980, 982, 983, 984, 996, 997, 1008], "excluded_lines": [], "start_line": 778, "executed_branches": [[784, 785], [784, 790], [786, 787], [790, 793], [805, 806], [805, 811], [807, 808], [812, 813], [830, 831], [839, 840], [839, 845], [841, 842], [847, 858], [858, 859], [858, 865], [860, 861], [865, 871], [877, 878], [877, 884], [879, 880], [917, 918], [925, 926], [925, 931], [927, 928], [936, 937], [936, 990], [939, 948], [948, 949], [964, 966], [970, 936], [970, 971], [975, 976], [987, 988], [1002, 1003]], "missing_branches": [[786, 788], [790, 791], [807, 809], [812, 820], [830, 832], [841, 843], [847, 848], [855, 856], [855, 858], [860, 862], [865, 866], [867, 868], [867, 869], [879, 881], [917, 919], [927, 929], [939, 940], [948, 956], [956, 957], [956, 961], [964, 936], [975, 990], [979, 980], [979, 990], [983, 984], [983, 990], [987, 990], [1002, 1008]]}, "GoeChargerApiV2Bridge.extract_ws_message_data": {"executed_lines": [1017, 1018, 1020, 1021, 1022, 1023, 1024, 1027, 1028, 1029, 1030, 1032, 1034, 1035, 1037, 1038, 1039, 1041, 1043, 1044, 1045, 1048, 1049, 1050, 1051, 1052, 1054, 1055, 1056, 1057, 1058, 1059, 1062, 1064, 1065, 1070, 1072], "summary": {"covered_lines": 37, "num_statements": 39, "percent_covered": 90.76923076923077, "percent_covered_display": "91", "missing_lines": 2, "excluded_lines": 0, "percent_statements_covered": 94.87179487179488, "percent_statements_covered_display": "95", "num_branches": 26, "num_partial_branches": 4, "covered_branches": 22, "missing_branches": 4, "percent_branches_covered": 84.61538461538461, "percent_branches_covered_display": "85"}, "missing_lines": [1060, 1067], "excluded_lines": [], "start_line": 1016, "executed_branches": [[1020, 1021], [1020, 1037], [1022, 1023], [1022, 1072], [1023, 1024], [1023, 1027], [1028, 1029], [1028, 1032], [1037, 1038], [1037, 1048], [1039, 1041], [1039, 1072], [1043, 1044], [1043, 1072], [1048, 1049], [1048, 1070], [1050, 1051], [1054, 1055], [1054, 1064], [1056, 1057], [1059, 1062], [1064, 1065]], "missing_branches": [[1050, 1054], [1056, 1072], [1059, 1060], [1064, 1067]]}, "bcryptjs_base64_encode_serial": {"executed_lines": [1084, 1086, 1087, 1088, 1091, 1093, 1094, 1095, 1096, 1097, 1098, 1099, 1100, 1101, 1102, 1103, 1104, 1105, 1108, 1109, 1110, 1111], "summary": {"covered_lines": 22, "num_statements": 24, "percent_covered": 87.5, "percent_covered_display": "88", "missing_lines": 2, "excluded_lines": 0, "percent_statements_covered": 91.66666666666667, "percent_statements_covered_display": "92", "num_branches": 8, "num_partial_branches": 2, "covered_branches": 6, "missing_branches": 2, "percent_branches_covered": 75.0, "percent_branches_covered_display": "75"}, "missing_lines": [1106, 1107], "excluded_lines": [], "start_line": 1075, "executed_branches": [[1086, 1087], [1086, 1091], [1095, 1096], [1099, 1100], [1099, 1102], [1105, 1108]], "missing_branches": [[1095, 1111], [1105, 1106]]}, "": {"executed_lines": [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 14, 15, 16, 18, 43, 45, 47, 49, 69, 81, 84, 88, 89, 93, 96, 100, 108, 119, 126, 131, 133, 234, 237, 255, 267, 275, 319, 331, 380, 387, 422, 433, 475, 504, 543, 562, 617, 621, 629, 649, 667, 690, 696, 707, 723, 731, 778, 1016, 1074, 1075], "summary": {"covered_lines": 62, "num_statements": 62, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 1, "executed_branches": [], "missing_branches": []}}, "classes": {"TargetedEvent": {"executed_lines": [50, 51, 52, 53, 56, 59, 62, 64, 66, 67, 71, 72, 73, 76, 77, 79, 82, 85, 86], "summary": {"covered_lines": 19, "num_statements": 19, "percent_covered": 96.0, "percent_covered_display": "96", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 6, "num_partial_branches": 1, "covered_branches": 5, "missing_branches": 1, "percent_branches_covered": 83.33333333333333, "percent_branches_covered_display": "83"}, "missing_lines": [], "excluded_lines": [], "start_line": 47, "executed_branches": [[51, 52], [52, 53], [52, 56], [71, 72], [71, 76]], "missing_branches": [[51, 66]]}, "NonNullDict": {"executed_lines": [90, 91], "summary": {"covered_lines": 2, "num_statements": 2, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 88, "executed_branches": [], "missing_branches": []}, "ExpiringStore": {"executed_lines": [97, 98, 102, 106, 110, 111, 112, 113, 114, 117, 121, 122, 123], "summary": {"covered_lines": 13, "num_statements": 17, "percent_covered": 73.91304347826087, "percent_covered_display": "74", "missing_lines": 4, "excluded_lines": 0, "percent_statements_covered": 76.47058823529412, "percent_statements_covered_display": "76", "num_branches": 6, "num_partial_branches": 2, "covered_branches": 4, "missing_branches": 2, "percent_branches_covered": 66.66666666666667, "percent_branches_covered_display": "67"}, "missing_lines": [116, 124, 127, 128], "excluded_lines": [], "start_line": 93, "executed_branches": [[111, 112], [111, 117], [113, 114], [123, -119]], "missing_branches": [[113, 116], [123, 124]]}, "GoeChargerApiV2Bridge": {"executed_lines": [137, 138, 140, 142, 145, 146, 152, 153, 155, 158, 160, 164, 165, 168, 172, 173, 174, 176, 177, 178, 179, 180, 181, 182, 183, 184, 185, 186, 188, 189, 190, 191, 192, 194, 195, 196, 198, 199, 201, 202, 203, 204, 208, 209, 210, 211, 212, 213, 216, 217, 218, 219, 220, 221, 222, 223, 224, 225, 226, 227, 228, 229, 230, 231, 232, 268, 273, 276, 281, 282, 283, 284, 285, 286, 291, 295, 304, 305, 309, 310, 311, 312, 317, 434, 435, 436, 437, 438, 440, 441, 442, 443, 444, 445, 446, 447, 448, 449, 451, 452, 453, 454, 455, 457, 458, 460, 461, 467, 473, 505, 507, 508, 510, 512, 513, 514, 518, 532, 533, 534, 535, 536, 537, 539, 541, 544, 545, 546, 547, 549, 550, 563, 565, 566, 568, 570, 571, 572, 573, 574, 575, 576, 578, 579, 580, 582, 583, 584, 594, 595, 596, 597, 603, 610, 631, 632, 636, 637, 638, 639, 640, 641, 645, 650, 653, 654, 665, 668, 670, 677, 678, 682, 685, 686, 688, 692, 693, 694, 698, 699, 700, 702, 703, 704, 709, 710, 711, 714, 715, 716, 717, 718, 721, 725, 726, 727, 728, 729, 733, 734, 735, 743, 751, 753, 755, 756, 757, 769, 770, 773, 774, 776, 780, 781, 782, 784, 785, 786, 787, 788, 790, 793, 795, 796, 797, 798, 801, 802, 803, 804, 805, 806, 807, 808, 809, 811, 812, 813, 814, 815, 823, 824, 825, 826, 828, 829, 830, 831, 832, 835, 836, 837, 839, 840, 841, 842, 843, 845, 846, 847, 858, 859, 860, 861, 862, 865, 871, 872, 873, 875, 876, 877, 878, 879, 880, 881, 884, 885, 888, 907, 908, 911, 913, 914, 915, 916, 917, 918, 919, 921, 922, 923, 924, 925, 926, 927, 928, 929, 931, 932, 933, 936, 937, 939, 948, 949, 950, 951, 964, 966, 967, 970, 971, 973, 974, 975, 976, 977, 981, 985, 986, 987, 988, 990, 992, 993, 994, 995, 1002, 1003, 1004, 1010, 1011, 1012, 1013, 1014, 1017, 1018, 1020, 1021, 1022, 1023, 1024, 1027, 1028, 1029, 1030, 1032, 1034, 1035, 1037, 1038, 1039, 1041, 1043, 1044, 1045, 1048, 1049, 1050, 1051, 1052, 1054, 1055, 1056, 1057, 1058, 1059, 1062, 1064, 1065, 1070, 1072], "summary": {"covered_lines": 373, "num_statements": 597, "percent_covered": 58.47554038680318, "percent_covered_display": "58", "missing_lines": 224, "excluded_lines": 0, "percent_statements_covered": 62.47906197654942, "percent_statements_covered_display": "62", "num_branches": 282, "num_partial_branches": 47, "covered_branches": 141, "missing_branches": 141, "percent_branches_covered": 50.0, "percent_branches_covered_display": "50"}, "missing_lines": [148, 149, 156, 169, 170, 206, 235, 238, 239, 240, 241, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 253, 256, 257, 258, 259, 262, 263, 264, 271, 279, 287, 289, 292, 293, 306, 307, 314, 315, 316, 320, 323, 324, 326, 327, 329, 332, 333, 335, 336, 337, 338, 339, 340, 342, 343, 344, 345, 348, 350, 351, 353, 355, 357, 361, 362, 365, 367, 368, 371, 372, 374, 375, 376, 377, 378, 381, 382, 383, 384, 385, 388, 389, 390, 392, 393, 394, 396, 397, 398, 403, 404, 405, 406, 407, 408, 416, 417, 420, 423, 424, 426, 427, 428, 429, 431, 463, 464, 468, 469, 470, 471, 476, 477, 478, 480, 481, 482, 483, 484, 485, 486, 487, 489, 490, 492, 493, 496, 498, 499, 500, 501, 502, 516, 521, 522, 523, 524, 525, 530, 552, 553, 554, 555, 556, 557, 558, 559, 560, 586, 588, 589, 591, 592, 598, 599, 600, 601, 605, 606, 607, 608, 618, 619, 622, 623, 624, 626, 627, 634, 642, 643, 647, 651, 655, 656, 657, 658, 661, 662, 663, 705, 712, 713, 719, 791, 816, 817, 818, 820, 821, 848, 851, 854, 855, 856, 866, 867, 868, 869, 940, 941, 942, 943, 945, 946, 953, 954, 956, 957, 958, 961, 978, 979, 980, 982, 983, 984, 996, 997, 1008, 1060, 1067], "excluded_lines": [], "start_line": 131, "executed_branches": [[140, 142], [140, 160], [145, 146], [152, 153], [152, 172], [155, 158], [160, 164], [160, 172], [168, 172], [172, 173], [172, 176], [176, 177], [176, 188], [203, 204], [268, 273], [276, 281], [281, 282], [283, 284], [285, 286], [291, 295], [295, 304], [305, 309], [309, 310], [437, 438], [437, 440], [443, 444], [443, 467], [446, 447], [446, 473], [448, 449], [448, 451], [453, 454], [453, 457], [454, 453], [454, 455], [507, 508], [507, 518], [508, 510], [508, 512], [512, 513], [518, 532], [532, 533], [532, 534], [534, 535], [534, 536], [536, 537], [536, 539], [544, 545], [545, 546], [545, 549], [565, 566], [565, 568], [572, 573], [572, 594], [575, 576], [576, 578], [578, 579], [578, 582], [582, 583], [594, 595], [594, 603], [631, 632], [638, 639], [650, 653], [654, -653], [668, 670], [668, 678], [678, 682], [678, 688], [698, 699], [698, 703], [699, 700], [699, 702], [703, 704], [709, 710], [709, 714], [714, 715], [714, 716], [716, 717], [716, 718], [718, 721], [726, 727], [726, 729], [753, 755], [753, 773], [784, 785], [784, 790], [786, 787], [790, 793], [805, 806], [805, 811], [807, 808], [812, 813], [830, 831], [839, 840], [839, 845], [841, 842], [847, 858], [858, 859], [858, 865], [860, 861], [865, 871], [877, 878], [877, 884], [879, 880], [917, 918], [925, 926], [925, 931], [927, 928], [936, 937], [936, 990], [939, 948], [948, 949], [964, 966], [970, 936], [970, 971], [975, 976], [987, 988], [1002, 1003], [1020, 1021], [1020, 1037], [1022, 1023], [1022, 1072], [1023, 1024], [1023, 1027], [1028, 1029], [1028, 1032], [1037, 1038], [1037, 1048], [1039, 1041], [1039, 1072], [1043, 1044], [1043, 1072], [1048, 1049], [1048, 1070], [1050, 1051], [1054, 1055], [1054, 1064], [1056, 1057], [1059, 1062], [1064, 1065]], "missing_branches": [[145, 148], [155, 156], [168, 169], [203, 206], [238, 239], [238, 240], [262, -255], [262, 263], [263, -255], [263, 264], [268, 271], [276, 279], [281, 291], [283, 285], [285, 287], [287, 281], [287, 289], [291, 292], [295, 317], [305, 306], [309, 314], [323, 324], [323, 326], [326, 327], [326, 329], [333, 335], [333, 338], [335, 336], [335, 342], [336, 337], [336, 342], [338, 339], [338, 342], [339, 340], [339, 342], [342, 343], [342, 371], [344, 345], [344, 348], [350, 351], [350, 353], [355, -331], [355, 357], [361, 362], [361, 365], [365, -331], [365, 367], [371, 372], [371, 374], [375, 376], [375, 377], [377, -331], [377, 378], [384, -380], [384, 385], [388, 389], [388, 420], [389, 390], [389, 392], [393, 394], [393, 396], [396, 397], [396, 416], [398, -387], [398, 403], [403, -387], [403, 404], [404, 403], [404, 405], [406, 403], [406, 407], [428, 429], [428, 431], [477, 478], [477, 480], [483, 484], [483, 496], [486, 487], [486, 502], [512, 516], [518, 521], [522, 523], [522, 524], [524, 525], [524, 530], [544, 552], [553, 554], [553, 560], [554, 555], [554, 556], [556, 557], [556, 558], [558, 553], [558, 559], [575, 610], [576, 586], [582, 584], [622, 623], [622, 626], [631, 634], [638, 647], [650, 651], [654, 655], [656, 657], [656, 661], [661, -653], [661, 662], [703, 705], [718, 719], [786, 788], [790, 791], [807, 809], [812, 820], [830, 832], [841, 843], [847, 848], [855, 856], [855, 858], [860, 862], [865, 866], [867, 868], [867, 869], [879, 881], [917, 919], [927, 929], [939, 940], [948, 956], [956, 957], [956, 961], [964, 936], [975, 990], [979, 980], [979, 990], [983, 984], [983, 990], [987, 990], [1002, 1008], [1050, 1054], [1056, 1072], [1059, 1060], [1064, 1067]]}, "": {"executed_lines": [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 14, 15, 16, 18, 43, 45, 47, 49, 69, 81, 84, 88, 89, 93, 96, 100, 108, 119, 126, 131, 133, 234, 237, 255, 267, 275, 319, 331, 380, 387, 422, 433, 475, 504, 543, 562, 617, 621, 629, 649, 667, 690, 696, 707, 723, 731, 778, 1016, 1074, 1075, 1084, 1086, 1087, 1088, 1091, 1093, 1094, 1095, 1096, 1097, 1098, 1099, 1100, 1101, 1102, 1103, 1104, 1105, 1108, 1109, 1110, 1111], "summary": {"covered_lines": 84, "num_statements": 86, "percent_covered": 95.74468085106383, "percent_covered_display": "96", "missing_lines": 2, "excluded_lines": 0, "percent_statements_covered": 97.67441860465117, "percent_statements_covered_display": "98", "num_branches": 8, "num_partial_branches": 2, "covered_branches": 6, "missing_branches": 2, "percent_branches_covered": 75.0, "percent_branches_covered_display": "75"}, "missing_lines": [1106, 1107], "excluded_lines": [], "start_line": 1, "executed_branches": [[1086, 1087], [1086, 1091], [1095, 1096], [1099, 1100], [1099, 1102], [1105, 1108]], "missing_branches": [[1095, 1111], [1105, 1106]]}}}, "custom_components/goecharger_api2/pygoecharger_ha/const.py": {"executed_lines": [1, 2, 6, 11, 12, 13, 14, 16, 17, 18, 19, 22, 23, 24, 25, 26, 29, 31, 32, 34, 37, 39, 40, 41, 46, 47, 48, 51, 52, 53, 56, 57, 58, 60, 61, 62, 63, 64, 65, 66, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 90, 111], "summary": {"covered_lines": 61, "num_statements": 61, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "executed_branches": [], "missing_branches": [], "functions": {"": {"executed_lines": [1, 2, 6, 11, 12, 13, 14, 16, 17, 18, 19, 22, 23, 24, 25, 26, 29, 31, 32, 34, 37, 39, 40, 41, 46, 47, 48, 51, 52, 53, 56, 57, 58, 60, 61, 62, 63, 64, 65, 66, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 90, 111], "summary": {"covered_lines": 61, "num_statements": 61, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 1, "executed_branches": [], "missing_branches": []}}, "classes": {"INTG_TYPE": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 0, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 56, "executed_branches": [], "missing_branches": []}, "CAR_VALUES": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 0, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 60, "executed_branches": [], "missing_branches": []}, "CT_VALUES": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 0, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 70, "executed_branches": [], "missing_branches": []}, "": {"executed_lines": [1, 2, 6, 11, 12, 13, 14, 16, 17, 18, 19, 22, 23, 24, 25, 26, 29, 31, 32, 34, 37, 39, 40, 41, 46, 47, 48, 51, 52, 53, 56, 57, 58, 60, 61, 62, 63, 64, 65, 66, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 90, 111], "summary": {"covered_lines": 61, "num_statements": 61, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 1, "executed_branches": [], "missing_branches": []}}}, "custom_components/goecharger_api2/pygoecharger_ha/keys.py": {"executed_lines": [1, 2, 3, 9, 11, 13, 14, 15, 16, 17, 20, 23, 24, 27, 29, 32, 36, 38, 40, 42, 44, 46, 48, 50, 52, 54, 56, 58, 60, 62, 64, 66, 68, 70, 72, 74, 76, 78, 80, 82, 84, 95, 98, 100, 102, 104, 107, 109, 111, 113, 115, 117, 119, 121, 123, 125, 127, 129, 131, 133, 135, 137, 139, 141, 143, 145, 147, 149, 151, 153, 155, 157, 159, 161, 163, 165, 167, 169, 171, 173, 175, 179, 181, 183, 185, 187, 189, 191, 193, 195, 197, 199, 201, 203, 205, 207, 209, 211, 213, 215, 217, 219, 221, 223, 225, 227, 229, 232, 234, 236, 238, 240, 242, 244, 246, 248, 250, 252, 254, 256, 258, 260, 262, 264, 266, 268, 270, 272, 274, 276, 278, 280, 282, 284, 286, 288, 290, 292, 294, 296, 298, 300, 302, 304, 306, 308, 310, 312, 314, 316, 318, 320, 322, 324, 326, 328, 330, 332, 334, 336, 338, 340, 342, 344, 346, 350, 353, 354, 355, 359, 361, 366, 372, 378, 384, 390, 396, 402, 409, 411, 413, 415, 426, 428, 430, 432, 434, 436, 438, 440, 442, 444, 446, 448, 450, 452, 454, 456, 458, 460, 462, 464, 466, 523, 525, 527, 529, 531, 533, 535, 537, 539, 541, 543, 545, 547, 549, 551, 560, 562, 564, 566, 568, 570, 664, 668, 670, 673, 689, 691, 693, 695, 697, 699, 701, 703, 705, 707, 737, 739, 743, 753, 767, 785, 787, 789, 791, 793, 805, 807, 809, 811, 813], "summary": {"covered_lines": 253, "num_statements": 255, "percent_covered": 99.2156862745098, "percent_covered_display": "99", "missing_lines": 2, "excluded_lines": 0, "percent_statements_covered": 99.2156862745098, "percent_statements_covered_display": "99", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [30, 33], "excluded_lines": [], "executed_branches": [], "missing_branches": [], "functions": {"Tag.__hash__": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 1, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 1, "excluded_lines": 0, "percent_statements_covered": 0.0, "percent_statements_covered_display": "0", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [30], "excluded_lines": [], "start_line": 29, "executed_branches": [], "missing_branches": []}, "Tag.__str__": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 1, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 1, "excluded_lines": 0, "percent_statements_covered": 0.0, "percent_statements_covered_display": "0", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [33], "excluded_lines": [], "start_line": 32, "executed_branches": [], "missing_branches": []}, "": {"executed_lines": [1, 2, 3, 9, 11, 13, 14, 15, 16, 17, 20, 23, 24, 27, 29, 32, 36, 38, 40, 42, 44, 46, 48, 50, 52, 54, 56, 58, 60, 62, 64, 66, 68, 70, 72, 74, 76, 78, 80, 82, 84, 95, 98, 100, 102, 104, 107, 109, 111, 113, 115, 117, 119, 121, 123, 125, 127, 129, 131, 133, 135, 137, 139, 141, 143, 145, 147, 149, 151, 153, 155, 157, 159, 161, 163, 165, 167, 169, 171, 173, 175, 179, 181, 183, 185, 187, 189, 191, 193, 195, 197, 199, 201, 203, 205, 207, 209, 211, 213, 215, 217, 219, 221, 223, 225, 227, 229, 232, 234, 236, 238, 240, 242, 244, 246, 248, 250, 252, 254, 256, 258, 260, 262, 264, 266, 268, 270, 272, 274, 276, 278, 280, 282, 284, 286, 288, 290, 292, 294, 296, 298, 300, 302, 304, 306, 308, 310, 312, 314, 316, 318, 320, 322, 324, 326, 328, 330, 332, 334, 336, 338, 340, 342, 344, 346, 350, 353, 354, 355, 359, 361, 366, 372, 378, 384, 390, 396, 402, 409, 411, 413, 415, 426, 428, 430, 432, 434, 436, 438, 440, 442, 444, 446, 448, 450, 452, 454, 456, 458, 460, 462, 464, 466, 523, 525, 527, 529, 531, 533, 535, 537, 539, 541, 543, 545, 547, 549, 551, 560, 562, 564, 566, 568, 570, 664, 668, 670, 673, 689, 691, 693, 695, 697, 699, 701, 703, 705, 707, 737, 739, 743, 753, 767, 785, 787, 789, 791, 793, 805, 807, 809, 811, 813], "summary": {"covered_lines": 253, "num_statements": 253, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 1, "executed_branches": [], "missing_branches": []}}, "classes": {"CAT": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 0, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 13, "executed_branches": [], "missing_branches": []}, "ApiKey": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 0, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 20, "executed_branches": [], "missing_branches": []}, "Tag": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 2, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 2, "excluded_lines": 0, "percent_statements_covered": 0.0, "percent_statements_covered_display": "0", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [30, 33], "excluded_lines": [], "start_line": 27, "executed_branches": [], "missing_branches": []}, "": {"executed_lines": [1, 2, 3, 9, 11, 13, 14, 15, 16, 17, 20, 23, 24, 27, 29, 32, 36, 38, 40, 42, 44, 46, 48, 50, 52, 54, 56, 58, 60, 62, 64, 66, 68, 70, 72, 74, 76, 78, 80, 82, 84, 95, 98, 100, 102, 104, 107, 109, 111, 113, 115, 117, 119, 121, 123, 125, 127, 129, 131, 133, 135, 137, 139, 141, 143, 145, 147, 149, 151, 153, 155, 157, 159, 161, 163, 165, 167, 169, 171, 173, 175, 179, 181, 183, 185, 187, 189, 191, 193, 195, 197, 199, 201, 203, 205, 207, 209, 211, 213, 215, 217, 219, 221, 223, 225, 227, 229, 232, 234, 236, 238, 240, 242, 244, 246, 248, 250, 252, 254, 256, 258, 260, 262, 264, 266, 268, 270, 272, 274, 276, 278, 280, 282, 284, 286, 288, 290, 292, 294, 296, 298, 300, 302, 304, 306, 308, 310, 312, 314, 316, 318, 320, 322, 324, 326, 328, 330, 332, 334, 336, 338, 340, 342, 344, 346, 350, 353, 354, 355, 359, 361, 366, 372, 378, 384, 390, 396, 402, 409, 411, 413, 415, 426, 428, 430, 432, 434, 436, 438, 440, 442, 444, 446, 448, 450, 452, 454, 456, 458, 460, 462, 464, 466, 523, 525, 527, 529, 531, 533, 535, 537, 539, 541, 543, 545, 547, 549, 551, 560, 562, 564, 566, 568, 570, 664, 668, 670, 673, 689, 691, 693, 695, 697, 699, 701, 703, 705, 707, 737, 739, 743, 753, 767, 785, 787, 789, 791, 793, 805, 807, 809, 811, 813], "summary": {"covered_lines": 253, "num_statements": 253, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 1, "executed_branches": [], "missing_branches": []}}}, "custom_components/goecharger_api2/select.py": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 100, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 100, "excluded_lines": 0, "percent_statements_covered": 0.0, "percent_statements_covered_display": "0", "num_branches": 56, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 56, "percent_branches_covered": 0.0, "percent_branches_covered_display": "0"}, "missing_lines": [1, 3, 4, 5, 6, 7, 9, 10, 12, 13, 14, 16, 19, 20, 21, 22, 24, 25, 26, 27, 28, 30, 31, 32, 34, 37, 38, 39, 40, 43, 59, 61, 62, 63, 64, 65, 66, 67, 68, 70, 71, 73, 74, 76, 79, 82, 83, 84, 85, 87, 89, 90, 92, 93, 95, 96, 97, 98, 99, 101, 102, 104, 105, 107, 108, 109, 111, 113, 116, 117, 118, 119, 120, 122, 124, 126, 127, 129, 130, 133, 135, 165, 167, 169, 172, 173, 175, 178, 179, 181, 184, 185, 187, 190, 191, 193, 196, 198, 199, 200], "excluded_lines": [], "executed_branches": [], "missing_branches": [[24, 25], [24, 30], [25, 26], [25, 34], [26, 25], [26, 27], [30, 31], [30, 34], [39, 40], [39, 59], [65, 66], [65, 79], [66, 67], [66, 73], [67, 68], [67, 70], [73, 74], [73, 76], [79, 82], [79, 89], [82, 83], [82, 84], [84, 85], [84, 87], [89, 90], [89, 92], [92, 93], [92, 99], [104, 105], [104, 129], [105, 107], [105, 126], [108, 109], [108, 111], [111, 113], [111, 122], [116, 117], [116, 122], [117, 118], [117, 119], [119, 116], [119, 120], [129, 130], [129, 133], [133, 135], [133, 198], [165, 167], [165, 169], [172, 173], [172, 175], [178, 179], [178, 181], [184, 185], [184, 187], [190, 191], [190, 193]], "functions": {"async_setup_entry": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 12, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 12, "excluded_lines": 0, "percent_statements_covered": 0.0, "percent_statements_covered_display": "0", "num_branches": 8, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 8, "percent_branches_covered": 0.0, "percent_branches_covered_display": "0"}, "missing_lines": [20, 21, 22, 24, 25, 26, 27, 28, 30, 31, 32, 34], "excluded_lines": [], "start_line": 19, "executed_branches": [], "missing_branches": [[24, 25], [24, 30], [25, 26], [25, 34], [26, 25], [26, 27], [30, 31], [30, 34]]}, "GoeChargerSelect.__init__": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 4, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 4, "excluded_lines": 0, "percent_statements_covered": 0.0, "percent_statements_covered_display": "0", "num_branches": 2, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 2, "percent_branches_covered": 0.0, "percent_branches_covered_display": "0"}, "missing_lines": [39, 40, 43, 59], "excluded_lines": [], "start_line": 38, "executed_branches": [], "missing_branches": [[39, 40], [39, 59]]}, "GoeChargerSelect.current_option": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 26, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 26, "excluded_lines": 0, "percent_statements_covered": 0.0, "percent_statements_covered_display": "0", "num_branches": 18, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 18, "percent_branches_covered": 0.0, "percent_branches_covered_display": "0"}, "missing_lines": [63, 64, 65, 66, 67, 68, 70, 71, 73, 74, 76, 79, 82, 83, 84, 85, 87, 89, 90, 92, 93, 95, 96, 97, 98, 99], "excluded_lines": [], "start_line": 62, "executed_branches": [], "missing_branches": [[65, 66], [65, 79], [66, 67], [66, 73], [67, 68], [67, 70], [73, 74], [73, 76], [79, 82], [79, 89], [82, 83], [82, 84], [84, 85], [84, 87], [89, 90], [89, 92], [92, 93], [92, 99]]}, "GoeChargerSelect.async_select_option": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 40, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 40, "excluded_lines": 0, "percent_statements_covered": 0.0, "percent_statements_covered_display": "0", "num_branches": 28, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 28, "percent_branches_covered": 0.0, "percent_branches_covered_display": "0"}, "missing_lines": [102, 104, 105, 107, 108, 109, 111, 113, 116, 117, 118, 119, 120, 122, 124, 126, 127, 129, 130, 133, 135, 165, 167, 169, 172, 173, 175, 178, 179, 181, 184, 185, 187, 190, 191, 193, 196, 198, 199, 200], "excluded_lines": [], "start_line": 101, "executed_branches": [], "missing_branches": [[104, 105], [104, 129], [105, 107], [105, 126], [108, 109], [108, 111], [111, 113], [111, 122], [116, 117], [116, 122], [117, 118], [117, 119], [119, 116], [119, 120], [129, 130], [129, 133], [133, 135], [133, 198], [165, 167], [165, 169], [172, 173], [172, 175], [178, 179], [178, 181], [184, 185], [184, 187], [190, 191], [190, 193]]}, "": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 18, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 18, "excluded_lines": 0, "percent_statements_covered": 0.0, "percent_statements_covered_display": "0", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [1, 3, 4, 5, 6, 7, 9, 10, 12, 13, 14, 16, 19, 37, 38, 61, 62, 101], "excluded_lines": [], "start_line": 1, "executed_branches": [], "missing_branches": []}}, "classes": {"GoeChargerSelect": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 70, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 70, "excluded_lines": 0, "percent_statements_covered": 0.0, "percent_statements_covered_display": "0", "num_branches": 48, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 48, "percent_branches_covered": 0.0, "percent_branches_covered_display": "0"}, "missing_lines": [39, 40, 43, 59, 63, 64, 65, 66, 67, 68, 70, 71, 73, 74, 76, 79, 82, 83, 84, 85, 87, 89, 90, 92, 93, 95, 96, 97, 98, 99, 102, 104, 105, 107, 108, 109, 111, 113, 116, 117, 118, 119, 120, 122, 124, 126, 127, 129, 130, 133, 135, 165, 167, 169, 172, 173, 175, 178, 179, 181, 184, 185, 187, 190, 191, 193, 196, 198, 199, 200], "excluded_lines": [], "start_line": 37, "executed_branches": [], "missing_branches": [[39, 40], [39, 59], [65, 66], [65, 79], [66, 67], [66, 73], [67, 68], [67, 70], [73, 74], [73, 76], [79, 82], [79, 89], [82, 83], [82, 84], [84, 85], [84, 87], [89, 90], [89, 92], [92, 93], [92, 99], [104, 105], [104, 129], [105, 107], [105, 126], [108, 109], [108, 111], [111, 113], [111, 122], [116, 117], [116, 122], [117, 118], [117, 119], [119, 116], [119, 120], [129, 130], [129, 133], [133, 135], [133, 198], [165, 167], [165, 169], [172, 173], [172, 175], [178, 179], [178, 181], [184, 185], [184, 187], [190, 191], [190, 193]]}, "": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 30, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 30, "excluded_lines": 0, "percent_statements_covered": 0.0, "percent_statements_covered_display": "0", "num_branches": 8, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 8, "percent_branches_covered": 0.0, "percent_branches_covered_display": "0"}, "missing_lines": [1, 3, 4, 5, 6, 7, 9, 10, 12, 13, 14, 16, 19, 20, 21, 22, 24, 25, 26, 27, 28, 30, 31, 32, 34, 37, 38, 61, 62, 101], "excluded_lines": [], "start_line": 1, "executed_branches": [], "missing_branches": [[24, 25], [24, 30], [25, 26], [25, 34], [26, 25], [26, 27], [30, 31], [30, 34]]}}}, "custom_components/goecharger_api2/sensor.py": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 109, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 109, "excluded_lines": 0, "percent_statements_covered": 0.0, "percent_statements_covered_display": "0", "num_branches": 60, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 60, "percent_branches_covered": 0.0, "percent_branches_covered_display": "0"}, "missing_lines": [1, 2, 3, 4, 6, 7, 8, 9, 10, 11, 13, 14, 15, 16, 18, 21, 22, 23, 24, 26, 27, 28, 29, 30, 32, 33, 34, 36, 38, 39, 40, 42, 44, 45, 47, 48, 49, 50, 51, 52, 54, 55, 57, 58, 59, 60, 61, 63, 65, 66, 67, 68, 69, 70, 71, 72, 77, 78, 81, 82, 83, 84, 85, 86, 88, 89, 91, 93, 94, 95, 96, 99, 100, 101, 102, 103, 105, 107, 109, 111, 112, 114, 115, 116, 118, 122, 125, 126, 127, 128, 130, 131, 133, 134, 135, 136, 137, 138, 139, 140, 141, 143, 144, 145, 147, 148, 149, 150, 153], "excluded_lines": [], "executed_branches": [], "missing_branches": [[26, 27], [26, 32], [27, 28], [27, 36], [28, 27], [28, 29], [32, 33], [32, 36], [57, 58], [57, 63], [59, 60], [59, 61], [69, 70], [69, 111], [70, 71], [70, 94], [77, 78], [77, 93], [81, 82], [81, 83], [83, 84], [83, 85], [85, 86], [85, 88], [88, 89], [88, 91], [94, 95], [94, 96], [96, 99], [96, 109], [100, 101], [100, 107], [102, 103], [102, 105], [111, 112], [111, 114], [114, 115], [114, 122], [115, 116], [115, 118], [122, 125], [122, 133], [125, 126], [125, 130], [127, 128], [127, 130], [130, 131], [130, 133], [133, 134], [133, 135], [135, 136], [135, 137], [137, 138], [137, 153], [138, 139], [138, 140], [140, 141], [140, 153], [144, 145], [144, 147]], "functions": {"async_setup_entry": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 12, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 12, "excluded_lines": 0, "percent_statements_covered": 0.0, "percent_statements_covered_display": "0", "num_branches": 8, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 8, "percent_branches_covered": 0.0, "percent_branches_covered_display": "0"}, "missing_lines": [22, 23, 24, 26, 27, 28, 29, 30, 32, 33, 34, 36], "excluded_lines": [], "start_line": 21, "executed_branches": [], "missing_branches": [[26, 27], [26, 32], [27, 28], [27, 36], [28, 27], [28, 29], [32, 33], [32, 36]]}, "GoeChargerSensor.__init__": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 1, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 1, "excluded_lines": 0, "percent_statements_covered": 0.0, "percent_statements_covered_display": "0", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [45], "excluded_lines": [], "start_line": 44, "executed_branches": [], "missing_branches": []}, "GoeChargerSensor._camel_to_snake": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 4, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 4, "excluded_lines": 0, "percent_statements_covered": 0.0, "percent_statements_covered_display": "0", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [49, 50, 51, 52], "excluded_lines": [], "start_line": 48, "executed_branches": [], "missing_branches": []}, "GoeChargerSensor.state": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 6, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 6, "excluded_lines": 0, "percent_statements_covered": 0.0, "percent_statements_covered_display": "0", "num_branches": 4, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 4, "percent_branches_covered": 0.0, "percent_branches_covered_display": "0"}, "missing_lines": [57, 58, 59, 60, 61, 63], "excluded_lines": [], "start_line": 55, "executed_branches": [], "missing_branches": [[57, 58], [57, 63], [59, 60], [59, 61]]}, "GoeChargerSensor.native_value": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 59, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 59, "excluded_lines": 0, "percent_statements_covered": 0.0, "percent_statements_covered_display": "0", "num_branches": 48, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 48, "percent_branches_covered": 0.0, "percent_branches_covered_display": "0"}, "missing_lines": [67, 68, 69, 70, 71, 72, 77, 78, 81, 82, 83, 84, 85, 86, 88, 89, 91, 93, 94, 95, 96, 99, 100, 101, 102, 103, 105, 107, 109, 111, 112, 114, 115, 116, 118, 122, 125, 126, 127, 128, 130, 131, 133, 134, 135, 136, 137, 138, 139, 140, 141, 143, 144, 145, 147, 148, 149, 150, 153], "excluded_lines": [], "start_line": 66, "executed_branches": [], "missing_branches": [[69, 70], [69, 111], [70, 71], [70, 94], [77, 78], [77, 93], [81, 82], [81, 83], [83, 84], [83, 85], [85, 86], [85, 88], [88, 89], [88, 91], [94, 95], [94, 96], [96, 99], [96, 109], [100, 101], [100, 107], [102, 103], [102, 105], [111, 112], [111, 114], [114, 115], [114, 122], [115, 116], [115, 118], [122, 125], [122, 133], [125, 126], [125, 130], [127, 128], [127, 130], [130, 131], [130, 133], [133, 134], [133, 135], [135, 136], [135, 137], [137, 138], [137, 153], [138, 139], [138, 140], [140, 141], [140, 153], [144, 145], [144, 147]]}, "": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 27, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 27, "excluded_lines": 0, "percent_statements_covered": 0.0, "percent_statements_covered_display": "0", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [1, 2, 3, 4, 6, 7, 8, 9, 10, 11, 13, 14, 15, 16, 18, 21, 38, 39, 40, 42, 44, 47, 48, 54, 55, 65, 66], "excluded_lines": [], "start_line": 1, "executed_branches": [], "missing_branches": []}}, "classes": {"GoeChargerSensor": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 70, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 70, "excluded_lines": 0, "percent_statements_covered": 0.0, "percent_statements_covered_display": "0", "num_branches": 52, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 52, "percent_branches_covered": 0.0, "percent_branches_covered_display": "0"}, "missing_lines": [45, 49, 50, 51, 52, 57, 58, 59, 60, 61, 63, 67, 68, 69, 70, 71, 72, 77, 78, 81, 82, 83, 84, 85, 86, 88, 89, 91, 93, 94, 95, 96, 99, 100, 101, 102, 103, 105, 107, 109, 111, 112, 114, 115, 116, 118, 122, 125, 126, 127, 128, 130, 131, 133, 134, 135, 136, 137, 138, 139, 140, 141, 143, 144, 145, 147, 148, 149, 150, 153], "excluded_lines": [], "start_line": 42, "executed_branches": [], "missing_branches": [[57, 58], [57, 63], [59, 60], [59, 61], [69, 70], [69, 111], [70, 71], [70, 94], [77, 78], [77, 93], [81, 82], [81, 83], [83, 84], [83, 85], [85, 86], [85, 88], [88, 89], [88, 91], [94, 95], [94, 96], [96, 99], [96, 109], [100, 101], [100, 107], [102, 103], [102, 105], [111, 112], [111, 114], [114, 115], [114, 122], [115, 116], [115, 118], [122, 125], [122, 133], [125, 126], [125, 130], [127, 128], [127, 130], [130, 131], [130, 133], [133, 134], [133, 135], [135, 136], [135, 137], [137, 138], [137, 153], [138, 139], [138, 140], [140, 141], [140, 153], [144, 145], [144, 147]]}, "": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 39, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 39, "excluded_lines": 0, "percent_statements_covered": 0.0, "percent_statements_covered_display": "0", "num_branches": 8, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 8, "percent_branches_covered": 0.0, "percent_branches_covered_display": "0"}, "missing_lines": [1, 2, 3, 4, 6, 7, 8, 9, 10, 11, 13, 14, 15, 16, 18, 21, 22, 23, 24, 26, 27, 28, 29, 30, 32, 33, 34, 36, 38, 39, 40, 42, 44, 47, 48, 54, 55, 65, 66], "excluded_lines": [], "start_line": 1, "executed_branches": [], "missing_branches": [[26, 27], [26, 32], [27, 28], [27, 36], [28, 27], [28, 29], [32, 33], [32, 36]]}}}, "custom_components/goecharger_api2/service.py": {"executed_lines": [1, 2, 3, 4, 6, 8, 9, 10, 11, 13, 14, 15, 17, 19, 21, 23, 25, 26, 30, 32, 33, 35, 37, 38, 39, 41, 42, 44, 45, 49, 50, 51, 53, 55, 56, 57, 60, 71, 72, 73, 74, 75, 76, 77, 78, 80, 85, 86, 87, 88, 89, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 115, 117, 118, 119, 132, 133, 134, 140, 142, 143, 144, 147, 149, 150, 151, 159], "summary": {"covered_lines": 79, "num_statements": 104, "percent_covered": 66.43835616438356, "percent_covered_display": "66", "missing_lines": 25, "excluded_lines": 0, "percent_statements_covered": 75.96153846153847, "percent_statements_covered_display": "76", "num_branches": 42, "num_partial_branches": 10, "covered_branches": 18, "missing_branches": 24, "percent_branches_covered": 42.857142857142854, "percent_branches_covered_display": "43"}, "missing_lines": [24, 27, 28, 62, 63, 64, 65, 66, 67, 69, 95, 96, 97, 99, 100, 126, 128, 129, 130, 137, 138, 153, 154, 156, 157], "excluded_lines": [], "executed_branches": [[23, 25], [37, -35], [37, 38], [60, 71], [74, 75], [75, 76], [75, 77], [77, 78], [77, 80], [88, 89], [103, 104], [110, 111], [118, 119], [133, 134], [143, 144], [143, 147], [149, 150], [150, 151]], "missing_branches": [[23, 24], [60, 62], [63, 64], [63, 71], [64, 65], [64, 71], [66, 67], [66, 69], [74, 99], [88, 99], [96, 97], [96, 99], [99, -55], [99, 100], [103, 137], [110, 126], [118, -102], [129, -102], [129, 130], [133, -102], [137, -102], [137, 138], [149, 156], [150, 153]], "functions": {"_is_finite_number": {"executed_lines": [23, 25, 26], "summary": {"covered_lines": 3, "num_statements": 6, "percent_covered": 50.0, "percent_covered_display": "50", "missing_lines": 3, "excluded_lines": 0, "percent_statements_covered": 50.0, "percent_statements_covered_display": "50", "num_branches": 2, "num_partial_branches": 1, "covered_branches": 1, "missing_branches": 1, "percent_branches_covered": 50.0, "percent_branches_covered_display": "50"}, "missing_lines": [24, 27, 28], "excluded_lines": [], "start_line": 21, "executed_branches": [[23, 25]], "missing_branches": [[23, 24]]}, "add_config_entry_to_service": {"executed_lines": [32, 33], "summary": {"covered_lines": 2, "num_statements": 2, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 30, "executed_branches": [], "missing_branches": []}, "remove_config_entry_from_service": {"executed_lines": [37, 38, 39], "summary": {"covered_lines": 3, "num_statements": 3, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 2, "num_partial_branches": 0, "covered_branches": 2, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 35, "executed_branches": [[37, -35], [37, 38]], "missing_branches": []}, "GoeChargerApiV2Service.__init__": {"executed_lines": [44, 45, 49, 50, 51, 53], "summary": {"covered_lines": 6, "num_statements": 6, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 42, "executed_branches": [], "missing_branches": []}, "GoeChargerApiV2Service.set_pv_data": {"executed_lines": [56, 57, 60, 71, 72, 73, 74, 75, 76, 77, 78, 80, 85, 86, 87, 88, 89], "summary": {"covered_lines": 17, "num_statements": 29, "percent_covered": 48.97959183673469, "percent_covered_display": "49", "missing_lines": 12, "excluded_lines": 0, "percent_statements_covered": 58.62068965517241, "percent_statements_covered_display": "59", "num_branches": 20, "num_partial_branches": 3, "covered_branches": 7, "missing_branches": 13, "percent_branches_covered": 35.0, "percent_branches_covered_display": "35"}, "missing_lines": [62, 63, 64, 65, 66, 67, 69, 95, 96, 97, 99, 100], "excluded_lines": [], "start_line": 55, "executed_branches": [[60, 71], [74, 75], [75, 76], [75, 77], [77, 78], [77, 80], [88, 89]], "missing_branches": [[60, 62], [63, 64], [63, 71], [64, 65], [64, 71], [66, 67], [66, 69], [74, 99], [88, 99], [96, 97], [96, 99], [99, -55], [99, 100]]}, "GoeChargerApiV2Service.stop_charging": {"executed_lines": [103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 115, 117, 118, 119, 132, 133, 134], "summary": {"covered_lines": 18, "num_statements": 24, "percent_covered": 61.111111111111114, "percent_covered_display": "61", "missing_lines": 6, "excluded_lines": 0, "percent_statements_covered": 75.0, "percent_statements_covered_display": "75", "num_branches": 12, "num_partial_branches": 4, "covered_branches": 4, "missing_branches": 8, "percent_branches_covered": 33.333333333333336, "percent_branches_covered_display": "33"}, "missing_lines": [126, 128, 129, 130, 137, 138], "excluded_lines": [], "start_line": 102, "executed_branches": [[103, 104], [110, 111], [118, 119], [133, 134]], "missing_branches": [[103, 137], [110, 126], [118, -102], [129, -102], [129, 130], [133, -102], [137, -102], [137, 138]]}, "GoeChargerApiV2Service.get_coordinator": {"executed_lines": [142, 143, 144, 147, 149, 150, 151, 159], "summary": {"covered_lines": 8, "num_statements": 12, "percent_covered": 66.66666666666667, "percent_covered_display": "67", "missing_lines": 4, "excluded_lines": 0, "percent_statements_covered": 66.66666666666667, "percent_statements_covered_display": "67", "num_branches": 6, "num_partial_branches": 2, "covered_branches": 4, "missing_branches": 2, "percent_branches_covered": 66.66666666666667, "percent_branches_covered_display": "67"}, "missing_lines": [153, 154, 156, 157], "excluded_lines": [], "start_line": 140, "executed_branches": [[143, 144], [143, 147], [149, 150], [150, 151]], "missing_branches": [[149, 156], [150, 153]]}, "": {"executed_lines": [1, 2, 3, 4, 6, 8, 9, 10, 11, 13, 14, 15, 17, 19, 21, 30, 35, 41, 42, 55, 102, 140], "summary": {"covered_lines": 22, "num_statements": 22, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 1, "executed_branches": [], "missing_branches": []}}, "classes": {"GoeChargerApiV2Service": {"executed_lines": [44, 45, 49, 50, 51, 53, 56, 57, 60, 71, 72, 73, 74, 75, 76, 77, 78, 80, 85, 86, 87, 88, 89, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 115, 117, 118, 119, 132, 133, 134, 142, 143, 144, 147, 149, 150, 151, 159], "summary": {"covered_lines": 49, "num_statements": 71, "percent_covered": 58.71559633027523, "percent_covered_display": "59", "missing_lines": 22, "excluded_lines": 0, "percent_statements_covered": 69.01408450704226, "percent_statements_covered_display": "69", "num_branches": 38, "num_partial_branches": 9, "covered_branches": 15, "missing_branches": 23, "percent_branches_covered": 39.473684210526315, "percent_branches_covered_display": "39"}, "missing_lines": [62, 63, 64, 65, 66, 67, 69, 95, 96, 97, 99, 100, 126, 128, 129, 130, 137, 138, 153, 154, 156, 157], "excluded_lines": [], "start_line": 41, "executed_branches": [[60, 71], [74, 75], [75, 76], [75, 77], [77, 78], [77, 80], [88, 89], [103, 104], [110, 111], [118, 119], [133, 134], [143, 144], [143, 147], [149, 150], [150, 151]], "missing_branches": [[60, 62], [63, 64], [63, 71], [64, 65], [64, 71], [66, 67], [66, 69], [74, 99], [88, 99], [96, 97], [96, 99], [99, -55], [99, 100], [103, 137], [110, 126], [118, -102], [129, -102], [129, 130], [133, -102], [137, -102], [137, 138], [149, 156], [150, 153]]}, "": {"executed_lines": [1, 2, 3, 4, 6, 8, 9, 10, 11, 13, 14, 15, 17, 19, 21, 23, 25, 26, 30, 32, 33, 35, 37, 38, 39, 41, 42, 55, 102, 140], "summary": {"covered_lines": 30, "num_statements": 33, "percent_covered": 89.1891891891892, "percent_covered_display": "89", "missing_lines": 3, "excluded_lines": 0, "percent_statements_covered": 90.9090909090909, "percent_statements_covered_display": "91", "num_branches": 4, "num_partial_branches": 1, "covered_branches": 3, "missing_branches": 1, "percent_branches_covered": 75.0, "percent_branches_covered_display": "75"}, "missing_lines": [24, 27, 28], "excluded_lines": [], "start_line": 1, "executed_branches": [[23, 25], [37, -35], [37, 38]], "missing_branches": [[23, 24]]}}}, "custom_components/goecharger_api2/switch.py": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 101, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 101, "excluded_lines": 0, "percent_statements_covered": 0.0, "percent_statements_covered_display": "0", "num_branches": 48, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 48, "percent_branches_covered": 0.0, "percent_branches_covered_display": "0"}, "missing_lines": [1, 2, 4, 5, 6, 7, 8, 10, 11, 12, 14, 17, 18, 19, 20, 22, 23, 24, 25, 26, 28, 29, 30, 32, 35, 36, 37, 38, 40, 42, 43, 44, 46, 47, 48, 50, 52, 53, 55, 56, 58, 60, 61, 63, 64, 66, 68, 69, 70, 72, 73, 74, 76, 78, 79, 81, 82, 84, 86, 87, 88, 89, 91, 92, 93, 94, 95, 96, 97, 98, 100, 102, 103, 104, 105, 107, 108, 109, 110, 111, 113, 114, 115, 116, 118, 119, 121, 122, 124, 126, 128, 130, 131, 133, 134, 135, 137, 138, 140, 141, 143], "excluded_lines": [], "executed_branches": [], "missing_branches": [[22, 23], [22, 28], [23, 24], [23, 32], [24, 23], [24, 25], [28, 29], [28, 32], [43, 44], [43, 55], [44, 46], [44, 52], [47, 48], [47, 50], [55, 56], [55, 58], [69, 70], [69, 81], [70, 72], [70, 78], [73, 74], [73, 76], [81, 82], [81, 84], [95, 96], [95, 113], [96, 97], [96, 102], [97, 98], [97, 100], [102, 103], [102, 104], [104, 105], [104, 113], [113, 114], [113, 128], [114, 115], [114, 119], [115, 116], [115, 118], [119, 121], [119, 126], [121, 122], [121, 124], [133, 134], [133, 135], [140, 141], [140, 143]], "functions": {"async_setup_entry": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 12, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 12, "excluded_lines": 0, "percent_statements_covered": 0.0, "percent_statements_covered_display": "0", "num_branches": 8, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 8, "percent_branches_covered": 0.0, "percent_branches_covered_display": "0"}, "missing_lines": [18, 19, 20, 22, 23, 24, 25, 26, 28, 29, 30, 32], "excluded_lines": [], "start_line": 17, "executed_branches": [], "missing_branches": [[22, 23], [22, 28], [23, 24], [23, 32], [24, 23], [24, 25], [28, 29], [28, 32]]}, "GoeChargerSwitch.__init__": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 2, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 2, "excluded_lines": 0, "percent_statements_covered": 0.0, "percent_statements_covered_display": "0", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [37, 38], "excluded_lines": [], "start_line": 36, "executed_branches": [], "missing_branches": []}, "GoeChargerSwitch.async_turn_on": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 16, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 16, "excluded_lines": 0, "percent_statements_covered": 0.0, "percent_statements_covered_display": "0", "num_branches": 8, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 8, "percent_branches_covered": 0.0, "percent_branches_covered_display": "0"}, "missing_lines": [42, 43, 44, 46, 47, 48, 50, 52, 53, 55, 56, 58, 60, 61, 63, 64], "excluded_lines": [], "start_line": 40, "executed_branches": [], "missing_branches": [[43, 44], [43, 55], [44, 46], [44, 52], [47, 48], [47, 50], [55, 56], [55, 58]]}, "GoeChargerSwitch.async_turn_off": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 16, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 16, "excluded_lines": 0, "percent_statements_covered": 0.0, "percent_statements_covered_display": "0", "num_branches": 8, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 8, "percent_branches_covered": 0.0, "percent_branches_covered_display": "0"}, "missing_lines": [68, 69, 70, 72, 73, 74, 76, 78, 79, 81, 82, 84, 86, 87, 88, 89], "excluded_lines": [], "start_line": 66, "executed_branches": [], "missing_branches": [[69, 70], [69, 81], [70, 72], [70, 78], [73, 74], [73, 76], [81, 82], [81, 84]]}, "GoeChargerSwitch.is_on": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 27, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 27, "excluded_lines": 0, "percent_statements_covered": 0.0, "percent_statements_covered_display": "0", "num_branches": 20, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 20, "percent_branches_covered": 0.0, "percent_branches_covered_display": "0"}, "missing_lines": [93, 94, 95, 96, 97, 98, 100, 102, 103, 104, 105, 107, 108, 109, 110, 111, 113, 114, 115, 116, 118, 119, 121, 122, 124, 126, 128], "excluded_lines": [], "start_line": 92, "executed_branches": [], "missing_branches": [[95, 96], [95, 113], [96, 97], [96, 102], [97, 98], [97, 100], [102, 103], [102, 104], [104, 105], [104, 113], [113, 114], [113, 128], [114, 115], [114, 119], [115, 116], [115, 118], [119, 121], [119, 126], [121, 122], [121, 124]]}, "GoeChargerSwitch.state": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 3, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 3, "excluded_lines": 0, "percent_statements_covered": 0.0, "percent_statements_covered_display": "0", "num_branches": 2, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 2, "percent_branches_covered": 0.0, "percent_branches_covered_display": "0"}, "missing_lines": [133, 134, 135], "excluded_lines": [], "start_line": 131, "executed_branches": [], "missing_branches": [[133, 134], [133, 135]]}, "GoeChargerSwitch.icon": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 3, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 3, "excluded_lines": 0, "percent_statements_covered": 0.0, "percent_statements_covered_display": "0", "num_branches": 2, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 2, "percent_branches_covered": 0.0, "percent_branches_covered_display": "0"}, "missing_lines": [140, 141, 143], "excluded_lines": [], "start_line": 138, "executed_branches": [], "missing_branches": [[140, 141], [140, 143]]}, "": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 22, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 22, "excluded_lines": 0, "percent_statements_covered": 0.0, "percent_statements_covered_display": "0", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [1, 2, 4, 5, 6, 7, 8, 10, 11, 12, 14, 17, 35, 36, 40, 66, 91, 92, 130, 131, 137, 138], "excluded_lines": [], "start_line": 1, "executed_branches": [], "missing_branches": []}}, "classes": {"GoeChargerSwitch": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 67, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 67, "excluded_lines": 0, "percent_statements_covered": 0.0, "percent_statements_covered_display": "0", "num_branches": 40, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 40, "percent_branches_covered": 0.0, "percent_branches_covered_display": "0"}, "missing_lines": [37, 38, 42, 43, 44, 46, 47, 48, 50, 52, 53, 55, 56, 58, 60, 61, 63, 64, 68, 69, 70, 72, 73, 74, 76, 78, 79, 81, 82, 84, 86, 87, 88, 89, 93, 94, 95, 96, 97, 98, 100, 102, 103, 104, 105, 107, 108, 109, 110, 111, 113, 114, 115, 116, 118, 119, 121, 122, 124, 126, 128, 133, 134, 135, 140, 141, 143], "excluded_lines": [], "start_line": 35, "executed_branches": [], "missing_branches": [[43, 44], [43, 55], [44, 46], [44, 52], [47, 48], [47, 50], [55, 56], [55, 58], [69, 70], [69, 81], [70, 72], [70, 78], [73, 74], [73, 76], [81, 82], [81, 84], [95, 96], [95, 113], [96, 97], [96, 102], [97, 98], [97, 100], [102, 103], [102, 104], [104, 105], [104, 113], [113, 114], [113, 128], [114, 115], [114, 119], [115, 116], [115, 118], [119, 121], [119, 126], [121, 122], [121, 124], [133, 134], [133, 135], [140, 141], [140, 143]]}, "": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 34, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 34, "excluded_lines": 0, "percent_statements_covered": 0.0, "percent_statements_covered_display": "0", "num_branches": 8, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 8, "percent_branches_covered": 0.0, "percent_branches_covered_display": "0"}, "missing_lines": [1, 2, 4, 5, 6, 7, 8, 10, 11, 12, 14, 17, 18, 19, 20, 22, 23, 24, 25, 26, 28, 29, 30, 32, 35, 36, 40, 66, 91, 92, 130, 131, 137, 138], "excluded_lines": [], "start_line": 1, "executed_branches": [], "missing_branches": [[22, 23], [22, 28], [23, 24], [23, 32], [24, 23], [24, 25], [28, 29], [28, 32]]}}}}, "totals": {"covered_lines": 1397, "num_statements": 2529, "percent_covered": 48.97131266299623, "percent_covered_display": "49", "missing_lines": 1132, "excluded_lines": 0, "percent_statements_covered": 55.23922499011467, "percent_statements_covered_display": "55", "num_branches": 922, "num_partial_branches": 105, "covered_branches": 293, "missing_branches": 629, "percent_branches_covered": 31.77874186550976, "percent_branches_covered_display": "32"}} \ No newline at end of file diff --git a/tests/reports/2026-10-29/coverage-blocking.json b/tests/reports/2026-10-29/coverage-blocking.json deleted file mode 100644 index 179221e..0000000 --- a/tests/reports/2026-10-29/coverage-blocking.json +++ /dev/null @@ -1 +0,0 @@ -{"meta": {"format": 3, "version": "7.16.2", "timestamp": "2026-10-29T11:48:45.708953", "branch_coverage": true, "show_contexts": false}, "files": {"custom_components/goecharger_api2/__init__.py": {"executed_lines": [1, 2, 3, 4, 5, 6, 7, 9, 10, 11, 12, 23, 24, 25, 30, 31, 32, 33, 34, 35, 36, 38, 45, 46, 60, 61, 63, 65, 66, 67, 68, 69, 70, 71, 73, 74, 75, 76, 77, 78, 81, 82, 85, 86, 87, 88, 89, 90, 91, 92, 93, 97, 98, 100, 103, 105, 108, 109, 110, 111, 112, 113, 116, 117, 118, 120, 122, 123, 124, 125, 126, 127, 128, 129, 131, 132, 133, 135, 136, 137, 138, 140, 141, 144, 145, 148, 149, 150, 151, 153, 154, 155, 156, 158, 162, 165, 166, 173, 175, 176, 177, 179, 180, 182, 184, 187, 188, 190, 191, 192, 193, 194, 195, 196, 197, 198, 201, 202, 203, 204, 206, 209, 210, 211, 214, 215, 216, 217, 218, 219, 220, 221, 224, 225, 226, 227, 228, 230, 231, 232, 233, 234, 236, 238, 240, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 254, 255, 257, 258, 259, 260, 261, 262, 269, 270, 271, 272, 273, 275, 276, 278, 279, 280, 281, 283, 284, 285, 286, 287, 288, 289, 290, 291, 294, 296, 297, 298, 300, 301, 302, 303, 304, 306, 307, 310, 311, 312, 314, 315, 316, 325, 326, 336, 337, 339, 340, 341, 343, 346, 347, 348, 349, 350, 351, 352, 353, 354, 356, 358, 359, 360, 361, 362, 363, 368, 371, 374, 375, 377, 378, 379, 380, 381, 382, 383, 384, 386, 387, 388, 391, 392, 393, 394, 396, 398, 403, 404, 406, 410, 411, 412, 413, 416, 417, 418, 419, 423, 424, 425, 428, 431, 432, 433, 435, 437, 438, 442, 445, 446, 448, 452, 453, 454, 456, 457, 458, 460, 463, 464, 465, 468, 470, 471, 472, 473, 474, 475, 476, 478, 479, 481, 482, 484, 486, 487, 493, 494, 495, 496, 498, 499, 500, 501, 502, 503, 504, 505, 506, 508, 509, 510, 511, 512, 513, 514, 515, 516, 518, 519, 520, 521, 524, 525, 526, 528, 529, 530, 531, 532, 533, 534, 535, 536, 537, 538, 540, 542, 544, 545, 546, 547, 549, 550, 551, 553, 554, 555, 557, 558, 559, 561, 562, 565, 567, 569, 570, 572, 574, 575, 577, 578, 579, 580, 582, 583, 584, 586, 587, 588, 591, 595, 596, 597, 598, 599, 600, 601, 602, 604, 606, 608, 609, 613, 614, 615, 616, 617, 618, 619, 621, 622, 623, 624, 625, 631, 633, 634, 635, 636, 638, 639, 640, 642, 643, 644, 645, 646, 647, 648, 649, 651, 652, 653, 654, 656, 657, 658, 659, 660, 661, 663, 664, 665, 666, 667, 668, 669, 670, 672, 673, 677, 678, 679, 684, 685, 686, 688, 692, 693, 694, 696, 700, 701, 702, 703, 704, 707, 710, 711, 712, 713, 714, 715, 716, 719, 720, 721, 723, 725, 727, 729, 730, 731, 732, 733, 734, 736, 737, 738, 739, 740, 742, 743, 745, 750, 751, 752, 753, 754, 755, 756, 757, 758, 759, 760, 761, 763, 768, 769, 771, 772, 773, 774, 776, 778, 779, 780, 783, 784, 785, 787, 788, 789, 791, 792, 794, 797, 799, 800, 801, 803, 805, 806, 808, 811, 812, 813, 814, 815, 817, 819, 834, 836, 837, 838, 839, 840, 842, 843, 845, 846, 848, 849, 852, 855, 858, 859, 861, 863, 864, 866, 867, 870, 873, 874, 875, 877, 878, 879, 880, 881, 882, 883, 885, 886, 887, 888, 889, 890, 892, 894, 895, 897, 898, 900, 907, 911, 912, 915, 916, 920, 921, 923, 925], "summary": {"covered_lines": 575, "num_statements": 604, "percent_covered": 93.33333333333333, "percent_covered_display": "93", "missing_lines": 29, "excluded_lines": 0, "percent_statements_covered": 95.19867549668874, "percent_statements_covered_display": "95", "num_branches": 266, "num_partial_branches": 19, "covered_branches": 237, "missing_branches": 29, "percent_branches_covered": 89.09774436090225, "percent_branches_covered_display": "89"}, "missing_lines": [80, 94, 95, 364, 390, 449, 610, 611, 637, 674, 675, 680, 681, 682, 909, 913, 918, 931, 932, 933, 935, 936, 938, 939, 940, 943, 944, 945, 947], "excluded_lines": [], "executed_branches": [[74, 75], [74, 85], [75, 76], [77, 78], [85, 86], [85, 100], [89, 90], [91, 92], [91, 97], [109, 110], [109, 116], [117, 118], [117, 120], [124, 125], [124, 135], [127, 128], [127, 144], [137, 138], [137, 140], [140, 141], [140, 144], [148, 149], [148, 165], [149, 150], [149, 151], [153, 154], [153, 162], [165, 166], [165, 173], [173, 175], [173, 182], [175, 176], [175, 179], [190, 191], [190, 206], [191, 192], [191, 201], [201, 202], [201, 206], [218, 219], [220, 221], [225, 226], [225, 242], [226, 225], [226, 227], [227, 225], [227, 228], [228, 225], [228, 230], [230, 231], [230, 240], [231, 232], [231, 238], [232, 233], [232, 236], [242, -214], [242, 243], [246, 247], [246, 257], [247, 248], [247, 254], [249, 250], [249, 251], [254, 246], [254, 255], [260, 261], [260, 283], [262, 269], [262, 273], [269, 260], [269, 270], [271, 272], [273, 260], [273, 275], [276, 260], [276, 278], [278, 276], [278, 279], [280, 281], [284, -214], [284, 285], [285, 286], [285, 288], [311, 312], [311, 314], [314, 315], [314, 325], [340, 341], [340, 343], [353, 354], [353, 356], [363, 368], [379, -377], [379, 380], [380, 381], [382, 383], [382, 386], [386, 387], [387, 388], [391, -377], [391, 392], [393, 394], [393, 396], [411, 412], [411, 416], [417, 418], [417, 423], [432, 433], [432, 435], [448, 452], [463, 464], [463, 468], [470, 471], [470, 478], [472, 473], [472, 478], [494, -493], [494, 495], [499, -498], [499, 500], [509, 510], [509, 518], [514, 515], [519, -508], [519, 520], [532, 533], [550, -549], [550, 551], [562, 565], [562, 567], [569, 570], [569, 572], [575, 577], [575, 586], [577, 578], [577, 582], [582, 583], [582, 586], [586, -561], [586, 587], [587, -561], [587, 588], [597, 598], [597, 604], [599, 600], [609, 613], [613, 614], [613, 617], [617, 618], [617, 621], [623, 624], [643, 644], [643, 647], [647, 648], [647, 651], [664, 665], [664, 668], [668, 669], [668, 672], [685, 686], [685, 688], [688, 692], [688, 700], [692, 693], [692, 696], [700, 701], [700, 707], [702, 703], [702, 707], [711, 712], [711, 715], [713, 714], [715, 716], [715, 719], [720, 721], [720, 723], [727, 729], [727, 797], [730, 731], [730, 736], [731, 732], [731, 742], [732, 733], [732, 734], [736, 737], [736, 742], [738, 739], [738, 740], [742, 743], [742, 745], [752, 753], [752, 778], [754, 755], [754, 776], [758, 759], [758, 763], [773, 774], [773, 778], [778, 779], [778, 783], [783, 784], [783, 787], [791, 792], [791, 794], [800, 801], [800, 803], [805, 806], [805, 808], [811, 812], [811, 817], [813, 814], [813, 819], [843, 845], [843, 852], [845, 846], [845, 848], [848, 849], [848, 861], [852, 855], [852, 858], [858, 859], [858, 861], [873, 874], [873, 885], [874, 875], [874, 877], [877, 878], [877, 881], [881, 882], [885, 886], [885, 887], [887, 888], [887, 889], [889, 890], [889, 892], [897, 898], [897, 900]], "missing_branches": [[75, 85], [77, 80], [89, 97], [218, -214], [220, -214], [271, 260], [280, 276], [363, 364], [380, -377], [386, -377], [387, 390], [448, 449], [514, -508], [532, 534], [599, -591], [609, 610], [623, 631], [713, 715], [881, 894], [932, 933], [932, 935], [935, 936], [935, 938], [939, 940], [939, 943], [943, 944], [943, 947], [944, 945], [944, 947]], "functions": {"async_migrate_entry": {"executed_lines": [74, 75, 76, 77, 78, 81, 82, 85, 86, 87, 88, 89, 90, 91, 92, 93, 97, 98, 100], "summary": {"covered_lines": 19, "num_statements": 22, "percent_covered": 82.3529411764706, "percent_covered_display": "82", "missing_lines": 3, "excluded_lines": 0, "percent_statements_covered": 86.36363636363636, "percent_statements_covered_display": "86", "num_branches": 12, "num_partial_branches": 3, "covered_branches": 9, "missing_branches": 3, "percent_branches_covered": 75.0, "percent_branches_covered_display": "75"}, "missing_lines": [80, 94, 95], "excluded_lines": [], "start_line": 73, "executed_branches": [[74, 75], [74, 85], [75, 76], [77, 78], [85, 86], [85, 100], [89, 90], [91, 92], [91, 97]], "missing_branches": [[75, 85], [77, 80], [89, 97]]}, "async_setup": {"executed_lines": [105], "summary": {"covered_lines": 1, "num_statements": 1, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 103, "executed_branches": [], "missing_branches": []}, "async_setup_entry": {"executed_lines": [109, 110, 111, 112, 113, 116, 117, 118, 120, 122, 123, 124, 125, 126, 127, 128, 129, 131, 132, 133, 135, 136, 137, 138, 140, 141, 144, 145, 148, 149, 150, 151, 153, 154, 155, 156, 158, 162, 165, 166, 173, 175, 176, 177, 179, 180, 182, 184], "summary": {"covered_lines": 48, "num_statements": 48, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 24, "num_partial_branches": 0, "covered_branches": 24, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 108, "executed_branches": [[109, 110], [109, 116], [117, 118], [117, 120], [124, 125], [124, 135], [127, 128], [127, 144], [137, 138], [137, 140], [140, 141], [140, 144], [148, 149], [148, 165], [149, 150], [149, 151], [153, 154], [153, 162], [165, 166], [165, 173], [173, 175], [173, 182], [175, 176], [175, 179]], "missing_branches": []}, "async_unload_entry": {"executed_lines": [188, 190, 191, 192, 193, 194, 195, 196, 197, 198, 201, 202, 203, 204, 206], "summary": {"covered_lines": 15, "num_statements": 15, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 6, "num_partial_branches": 0, "covered_branches": 6, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 187, "executed_branches": [[190, 191], [190, 206], [191, 192], [191, 201], [201, 202], [201, 206]], "missing_branches": []}, "entry_update_listener": {"executed_lines": [210, 211], "summary": {"covered_lines": 2, "num_statements": 2, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 209, "executed_branches": [], "missing_branches": []}, "check_and_write_to_16a": {"executed_lines": [216, 217, 218, 219, 220, 221, 224, 225, 226, 227, 228, 230, 231, 232, 233, 234, 236, 238, 240, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 254, 255, 257, 258, 259, 260, 261, 262, 269, 270, 271, 272, 273, 275, 276, 278, 279, 280, 281, 283, 284, 285, 286, 287, 288, 289, 290, 291], "summary": {"covered_lines": 58, "num_statements": 58, "percent_covered": 96.22641509433963, "percent_covered_display": "96", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 48, "num_partial_branches": 4, "covered_branches": 44, "missing_branches": 4, "percent_branches_covered": 91.66666666666667, "percent_branches_covered_display": "92"}, "missing_lines": [], "excluded_lines": [], "start_line": 215, "executed_branches": [[218, 219], [220, 221], [225, 226], [225, 242], [226, 225], [226, 227], [227, 225], [227, 228], [228, 225], [228, 230], [230, 231], [230, 240], [231, 232], [231, 238], [232, 233], [232, 236], [242, -214], [242, 243], [246, 247], [246, 257], [247, 248], [247, 254], [249, 250], [249, 251], [254, 246], [254, 255], [260, 261], [260, 283], [262, 269], [262, 273], [269, 260], [269, 270], [271, 272], [273, 260], [273, 275], [276, 260], [276, 278], [278, 276], [278, 279], [280, 281], [284, -214], [284, 285], [285, 286], [285, 288]], "missing_branches": [[218, -214], [220, -214], [271, 260], [280, 276]]}, "GoeChargerDataUpdateCoordinator.__init__": {"executed_lines": [301, 302, 303, 304, 306, 307, 310, 311, 312, 314, 315, 316, 325, 326, 336, 337, 339, 340, 341, 343, 346, 347, 348, 349, 350, 351, 352, 353, 354, 356, 358, 359, 360, 361, 362, 363, 368, 371, 374, 375], "summary": {"covered_lines": 40, "num_statements": 41, "percent_covered": 96.07843137254902, "percent_covered_display": "96", "missing_lines": 1, "excluded_lines": 0, "percent_statements_covered": 97.5609756097561, "percent_statements_covered_display": "98", "num_branches": 10, "num_partial_branches": 1, "covered_branches": 9, "missing_branches": 1, "percent_branches_covered": 90.0, "percent_branches_covered_display": "90"}, "missing_lines": [364], "excluded_lines": [], "start_line": 300, "executed_branches": [[311, 312], [311, 314], [314, 315], [314, 325], [340, 341], [340, 343], [353, 354], [353, 356], [363, 368]], "missing_branches": [[363, 364]]}, "GoeChargerDataUpdateCoordinator.call_later_update_device_registry": {"executed_lines": [378, 379, 380, 381, 382, 383, 384, 386, 387, 388, 391, 392, 393, 394, 396, 398], "summary": {"covered_lines": 16, "num_statements": 17, "percent_covered": 87.09677419354838, "percent_covered_display": "87", "missing_lines": 1, "excluded_lines": 0, "percent_statements_covered": 94.11764705882354, "percent_statements_covered_display": "94", "num_branches": 14, "num_partial_branches": 3, "covered_branches": 11, "missing_branches": 3, "percent_branches_covered": 78.57142857142857, "percent_branches_covered_display": "79"}, "missing_lines": [390], "excluded_lines": [], "start_line": 377, "executed_branches": [[379, -377], [379, 380], [380, 381], [382, 383], [382, 386], [386, 387], [387, 388], [391, -377], [391, 392], [393, 394], [393, 396]], "missing_branches": [[380, -377], [386, -377], [387, 390]]}, "GoeChargerDataUpdateCoordinator.cards_as_object_present": {"executed_lines": [404], "summary": {"covered_lines": 1, "num_statements": 1, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 403, "executed_branches": [], "missing_branches": []}, "GoeChargerDataUpdateCoordinator.force_async_update_now": {"executed_lines": [410, 411, 412, 413, 416, 417, 418, 419, 423, 424, 425, 428, 431, 432, 433, 435], "summary": {"covered_lines": 16, "num_statements": 16, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 6, "num_partial_branches": 0, "covered_branches": 6, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 406, "executed_branches": [[411, 412], [411, 416], [417, 418], [417, 423], [432, 433], [432, 435]], "missing_branches": []}, "GoeChargerDataUpdateCoordinator.start_websocket_and_wait_for_first_data": {"executed_lines": [438, 442, 445, 446, 448, 452, 453, 454, 456, 457, 458, 460, 463, 464, 465, 468, 470, 471, 472, 473, 474, 475, 476, 478, 479, 481, 482], "summary": {"covered_lines": 27, "num_statements": 28, "percent_covered": 94.44444444444444, "percent_covered_display": "94", "missing_lines": 1, "excluded_lines": 0, "percent_statements_covered": 96.42857142857143, "percent_statements_covered_display": "96", "num_branches": 8, "num_partial_branches": 1, "covered_branches": 7, "missing_branches": 1, "percent_branches_covered": 87.5, "percent_branches_covered_display": "88"}, "missing_lines": [449], "excluded_lines": [], "start_line": 437, "executed_branches": [[448, 452], [463, 464], [463, 468], [470, 471], [470, 478], [472, 473], [472, 478]], "missing_branches": [[448, 449]]}, "GoeChargerDataUpdateCoordinator.start_watchdog": {"executed_lines": [486, 487], "summary": {"covered_lines": 2, "num_statements": 2, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 484, "executed_branches": [], "missing_branches": []}, "GoeChargerDataUpdateCoordinator.stop_watchdog": {"executed_lines": [494, 495, 496], "summary": {"covered_lines": 3, "num_statements": 3, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 2, "num_partial_branches": 0, "covered_branches": 2, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 493, "executed_branches": [[494, -493], [494, 495]], "missing_branches": []}, "GoeChargerDataUpdateCoordinator._check_for_ws_task_and_cancel_if_running": {"executed_lines": [499, 500, 501, 502, 503, 504, 505, 506], "summary": {"covered_lines": 8, "num_statements": 8, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 2, "num_partial_branches": 0, "covered_branches": 2, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 498, "executed_branches": [[499, -498], [499, 500]], "missing_branches": []}, "GoeChargerDataUpdateCoordinator._async_watchdog_check": {"executed_lines": [509, 510, 511, 512, 513, 514, 515, 516, 518, 519, 520, 521], "summary": {"covered_lines": 12, "num_statements": 12, "percent_covered": 94.44444444444444, "percent_covered_display": "94", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 6, "num_partial_branches": 1, "covered_branches": 5, "missing_branches": 1, "percent_branches_covered": 83.33333333333333, "percent_branches_covered_display": "83"}, "missing_lines": [], "excluded_lines": [], "start_line": 508, "executed_branches": [[509, 510], [509, 518], [514, 515], [519, -508], [519, 520]], "missing_branches": [[514, -508]]}, "GoeChargerDataUpdateCoordinator.__call__": {"executed_lines": [525, 526], "summary": {"covered_lines": 2, "num_statements": 2, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 524, "executed_branches": [], "missing_branches": []}, "GoeChargerDataUpdateCoordinator.clear_data": {"executed_lines": [529, 530, 531, 532, 533, 534, 535, 536, 537, 538], "summary": {"covered_lines": 10, "num_statements": 10, "percent_covered": 91.66666666666667, "percent_covered_display": "92", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 2, "num_partial_branches": 1, "covered_branches": 1, "missing_branches": 1, "percent_branches_covered": 50.0, "percent_branches_covered_display": "50"}, "missing_lines": [], "excluded_lines": [], "start_line": 528, "executed_branches": [[532, 533]], "missing_branches": [[532, 534]]}, "GoeChargerDataUpdateCoordinator.trigger_restart_delayed": {"executed_lines": [542, 544, 545, 546, 547], "summary": {"covered_lines": 5, "num_statements": 5, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 540, "executed_branches": [], "missing_branches": []}, "GoeChargerDataUpdateCoordinator.cancel_existing_debouncer": {"executed_lines": [550, 551], "summary": {"covered_lines": 2, "num_statements": 2, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 2, "num_partial_branches": 0, "covered_branches": 2, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 549, "executed_branches": [[550, -549], [550, 551]], "missing_branches": []}, "GoeChargerDataUpdateCoordinator.request_update_in_sec": {"executed_lines": [554, 555], "summary": {"covered_lines": 2, "num_statements": 2, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 553, "executed_branches": [], "missing_branches": []}, "GoeChargerDataUpdateCoordinator._debounce_coordinator_update": {"executed_lines": [558, 559], "summary": {"covered_lines": 2, "num_statements": 2, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 557, "executed_branches": [], "missing_branches": []}, "GoeChargerDataUpdateCoordinator.handle_write_result": {"executed_lines": [562, 565, 567, 569, 570, 572, 574, 575, 577, 578, 579, 580, 582, 583, 584, 586, 587, 588], "summary": {"covered_lines": 18, "num_statements": 18, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 14, "num_partial_branches": 0, "covered_branches": 14, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 561, "executed_branches": [[562, 565], [562, 567], [569, 570], [569, 572], [575, 577], [575, 586], [577, 578], [577, 582], [582, 583], [582, 586], [586, -561], [586, 587], [587, -561], [587, 588]], "missing_branches": []}, "GoeChargerDataUpdateCoordinator._handle_client_connection_error": {"executed_lines": [595, 596, 597, 598, 599, 600, 601, 602, 604], "summary": {"covered_lines": 9, "num_statements": 9, "percent_covered": 92.3076923076923, "percent_covered_display": "92", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 4, "num_partial_branches": 1, "covered_branches": 3, "missing_branches": 1, "percent_branches_covered": 75.0, "percent_branches_covered_display": "75"}, "missing_lines": [], "excluded_lines": [], "start_line": 591, "executed_branches": [[597, 598], [597, 604], [599, 600]], "missing_branches": [[599, -591]]}, "GoeChargerDataUpdateCoordinator._async_update_data": {"executed_lines": [608, 609, 613, 614, 615, 616, 617, 618, 619, 621, 622, 623, 624, 625, 631, 633, 634, 635, 636, 638, 639, 640], "summary": {"covered_lines": 22, "num_statements": 25, "percent_covered": 84.84848484848484, "percent_covered_display": "85", "missing_lines": 3, "excluded_lines": 0, "percent_statements_covered": 88.0, "percent_statements_covered_display": "88", "num_branches": 8, "num_partial_branches": 2, "covered_branches": 6, "missing_branches": 2, "percent_branches_covered": 75.0, "percent_branches_covered_display": "75"}, "missing_lines": [610, 611, 637], "excluded_lines": [], "start_line": 606, "executed_branches": [[609, 613], [613, 614], [613, 617], [617, 618], [617, 621], [623, 624]], "missing_branches": [[609, 610], [623, 631]]}, "GoeChargerDataUpdateCoordinator.async_write_key": {"executed_lines": [643, 644, 645, 646, 647, 648, 649, 651, 652, 653, 654, 656, 657, 658, 659, 660, 661], "summary": {"covered_lines": 17, "num_statements": 17, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 4, "num_partial_branches": 0, "covered_branches": 4, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 642, "executed_branches": [[643, 644], [643, 647], [647, 648], [647, 651]], "missing_branches": []}, "GoeChargerDataUpdateCoordinator.async_write_multiple_keys": {"executed_lines": [664, 665, 666, 667, 668, 669, 670, 672, 673, 677, 678, 679], "summary": {"covered_lines": 12, "num_statements": 17, "percent_covered": 76.19047619047619, "percent_covered_display": "76", "missing_lines": 5, "excluded_lines": 0, "percent_statements_covered": 70.58823529411765, "percent_statements_covered_display": "71", "num_branches": 4, "num_partial_branches": 0, "covered_branches": 4, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [674, 675, 680, 681, 682], "excluded_lines": [], "start_line": 663, "executed_branches": [[664, 665], [664, 668], [668, 669], [668, 672]], "missing_branches": []}, "GoeChargerDataUpdateCoordinator.read_versions": {"executed_lines": [685, 686, 688, 692, 693, 694, 696, 700, 701, 702, 703, 704, 707, 710, 711, 712, 713, 714, 715, 716, 719, 720, 721, 723, 725, 727, 729, 730, 731, 732, 733, 734, 736, 737, 738, 739, 740, 742, 743, 745, 750, 751, 752, 753, 754, 755, 756, 757, 758, 759, 760, 761, 763, 768, 769, 771, 772, 773, 774, 776, 778, 779, 780, 783, 784, 785, 787, 788, 789, 791, 792, 794, 797, 799, 800, 801, 803, 805, 806, 808, 811, 812, 813, 814, 815, 817, 819, 834], "summary": {"covered_lines": 88, "num_statements": 88, "percent_covered": 99.29577464788733, "percent_covered_display": "99", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 54, "num_partial_branches": 1, "covered_branches": 53, "missing_branches": 1, "percent_branches_covered": 98.14814814814815, "percent_branches_covered_display": "98"}, "missing_lines": [], "excluded_lines": [], "start_line": 684, "executed_branches": [[685, 686], [685, 688], [688, 692], [688, 700], [692, 693], [692, 696], [700, 701], [700, 707], [702, 703], [702, 707], [711, 712], [711, 715], [713, 714], [715, 716], [715, 719], [720, 721], [720, 723], [727, 729], [727, 797], [730, 731], [730, 736], [731, 732], [731, 742], [732, 733], [732, 734], [736, 737], [736, 742], [738, 739], [738, 740], [742, 743], [742, 745], [752, 753], [752, 778], [754, 755], [754, 776], [758, 759], [758, 763], [773, 774], [773, 778], [778, 779], [778, 783], [783, 784], [783, 787], [791, 792], [791, 794], [800, 801], [800, 803], [805, 806], [805, 808], [811, 812], [811, 817], [813, 814], [813, 819]], "missing_branches": [[713, 715]]}, "GoeChargerDataUpdateCoordinator.check_for_16a_limit": {"executed_lines": [837, 838, 839, 840], "summary": {"covered_lines": 4, "num_statements": 4, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 836, "executed_branches": [], "missing_branches": []}, "GoeChargerDataUpdateCoordinator.is_valid_charger_entity": {"executed_lines": [843, 845, 846, 848, 849, 852, 855, 858, 859, 861], "summary": {"covered_lines": 10, "num_statements": 10, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 10, "num_partial_branches": 0, "covered_branches": 10, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 842, "executed_branches": [[843, 845], [843, 852], [845, 846], [845, 848], [848, 849], [848, 861], [852, 855], [852, 858], [858, 859], [858, 861]], "missing_branches": []}, "GoeChargerBaseEntity.__init__": {"executed_lines": [867, 870, 873, 874, 875, 877, 878, 879, 880, 881, 882, 883, 885, 886, 887, 888, 889, 890, 892, 894, 895, 897, 898, 900], "summary": {"covered_lines": 24, "num_statements": 24, "percent_covered": 97.5, "percent_covered_display": "98", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 16, "num_partial_branches": 1, "covered_branches": 15, "missing_branches": 1, "percent_branches_covered": 93.75, "percent_branches_covered_display": "94"}, "missing_lines": [], "excluded_lines": [], "start_line": 866, "executed_branches": [[873, 874], [873, 885], [874, 875], [874, 877], [877, 878], [877, 881], [881, 882], [885, 886], [885, 887], [887, 888], [887, 889], [889, 890], [889, 892], [897, 898], [897, 900]], "missing_branches": [[881, 894]]}, "GoeChargerBaseEntity._name_internal": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 1, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 1, "excluded_lines": 0, "percent_statements_covered": 0.0, "percent_statements_covered_display": "0", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [909], "excluded_lines": [], "start_line": 907, "executed_branches": [], "missing_branches": []}, "GoeChargerBaseEntity.device_info": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 1, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 1, "excluded_lines": 0, "percent_statements_covered": 0.0, "percent_statements_covered_display": "0", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [913], "excluded_lines": [], "start_line": 912, "executed_branches": [], "missing_branches": []}, "GoeChargerBaseEntity.available": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 1, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 1, "excluded_lines": 0, "percent_statements_covered": 0.0, "percent_statements_covered_display": "0", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [918], "excluded_lines": [], "start_line": 916, "executed_branches": [], "missing_branches": []}, "GoeChargerBaseEntity.unique_id": {"executed_lines": [923], "summary": {"covered_lines": 1, "num_statements": 1, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 921, "executed_branches": [], "missing_branches": []}, "GoeChargerBaseEntity._friendly_name_internal": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 12, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 12, "excluded_lines": 0, "percent_statements_covered": 0.0, "percent_statements_covered_display": "0", "num_branches": 10, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 10, "percent_branches_covered": 0.0, "percent_branches_covered_display": "0"}, "missing_lines": [931, 932, 933, 935, 936, 938, 939, 940, 943, 944, 945, 947], "excluded_lines": [], "start_line": 925, "executed_branches": [], "missing_branches": [[932, 933], [932, 935], [935, 936], [935, 938], [939, 940], [939, 943], [943, 944], [943, 947], [944, 945], [944, 947]]}, "": {"executed_lines": [1, 2, 3, 4, 5, 6, 7, 9, 10, 11, 12, 23, 24, 25, 30, 31, 32, 33, 34, 35, 36, 38, 45, 46, 60, 61, 63, 65, 66, 67, 68, 69, 70, 71, 73, 103, 108, 187, 209, 214, 215, 294, 296, 297, 298, 300, 377, 403, 406, 437, 484, 493, 498, 508, 524, 528, 540, 549, 553, 557, 561, 591, 606, 642, 663, 684, 836, 842, 863, 864, 866, 907, 911, 912, 915, 916, 920, 921, 925], "summary": {"covered_lines": 79, "num_statements": 79, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 1, "executed_branches": [], "missing_branches": []}}, "classes": {"GoeChargerDataUpdateCoordinator": {"executed_lines": [301, 302, 303, 304, 306, 307, 310, 311, 312, 314, 315, 316, 325, 326, 336, 337, 339, 340, 341, 343, 346, 347, 348, 349, 350, 351, 352, 353, 354, 356, 358, 359, 360, 361, 362, 363, 368, 371, 374, 375, 378, 379, 380, 381, 382, 383, 384, 386, 387, 388, 391, 392, 393, 394, 396, 398, 404, 410, 411, 412, 413, 416, 417, 418, 419, 423, 424, 425, 428, 431, 432, 433, 435, 438, 442, 445, 446, 448, 452, 453, 454, 456, 457, 458, 460, 463, 464, 465, 468, 470, 471, 472, 473, 474, 475, 476, 478, 479, 481, 482, 486, 487, 494, 495, 496, 499, 500, 501, 502, 503, 504, 505, 506, 509, 510, 511, 512, 513, 514, 515, 516, 518, 519, 520, 521, 525, 526, 529, 530, 531, 532, 533, 534, 535, 536, 537, 538, 542, 544, 545, 546, 547, 550, 551, 554, 555, 558, 559, 562, 565, 567, 569, 570, 572, 574, 575, 577, 578, 579, 580, 582, 583, 584, 586, 587, 588, 595, 596, 597, 598, 599, 600, 601, 602, 604, 608, 609, 613, 614, 615, 616, 617, 618, 619, 621, 622, 623, 624, 625, 631, 633, 634, 635, 636, 638, 639, 640, 643, 644, 645, 646, 647, 648, 649, 651, 652, 653, 654, 656, 657, 658, 659, 660, 661, 664, 665, 666, 667, 668, 669, 670, 672, 673, 677, 678, 679, 685, 686, 688, 692, 693, 694, 696, 700, 701, 702, 703, 704, 707, 710, 711, 712, 713, 714, 715, 716, 719, 720, 721, 723, 725, 727, 729, 730, 731, 732, 733, 734, 736, 737, 738, 739, 740, 742, 743, 745, 750, 751, 752, 753, 754, 755, 756, 757, 758, 759, 760, 761, 763, 768, 769, 771, 772, 773, 774, 776, 778, 779, 780, 783, 784, 785, 787, 788, 789, 791, 792, 794, 797, 799, 800, 801, 803, 805, 806, 808, 811, 812, 813, 814, 815, 817, 819, 834, 837, 838, 839, 840, 843, 845, 846, 848, 849, 852, 855, 858, 859, 861], "summary": {"covered_lines": 328, "num_statements": 339, "percent_covered": 95.50102249488752, "percent_covered_display": "96", "missing_lines": 11, "excluded_lines": 0, "percent_statements_covered": 96.7551622418879, "percent_statements_covered_display": "97", "num_branches": 150, "num_partial_branches": 11, "covered_branches": 139, "missing_branches": 11, "percent_branches_covered": 92.66666666666667, "percent_branches_covered_display": "93"}, "missing_lines": [364, 390, 449, 610, 611, 637, 674, 675, 680, 681, 682], "excluded_lines": [], "start_line": 294, "executed_branches": [[311, 312], [311, 314], [314, 315], [314, 325], [340, 341], [340, 343], [353, 354], [353, 356], [363, 368], [379, -377], [379, 380], [380, 381], [382, 383], [382, 386], [386, 387], [387, 388], [391, -377], [391, 392], [393, 394], [393, 396], [411, 412], [411, 416], [417, 418], [417, 423], [432, 433], [432, 435], [448, 452], [463, 464], [463, 468], [470, 471], [470, 478], [472, 473], [472, 478], [494, -493], [494, 495], [499, -498], [499, 500], [509, 510], [509, 518], [514, 515], [519, -508], [519, 520], [532, 533], [550, -549], [550, 551], [562, 565], [562, 567], [569, 570], [569, 572], [575, 577], [575, 586], [577, 578], [577, 582], [582, 583], [582, 586], [586, -561], [586, 587], [587, -561], [587, 588], [597, 598], [597, 604], [599, 600], [609, 613], [613, 614], [613, 617], [617, 618], [617, 621], [623, 624], [643, 644], [643, 647], [647, 648], [647, 651], [664, 665], [664, 668], [668, 669], [668, 672], [685, 686], [685, 688], [688, 692], [688, 700], [692, 693], [692, 696], [700, 701], [700, 707], [702, 703], [702, 707], [711, 712], [711, 715], [713, 714], [715, 716], [715, 719], [720, 721], [720, 723], [727, 729], [727, 797], [730, 731], [730, 736], [731, 732], [731, 742], [732, 733], [732, 734], [736, 737], [736, 742], [738, 739], [738, 740], [742, 743], [742, 745], [752, 753], [752, 778], [754, 755], [754, 776], [758, 759], [758, 763], [773, 774], [773, 778], [778, 779], [778, 783], [783, 784], [783, 787], [791, 792], [791, 794], [800, 801], [800, 803], [805, 806], [805, 808], [811, 812], [811, 817], [813, 814], [813, 819], [843, 845], [843, 852], [845, 846], [845, 848], [848, 849], [848, 861], [852, 855], [852, 858], [858, 859], [858, 861]], "missing_branches": [[363, 364], [380, -377], [386, -377], [387, 390], [448, 449], [514, -508], [532, 534], [599, -591], [609, 610], [623, 631], [713, 715]]}, "GoeChargerBaseEntity": {"executed_lines": [867, 870, 873, 874, 875, 877, 878, 879, 880, 881, 882, 883, 885, 886, 887, 888, 889, 890, 892, 894, 895, 897, 898, 900, 923], "summary": {"covered_lines": 25, "num_statements": 40, "percent_covered": 60.60606060606061, "percent_covered_display": "61", "missing_lines": 15, "excluded_lines": 0, "percent_statements_covered": 62.5, "percent_statements_covered_display": "62", "num_branches": 26, "num_partial_branches": 1, "covered_branches": 15, "missing_branches": 11, "percent_branches_covered": 57.69230769230769, "percent_branches_covered_display": "58"}, "missing_lines": [909, 913, 918, 931, 932, 933, 935, 936, 938, 939, 940, 943, 944, 945, 947], "excluded_lines": [], "start_line": 863, "executed_branches": [[873, 874], [873, 885], [874, 875], [874, 877], [877, 878], [877, 881], [881, 882], [885, 886], [885, 887], [887, 888], [887, 889], [889, 890], [889, 892], [897, 898], [897, 900]], "missing_branches": [[881, 894], [932, 933], [932, 935], [935, 936], [935, 938], [939, 940], [939, 943], [943, 944], [943, 947], [944, 945], [944, 947]]}, "": {"executed_lines": [1, 2, 3, 4, 5, 6, 7, 9, 10, 11, 12, 23, 24, 25, 30, 31, 32, 33, 34, 35, 36, 38, 45, 46, 60, 61, 63, 65, 66, 67, 68, 69, 70, 71, 73, 74, 75, 76, 77, 78, 81, 82, 85, 86, 87, 88, 89, 90, 91, 92, 93, 97, 98, 100, 103, 105, 108, 109, 110, 111, 112, 113, 116, 117, 118, 120, 122, 123, 124, 125, 126, 127, 128, 129, 131, 132, 133, 135, 136, 137, 138, 140, 141, 144, 145, 148, 149, 150, 151, 153, 154, 155, 156, 158, 162, 165, 166, 173, 175, 176, 177, 179, 180, 182, 184, 187, 188, 190, 191, 192, 193, 194, 195, 196, 197, 198, 201, 202, 203, 204, 206, 209, 210, 211, 214, 215, 216, 217, 218, 219, 220, 221, 224, 225, 226, 227, 228, 230, 231, 232, 233, 234, 236, 238, 240, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 254, 255, 257, 258, 259, 260, 261, 262, 269, 270, 271, 272, 273, 275, 276, 278, 279, 280, 281, 283, 284, 285, 286, 287, 288, 289, 290, 291, 294, 296, 297, 298, 300, 377, 403, 406, 437, 484, 493, 498, 508, 524, 528, 540, 549, 553, 557, 561, 591, 606, 642, 663, 684, 836, 842, 863, 864, 866, 907, 911, 912, 915, 916, 920, 921, 925], "summary": {"covered_lines": 222, "num_statements": 225, "percent_covered": 96.82539682539682, "percent_covered_display": "97", "missing_lines": 3, "excluded_lines": 0, "percent_statements_covered": 98.66666666666667, "percent_statements_covered_display": "99", "num_branches": 90, "num_partial_branches": 7, "covered_branches": 83, "missing_branches": 7, "percent_branches_covered": 92.22222222222223, "percent_branches_covered_display": "92"}, "missing_lines": [80, 94, 95], "excluded_lines": [], "start_line": 1, "executed_branches": [[74, 75], [74, 85], [75, 76], [77, 78], [85, 86], [85, 100], [89, 90], [91, 92], [91, 97], [109, 110], [109, 116], [117, 118], [117, 120], [124, 125], [124, 135], [127, 128], [127, 144], [137, 138], [137, 140], [140, 141], [140, 144], [148, 149], [148, 165], [149, 150], [149, 151], [153, 154], [153, 162], [165, 166], [165, 173], [173, 175], [173, 182], [175, 176], [175, 179], [190, 191], [190, 206], [191, 192], [191, 201], [201, 202], [201, 206], [218, 219], [220, 221], [225, 226], [225, 242], [226, 225], [226, 227], [227, 225], [227, 228], [228, 225], [228, 230], [230, 231], [230, 240], [231, 232], [231, 238], [232, 233], [232, 236], [242, -214], [242, 243], [246, 247], [246, 257], [247, 248], [247, 254], [249, 250], [249, 251], [254, 246], [254, 255], [260, 261], [260, 283], [262, 269], [262, 273], [269, 260], [269, 270], [271, 272], [273, 260], [273, 275], [276, 260], [276, 278], [278, 276], [278, 279], [280, 281], [284, -214], [284, 285], [285, 286], [285, 288]], "missing_branches": [[75, 85], [77, 80], [89, 97], [218, -214], [220, -214], [271, 260], [280, 276]]}}}, "custom_components/goecharger_api2/binary_sensor.py": {"executed_lines": [1, 3, 4, 5, 6, 7, 9, 10, 11, 12, 15, 18, 36, 37, 38, 39, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 53, 54, 56, 58, 61, 62, 63, 64, 66, 67, 68, 69, 70, 73, 74, 75, 76, 77, 78, 80, 81, 83, 84, 86, 88, 90, 92, 93, 95, 96, 98], "summary": {"covered_lines": 58, "num_statements": 71, "percent_covered": 77.14285714285714, "percent_covered_display": "77", "missing_lines": 13, "excluded_lines": 0, "percent_statements_covered": 81.69014084507042, "percent_statements_covered_display": "82", "num_branches": 34, "num_partial_branches": 3, "covered_branches": 23, "missing_branches": 11, "percent_branches_covered": 67.6470588235294, "percent_branches_covered_display": "68"}, "missing_lines": [19, 20, 21, 23, 24, 25, 26, 27, 29, 30, 31, 33, 72], "excluded_lines": [], "executed_branches": [[45, 46], [46, 47], [46, 61], [47, 48], [47, 51], [51, 53], [51, 58], [53, 54], [53, 56], [61, 62], [63, 64], [63, 80], [67, 68], [67, 69], [69, 70], [80, 81], [80, 90], [81, 83], [81, 88], [83, 84], [83, 86], [95, 96], [95, 98]], "missing_branches": [[23, 24], [23, 29], [24, 25], [24, 33], [25, 24], [25, 26], [29, 30], [29, 33], [45, 80], [61, 63], [69, 72]], "functions": {"async_setup_entry": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 12, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 12, "excluded_lines": 0, "percent_statements_covered": 0.0, "percent_statements_covered_display": "0", "num_branches": 8, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 8, "percent_branches_covered": 0.0, "percent_branches_covered_display": "0"}, "missing_lines": [19, 20, 21, 23, 24, 25, 26, 27, 29, 30, 31, 33], "excluded_lines": [], "start_line": 18, "executed_branches": [], "missing_branches": [[23, 24], [23, 29], [24, 25], [24, 33], [25, 24], [25, 26], [29, 30], [29, 33]]}, "GoeChargerApiV2BinarySensor.__init__": {"executed_lines": [38, 39], "summary": {"covered_lines": 2, "num_statements": 2, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 37, "executed_branches": [], "missing_branches": []}, "GoeChargerApiV2BinarySensor.is_on": {"executed_lines": [43, 44, 45, 46, 47, 48, 49, 50, 51, 53, 54, 56, 58, 61, 62, 63, 64, 66, 67, 68, 69, 70, 73, 74, 75, 76, 77, 78, 80, 81, 83, 84, 86, 88, 90], "summary": {"covered_lines": 35, "num_statements": 36, "percent_covered": 93.33333333333333, "percent_covered_display": "93", "missing_lines": 1, "excluded_lines": 0, "percent_statements_covered": 97.22222222222223, "percent_statements_covered_display": "97", "num_branches": 24, "num_partial_branches": 3, "covered_branches": 21, "missing_branches": 3, "percent_branches_covered": 87.5, "percent_branches_covered_display": "88"}, "missing_lines": [72], "excluded_lines": [], "start_line": 42, "executed_branches": [[45, 46], [46, 47], [46, 61], [47, 48], [47, 51], [51, 53], [51, 58], [53, 54], [53, 56], [61, 62], [63, 64], [63, 80], [67, 68], [67, 69], [69, 70], [80, 81], [80, 90], [81, 83], [81, 88], [83, 84], [83, 86]], "missing_branches": [[45, 80], [61, 63], [69, 72]]}, "GoeChargerApiV2BinarySensor.icon": {"executed_lines": [95, 96, 98], "summary": {"covered_lines": 3, "num_statements": 3, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 2, "num_partial_branches": 0, "covered_branches": 2, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 93, "executed_branches": [[95, 96], [95, 98]], "missing_branches": []}, "": {"executed_lines": [1, 3, 4, 5, 6, 7, 9, 10, 11, 12, 15, 18, 36, 37, 41, 42, 92, 93], "summary": {"covered_lines": 18, "num_statements": 18, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 1, "executed_branches": [], "missing_branches": []}}, "classes": {"GoeChargerApiV2BinarySensor": {"executed_lines": [38, 39, 43, 44, 45, 46, 47, 48, 49, 50, 51, 53, 54, 56, 58, 61, 62, 63, 64, 66, 67, 68, 69, 70, 73, 74, 75, 76, 77, 78, 80, 81, 83, 84, 86, 88, 90, 95, 96, 98], "summary": {"covered_lines": 40, "num_statements": 41, "percent_covered": 94.02985074626865, "percent_covered_display": "94", "missing_lines": 1, "excluded_lines": 0, "percent_statements_covered": 97.5609756097561, "percent_statements_covered_display": "98", "num_branches": 26, "num_partial_branches": 3, "covered_branches": 23, "missing_branches": 3, "percent_branches_covered": 88.46153846153847, "percent_branches_covered_display": "88"}, "missing_lines": [72], "excluded_lines": [], "start_line": 36, "executed_branches": [[45, 46], [46, 47], [46, 61], [47, 48], [47, 51], [51, 53], [51, 58], [53, 54], [53, 56], [61, 62], [63, 64], [63, 80], [67, 68], [67, 69], [69, 70], [80, 81], [80, 90], [81, 83], [81, 88], [83, 84], [83, 86], [95, 96], [95, 98]], "missing_branches": [[45, 80], [61, 63], [69, 72]]}, "": {"executed_lines": [1, 3, 4, 5, 6, 7, 9, 10, 11, 12, 15, 18, 36, 37, 41, 42, 92, 93], "summary": {"covered_lines": 18, "num_statements": 30, "percent_covered": 47.36842105263158, "percent_covered_display": "47", "missing_lines": 12, "excluded_lines": 0, "percent_statements_covered": 60.0, "percent_statements_covered_display": "60", "num_branches": 8, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 8, "percent_branches_covered": 0.0, "percent_branches_covered_display": "0"}, "missing_lines": [19, 20, 21, 23, 24, 25, 26, 27, 29, 30, 31, 33], "excluded_lines": [], "start_line": 1, "executed_branches": [], "missing_branches": [[23, 24], [23, 29], [24, 25], [24, 33], [25, 24], [25, 26], [29, 30], [29, 33]]}}}, "custom_components/goecharger_api2/button.py": {"executed_lines": [1, 3, 4, 5, 6, 7, 9, 10, 11, 13, 16, 34, 35, 36, 38, 39, 40, 41, 42], "summary": {"covered_lines": 19, "num_statements": 31, "percent_covered": 48.717948717948715, "percent_covered_display": "49", "missing_lines": 12, "excluded_lines": 0, "percent_statements_covered": 61.29032258064516, "percent_statements_covered_display": "61", "num_branches": 8, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 8, "percent_branches_covered": 0.0, "percent_branches_covered_display": "0"}, "missing_lines": [17, 18, 19, 21, 22, 23, 24, 25, 27, 28, 29, 31], "excluded_lines": [], "executed_branches": [], "missing_branches": [[21, 22], [21, 27], [22, 23], [22, 31], [23, 22], [23, 24], [27, 28], [27, 31]], "functions": {"async_setup_entry": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 12, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 12, "excluded_lines": 0, "percent_statements_covered": 0.0, "percent_statements_covered_display": "0", "num_branches": 8, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 8, "percent_branches_covered": 0.0, "percent_branches_covered_display": "0"}, "missing_lines": [17, 18, 19, 21, 22, 23, 24, 25, 27, 28, 29, 31], "excluded_lines": [], "start_line": 16, "executed_branches": [], "missing_branches": [[21, 22], [21, 27], [22, 23], [22, 31], [23, 22], [23, 24], [27, 28], [27, 31]]}, "GoeChargerApiV2Button.__init__": {"executed_lines": [36], "summary": {"covered_lines": 1, "num_statements": 1, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 35, "executed_branches": [], "missing_branches": []}, "GoeChargerApiV2Button.async_press": {"executed_lines": [39, 40, 41, 42], "summary": {"covered_lines": 4, "num_statements": 4, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 38, "executed_branches": [], "missing_branches": []}, "": {"executed_lines": [1, 3, 4, 5, 6, 7, 9, 10, 11, 13, 16, 34, 35, 38], "summary": {"covered_lines": 14, "num_statements": 14, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 1, "executed_branches": [], "missing_branches": []}}, "classes": {"GoeChargerApiV2Button": {"executed_lines": [36, 39, 40, 41, 42], "summary": {"covered_lines": 5, "num_statements": 5, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 34, "executed_branches": [], "missing_branches": []}, "": {"executed_lines": [1, 3, 4, 5, 6, 7, 9, 10, 11, 13, 16, 34, 35, 38], "summary": {"covered_lines": 14, "num_statements": 26, "percent_covered": 41.1764705882353, "percent_covered_display": "41", "missing_lines": 12, "excluded_lines": 0, "percent_statements_covered": 53.84615384615385, "percent_statements_covered_display": "54", "num_branches": 8, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 8, "percent_branches_covered": 0.0, "percent_branches_covered_display": "0"}, "missing_lines": [17, 18, 19, 21, 22, 23, 24, 25, 27, 28, 29, 31], "excluded_lines": [], "start_line": 1, "executed_branches": [], "missing_branches": [[21, 22], [21, 27], [22, 23], [22, 31], [23, 22], [23, 24], [27, 28], [27, 31]]}}}, "custom_components/goecharger_api2/config_flow.py": {"executed_lines": [1, 2, 3, 4, 6, 7, 8, 9, 10, 21, 22, 24, 33, 39, 41, 43, 44, 47, 50, 51, 52, 54, 56, 58, 59, 60, 62, 63, 64, 66, 67, 68, 70, 71, 72, 74, 75, 76, 78, 79, 80, 81, 82, 83, 84, 85, 87, 88, 89, 90, 91, 92, 93, 94, 96, 97, 98, 99, 101, 102, 105, 107, 111, 129, 131, 137, 138, 139, 140, 141, 143, 144, 145, 146, 147, 148, 149, 151, 152, 154, 155, 156, 158, 160, 161, 163, 172, 196, 198, 204, 205, 207, 208, 209, 211, 212, 213, 214, 215, 216, 217, 219, 220, 224, 225, 226, 228, 230, 231, 233, 242, 268, 271, 275, 276, 277, 278, 279, 280, 281, 282, 284, 285, 289, 290, 306, 307, 308, 310, 311, 312, 316, 318, 319, 324, 325, 329, 331, 332, 333, 334, 340, 341, 342, 343, 344, 345, 347, 349, 351, 352, 353, 354, 357, 358, 359, 360, 366, 367, 368, 370, 371, 372, 376, 378, 379, 380, 381, 382, 385], "summary": {"covered_lines": 170, "num_statements": 178, "percent_covered": 95.04504504504504, "percent_covered_display": "95", "missing_lines": 8, "excluded_lines": 0, "percent_statements_covered": 95.50561797752809, "percent_statements_covered_display": "96", "num_branches": 44, "num_partial_branches": 3, "covered_branches": 41, "missing_branches": 3, "percent_branches_covered": 93.18181818181819, "percent_branches_covered_display": "93"}, "missing_lines": [142, 210, 320, 321, 326, 327, 383, 384], "excluded_lines": [], "executed_branches": [[78, 79], [78, 87], [98, 99], [98, 107], [101, 102], [101, 105], [137, 138], [137, 163], [140, 141], [140, 160], [141, 143], [147, 148], [147, 151], [155, 156], [155, 158], [204, 205], [204, 233], [208, 209], [208, 230], [209, 211], [215, 216], [215, 219], [225, 226], [225, 228], [280, 281], [280, 284], [284, 285], [341, 342], [341, 349], [343, 344], [343, 347], [349, 351], [349, 366], [353, 354], [353, 357], [359, 360], [359, 366], [367, 368], [367, 385], [371, 372], [371, 376]], "missing_branches": [[141, 142], [209, 210], [284, 316]], "functions": {"GoeChargerApiV2FlowHandler.__init__": {"executed_lines": [56, 58, 59, 60, 62, 63, 64, 66, 67, 68, 70, 71, 72], "summary": {"covered_lines": 13, "num_statements": 13, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 54, "executed_branches": [], "missing_branches": []}, "GoeChargerApiV2FlowHandler.async_step_reconfigure": {"executed_lines": [75, 76, 78, 79, 80, 81, 82, 83, 84, 85, 87, 88, 89, 90, 91, 92, 93, 94], "summary": {"covered_lines": 18, "num_statements": 18, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 2, "num_partial_branches": 0, "covered_branches": 2, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 74, "executed_branches": [[78, 79], [78, 87]], "missing_branches": []}, "GoeChargerApiV2FlowHandler.async_step_user": {"executed_lines": [97, 98, 99, 101, 102, 105, 107, 111], "summary": {"covered_lines": 8, "num_statements": 8, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 4, "num_partial_branches": 0, "covered_branches": 4, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 96, "executed_branches": [[98, 99], [98, 107], [101, 102], [101, 105]], "missing_branches": []}, "GoeChargerApiV2FlowHandler.async_step_user_lan": {"executed_lines": [131, 137, 138, 139, 140, 141, 143, 144, 145, 146, 147, 148, 149, 151, 152, 154, 155, 156, 158, 160, 161, 163, 172], "summary": {"covered_lines": 23, "num_statements": 24, "percent_covered": 94.11764705882354, "percent_covered_display": "94", "missing_lines": 1, "excluded_lines": 0, "percent_statements_covered": 95.83333333333333, "percent_statements_covered_display": "96", "num_branches": 10, "num_partial_branches": 1, "covered_branches": 9, "missing_branches": 1, "percent_branches_covered": 90.0, "percent_branches_covered_display": "90"}, "missing_lines": [142], "excluded_lines": [], "start_line": 129, "executed_branches": [[137, 138], [137, 163], [140, 141], [140, 160], [141, 143], [147, 148], [147, 151], [155, 156], [155, 158]], "missing_branches": [[141, 142]]}, "GoeChargerApiV2FlowHandler.async_step_user_wan": {"executed_lines": [198, 204, 205, 207, 208, 209, 211, 212, 213, 214, 215, 216, 217, 219, 220, 224, 225, 226, 228, 230, 231, 233, 242], "summary": {"covered_lines": 23, "num_statements": 24, "percent_covered": 94.11764705882354, "percent_covered_display": "94", "missing_lines": 1, "excluded_lines": 0, "percent_statements_covered": 95.83333333333333, "percent_statements_covered_display": "96", "num_branches": 10, "num_partial_branches": 1, "covered_branches": 9, "missing_branches": 1, "percent_branches_covered": 90.0, "percent_branches_covered_display": "90"}, "missing_lines": [210], "excluded_lines": [], "start_line": 196, "executed_branches": [[204, 205], [204, 233], [208, 209], [208, 230], [209, 211], [215, 216], [215, 219], [225, 226], [225, 228]], "missing_branches": [[209, 210]]}, "GoeChargerApiV2FlowHandler._check_ws_connection_and_flags": {"executed_lines": [271, 275, 276, 277, 278, 279, 280, 281, 282, 284, 285, 289, 290, 306, 307, 308, 310, 311, 312, 316, 318, 319, 324, 325, 329], "summary": {"covered_lines": 25, "num_statements": 29, "percent_covered": 84.84848484848484, "percent_covered_display": "85", "missing_lines": 4, "excluded_lines": 0, "percent_statements_covered": 86.20689655172414, "percent_statements_covered_display": "86", "num_branches": 4, "num_partial_branches": 1, "covered_branches": 3, "missing_branches": 1, "percent_branches_covered": 75.0, "percent_branches_covered_display": "75"}, "missing_lines": [320, 321, 326, 327], "excluded_lines": [], "start_line": 268, "executed_branches": [[280, 281], [280, 284], [284, 285]], "missing_branches": [[284, 316]]}, "GoeChargerApiV2FlowHandler._test_host": {"executed_lines": [332, 333, 334, 340, 341, 342, 343, 344, 345, 347, 349, 351, 352, 353, 354, 357, 358, 359, 360, 366, 367, 368, 370, 371, 372, 376, 378, 379, 380, 381, 382, 385], "summary": {"covered_lines": 32, "num_statements": 34, "percent_covered": 95.83333333333333, "percent_covered_display": "96", "missing_lines": 2, "excluded_lines": 0, "percent_statements_covered": 94.11764705882354, "percent_statements_covered_display": "94", "num_branches": 14, "num_partial_branches": 0, "covered_branches": 14, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [383, 384], "excluded_lines": [], "start_line": 331, "executed_branches": [[341, 342], [341, 349], [343, 344], [343, 347], [349, 351], [349, 366], [353, 354], [353, 357], [359, 360], [359, 366], [367, 368], [367, 385], [371, 372], [371, 376]], "missing_branches": []}, "": {"executed_lines": [1, 2, 3, 4, 6, 7, 8, 9, 10, 21, 22, 24, 33, 39, 41, 43, 44, 47, 50, 51, 52, 54, 74, 96, 129, 196, 268, 331], "summary": {"covered_lines": 28, "num_statements": 28, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 1, "executed_branches": [], "missing_branches": []}}, "classes": {"GoeChargerApiV2FlowHandler": {"executed_lines": [56, 58, 59, 60, 62, 63, 64, 66, 67, 68, 70, 71, 72, 75, 76, 78, 79, 80, 81, 82, 83, 84, 85, 87, 88, 89, 90, 91, 92, 93, 94, 97, 98, 99, 101, 102, 105, 107, 111, 131, 137, 138, 139, 140, 141, 143, 144, 145, 146, 147, 148, 149, 151, 152, 154, 155, 156, 158, 160, 161, 163, 172, 198, 204, 205, 207, 208, 209, 211, 212, 213, 214, 215, 216, 217, 219, 220, 224, 225, 226, 228, 230, 231, 233, 242, 271, 275, 276, 277, 278, 279, 280, 281, 282, 284, 285, 289, 290, 306, 307, 308, 310, 311, 312, 316, 318, 319, 324, 325, 329, 332, 333, 334, 340, 341, 342, 343, 344, 345, 347, 349, 351, 352, 353, 354, 357, 358, 359, 360, 366, 367, 368, 370, 371, 372, 376, 378, 379, 380, 381, 382, 385], "summary": {"covered_lines": 142, "num_statements": 150, "percent_covered": 94.3298969072165, "percent_covered_display": "94", "missing_lines": 8, "excluded_lines": 0, "percent_statements_covered": 94.66666666666667, "percent_statements_covered_display": "95", "num_branches": 44, "num_partial_branches": 3, "covered_branches": 41, "missing_branches": 3, "percent_branches_covered": 93.18181818181819, "percent_branches_covered_display": "93"}, "missing_lines": [142, 210, 320, 321, 326, 327, 383, 384], "excluded_lines": [], "start_line": 47, "executed_branches": [[78, 79], [78, 87], [98, 99], [98, 107], [101, 102], [101, 105], [137, 138], [137, 163], [140, 141], [140, 160], [141, 143], [147, 148], [147, 151], [155, 156], [155, 158], [204, 205], [204, 233], [208, 209], [208, 230], [209, 211], [215, 216], [215, 219], [225, 226], [225, 228], [280, 281], [280, 284], [284, 285], [341, 342], [341, 349], [343, 344], [343, 347], [349, 351], [349, 366], [353, 354], [353, 357], [359, 360], [359, 366], [367, 368], [367, 385], [371, 372], [371, 376]], "missing_branches": [[141, 142], [209, 210], [284, 316]]}, "": {"executed_lines": [1, 2, 3, 4, 6, 7, 8, 9, 10, 21, 22, 24, 33, 39, 41, 43, 44, 47, 50, 51, 52, 54, 74, 96, 129, 196, 268, 331], "summary": {"covered_lines": 28, "num_statements": 28, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 1, "executed_branches": [], "missing_branches": []}}}, "custom_components/goecharger_api2/const.py": {"executed_lines": [1, 2, 4, 5, 6, 7, 8, 9, 10, 16, 17, 20, 21, 22, 23, 25, 26, 28, 37, 38, 39, 40, 42, 43, 45, 46, 47, 48, 49, 52, 53, 54, 57, 58, 59, 60, 61, 62, 63, 66, 67, 68, 71, 72, 73, 74, 75, 76, 77, 80, 81, 82, 83, 84, 87, 89, 216, 242, 710, 837, 1953, 2086, 2097, 2315, 2317, 2327, 2329, 2385, 3242], "summary": {"covered_lines": 69, "num_statements": 69, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "executed_branches": [], "missing_branches": [], "functions": {"": {"executed_lines": [1, 2, 4, 5, 6, 7, 8, 9, 10, 16, 17, 20, 21, 22, 23, 25, 26, 28, 37, 38, 39, 40, 42, 43, 45, 46, 47, 48, 49, 52, 53, 54, 57, 58, 59, 60, 61, 62, 63, 66, 67, 68, 71, 72, 73, 74, 75, 76, 77, 80, 81, 82, 83, 84, 87, 89, 216, 242, 710, 837, 1953, 2086, 2097, 2315, 2317, 2327, 2329, 2385, 3242], "summary": {"covered_lines": 69, "num_statements": 69, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 1, "executed_branches": [], "missing_branches": []}}, "classes": {"ExtBinarySensorEntityDescription": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 0, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 46, "executed_branches": [], "missing_branches": []}, "ExtButtonEntityDescription": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 0, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 53, "executed_branches": [], "missing_branches": []}, "ExtNumberEntityDescription": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 0, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 58, "executed_branches": [], "missing_branches": []}, "ExtSelectEntityDescription": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 0, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 67, "executed_branches": [], "missing_branches": []}, "ExtSensorEntityDescription": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 0, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 72, "executed_branches": [], "missing_branches": []}, "ExtSwitchEntityDescription": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 0, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 81, "executed_branches": [], "missing_branches": []}, "": {"executed_lines": [1, 2, 4, 5, 6, 7, 8, 9, 10, 16, 17, 20, 21, 22, 23, 25, 26, 28, 37, 38, 39, 40, 42, 43, 45, 46, 47, 48, 49, 52, 53, 54, 57, 58, 59, 60, 61, 62, 63, 66, 67, 68, 71, 72, 73, 74, 75, 76, 77, 80, 81, 82, 83, 84, 87, 89, 216, 242, 710, 837, 1953, 2086, 2097, 2315, 2317, 2327, 2329, 2385, 3242], "summary": {"covered_lines": 69, "num_statements": 69, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 1, "executed_branches": [], "missing_branches": []}}}, "custom_components/goecharger_api2/entity.py": {"executed_lines": [1, 3, 4, 5, 7, 9, 13, 16], "summary": {"covered_lines": 8, "num_statements": 31, "percent_covered": 17.77777777777778, "percent_covered_display": "18", "missing_lines": 23, "excluded_lines": 0, "percent_statements_covered": 25.806451612903224, "percent_statements_covered_display": "26", "num_branches": 14, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 14, "percent_branches_covered": 0.0, "percent_branches_covered_display": "0"}, "missing_lines": [20, 22, 23, 26, 27, 30, 33, 34, 35, 36, 38, 39, 40, 41, 42, 44, 45, 46, 49, 50, 51, 52, 54], "excluded_lines": [], "executed_branches": [], "missing_branches": [[22, 23], [22, 26], [26, 27], [26, 30], [33, 34], [33, 54], [38, 39], [38, 44], [39, 38], [39, 40], [44, 45], [44, 49], [49, 50], [49, 54]], "functions": {"CustomFriendlyNameEntity._Entity__async_calculate_state": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 23, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 23, "excluded_lines": 0, "percent_statements_covered": 0.0, "percent_statements_covered_display": "0", "num_branches": 14, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 14, "percent_branches_covered": 0.0, "percent_branches_covered_display": "0"}, "missing_lines": [20, 22, 23, 26, 27, 30, 33, 34, 35, 36, 38, 39, 40, 41, 42, 44, 45, 46, 49, 50, 51, 52, 54], "excluded_lines": [], "start_line": 16, "executed_branches": [], "missing_branches": [[22, 23], [22, 26], [26, 27], [26, 30], [33, 34], [33, 54], [38, 39], [38, 44], [39, 38], [39, 40], [44, 45], [44, 49], [49, 50], [49, 54]]}, "": {"executed_lines": [1, 3, 4, 5, 7, 9, 13, 16], "summary": {"covered_lines": 8, "num_statements": 8, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 1, "executed_branches": [], "missing_branches": []}}, "classes": {"CustomFriendlyNameEntity": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 23, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 23, "excluded_lines": 0, "percent_statements_covered": 0.0, "percent_statements_covered_display": "0", "num_branches": 14, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 14, "percent_branches_covered": 0.0, "percent_branches_covered_display": "0"}, "missing_lines": [20, 22, 23, 26, 27, 30, 33, 34, 35, 36, 38, 39, 40, 41, 42, 44, 45, 46, 49, 50, 51, 52, 54], "excluded_lines": [], "start_line": 13, "executed_branches": [], "missing_branches": [[22, 23], [22, 26], [26, 27], [26, 30], [33, 34], [33, 54], [38, 39], [38, 44], [39, 38], [39, 40], [44, 45], [44, 49], [49, 50], [49, 54]]}, "": {"executed_lines": [1, 3, 4, 5, 7, 9, 13, 16], "summary": {"covered_lines": 8, "num_statements": 8, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 1, "executed_branches": [], "missing_branches": []}}}, "custom_components/goecharger_api2/number.py": {"executed_lines": [1, 2, 4, 10, 11, 12, 13, 15, 16, 17, 19, 21, 46, 47, 48, 50, 60, 61, 62, 63, 64, 65, 66, 68, 70, 71, 72, 73, 74, 76, 77, 78, 80, 81, 83, 84, 87, 89, 91, 93, 94, 96, 98, 99, 100, 103, 104, 105, 108, 110, 113, 114, 115, 118, 119, 120, 123, 125, 126, 128, 129, 130, 131, 133, 134, 135, 137, 138, 139, 140, 142, 143, 144, 146, 147, 148], "summary": {"covered_lines": 76, "num_statements": 94, "percent_covered": 77.46478873239437, "percent_covered_display": "77", "missing_lines": 18, "excluded_lines": 0, "percent_statements_covered": 80.85106382978724, "percent_statements_covered_display": "81", "num_branches": 48, "num_partial_branches": 2, "covered_branches": 34, "missing_branches": 14, "percent_branches_covered": 70.83333333333333, "percent_branches_covered_display": "71"}, "missing_lines": [22, 23, 24, 26, 27, 28, 31, 32, 34, 36, 37, 39, 40, 41, 43, 52, 57, 58], "excluded_lines": [], "executed_branches": [[64, 65], [65, 66], [65, 68], [70, 71], [70, 72], [72, 73], [72, 77], [73, 74], [73, 76], [77, 78], [77, 87], [94, 96], [94, 113], [98, 99], [98, 100], [100, 103], [100, 104], [104, 105], [104, 108], [113, 114], [113, 115], [115, 118], [115, 119], [119, 120], [119, 123], [129, -128], [129, 130], [131, -128], [131, 133], [138, 139], [138, 146], [143, 144], [146, -128], [146, 147]], "missing_branches": [[26, 27], [26, 39], [27, 28], [27, 43], [28, 31], [28, 36], [31, 32], [31, 34], [39, 40], [39, 43], [57, -50], [57, 58], [64, 70], [143, 146]], "functions": {"async_setup_entry": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 15, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 15, "excluded_lines": 0, "percent_statements_covered": 0.0, "percent_statements_covered_display": "0", "num_branches": 10, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 10, "percent_branches_covered": 0.0, "percent_branches_covered_display": "0"}, "missing_lines": [22, 23, 24, 26, 27, 28, 31, 32, 34, 36, 37, 39, 40, 41, 43], "excluded_lines": [], "start_line": 21, "executed_branches": [], "missing_branches": [[26, 27], [26, 39], [27, 28], [27, 43], [28, 31], [28, 36], [31, 32], [31, 34], [39, 40], [39, 43]]}, "GoeChargerNumber.__init__": {"executed_lines": [48], "summary": {"covered_lines": 1, "num_statements": 1, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 47, "executed_branches": [], "missing_branches": []}, "GoeChargerNumber.async_added_to_hass": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 3, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 3, "excluded_lines": 0, "percent_statements_covered": 0.0, "percent_statements_covered_display": "0", "num_branches": 2, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 2, "percent_branches_covered": 0.0, "percent_branches_covered_display": "0"}, "missing_lines": [52, 57, 58], "excluded_lines": [], "start_line": 50, "executed_branches": [], "missing_branches": [[57, -50], [57, 58]]}, "GoeChargerNumber.native_value": {"executed_lines": [62, 63, 64, 65, 66, 68, 70, 71, 72, 73, 74, 76, 77, 78, 80, 81, 83, 84, 87, 89], "summary": {"covered_lines": 20, "num_statements": 20, "percent_covered": 96.875, "percent_covered_display": "97", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 12, "num_partial_branches": 1, "covered_branches": 11, "missing_branches": 1, "percent_branches_covered": 91.66666666666667, "percent_branches_covered_display": "92"}, "missing_lines": [], "excluded_lines": [], "start_line": 61, "executed_branches": [[64, 65], [65, 66], [65, 68], [70, 71], [70, 72], [72, 73], [72, 77], [73, 74], [73, 76], [77, 78], [77, 87]], "missing_branches": [[64, 70]]}, "GoeChargerNumber.async_set_native_value": {"executed_lines": [93, 94, 96, 98, 99, 100, 103, 104, 105, 108, 110, 113, 114, 115, 118, 119, 120, 123, 125, 126], "summary": {"covered_lines": 20, "num_statements": 20, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 14, "num_partial_branches": 0, "covered_branches": 14, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 91, "executed_branches": [[94, 96], [94, 113], [98, 99], [98, 100], [100, 103], [100, 104], [104, 105], [104, 108], [113, 114], [113, 115], [115, 118], [115, 119], [119, 120], [119, 123]], "missing_branches": []}, "GoeChargerNumber.check_amp_min_max": {"executed_lines": [129, 130, 131, 133, 134, 135, 137, 138, 139, 140, 142, 143, 144, 146, 147, 148], "summary": {"covered_lines": 16, "num_statements": 16, "percent_covered": 96.15384615384616, "percent_covered_display": "96", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 10, "num_partial_branches": 1, "covered_branches": 9, "missing_branches": 1, "percent_branches_covered": 90.0, "percent_branches_covered_display": "90"}, "missing_lines": [], "excluded_lines": [], "start_line": 128, "executed_branches": [[129, -128], [129, 130], [131, -128], [131, 133], [138, 139], [138, 146], [143, 144], [146, -128], [146, 147]], "missing_branches": [[143, 146]]}, "": {"executed_lines": [1, 2, 4, 10, 11, 12, 13, 15, 16, 17, 19, 21, 46, 47, 50, 60, 61, 91, 128], "summary": {"covered_lines": 19, "num_statements": 19, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 1, "executed_branches": [], "missing_branches": []}}, "classes": {"GoeChargerNumber": {"executed_lines": [48, 62, 63, 64, 65, 66, 68, 70, 71, 72, 73, 74, 76, 77, 78, 80, 81, 83, 84, 87, 89, 93, 94, 96, 98, 99, 100, 103, 104, 105, 108, 110, 113, 114, 115, 118, 119, 120, 123, 125, 126, 129, 130, 131, 133, 134, 135, 137, 138, 139, 140, 142, 143, 144, 146, 147, 148], "summary": {"covered_lines": 57, "num_statements": 60, "percent_covered": 92.85714285714286, "percent_covered_display": "93", "missing_lines": 3, "excluded_lines": 0, "percent_statements_covered": 95.0, "percent_statements_covered_display": "95", "num_branches": 38, "num_partial_branches": 2, "covered_branches": 34, "missing_branches": 4, "percent_branches_covered": 89.47368421052632, "percent_branches_covered_display": "89"}, "missing_lines": [52, 57, 58], "excluded_lines": [], "start_line": 46, "executed_branches": [[64, 65], [65, 66], [65, 68], [70, 71], [70, 72], [72, 73], [72, 77], [73, 74], [73, 76], [77, 78], [77, 87], [94, 96], [94, 113], [98, 99], [98, 100], [100, 103], [100, 104], [104, 105], [104, 108], [113, 114], [113, 115], [115, 118], [115, 119], [119, 120], [119, 123], [129, -128], [129, 130], [131, -128], [131, 133], [138, 139], [138, 146], [143, 144], [146, -128], [146, 147]], "missing_branches": [[57, -50], [57, 58], [64, 70], [143, 146]]}, "": {"executed_lines": [1, 2, 4, 10, 11, 12, 13, 15, 16, 17, 19, 21, 46, 47, 50, 60, 61, 91, 128], "summary": {"covered_lines": 19, "num_statements": 34, "percent_covered": 43.18181818181818, "percent_covered_display": "43", "missing_lines": 15, "excluded_lines": 0, "percent_statements_covered": 55.88235294117647, "percent_statements_covered_display": "56", "num_branches": 10, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 10, "percent_branches_covered": 0.0, "percent_branches_covered_display": "0"}, "missing_lines": [22, 23, 24, 26, 27, 28, 31, 32, 34, 36, 37, 39, 40, 41, 43], "excluded_lines": [], "start_line": 1, "executed_branches": [], "missing_branches": [[26, 27], [26, 39], [27, 28], [27, 43], [28, 31], [28, 36], [31, 32], [31, 34], [39, 40], [39, 43]]}}}, "custom_components/goecharger_api2/pygoecharger_ha/__init__.py": {"executed_lines": [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 14, 15, 16, 18, 43, 45, 47, 49, 50, 51, 52, 53, 56, 59, 62, 64, 66, 67, 69, 71, 72, 73, 76, 77, 79, 81, 82, 84, 85, 86, 88, 89, 90, 91, 93, 96, 97, 98, 100, 102, 106, 108, 110, 111, 112, 113, 114, 117, 119, 121, 122, 123, 124, 126, 127, 128, 131, 133, 137, 138, 140, 142, 145, 146, 148, 149, 152, 153, 155, 156, 158, 160, 164, 165, 168, 169, 170, 172, 173, 174, 176, 177, 178, 179, 180, 181, 182, 183, 184, 185, 186, 188, 189, 190, 191, 192, 194, 195, 196, 198, 199, 201, 202, 203, 204, 206, 208, 209, 210, 211, 212, 213, 216, 217, 218, 219, 220, 221, 222, 223, 224, 225, 226, 227, 228, 229, 230, 231, 232, 234, 235, 237, 238, 239, 240, 241, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 253, 255, 256, 257, 258, 259, 262, 263, 264, 267, 268, 271, 273, 275, 276, 279, 281, 282, 283, 284, 285, 286, 287, 289, 291, 292, 293, 295, 304, 305, 306, 307, 309, 310, 311, 312, 314, 315, 316, 317, 319, 320, 323, 324, 326, 327, 329, 331, 332, 333, 335, 336, 337, 338, 339, 340, 342, 343, 344, 345, 348, 350, 351, 353, 355, 357, 361, 362, 365, 367, 368, 371, 372, 374, 375, 376, 377, 378, 380, 381, 382, 383, 384, 385, 387, 388, 389, 390, 392, 393, 394, 396, 397, 398, 403, 404, 405, 406, 407, 408, 416, 417, 420, 422, 423, 424, 426, 427, 428, 429, 431, 433, 434, 435, 436, 437, 438, 440, 441, 442, 443, 444, 445, 446, 447, 448, 449, 451, 452, 453, 454, 455, 457, 458, 460, 461, 467, 473, 475, 476, 477, 478, 480, 481, 482, 483, 484, 485, 486, 487, 489, 490, 496, 502, 504, 505, 507, 508, 510, 512, 513, 514, 516, 518, 521, 522, 523, 524, 525, 530, 532, 533, 534, 535, 536, 537, 539, 541, 543, 544, 545, 546, 547, 549, 550, 552, 553, 554, 555, 556, 557, 558, 559, 560, 562, 563, 565, 566, 568, 570, 571, 572, 573, 574, 575, 576, 578, 579, 580, 582, 583, 584, 586, 588, 589, 594, 595, 596, 597, 603, 610, 617, 618, 619, 621, 622, 623, 624, 626, 627, 629, 631, 632, 634, 636, 637, 638, 639, 640, 641, 642, 643, 645, 647, 649, 650, 651, 653, 654, 655, 656, 657, 658, 661, 662, 663, 665, 667, 668, 670, 677, 678, 682, 685, 686, 688, 690, 692, 693, 694, 696, 698, 699, 700, 702, 703, 704, 707, 709, 710, 711, 712, 713, 714, 715, 716, 717, 718, 719, 721, 723, 725, 726, 727, 728, 729, 731, 733, 734, 735, 743, 751, 753, 755, 756, 757, 769, 770, 773, 774, 776, 778, 780, 781, 782, 784, 785, 786, 787, 788, 790, 791, 793, 795, 796, 797, 798, 801, 802, 803, 804, 805, 806, 807, 808, 809, 811, 812, 813, 814, 815, 816, 817, 818, 820, 821, 823, 824, 825, 826, 828, 829, 830, 831, 832, 835, 836, 837, 839, 840, 841, 842, 843, 845, 846, 847, 848, 851, 854, 855, 856, 858, 859, 860, 861, 862, 865, 871, 872, 873, 875, 876, 877, 878, 879, 880, 881, 884, 885, 888, 907, 908, 911, 913, 914, 915, 916, 917, 918, 919, 921, 922, 923, 924, 925, 926, 927, 928, 929, 931, 932, 933, 936, 937, 939, 940, 941, 942, 943, 945, 946, 948, 949, 950, 951, 953, 954, 956, 957, 958, 961, 964, 966, 967, 970, 971, 973, 974, 975, 976, 977, 978, 979, 980, 981, 982, 983, 984, 985, 986, 987, 988, 990, 992, 993, 994, 995, 1002, 1003, 1004, 1008, 1010, 1011, 1012, 1013, 1014, 1016, 1017, 1018, 1020, 1021, 1022, 1023, 1024, 1027, 1028, 1029, 1030, 1032, 1034, 1035, 1037, 1038, 1039, 1041, 1043, 1044, 1045, 1048, 1049, 1050, 1051, 1052, 1054, 1055, 1056, 1057, 1058, 1059, 1062, 1064, 1065, 1070, 1072, 1074, 1075, 1084, 1086, 1087, 1088, 1091, 1093, 1094, 1095, 1096, 1097, 1098, 1099, 1100, 1101, 1102, 1103, 1104, 1105, 1108, 1109, 1110, 1111], "summary": {"covered_lines": 687, "num_statements": 721, "percent_covered": 93.93939393939394, "percent_covered_display": "94", "missing_lines": 34, "excluded_lines": 0, "percent_statements_covered": 95.28432732316227, "percent_statements_covered_display": "95", "num_branches": 302, "num_partial_branches": 26, "covered_branches": 274, "missing_branches": 28, "percent_branches_covered": 90.72847682119205, "percent_branches_covered_display": "91"}, "missing_lines": [116, 463, 464, 468, 469, 470, 471, 492, 493, 498, 499, 500, 501, 591, 592, 598, 599, 600, 601, 605, 606, 607, 608, 705, 866, 867, 868, 869, 996, 997, 1060, 1067, 1106, 1107], "excluded_lines": [], "executed_branches": [[51, 52], [52, 53], [52, 56], [71, 72], [71, 76], [111, 112], [111, 117], [113, 114], [123, -119], [123, 124], [140, 142], [140, 160], [145, 146], [145, 148], [152, 153], [152, 172], [155, 156], [155, 158], [160, 164], [160, 172], [168, 169], [168, 172], [172, 173], [172, 176], [176, 177], [176, 188], [203, 204], [203, 206], [238, 239], [238, 240], [262, -255], [262, 263], [263, -255], [263, 264], [268, 271], [268, 273], [276, 279], [276, 281], [281, 282], [281, 291], [283, 284], [283, 285], [285, 286], [285, 287], [287, 281], [287, 289], [291, 292], [291, 295], [295, 304], [295, 317], [305, 306], [305, 309], [309, 310], [309, 314], [323, 324], [323, 326], [326, 327], [326, 329], [333, 335], [333, 338], [335, 336], [335, 342], [336, 337], [336, 342], [338, 339], [339, 340], [339, 342], [342, 343], [342, 371], [344, 345], [344, 348], [350, 351], [350, 353], [355, -331], [355, 357], [361, 362], [361, 365], [365, -331], [365, 367], [371, 372], [371, 374], [375, 376], [375, 377], [377, 378], [384, -380], [384, 385], [388, 389], [388, 420], [389, 390], [389, 392], [393, 394], [393, 396], [396, 397], [396, 416], [398, -387], [398, 403], [403, -387], [403, 404], [404, 405], [406, 403], [406, 407], [428, 429], [437, 438], [437, 440], [443, 444], [443, 467], [446, 447], [446, 473], [448, 449], [448, 451], [453, 454], [453, 457], [454, 453], [454, 455], [477, 478], [477, 480], [483, 484], [483, 496], [486, 487], [486, 502], [507, 508], [507, 518], [508, 510], [508, 512], [512, 513], [512, 516], [518, 521], [518, 532], [522, 523], [522, 524], [524, 525], [524, 530], [532, 533], [532, 534], [534, 535], [534, 536], [536, 537], [536, 539], [544, 545], [544, 552], [545, 546], [545, 549], [553, 554], [553, 560], [554, 555], [554, 556], [556, 557], [556, 558], [558, 553], [558, 559], [565, 566], [565, 568], [572, 573], [572, 594], [575, 576], [575, 610], [576, 578], [576, 586], [578, 579], [578, 582], [582, 583], [582, 584], [594, 595], [594, 603], [622, 623], [622, 626], [631, 632], [631, 634], [638, 639], [638, 647], [650, 651], [650, 653], [654, -653], [654, 655], [656, 657], [656, 661], [661, 662], [668, 670], [668, 678], [678, 682], [678, 688], [698, 699], [698, 703], [699, 700], [699, 702], [703, 704], [709, 710], [709, 714], [714, 715], [714, 716], [716, 717], [716, 718], [718, 719], [718, 721], [726, 727], [726, 729], [753, 755], [753, 773], [784, 785], [784, 790], [786, 787], [790, 791], [790, 793], [805, 806], [805, 811], [807, 808], [812, 813], [812, 820], [830, 831], [839, 840], [839, 845], [841, 842], [847, 848], [847, 858], [855, 856], [855, 858], [858, 859], [858, 865], [860, 861], [865, 871], [877, 878], [877, 884], [879, 880], [917, 918], [925, 926], [925, 931], [927, 928], [936, 937], [936, 990], [939, 940], [939, 948], [948, 949], [948, 956], [956, 957], [956, 961], [964, 936], [964, 966], [970, 936], [970, 971], [975, 976], [975, 990], [979, 980], [983, 984], [987, 988], [1002, 1003], [1002, 1008], [1020, 1021], [1020, 1037], [1022, 1023], [1022, 1072], [1023, 1024], [1023, 1027], [1028, 1029], [1028, 1032], [1037, 1038], [1037, 1048], [1039, 1041], [1039, 1072], [1043, 1044], [1043, 1072], [1048, 1049], [1048, 1070], [1050, 1051], [1054, 1055], [1054, 1064], [1056, 1057], [1059, 1062], [1064, 1065], [1086, 1087], [1086, 1091], [1095, 1096], [1099, 1100], [1099, 1102], [1105, 1108]], "missing_branches": [[51, 66], [113, 116], [338, 342], [377, -331], [404, 403], [428, 431], [661, -653], [703, 705], [786, 788], [807, 809], [830, 832], [841, 843], [860, 862], [865, 866], [867, 868], [867, 869], [879, 881], [917, 919], [927, 929], [979, 990], [983, 990], [987, 990], [1050, 1054], [1056, 1072], [1059, 1060], [1064, 1067], [1095, 1111], [1105, 1106]], "functions": {"TargetedEvent.__init__": {"executed_lines": [50, 51, 52, 53, 56, 59, 62, 64, 66, 67], "summary": {"covered_lines": 10, "num_statements": 10, "percent_covered": 92.85714285714286, "percent_covered_display": "93", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 4, "num_partial_branches": 1, "covered_branches": 3, "missing_branches": 1, "percent_branches_covered": 75.0, "percent_branches_covered_display": "75"}, "missing_lines": [], "excluded_lines": [], "start_line": 49, "executed_branches": [[51, 52], [52, 53], [52, 56]], "missing_branches": [[51, 66]]}, "TargetedEvent.check_and_set": {"executed_lines": [71, 72, 73, 76, 77, 79], "summary": {"covered_lines": 6, "num_statements": 6, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 2, "num_partial_branches": 0, "covered_branches": 2, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 69, "executed_branches": [[71, 72], [71, 76]], "missing_branches": []}, "TargetedEvent.has_errors": {"executed_lines": [82], "summary": {"covered_lines": 1, "num_statements": 1, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 81, "executed_branches": [], "missing_branches": []}, "TargetedEvent.set_error": {"executed_lines": [85, 86], "summary": {"covered_lines": 2, "num_statements": 2, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 84, "executed_branches": [], "missing_branches": []}, "NonNullDict.none_null_get": {"executed_lines": [90, 91], "summary": {"covered_lines": 2, "num_statements": 2, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 89, "executed_branches": [], "missing_branches": []}, "ExpiringStore.__init__": {"executed_lines": [97, 98], "summary": {"covered_lines": 2, "num_statements": 2, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 96, "executed_branches": [], "missing_branches": []}, "ExpiringStore.set": {"executed_lines": [102, 106], "summary": {"covered_lines": 2, "num_statements": 2, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 100, "executed_branches": [], "missing_branches": []}, "ExpiringStore.get": {"executed_lines": [110, 111, 112, 113, 114, 117], "summary": {"covered_lines": 6, "num_statements": 7, "percent_covered": 81.81818181818181, "percent_covered_display": "82", "missing_lines": 1, "excluded_lines": 0, "percent_statements_covered": 85.71428571428571, "percent_statements_covered_display": "86", "num_branches": 4, "num_partial_branches": 1, "covered_branches": 3, "missing_branches": 1, "percent_branches_covered": 75.0, "percent_branches_covered_display": "75"}, "missing_lines": [116], "excluded_lines": [], "start_line": 108, "executed_branches": [[111, 112], [111, 117], [113, 114]], "missing_branches": [[113, 116]]}, "ExpiringStore._cleanup": {"executed_lines": [121, 122, 123, 124], "summary": {"covered_lines": 4, "num_statements": 4, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 2, "num_partial_branches": 0, "covered_branches": 2, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 119, "executed_branches": [[123, -119], [123, 124]], "missing_branches": []}, "ExpiringStore.__len__": {"executed_lines": [127, 128], "summary": {"covered_lines": 2, "num_statements": 2, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 126, "executed_branches": [], "missing_branches": []}, "GoeChargerApiV2Bridge.__init__": {"executed_lines": [137, 138, 140, 142, 145, 146, 148, 149, 152, 153, 155, 156, 158, 160, 164, 165, 168, 169, 170, 172, 173, 174, 176, 177, 178, 179, 180, 181, 182, 183, 184, 185, 186, 188, 189, 190, 191, 192, 194, 195, 196, 198, 199, 201, 202, 203, 204, 206, 208, 209, 210, 211, 212, 213, 216, 217, 218, 219, 220, 221, 222, 223, 224, 225, 226, 227, 228, 229, 230, 231, 232], "summary": {"covered_lines": 71, "num_statements": 71, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 18, "num_partial_branches": 0, "covered_branches": 18, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 133, "executed_branches": [[140, 142], [140, 160], [145, 146], [145, 148], [152, 153], [152, 172], [155, 156], [155, 158], [160, 164], [160, 172], [168, 169], [168, 172], [172, 173], [172, 176], [176, 177], [176, 188], [203, 204], [203, 206]], "missing_branches": []}, "GoeChargerApiV2Bridge.available_fields": {"executed_lines": [235], "summary": {"covered_lines": 1, "num_statements": 1, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 234, "executed_branches": [], "missing_branches": []}, "GoeChargerApiV2Bridge.clear_data": {"executed_lines": [238, 239, 240, 241, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 253], "summary": {"covered_lines": 16, "num_statements": 16, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 2, "num_partial_branches": 0, "covered_branches": 2, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 237, "executed_branches": [[238, 239], [238, 240]], "missing_branches": []}, "GoeChargerApiV2Bridge.reset_stored_update_ts": {"executed_lines": [256, 257, 258, 259, 262, 263, 264], "summary": {"covered_lines": 7, "num_statements": 7, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 4, "num_partial_branches": 0, "covered_branches": 4, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 255, "executed_branches": [[262, -255], [262, 263], [263, -255], [263, 264]], "missing_branches": []}, "GoeChargerApiV2Bridge.read_system": {"executed_lines": [268, 271, 273], "summary": {"covered_lines": 3, "num_statements": 3, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 2, "num_partial_branches": 0, "covered_branches": 2, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 267, "executed_branches": [[268, 271], [268, 273]], "missing_branches": []}, "GoeChargerApiV2Bridge.read_versions": {"executed_lines": [276, 279, 281, 282, 283, 284, 285, 286, 287, 289, 291, 292, 293, 295, 304, 305, 306, 307, 309, 310, 311, 312, 314, 315, 316, 317], "summary": {"covered_lines": 26, "num_statements": 26, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 18, "num_partial_branches": 0, "covered_branches": 18, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 275, "executed_branches": [[276, 279], [276, 281], [281, 282], [281, 291], [283, 284], [283, 285], [285, 286], [285, 287], [287, 281], [287, 289], [291, 292], [291, 295], [295, 304], [295, 317], [305, 306], [305, 309], [309, 310], [309, 314]], "missing_branches": []}, "GoeChargerApiV2Bridge.read_all": {"executed_lines": [320, 323, 324, 326, 327, 329], "summary": {"covered_lines": 6, "num_statements": 6, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 4, "num_partial_branches": 0, "covered_branches": 4, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 319, "executed_branches": [[323, 324], [323, 326], [326, 327], [326, 329]], "missing_branches": []}, "GoeChargerApiV2Bridge.read_all_states": {"executed_lines": [332, 333, 335, 336, 337, 338, 339, 340, 342, 343, 344, 345, 348, 350, 351, 353, 355, 357, 361, 362, 365, 367, 368, 371, 372, 374, 375, 376, 377, 378], "summary": {"covered_lines": 30, "num_statements": 30, "percent_covered": 96.55172413793103, "percent_covered_display": "97", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 28, "num_partial_branches": 2, "covered_branches": 26, "missing_branches": 2, "percent_branches_covered": 92.85714285714286, "percent_branches_covered_display": "93"}, "missing_lines": [], "excluded_lines": [], "start_line": 331, "executed_branches": [[333, 335], [333, 338], [335, 336], [335, 342], [336, 337], [336, 342], [338, 339], [339, 340], [339, 342], [342, 343], [342, 371], [344, 345], [344, 348], [350, 351], [350, 353], [355, -331], [355, 357], [361, 362], [361, 365], [365, -331], [365, 367], [371, 372], [371, 374], [375, 376], [375, 377], [377, 378]], "missing_branches": [[338, 342], [377, -331]]}, "GoeChargerApiV2Bridge.force_config_update": {"executed_lines": [381, 382, 383, 384, 385], "summary": {"covered_lines": 5, "num_statements": 5, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 2, "num_partial_branches": 0, "covered_branches": 2, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 380, "executed_branches": [[384, -380], [384, 385]], "missing_branches": []}, "GoeChargerApiV2Bridge.read_all_config": {"executed_lines": [388, 389, 390, 392, 393, 394, 396, 397, 398, 403, 404, 405, 406, 407, 408, 416, 417, 420], "summary": {"covered_lines": 18, "num_statements": 18, "percent_covered": 97.05882352941177, "percent_covered_display": "97", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 16, "num_partial_branches": 1, "covered_branches": 15, "missing_branches": 1, "percent_branches_covered": 93.75, "percent_branches_covered_display": "94"}, "missing_lines": [], "excluded_lines": [], "start_line": 387, "executed_branches": [[388, 389], [388, 420], [389, 390], [389, 392], [393, 394], [393, 396], [396, 397], [396, 416], [398, -387], [398, 403], [403, -387], [403, 404], [404, 405], [406, 403], [406, 407]], "missing_branches": [[404, 403]]}, "GoeChargerApiV2Bridge._read_filtered_data_from_ws_source": {"executed_lines": [423, 424, 426, 427, 428, 429, 431], "summary": {"covered_lines": 7, "num_statements": 7, "percent_covered": 88.88888888888889, "percent_covered_display": "89", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 2, "num_partial_branches": 1, "covered_branches": 1, "missing_branches": 1, "percent_branches_covered": 50.0, "percent_branches_covered_display": "50"}, "missing_lines": [], "excluded_lines": [], "start_line": 422, "executed_branches": [[428, 429]], "missing_branches": [[428, 431]]}, "GoeChargerApiV2Bridge._read_filtered_data": {"executed_lines": [434, 435, 436, 437, 438, 440, 441, 442, 443, 444, 445, 446, 447, 448, 449, 451, 452, 453, 454, 455, 457, 458, 460, 461, 467, 473], "summary": {"covered_lines": 26, "num_statements": 32, "percent_covered": 86.36363636363636, "percent_covered_display": "86", "missing_lines": 6, "excluded_lines": 0, "percent_statements_covered": 81.25, "percent_statements_covered_display": "81", "num_branches": 12, "num_partial_branches": 0, "covered_branches": 12, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [463, 464, 468, 469, 470, 471], "excluded_lines": [], "start_line": 433, "executed_branches": [[437, 438], [437, 440], [443, 444], [443, 467], [446, 447], [446, 473], [448, 449], [448, 451], [453, 454], [453, 457], [454, 453], [454, 455]], "missing_branches": []}, "GoeChargerApiV2Bridge._read_all_data": {"executed_lines": [476, 477, 478, 480, 481, 482, 483, 484, 485, 486, 487, 489, 490, 496, 502], "summary": {"covered_lines": 15, "num_statements": 21, "percent_covered": 77.77777777777777, "percent_covered_display": "78", "missing_lines": 6, "excluded_lines": 0, "percent_statements_covered": 71.42857142857143, "percent_statements_covered_display": "71", "num_branches": 6, "num_partial_branches": 0, "covered_branches": 6, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [492, 493, 498, 499, 500, 501], "excluded_lines": [], "start_line": 475, "executed_branches": [[477, 478], [477, 480], [483, 484], [483, 496], [486, 487], [486, 502]], "missing_branches": []}, "GoeChargerApiV2Bridge.write_value_to_key": {"executed_lines": [505, 507, 508, 510, 512, 513, 514, 516, 518, 521, 522, 523, 524, 525, 530, 532, 533, 534, 535, 536, 537, 539, 541], "summary": {"covered_lines": 23, "num_statements": 23, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 18, "num_partial_branches": 0, "covered_branches": 18, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 504, "executed_branches": [[507, 508], [507, 518], [508, 510], [508, 512], [512, 513], [512, 516], [518, 521], [518, 532], [522, 523], [522, 524], [524, 525], [524, 530], [532, 533], [532, 534], [534, 535], [534, 536], [536, 537], [536, 539]], "missing_branches": []}, "GoeChargerApiV2Bridge.write_multiple_values_to_keys": {"executed_lines": [544, 545, 546, 547, 549, 550, 552, 553, 554, 555, 556, 557, 558, 559, 560], "summary": {"covered_lines": 15, "num_statements": 15, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 12, "num_partial_branches": 0, "covered_branches": 12, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 543, "executed_branches": [[544, 545], [544, 552], [545, 546], [545, 549], [553, 554], [553, 560], [554, 555], [554, 556], [556, 557], [556, 558], [558, 553], [558, 559]], "missing_branches": []}, "GoeChargerApiV2Bridge._write_values_int": {"executed_lines": [563, 565, 566, 568, 570, 571, 572, 573, 574, 575, 576, 578, 579, 580, 582, 583, 584, 586, 588, 589, 594, 595, 596, 597, 603, 610], "summary": {"covered_lines": 26, "num_statements": 36, "percent_covered": 80.0, "percent_covered_display": "80", "missing_lines": 10, "excluded_lines": 0, "percent_statements_covered": 72.22222222222223, "percent_statements_covered_display": "72", "num_branches": 14, "num_partial_branches": 0, "covered_branches": 14, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [591, 592, 598, 599, 600, 601, 605, 606, 607, 608], "excluded_lines": [], "start_line": 562, "executed_branches": [[565, 566], [565, 568], [572, 573], [572, 594], [575, 576], [575, 610], [576, 578], [576, 586], [578, 579], [578, 582], [582, 583], [582, 584], [594, 595], [594, 603]], "missing_branches": []}, "GoeChargerApiV2Bridge.ws_set_coordinator": {"executed_lines": [618, 619], "summary": {"covered_lines": 2, "num_statements": 2, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 617, "executed_branches": [], "missing_branches": []}, "GoeChargerApiV2Bridge.ws_check_last_update": {"executed_lines": [622, 623, 624, 626, 627], "summary": {"covered_lines": 5, "num_statements": 5, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 2, "num_partial_branches": 0, "covered_branches": 2, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 621, "executed_branches": [[622, 623], [622, 626]], "missing_branches": []}, "GoeChargerApiV2Bridge.ws_close": {"executed_lines": [631, 632, 634, 636, 637, 638, 639, 640, 641, 642, 643, 645, 647], "summary": {"covered_lines": 13, "num_statements": 13, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 4, "num_partial_branches": 0, "covered_branches": 4, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 629, "executed_branches": [[631, 632], [631, 634], [638, 639], [638, 647]], "missing_branches": []}, "GoeChargerApiV2Bridge._ws_notify_for_new_data": {"executed_lines": [650, 651, 653, 665], "summary": {"covered_lines": 4, "num_statements": 4, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 2, "num_partial_branches": 0, "covered_branches": 2, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 649, "executed_branches": [[650, 651], [650, 653]], "missing_branches": []}, "GoeChargerApiV2Bridge._ws_notify_for_new_data._ws_debounce_coordinator_update": {"executed_lines": [654, 655, 656, 657, 658, 661, 662, 663], "summary": {"covered_lines": 8, "num_statements": 8, "percent_covered": 92.85714285714286, "percent_covered_display": "93", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 6, "num_partial_branches": 1, "covered_branches": 5, "missing_branches": 1, "percent_branches_covered": 83.33333333333333, "percent_branches_covered_display": "83"}, "missing_lines": [], "excluded_lines": [], "start_line": 653, "executed_branches": [[654, -653], [654, 655], [656, 657], [656, 661], [661, 662]], "missing_branches": [[661, -653]]}, "GoeChargerApiV2Bridge._ws_compute_hashed_password": {"executed_lines": [668, 670, 677, 678, 682, 685, 686, 688], "summary": {"covered_lines": 8, "num_statements": 8, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 4, "num_partial_branches": 0, "covered_branches": 4, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 667, "executed_branches": [[668, 670], [668, 678], [678, 682], [678, 688]], "missing_branches": []}, "GoeChargerApiV2Bridge._ws_compute_auth_hash": {"executed_lines": [692, 693, 694], "summary": {"covered_lines": 3, "num_statements": 3, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 690, "executed_branches": [], "missing_branches": []}, "GoeChargerApiV2Bridge._ws_decode_message": {"executed_lines": [698, 699, 700, 702, 703, 704], "summary": {"covered_lines": 6, "num_statements": 7, "percent_covered": 84.61538461538461, "percent_covered_display": "85", "missing_lines": 1, "excluded_lines": 0, "percent_statements_covered": 85.71428571428571, "percent_statements_covered_display": "86", "num_branches": 6, "num_partial_branches": 1, "covered_branches": 5, "missing_branches": 1, "percent_branches_covered": 83.33333333333333, "percent_branches_covered_display": "83"}, "missing_lines": [705], "excluded_lines": [], "start_line": 696, "executed_branches": [[698, 699], [698, 703], [699, 700], [699, 702], [703, 704]], "missing_branches": [[703, 705]]}, "GoeChargerApiV2Bridge._ws_normalize_value": {"executed_lines": [709, 710, 711, 712, 713, 714, 715, 716, 717, 718, 719, 721], "summary": {"covered_lines": 12, "num_statements": 12, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 8, "num_partial_branches": 0, "covered_branches": 8, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 707, "executed_branches": [[709, 710], [709, 714], [714, 715], [714, 716], [716, 717], [716, 718], [718, 719], [718, 721]], "missing_branches": []}, "GoeChargerApiV2Bridge._ws_normalize_dict": {"executed_lines": [725, 726, 727, 728, 729], "summary": {"covered_lines": 5, "num_statements": 5, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 2, "num_partial_branches": 0, "covered_branches": 2, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 723, "executed_branches": [[726, 727], [726, 729]], "missing_branches": []}, "GoeChargerApiV2Bridge._ws_send_command": {"executed_lines": [733, 734, 735, 743, 751, 753, 755, 756, 757, 769, 770, 773, 774, 776], "summary": {"covered_lines": 14, "num_statements": 14, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 2, "num_partial_branches": 0, "covered_branches": 2, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 731, "executed_branches": [[753, 755], [753, 773]], "missing_branches": []}, "GoeChargerApiV2Bridge.ws_connect": {"executed_lines": [780, 781, 782, 784, 785, 786, 787, 788, 790, 791, 793, 795, 796, 797, 798, 801, 802, 803, 804, 805, 806, 807, 808, 809, 811, 812, 813, 814, 815, 816, 817, 818, 820, 821, 823, 824, 825, 826, 828, 829, 830, 831, 832, 835, 836, 837, 839, 840, 841, 842, 843, 845, 846, 847, 848, 851, 854, 855, 856, 858, 859, 860, 861, 862, 865, 871, 872, 873, 875, 876, 877, 878, 879, 880, 881, 884, 885, 888, 907, 908, 911, 913, 914, 915, 916, 917, 918, 919, 921, 922, 923, 924, 925, 926, 927, 928, 929, 931, 932, 933, 936, 937, 939, 940, 941, 942, 943, 945, 946, 948, 949, 950, 951, 953, 954, 956, 957, 958, 961, 964, 966, 967, 970, 971, 973, 974, 975, 976, 977, 978, 979, 980, 981, 982, 983, 984, 985, 986, 987, 988, 990, 992, 993, 994, 995, 1002, 1003, 1004, 1008, 1010, 1011, 1012, 1013, 1014], "summary": {"covered_lines": 154, "num_statements": 160, "percent_covered": 90.990990990991, "percent_covered_display": "91", "missing_lines": 6, "excluded_lines": 0, "percent_statements_covered": 96.25, "percent_statements_covered_display": "96", "num_branches": 62, "num_partial_branches": 12, "covered_branches": 48, "missing_branches": 14, "percent_branches_covered": 77.41935483870968, "percent_branches_covered_display": "77"}, "missing_lines": [866, 867, 868, 869, 996, 997], "excluded_lines": [], "start_line": 778, "executed_branches": [[784, 785], [784, 790], [786, 787], [790, 791], [790, 793], [805, 806], [805, 811], [807, 808], [812, 813], [812, 820], [830, 831], [839, 840], [839, 845], [841, 842], [847, 848], [847, 858], [855, 856], [855, 858], [858, 859], [858, 865], [860, 861], [865, 871], [877, 878], [877, 884], [879, 880], [917, 918], [925, 926], [925, 931], [927, 928], [936, 937], [936, 990], [939, 940], [939, 948], [948, 949], [948, 956], [956, 957], [956, 961], [964, 936], [964, 966], [970, 936], [970, 971], [975, 976], [975, 990], [979, 980], [983, 984], [987, 988], [1002, 1003], [1002, 1008]], "missing_branches": [[786, 788], [807, 809], [830, 832], [841, 843], [860, 862], [865, 866], [867, 868], [867, 869], [879, 881], [917, 919], [927, 929], [979, 990], [983, 990], [987, 990]]}, "GoeChargerApiV2Bridge.extract_ws_message_data": {"executed_lines": [1017, 1018, 1020, 1021, 1022, 1023, 1024, 1027, 1028, 1029, 1030, 1032, 1034, 1035, 1037, 1038, 1039, 1041, 1043, 1044, 1045, 1048, 1049, 1050, 1051, 1052, 1054, 1055, 1056, 1057, 1058, 1059, 1062, 1064, 1065, 1070, 1072], "summary": {"covered_lines": 37, "num_statements": 39, "percent_covered": 90.76923076923077, "percent_covered_display": "91", "missing_lines": 2, "excluded_lines": 0, "percent_statements_covered": 94.87179487179488, "percent_statements_covered_display": "95", "num_branches": 26, "num_partial_branches": 4, "covered_branches": 22, "missing_branches": 4, "percent_branches_covered": 84.61538461538461, "percent_branches_covered_display": "85"}, "missing_lines": [1060, 1067], "excluded_lines": [], "start_line": 1016, "executed_branches": [[1020, 1021], [1020, 1037], [1022, 1023], [1022, 1072], [1023, 1024], [1023, 1027], [1028, 1029], [1028, 1032], [1037, 1038], [1037, 1048], [1039, 1041], [1039, 1072], [1043, 1044], [1043, 1072], [1048, 1049], [1048, 1070], [1050, 1051], [1054, 1055], [1054, 1064], [1056, 1057], [1059, 1062], [1064, 1065]], "missing_branches": [[1050, 1054], [1056, 1072], [1059, 1060], [1064, 1067]]}, "bcryptjs_base64_encode_serial": {"executed_lines": [1084, 1086, 1087, 1088, 1091, 1093, 1094, 1095, 1096, 1097, 1098, 1099, 1100, 1101, 1102, 1103, 1104, 1105, 1108, 1109, 1110, 1111], "summary": {"covered_lines": 22, "num_statements": 24, "percent_covered": 87.5, "percent_covered_display": "88", "missing_lines": 2, "excluded_lines": 0, "percent_statements_covered": 91.66666666666667, "percent_statements_covered_display": "92", "num_branches": 8, "num_partial_branches": 2, "covered_branches": 6, "missing_branches": 2, "percent_branches_covered": 75.0, "percent_branches_covered_display": "75"}, "missing_lines": [1106, 1107], "excluded_lines": [], "start_line": 1075, "executed_branches": [[1086, 1087], [1086, 1091], [1095, 1096], [1099, 1100], [1099, 1102], [1105, 1108]], "missing_branches": [[1095, 1111], [1105, 1106]]}, "": {"executed_lines": [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 14, 15, 16, 18, 43, 45, 47, 49, 69, 81, 84, 88, 89, 93, 96, 100, 108, 119, 126, 131, 133, 234, 237, 255, 267, 275, 319, 331, 380, 387, 422, 433, 475, 504, 543, 562, 617, 621, 629, 649, 667, 690, 696, 707, 723, 731, 778, 1016, 1074, 1075], "summary": {"covered_lines": 62, "num_statements": 62, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 1, "executed_branches": [], "missing_branches": []}}, "classes": {"TargetedEvent": {"executed_lines": [50, 51, 52, 53, 56, 59, 62, 64, 66, 67, 71, 72, 73, 76, 77, 79, 82, 85, 86], "summary": {"covered_lines": 19, "num_statements": 19, "percent_covered": 96.0, "percent_covered_display": "96", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 6, "num_partial_branches": 1, "covered_branches": 5, "missing_branches": 1, "percent_branches_covered": 83.33333333333333, "percent_branches_covered_display": "83"}, "missing_lines": [], "excluded_lines": [], "start_line": 47, "executed_branches": [[51, 52], [52, 53], [52, 56], [71, 72], [71, 76]], "missing_branches": [[51, 66]]}, "NonNullDict": {"executed_lines": [90, 91], "summary": {"covered_lines": 2, "num_statements": 2, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 88, "executed_branches": [], "missing_branches": []}, "ExpiringStore": {"executed_lines": [97, 98, 102, 106, 110, 111, 112, 113, 114, 117, 121, 122, 123, 124, 127, 128], "summary": {"covered_lines": 16, "num_statements": 17, "percent_covered": 91.30434782608695, "percent_covered_display": "91", "missing_lines": 1, "excluded_lines": 0, "percent_statements_covered": 94.11764705882354, "percent_statements_covered_display": "94", "num_branches": 6, "num_partial_branches": 1, "covered_branches": 5, "missing_branches": 1, "percent_branches_covered": 83.33333333333333, "percent_branches_covered_display": "83"}, "missing_lines": [116], "excluded_lines": [], "start_line": 93, "executed_branches": [[111, 112], [111, 117], [113, 114], [123, -119], [123, 124]], "missing_branches": [[113, 116]]}, "GoeChargerApiV2Bridge": {"executed_lines": [137, 138, 140, 142, 145, 146, 148, 149, 152, 153, 155, 156, 158, 160, 164, 165, 168, 169, 170, 172, 173, 174, 176, 177, 178, 179, 180, 181, 182, 183, 184, 185, 186, 188, 189, 190, 191, 192, 194, 195, 196, 198, 199, 201, 202, 203, 204, 206, 208, 209, 210, 211, 212, 213, 216, 217, 218, 219, 220, 221, 222, 223, 224, 225, 226, 227, 228, 229, 230, 231, 232, 235, 238, 239, 240, 241, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 253, 256, 257, 258, 259, 262, 263, 264, 268, 271, 273, 276, 279, 281, 282, 283, 284, 285, 286, 287, 289, 291, 292, 293, 295, 304, 305, 306, 307, 309, 310, 311, 312, 314, 315, 316, 317, 320, 323, 324, 326, 327, 329, 332, 333, 335, 336, 337, 338, 339, 340, 342, 343, 344, 345, 348, 350, 351, 353, 355, 357, 361, 362, 365, 367, 368, 371, 372, 374, 375, 376, 377, 378, 381, 382, 383, 384, 385, 388, 389, 390, 392, 393, 394, 396, 397, 398, 403, 404, 405, 406, 407, 408, 416, 417, 420, 423, 424, 426, 427, 428, 429, 431, 434, 435, 436, 437, 438, 440, 441, 442, 443, 444, 445, 446, 447, 448, 449, 451, 452, 453, 454, 455, 457, 458, 460, 461, 467, 473, 476, 477, 478, 480, 481, 482, 483, 484, 485, 486, 487, 489, 490, 496, 502, 505, 507, 508, 510, 512, 513, 514, 516, 518, 521, 522, 523, 524, 525, 530, 532, 533, 534, 535, 536, 537, 539, 541, 544, 545, 546, 547, 549, 550, 552, 553, 554, 555, 556, 557, 558, 559, 560, 563, 565, 566, 568, 570, 571, 572, 573, 574, 575, 576, 578, 579, 580, 582, 583, 584, 586, 588, 589, 594, 595, 596, 597, 603, 610, 618, 619, 622, 623, 624, 626, 627, 631, 632, 634, 636, 637, 638, 639, 640, 641, 642, 643, 645, 647, 650, 651, 653, 654, 655, 656, 657, 658, 661, 662, 663, 665, 668, 670, 677, 678, 682, 685, 686, 688, 692, 693, 694, 698, 699, 700, 702, 703, 704, 709, 710, 711, 712, 713, 714, 715, 716, 717, 718, 719, 721, 725, 726, 727, 728, 729, 733, 734, 735, 743, 751, 753, 755, 756, 757, 769, 770, 773, 774, 776, 780, 781, 782, 784, 785, 786, 787, 788, 790, 791, 793, 795, 796, 797, 798, 801, 802, 803, 804, 805, 806, 807, 808, 809, 811, 812, 813, 814, 815, 816, 817, 818, 820, 821, 823, 824, 825, 826, 828, 829, 830, 831, 832, 835, 836, 837, 839, 840, 841, 842, 843, 845, 846, 847, 848, 851, 854, 855, 856, 858, 859, 860, 861, 862, 865, 871, 872, 873, 875, 876, 877, 878, 879, 880, 881, 884, 885, 888, 907, 908, 911, 913, 914, 915, 916, 917, 918, 919, 921, 922, 923, 924, 925, 926, 927, 928, 929, 931, 932, 933, 936, 937, 939, 940, 941, 942, 943, 945, 946, 948, 949, 950, 951, 953, 954, 956, 957, 958, 961, 964, 966, 967, 970, 971, 973, 974, 975, 976, 977, 978, 979, 980, 981, 982, 983, 984, 985, 986, 987, 988, 990, 992, 993, 994, 995, 1002, 1003, 1004, 1008, 1010, 1011, 1012, 1013, 1014, 1017, 1018, 1020, 1021, 1022, 1023, 1024, 1027, 1028, 1029, 1030, 1032, 1034, 1035, 1037, 1038, 1039, 1041, 1043, 1044, 1045, 1048, 1049, 1050, 1051, 1052, 1054, 1055, 1056, 1057, 1058, 1059, 1062, 1064, 1065, 1070, 1072], "summary": {"covered_lines": 566, "num_statements": 597, "percent_covered": 93.7428896473265, "percent_covered_display": "94", "missing_lines": 31, "excluded_lines": 0, "percent_statements_covered": 94.80737018425461, "percent_statements_covered_display": "95", "num_branches": 282, "num_partial_branches": 22, "covered_branches": 258, "missing_branches": 24, "percent_branches_covered": 91.48936170212765, "percent_branches_covered_display": "91"}, "missing_lines": [463, 464, 468, 469, 470, 471, 492, 493, 498, 499, 500, 501, 591, 592, 598, 599, 600, 601, 605, 606, 607, 608, 705, 866, 867, 868, 869, 996, 997, 1060, 1067], "excluded_lines": [], "start_line": 131, "executed_branches": [[140, 142], [140, 160], [145, 146], [145, 148], [152, 153], [152, 172], [155, 156], [155, 158], [160, 164], [160, 172], [168, 169], [168, 172], [172, 173], [172, 176], [176, 177], [176, 188], [203, 204], [203, 206], [238, 239], [238, 240], [262, -255], [262, 263], [263, -255], [263, 264], [268, 271], [268, 273], [276, 279], [276, 281], [281, 282], [281, 291], [283, 284], [283, 285], [285, 286], [285, 287], [287, 281], [287, 289], [291, 292], [291, 295], [295, 304], [295, 317], [305, 306], [305, 309], [309, 310], [309, 314], [323, 324], [323, 326], [326, 327], [326, 329], [333, 335], [333, 338], [335, 336], [335, 342], [336, 337], [336, 342], [338, 339], [339, 340], [339, 342], [342, 343], [342, 371], [344, 345], [344, 348], [350, 351], [350, 353], [355, -331], [355, 357], [361, 362], [361, 365], [365, -331], [365, 367], [371, 372], [371, 374], [375, 376], [375, 377], [377, 378], [384, -380], [384, 385], [388, 389], [388, 420], [389, 390], [389, 392], [393, 394], [393, 396], [396, 397], [396, 416], [398, -387], [398, 403], [403, -387], [403, 404], [404, 405], [406, 403], [406, 407], [428, 429], [437, 438], [437, 440], [443, 444], [443, 467], [446, 447], [446, 473], [448, 449], [448, 451], [453, 454], [453, 457], [454, 453], [454, 455], [477, 478], [477, 480], [483, 484], [483, 496], [486, 487], [486, 502], [507, 508], [507, 518], [508, 510], [508, 512], [512, 513], [512, 516], [518, 521], [518, 532], [522, 523], [522, 524], [524, 525], [524, 530], [532, 533], [532, 534], [534, 535], [534, 536], [536, 537], [536, 539], [544, 545], [544, 552], [545, 546], [545, 549], [553, 554], [553, 560], [554, 555], [554, 556], [556, 557], [556, 558], [558, 553], [558, 559], [565, 566], [565, 568], [572, 573], [572, 594], [575, 576], [575, 610], [576, 578], [576, 586], [578, 579], [578, 582], [582, 583], [582, 584], [594, 595], [594, 603], [622, 623], [622, 626], [631, 632], [631, 634], [638, 639], [638, 647], [650, 651], [650, 653], [654, -653], [654, 655], [656, 657], [656, 661], [661, 662], [668, 670], [668, 678], [678, 682], [678, 688], [698, 699], [698, 703], [699, 700], [699, 702], [703, 704], [709, 710], [709, 714], [714, 715], [714, 716], [716, 717], [716, 718], [718, 719], [718, 721], [726, 727], [726, 729], [753, 755], [753, 773], [784, 785], [784, 790], [786, 787], [790, 791], [790, 793], [805, 806], [805, 811], [807, 808], [812, 813], [812, 820], [830, 831], [839, 840], [839, 845], [841, 842], [847, 848], [847, 858], [855, 856], [855, 858], [858, 859], [858, 865], [860, 861], [865, 871], [877, 878], [877, 884], [879, 880], [917, 918], [925, 926], [925, 931], [927, 928], [936, 937], [936, 990], [939, 940], [939, 948], [948, 949], [948, 956], [956, 957], [956, 961], [964, 936], [964, 966], [970, 936], [970, 971], [975, 976], [975, 990], [979, 980], [983, 984], [987, 988], [1002, 1003], [1002, 1008], [1020, 1021], [1020, 1037], [1022, 1023], [1022, 1072], [1023, 1024], [1023, 1027], [1028, 1029], [1028, 1032], [1037, 1038], [1037, 1048], [1039, 1041], [1039, 1072], [1043, 1044], [1043, 1072], [1048, 1049], [1048, 1070], [1050, 1051], [1054, 1055], [1054, 1064], [1056, 1057], [1059, 1062], [1064, 1065]], "missing_branches": [[338, 342], [377, -331], [404, 403], [428, 431], [661, -653], [703, 705], [786, 788], [807, 809], [830, 832], [841, 843], [860, 862], [865, 866], [867, 868], [867, 869], [879, 881], [917, 919], [927, 929], [979, 990], [983, 990], [987, 990], [1050, 1054], [1056, 1072], [1059, 1060], [1064, 1067]]}, "": {"executed_lines": [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 14, 15, 16, 18, 43, 45, 47, 49, 69, 81, 84, 88, 89, 93, 96, 100, 108, 119, 126, 131, 133, 234, 237, 255, 267, 275, 319, 331, 380, 387, 422, 433, 475, 504, 543, 562, 617, 621, 629, 649, 667, 690, 696, 707, 723, 731, 778, 1016, 1074, 1075, 1084, 1086, 1087, 1088, 1091, 1093, 1094, 1095, 1096, 1097, 1098, 1099, 1100, 1101, 1102, 1103, 1104, 1105, 1108, 1109, 1110, 1111], "summary": {"covered_lines": 84, "num_statements": 86, "percent_covered": 95.74468085106383, "percent_covered_display": "96", "missing_lines": 2, "excluded_lines": 0, "percent_statements_covered": 97.67441860465117, "percent_statements_covered_display": "98", "num_branches": 8, "num_partial_branches": 2, "covered_branches": 6, "missing_branches": 2, "percent_branches_covered": 75.0, "percent_branches_covered_display": "75"}, "missing_lines": [1106, 1107], "excluded_lines": [], "start_line": 1, "executed_branches": [[1086, 1087], [1086, 1091], [1095, 1096], [1099, 1100], [1099, 1102], [1105, 1108]], "missing_branches": [[1095, 1111], [1105, 1106]]}}}, "custom_components/goecharger_api2/pygoecharger_ha/const.py": {"executed_lines": [1, 2, 6, 11, 12, 13, 14, 16, 17, 18, 19, 22, 23, 24, 25, 26, 29, 31, 32, 34, 37, 39, 40, 41, 46, 47, 48, 51, 52, 53, 56, 57, 58, 60, 61, 62, 63, 64, 65, 66, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 90, 111], "summary": {"covered_lines": 61, "num_statements": 61, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "executed_branches": [], "missing_branches": [], "functions": {"": {"executed_lines": [1, 2, 6, 11, 12, 13, 14, 16, 17, 18, 19, 22, 23, 24, 25, 26, 29, 31, 32, 34, 37, 39, 40, 41, 46, 47, 48, 51, 52, 53, 56, 57, 58, 60, 61, 62, 63, 64, 65, 66, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 90, 111], "summary": {"covered_lines": 61, "num_statements": 61, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 1, "executed_branches": [], "missing_branches": []}}, "classes": {"INTG_TYPE": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 0, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 56, "executed_branches": [], "missing_branches": []}, "CAR_VALUES": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 0, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 60, "executed_branches": [], "missing_branches": []}, "CT_VALUES": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 0, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 70, "executed_branches": [], "missing_branches": []}, "": {"executed_lines": [1, 2, 6, 11, 12, 13, 14, 16, 17, 18, 19, 22, 23, 24, 25, 26, 29, 31, 32, 34, 37, 39, 40, 41, 46, 47, 48, 51, 52, 53, 56, 57, 58, 60, 61, 62, 63, 64, 65, 66, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 90, 111], "summary": {"covered_lines": 61, "num_statements": 61, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 1, "executed_branches": [], "missing_branches": []}}}, "custom_components/goecharger_api2/pygoecharger_ha/keys.py": {"executed_lines": [1, 2, 3, 9, 11, 13, 14, 15, 16, 17, 20, 23, 24, 27, 29, 32, 36, 38, 40, 42, 44, 46, 48, 50, 52, 54, 56, 58, 60, 62, 64, 66, 68, 70, 72, 74, 76, 78, 80, 82, 84, 95, 98, 100, 102, 104, 107, 109, 111, 113, 115, 117, 119, 121, 123, 125, 127, 129, 131, 133, 135, 137, 139, 141, 143, 145, 147, 149, 151, 153, 155, 157, 159, 161, 163, 165, 167, 169, 171, 173, 175, 179, 181, 183, 185, 187, 189, 191, 193, 195, 197, 199, 201, 203, 205, 207, 209, 211, 213, 215, 217, 219, 221, 223, 225, 227, 229, 232, 234, 236, 238, 240, 242, 244, 246, 248, 250, 252, 254, 256, 258, 260, 262, 264, 266, 268, 270, 272, 274, 276, 278, 280, 282, 284, 286, 288, 290, 292, 294, 296, 298, 300, 302, 304, 306, 308, 310, 312, 314, 316, 318, 320, 322, 324, 326, 328, 330, 332, 334, 336, 338, 340, 342, 344, 346, 350, 353, 354, 355, 359, 361, 366, 372, 378, 384, 390, 396, 402, 409, 411, 413, 415, 426, 428, 430, 432, 434, 436, 438, 440, 442, 444, 446, 448, 450, 452, 454, 456, 458, 460, 462, 464, 466, 523, 525, 527, 529, 531, 533, 535, 537, 539, 541, 543, 545, 547, 549, 551, 560, 562, 564, 566, 568, 570, 664, 668, 670, 673, 689, 691, 693, 695, 697, 699, 701, 703, 705, 707, 737, 739, 743, 753, 767, 785, 787, 789, 791, 793, 805, 807, 809, 811, 813], "summary": {"covered_lines": 253, "num_statements": 255, "percent_covered": 99.2156862745098, "percent_covered_display": "99", "missing_lines": 2, "excluded_lines": 0, "percent_statements_covered": 99.2156862745098, "percent_statements_covered_display": "99", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [30, 33], "excluded_lines": [], "executed_branches": [], "missing_branches": [], "functions": {"Tag.__hash__": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 1, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 1, "excluded_lines": 0, "percent_statements_covered": 0.0, "percent_statements_covered_display": "0", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [30], "excluded_lines": [], "start_line": 29, "executed_branches": [], "missing_branches": []}, "Tag.__str__": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 1, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 1, "excluded_lines": 0, "percent_statements_covered": 0.0, "percent_statements_covered_display": "0", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [33], "excluded_lines": [], "start_line": 32, "executed_branches": [], "missing_branches": []}, "": {"executed_lines": [1, 2, 3, 9, 11, 13, 14, 15, 16, 17, 20, 23, 24, 27, 29, 32, 36, 38, 40, 42, 44, 46, 48, 50, 52, 54, 56, 58, 60, 62, 64, 66, 68, 70, 72, 74, 76, 78, 80, 82, 84, 95, 98, 100, 102, 104, 107, 109, 111, 113, 115, 117, 119, 121, 123, 125, 127, 129, 131, 133, 135, 137, 139, 141, 143, 145, 147, 149, 151, 153, 155, 157, 159, 161, 163, 165, 167, 169, 171, 173, 175, 179, 181, 183, 185, 187, 189, 191, 193, 195, 197, 199, 201, 203, 205, 207, 209, 211, 213, 215, 217, 219, 221, 223, 225, 227, 229, 232, 234, 236, 238, 240, 242, 244, 246, 248, 250, 252, 254, 256, 258, 260, 262, 264, 266, 268, 270, 272, 274, 276, 278, 280, 282, 284, 286, 288, 290, 292, 294, 296, 298, 300, 302, 304, 306, 308, 310, 312, 314, 316, 318, 320, 322, 324, 326, 328, 330, 332, 334, 336, 338, 340, 342, 344, 346, 350, 353, 354, 355, 359, 361, 366, 372, 378, 384, 390, 396, 402, 409, 411, 413, 415, 426, 428, 430, 432, 434, 436, 438, 440, 442, 444, 446, 448, 450, 452, 454, 456, 458, 460, 462, 464, 466, 523, 525, 527, 529, 531, 533, 535, 537, 539, 541, 543, 545, 547, 549, 551, 560, 562, 564, 566, 568, 570, 664, 668, 670, 673, 689, 691, 693, 695, 697, 699, 701, 703, 705, 707, 737, 739, 743, 753, 767, 785, 787, 789, 791, 793, 805, 807, 809, 811, 813], "summary": {"covered_lines": 253, "num_statements": 253, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 1, "executed_branches": [], "missing_branches": []}}, "classes": {"CAT": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 0, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 13, "executed_branches": [], "missing_branches": []}, "ApiKey": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 0, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 20, "executed_branches": [], "missing_branches": []}, "Tag": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 2, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 2, "excluded_lines": 0, "percent_statements_covered": 0.0, "percent_statements_covered_display": "0", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [30, 33], "excluded_lines": [], "start_line": 27, "executed_branches": [], "missing_branches": []}, "": {"executed_lines": [1, 2, 3, 9, 11, 13, 14, 15, 16, 17, 20, 23, 24, 27, 29, 32, 36, 38, 40, 42, 44, 46, 48, 50, 52, 54, 56, 58, 60, 62, 64, 66, 68, 70, 72, 74, 76, 78, 80, 82, 84, 95, 98, 100, 102, 104, 107, 109, 111, 113, 115, 117, 119, 121, 123, 125, 127, 129, 131, 133, 135, 137, 139, 141, 143, 145, 147, 149, 151, 153, 155, 157, 159, 161, 163, 165, 167, 169, 171, 173, 175, 179, 181, 183, 185, 187, 189, 191, 193, 195, 197, 199, 201, 203, 205, 207, 209, 211, 213, 215, 217, 219, 221, 223, 225, 227, 229, 232, 234, 236, 238, 240, 242, 244, 246, 248, 250, 252, 254, 256, 258, 260, 262, 264, 266, 268, 270, 272, 274, 276, 278, 280, 282, 284, 286, 288, 290, 292, 294, 296, 298, 300, 302, 304, 306, 308, 310, 312, 314, 316, 318, 320, 322, 324, 326, 328, 330, 332, 334, 336, 338, 340, 342, 344, 346, 350, 353, 354, 355, 359, 361, 366, 372, 378, 384, 390, 396, 402, 409, 411, 413, 415, 426, 428, 430, 432, 434, 436, 438, 440, 442, 444, 446, 448, 450, 452, 454, 456, 458, 460, 462, 464, 466, 523, 525, 527, 529, 531, 533, 535, 537, 539, 541, 543, 545, 547, 549, 551, 560, 562, 564, 566, 568, 570, 664, 668, 670, 673, 689, 691, 693, 695, 697, 699, 701, 703, 705, 707, 737, 739, 743, 753, 767, 785, 787, 789, 791, 793, 805, 807, 809, 811, 813], "summary": {"covered_lines": 253, "num_statements": 253, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 1, "executed_branches": [], "missing_branches": []}}}, "custom_components/goecharger_api2/select.py": {"executed_lines": [1, 3, 4, 5, 6, 7, 9, 10, 12, 13, 14, 16, 19, 37, 38, 39, 40, 43, 59, 61, 62, 63, 64, 65, 66, 67, 68, 70, 71, 73, 74, 76, 79, 82, 83, 84, 85, 87, 89, 90, 92, 93, 95, 96, 97, 98, 99, 101, 102, 104, 105, 107, 108, 109, 111, 113, 116, 117, 118, 119, 120, 122, 124, 126, 127, 129, 130, 133, 135, 165, 167, 169, 172, 173, 175, 178, 179, 181, 184, 185, 187, 190, 191, 193, 196, 198, 199, 200], "summary": {"covered_lines": 88, "num_statements": 100, "percent_covered": 86.53846153846153, "percent_covered_display": "87", "missing_lines": 12, "excluded_lines": 0, "percent_statements_covered": 88.0, "percent_statements_covered_display": "88", "num_branches": 56, "num_partial_branches": 1, "covered_branches": 47, "missing_branches": 9, "percent_branches_covered": 83.92857142857143, "percent_branches_covered_display": "84"}, "missing_lines": [20, 21, 22, 24, 25, 26, 27, 28, 30, 31, 32, 34], "excluded_lines": [], "executed_branches": [[39, 40], [39, 59], [65, 66], [66, 67], [66, 73], [67, 68], [67, 70], [73, 74], [73, 76], [79, 82], [79, 89], [82, 83], [82, 84], [84, 85], [84, 87], [89, 90], [89, 92], [92, 93], [92, 99], [104, 105], [104, 129], [105, 107], [105, 126], [108, 109], [108, 111], [111, 113], [111, 122], [116, 117], [116, 122], [117, 118], [117, 119], [119, 116], [119, 120], [129, 130], [129, 133], [133, 135], [133, 198], [165, 167], [165, 169], [172, 173], [172, 175], [178, 179], [178, 181], [184, 185], [184, 187], [190, 191], [190, 193]], "missing_branches": [[24, 25], [24, 30], [25, 26], [25, 34], [26, 25], [26, 27], [30, 31], [30, 34], [65, 79]], "functions": {"async_setup_entry": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 12, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 12, "excluded_lines": 0, "percent_statements_covered": 0.0, "percent_statements_covered_display": "0", "num_branches": 8, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 8, "percent_branches_covered": 0.0, "percent_branches_covered_display": "0"}, "missing_lines": [20, 21, 22, 24, 25, 26, 27, 28, 30, 31, 32, 34], "excluded_lines": [], "start_line": 19, "executed_branches": [], "missing_branches": [[24, 25], [24, 30], [25, 26], [25, 34], [26, 25], [26, 27], [30, 31], [30, 34]]}, "GoeChargerSelect.__init__": {"executed_lines": [39, 40, 43, 59], "summary": {"covered_lines": 4, "num_statements": 4, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 2, "num_partial_branches": 0, "covered_branches": 2, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 38, "executed_branches": [[39, 40], [39, 59]], "missing_branches": []}, "GoeChargerSelect.current_option": {"executed_lines": [63, 64, 65, 66, 67, 68, 70, 71, 73, 74, 76, 79, 82, 83, 84, 85, 87, 89, 90, 92, 93, 95, 96, 97, 98, 99], "summary": {"covered_lines": 26, "num_statements": 26, "percent_covered": 97.72727272727273, "percent_covered_display": "98", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 18, "num_partial_branches": 1, "covered_branches": 17, "missing_branches": 1, "percent_branches_covered": 94.44444444444444, "percent_branches_covered_display": "94"}, "missing_lines": [], "excluded_lines": [], "start_line": 62, "executed_branches": [[65, 66], [66, 67], [66, 73], [67, 68], [67, 70], [73, 74], [73, 76], [79, 82], [79, 89], [82, 83], [82, 84], [84, 85], [84, 87], [89, 90], [89, 92], [92, 93], [92, 99]], "missing_branches": [[65, 79]]}, "GoeChargerSelect.async_select_option": {"executed_lines": [102, 104, 105, 107, 108, 109, 111, 113, 116, 117, 118, 119, 120, 122, 124, 126, 127, 129, 130, 133, 135, 165, 167, 169, 172, 173, 175, 178, 179, 181, 184, 185, 187, 190, 191, 193, 196, 198, 199, 200], "summary": {"covered_lines": 40, "num_statements": 40, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 28, "num_partial_branches": 0, "covered_branches": 28, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 101, "executed_branches": [[104, 105], [104, 129], [105, 107], [105, 126], [108, 109], [108, 111], [111, 113], [111, 122], [116, 117], [116, 122], [117, 118], [117, 119], [119, 116], [119, 120], [129, 130], [129, 133], [133, 135], [133, 198], [165, 167], [165, 169], [172, 173], [172, 175], [178, 179], [178, 181], [184, 185], [184, 187], [190, 191], [190, 193]], "missing_branches": []}, "": {"executed_lines": [1, 3, 4, 5, 6, 7, 9, 10, 12, 13, 14, 16, 19, 37, 38, 61, 62, 101], "summary": {"covered_lines": 18, "num_statements": 18, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 1, "executed_branches": [], "missing_branches": []}}, "classes": {"GoeChargerSelect": {"executed_lines": [39, 40, 43, 59, 63, 64, 65, 66, 67, 68, 70, 71, 73, 74, 76, 79, 82, 83, 84, 85, 87, 89, 90, 92, 93, 95, 96, 97, 98, 99, 102, 104, 105, 107, 108, 109, 111, 113, 116, 117, 118, 119, 120, 122, 124, 126, 127, 129, 130, 133, 135, 165, 167, 169, 172, 173, 175, 178, 179, 181, 184, 185, 187, 190, 191, 193, 196, 198, 199, 200], "summary": {"covered_lines": 70, "num_statements": 70, "percent_covered": 99.15254237288136, "percent_covered_display": "99", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 48, "num_partial_branches": 1, "covered_branches": 47, "missing_branches": 1, "percent_branches_covered": 97.91666666666667, "percent_branches_covered_display": "98"}, "missing_lines": [], "excluded_lines": [], "start_line": 37, "executed_branches": [[39, 40], [39, 59], [65, 66], [66, 67], [66, 73], [67, 68], [67, 70], [73, 74], [73, 76], [79, 82], [79, 89], [82, 83], [82, 84], [84, 85], [84, 87], [89, 90], [89, 92], [92, 93], [92, 99], [104, 105], [104, 129], [105, 107], [105, 126], [108, 109], [108, 111], [111, 113], [111, 122], [116, 117], [116, 122], [117, 118], [117, 119], [119, 116], [119, 120], [129, 130], [129, 133], [133, 135], [133, 198], [165, 167], [165, 169], [172, 173], [172, 175], [178, 179], [178, 181], [184, 185], [184, 187], [190, 191], [190, 193]], "missing_branches": [[65, 79]]}, "": {"executed_lines": [1, 3, 4, 5, 6, 7, 9, 10, 12, 13, 14, 16, 19, 37, 38, 61, 62, 101], "summary": {"covered_lines": 18, "num_statements": 30, "percent_covered": 47.36842105263158, "percent_covered_display": "47", "missing_lines": 12, "excluded_lines": 0, "percent_statements_covered": 60.0, "percent_statements_covered_display": "60", "num_branches": 8, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 8, "percent_branches_covered": 0.0, "percent_branches_covered_display": "0"}, "missing_lines": [20, 21, 22, 24, 25, 26, 27, 28, 30, 31, 32, 34], "excluded_lines": [], "start_line": 1, "executed_branches": [], "missing_branches": [[24, 25], [24, 30], [25, 26], [25, 34], [26, 25], [26, 27], [30, 31], [30, 34]]}}}, "custom_components/goecharger_api2/sensor.py": {"executed_lines": [1, 2, 3, 4, 6, 7, 8, 9, 10, 11, 13, 14, 15, 16, 18, 21, 38, 39, 40, 42, 44, 45, 47, 48, 49, 50, 51, 52, 54, 55, 57, 58, 59, 60, 61, 65, 66, 67, 68, 69, 70, 71, 72, 77, 78, 81, 82, 83, 84, 85, 86, 88, 89, 91, 93, 94, 95, 96, 99, 100, 101, 102, 103, 105, 107, 109, 111, 112, 114, 115, 116, 118, 122, 125, 126, 127, 128, 130, 131, 133, 134, 135, 136, 137, 138, 139, 140, 141, 143, 144, 147, 148, 149, 150, 153], "summary": {"covered_lines": 95, "num_statements": 109, "percent_covered": 85.20710059171597, "percent_covered_display": "85", "missing_lines": 14, "excluded_lines": 0, "percent_statements_covered": 87.1559633027523, "percent_statements_covered_display": "87", "num_branches": 60, "num_partial_branches": 3, "covered_branches": 49, "missing_branches": 11, "percent_branches_covered": 81.66666666666667, "percent_branches_covered_display": "82"}, "missing_lines": [22, 23, 24, 26, 27, 28, 29, 30, 32, 33, 34, 36, 63, 145], "excluded_lines": [], "executed_branches": [[57, 58], [59, 60], [59, 61], [69, 70], [69, 111], [70, 71], [70, 94], [77, 78], [77, 93], [81, 82], [81, 83], [83, 84], [83, 85], [85, 86], [85, 88], [88, 89], [88, 91], [94, 95], [94, 96], [96, 99], [96, 109], [100, 101], [100, 107], [102, 103], [102, 105], [111, 112], [111, 114], [114, 115], [114, 122], [115, 116], [115, 118], [122, 125], [122, 133], [125, 126], [125, 130], [127, 128], [127, 130], [130, 131], [130, 133], [133, 134], [133, 135], [135, 136], [135, 137], [137, 138], [137, 153], [138, 139], [138, 140], [140, 141], [144, 147]], "missing_branches": [[26, 27], [26, 32], [27, 28], [27, 36], [28, 27], [28, 29], [32, 33], [32, 36], [57, 63], [140, 153], [144, 145]], "functions": {"async_setup_entry": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 12, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 12, "excluded_lines": 0, "percent_statements_covered": 0.0, "percent_statements_covered_display": "0", "num_branches": 8, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 8, "percent_branches_covered": 0.0, "percent_branches_covered_display": "0"}, "missing_lines": [22, 23, 24, 26, 27, 28, 29, 30, 32, 33, 34, 36], "excluded_lines": [], "start_line": 21, "executed_branches": [], "missing_branches": [[26, 27], [26, 32], [27, 28], [27, 36], [28, 27], [28, 29], [32, 33], [32, 36]]}, "GoeChargerSensor.__init__": {"executed_lines": [45], "summary": {"covered_lines": 1, "num_statements": 1, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 44, "executed_branches": [], "missing_branches": []}, "GoeChargerSensor._camel_to_snake": {"executed_lines": [49, 50, 51, 52], "summary": {"covered_lines": 4, "num_statements": 4, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 48, "executed_branches": [], "missing_branches": []}, "GoeChargerSensor.state": {"executed_lines": [57, 58, 59, 60, 61], "summary": {"covered_lines": 5, "num_statements": 6, "percent_covered": 80.0, "percent_covered_display": "80", "missing_lines": 1, "excluded_lines": 0, "percent_statements_covered": 83.33333333333333, "percent_statements_covered_display": "83", "num_branches": 4, "num_partial_branches": 1, "covered_branches": 3, "missing_branches": 1, "percent_branches_covered": 75.0, "percent_branches_covered_display": "75"}, "missing_lines": [63], "excluded_lines": [], "start_line": 55, "executed_branches": [[57, 58], [59, 60], [59, 61]], "missing_branches": [[57, 63]]}, "GoeChargerSensor.native_value": {"executed_lines": [67, 68, 69, 70, 71, 72, 77, 78, 81, 82, 83, 84, 85, 86, 88, 89, 91, 93, 94, 95, 96, 99, 100, 101, 102, 103, 105, 107, 109, 111, 112, 114, 115, 116, 118, 122, 125, 126, 127, 128, 130, 131, 133, 134, 135, 136, 137, 138, 139, 140, 141, 143, 144, 147, 148, 149, 150, 153], "summary": {"covered_lines": 58, "num_statements": 59, "percent_covered": 97.19626168224299, "percent_covered_display": "97", "missing_lines": 1, "excluded_lines": 0, "percent_statements_covered": 98.30508474576271, "percent_statements_covered_display": "98", "num_branches": 48, "num_partial_branches": 2, "covered_branches": 46, "missing_branches": 2, "percent_branches_covered": 95.83333333333333, "percent_branches_covered_display": "96"}, "missing_lines": [145], "excluded_lines": [], "start_line": 66, "executed_branches": [[69, 70], [69, 111], [70, 71], [70, 94], [77, 78], [77, 93], [81, 82], [81, 83], [83, 84], [83, 85], [85, 86], [85, 88], [88, 89], [88, 91], [94, 95], [94, 96], [96, 99], [96, 109], [100, 101], [100, 107], [102, 103], [102, 105], [111, 112], [111, 114], [114, 115], [114, 122], [115, 116], [115, 118], [122, 125], [122, 133], [125, 126], [125, 130], [127, 128], [127, 130], [130, 131], [130, 133], [133, 134], [133, 135], [135, 136], [135, 137], [137, 138], [137, 153], [138, 139], [138, 140], [140, 141], [144, 147]], "missing_branches": [[140, 153], [144, 145]]}, "": {"executed_lines": [1, 2, 3, 4, 6, 7, 8, 9, 10, 11, 13, 14, 15, 16, 18, 21, 38, 39, 40, 42, 44, 47, 48, 54, 55, 65, 66], "summary": {"covered_lines": 27, "num_statements": 27, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 1, "executed_branches": [], "missing_branches": []}}, "classes": {"GoeChargerSensor": {"executed_lines": [45, 49, 50, 51, 52, 57, 58, 59, 60, 61, 67, 68, 69, 70, 71, 72, 77, 78, 81, 82, 83, 84, 85, 86, 88, 89, 91, 93, 94, 95, 96, 99, 100, 101, 102, 103, 105, 107, 109, 111, 112, 114, 115, 116, 118, 122, 125, 126, 127, 128, 130, 131, 133, 134, 135, 136, 137, 138, 139, 140, 141, 143, 144, 147, 148, 149, 150, 153], "summary": {"covered_lines": 68, "num_statements": 70, "percent_covered": 95.90163934426229, "percent_covered_display": "96", "missing_lines": 2, "excluded_lines": 0, "percent_statements_covered": 97.14285714285714, "percent_statements_covered_display": "97", "num_branches": 52, "num_partial_branches": 3, "covered_branches": 49, "missing_branches": 3, "percent_branches_covered": 94.23076923076923, "percent_branches_covered_display": "94"}, "missing_lines": [63, 145], "excluded_lines": [], "start_line": 42, "executed_branches": [[57, 58], [59, 60], [59, 61], [69, 70], [69, 111], [70, 71], [70, 94], [77, 78], [77, 93], [81, 82], [81, 83], [83, 84], [83, 85], [85, 86], [85, 88], [88, 89], [88, 91], [94, 95], [94, 96], [96, 99], [96, 109], [100, 101], [100, 107], [102, 103], [102, 105], [111, 112], [111, 114], [114, 115], [114, 122], [115, 116], [115, 118], [122, 125], [122, 133], [125, 126], [125, 130], [127, 128], [127, 130], [130, 131], [130, 133], [133, 134], [133, 135], [135, 136], [135, 137], [137, 138], [137, 153], [138, 139], [138, 140], [140, 141], [144, 147]], "missing_branches": [[57, 63], [140, 153], [144, 145]]}, "": {"executed_lines": [1, 2, 3, 4, 6, 7, 8, 9, 10, 11, 13, 14, 15, 16, 18, 21, 38, 39, 40, 42, 44, 47, 48, 54, 55, 65, 66], "summary": {"covered_lines": 27, "num_statements": 39, "percent_covered": 57.4468085106383, "percent_covered_display": "57", "missing_lines": 12, "excluded_lines": 0, "percent_statements_covered": 69.23076923076923, "percent_statements_covered_display": "69", "num_branches": 8, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 8, "percent_branches_covered": 0.0, "percent_branches_covered_display": "0"}, "missing_lines": [22, 23, 24, 26, 27, 28, 29, 30, 32, 33, 34, 36], "excluded_lines": [], "start_line": 1, "executed_branches": [], "missing_branches": [[26, 27], [26, 32], [27, 28], [27, 36], [28, 27], [28, 29], [32, 33], [32, 36]]}}}, "custom_components/goecharger_api2/service.py": {"executed_lines": [1, 2, 3, 4, 6, 8, 9, 10, 11, 13, 14, 15, 17, 19, 21, 23, 25, 26, 30, 32, 33, 35, 37, 38, 39, 41, 42, 44, 45, 49, 50, 51, 53, 55, 56, 57, 60, 62, 63, 64, 65, 66, 67, 69, 71, 72, 73, 74, 75, 76, 77, 78, 80, 85, 86, 87, 88, 89, 95, 96, 97, 99, 100, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 115, 117, 118, 119, 126, 128, 129, 130, 132, 133, 134, 137, 138, 140, 142, 143, 144, 147, 149, 150, 151, 153, 154, 156, 157, 159], "summary": {"covered_lines": 101, "num_statements": 104, "percent_covered": 97.26027397260275, "percent_covered_display": "97", "missing_lines": 3, "excluded_lines": 0, "percent_statements_covered": 97.11538461538461, "percent_statements_covered_display": "97", "num_branches": 42, "num_partial_branches": 1, "covered_branches": 41, "missing_branches": 1, "percent_branches_covered": 97.61904761904762, "percent_branches_covered_display": "98"}, "missing_lines": [24, 27, 28], "excluded_lines": [], "executed_branches": [[23, 25], [37, -35], [37, 38], [60, 62], [60, 71], [63, 64], [63, 71], [64, 65], [64, 71], [66, 67], [66, 69], [74, 75], [74, 99], [75, 76], [75, 77], [77, 78], [77, 80], [88, 89], [88, 99], [96, 97], [96, 99], [99, -55], [99, 100], [103, 104], [103, 137], [110, 111], [110, 126], [118, -102], [118, 119], [129, -102], [129, 130], [133, -102], [133, 134], [137, -102], [137, 138], [143, 144], [143, 147], [149, 150], [149, 156], [150, 151], [150, 153]], "missing_branches": [[23, 24]], "functions": {"_is_finite_number": {"executed_lines": [23, 25, 26], "summary": {"covered_lines": 3, "num_statements": 6, "percent_covered": 50.0, "percent_covered_display": "50", "missing_lines": 3, "excluded_lines": 0, "percent_statements_covered": 50.0, "percent_statements_covered_display": "50", "num_branches": 2, "num_partial_branches": 1, "covered_branches": 1, "missing_branches": 1, "percent_branches_covered": 50.0, "percent_branches_covered_display": "50"}, "missing_lines": [24, 27, 28], "excluded_lines": [], "start_line": 21, "executed_branches": [[23, 25]], "missing_branches": [[23, 24]]}, "add_config_entry_to_service": {"executed_lines": [32, 33], "summary": {"covered_lines": 2, "num_statements": 2, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 30, "executed_branches": [], "missing_branches": []}, "remove_config_entry_from_service": {"executed_lines": [37, 38, 39], "summary": {"covered_lines": 3, "num_statements": 3, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 2, "num_partial_branches": 0, "covered_branches": 2, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 35, "executed_branches": [[37, -35], [37, 38]], "missing_branches": []}, "GoeChargerApiV2Service.__init__": {"executed_lines": [44, 45, 49, 50, 51, 53], "summary": {"covered_lines": 6, "num_statements": 6, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 42, "executed_branches": [], "missing_branches": []}, "GoeChargerApiV2Service.set_pv_data": {"executed_lines": [56, 57, 60, 62, 63, 64, 65, 66, 67, 69, 71, 72, 73, 74, 75, 76, 77, 78, 80, 85, 86, 87, 88, 89, 95, 96, 97, 99, 100], "summary": {"covered_lines": 29, "num_statements": 29, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 20, "num_partial_branches": 0, "covered_branches": 20, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 55, "executed_branches": [[60, 62], [60, 71], [63, 64], [63, 71], [64, 65], [64, 71], [66, 67], [66, 69], [74, 75], [74, 99], [75, 76], [75, 77], [77, 78], [77, 80], [88, 89], [88, 99], [96, 97], [96, 99], [99, -55], [99, 100]], "missing_branches": []}, "GoeChargerApiV2Service.stop_charging": {"executed_lines": [103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 115, 117, 118, 119, 126, 128, 129, 130, 132, 133, 134, 137, 138], "summary": {"covered_lines": 24, "num_statements": 24, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 12, "num_partial_branches": 0, "covered_branches": 12, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 102, "executed_branches": [[103, 104], [103, 137], [110, 111], [110, 126], [118, -102], [118, 119], [129, -102], [129, 130], [133, -102], [133, 134], [137, -102], [137, 138]], "missing_branches": []}, "GoeChargerApiV2Service.get_coordinator": {"executed_lines": [142, 143, 144, 147, 149, 150, 151, 153, 154, 156, 157, 159], "summary": {"covered_lines": 12, "num_statements": 12, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 6, "num_partial_branches": 0, "covered_branches": 6, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 140, "executed_branches": [[143, 144], [143, 147], [149, 150], [149, 156], [150, 151], [150, 153]], "missing_branches": []}, "": {"executed_lines": [1, 2, 3, 4, 6, 8, 9, 10, 11, 13, 14, 15, 17, 19, 21, 30, 35, 41, 42, 55, 102, 140], "summary": {"covered_lines": 22, "num_statements": 22, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 1, "executed_branches": [], "missing_branches": []}}, "classes": {"GoeChargerApiV2Service": {"executed_lines": [44, 45, 49, 50, 51, 53, 56, 57, 60, 62, 63, 64, 65, 66, 67, 69, 71, 72, 73, 74, 75, 76, 77, 78, 80, 85, 86, 87, 88, 89, 95, 96, 97, 99, 100, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 115, 117, 118, 119, 126, 128, 129, 130, 132, 133, 134, 137, 138, 142, 143, 144, 147, 149, 150, 151, 153, 154, 156, 157, 159], "summary": {"covered_lines": 71, "num_statements": 71, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 38, "num_partial_branches": 0, "covered_branches": 38, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 41, "executed_branches": [[60, 62], [60, 71], [63, 64], [63, 71], [64, 65], [64, 71], [66, 67], [66, 69], [74, 75], [74, 99], [75, 76], [75, 77], [77, 78], [77, 80], [88, 89], [88, 99], [96, 97], [96, 99], [99, -55], [99, 100], [103, 104], [103, 137], [110, 111], [110, 126], [118, -102], [118, 119], [129, -102], [129, 130], [133, -102], [133, 134], [137, -102], [137, 138], [143, 144], [143, 147], [149, 150], [149, 156], [150, 151], [150, 153]], "missing_branches": []}, "": {"executed_lines": [1, 2, 3, 4, 6, 8, 9, 10, 11, 13, 14, 15, 17, 19, 21, 23, 25, 26, 30, 32, 33, 35, 37, 38, 39, 41, 42, 55, 102, 140], "summary": {"covered_lines": 30, "num_statements": 33, "percent_covered": 89.1891891891892, "percent_covered_display": "89", "missing_lines": 3, "excluded_lines": 0, "percent_statements_covered": 90.9090909090909, "percent_statements_covered_display": "91", "num_branches": 4, "num_partial_branches": 1, "covered_branches": 3, "missing_branches": 1, "percent_branches_covered": 75.0, "percent_branches_covered_display": "75"}, "missing_lines": [24, 27, 28], "excluded_lines": [], "start_line": 1, "executed_branches": [[23, 25], [37, -35], [37, 38]], "missing_branches": [[23, 24]]}}}, "custom_components/goecharger_api2/switch.py": {"executed_lines": [1, 2, 4, 5, 6, 7, 8, 10, 11, 12, 14, 17, 35, 36, 37, 38, 40, 42, 43, 44, 46, 47, 48, 50, 52, 53, 55, 56, 58, 60, 61, 63, 64, 66, 68, 69, 70, 72, 73, 74, 76, 78, 79, 81, 82, 84, 86, 87, 88, 89, 91, 92, 93, 94, 95, 96, 97, 98, 100, 102, 103, 104, 105, 107, 108, 109, 110, 111, 113, 114, 115, 116, 118, 119, 121, 122, 124, 126, 128, 130, 131, 133, 134, 135, 137, 138, 140, 141, 143], "summary": {"covered_lines": 89, "num_statements": 101, "percent_covered": 85.23489932885906, "percent_covered_display": "85", "missing_lines": 12, "excluded_lines": 0, "percent_statements_covered": 88.11881188118812, "percent_statements_covered_display": "88", "num_branches": 48, "num_partial_branches": 2, "covered_branches": 38, "missing_branches": 10, "percent_branches_covered": 79.16666666666667, "percent_branches_covered_display": "79"}, "missing_lines": [18, 19, 20, 22, 23, 24, 25, 26, 28, 29, 30, 32], "excluded_lines": [], "executed_branches": [[43, 44], [43, 55], [44, 46], [44, 52], [47, 48], [47, 50], [55, 56], [55, 58], [69, 70], [69, 81], [70, 72], [70, 78], [73, 74], [73, 76], [81, 82], [81, 84], [95, 96], [96, 97], [96, 102], [97, 98], [97, 100], [102, 103], [104, 105], [104, 113], [113, 114], [113, 128], [114, 115], [114, 119], [115, 116], [115, 118], [119, 121], [119, 126], [121, 122], [121, 124], [133, 134], [133, 135], [140, 141], [140, 143]], "missing_branches": [[22, 23], [22, 28], [23, 24], [23, 32], [24, 23], [24, 25], [28, 29], [28, 32], [95, 113], [102, 104]], "functions": {"async_setup_entry": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 12, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 12, "excluded_lines": 0, "percent_statements_covered": 0.0, "percent_statements_covered_display": "0", "num_branches": 8, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 8, "percent_branches_covered": 0.0, "percent_branches_covered_display": "0"}, "missing_lines": [18, 19, 20, 22, 23, 24, 25, 26, 28, 29, 30, 32], "excluded_lines": [], "start_line": 17, "executed_branches": [], "missing_branches": [[22, 23], [22, 28], [23, 24], [23, 32], [24, 23], [24, 25], [28, 29], [28, 32]]}, "GoeChargerSwitch.__init__": {"executed_lines": [37, 38], "summary": {"covered_lines": 2, "num_statements": 2, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 36, "executed_branches": [], "missing_branches": []}, "GoeChargerSwitch.async_turn_on": {"executed_lines": [42, 43, 44, 46, 47, 48, 50, 52, 53, 55, 56, 58, 60, 61, 63, 64], "summary": {"covered_lines": 16, "num_statements": 16, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 8, "num_partial_branches": 0, "covered_branches": 8, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 40, "executed_branches": [[43, 44], [43, 55], [44, 46], [44, 52], [47, 48], [47, 50], [55, 56], [55, 58]], "missing_branches": []}, "GoeChargerSwitch.async_turn_off": {"executed_lines": [68, 69, 70, 72, 73, 74, 76, 78, 79, 81, 82, 84, 86, 87, 88, 89], "summary": {"covered_lines": 16, "num_statements": 16, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 8, "num_partial_branches": 0, "covered_branches": 8, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 66, "executed_branches": [[69, 70], [69, 81], [70, 72], [70, 78], [73, 74], [73, 76], [81, 82], [81, 84]], "missing_branches": []}, "GoeChargerSwitch.is_on": {"executed_lines": [93, 94, 95, 96, 97, 98, 100, 102, 103, 104, 105, 107, 108, 109, 110, 111, 113, 114, 115, 116, 118, 119, 121, 122, 124, 126, 128], "summary": {"covered_lines": 27, "num_statements": 27, "percent_covered": 95.74468085106383, "percent_covered_display": "96", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 20, "num_partial_branches": 2, "covered_branches": 18, "missing_branches": 2, "percent_branches_covered": 90.0, "percent_branches_covered_display": "90"}, "missing_lines": [], "excluded_lines": [], "start_line": 92, "executed_branches": [[95, 96], [96, 97], [96, 102], [97, 98], [97, 100], [102, 103], [104, 105], [104, 113], [113, 114], [113, 128], [114, 115], [114, 119], [115, 116], [115, 118], [119, 121], [119, 126], [121, 122], [121, 124]], "missing_branches": [[95, 113], [102, 104]]}, "GoeChargerSwitch.state": {"executed_lines": [133, 134, 135], "summary": {"covered_lines": 3, "num_statements": 3, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 2, "num_partial_branches": 0, "covered_branches": 2, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 131, "executed_branches": [[133, 134], [133, 135]], "missing_branches": []}, "GoeChargerSwitch.icon": {"executed_lines": [140, 141, 143], "summary": {"covered_lines": 3, "num_statements": 3, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 2, "num_partial_branches": 0, "covered_branches": 2, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 138, "executed_branches": [[140, 141], [140, 143]], "missing_branches": []}, "": {"executed_lines": [1, 2, 4, 5, 6, 7, 8, 10, 11, 12, 14, 17, 35, 36, 40, 66, 91, 92, 130, 131, 137, 138], "summary": {"covered_lines": 22, "num_statements": 22, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0, "percent_branches_covered": 100.0, "percent_branches_covered_display": "100"}, "missing_lines": [], "excluded_lines": [], "start_line": 1, "executed_branches": [], "missing_branches": []}}, "classes": {"GoeChargerSwitch": {"executed_lines": [37, 38, 42, 43, 44, 46, 47, 48, 50, 52, 53, 55, 56, 58, 60, 61, 63, 64, 68, 69, 70, 72, 73, 74, 76, 78, 79, 81, 82, 84, 86, 87, 88, 89, 93, 94, 95, 96, 97, 98, 100, 102, 103, 104, 105, 107, 108, 109, 110, 111, 113, 114, 115, 116, 118, 119, 121, 122, 124, 126, 128, 133, 134, 135, 140, 141, 143], "summary": {"covered_lines": 67, "num_statements": 67, "percent_covered": 98.13084112149532, "percent_covered_display": "98", "missing_lines": 0, "excluded_lines": 0, "percent_statements_covered": 100.0, "percent_statements_covered_display": "100", "num_branches": 40, "num_partial_branches": 2, "covered_branches": 38, "missing_branches": 2, "percent_branches_covered": 95.0, "percent_branches_covered_display": "95"}, "missing_lines": [], "excluded_lines": [], "start_line": 35, "executed_branches": [[43, 44], [43, 55], [44, 46], [44, 52], [47, 48], [47, 50], [55, 56], [55, 58], [69, 70], [69, 81], [70, 72], [70, 78], [73, 74], [73, 76], [81, 82], [81, 84], [95, 96], [96, 97], [96, 102], [97, 98], [97, 100], [102, 103], [104, 105], [104, 113], [113, 114], [113, 128], [114, 115], [114, 119], [115, 116], [115, 118], [119, 121], [119, 126], [121, 122], [121, 124], [133, 134], [133, 135], [140, 141], [140, 143]], "missing_branches": [[95, 113], [102, 104]]}, "": {"executed_lines": [1, 2, 4, 5, 6, 7, 8, 10, 11, 12, 14, 17, 35, 36, 40, 66, 91, 92, 130, 131, 137, 138], "summary": {"covered_lines": 22, "num_statements": 34, "percent_covered": 52.38095238095238, "percent_covered_display": "52", "missing_lines": 12, "excluded_lines": 0, "percent_statements_covered": 64.70588235294117, "percent_statements_covered_display": "65", "num_branches": 8, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 8, "percent_branches_covered": 0.0, "percent_branches_covered_display": "0"}, "missing_lines": [18, 19, 20, 22, 23, 24, 25, 26, 28, 29, 30, 32], "excluded_lines": [], "start_line": 1, "executed_branches": [], "missing_branches": [[22, 23], [22, 28], [23, 24], [23, 32], [24, 23], [24, 25], [28, 29], [28, 32]]}}}}, "totals": {"covered_lines": 2349, "num_statements": 2529, "percent_covered": 90.78527962909301, "percent_covered_display": "91", "missing_lines": 180, "excluded_lines": 0, "percent_statements_covered": 92.88256227758008, "percent_statements_covered_display": "93", "num_branches": 922, "num_partial_branches": 60, "covered_branches": 784, "missing_branches": 138, "percent_branches_covered": 85.03253796095444, "percent_branches_covered_display": "85"}} \ No newline at end of file diff --git a/tests/reports/2026-10-29/junit-all.xml b/tests/reports/2026-10-29/junit-all.xml deleted file mode 100644 index 4aeaa40..0000000 --- a/tests/reports/2026-10-29/junit-all.xml +++ /dev/null @@ -1,416 +0,0 @@ -key = 'cco' - - @pytest.mark.parametrize("key", _cases("writeable")) - def test_write_flag_matches_documentation(key): - documented = DOC[key]["rw"] in ("R/W", "W") -> assert TAGS[key].writeable is documented, f"documented {DOC[key]['rw']!r}, keys.py writeable={TAGS[key].writeable}" -E AssertionError: documented 'R/W', keys.py writeable=False -E assert False is True -E + where False = <Tag.CCO: ApiKey(key='cco', cat=<CAT.CONFIG: 'CONF'>, writeable=False, writeonly=False)>.writeable - -tests/integration/test_api_contract.py:101: AssertionErrorkey = 'di1' - - @pytest.mark.parametrize("key", _cases("writeable")) - def test_write_flag_matches_documentation(key): - documented = DOC[key]["rw"] in ("R/W", "W") -> assert TAGS[key].writeable is documented, f"documented {DOC[key]['rw']!r}, keys.py writeable={TAGS[key].writeable}" -E AssertionError: documented 'R/W', keys.py writeable=False -E assert False is True -E + where False = <Tag.DI1: ApiKey(key='di1', cat=<CAT.STATUS: 'STAT'>, writeable=False, writeonly=False)>.writeable - -tests/integration/test_api_contract.py:101: AssertionErrorkey = 'esk' - - @pytest.mark.parametrize("key", _cases("writeable")) - def test_write_flag_matches_documentation(key): - documented = DOC[key]["rw"] in ("R/W", "W") -> assert TAGS[key].writeable is documented, f"documented {DOC[key]['rw']!r}, keys.py writeable={TAGS[key].writeable}" -E AssertionError: documented 'R/W', keys.py writeable=False -E assert False is True -E + where False = <Tag.ESK: ApiKey(key='esk', cat=<CAT.CONFIG: 'CONF'>, writeable=False, writeonly=False)>.writeable - -tests/integration/test_api_contract.py:101: AssertionErrorkey = 'frm' - - @pytest.mark.parametrize("key", _cases("writeable")) - def test_write_flag_matches_documentation(key): - documented = DOC[key]["rw"] in ("R/W", "W") -> assert TAGS[key].writeable is documented, f"documented {DOC[key]['rw']!r}, keys.py writeable={TAGS[key].writeable}" -E AssertionError: documented 'R', keys.py writeable=True -E assert True is False -E + where True = <Tag.FRM: ApiKey(key='frm', cat=<CAT.CONFIG: 'CONF'>, writeable=True, writeonly=False)>.writeable - -tests/integration/test_api_contract.py:101: AssertionErrorkey = 'rdbf' - - @pytest.mark.parametrize("key", _cases("writeable")) - def test_write_flag_matches_documentation(key): - documented = DOC[key]["rw"] in ("R/W", "W") -> assert TAGS[key].writeable is documented, f"documented {DOC[key]['rw']!r}, keys.py writeable={TAGS[key].writeable}" -E AssertionError: documented 'R/W', keys.py writeable=False -E assert False is True -E + where False = <Tag.RDBF: ApiKey(key='rdbf', cat=<CAT.CONFIG: 'CONF'>, writeable=False, writeonly=False)>.writeable - -tests/integration/test_api_contract.py:101: AssertionErrorkey = 'rdbs' - - @pytest.mark.parametrize("key", _cases("writeable")) - def test_write_flag_matches_documentation(key): - documented = DOC[key]["rw"] in ("R/W", "W") -> assert TAGS[key].writeable is documented, f"documented {DOC[key]['rw']!r}, keys.py writeable={TAGS[key].writeable}" -E AssertionError: documented 'R/W', keys.py writeable=False -E assert False is True -E + where False = <Tag.RDBS: ApiKey(key='rdbs', cat=<CAT.CONFIG: 'CONF'>, writeable=False, writeonly=False)>.writeable - -tests/integration/test_api_contract.py:101: AssertionErrorkey = 'rdef' - - @pytest.mark.parametrize("key", _cases("writeable")) - def test_write_flag_matches_documentation(key): - documented = DOC[key]["rw"] in ("R/W", "W") -> assert TAGS[key].writeable is documented, f"documented {DOC[key]['rw']!r}, keys.py writeable={TAGS[key].writeable}" -E AssertionError: documented 'R/W', keys.py writeable=False -E assert False is True -E + where False = <Tag.RDEF: ApiKey(key='rdef', cat=<CAT.CONFIG: 'CONF'>, writeable=False, writeonly=False)>.writeable - -tests/integration/test_api_contract.py:101: AssertionErrorkey = 'rdes' - - @pytest.mark.parametrize("key", _cases("writeable")) - def test_write_flag_matches_documentation(key): - documented = DOC[key]["rw"] in ("R/W", "W") -> assert TAGS[key].writeable is documented, f"documented {DOC[key]['rw']!r}, keys.py writeable={TAGS[key].writeable}" -E AssertionError: documented 'R/W', keys.py writeable=False -E assert False is True -E + where False = <Tag.RDES: ApiKey(key='rdes', cat=<CAT.CONFIG: 'CONF'>, writeable=False, writeonly=False)>.writeable - -tests/integration/test_api_contract.py:101: AssertionErrorkey = 'rdpl' - - @pytest.mark.parametrize("key", _cases("writeable")) - def test_write_flag_matches_documentation(key): - documented = DOC[key]["rw"] in ("R/W", "W") -> assert TAGS[key].writeable is documented, f"documented {DOC[key]['rw']!r}, keys.py writeable={TAGS[key].writeable}" -E AssertionError: documented 'R/W', keys.py writeable=False -E assert False is True -E + where False = <Tag.RDPL: ApiKey(key='rdpl', cat=<CAT.CONFIG: 'CONF'>, writeable=False, writeonly=False)>.writeable - -tests/integration/test_api_contract.py:101: AssertionErrorkey = 'rdre' - - @pytest.mark.parametrize("key", _cases("writeable")) - def test_write_flag_matches_documentation(key): - documented = DOC[key]["rw"] in ("R/W", "W") -> assert TAGS[key].writeable is documented, f"documented {DOC[key]['rw']!r}, keys.py writeable={TAGS[key].writeable}" -E AssertionError: documented 'R/W', keys.py writeable=False -E assert False is True -E + where False = <Tag.RDRE: ApiKey(key='rdre', cat=<CAT.CONFIG: 'CONF'>, writeable=False, writeonly=False)>.writeable - -tests/integration/test_api_contract.py:101: AssertionErrorkey = 'deltaa' - - @pytest.mark.parametrize("key", _cases("category")) - def test_category_matches_documentation(key): -> assert TAGS[key].cat == CATEGORY[DOC[key]["category"]], \ - f"documented {DOC[key]['category']!r}, keys.py {TAGS[key].cat.name}" -E AssertionError: documented 'Other', keys.py STATUS -E assert <CAT.STATUS: 'STAT'> == <CAT.OTHER: 'OTHE'> -E + where <CAT.STATUS: 'STAT'> = <Tag.DELTAA: ApiKey(key='deltaa', cat=<CAT.STATUS: 'STAT'>, writeable=False, writeonly=False)>.cat - -tests/integration/test_api_contract.py:106: AssertionErrorkey = 'di1' - - @pytest.mark.parametrize("key", _cases("category")) - def test_category_matches_documentation(key): -> assert TAGS[key].cat == CATEGORY[DOC[key]["category"]], \ - f"documented {DOC[key]['category']!r}, keys.py {TAGS[key].cat.name}" -E AssertionError: documented 'Config', keys.py STATUS -E assert <CAT.STATUS: 'STAT'> == <CAT.CONFIG: 'CONF'> -E + where <CAT.STATUS: 'STAT'> = <Tag.DI1: ApiKey(key='di1', cat=<CAT.STATUS: 'STAT'>, writeable=False, writeonly=False)>.cat - -tests/integration/test_api_contract.py:106: AssertionErrorkey = 'ids' - - @pytest.mark.parametrize("key", _cases("category")) - def test_category_matches_documentation(key): -> assert TAGS[key].cat == CATEGORY[DOC[key]["category"]], \ - f"documented {DOC[key]['category']!r}, keys.py {TAGS[key].cat.name}" -E AssertionError: documented 'Other', keys.py STATUS -E assert <CAT.STATUS: 'STAT'> == <CAT.OTHER: 'OTHE'> -E + where <CAT.STATUS: 'STAT'> = <Tag.IDS: ApiKey(key='ids', cat=<CAT.STATUS: 'STAT'>, writeable=True, writeonly=True)>.cat - -tests/integration/test_api_contract.py:106: AssertionErrorkey = 'lcs' - - @pytest.mark.parametrize("key", _cases("category")) - def test_category_matches_documentation(key): -> assert TAGS[key].cat == CATEGORY[DOC[key]["category"]], \ - f"documented {DOC[key]['category']!r}, keys.py {TAGS[key].cat.name}" -E AssertionError: documented 'Status', keys.py CONFIG -E assert <CAT.CONFIG: 'CONF'> == <CAT.STATUS: 'STAT'> -E + where <CAT.CONFIG: 'CONF'> = <Tag.LCS: ApiKey(key='lcs', cat=<CAT.CONFIG: 'CONF'>, writeable=False, writeonly=False)>.cat - -tests/integration/test_api_contract.py:106: AssertionErrorhass = <HomeAssistant RUNNING> -aioclient_mock = <pytest_homeassistant_custom_component.test_util.aiohttp.AiohttpClientMocker object at 0x10dd2cc50> - - @pytest.mark.known_bug(( - "http-en.md: the answer holds 'true for success, or a string error message' per key. The bridge " - "tests the value for truthiness, so an error string counts as success. Not verified on a real charger.")) - async def test_write_rejected_by_charger_returns_err(hass, aioclient_mock): - aioclient_mock.get(LAN_SET, json={"amp": "value out of range"}) - result = await _lan(hass).write_value_to_key("amp", 99) -> assert result == {"err": {"amp": "value out of range"}} -E AssertionError: assert {'amp': 99} == {'err': {'amp...ut of range'}} -E -E Left contains 1 more item: -E {'amp': 99} -E Right contains 1 more item: -E {'err': {'amp': 'value out of range'}} -E Use -v to get more diff - -tests/integration/test_bridge_http.py:104: AssertionErrorhass = <HomeAssistant RUNNING> -aioclient_mock = <pytest_homeassistant_custom_component.test_util.aiohttp.AiohttpClientMocker object at 0x10dfaf390> - - @pytest.mark.known_bug(( - "Official cloud docs: /api/set answers HTTP 202 on success; the bridge only accepts 200. " - "Not verified against the real cloud. Remove the marker when the behaviour is clarified.")) - async def test_cloud_write_accepts_http_202(hass, aioclient_mock): - aioclient_mock.get("https://001234.api.v3.go-e.io/api/set", status=202, json={"amp": True}) -> assert await _cloud(hass).write_value_to_key("amp", 10) == {"amp": 10} -E AssertionError: assert {} == {'amp': 10} -E -E Right contains 1 more item: -E {'amp': 10} -E Use -v to get more diff - -tests/integration/test_bridge_http.py:127: AssertionErrorsvc = (<custom_components.goecharger_api2.service.GoeChargerApiV2Service object at 0x10f9e6e90>, namespace(intg_type='charger', mode='lan', name='test charger', _serial='123456', data={}, async_write_key=<AsyncMock id='4544635168'>)) -bad = nan - - @pytest.mark.known_bug("service.py checks pgrid with isinstance(Number) only; NaN and inf pass") - @pytest.mark.parametrize("bad", [math.nan, math.inf, -math.inf]) - async def test_non_finite_grid_value_is_rejected(svc, bad): - service, coordinator = svc - result = await service.set_pv_data(_call(pgrid=bad, ppv=1500)) -> assert "error" in result -E AssertionError: assert 'error' in {'date': '11:48:18.464227', 'response': {'ids': True}, 'success': 'true'} - -tests/integration/test_service.py:41: AssertionErrorsvc = (<custom_components.goecharger_api2.service.GoeChargerApiV2Service object at 0x10f9e5e50>, namespace(intg_type='charger', mode='lan', name='test charger', _serial='123456', data={}, async_write_key=<AsyncMock id='4532624688'>)) -bad = inf - - @pytest.mark.known_bug("service.py checks pgrid with isinstance(Number) only; NaN and inf pass") - @pytest.mark.parametrize("bad", [math.nan, math.inf, -math.inf]) - async def test_non_finite_grid_value_is_rejected(svc, bad): - service, coordinator = svc - result = await service.set_pv_data(_call(pgrid=bad, ppv=1500)) -> assert "error" in result -E AssertionError: assert 'error' in {'date': '11:48:18.477353', 'response': {'ids': True}, 'success': 'true'} - -tests/integration/test_service.py:41: AssertionErrorsvc = (<custom_components.goecharger_api2.service.GoeChargerApiV2Service object at 0x10dc3d6e0>, namespace(intg_type='charger', mode='lan', name='test charger', _serial='123456', data={}, async_write_key=<AsyncMock id='4550494928'>)) -bad = -inf - - @pytest.mark.known_bug("service.py checks pgrid with isinstance(Number) only; NaN and inf pass") - @pytest.mark.parametrize("bad", [math.nan, math.inf, -math.inf]) - async def test_non_finite_grid_value_is_rejected(svc, bad): - service, coordinator = svc - result = await service.set_pv_data(_call(pgrid=bad, ppv=1500)) -> assert "error" in result -E AssertionError: assert 'error' in {'date': '11:48:18.491423', 'response': {'ids': True}, 'success': 'true'} - -tests/integration/test_service.py:41: AssertionErrorhass = <HomeAssistant RUNNING> -config_entry = <ConfigEntry entry_id=01M63MCRDA7KCWJETHAYFQX6ZB version=1 domain=goecharger_api2 title=go-e 123456 state=ConfigEntryState.NOT_LOADED unique_id=None> - - async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry): - if DOMAIN not in hass.data: - the_integration = await async_get_integration(hass, DOMAIN) - intg_version = the_integration.version if the_integration is not None else "UNKNOWN" - _LOGGER.info(STARTUP_MESSAGE % intg_version) - hass.data.setdefault(DOMAIN, {"manifest_version": intg_version}) - - # ok starting the init sequence... - lang = hass.config.language.lower() - if lang in TRANSLATIONS: - lang_map = TRANSLATIONS[lang] - else: - lang_map = TRANSLATIONS["en"] - - coordinator = GoeChargerDataUpdateCoordinator(hass, config_entry) - a_pwd = config_entry.data.get(CONF_PASSWORD, None) - if a_pwd is not None and len(a_pwd.strip()) > 0: - use_websocket = True - try: - if not await coordinator.start_websocket_and_wait_for_first_data(is_integration_init=True): - _LOGGER.warning(f"async_setup_entry(): The coordinator.start_websocket_and_wait_for_first_data() as returned FALSE") -> raise ConfigEntryNotReady(lang_map["coord_no_device_data"]) -E homeassistant.exceptions.ConfigEntryNotReady: Coordinator could not fetch essential information from your configured device. Please check your Home Assistant system log for possible error messages. - -custom_components/goecharger_api2/__init__.py:129: ConfigEntryNotReady - -During handling of the above exception, another exception occurred: - -hass = <HomeAssistant RUNNING> -setup_patches = <integration.test_services_and_setup.SimpleNamespace_ object at 0x10eb0bdf0> - - @pytest.mark.known_bug("the specific message 'could not fetch essential information' is swallowed by the broad except and replaced by the generic websocket message (__init__.py:131)") - async def test_missing_device_data_is_reported_with_its_own_message(hass, setup_patches): - from homeassistant.exceptions import ConfigEntryNotReady - entry = make_entry(hass, password="secret") - setup_patches.ws_start.return_value = False - with pytest.raises(ConfigEntryNotReady, match="could not fetch essential"): -> await _setup(hass, entry) - -tests/integration/test_services_and_setup.py:303: -_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ -tests/integration/test_services_and_setup.py:228: in _setup - result = await async_setup_entry(hass, entry) - ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ - -hass = <HomeAssistant RUNNING> -config_entry = <ConfigEntry entry_id=01M63MCRDA7KCWJETHAYFQX6ZB version=1 domain=goecharger_api2 title=go-e 123456 state=ConfigEntryState.NOT_LOADED unique_id=None> - - async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry): - if DOMAIN not in hass.data: - the_integration = await async_get_integration(hass, DOMAIN) - intg_version = the_integration.version if the_integration is not None else "UNKNOWN" - _LOGGER.info(STARTUP_MESSAGE % intg_version) - hass.data.setdefault(DOMAIN, {"manifest_version": intg_version}) - - # ok starting the init sequence... - lang = hass.config.language.lower() - if lang in TRANSLATIONS: - lang_map = TRANSLATIONS[lang] - else: - lang_map = TRANSLATIONS["en"] - - coordinator = GoeChargerDataUpdateCoordinator(hass, config_entry) - a_pwd = config_entry.data.get(CONF_PASSWORD, None) - if a_pwd is not None and len(a_pwd.strip()) > 0: - use_websocket = True - try: - if not await coordinator.start_websocket_and_wait_for_first_data(is_integration_init=True): - _LOGGER.warning(f"async_setup_entry(): The coordinator.start_websocket_and_wait_for_first_data() as returned FALSE") - raise ConfigEntryNotReady(lang_map["coord_no_device_data"]) - - except BaseException as exc: - _LOGGER.error(f"async_setup_entry(): Error starting websocket connection: {type(exc).__name__} - {exc}", stack_info=True) -> raise ConfigEntryNotReady(lang_map["websocket_start_failed"]) -E homeassistant.exceptions.ConfigEntryNotReady: The Websocket could not be started within one minute. Please check your Home Assistant system log for possible error messages. - -custom_components/goecharger_api2/__init__.py:133: ConfigEntryNotReady - -During handling of the above exception, another exception occurred: - -hass = <HomeAssistant RUNNING> -setup_patches = <integration.test_services_and_setup.SimpleNamespace_ object at 0x10eb0bdf0> - - @pytest.mark.known_bug("the specific message 'could not fetch essential information' is swallowed by the broad except and replaced by the generic websocket message (__init__.py:131)") - async def test_missing_device_data_is_reported_with_its_own_message(hass, setup_patches): - from homeassistant.exceptions import ConfigEntryNotReady - entry = make_entry(hass, password="secret") - setup_patches.ws_start.return_value = False -> with pytest.raises(ConfigEntryNotReady, match="could not fetch essential"): - ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -E AssertionError: Regex pattern did not match. -E Expected regex: 'could not fetch essential' -E Actual message: 'The Websocket could not be started within one minute. Please check your Home Assistant system log for possible error messages.' - -tests/integration/test_services_and_setup.py:302: AssertionErrorhass = <HomeAssistant RUNNING> - - @pytest.mark.known_bug("stop_charging does not reset _stop_in_progress when the first write fails (service.py)") - async def test_failed_lock_write_blocks_later_stop_requests(hass): - """a ValueError on the first write leaves _stop_in_progress True forever.""" - coordinator = _coordinator(hass, _entry(hass), websocket=True) - service = GoeChargerApiV2Service(hass, coordinator._config_entry, coordinator) - coordinator.async_write_key = AsyncMock(side_effect=ValueError("charger unreachable")) - await service.stop_charging(_call()) -> assert service._stop_in_progress is False -E assert True is False -E + where True = <custom_components.goecharger_api2.service.GoeChargerApiV2Service object at 0x10f3fe930>._stop_in_progress - -tests/integration/test_services_and_unload.py:88: AssertionErrorhass = <HomeAssistant RUNNING> - - @pytest.mark.known_bug("stop_charging does not reset _stop_in_progress when the unlock write fails (service.py)") - async def test_failed_unlock_write_leaves_flag_stuck(hass): - """if frc=0 cannot be written after the wait, the charger stays locked - and the flag blocks every later stop_charging call.""" - coordinator = _coordinator(hass, _entry(hass), websocket=True) - service = GoeChargerApiV2Service(hass, coordinator._config_entry, coordinator) - coordinator.async_write_key = AsyncMock(side_effect=[{Tag.FRC.key: 1}, ValueError("down")]) - with patch(SERVICE_SLEEP, AsyncMock()): - await service.stop_charging(_call()) -> assert service._stop_in_progress is False -E assert True is False -E + where True = <custom_components.goecharger_api2.service.GoeChargerApiV2Service object at 0x10f7b6db0>._stop_in_progress - -tests/integration/test_services_and_unload.py:100: AssertionErrorhass = <HomeAssistant RUNNING> -caplog = <_pytest.logging.LogCaptureFixture object at 0x10dc3d6e0> - - @pytest.mark.known_bug("write_multiple_values_to_keys returns {} over WebSocket; handle_write_result only skips on None") - async def test_websocket_multi_write_logs_no_error_on_success(hass, caplog): - """a successful multi-key write in WebSocket mode is logged as an ERROR.""" - coordinator = _coordinator(hass, _entry(hass), websocket=True) - with patch(BRIDGE_SLEEP, AsyncMock()), caplog.at_level(logging.ERROR): - await coordinator.async_write_multiple_keys({"ct": "1", "mca": 6}, "ct", "default") - assert [k for k, _ in _sent(coordinator)] == ["ct", "mca"] - errors = [r.message for r in caplog.records if r.levelno >= logging.ERROR] -> assert errors == [] -E assert ['handle_writ...sult was: {}'] == [] -E -E Left contains one more item: "handle_write_result() could not write multiple value: 'default' to: ct result was: {}" -E Use -v to get more diff - -tests/integration/test_services_and_unload.py:113: AssertionErrorhass = <HomeAssistant RUNNING> - - @pytest.mark.known_bug("a reloaded entry is appended behind the others, so the default charger changes") - async def test_default_charger_does_not_change_after_reload(hass): - """without configid the first registered charger is used. After A is reloaded it - is appended behind B, so the default silently switches from A to B.""" - a = _coordinator(hass, _entry(hass, "111111"), websocket=False) - b = _coordinator(hass, _entry(hass, "222222"), websocket=False) - service = GoeChargerApiV2Service(hass, a._config_entry, a) - add_config_entry_to_service(b._config_entry, b) - _, before = service.get_coordinator(_call()) - - remove_config_entry_from_service(a._config_entry) - add_config_entry_to_service(a._config_entry, a) - _, after = service.get_coordinator(_call()) -> assert after is before -E assert <custom_components.goecharger_api2.GoeChargerDataUpdateCoordinator object at 0x10ffb11d0> is <custom_components.goecharger_api2.GoeChargerDataUpdateCoordinator object at 0x10ffb0e10> - -tests/integration/test_services_and_unload.py:162: AssertionErrorhass = <HomeAssistant RUNNING> - - @pytest.mark.known_bug("ACTIVE_CHARGER_COUNTER is raised for chargers only but lowered for every entry") - async def test_unloading_a_controller_keeps_the_charger_services(hass): - """the active-charger counter is only raised for chargers but lowered for every - entry, so unloading a controller removes the services of a still-running charger.""" - controller = _entry(hass, "999999", intg_type="controller") - hass.data[DOMAIN] = {controller.entry_id: MagicMock(), ACTIVE_CHARGER_COUNTER: 1} # one charger - _register_services(hass) - with patch.object(hass.config_entries, "async_unload_platforms", AsyncMock(return_value=True)): - await async_unload_entry(hass, controller) -> assert hass.services.has_service(DOMAIN, SERVICE_SET_PV_DATA) -E AssertionError: assert False -E + where False = has_service('goecharger_api2', 'set_pv_data') -E + where has_service = <homeassistant.core.ServiceRegistry object at 0x1100c3af0>.has_service -E + where <homeassistant.core.ServiceRegistry object at 0x1100c3af0> = <HomeAssistant RUNNING>.services - -tests/integration/test_services_and_unload.py:183: AssertionErrorhass = <HomeAssistant RUNNING> - - @pytest.mark.known_bug("ACTIVE_CHARGER_COUNTER key does not exist when only controllers are configured") - async def test_unloading_a_controller_without_any_charger_does_not_raise(hass): - """with only controllers configured the counter key does not exist.""" - controller = _entry(hass, "999999", intg_type="controller") - hass.data[DOMAIN] = {controller.entry_id: MagicMock()} - with patch.object(hass.config_entries, "async_unload_platforms", AsyncMock(return_value=True)): -> assert await async_unload_entry(hass, controller) is True - ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -tests/integration/test_services_and_unload.py:192: -_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ - -hass = <HomeAssistant RUNNING> -config_entry = <ConfigEntry entry_id=01M63MCRT0KH5RDX17D1NGX058 version=1 domain=goecharger_api2 title=go-e 999999 state=ConfigEntryState.NOT_LOADED unique_id=None> - - async def async_unload_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> bool: - unload_ok = await hass.config_entries.async_unload_platforms(config_entry, PLATFORMS) - - if unload_ok: - if DOMAIN in hass.data and config_entry.entry_id in hass.data[DOMAIN]: - coordinator = hass.data[DOMAIN][config_entry.entry_id] - coordinator.cancel_existing_debouncer() - coordinator.stop_watchdog() - coordinator.clear_data() - hass.data[DOMAIN].pop(config_entry.entry_id) -> hass.data[DOMAIN][ACTIVE_CHARGER_COUNTER] = hass.data[DOMAIN][ACTIVE_CHARGER_COUNTER] - 1 - ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -E KeyError: 'active_charger_counter' - -custom_components/goecharger_api2/__init__.py:197: KeyError \ No newline at end of file diff --git a/tests/reports/2026-10-29/pathtrace-all.json b/tests/reports/2026-10-29/pathtrace-all.json deleted file mode 100644 index be8abfc..0000000 --- a/tests/reports/2026-10-29/pathtrace-all.json +++ /dev/null @@ -1,638 +0,0 @@ -{ - "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/pygoecharger_ha/const.py::INTG_TYPE": { - "calls": 1, - "distinct_paths": 1 - }, - "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/pygoecharger_ha/const.py::CAR_VALUES": { - "calls": 1, - "distinct_paths": 1 - }, - "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/pygoecharger_ha/const.py::CT_VALUES": { - "calls": 1, - "distinct_paths": 1 - }, - "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/pygoecharger_ha/const.py::": { - "calls": 1, - "distinct_paths": 1 - }, - "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/pygoecharger_ha/keys.py::CAT": { - "calls": 1, - "distinct_paths": 1 - }, - "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/pygoecharger_ha/keys.py::ApiKey": { - "calls": 1, - "distinct_paths": 1 - }, - "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/pygoecharger_ha/keys.py::ApiKey.__annotate__": { - "calls": 2, - "distinct_paths": 2 - }, - "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/pygoecharger_ha/keys.py::Tag": { - "calls": 1, - "distinct_paths": 1 - }, - "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/pygoecharger_ha/keys.py::": { - "calls": 1, - "distinct_paths": 1 - }, - "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/pygoecharger_ha/__init__.py::TargetedEvent": { - "calls": 1, - "distinct_paths": 1 - }, - "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/pygoecharger_ha/__init__.py::NonNullDict": { - "calls": 1, - "distinct_paths": 1 - }, - "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/pygoecharger_ha/__init__.py::ExpiringStore": { - "calls": 1, - "distinct_paths": 1 - }, - "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/pygoecharger_ha/__init__.py::GoeChargerApiV2Bridge": { - "calls": 1, - "distinct_paths": 1 - }, - "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/pygoecharger_ha/__init__.py::": { - "calls": 1, - "distinct_paths": 1 - }, - "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/const.py::ExtBinarySensorEntityDescription": { - "calls": 1, - "distinct_paths": 1 - }, - "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/const.py::ExtBinarySensorEntityDescription.__annotate__": { - "calls": 1, - "distinct_paths": 1 - }, - "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/const.py::ExtButtonEntityDescription": { - "calls": 1, - "distinct_paths": 1 - }, - "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/const.py::ExtButtonEntityDescription.__annotate__": { - "calls": 1, - "distinct_paths": 1 - }, - "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/const.py::ExtNumberEntityDescription": { - "calls": 1, - "distinct_paths": 1 - }, - "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/const.py::ExtNumberEntityDescription.__annotate__": { - "calls": 1, - "distinct_paths": 1 - }, - "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/const.py::ExtSelectEntityDescription": { - "calls": 1, - "distinct_paths": 1 - }, - "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/const.py::ExtSelectEntityDescription.__annotate__": { - "calls": 1, - "distinct_paths": 1 - }, - "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/const.py::ExtSensorEntityDescription": { - "calls": 1, - "distinct_paths": 1 - }, - "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/const.py::ExtSensorEntityDescription.__annotate__": { - "calls": 1, - "distinct_paths": 1 - }, - "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/const.py::ExtSwitchEntityDescription": { - "calls": 1, - "distinct_paths": 1 - }, - "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/const.py::ExtSwitchEntityDescription.__annotate__": { - "calls": 1, - "distinct_paths": 1 - }, - "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/const.py::": { - "calls": 1, - "distinct_paths": 1 - }, - "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/entity.py::CustomFriendlyNameEntity": { - "calls": 1, - "distinct_paths": 1 - }, - "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/entity.py::": { - "calls": 1, - "distinct_paths": 1 - }, - "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/service.py::GoeChargerApiV2Service": { - "calls": 1, - "distinct_paths": 1 - }, - "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/service.py::": { - "calls": 1, - "distinct_paths": 1 - }, - "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/__init__.py::GoeChargerDataUpdateCoordinator": { - "calls": 1, - "distinct_paths": 1 - }, - "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/__init__.py::GoeChargerBaseEntity": { - "calls": 1, - "distinct_paths": 1 - }, - "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/__init__.py::": { - "calls": 1, - "distinct_paths": 1 - }, - "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/config_flow.py::GoeChargerApiV2FlowHandler": { - "calls": 1, - "distinct_paths": 1 - }, - "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/config_flow.py::": { - "calls": 1, - "distinct_paths": 1 - }, - "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/binary_sensor.py::GoeChargerApiV2BinarySensor": { - "calls": 1, - "distinct_paths": 1 - }, - "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/binary_sensor.py::": { - "calls": 1, - "distinct_paths": 1 - }, - "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/button.py::GoeChargerApiV2Button": { - "calls": 1, - "distinct_paths": 1 - }, - "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/button.py::": { - "calls": 1, - "distinct_paths": 1 - }, - "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/select.py::GoeChargerSelect": { - "calls": 1, - "distinct_paths": 1 - }, - "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/select.py::": { - "calls": 1, - "distinct_paths": 1 - }, - "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/switch.py::GoeChargerSwitch": { - "calls": 1, - "distinct_paths": 1 - }, - "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/switch.py::": { - "calls": 1, - "distinct_paths": 1 - }, - "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/number.py::GoeChargerNumber": { - "calls": 1, - "distinct_paths": 1 - }, - "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/number.py::": { - "calls": 1, - "distinct_paths": 1 - }, - "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/sensor.py::GoeChargerSensor": { - "calls": 1, - "distinct_paths": 1 - }, - "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/sensor.py::": { - "calls": 1, - "distinct_paths": 1 - }, - "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/pygoecharger_ha/__init__.py::GoeChargerApiV2Bridge.__init__": { - "calls": 466, - "distinct_paths": 11 - }, - "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/pygoecharger_ha/__init__.py::ExpiringStore.__init__": { - "calls": 466, - "distinct_paths": 1 - }, - "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/pygoecharger_ha/__init__.py::GoeChargerApiV2Bridge._write_values_int": { - "calls": 30, - "distinct_paths": 11 - }, - "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/pygoecharger_ha/__init__.py::GoeChargerApiV2Bridge._read_filtered_data": { - "calls": 57, - "distinct_paths": 10 - }, - "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/pygoecharger_ha/__init__.py::NonNullDict.none_null_get": { - "calls": 399, - "distinct_paths": 1 - }, - "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/pygoecharger_ha/__init__.py::GoeChargerApiV2Bridge.write_value_to_key": { - "calls": 36, - "distinct_paths": 11 - }, - "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/pygoecharger_ha/__init__.py::GoeChargerApiV2Bridge.read_all_states": { - "calls": 17, - "distinct_paths": 11 - }, - "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/pygoecharger_ha/__init__.py::GoeChargerApiV2Bridge._read_filtered_data_from_ws_source": { - "calls": 16, - "distinct_paths": 1 - }, - "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/pygoecharger_ha/__init__.py::GoeChargerApiV2Bridge.read_all_config": { - "calls": 8, - "distinct_paths": 5 - }, - "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/pygoecharger_ha/__init__.py::GoeChargerApiV2Bridge.force_config_update": { - "calls": 2, - "distinct_paths": 2 - }, - "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/pygoecharger_ha/__init__.py::GoeChargerApiV2Bridge.read_all": { - "calls": 4, - "distinct_paths": 3 - }, - "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/pygoecharger_ha/__init__.py::GoeChargerApiV2Bridge.read_versions": { - "calls": 18, - "distinct_paths": 9 - }, - "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/pygoecharger_ha/__init__.py::GoeChargerApiV2Bridge.read_system": { - "calls": 20, - "distinct_paths": 3 - }, - "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/pygoecharger_ha/__init__.py::GoeChargerApiV2Bridge._read_all_data": { - "calls": 6, - "distinct_paths": 5 - }, - "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/pygoecharger_ha/__init__.py::GoeChargerApiV2Bridge.clear_data": { - "calls": 4, - "distinct_paths": 2 - }, - "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/pygoecharger_ha/__init__.py::GoeChargerApiV2Bridge.available_fields": { - "calls": 1, - "distinct_paths": 1 - }, - "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/pygoecharger_ha/__init__.py::GoeChargerApiV2Bridge.reset_stored_update_ts": { - "calls": 3, - "distinct_paths": 3 - }, - "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/pygoecharger_ha/__init__.py::GoeChargerApiV2Bridge._ws_notify_for_new_data": { - "calls": 15, - "distinct_paths": 2 - }, - "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/pygoecharger_ha/__init__.py::GoeChargerApiV2Bridge._ws_notify_for_new_data.._ws_debounce_coordinator_update": { - "calls": 12, - "distinct_paths": 4 - }, - "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/pygoecharger_ha/__init__.py::GoeChargerApiV2Bridge.ws_check_last_update": { - "calls": 6, - "distinct_paths": 2 - }, - "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/pygoecharger_ha/__init__.py::GoeChargerApiV2Bridge.ws_close": { - "calls": 25, - "distinct_paths": 3 - }, - "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/pygoecharger_ha/__init__.py::ExpiringStore._cleanup": { - "calls": 22, - "distinct_paths": 2 - }, - "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/pygoecharger_ha/__init__.py::ExpiringStore.set": { - "calls": 14, - "distinct_paths": 1 - }, - "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/pygoecharger_ha/__init__.py::ExpiringStore.get": { - "calls": 6, - "distinct_paths": 2 - }, - "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/pygoecharger_ha/__init__.py::ExpiringStore.__len__": { - "calls": 2, - "distinct_paths": 1 - }, - "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/pygoecharger_ha/__init__.py::GoeChargerApiV2Bridge._ws_send_command": { - "calls": 11, - "distinct_paths": 2 - }, - "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/pygoecharger_ha/__init__.py::GoeChargerApiV2Bridge.write_multiple_values_to_keys": { - "calls": 3, - "distinct_paths": 2 - }, - "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/config_flow.py::GoeChargerApiV2FlowHandler.__init__": { - "calls": 25, - "distinct_paths": 1 - }, - "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/config_flow.py::GoeChargerApiV2FlowHandler.async_step_user": { - "calls": 33, - "distinct_paths": 3 - }, - "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/config_flow.py::GoeChargerApiV2FlowHandler.async_step_user_lan": { - "calls": 27, - "distinct_paths": 5 - }, - "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/config_flow.py::GoeChargerApiV2FlowHandler._test_host": { - "calls": 18, - "distinct_paths": 7 - }, - "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/__init__.py::async_setup": { - "calls": 11, - "distinct_paths": 1 - }, - "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/__init__.py::async_unload_entry": { - "calls": 14, - "distinct_paths": 5 - }, - "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/config_flow.py::GoeChargerApiV2FlowHandler.async_step_user_wan": { - "calls": 11, - "distinct_paths": 5 - }, - "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/config_flow.py::GoeChargerApiV2FlowHandler.async_step_reconfigure": { - "calls": 4, - "distinct_paths": 2 - }, - "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/pygoecharger_ha/__init__.py::TargetedEvent.__init__": { - "calls": 30, - "distinct_paths": 2 - }, - "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/pygoecharger_ha/__init__.py::TargetedEvent.has_errors": { - "calls": 21, - "distinct_paths": 1 - }, - "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/config_flow.py::GoeChargerApiV2FlowHandler._check_ws_connection_and_flags": { - "calls": 4, - "distinct_paths": 4 - }, - "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/pygoecharger_ha/__init__.py::TargetedEvent.set_error": { - "calls": 15, - "distinct_paths": 1 - }, - "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/__init__.py::GoeChargerDataUpdateCoordinator.__init__": { - "calls": 286, - "distinct_paths": 5 - }, - "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/__init__.py::GoeChargerDataUpdateCoordinator._handle_client_connection_error": { - "calls": 15, - "distinct_paths": 2 - }, - "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/__init__.py::GoeChargerDataUpdateCoordinator.async_write_key": { - "calls": 7, - "distinct_paths": 5 - }, - "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/__init__.py::GoeChargerDataUpdateCoordinator.async_write_multiple_keys": { - "calls": 4, - "distinct_paths": 4 - }, - "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/__init__.py::GoeChargerDataUpdateCoordinator._async_update_data": { - "calls": 19, - "distinct_paths": 5 - }, - "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/__init__.py::async_setup_entry": { - "calls": 17, - "distinct_paths": 11 - }, - "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/__init__.py::GoeChargerDataUpdateCoordinator.handle_write_result": { - "calls": 11, - "distinct_paths": 7 - }, - "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/__init__.py::check_and_write_to_16a": { - "calls": 8, - "distinct_paths": 8 - }, - "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/__init__.py::GoeChargerDataUpdateCoordinator.check_for_16a_limit": { - "calls": 1, - "distinct_paths": 1 - }, - "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/__init__.py::GoeChargerDataUpdateCoordinator.read_versions": { - "calls": 28, - "distinct_paths": 21 - }, - "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/__init__.py::GoeChargerDataUpdateCoordinator.read_versions..": { - "calls": 26, - "distinct_paths": 2 - }, - "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/__init__.py::GoeChargerDataUpdateCoordinator.is_valid_charger_entity": { - "calls": 14, - "distinct_paths": 7 - }, - "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/pygoecharger_ha/__init__.py::GoeChargerApiV2Bridge.ws_set_coordinator": { - "calls": 7, - "distinct_paths": 1 - }, - "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/__init__.py::GoeChargerDataUpdateCoordinator.start_websocket_and_wait_for_first_data": { - "calls": 6, - "distinct_paths": 6 - }, - "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/__init__.py::GoeChargerDataUpdateCoordinator.force_async_update_now": { - "calls": 4, - "distinct_paths": 4 - }, - "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/__init__.py::GoeChargerDataUpdateCoordinator._check_for_ws_task_and_cancel_if_running": { - "calls": 10, - "distinct_paths": 3 - }, - "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/__init__.py::GoeChargerDataUpdateCoordinator._async_watchdog_check": { - "calls": 4, - "distinct_paths": 3 - }, - "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/__init__.py::GoeChargerDataUpdateCoordinator.start_watchdog": { - "calls": 1, - "distinct_paths": 1 - }, - "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/__init__.py::GoeChargerDataUpdateCoordinator.stop_watchdog": { - "calls": 4, - "distinct_paths": 2 - }, - "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/__init__.py::GoeChargerDataUpdateCoordinator.call_later_update_device_registry": { - "calls": 5, - "distinct_paths": 5 - }, - "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/__init__.py::GoeChargerDataUpdateCoordinator.cancel_existing_debouncer": { - "calls": 9, - "distinct_paths": 2 - }, - "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/__init__.py::GoeChargerDataUpdateCoordinator.request_update_in_sec": { - "calls": 3, - "distinct_paths": 1 - }, - "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/__init__.py::GoeChargerDataUpdateCoordinator._debounce_coordinator_update": { - "calls": 2, - "distinct_paths": 2 - }, - "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/__init__.py::GoeChargerDataUpdateCoordinator.clear_data": { - "calls": 3, - "distinct_paths": 1 - }, - "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/__init__.py::GoeChargerDataUpdateCoordinator.trigger_restart_delayed": { - "calls": 1, - "distinct_paths": 1 - }, - "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/__init__.py::GoeChargerDataUpdateCoordinator.__call__": { - "calls": 1, - "distinct_paths": 1 - }, - "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/__init__.py::GoeChargerDataUpdateCoordinator.cards_as_object_present": { - "calls": 7, - "distinct_paths": 1 - }, - "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/__init__.py::GoeChargerBaseEntity.__init__": { - "calls": 1908, - "distinct_paths": 13 - }, - "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/__init__.py::GoeChargerBaseEntity.unique_id": { - "calls": 467, - "distinct_paths": 1 - }, - "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/__init__.py::async_migrate_entry": { - "calls": 3, - "distinct_paths": 3 - }, - "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/select.py::GoeChargerSelect.__init__": { - "calls": 27, - "distinct_paths": 2 - }, - "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/select.py::GoeChargerSelect.async_select_option": { - "calls": 15, - "distinct_paths": 13 - }, - "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/switch.py::GoeChargerSwitch.__init__": { - "calls": 26, - "distinct_paths": 1 - }, - "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/switch.py::GoeChargerSwitch.async_turn_on": { - "calls": 6, - "distinct_paths": 6 - }, - "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/switch.py::GoeChargerSwitch.async_turn_off": { - "calls": 6, - "distinct_paths": 6 - }, - "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/switch.py::GoeChargerSwitch.is_on": { - "calls": 18, - "distinct_paths": 11 - }, - "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/switch.py::GoeChargerSwitch.state": { - "calls": 5, - "distinct_paths": 2 - }, - "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/switch.py::GoeChargerSwitch.icon": { - "calls": 2, - "distinct_paths": 2 - }, - "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/binary_sensor.py::GoeChargerApiV2BinarySensor.__init__": { - "calls": 17, - "distinct_paths": 1 - }, - "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/binary_sensor.py::GoeChargerApiV2BinarySensor.is_on": { - "calls": 18, - "distinct_paths": 13 - }, - "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/binary_sensor.py::GoeChargerApiV2BinarySensor.icon": { - "calls": 2, - "distinct_paths": 2 - }, - "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/button.py::GoeChargerApiV2Button.__init__": { - "calls": 2, - "distinct_paths": 1 - }, - "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/button.py::GoeChargerApiV2Button.async_press": { - "calls": 2, - "distinct_paths": 2 - }, - "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/sensor.py::GoeChargerSensor.__init__": { - "calls": 33, - "distinct_paths": 1 - }, - "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/sensor.py::GoeChargerSensor.native_value": { - "calls": 33, - "distinct_paths": 28 - }, - "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/sensor.py::GoeChargerSensor._camel_to_snake": { - "calls": 3, - "distinct_paths": 1 - }, - "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/sensor.py::GoeChargerSensor.state": { - "calls": 2, - "distinct_paths": 2 - }, - "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/number.py::GoeChargerNumber.__init__": { - "calls": 26, - "distinct_paths": 1 - }, - "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/number.py::GoeChargerNumber.check_amp_min_max": { - "calls": 11, - "distinct_paths": 4 - }, - "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/number.py::GoeChargerNumber.native_value": { - "calls": 9, - "distinct_paths": 8 - }, - "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/number.py::GoeChargerNumber.async_set_native_value": { - "calls": 11, - "distinct_paths": 9 - }, - "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/select.py::GoeChargerSelect.current_option": { - "calls": 11, - "distinct_paths": 11 - }, - "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/service.py::GoeChargerApiV2Service.__init__": { - "calls": 46, - "distinct_paths": 1 - }, - "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/service.py::GoeChargerApiV2Service.get_coordinator": { - "calls": 38, - "distinct_paths": 5 - }, - "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/service.py::_is_finite_number": { - "calls": 28, - "distinct_paths": 1 - }, - "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/service.py::GoeChargerApiV2Service.set_pv_data": { - "calls": 19, - "distinct_paths": 11 - }, - "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/service.py::GoeChargerApiV2Service.stop_charging": { - "calls": 11, - "distinct_paths": 9 - }, - "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/service.py::remove_config_entry_from_service": { - "calls": 6, - "distinct_paths": 2 - }, - "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/service.py::add_config_entry_to_service": { - "calls": 7, - "distinct_paths": 1 - }, - "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/__init__.py::entry_update_listener": { - "calls": 1, - "distinct_paths": 1 - }, - "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/pygoecharger_ha/__init__.py::GoeChargerApiV2Bridge._ws_decode_message": { - "calls": 88, - "distinct_paths": 4 - }, - "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/pygoecharger_ha/__init__.py::GoeChargerApiV2Bridge._ws_normalize_value": { - "calls": 271, - "distinct_paths": 6 - }, - "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/pygoecharger_ha/__init__.py::GoeChargerApiV2Bridge._ws_normalize_dict": { - "calls": 86, - "distinct_paths": 2 - }, - "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/pygoecharger_ha/__init__.py::GoeChargerApiV2Bridge._ws_compute_hashed_password": { - "calls": 31, - "distinct_paths": 3 - }, - "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/pygoecharger_ha/__init__.py::GoeChargerApiV2Bridge._ws_compute_auth_hash": { - "calls": 26, - "distinct_paths": 1 - }, - "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/pygoecharger_ha/__init__.py::GoeChargerApiV2Bridge.extract_ws_message_data": { - "calls": 25, - "distinct_paths": 11 - }, - "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/pygoecharger_ha/__init__.py::GoeChargerApiV2Bridge.ws_connect": { - "calls": 36, - "distinct_paths": 27 - }, - "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/pygoecharger_ha/__init__.py::bcryptjs_base64_encode_serial": { - "calls": 8, - "distinct_paths": 2 - }, - "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/pygoecharger_ha/__init__.py::GoeChargerApiV2Bridge.ws_connect..": { - "calls": 3, - "distinct_paths": 2 - }, - "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/pygoecharger_ha/__init__.py::TargetedEvent.check_and_set..": { - "calls": 5, - "distinct_paths": 2 - }, - "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/pygoecharger_ha/__init__.py::TargetedEvent.check_and_set": { - "calls": 5, - "distinct_paths": 2 - }, - "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/pygoecharger_ha/__init__.py::GoeChargerApiV2Bridge._ws_normalize_value..": { - "calls": 1, - "distinct_paths": 1 - } -} \ No newline at end of file diff --git a/tests/reports/2026-10-29/pathtrace-baseline.json b/tests/reports/2026-10-29/pathtrace-baseline.json deleted file mode 100644 index 1669d25..0000000 --- a/tests/reports/2026-10-29/pathtrace-baseline.json +++ /dev/null @@ -1,366 +0,0 @@ -{ - "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/pygoecharger_ha/const.py::INTG_TYPE": { - "calls": 1, - "distinct_paths": 1 - }, - "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/pygoecharger_ha/const.py::CAR_VALUES": { - "calls": 1, - "distinct_paths": 1 - }, - "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/pygoecharger_ha/const.py::CT_VALUES": { - "calls": 1, - "distinct_paths": 1 - }, - "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/pygoecharger_ha/const.py::": { - "calls": 1, - "distinct_paths": 1 - }, - "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/pygoecharger_ha/keys.py::CAT": { - "calls": 1, - "distinct_paths": 1 - }, - "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/pygoecharger_ha/keys.py::ApiKey": { - "calls": 1, - "distinct_paths": 1 - }, - "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/pygoecharger_ha/keys.py::ApiKey.__annotate__": { - "calls": 2, - "distinct_paths": 2 - }, - "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/pygoecharger_ha/keys.py::Tag": { - "calls": 1, - "distinct_paths": 1 - }, - "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/pygoecharger_ha/keys.py::": { - "calls": 1, - "distinct_paths": 1 - }, - "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/pygoecharger_ha/__init__.py::TargetedEvent": { - "calls": 1, - "distinct_paths": 1 - }, - "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/pygoecharger_ha/__init__.py::NonNullDict": { - "calls": 1, - "distinct_paths": 1 - }, - "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/pygoecharger_ha/__init__.py::ExpiringStore": { - "calls": 1, - "distinct_paths": 1 - }, - "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/pygoecharger_ha/__init__.py::GoeChargerApiV2Bridge": { - "calls": 1, - "distinct_paths": 1 - }, - "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/pygoecharger_ha/__init__.py::": { - "calls": 1, - "distinct_paths": 1 - }, - "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/const.py::ExtBinarySensorEntityDescription": { - "calls": 1, - "distinct_paths": 1 - }, - "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/const.py::ExtBinarySensorEntityDescription.__annotate__": { - "calls": 1, - "distinct_paths": 1 - }, - "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/const.py::ExtButtonEntityDescription": { - "calls": 1, - "distinct_paths": 1 - }, - "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/const.py::ExtButtonEntityDescription.__annotate__": { - "calls": 1, - "distinct_paths": 1 - }, - "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/const.py::ExtNumberEntityDescription": { - "calls": 1, - "distinct_paths": 1 - }, - "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/const.py::ExtNumberEntityDescription.__annotate__": { - "calls": 1, - "distinct_paths": 1 - }, - "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/const.py::ExtSelectEntityDescription": { - "calls": 1, - "distinct_paths": 1 - }, - "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/const.py::ExtSelectEntityDescription.__annotate__": { - "calls": 1, - "distinct_paths": 1 - }, - "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/const.py::ExtSensorEntityDescription": { - "calls": 1, - "distinct_paths": 1 - }, - "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/const.py::ExtSensorEntityDescription.__annotate__": { - "calls": 1, - "distinct_paths": 1 - }, - "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/const.py::ExtSwitchEntityDescription": { - "calls": 1, - "distinct_paths": 1 - }, - "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/const.py::ExtSwitchEntityDescription.__annotate__": { - "calls": 1, - "distinct_paths": 1 - }, - "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/const.py::": { - "calls": 1, - "distinct_paths": 1 - }, - "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/entity.py::CustomFriendlyNameEntity": { - "calls": 1, - "distinct_paths": 1 - }, - "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/entity.py::": { - "calls": 1, - "distinct_paths": 1 - }, - "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/service.py::GoeChargerApiV2Service": { - "calls": 1, - "distinct_paths": 1 - }, - "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/service.py::": { - "calls": 1, - "distinct_paths": 1 - }, - "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/__init__.py::GoeChargerDataUpdateCoordinator": { - "calls": 1, - "distinct_paths": 1 - }, - "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/__init__.py::GoeChargerBaseEntity": { - "calls": 1, - "distinct_paths": 1 - }, - "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/__init__.py::": { - "calls": 1, - "distinct_paths": 1 - }, - "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/config_flow.py::GoeChargerApiV2FlowHandler": { - "calls": 1, - "distinct_paths": 1 - }, - "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/config_flow.py::": { - "calls": 1, - "distinct_paths": 1 - }, - "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/pygoecharger_ha/__init__.py::GoeChargerApiV2Bridge.__init__": { - "calls": 114, - "distinct_paths": 5 - }, - "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/pygoecharger_ha/__init__.py::ExpiringStore.__init__": { - "calls": 113, - "distinct_paths": 1 - }, - "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/pygoecharger_ha/__init__.py::GoeChargerApiV2Bridge._write_values_int": { - "calls": 14, - "distinct_paths": 5 - }, - "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/pygoecharger_ha/__init__.py::GoeChargerApiV2Bridge._read_filtered_data": { - "calls": 17, - "distinct_paths": 8 - }, - "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/pygoecharger_ha/__init__.py::NonNullDict.none_null_get": { - "calls": 154, - "distinct_paths": 1 - }, - "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/pygoecharger_ha/__init__.py::GoeChargerApiV2Bridge.write_value_to_key": { - "calls": 17, - "distinct_paths": 6 - }, - "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/config_flow.py::GoeChargerApiV2FlowHandler.__init__": { - "calls": 8, - "distinct_paths": 1 - }, - "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/config_flow.py::GoeChargerApiV2FlowHandler.async_step_user": { - "calls": 16, - "distinct_paths": 3 - }, - "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/config_flow.py::GoeChargerApiV2FlowHandler.async_step_user_lan": { - "calls": 14, - "distinct_paths": 3 - }, - "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/pygoecharger_ha/__init__.py::GoeChargerApiV2Bridge.read_system": { - "calls": 8, - "distinct_paths": 2 - }, - "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/pygoecharger_ha/__init__.py::GoeChargerApiV2Bridge.read_versions": { - "calls": 1, - "distinct_paths": 1 - }, - "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/config_flow.py::GoeChargerApiV2FlowHandler._test_host": { - "calls": 8, - "distinct_paths": 4 - }, - "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/__init__.py::async_setup": { - "calls": 1, - "distinct_paths": 1 - }, - "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/__init__.py::async_unload_entry": { - "calls": 3, - "distinct_paths": 3 - }, - "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/config_flow.py::GoeChargerApiV2FlowHandler.async_step_user_wan": { - "calls": 2, - "distinct_paths": 2 - }, - "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/__init__.py::GoeChargerDataUpdateCoordinator.__init__": { - "calls": 49, - "distinct_paths": 4 - }, - "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/__init__.py::GoeChargerDataUpdateCoordinator._handle_client_connection_error": { - "calls": 15, - "distinct_paths": 2 - }, - "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/__init__.py::GoeChargerDataUpdateCoordinator.async_write_key": { - "calls": 7, - "distinct_paths": 5 - }, - "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/__init__.py::GoeChargerDataUpdateCoordinator.async_write_multiple_keys": { - "calls": 4, - "distinct_paths": 4 - }, - "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/__init__.py::GoeChargerDataUpdateCoordinator._async_update_data": { - "calls": 19, - "distinct_paths": 5 - }, - "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/__init__.py::async_setup_entry": { - "calls": 4, - "distinct_paths": 4 - }, - "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/__init__.py::GoeChargerBaseEntity.__init__": { - "calls": 1777, - "distinct_paths": 12 - }, - "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/__init__.py::GoeChargerBaseEntity.unique_id": { - "calls": 467, - "distinct_paths": 1 - }, - "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/__init__.py::async_migrate_entry": { - "calls": 3, - "distinct_paths": 3 - }, - "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/service.py::GoeChargerApiV2Service.__init__": { - "calls": 13, - "distinct_paths": 1 - }, - "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/service.py::GoeChargerApiV2Service.get_coordinator": { - "calls": 14, - "distinct_paths": 2 - }, - "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/service.py::_is_finite_number": { - "calls": 14, - "distinct_paths": 1 - }, - "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/service.py::GoeChargerApiV2Service.set_pv_data": { - "calls": 7, - "distinct_paths": 2 - }, - "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/pygoecharger_ha/__init__.py::ExpiringStore._cleanup": { - "calls": 12, - "distinct_paths": 1 - }, - "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/pygoecharger_ha/__init__.py::ExpiringStore.set": { - "calls": 10, - "distinct_paths": 1 - }, - "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/pygoecharger_ha/__init__.py::GoeChargerApiV2Bridge._ws_send_command": { - "calls": 9, - "distinct_paths": 2 - }, - "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/__init__.py::GoeChargerDataUpdateCoordinator.handle_write_result": { - "calls": 4, - "distinct_paths": 2 - }, - "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/service.py::GoeChargerApiV2Service.stop_charging": { - "calls": 3, - "distinct_paths": 3 - }, - "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/pygoecharger_ha/__init__.py::GoeChargerApiV2Bridge.write_multiple_values_to_keys": { - "calls": 1, - "distinct_paths": 1 - }, - "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/service.py::add_config_entry_to_service": { - "calls": 5, - "distinct_paths": 1 - }, - "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/service.py::remove_config_entry_from_service": { - "calls": 3, - "distinct_paths": 2 - }, - "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/__init__.py::GoeChargerDataUpdateCoordinator.read_versions..": { - "calls": 10, - "distinct_paths": 2 - }, - "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/__init__.py::GoeChargerDataUpdateCoordinator.read_versions": { - "calls": 10, - "distinct_paths": 7 - }, - "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/pygoecharger_ha/__init__.py::TargetedEvent.__init__": { - "calls": 16, - "distinct_paths": 2 - }, - "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/pygoecharger_ha/__init__.py::GoeChargerApiV2Bridge._ws_decode_message": { - "calls": 26, - "distinct_paths": 3 - }, - "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/pygoecharger_ha/__init__.py::GoeChargerApiV2Bridge._ws_normalize_value": { - "calls": 80, - "distinct_paths": 4 - }, - "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/pygoecharger_ha/__init__.py::GoeChargerApiV2Bridge._ws_normalize_dict": { - "calls": 24, - "distinct_paths": 2 - }, - "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/pygoecharger_ha/__init__.py::GoeChargerApiV2Bridge._ws_compute_hashed_password": { - "calls": 11, - "distinct_paths": 3 - }, - "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/pygoecharger_ha/__init__.py::GoeChargerApiV2Bridge._ws_compute_auth_hash": { - "calls": 6, - "distinct_paths": 1 - }, - "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/pygoecharger_ha/__init__.py::GoeChargerApiV2Bridge.extract_ws_message_data": { - "calls": 13, - "distinct_paths": 10 - }, - "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/pygoecharger_ha/__init__.py::GoeChargerApiV2Bridge._ws_notify_for_new_data": { - "calls": 2, - "distinct_paths": 1 - }, - "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/pygoecharger_ha/__init__.py::TargetedEvent.check_and_set..": { - "calls": 3, - "distinct_paths": 2 - }, - "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/pygoecharger_ha/__init__.py::TargetedEvent.check_and_set": { - "calls": 3, - "distinct_paths": 2 - }, - "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/pygoecharger_ha/__init__.py::GoeChargerApiV2Bridge.ws_close": { - "calls": 2, - "distinct_paths": 1 - }, - "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/pygoecharger_ha/__init__.py::GoeChargerApiV2Bridge.ws_connect": { - "calls": 13, - "distinct_paths": 11 - }, - "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/pygoecharger_ha/__init__.py::TargetedEvent.has_errors": { - "calls": 11, - "distinct_paths": 1 - }, - "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/pygoecharger_ha/__init__.py::GoeChargerApiV2Bridge._ws_notify_for_new_data.._ws_debounce_coordinator_update": { - "calls": 2, - "distinct_paths": 1 - }, - "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/pygoecharger_ha/__init__.py::TargetedEvent.set_error": { - "calls": 11, - "distinct_paths": 1 - }, - "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/pygoecharger_ha/__init__.py::ExpiringStore.get": { - "calls": 2, - "distinct_paths": 2 - }, - "/Users/oliver-marcuseder/01-vibe-coding/01-homeassistant/ha-goecharger-api2-main/tests/integration/../../custom_components/goecharger_api2/pygoecharger_ha/__init__.py::bcryptjs_base64_encode_serial": { - "calls": 5, - "distinct_paths": 2 - } -} \ No newline at end of file diff --git a/tests/reports/2026-10-29/testreport.md b/tests/reports/2026-10-29/testreport.md deleted file mode 100644 index 5de3007..0000000 --- a/tests/reports/2026-10-29/testreport.md +++ /dev/null @@ -1,269 +0,0 @@ -# Testreport und Testabdeckung: `ha-goecharger-api2` - -| | | -|---|---| -| **Datum** | 2026-10-29 | -| **Geprüfter Code** | `marq24/ha-goecharger-api2` `main`, Commit `98af3e0` | -| **Tests** | Fork `OliEder/ha-goecharger-api2`, Branch `tests/regression-suite`, Commit `1d50118+local` | -| **Umgebung** | Python 3.14.3, Home Assistant 2026.10.4, coverage 7.16.2, radon 6.0.1 | -| **Rohdaten** | `coverage-all.json`, `coverage-blocking.json`, `coverage-baseline.json`, `junit-all.xml`, `pathtrace-all.json`, `pathtrace-baseline.json` in diesem Ordner (der HTML-Bericht ist nicht eingecheckt und lässt sich mit `tests/tools/measure.sh` neu erzeugen) | - -## 1. Zusammenfassung - -| Kennzahl | Wert | -|---|---| -| Tests gesammelt / bestanden / fehlgeschlagen | 911 / 885 / 26 (alle beabsichtigt: 12 `known_bug`, 14 `needs_decision`) | -| Blockierende Menge (ohne die beiden Marker) | 885 von 885 bestanden | -| **Zeilenabdeckung** (alle Tests) | **93.0 %** (2351 von 2529 Anweisungen) | -| **Zweigabdeckung** (alle Tests) | **85.0 %** (784 von 922 Zweigen) | -| **Pfadabdeckung** (obere Schranke der Basispfade, alle Funktionen) | **61 %** (484 von 794) | -| **Pfadabdeckung** (obere Schranke, nur die vier Kernmodule) | **62 %** (357 von 580) | -| Funktionen: nie ausgeführt / mit zu wenigen Pfaden / Pfadzahl nicht widerlegt | 14 / 58 / 48 (von 120) | - -**Entwicklung gegenüber dem ersten Report (gleicher Tag, Stand vor dem Ausbau der Tests)** - -| Kennzahl | Vorher | Jetzt | -|---|---|---| -| Verhaltens- und Fehlertests (ohne Vertragsprüfungen je Schlüssel) | 125 | 491 | -| Zeilenabdeckung | 55.2 % | 93.0 % | -| Zweigabdeckung | 31.8 % | 85.0 % | -| Pfadabdeckung (obere Schranke), alle Funktionen | 20 % | 61 % | -| Pfadabdeckung (obere Schranke), Kernmodule | 27 % | 62 % | - -**Was das bedeutet:** Die Tests prüfen jetzt auch die Plattformen (Wertdarstellung, Schreiben) und die Laufzeitpfade des Koordinators. Zeilen- und Zweigabdeckung sind hoch, die Pfadabdeckung ist deutlich niedriger, weil sie verlangt, dass jede Funktion **viele verschiedene Kombinationen** von Entscheidungen durchläuft. Die Pfadzahl ist eine obere Schranke und kein Beleg für Vollständigkeit (Abschnitt 2). - -## 2. Begriffe und Messverfahren (wichtig für die Einordnung) - -| Kennzahl | Bedeutung | Gemessen mit | -|---|---|---| -| Zeilenabdeckung | Anteil der ausgeführten Anweisungen | `coverage.py` | -| Zweigabdeckung | Anteil der genommenen Verzweigungsausgänge (`if`/`else`, Schleife betreten/übersprungen, `try`/`except`). Entspricht der Entscheidungsabdeckung. | `coverage.py --cov-branch` | -| Pfadabdeckung | Anteil der möglichen Ausführungswege durch eine Funktion. **Wird von `coverage.py` nicht gemessen** und ist bei Schleifen und vielen Entscheidungen praktisch nicht vollständig erreichbar (Zahl der Pfade wächst exponentiell). | siehe unten | - -**Wie die Pfadzahlen entstehen.** Ein eigenes Test-Plugin (`tests/tools/pathtracer.py`, nutzt `sys.monitoring`) zeichnet für **jeden Aufruf** einer Funktion die Folge der ausgeführten Zeilen auf. Schleifen werden verkürzt: eine Zeile zählt je Aufruf höchstens zweimal. Danach zählt es, wie viele **verschiedene** Pfade je Funktion im Testlauf vorkamen. - -Diese Zahl wird mit der **zyklomatischen Komplexität** (CC, `radon`) verglichen. Die CC ist die Zahl der linear unabhängigen Basispfade einer Funktion. Daraus folgt eine **sichere Aussage in eine Richtung**: -- Wurden weniger verschiedene Pfade beobachtet als CC, sind **sicher nicht** alle Basispfade getestet. -- Wurden mindestens CC Pfade beobachtet, ist das **kein Beleg** für vollständige Abdeckung. Die Pfade können sich in denselben Entscheidungen ähneln. -- Die Kennzahl „Pfadabdeckung (obere Schranke)“ summiert je Funktion `min(beobachtete Pfade, CC)` und teilt durch die Summe der CC. Die tatsächliche Basispfadabdeckung ist **höchstens so hoch**. - -Grenzen: Die Messung ist empirisch und heuristisch. Verschachtelte Funktionen werden der umgebenden Funktion zugerechnet oder nicht erfasst. Nur eine Tracing-Variante (Zeilenfolgen) wurde verwendet. - -## 3. Testergebnisse - -| Datei | Tests | fehlgeschlagen | Zeit | -|---|---|---|---| -| `test_api_contract.py` | 420 | 14 | 5.77 s | -| `test_bridge.py` | 3 | 0 | 0.06 s | -| `test_bridge_http.py` | 20 | 2 | 0.29 s | -| `test_bridge_polling.py` | 47 | 0 | 0.63 s | -| `test_bridge_write_paths.py` | 21 | 0 | 0.44 s | -| `test_config_flow_failures.py` | 8 | 0 | 0.17 s | -| `test_config_flow_paths.py` | 17 | 0 | 0.32 s | -| `test_coordinator_errors.py` | 2 | 0 | 0.03 s | -| `test_coordinator_failures.py` | 14 | 0 | 0.23 s | -| `test_coordinator_runtime.py` | 76 | 0 | 1.12 s | -| `test_entity_ids.py` | 10 | 0 | 0.16 s | -| `test_migration.py` | 3 | 0 | 0.04 s | -| `test_platform_actions.py` | 60 | 0 | 0.89 s | -| `test_platform_values.py` | 72 | 0 | 1.04 s | -| `test_service.py` | 7 | 3 | 0.10 s | -| `test_services_and_setup.py` | 39 | 1 | 0.68 s | -| `test_services_and_unload.py` | 10 | 6 | 0.22 s | -| `test_versions_16a.py` | 10 | 0 | 0.20 s | -| `test_ws_connect_paths.py` | 34 | 0 | 2.54 s | -| `test_ws_login_failures.py` | 13 | 0 | 0.96 s | -| `test_ws_protocol.py` | 25 | 0 | 0.58 s | - -Hinweis: `test_api_contract.py` enthält 420 parametrisierte Vertragsprüfungen (je Schlüssel). Verhaltens- und Fehlerpfadtests insgesamt: 491. - -**Gesamtdauer** 18.619 s. - -## 4. Abdeckung je Datei - -| Datei | Anweisungen | Zeilenabdeckung | Zweige | Zweigabdeckung | CC (Summe) | Pfade (obere Schranke) | -|---|---|---|---|---|---|---| -| `__init__.py` | 604 | 96 % | 266 | 89 % | 224 | 59 % | -| `binary_sensor.py` | 71 | 82 % | 34 | 68 % | 27 | 59 % | -| `button.py` | 31 | 61 % | 8 | 0 % | 8 | 38 % | -| `config_flow.py` | 178 | 96 % | 44 | 93 % | 42 | 64 % | -| `const.py` | 69 | 100 % | 0 | – | 0 | – | -| `entity.py` | 31 | 26 % | 14 | 0 % | 11 | 0 % | -| `number.py` | 94 | 81 % | 48 | 71 % | 50 | 44 % | -| `pygoecharger_ha/__init__.py` | 721 | 95 % | 302 | 91 % | 280 | 60 % | -| `pygoecharger_ha/const.py` | 61 | 100 % | 0 | – | 0 | – | -| `pygoecharger_ha/keys.py` | 255 | 99 % | 0 | – | 2 | 0 % | -| `select.py` | 100 | 88 % | 56 | 84 % | 36 | 72 % | -| `sensor.py` | 109 | 87 % | 60 | 82 % | 42 | 76 % | -| `service.py` | 104 | 97 % | 42 | 98 % | 34 | 85 % | -| `switch.py` | 101 | 88 % | 48 | 79 % | 38 | 74 % | - -**Vergleich:** Nur die blockierenden Tests: Zeilen 92.9 %, Zweige 85.0 %. Die beabsichtigt roten Tests (bekannte Fehler und Abweichungen) verändern die Abdeckung kaum (0.1 Prozentpunkte bei den Zeilen). - - -## 5. Pfade nach Kritikalität - -**Maßstab.** Kritikalität ist die Auswirkung eines falschen Pfades auf den Nutzer, nicht die Häufigkeit. Die Einstufung gilt für eine ganze Funktion und damit für alle ihre Pfade. Die Zuordnung steht in `tests/tools/criticality.py` (mit Begründung je Funktion) und ist eine Einschätzung, keine Messung. - -| Klasse | Bedeutung | Beispiele | -|---|---|---| -| **K1 kritisch** | Ändert den Charger, seine Grenzen oder die HA-Konfiguration; ein falscher Pfad kann den Charger sperren, falsche Werte senden, Geräte löschen oder Dienste entfernen | `write_value_to_key`, `stop_charging`, `set_pv_data`, `check_and_write_to_16a`, `async_migrate_entry` | -| **K2 hoch** | Verbindung, Anmeldung, Erholung, Start; ein falscher Pfad macht die Integration unverfügbar oder zeigt alte Daten | `ws_connect`, `_async_update_data`, `async_setup_entry`, Config-Flow | -| **K3 mittel** | Korrektheit dessen, was angezeigt oder erkannt wird, ohne Schreiben | Wertdarstellung der Entitäten, `is_valid_charger_entity` | -| **K4 niedrig** | Hilfsfunktionen, Namen, Symbole, Logging | `_ws_normalize_value`, `unique_id`, Anzeigename | - -| Klasse | Funktionen | CC (Basispfade) | Pfade vorher | Pfade jetzt (obere Schranke) | Zeilen | Zweige | nie ausgeführt | -|---|---|---|---|---|---|---|---| -| **K1 kritisch** | 24 | 195 | 22 % | **66 %** (128 von 195) | 96 % | 96 % | 0 | -| **K2 hoch** | 38 | 335 | 24 % | **60 %** (202 von 335) | 96 % | 91 % | 0 | -| **K3 mittel** | 33 | 171 | 1 % | **64 %** (109 von 171) | 78 % | 72 % | 6 | -| **K4 niedrig** | 25 | 93 | 34 % | **48 %** (45 von 93) | 71 % | 57 % | 8 | - -Lesehilfe: „Pfade jetzt“ ist die obere Schranke der Basispfadabdeckung (Abschnitt 2). Die Spalten „Zeilen“ und „Zweige“ stehen für dieselben Funktionen und liegen viel höher; der Unterschied zeigt, wie viel strenger die Pfadabdeckung ist. - -### 5.1 Offene Lücken in K1 und K2 - -Funktionen, bei denen **sicher** Basispfade fehlen (beobachtete Pfade unter der CC), geordnet nach Klasse und Zahl der fehlenden Pfade. „Fehlerpfad“ markiert nicht genommene Zweige, deren Zeile nach einem Fehlerfall aussieht (Schlüsselwörter wie `except`, `raise`, `error`, `warning`); das ist eine Heuristik. - -**K1 kritisch** - -| Funktion | CC | Pfade | fehlen mind. | Aufrufe | Warum kritisch | Nicht genommene Zweige | -|---|---|---|---|---|---|---| -| `check_and_write_to_16a` | 29 | 8 | 21 | 8 | schreibt beim Start Stromgrenzen an den Charger | Z.218 `if hass is not None:` (Normalpfad)
    Z.220 `if a_entity_reg is not None:` (Normalpfad)
    Z.271 `if a_res_key not in keys_to_patch:` (Normalpfad)
    Z.280 `if a_res_key not in keys_to_patch:` (Normalpfad) | -| `async_migrate_entry` | 11 | 3 | 8 | 3 | löscht Geräte und damit Entitäten | Z.75 `if config_entry.data is not None and len(confi` (Normalpfad)
    Z.77 `if config_entry.options is not None and len(co` (Normalpfad)
    Z.89 `if a_device_reg is not None:` (Normalpfad) | -| `GoeChargerNumber.async_set_native_value` | 17 | 9 | 8 | 11 | schreibt Zahlenwerte (Ströme, Grenzen) | – | -| `GoeChargerApiV2Bridge._write_values_int` | 18 | 11 | 7 | 30 | wertet die Antwort des Chargers auf Schreibbefehle aus | – | -| `GoeChargerApiV2Bridge.write_multiple_values_to_keys` | 8 | 2 | 6 | 3 | schreibt mehrere Werte (Fahrzeugauswahl) | – | -| `GoeChargerNumber.check_amp_min_max` | 9 | 4 | 5 | 11 | löst automatisch das Setzen von amp aus | Z.143 `if amp_value_exceeds_limit:` (Normalpfad) | -| `GoeChargerSelect.async_select_option` | 16 | 13 | 3 | 15 | schreibt Auswahl, inklusive Fahrzeugprofil mit mehreren Werten | – | -| `GoeChargerApiV2Service.set_pv_data` | 14 | 11 | 3 | 19 | sendet PV-Daten; steuert das PV-Laden | – | -| `GoeChargerDataUpdateCoordinator.handle_write_result` | 9 | 7 | 2 | 11 | übernimmt das Schreibergebnis in die Daten | – | -| `_is_finite_number` | 3 | 1 | 2 | 28 | verhindert ungültige Werte an den Charger | Z.23 `if not isinstance(value, Number):` (Normalpfad) | -| `GoeChargerDataUpdateCoordinator.async_write_multiple_keys` | 5 | 4 | 1 | 4 | Mehrfach-Schreibpfad mit Fehlerpause | – | -| `GoeChargerApiV2Bridge.write_value_to_key` | 12 | 11 | 1 | 36 | schreibt einen Wert an den Charger (HTTP oder WebSocket) | – | - -**K2 hoch** - -| Funktion | CC | Pfade | fehlen mind. | Aufrufe | Warum kritisch | Nicht genommene Zweige | -|---|---|---|---|---|---|---| -| `GoeChargerApiV2Bridge.ws_connect` | 61 | 27 | 34 | 36 | Anmeldung und Nachrichtenschleife der WebSocket-Verbindung | Z.786 `if a_event is not None and not a_event.is_set(` (Normalpfad)
    Z.807 `if a_event is not None and not a_event.is_set(` (Normalpfad)
    Z.830 `if 'type' in self._ws_device_info:` (Normalpfad)
    Z.841 `if a_event is not None and not a_event.is_set(` (Normalpfad) | -| `GoeChargerDataUpdateCoordinator.read_versions` | 37 | 21 | 16 | 28 | erkennt Gerätetyp, 16-A-Limit und Karten | Z.713 `if lc_model_type is not None:` (Normalpfad) | -| `GoeChargerApiV2Bridge.read_all_states` | 22 | 11 | 11 | 17 | Statuslesen mit Hibernation | Z.338 `elif self.isController:` (Normalpfad)
    Z.377 `if len(self._states) > 0:` (Normalpfad) | -| `GoeChargerApiV2Bridge.extract_ws_message_data` | 20 | 11 | 9 | 25 | verarbeitet alle Daten und Antworten vom WebSocket | Z.1050 `if 'requestId' in data:` (Normalpfad)
    Z.1056 `if status_data and len(status_data) > 0:` (Normalpfad)
    Z.1059 `if the_request_payload is not None:` (Normalpfad)
    Z.1064 `if the_request_payload is not None:` (Fehlerpfad) | -| `GoeChargerApiV2FlowHandler._test_host` | 14 | 7 | 7 | 18 | prüft Zugang beim Einrichten | – | -| `GoeChargerApiV2Bridge.read_all_config` | 11 | 5 | 6 | 8 | Konfigurationslesen mit Wiederholungs-Pause | Z.404 `if a_key in self._ws_states:` (Normalpfad) | -| `async_setup_entry` | 16 | 11 | 5 | 17 | Start der Integration | – | -| `GoeChargerDataUpdateCoordinator._async_update_data` | 10 | 5 | 5 | 19 | Lesezyklus mit Fehlerpause | Z.609 `if self.bridge.ws_connected and self._force_cl` (Normalpfad)
    Z.623 `if new_data is not None and len(new_data) > 0:` (Normalpfad) | -| `GoeChargerApiV2Bridge.read_versions` | 14 | 9 | 5 | 18 | liest Geräte- und Versionsdaten beim Start | – | -| `GoeChargerApiV2Bridge.__init__` | 15 | 11 | 4 | 466 | bildet Adressen für LAN, Cloud und WebSocket | – | -| `GoeChargerApiV2Bridge._read_all_data` | 9 | 5 | 4 | 6 | Lesen aller Daten per HTTP | – | -| `bcryptjs_base64_encode_serial` | 6 | 2 | 4 | 8 | Salz für die bcrypt-Anmeldung | Z.1095 `while i < length:` (Normalpfad)
    Z.1105 `if i >= length:` (Normalpfad) | -| `GoeChargerApiV2FlowHandler.async_step_user_wan` | 8 | 5 | 3 | 11 | Einrichtung Cloud | Z.209 `if len(a_user_pwd) == 0 and CONF_PASSWORD in u` (Normalpfad) | -| `GoeChargerApiV2FlowHandler._check_ws_connection_and_flags` | 7 | 4 | 3 | 4 | prüft WebSocket beim Einrichten | Z.284 `if ready_event.is_set():` (Normalpfad) | - - -## 6. Kritische Funktionen (nach Komplexität) - -Kernmodule (`__init__.py` mit Koordinator, `pygoecharger_ha/__init__.py` mit Bridge, `service.py`, `config_flow.py`), sortiert nach Komplexität, gezeigt ab CC 4. „Pfade“ = beobachtete verschiedene Pfade, „CC“ = Basispfade. - -| Funktion | CC | Aufrufe | Pfade | Zeilen | Zweige | Bewertung | -|---|---|---|---|---|---|---| -| `GoeChargerApiV2Bridge.ws_connect` (Bridge) | 61 | 36 | 27 | 96 % | 77 % | es fehlen mindestens 34 Basispfade | -| `GoeChargerDataUpdateCoordinator.read_versions` (Koordinator) | 37 | 28 | 21 | 100 % | 98 % | es fehlen mindestens 16 Basispfade | -| `check_and_write_to_16a` (Setup) | 29 | 8 | 8 | 100 % | 92 % | es fehlen mindestens 21 Basispfade | -| `GoeChargerApiV2Bridge.read_all_states` (Bridge) | 22 | 17 | 11 | 100 % | 93 % | es fehlen mindestens 11 Basispfade | -| `GoeChargerApiV2Bridge.extract_ws_message_data` (Bridge) | 20 | 25 | 11 | 95 % | 85 % | es fehlen mindestens 9 Basispfade | -| `GoeChargerApiV2Bridge._write_values_int` (Bridge) | 18 | 30 | 11 | 72 % | 100 % | es fehlen mindestens 7 Basispfade | -| `async_setup_entry` (Setup) | 16 | 17 | 11 | 100 % | 100 % | es fehlen mindestens 5 Basispfade | -| `GoeChargerApiV2Bridge.__init__` (Bridge) | 15 | 466 | 11 | 100 % | 100 % | es fehlen mindestens 4 Basispfade | -| `GoeChargerApiV2FlowHandler._test_host` (Flow) | 14 | 18 | 7 | 94 % | 100 % | es fehlen mindestens 7 Basispfade | -| `GoeChargerApiV2Bridge.read_versions` (Bridge) | 14 | 18 | 9 | 100 % | 100 % | es fehlen mindestens 5 Basispfade | -| `GoeChargerApiV2Service.set_pv_data` (Dienst) | 14 | 19 | 11 | 100 % | 100 % | es fehlen mindestens 3 Basispfade | -| `GoeChargerBaseEntity.__init__` (Entität) | 13 | 1908 | 13 | 100 % | 94 % | Pfadzahl nicht widerlegt | -| `GoeChargerApiV2Bridge._read_filtered_data` (Bridge) | 12 | 57 | 10 | 81 % | 100 % | es fehlen mindestens 2 Basispfade | -| `GoeChargerApiV2Bridge.write_value_to_key` (Bridge) | 12 | 36 | 11 | 100 % | 100 % | es fehlen mindestens 1 Basispfade | -| `async_migrate_entry` (Setup) | 11 | 3 | 3 | 86 % | 75 % | es fehlen mindestens 8 Basispfade | -| `GoeChargerDataUpdateCoordinator.call_later_update_device_registry` (Koordinator) | 11 | 5 | 5 | 94 % | 79 % | es fehlen mindestens 6 Basispfade | -| `GoeChargerDataUpdateCoordinator.is_valid_charger_entity` (Koordinator) | 11 | 14 | 7 | 100 % | 100 % | es fehlen mindestens 4 Basispfade | -| `GoeChargerApiV2Bridge.read_all_config` (Bridge) | 11 | 8 | 5 | 100 % | 94 % | es fehlen mindestens 6 Basispfade | -| `GoeChargerDataUpdateCoordinator._async_update_data` (Koordinator) | 10 | 19 | 5 | 88 % | 75 % | es fehlen mindestens 5 Basispfade | -| `GoeChargerBaseEntity._friendly_name_internal` (Entität) | 10 | 0 | 0 | 0 % | 0 % | nie ausgeführt | -| `GoeChargerDataUpdateCoordinator.__init__` (Koordinator) | 9 | 286 | 5 | 98 % | 90 % | es fehlen mindestens 4 Basispfade | -| `GoeChargerDataUpdateCoordinator.handle_write_result` (Koordinator) | 9 | 11 | 7 | 100 % | 100 % | es fehlen mindestens 2 Basispfade | -| `GoeChargerApiV2Bridge._read_all_data` (Bridge) | 9 | 6 | 5 | 71 % | 100 % | es fehlen mindestens 4 Basispfade | -| `GoeChargerApiV2Bridge._ws_normalize_value` (Bridge) | 9 | 271 | 6 | 100 % | 100 % | es fehlen mindestens 3 Basispfade | -| `GoeChargerApiV2FlowHandler.async_step_user_wan` (Flow) | 8 | 11 | 5 | 96 % | 90 % | es fehlen mindestens 3 Basispfade | -| `GoeChargerApiV2Bridge.write_multiple_values_to_keys` (Bridge) | 8 | 3 | 2 | 100 % | 100 % | es fehlen mindestens 6 Basispfade | -| `GoeChargerApiV2Service.stop_charging` (Dienst) | 8 | 11 | 9 | 100 % | 100 % | Pfadzahl nicht widerlegt | -| `GoeChargerDataUpdateCoordinator.start_websocket_and_wait_for_first_data` (Koordinator) | 7 | 6 | 6 | 96 % | 88 % | es fehlen mindestens 1 Basispfade | -| `GoeChargerApiV2FlowHandler.async_step_user_lan` (Flow) | 7 | 27 | 5 | 96 % | 90 % | es fehlen mindestens 2 Basispfade | -| `GoeChargerApiV2FlowHandler._check_ws_connection_and_flags` (Flow) | 7 | 4 | 4 | 86 % | 75 % | es fehlen mindestens 3 Basispfade | -| `bcryptjs_base64_encode_serial` (Bridge) | 6 | 8 | 2 | 92 % | 75 % | es fehlen mindestens 4 Basispfade | -| `async_unload_entry` (Setup) | 5 | 14 | 5 | 100 % | 100 % | Pfadzahl nicht widerlegt | -| `GoeChargerDataUpdateCoordinator.async_write_key` (Koordinator) | 5 | 7 | 5 | 100 % | 100 % | Pfadzahl nicht widerlegt | -| `GoeChargerDataUpdateCoordinator.async_write_multiple_keys` (Koordinator) | 5 | 4 | 4 | 82 % | 100 % | es fehlen mindestens 1 Basispfade | -| `TargetedEvent.check_and_set` (Bridge) | 5 | 5 | 2 | 100 % | 100 % | es fehlen mindestens 3 Basispfade | -| `GoeChargerApiV2Service.get_coordinator` (Dienst) | 5 | 38 | 5 | 100 % | 100 % | Pfadzahl nicht widerlegt | -| `GoeChargerDataUpdateCoordinator.force_async_update_now` (Koordinator) | 4 | 4 | 4 | 100 % | 100 % | Pfadzahl nicht widerlegt | -| `GoeChargerDataUpdateCoordinator._check_for_ws_task_and_cancel_if_running` (Koordinator) | 4 | 10 | 3 | 100 % | 100 % | es fehlen mindestens 1 Basispfade | -| `GoeChargerDataUpdateCoordinator._async_watchdog_check` (Koordinator) | 4 | 4 | 3 | 100 % | 83 % | es fehlen mindestens 1 Basispfade | -| `TargetedEvent.__init__` (Bridge) | 4 | 30 | 2 | 100 % | 75 % | es fehlen mindestens 2 Basispfade | -| `ExpiringStore._cleanup` (Bridge) | 4 | 22 | 2 | 100 % | 100 % | es fehlen mindestens 2 Basispfade | -| `GoeChargerApiV2Bridge.ws_close` (Bridge) | 4 | 25 | 3 | 100 % | 100 % | es fehlen mindestens 1 Basispfade | -| `GoeChargerApiV2Bridge._ws_decode_message` (Bridge) | 4 | 88 | 4 | 86 % | 83 % | Pfadzahl nicht widerlegt | - -### 6.1 Nicht genommene Zweige in den wichtigsten Pfaden -Je Funktion die Zeilen, an denen mindestens ein Ausgang nie genommen wurde (bis zu sechs, Auswahl der Funktionen mit CC ab 8). - -**`GoeChargerApiV2Bridge.ws_connect`** (pygoecharger_ha/__init__.py): Z. 786 `if a_event is not None and not a_event.is_set():`; Z. 807 `if a_event is not None and not a_event.is_set():`; Z. 830 `if 'type' in self._ws_device_info:`; Z. 841 `if a_event is not None and not a_event.is_set():`; Z. 860 `if a_event is not None and not a_event.is_set():`; Z. 865 `if not hasattr(self, 'access_password') or not self.access_password:` - -**`GoeChargerDataUpdateCoordinator.read_versions`** (__init__.py): Z. 713 `if lc_model_type is not None:` - -**`check_and_write_to_16a`** (__init__.py): Z. 218 `if hass is not None:`; Z. 220 `if a_entity_reg is not None:`; Z. 271 `if a_res_key not in keys_to_patch:`; Z. 280 `if a_res_key not in keys_to_patch:` - -**`GoeChargerApiV2Bridge.read_all_states`** (pygoecharger_ha/__init__.py): Z. 338 `elif self.isController:`; Z. 377 `if len(self._states) > 0:` - -**`GoeChargerApiV2Bridge.extract_ws_message_data`** (pygoecharger_ha/__init__.py): Z. 1050 `if 'requestId' in data:`; Z. 1056 `if status_data and len(status_data) > 0:`; Z. 1059 `if the_request_payload is not None:`; Z. 1064 `if the_request_payload is not None:` - -**`GoeChargerBaseEntity.__init__`** (__init__.py): Z. 881 `elif len(description.tuple_idx) > 0:` - -**`async_migrate_entry`** (__init__.py): Z. 75 `if config_entry.data is not None and len(config_entry.data) > 0:`; Z. 77 `if config_entry.options is not None and len(config_entry.options):`; Z. 89 `if a_device_reg is not None:` - -**`GoeChargerDataUpdateCoordinator.call_later_update_device_registry`** (__init__.py): Z. 380 `if self.hass is not None:`; Z. 386 `if a_device_reg is not None:`; Z. 387 `if hasattr(a_device_reg, "async_get_device_by_identifier"):` - -**`GoeChargerApiV2Bridge.read_all_config`** (pygoecharger_ha/__init__.py): Z. 404 `if a_key in self._ws_states:` - -**`GoeChargerDataUpdateCoordinator._async_update_data`** (__init__.py): Z. 609 `if self.bridge.ws_connected and self._force_classic_requests is False:`; Z. 623 `if new_data is not None and len(new_data) > 0:` - -**`GoeChargerDataUpdateCoordinator.__init__`** (__init__.py): Z. 363 `if config_entry.data.get(CONF_DELAY, False):` - -**`GoeChargerApiV2FlowHandler.async_step_user_wan`** (config_flow.py): Z. 209 `if len(a_user_pwd) == 0 and CONF_PASSWORD in user_input:` - -## 7. Nicht ausgeführte Funktionen im Kern - -Funktionen der Kernmodule, die kein Test erreicht. - -| Funktion | CC | -|---|---| -| `GoeChargerBaseEntity._friendly_name_internal` (__init__.py) | 10 | -| `GoeChargerBaseEntity._name_internal` (__init__.py) | 1 | -| `GoeChargerBaseEntity.device_info` (__init__.py) | 1 | -| `GoeChargerBaseEntity.available` (__init__.py) | 1 | - -## 8. Plattformen und Hilfsdateien - -Die Plattformen sind jetzt getestet (Wertdarstellung und Schreiben der Entitäten). Nicht ausgeführt bleiben das Anlegen der Entitäten (`async_setup_entry` je Plattform) und der Anzeigename in `entity.py`: `binary_sensor.py: async_setup_entry`, `button.py: async_setup_entry`, `entity.py: CustomFriendlyNameEntity._Entity__async_calculate_state`, `number.py: async_setup_entry`, `number.py: GoeChargerNumber.async_added_to_hass`, `select.py: async_setup_entry`, `sensor.py: async_setup_entry`, `switch.py: async_setup_entry`. - -## 9. Einordnung - -- **Pfadabdeckung:** Vollständige Pfadabdeckung ist hier weder erreichbar noch ein sinnvolles Ziel. Mit 98 von 580 Basispfaden der Kernmodule (17 %) bestimmen `ws_connect` (CC 61) und `read_versions` des Koordinators (CC 37) die Kennzahl; für sie wurden 27 und 21 verschiedene Pfade beobachtet. Funktionen mit sehr hoher CC sind in dieser Form schwer vollständig zu testen. Das ist ein Hinweis auf die Struktur des Codes, kein Vorwurf an die Tests. -- **Das Ziel von 60 % ist erreicht** (alle Funktionen 61 %, Kernmodule 62 %), aber die Zahl ist eine **obere Schranke** und lässt sich durch Tests mit nur leicht verschiedenen Eingaben steigern, ohne dass die Prüfung besser wird. Deshalb gelten die Mutationsprüfungen als Gegenprobe: 33 gezielte Code-Änderungen, alle wurden von den Tests erkannt (eine Lücke wurde erst nach einem zusätzlichen Test geschlossen). -- **Zeilen- und Zweigabdeckung sagen nichts über die Qualität der Prüfungen.** Alle Tests laufen mit Attrappen und sagen nichts darüber, ob der echte Charger sich so verhält. - -## 10. Empfehlungen (nach Wirkung) - -1. K1 und K2 zuerst: die Tabellen in Abschnitt 5.1 listen, wo noch Basispfade sicher fehlen. Dort stehen auch nicht genommene Fehlerpfade. -2. Fehlende Tests für bekannte Risiken: R20 (alte Werte nach fehlgeschlagenem Lesen, Cloud-Betrieb) und R7 (Konfigurationsintervall). -3. Lesenden Abgleich am echten Charger durchführen; erst er belegt, dass die Attrappen dem Gerät entsprechen. -4. Eine Untergrenze für die Abdeckung in den Lauf einbauen (zum Beispiel Zweigabdeckung der Kernmodule), damit sie nicht unbemerkt sinkt. -5. `ws_connect` und `read_versions` zerlegen, wenn sie später geändert werden; erst dann lassen sich ihre Pfade sinnvoll einzeln prüfen. From bff8404c152c3310bdd71743906bca01d426a040 Mon Sep 17 00:00:00 2001 From: Oliver-Marcus Eder <15782304+OliEder@users.noreply.github.com> Date: Thu, 29 Oct 2026 21:17:22 +0100 Subject: [PATCH 18/24] ci: run the tests on pushes and pull requests, drop the fork-only workflows (#179) regression-on-release.yaml and pages.yaml were written for a fork: they check out the branch tests/regression-suite, sync the fork and publish GitHub Pages, which is not enabled here. tests.yaml runs the blocking tests on push to main and on pull requests and lists the open findings in the job summary. Co-authored-by: Oliver-Marcus Eder Co-authored-by: Claude Sonnet 5.5 --- .github/workflows/pages.yaml | 71 ------------ .github/workflows/regression-on-release.yaml | 114 ------------------- .github/workflows/test-report.yaml | 4 +- .github/workflows/tests.yaml | 50 ++++++++ tests/tools/README.md | 15 ++- 5 files changed, 59 insertions(+), 195 deletions(-) delete mode 100644 .github/workflows/pages.yaml delete mode 100644 .github/workflows/regression-on-release.yaml create mode 100644 .github/workflows/tests.yaml diff --git a/.github/workflows/pages.yaml b/.github/workflows/pages.yaml deleted file mode 100644 index 4174691..0000000 --- a/.github/workflows/pages.yaml +++ /dev/null @@ -1,71 +0,0 @@ -name: Publish test results (GitHub Pages) - -# Builds the site from the latest results and publishes it with GitHub Pages: -# - the summary of the latest run of "Regression tests on new upstream release" -# - the full test report of the latest run of "Test report (manual)" (with the coverage in detail) -# - the list of all test cases (built from the current tests and the junit file of that report) -# - the arc42 document and the test concept from docs/quality as web pages (Mermaid diagrams included) -# Starts after one of those two workflows has finished, or by hand. Needs Pages with the source -# "GitHub Actions" in the settings of the fork. - -on: - workflow_run: - workflows: ["Regression tests on new upstream release", "Test report (manual)"] - types: [completed] - workflow_dispatch: - -permissions: - contents: read - actions: read - pages: write - id-token: write - -concurrency: - group: pages - cancel-in-progress: false - -jobs: - publish: - runs-on: ubuntu-latest - environment: - name: github-pages - url: ${{ steps.deploy.outputs.page_url }} - steps: - - uses: actions/checkout@v4 - - - uses: actions/setup-python@v5 - with: - python-version: "3.14" - - - name: Download the latest results - env: - GH_TOKEN: ${{ github.token }} - run: | - mkdir -p results/report results/regression - fetch() { # - run="$(gh run list --workflow "$1" --status completed --limit 20 --json databaseId,conclusion \ - --jq '[.[] | select(.conclusion=="success" or .conclusion=="failure")][0].databaseId')" - [ -n "$run" ] && gh run download "$run" --pattern "$2*" --dir "$3" || echo "no results for $1" - } - fetch test-report.yaml testreport- results/report - fetch regression-on-release.yaml regression- results/regression - - - name: Build the site - run: | - pip install markdown - report="$(ls -d results/report/testreport-* 2>/dev/null | head -1)" - summary="$(ls results/regression/regression-*/summary.md 2>/dev/null | head -1)" - args="" - if [ -n "$report" ]; then - python tests/tools/build_testcases.py "$report/junit-all.xml" tests/integration testcases.md - args="--report $report --testcases testcases.md" - fi - [ -n "$summary" ] && args="$args --regression $summary" - python tests/tools/build_site.py _site --docs docs/quality $args - - - uses: actions/upload-pages-artifact@v3 - with: - path: _site - - - id: deploy - uses: actions/deploy-pages@v4 diff --git a/.github/workflows/regression-on-release.yaml b/.github/workflows/regression-on-release.yaml deleted file mode 100644 index 281bc45..0000000 --- a/.github/workflows/regression-on-release.yaml +++ /dev/null @@ -1,114 +0,0 @@ -name: Regression tests on new upstream release - -# Tests every new release of marq24/ha-goecharger-api2 with the tests of this branch. -# GitHub cannot start a workflow in this fork when another repository publishes a release, so the -# workflow asks for the latest release every three hours. A release is tested once: afterwards the tag -# "tested/" is set in this fork. Nothing is rebased or merged: the upstream release is cloned -# fresh and only tests/integration of this branch is copied into it (tests/tools/release_check.sh). -# -# Scheduled workflows only run from the default branch of the repository. This file therefore has to be -# on the default branch of the fork (see tests/tools/README.md). - -on: - schedule: - - cron: "23 */3 * * *" - workflow_dispatch: - inputs: - ref: - description: "Tag or branch of marq24/ha-goecharger-api2 (empty = latest release)" - required: false - default: "" - home_assistant: - description: "Home Assistant test framework: pinned (requirements_dev.txt) or latest (newest release, to see what a Home Assistant update breaks)" - type: choice - options: [pinned, latest] - default: pinned - push: - branches: [tests/regression-suite] - paths: - - "tests/**" - - ".github/workflows/regression-on-release.yaml" - -permissions: - contents: write - -concurrency: - group: regression-on-release - cancel-in-progress: false - -jobs: - regression: - runs-on: ubuntu-latest - steps: - - name: Check out the tests (branch tests/regression-suite) - uses: actions/checkout@v4 - with: - ref: tests/regression-suite - fetch-depth: 0 - - - name: Decide which upstream version to test - id: ref - env: - GH_TOKEN: ${{ github.token }} - INPUT_REF: ${{ inputs.ref }} - run: | - REF="$INPUT_REF" - if [ -z "$REF" ]; then - REF="$(gh api repos/marq24/ha-goecharger-api2/releases/latest --jq .tag_name)" - fi - SKIP=false - if [ "${{ github.event_name }}" = "schedule" ] && git ls-remote --exit-code --tags origin "refs/tags/tested/$REF" >/dev/null 2>&1; then - SKIP=true - fi - echo "ref=$REF" >> "$GITHUB_OUTPUT" - echo "skip=$SKIP" >> "$GITHUB_OUTPUT" - echo "Upstream version: $REF (already tested: $SKIP)" - - - name: Set up Python - if: steps.ref.outputs.skip != 'true' - uses: actions/setup-python@v5 - with: - python-version: "3.14" - cache: pip - cache-dependency-path: requirements_dev.txt - - - name: Install test dependencies - if: steps.ref.outputs.skip != 'true' - env: - HA_MODE: ${{ inputs.home_assistant }} - run: | - python -m pip install -r requirements_dev.txt msgpack - [ "$HA_MODE" = "latest" ] && python -m pip install --upgrade pytest-homeassistant-custom-component || true - python -m pip show homeassistant pytest-homeassistant-custom-component | grep -E "^(Name|Version)" - - - name: Run the regression tests against the upstream version - if: steps.ref.outputs.skip != 'true' - env: - GH_TOKEN: ${{ github.token }} - run: tests/tools/release_check.sh "${{ steps.ref.outputs.ref }}" "$RUNNER_TEMP/check" - - - name: Keep the results - if: always() && steps.ref.outputs.skip != 'true' - uses: actions/upload-artifact@v4 - with: - name: regression-${{ steps.ref.outputs.ref }} - path: | - ${{ runner.temp }}/check/*.xml - ${{ runner.temp }}/check/*.txt - ${{ runner.temp }}/check/summary.md - if-no-files-found: ignore - - - name: Remember that this release was tested - if: always() && steps.ref.outputs.skip != 'true' && github.event_name == 'schedule' - run: | - git config user.name "github-actions" - git config user.email "github-actions@users.noreply.github.com" - git tag -a "tested/${{ steps.ref.outputs.ref }}" -m "regression tests: ${{ job.status }}" - git push origin "tested/${{ steps.ref.outputs.ref }}" - - - name: Sync the fork main with upstream (best effort) - if: github.event_name == 'schedule' - continue-on-error: true - env: - GH_TOKEN: ${{ github.token }} - run: gh repo sync "${{ github.repository }}" --branch main --source marq24/ha-goecharger-api2 diff --git a/.github/workflows/test-report.yaml b/.github/workflows/test-report.yaml index a1a2f4d..33403d3 100644 --- a/.github/workflows/test-report.yaml +++ b/.github/workflows/test-report.yaml @@ -2,8 +2,8 @@ name: Test report (manual) # Builds the full test report (line, branch and path coverage, criticality) for a version of # marq24/ha-goecharger-api2. Started by hand only; nothing is committed. The report is shown in the job -# summary and kept as an artifact. Like regression-on-release.yaml it clones the upstream version fresh and -# copies only tests/integration and tests/tools of this branch into it. +# summary and kept as an artifact. It clones the chosen version fresh and copies only tests/integration and +# tests/tools of this branch into it. on: workflow_dispatch: diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml new file mode 100644 index 0000000..5cdd9a0 --- /dev/null +++ b/.github/workflows/tests.yaml @@ -0,0 +1,50 @@ +name: Tests + +# Runs the regression tests of tests/integration. Tests marked known_bug or needs_decision describe +# findings that are still open: they run in a second, non-blocking step and are listed in the job summary. +# Manual full report (coverage): test-report.yaml. + +on: + push: + branches: [main] + paths: + - "custom_components/**" + - "tests/**" + - "requirements_dev.txt" + - ".github/workflows/tests.yaml" + pull_request: + paths: + - "custom_components/**" + - "tests/**" + - "requirements_dev.txt" + - ".github/workflows/tests.yaml" + workflow_dispatch: + +permissions: + contents: read + +jobs: + tests: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-python@v5 + with: + python-version: "3.14" + cache: pip + cache-dependency-path: requirements_dev.txt + + - name: Install test dependencies + run: | + python -m pip install -r requirements_dev.txt msgpack + python -m pip show homeassistant pytest-homeassistant-custom-component | grep -E "^(Name|Version)" + + - name: Blocking tests + run: python -m pytest tests/integration -p no:cacheprovider -q -m "not known_bug and not needs_decision" + + - name: Open findings (known_bug, needs_decision) + continue-on-error: true + run: | + python -m pytest tests/integration -p no:cacheprovider -q -m "known_bug or needs_decision" | tee open-findings.txt + { echo "### Open findings"; echo; echo '```'; tail -n 40 open-findings.txt; echo '```'; } >> "$GITHUB_STEP_SUMMARY" diff --git a/tests/tools/README.md b/tests/tools/README.md index 2f81ade..bd34201 100644 --- a/tests/tools/README.md +++ b/tests/tools/README.md @@ -7,20 +7,19 @@ | `pathmetric.py` | Prints the path coverage (upper bound of basis paths) per criticality class from a path-trace file. | | `build_report.py` | Builds `testreport.md` from coverage, junit and path-trace data. | | `measure.sh` | Runs everything and builds the report. | -| `release_check.sh` | Tests a release (or branch) of the upstream integration with the tests of this branch. Used by the workflow `.github/workflows/regression-on-release.yaml`. | +| `release_check.sh` | Tests a release (or branch) of the upstream integration with the tests of this branch. Run by hand, for example to test an older release with the current tests. | Needs `pytest-cov` and `radon` in addition to `requirements_dev.txt`. -## Testing every new upstream release automatically +## Running the tests automatically -`.github/workflows/regression-on-release.yaml` checks every three hours for a new release of `marq24/ha-goecharger-api2`. A new release is cloned fresh, the tests of this branch are copied into the clone, and the blocking tests run (`release_check.sh`). The result appears in the job summary; a red run means a blocking test failed. After the run the tag `tested/` is set in the fork, so a release is tested once. The summary also lists known-bug tests that now pass, which means the bug looks fixed in that version. +`.github/workflows/tests.yaml` runs the blocking tests (`-m "not known_bug and not needs_decision"`) on every push to `main` and on every pull request that touches `custom_components/`, `tests/` or `requirements_dev.txt`. A second, non-blocking step runs the tests marked `known_bug` or `needs_decision` and lists them in the job summary; a test that passes there means the finding looks fixed and its marker can go. + +`.github/workflows/test-report.yaml` builds the full report (coverage, paths, criticality) by hand for a release or branch. Things to know: -- GitHub cannot start a workflow in a fork when another repository publishes a release. The workflow therefore polls. -- Scheduled workflows only run from the **default branch**. Either make `tests/regression-suite` the default branch of the fork, or copy the workflow file to `main`. Actions must be enabled for the fork (Actions tab). -- Nothing is rebased or merged by the workflow. It always tests a fresh clone of the upstream version, so the test branch does not have to follow upstream. The last step tries to fast-forward the fork's `main` to upstream and may fail harmlessly. -- The tests follow upstream `main`. An older release can fail a test that pins behaviour introduced later (for example the 16 A rule since commit `98af3e0` fails on release 2026.10.8), and a release that lacks functions the tests import cannot be collected. The summary says which case happened. -- Run it by hand: `tests/tools/release_check.sh [tag-or-branch]` (needs the test dependencies and the GitHub CLI for the latest release). +- The tests follow `main`. An older release can fail a test that pins behaviour introduced later (for example the 16 A rule since commit `98af3e0` fails on release 2026.10.8), and a release that lacks functions the tests import cannot be collected. +- Test an older release by hand: `tests/tools/release_check.sh [tag-or-branch]` (needs the test dependencies and the GitHub CLI for the latest release). ## Home Assistant version of the tests From a23b45951fdeef70e98c4a37f09c6b68639a4b2d Mon Sep 17 00:00:00 2001 From: marq24 Date: Thu, 29 Oct 2026 21:25:41 +0100 Subject: [PATCH 19/24] action versions --- .github/workflows/test-report.yaml | 2 +- .github/workflows/tests.yaml | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/test-report.yaml b/.github/workflows/test-report.yaml index 33403d3..a07607e 100644 --- a/.github/workflows/test-report.yaml +++ b/.github/workflows/test-report.yaml @@ -33,7 +33,7 @@ jobs: [ -n "$ref" ] || ref="$(gh api repos/marq24/ha-goecharger-api2/releases/latest --jq .tag_name)" echo "ref=$ref" >> "$GITHUB_OUTPUT" - - uses: actions/setup-python@v5 + - uses: actions/setup-python@v6 with: python-version: "3.14" diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml index 5cdd9a0..d0f0dc3 100644 --- a/.github/workflows/tests.yaml +++ b/.github/workflows/tests.yaml @@ -27,9 +27,9 @@ jobs: tests: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - - uses: actions/setup-python@v5 + - uses: actions/setup-python@v6 with: python-version: "3.14" cache: pip From d876dfddd901ff99a219d300df2b11bc9739f6fd Mon Sep 17 00:00:00 2001 From: marq24 Date: Thu, 29 Oct 2026 22:32:46 +0100 Subject: [PATCH 20/24] stop_charging will use require_ack=True --- custom_components/goecharger_api2/service.py | 5 ++--- tests/integration/test_services_and_unload.py | 4 ++++ 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/custom_components/goecharger_api2/service.py b/custom_components/goecharger_api2/service.py index 20ae18f..765c550 100644 --- a/custom_components/goecharger_api2/service.py +++ b/custom_components/goecharger_api2/service.py @@ -110,13 +110,12 @@ async def stop_charging(self, call: ServiceCall): self._stop_in_progress = True _LOGGER.debug(f"Service-stop_charging(): Provided config_id: {config_id} -> using {coordinator.name} serial: {coordinator._serial}") try: - resp = await coordinator.async_write_key(Tag.FRC.key, 1)#, require_ack=True) + resp = await coordinator.async_write_key(Tag.FRC.key, 1, require_ack=True) if Tag.FRC.key in resp: _LOGGER.debug(f"Service-stop_charging(): STOP_CHARGING: waiting for 5 minutes...") await asyncio.sleep(300) _LOGGER.debug(f"Service-stop_charging(): STOP_CHARGING: 5 minutes are over... disable charging LOCK again") - - resp = await coordinator.async_write_key(Tag.FRC.key, 0)#, require_ack=True) + resp = await coordinator.async_write_key(Tag.FRC.key, 0, require_ack=True) self._stop_in_progress = False if call.return_response: diff --git a/tests/integration/test_services_and_unload.py b/tests/integration/test_services_and_unload.py index 1828cc4..1765b46 100644 --- a/tests/integration/test_services_and_unload.py +++ b/tests/integration/test_services_and_unload.py @@ -71,6 +71,10 @@ async def test_stop_charging_in_websocket_mode_sends_lock_and_unlock(hass): """The reported TypeError is gone: frc=1, wait, frc=0.""" coordinator = _coordinator(hass, _entry(hass), websocket=True) service = GoeChargerApiV2Service(hass, coordinator._config_entry, coordinator) + async def mock_async_write_key(key, value, require_ack=False): + await coordinator.bridge._ws_connection.send_json({"key": key, "value": value}) + return {key: value} + coordinator.async_write_key = AsyncMock(side_effect=mock_async_write_key) with patch(SERVICE_SLEEP, AsyncMock()): result = await service.stop_charging(_call()) assert result["success"] == "true" From 047d1c25be4b95e0ee63a69aad8a1cb6ae04b2e0 Mon Sep 17 00:00:00 2001 From: Oliver-Marcus Eder <15782304+OliEder@users.noreply.github.com> Date: Thu, 29 Oct 2026 22:44:42 +0100 Subject: [PATCH 21/24] tests: lift the known_bug markers of the fixed findings, expect HTTP 200 from the cloud (#180) Ten tests describe findings that main fixes now (stop_charging flag, empty websocket result, controller unload counter, pgrid, 'essential data' message, rejected write). The 202 test is replaced: the maintainer states that the real cloud answers 200 and the documentation is wrong. Co-authored-by: Oliver-Marcus Eder Co-authored-by: Claude Sonnet 5.5 --- tests/integration/test_bridge_http.py | 12 ++++-------- tests/integration/test_service.py | 1 - tests/integration/test_services_and_setup.py | 1 - tests/integration/test_services_and_unload.py | 5 ----- 4 files changed, 4 insertions(+), 15 deletions(-) diff --git a/tests/integration/test_bridge_http.py b/tests/integration/test_bridge_http.py index 3a0b678..f89c77a 100644 --- a/tests/integration/test_bridge_http.py +++ b/tests/integration/test_bridge_http.py @@ -95,9 +95,6 @@ async def test_write_success_returns_key_and_value(hass, aioclient_mock): assert await _lan(hass).write_value_to_key("amp", 10) == {"amp": 10} -@pytest.mark.known_bug(( - "http-en.md: the answer holds 'true for success, or a string error message' per key. The bridge " - "tests the value for truthiness, so an error string counts as success. Not verified on a real charger.")) async def test_write_rejected_by_charger_returns_err(hass, aioclient_mock): aioclient_mock.get(LAN_SET, json={"amp": "value out of range"}) result = await _lan(hass).write_value_to_key("amp", 99) @@ -119,9 +116,8 @@ async def test_write_other_key_forces_config_and_state_refresh(hass, aioclient_m assert bridge._LAST_CONFIG_UPDATE_TS == 0 and bridge._LAST_FULL_STATE_UPDATE_TS == 0 -@pytest.mark.known_bug(( - "Official cloud docs: /api/set answers HTTP 202 on success; the bridge only accepts 200. " - "Not verified against the real cloud. Remove the marker when the behaviour is clarified.")) -async def test_cloud_write_accepts_http_202(hass, aioclient_mock): - aioclient_mock.get("https://001234.api.v3.go-e.io/api/set", status=202, json={"amp": True}) +async def test_cloud_write_succeeds_with_http_200(hass, aioclient_mock): + """The official cloud docs name HTTP 202 for /api/set, the maintainer states that the real cloud answers 200 + (the docs are wrong there, as they are for the controller cloud endpoint).""" + aioclient_mock.get("https://001234.api.v3.go-e.io/api/set", status=200, json={"amp": True}) assert await _cloud(hass).write_value_to_key("amp", 10) == {"amp": 10} diff --git a/tests/integration/test_service.py b/tests/integration/test_service.py index 3dd73c1..3dbd837 100644 --- a/tests/integration/test_service.py +++ b/tests/integration/test_service.py @@ -33,7 +33,6 @@ async def test_valid_values_are_written(svc): Tag.IDS.key, {"pGrid": -500.0, "pPv": 1500.0, "pAkku": 200.0}) -@pytest.mark.known_bug("service.py checks pgrid with isinstance(Number) only; NaN and inf pass") @pytest.mark.parametrize("bad", [math.nan, math.inf, -math.inf]) async def test_non_finite_grid_value_is_rejected(svc, bad): service, coordinator = svc diff --git a/tests/integration/test_services_and_setup.py b/tests/integration/test_services_and_setup.py index 5381603..9747781 100644 --- a/tests/integration/test_services_and_setup.py +++ b/tests/integration/test_services_and_setup.py @@ -309,7 +309,6 @@ async def test_unknown_language_falls_back_to_english_texts(hass, setup_patches) await _setup(hass, entry) -@pytest.mark.known_bug("the specific message 'could not fetch essential information' is swallowed by the broad except and replaced by the generic websocket message (__init__.py:131)") async def test_missing_device_data_is_reported_with_its_own_message(hass, setup_patches): from homeassistant.exceptions import ConfigEntryNotReady entry = make_entry(hass, password="secret") diff --git a/tests/integration/test_services_and_unload.py b/tests/integration/test_services_and_unload.py index 1765b46..50129db 100644 --- a/tests/integration/test_services_and_unload.py +++ b/tests/integration/test_services_and_unload.py @@ -82,7 +82,6 @@ async def mock_async_write_key(key, value, require_ack=False): assert service._stop_in_progress is False -@pytest.mark.known_bug("stop_charging does not reset _stop_in_progress when the first write fails (service.py)") async def test_failed_lock_write_blocks_later_stop_requests(hass): """a ValueError on the first write leaves _stop_in_progress True forever.""" coordinator = _coordinator(hass, _entry(hass), websocket=True) @@ -92,7 +91,6 @@ async def test_failed_lock_write_blocks_later_stop_requests(hass): assert service._stop_in_progress is False -@pytest.mark.known_bug("stop_charging does not reset _stop_in_progress when the unlock write fails (service.py)") async def test_failed_unlock_write_leaves_flag_stuck(hass): """if frc=0 cannot be written after the wait, the charger stays locked and the flag blocks every later stop_charging call.""" @@ -106,7 +104,6 @@ async def test_failed_unlock_write_leaves_flag_stuck(hass): # --------------------------------------------------------------------------- WebSocket write results -@pytest.mark.known_bug("write_multiple_values_to_keys returns {} over WebSocket; handle_write_result only skips on None") async def test_websocket_multi_write_logs_no_error_on_success(hass, caplog): """a successful multi-key write in WebSocket mode is logged as an ERROR.""" coordinator = _coordinator(hass, _entry(hass), websocket=True) @@ -159,7 +156,6 @@ async def _noop(call): hass.services.async_register(DOMAIN, SERVICE_STOP_CHARGING, _noop) -@pytest.mark.known_bug("ACTIVE_CHARGER_COUNTER is raised for chargers only but lowered for every entry") async def test_unloading_a_controller_keeps_the_charger_services(hass): """the active-charger counter is only raised for chargers but lowered for every entry, so unloading a controller removes the services of a still-running charger.""" @@ -171,7 +167,6 @@ async def test_unloading_a_controller_keeps_the_charger_services(hass): assert hass.services.has_service(DOMAIN, SERVICE_SET_PV_DATA) -@pytest.mark.known_bug("ACTIVE_CHARGER_COUNTER key does not exist when only controllers are configured") async def test_unloading_a_controller_without_any_charger_does_not_raise(hass): """with only controllers configured the counter key does not exist.""" controller = _entry(hass, "999999", intg_type="controller") From e6ee0c3c413365b9960b406d6ba048e721366677 Mon Sep 17 00:00:00 2001 From: Oliver-Marcus Eder <15782304+OliEder@users.noreply.github.com> Date: Thu, 29 Oct 2026 22:45:02 +0100 Subject: [PATCH 22/24] docs: record the configid decision (E10), update risk status and test numbers, tests live in this repository now (#181) Co-authored-by: Oliver-Marcus Eder Co-authored-by: Claude Sonnet 5.5 --- docs/quality/README.md | 4 +-- docs/quality/arc42-ha-goecharger-api2.md | 36 ++++++++++--------- .../quality/testkonzept-ha-goecharger-api2.md | 22 ++++++------ 3 files changed, 33 insertions(+), 29 deletions(-) diff --git a/docs/quality/README.md b/docs/quality/README.md index ad40a81..5926950 100644 --- a/docs/quality/README.md +++ b/docs/quality/README.md @@ -1,8 +1,8 @@ # Quality documentation -Written with AI assistance (Claude Code) and reviewed by Oliver. Not part of the upstream repository; this fork branch only. +Written with AI assistance (Claude Code) and reviewed by Oliver. Created in the fork `OliEder/ha-goecharger-api2` (branch `tests/regression-suite`) and merged into this repository with PR #178. - `arc42-ha-goecharger-api2.md`: architecture documentation (arc42, German) reconstructed from code, README and the official go-e API documentation. Statements are marked as code, documentation or inference. Chapter 11 lists risks and shows which of them have been fixed upstream. - `testkonzept-ha-goecharger-api2.md`: test concept (scope, levels, markers, current state, gaps). -Tests: `tests/integration/`. Measurement report: `tests/reports/`. Tools: `tests/tools/`. +Tests: `tests/integration/`. Measurement report: manually with `.github/workflows/test-report.yaml`. Tools: `tests/tools/`. diff --git a/docs/quality/arc42-ha-goecharger-api2.md b/docs/quality/arc42-ha-goecharger-api2.md index cb2b6f4..ed2fedb 100644 --- a/docs/quality/arc42-ha-goecharger-api2.md +++ b/docs/quality/arc42-ha-goecharger-api2.md @@ -142,7 +142,7 @@ Quelle: GitHub-Repository [`goecharger/go-eCharger-API-v2`](https://github.com/g | Cloud-Token | In der App sichtbar oder über den Schlüssel `cak` lesbar; neuer Token durch Setzen von `cak` | Integration liest `cak` nicht, speichert aber den Token im Config-Entry | | Cloud-Rate-Limit | Keins, aber nicht öfter als einmal pro Sekunde abfragen | Integration: Cloud-Intervall mindestens 30 s | | Cloud `/api/status` | 403 offline oder Cloud-API aus; 404 keine aktuellen Daten; 200 mit Daten, Header `last-modified`. Parameter `age` (Standard 30 s), `filter` | Integration wertet nur 200 und 400 aus und nutzt `age` nicht | -| Cloud `/api/set` | 403, 404 (Charger offline), 422 (alle Schlüssel abgelehnt), **202** (mindestens ein Schlüssel gesendet, ohne Garantie) | Integration behandelt in `_write_values_int` nur 200 und 500 als Antwort (Abschnitt 11, R15) | +| Cloud `/api/set` | 403, 404 (Charger offline), 422 (alle Schlüssel abgelehnt), **202** laut Doku (mindestens ein Schlüssel gesendet, ohne Garantie); laut Maintainer liefert die echte Cloud 200 und die Doku ist hier falsch | Integration behandelt in `_write_values_int` nur 200 und 500 als Antwort (Abschnitt 11, R15, kein Fehler) | --- @@ -200,7 +200,7 @@ flowchart TB | `GoeChargerBaseEntity` (`__init__.py`) | Gemeinsame Basis: Übersetzungsschlüssel, Entity-ID, `unique_id`, `device_info`, Verfügbarkeit | Entity-ID `…goe__` (LAN) oder `…goe_wan__…` (Cloud). | | `CustomFriendlyNameEntity` (`entity.py`) | Überschreibt den Anzeigenamen ab HA 2026.2 | Greift auf die private Methode `_Entity__async_calculate_state` zu, also fragil bei HA-Updates [Schluss]. | | Plattformen | Legen Entitäten aus den Beschreibungslisten an; filtern über `is_valid_charger_entity()` | Wertzugriff mit `idx` (Listen), `tuple_idx` (verschachtelt), `lookup` (Zahl→Text), `factor`, `differential_base_key`. | -| `service.py` | Zwei HA-Dienste | `set_pv_data`: Netzleistung Pflicht, PV und Batterie optional. `stop_charging`: setzt `frc=1`, wartet 300 s, setzt `frc=0`. Ein Register der Charger (`add_config_entry_to_service`, `remove_config_entry_from_service`) bildet `configid` auf den Koordinator ab; `get_coordinator` lehnt unbekannte oder deaktivierte Entries mit `ServiceValidationError` ab. | +| `service.py` | Zwei HA-Dienste | `set_pv_data`: Netzleistung Pflicht, PV und Batterie optional. `stop_charging`: setzt `frc=1`, wartet 300 s, setzt `frc=0`. Ein Register der Charger (`add_config_entry_to_service`, `remove_config_entry_from_service`) bildet `configid` auf den Koordinator ab; `get_coordinator` lehnt unbekannte oder deaktivierte Entries mit `ServiceValidationError` ab. Ohne `configid` wird nur dann ein Charger gewählt, wenn genau einer registriert ist, sonst folgt ebenfalls ein `ServiceValidationError` (E10). | | `config_flow.py` | Assistent LAN/Cloud, Verbindungstest über WebSocket oder HTTP, Rekonfiguration | Titel und Typ aus den gelesenen Gerätedaten. | | `const.py` / `pygoecharger_ha/const.py` | Entity-Beschreibungen; API-Filter (welche Schlüssel für welchen Zyklus); Übersetzungen; Enums (`CAR_VALUES`, `CT_VALUES`) | `FILTER_*`-Zeichenketten bestimmen den Datenumfang je Zyklus. | | `example/blueprint/…` | Blueprint für den PV-Überschuss | Eigene Qualitätssicherung im Blueprint (Messwertprüfung, Zielcharger) seit PR #171. | @@ -281,7 +281,7 @@ Schreibbefehle gehen als `setValue` mit fortlaufender `requestId`. Im lokalen ge ### 6.5 PV-Überschuss (`set_pv_data`) -1. Blueprint ruft alle 5 Sekunden den Dienst mit `pgrid`, `ppv`, `pakku` auf (und optional `configid`). +1. Blueprint ruft alle 5 Sekunden den Dienst mit `pgrid`, `ppv`, `pakku` auf (und `configid`; bei mehreren Chargern ist sie seit `4702213` Pflicht, siehe E10). 2. Cloud-Betrieb: Ist kein Fahrzeug verbunden (`car < 2`), wird **nichts** gesendet (Last-Schutz). 3. Der Dienst schreibt den Schlüssel `ids` mit `{"pGrid", "pPv", "pAkku"}`. 4. Im HTTP-Betrieb setzt die Bridge danach ein Flag, damit im Leerlauf zusätzlich `pakku, ppv, pgrid` gelesen werden. @@ -306,7 +306,7 @@ Schreibbefehle gehen als `setValue` mit fortlaufender `requestId`. Im lokalen ge flowchart LR subgraph GH[GitHub] Repo[Repo main] - CI["Workflows:
    HACS Action, hassfest,
    Zip Release"] + CI["Workflows:
    HACS Action, hassfest,
    Tests, Zip Release"] Rel[Release: goecharger_api2.zip] end HACS[HACS im HA] @@ -321,7 +321,7 @@ flowchart LR ``` - **Build:** Beim Veröffentlichen eines Releases zippt der Workflow den Ordner `custom_components/goecharger_api2` [Code, `release.yaml`]. -- **CI:** HACS Action und hassfest laufen bei Push, Pull Request und täglich. Es gibt keine Testausführung [Code]. +- **CI:** HACS Action und hassfest laufen bei Push, Pull Request und täglich. Seit PR #179 führt `tests.yaml` die Tests bei Push auf `main` und bei Pull Requests aus [Code]. - **Betrieb:** Läuft im HA-Prozess, keine eigenen Dienste. Zugangsdaten (Passwort, Cloud-Token) liegen im Config-Entry von HA. --- @@ -355,10 +355,10 @@ flowchart LR - Globale Variable `SCAN_INTERVAL` wird im Koordinator-Konstruktor überschrieben (gilt prozessweit, also für alle Config-Entries) [Code, `__init__.py:331`]. ### 8.7 Test und Qualitätssicherung -- **Im Repo (Upstream):** HACS-Validierung und hassfest. Keine Unit-, Integrations- oder Blueprint-Tests (gelöscht in `60d9a09`). -- **Regressionstests im Fork (Branch `tests/regression-suite`):** 987 Tests (420 Vertragsprüfungen je Schlüssel, 567 Verhaltens- und Fehlertests; 958 blockierend, 29 beabsichtigt rot), Zeilenabdeckung 93 %, Zweigabdeckung 85 %, Pfadabdeckung (obere Schranke) 61 %. Messberichte liegen im Fork unter `tests/reports/`; ein Workflow prüft jede neue Upstream-Version (siehe Testkonzept). +- **Im Repo (Upstream):** HACS-Validierung und hassfest. Die früheren Unit-, Integrations- und Blueprint-Tests wurden in `60d9a09` gelöscht; seit PR #178 (`738c3c6`) liegen die Regressionstests in `tests/integration`. +- **Regressionstests (`tests/integration`, entstanden im Fork, Branch `tests/regression-suite`):** 989 Tests (420 Vertragsprüfungen je Schlüssel, 569 Verhaltens- und Fehlertests; 972 blockierend, 17 beabsichtigt rot, mit PR #180), Zeilenabdeckung 93 %, Zweigabdeckung 85 %, Pfadabdeckung (obere Schranke) 61 %. Ein Messbericht entsteht von Hand über `test-report.yaml` (siehe Testkonzept). - **Testkonzept:** `docs/quality/testkonzept-ha-goecharger-api2.md` beschreibt Umfang, Stufen, Marker, Stand und Lücken. -- **Vertrag zur offiziellen API:** Im Fork-Branch prüft `test_api_contract.py`, ob jeder dokumentierte Schlüssel unterstützt oder erklärt ist (92 dokumentierte Schlüssel ohne Tag, 38 genutzte Schlüssel ohne Doku, 14 Abweichungen bei Schreibrecht und Kategorie). Die Listen stehen in `tests/integration/contract/` (`classification.json`, `deviations.json`). +- **Vertrag zur offiziellen API:** Im Repository prüft `test_api_contract.py`, ob jeder dokumentierte Schlüssel unterstützt oder erklärt ist (92 dokumentierte Schlüssel ohne Tag, 38 genutzte Schlüssel ohne Doku, 14 Abweichungen bei Schreibrecht und Kategorie). Die Listen stehen in `tests/integration/contract/` (`classification.json`, `deviations.json`). - **Lokal (nicht im Repo):** Unser Fork enthält Blueprint-Tests (240 Fälle) und Integrationstests (12 Fälle) als Arbeitsmittel (`OliEder/ha-goecharger-api2`, Branches `proposal/invalid-data-policy` und `fix/small-bugs`). --- @@ -377,7 +377,8 @@ Der Autor führt keine Entscheidungsprotokolle. Die folgenden Einträge sind aus | E6 | Hibernation im Polling | Geräte- und Cloud-Last senken | Im Leerlauf nur wenige Werte aktuell | | E7 | PV-Daten über HA-Automation statt Zusatzhardware | Kein Zusatzgerät nötig | Qualität liegt im Blueprint und in den Messwerten des Nutzers | | E8 | Neustart per Konfigurations-Reload bei dauerhaften Fehlern | Hängende Verbindung selbst heilen | Reload aller Entitäten für ein Verbindungsproblem | -| E9 | Keine automatisierten Tests im Repo | Aufwand/Haltung des Autors | Regressionen fallen erst bei Nutzern auf | +| E9 | Keine automatisierten Tests im Repo (überholt: seit PR #178, `738c3c6`, liegen die Tests in `tests/integration`) | Aufwand/Haltung des Autors | Vorher fielen Regressionen erst bei Nutzern auf; jetzt prüft die Suite jede Änderung gegen den Vertrag zur offiziellen API | +| E10 | Dienste (`set_pv_data`, `stop_charging`) ohne `configid`: nur bei genau einem registrierten Charger wird dieser verwendet, sonst `ServiceValidationError` (Commit `4702213`, 2026-10-29; Entscheidung des Maintainers im Code, im Issue #172 nicht begründet) | Vorher wurde der zuerst registrierte Charger genommen. Nach dem Neuladen eines Entries rückte dieser ans Ende der Liste, der Standard wechselte dann stillschweigend auf einen anderen Charger. Ein falscher Charger bei Schreibzugriffen (Strom, Stopp) wird vermieden [Schluss, aus Diff und Testfund]. | **Verhaltensänderung:** Wer mehrere Charger hat und `configid` bisher wegließ (zum Beispiel ein Blueprint oder eine Automation), bekommt jetzt einen Fehler statt eines Zugriffs auf den ersten Charger. Mit einem Charger oder mit `configid` ändert sich nichts. Alternativen, die nicht gewählt wurden: festen Standard-Charger bestimmen oder die Reihenfolge stabil halten. Tests: `test_without_configid_and_with_two_chargers_no_charger_is_guessed`, `test_with_an_empty_registry_no_charger_can_be_chosen`. `docs/PVSURPLUS.md` verlangt `configid` bei mehreren Chargern bereits; das Blueprint selbst enthält keine `configid` (per `grep` geprüft), ein Aufruf aus dem Blueprint mit mehreren Chargern scheitert deshalb jetzt, sofern der Nutzer sie nicht ergänzt. Am Gerät nicht nachgespielt. | --- @@ -435,19 +436,22 @@ Nummerierung nur für diese Liste. Status in der Spalte „Beleg“. | R10 | `check_and_write_to_16a`: Funktion mit `@staticmethod` auf Modulebene, außerdem privater Zugriff auf `bridge._read_filtered_data`. | Wartbarkeit. | Aus Code gelesen. | | R11 | Fehlende Abhängigkeit im Manifest (`bcrypt`), Mindestversion von HA unklar (`hacs.json` 2023.8.0 gegenüber genutzten neueren APIs). | Installationsfehler auf älteren Systemen möglich. | Aus Code und Manifest gelesen. Ob `bcrypt` über HA mitgeliefert wird, habe ich nicht geprüft. | | R12 | `CustomFriendlyNameEntity` überschreibt eine private HA-Methode. | Bricht bei HA-Änderungen. | Aus Code gelesen. | -| R13 | Keine Tests im Repo. | Regressionen werden erst von Nutzern gefunden. | Repo-Inhalt. | +| R13 | Keine Tests im Repo (überholt: seit PR #178 liegen sie in `tests/integration`). | Regressionen werden erst von Nutzern gefunden. | Repo-Inhalt. | | R14 | Bei Teilausfall des Geräts: Daten bleiben stehen, Entitäten bleiben `available`, solange der letzte Update-Erfolg anhält. | Nutzer sieht alte Werte als aktuell. | Aus Code gelesen (`available` = `last_update_success`). Wie sich ein WebSocket-Verlust auf die Verfügbarkeit auswirkt, habe ich nicht geprüft. | -**Stand der Risiken nach Version 2026.10.8 und `98af3e0` (2026-10-29, durch Tests im Fork geprüft)** +**Stand der Risiken nach Version 2026.10.8 und `98af3e0` (2026-10-29, durch Tests geprüft, `main` bei `9e7d62a`)** | # | Stand | Anmerkung | |---|---|---| -| R1 | **teilweise behoben** | Der `TypeError` im WebSocket-Modus ist weg (`write_value_to_key` liefert jetzt `{key: value}`). Offen: `_stop_in_progress` wird bei Fehlern nicht zurückgesetzt. Test: `known_bug`. | -| R2 | **behoben** | Register wird für jeden Charger gepflegt, Neuladen ersetzt den alten Koordinator. Offen: ohne `configid` ändert sich der „erste“ Charger nach einem Neuladen. Test: `known_bug`. | -| R3 | offen | `ACTIVE_CHARGER_COUNTER` bei Controllern. Zwei Tests: `known_bug`. | +| R1 | **behoben** | Der `TypeError` im WebSocket-Modus ist weg (`write_value_to_key` liefert `{key: value}`), `_stop_in_progress` wird seit `9e7d62a` in einem `finally` zurückgesetzt. Beide Tests sind grün. | +| R2 | **behoben** | Register wird für jeden Charger gepflegt, Neuladen ersetzt den alten Koordinator. Der wechselnde Standard-Charger ist seit `4702213` über E10 entschieden (kein Standard bei mehreren Chargern). | +| R3 | **behoben** | Seit `eb5da46` werden Zähler und Dienste nur für Charger angepasst. | | R4 | **behoben** | Aufruf hat jetzt zwei Argumente. | -| R5 | offen | `pgrid` ohne Endlichkeitsprüfung. Drei Tests: `known_bug`. | +| R5 | **behoben** | Seit `eb5da46` prüft `set_pv_data` auch `pgrid` auf Endlichkeit. | +| R25 | **behoben** | Seit `eb5da46` bleibt die konkrete `ConfigEntryNotReady`-Meldung erhalten. | +| R24 | **behoben** | Der Test `test_write_rejected_by_charger_returns_err` ist auf `9e7d62a` grün; wann genau die Antwort mit Fehlertext ausgewertet wird, habe ich nicht nachvollzogen. | +| R27 | **behoben** | Seit `9e7d62a` überspringt `handle_write_result` im WebSocket-Modus auch ein leeres Ergebnis. | Neue Risiken aus dem Abgleich mit `main` und den Tests (2026-10-29): @@ -477,7 +481,7 @@ Ergänzungen aus dem Abgleich mit der offiziellen Dokumentation (Abschnitt 3.3): | # | Befund | Wirkung | Beleg | |---|---|---|---| -| R15 | Laut Cloud-Doku antwortet `/api/set` bei Erfolg mit **HTTP 202**. `_write_values_int` wertet nur `200` (und `500` mit Inhalt) aus, alles andere gilt als Fehler (`failed with http-status …`, Rückgabe `{}`). | Wenn die Cloud tatsächlich 202 sendet, würden Schreibbefehle im Cloud-Polling trotz Erfolg als Fehler geloggt, und `handle_write_result` meldet „could not write“. Im WebSocket-Betrieb ist der Pfad nicht betroffen. | Doku und Code gelesen. **Ob die Cloud 202 oder 200 liefert, ist nicht geprüft**, ebenso nicht, ob Cloud-Nutzer das Problem sehen. | +| R15 | Laut Cloud-Doku antwortet `/api/set` bei Erfolg mit **HTTP 202**. `_write_values_int` wertet nur `200` (und `500` mit Inhalt) aus, alles andere gilt als Fehler (`failed with http-status …`, Rückgabe `{}`). | Wenn die Cloud tatsächlich 202 sendet, würden Schreibbefehle im Cloud-Polling trotz Erfolg als Fehler geloggt, und `handle_write_result` meldet „could not write“. Im WebSocket-Betrieb ist der Pfad nicht betroffen. | Doku und Code gelesen. **Geklärt am 2026-10-29 durch den Maintainer (Issue #172): Die Cloud antwortet mit HTTP 200, die Doku ist falsch. Kein Fehler im Code.** Von uns nicht gegen die echte Cloud geprüft. | | R16 | Der bevorzugte Datenpfad (WebSocket) ist nicht offiziell dokumentiert; Anmeldeverfahren und Nachrichten sind nachgebaut. | Eine Firmware- oder Cloud-Änderung von go-e kann ihn ohne Vorwarnung brechen. Das README erwähnt das schon für Firmware 60.2 und 60.4. | Doku und README gelesen. | | R17 | Cloud-`/api/status` liefert standardmäßig Daten bis 30 Sekunden Alter (`age=30`); die Integration setzt `age` nicht. | Im Cloud-Polling können Werte bis zu 30 Sekunden alt sein, ohne dass die Integration das erkennt. | Doku und Code gelesen (kein `age` in den Aufrufen). | | R18 | Offiziell bedeutet `pPv < 0` „Standby“, der Dienst `set_pv_data` begrenzt `ppv` in der Oberfläche auf Werte ab 0. | Standby kann über die Oberfläche nicht gesetzt werden. Ob das gewollt ist, ist unklar. | Doku und `services.yaml` gelesen. | diff --git a/docs/quality/testkonzept-ha-goecharger-api2.md b/docs/quality/testkonzept-ha-goecharger-api2.md index fd0fd34..e087b55 100644 --- a/docs/quality/testkonzept-ha-goecharger-api2.md +++ b/docs/quality/testkonzept-ha-goecharger-api2.md @@ -3,9 +3,9 @@ | | | |---|---| | **Gegenstand** | Automatisierte Tests der Home-Assistant-Integration `goecharger_api2` (go-eCharger, go-eController) | -| **Stand** | 2026-10-29, Fork `OliEder/ha-goecharger-api2`, Branch `tests/regression-suite` (Branch `tests/regression-suite`), aufbauend auf `main` bei `98af3e0` | -| **Bezug** | Architektur und Risiken: `arc42-ha-goecharger-api2.md` (Risiken R1 bis R29). Regeln: `CLAUDE.md` / `AGENTS.md` der Arbeitsumgebung. | -| **Status der Tests** | Die Tests liegen im Fork. Es gibt keinen PR an das Upstream-Repository. Das Dokument wurde mit KI-Unterstützung (Claude Code) erstellt und von Oliver geprüft. | +| **Stand** | 2026-10-29, `main` bei `9e7d62a` (die Tests entstanden im Fork `OliEder/ha-goecharger-api2`, Branch `tests/regression-suite`) | +| **Bezug** | Architektur und Risiken: `arc42-ha-goecharger-api2.md` (Risiken R1 bis R30). Regeln: `CLAUDE.md` / `AGENTS.md` der Arbeitsumgebung. | +| **Status der Tests** | Die Tests liegen seit PR #178 (`738c3c6`) in diesem Repository, `.github/workflows/tests.yaml` führt sie seit PR #179 bei Push auf `main` und bei Pull Requests aus. Das Dokument wurde mit KI-Unterstützung (Claude Code) erstellt und von Oliver geprüft. | **Lesehinweis.** Alle Tests laufen mit Attrappen. Sie belegen, was der Code tut und was die Doku sagt, **nicht**, was ein echter Charger tut. Wo das für eine Aussage wichtig ist, steht es dabei. @@ -74,7 +74,7 @@ Reihenfolge der Testtiefe nach Auswirkung auf den Betrieb (steuert ein physische | R6 keine `unique_id` im Config-Flow (kein Schutz vor Doppelanlage) | – | **nicht abgedeckt** | | R7 README „24 h“, Code „1 h“ | `test_read_all_reads_the_configuration_only_every_hour` | Code-Verhalten festgehalten (Abweichung zum README bleibt) | | R8 Zugangsdaten | – | bewusst nicht Teil der Tests | -| R15 Cloud-Antwort 202 | `test_cloud_write_accepts_http_202` | `known_bug`, rot (nur nach Doku) | +| R15 Cloud-Antwort | `test_cloud_write_succeeds_with_http_200` | grün; Doku nennt 202, Maintainer bestätigt 200 | | R16 WebSocket nicht dokumentiert | `test_ws_protocol.py`, `test_ws_login_failures.py` | Verhalten festgehalten | | R17 Cloud `age` nicht gesetzt | – | **nicht abgedeckt** | | R18 `ppv` negativ (Standby) | – | **nicht abgedeckt** | @@ -124,7 +124,7 @@ Verbindlich nach `CLAUDE.md` / `AGENTS.md`. Der Stand wird hier ehrlich festgeha ## 6. Testumgebung - **Software:** Python 3.14.3, Home Assistant 2026.10.4 (festgelegt durch `pytest-homeassistant-custom-component`), `pytest`, `pytest-asyncio` (Modus `auto`). `msgpack` muss zusätzlich installiert werden, weil das Test-Framework die Anforderungen aus dem Manifest nicht installiert. -- **Ort:** lokale virtuelle Umgebung (Python 3.14, `pip install -r requirements_dev.txt`); die Tests laufen im Branch `tests/regression-suite`. Messbericht und Werkzeuge: `tests/reports/`, `tests/tools/`. +- **Ort:** lokale virtuelle Umgebung (Python 3.14, `pip install -r requirements_dev.txt`); Werkzeuge: `tests/tools/`; der Messbericht entsteht von Hand über `.github/workflows/test-report.yaml` (die abgelegten Berichte unter `tests/reports/` wurden entfernt). - **Aufruf:** - alles: `python -m pytest tests/integration` - blockierende Menge: `python -m pytest tests/integration -m "not known_bug and not needs_decision"` @@ -158,18 +158,18 @@ Verbindlich nach `CLAUDE.md` / `AGENTS.md`. Der Stand wird hier ehrlich festgeha - Eine **neue** Abweichung zur Doku hat keinen Marker und blockiert sofort. - Wird ein Fehler behoben, schlägt das Entfernen des Markers nicht automatisch an. Der Test wird **grün**, der Marker bleibt stehen und muss von Hand entfernt werden. Das ist ein Nachteil gegenüber `xfail(strict)`. Mitigation: nach jedem Update von `main` die Marker prüfen (`-m known_bug -q`). -- Blockierende Menge heute: grün (958 bestanden). Bekannte Fehler: 29 (15 `known_bug`, 14 `needs_decision`). +- Blockierende Menge heute: grün (972 bestanden, mit PR #180). Offene Funde: 17, alle `needs_decision` (14 Abweichungen in `test_api_contract`, 3 Controller-Schlüssel). Alle `known_bug` sind behoben und ihre Marker entfernt. --- ## 9. Aktueller Stand -Stand 2026-10-29 (Messung nach dem Ausbau um Controller-Vertrag, Wertebereiche und Transportvergleich), Branch `tests/regression-suite` im Fork. Details und Rohdaten: `tests/reports/2026-10-29/testreport.md`. +Stand 2026-10-29 (Messung nach dem Ausbau um Controller-Vertrag, Wertebereiche und Transportvergleich), `main` bei `9e7d62a`. Die Abdeckung wurde vor den letzten Fehlerbehebungen gemessen; einen neuen Bericht liefert `test-report.yaml`. | Kennzahl | Wert | |---|---| -| Tests gesammelt | 987, davon 420 parametrisierte Vertragsprüfungen je Schlüssel; **567 Verhaltens- und Fehlertests** | -| Ergebnis | 958 bestanden, 29 beabsichtigt rot (15 `known_bug`, 14 `needs_decision`); blockierende Menge 958 von 958 grün | +| Tests gesammelt | 989, davon 420 parametrisierte Vertragsprüfungen je Schlüssel; **567 Verhaltens- und Fehlertests** | +| Ergebnis | 972 bestanden, 17 beabsichtigt rot (alle `needs_decision`); blockierende Menge 972 von 972 grün (mit PR #180, sonst 961 blockierend und 28 rot) | | Zeilenabdeckung | 92,9 % (vorher 55,2 %) | | Zweigabdeckung | 84,9 % (vorher 31,8 %) | | **Pfadabdeckung** (obere Schranke der Basispfade) | **61 %** alle Funktionen, **62 %** Kernmodule (vorher 20 % und 27 %) | @@ -231,13 +231,13 @@ Erledigt seit der ersten Fassung: Plattformen, Lesezyklus im Polling, Schreibpfa | Neue Entität oder neuer Schlüssel | Vertragstest schlägt an; Schlüssel zuordnen oder in die Klassifikation aufnehmen | | Abweichung `needs_decision` geklärt | Eintrag entfernen oder Doku-Lücke vermerken und Marker entfernen | -**Zuständig:** Oliver (Fork und Tests). Änderungen am Upstream-Repository macht ausschließlich der Maintainer. +**Zuständig:** Oliver (Tests und Doku). Änderungen am Code macht der Maintainer; Oliver schlägt sie höchstens vor. --- ## 13. Eintritts- und Abschlusskriterien -- **Vor jedem Push in den Fork:** blockierende Menge grün (`-m "not known_bug and not needs_decision"`). +- **Vor jedem Push und jedem Pull Request:** blockierende Menge grün (`-m "not known_bug and not needs_decision"`). - **Vor jedem Update auf einen neuen Upstream-Stand:** `main` holen, Gesamtlauf starten, neue Rotfälle einordnen (neuer Fehler oder behobener bekannter Fehler). - **Abschluss des Testkonzepts „offizielle API“:** Punkte 1 bis 3 aus Abschnitt 10 umgesetzt, Punkt 4 mindestens einmal durchgeführt und dokumentiert. From 884702480555b501a994a098fecfb5830385aee3 Mon Sep 17 00:00:00 2001 From: marq24 Date: Sat, 31 Oct 2026 17:48:10 +0100 Subject: [PATCH 23/24] removed cableLimit detector - at then end it's the task of the wallbox, when a 16A cable is connected, that the device will not charge with > 16A --- custom_components/goecharger_api2/__init__.py | 190 +++++------------- .../goecharger_api2/manifest.json | 2 +- .../goecharger_api2/pygoecharger_ha/const.py | 2 +- tests/integration/test_coordinator_runtime.py | 77 +------ tests/integration/test_services_and_setup.py | 15 +- tests/integration/test_versions_16a.py | 8 +- .../test_versions_transport_parity.py | 41 ---- 7 files changed, 64 insertions(+), 271 deletions(-) delete mode 100644 tests/integration/test_versions_transport_parity.py diff --git a/custom_components/goecharger_api2/__init__.py b/custom_components/goecharger_api2/__init__.py index 82eb70a..3011d92 100644 --- a/custom_components/goecharger_api2/__init__.py +++ b/custom_components/goecharger_api2/__init__.py @@ -166,14 +166,6 @@ async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry): # we need to let the service know the config-entry... add_config_entry_to_service(config_entry, coordinator) - # start the limit to 16a limiiter... - if coordinator.limit_to16a: - config_entry.async_create_background_task( - hass, - coordinator.check_for_16a_limit(hass, config_entry.entry_id), - name="coordinator-check_for_16a_limit-task", - ) - # finally, register the websocket-connection watchdog... if use_websocket: # ws watchdog... @@ -218,86 +210,6 @@ async def entry_update_listener(hass: HomeAssistant, config_entry: ConfigEntry) await hass.config_entries.async_reload(config_entry.entry_id) -@staticmethod -async def check_and_write_to_16a(hass: HomeAssistant, config_entry_id: str, bridge: GoeChargerApiV2Bridge, is_lbgroup_id_set: bool): - _LOGGER.info(f"checking entities") - tags = [] - if hass is not None: - a_entity_reg = entity_reg.async_get(hass) - if a_entity_reg is not None: - MAX_A: Final = 16 - # we query from the HA entity registry all entities that are created by this - # 'config_entry' -> we use here just default api calls [no more hacks!] - key_list = [] - for entity in entity_reg.async_entries_for_config_entry(registry=a_entity_reg, config_entry_id=config_entry_id): - if entity.original_device_class == NumberDeviceClass.CURRENT: - if "max" in entity.capabilities: - if entity.capabilities["max"] == MAX_A: - # we must check if the entity is disabled, or if it is orphaned... - if entity.disabled is None or entity.disabled is False: - if hasattr(entity, "orphaned_timestamp") is False or entity.orphaned_timestamp is None: - if entity.translation_key not in key_list: - key_list.append(entity.translation_key) - _LOGGER.info(f"16A checker: adding entity '{entity.translation_key}' to list") - else: - _LOGGER.debug(f"16A checker: entity '{entity.translation_key}' already in list") - else: - _LOGGER.debug(f"16A checker: ignoring entity '{entity.translation_key}' because it is orphaned") - else: - _LOGGER.debug(f"16A checker: ignoring entity '{entity.translation_key}' because it is disabled") - - if len(key_list) > 0: - _LOGGER.info(f"16A checker: found {len(key_list)} entities with max current of {MAX_A}A - {key_list}") - final_key_list = [] - final_dics = {} - for a_key in key_list: - if '_' in a_key: - res = a_key.split('_') - if res[0] not in final_dics: - final_dics[res[0]] = [] - final_dics[res[0]].append(res[1]) - a_key = res[0] - - if a_key not in final_key_list: - final_key_list.append(a_key) - - try: - res = await bridge._read_filtered_data(filters=",".join(final_key_list), log_info="16A checker") - keys_to_patch = [] - for a_res_key in res.keys(): - res_obj = res[a_res_key] - if isinstance(res_obj, int): - # WE MUST/SHOULD check, if this is really a value, that MUST be adjusted to 16A limit - # since we have some ExtNumberEntityDescription where the 'check_16a_limit is FALSE' - # - # mhhhh - thinking - but then in the EntityDescription.check_16a_limit = False, then - # the entity.capabilities["max"] will not be set to 16A ?! - so this might already - # solve my problem... - if res_obj > MAX_A: - res[a_res_key] = MAX_A - if a_res_key not in keys_to_patch: - keys_to_patch.append(a_res_key) - elif isinstance(res_obj, dict): - #_LOGGER.warning(f"found dict in 16A check: {res_obj}") - vals_to_check = final_dics.get(a_res_key) - for val in vals_to_check: - # WE MUST/SHOULD check, if this... see comment #216+ - if res_obj[val] > MAX_A: - res[a_res_key][val] = MAX_A - if a_res_key not in keys_to_patch: - keys_to_patch.append(a_res_key) - - _LOGGER.info(f"16A checker: reduce the following keys: {keys_to_patch}") - for a_key in keys_to_patch: - if is_lbgroup_id_set and a_key in [Tag.LOT.key]: - _LOGGER.debug(f"16A checker: skipping key {a_key}, since loadbalancer group id is set") - continue - _LOGGER.info(f"reduce {a_key} to 16A -> writing {res[a_key]}") - await bridge.write_value_to_key(a_key, res[a_key]) - except Exception as e: - _LOGGER.error(f"16A checker: Error while forcing 16A settings: {type(e).__name__} - {e}") - - class GoeChargerDataUpdateCoordinator(DataUpdateCoordinator): _debounced_update_task: asyncio.Task | None = None @@ -751,54 +663,62 @@ async def read_versions(self): else: _LOGGER.info(f"active cards {self.available_cards_idx}") - # check for the 16A limiter... - # we now always try to read a possible ccl object and use there the 'cableCurrentLimit' - if there - # is no ccl or we have an error, we make use of the adi as fallback (if it's not core_wallbox)... - wb_has_16a_cable_limit = None - use_adi_as_fallback = not self._is_core_wallbox - if Tag.CLL.key in self.bridge._versions: - ccl_obj = self.bridge._versions.none_null_get(Tag.CLL.key, {}) - if len(ccl_obj) > 0: - ccl_cable_limit_val = ccl_obj.get("cableCurrentLimit", "-1") - try: - ccl_cable_limit_int = int(ccl_cable_limit_val) - if ccl_cable_limit_int > 0: - _LOGGER.debug(f"read_versions(): read CLL:cableCurrentLimit: '{ccl_cable_limit_val}'") - wb_has_16a_cable_limit = ccl_cable_limit_int <= 16 - use_adi_as_fallback = False - else: - _LOGGER.debug(f"read_versions(): no 'cableCurrentLimit' found in CLL object, but there IS content: {ccl_obj} - will not enforce the 16A CABLE limit") - # 20266/10/28 this is astounding for me - as reported in #176 a 'go-eCharger_V5' running - # FW '59.4' does NOT contain the key 'cableCurrentLimit' - I even installed the '59.4' FW - # on my Flex and there the key is still present also in that 'old' FW... so I must assume, - # that ONLY a FLEX Wallbox will contain this 'cableCurrentLimit' key :-/ - wb_has_16a_cable_limit = False - use_adi_as_fallback = False - - except BaseException as exc: - _LOGGER.debug(f"read_versions(): try to handle CLL:cableCurrentLimit caused: {type(exc).__name__} - {exc}") - if self._is_core_wallbox: - wb_has_16a_cable_limit = True - else: - _LOGGER.debug(f"read_versions(): CLL object is empty! {ccl_obj} (or was not present) - we probably check ADI key...") - - if wb_has_16a_cable_limit is None and use_adi_as_fallback: - wb_has_16a_cable_limit = self.bridge._versions.none_null_get(Tag.ADI.key, False) - _LOGGER.debug(f"read_versions(): USE Fallback to API-ADI key in order to set a possible 16A limit - with the result ADI is: {wb_has_16a_cable_limit} (or not present)") - - # if we haven't seen any 'wb_has_16a_cable_limit' yet, then we enable the limit just to be sure... - if wb_has_16a_cable_limit is None: - _LOGGER.debug(f"read_versions(): enforcing 'wb_has_16a_cable_limit=True' cause previously the code did not set any value") - wb_has_16a_cable_limit = True + # # check for the 16A limiter... + # # we now always try to read a possible ccl object and use there the 'cableCurrentLimit' - if there + # # is no ccl or we have an error, we make use of the adi as fallback (if it's not core_wallbox)... + # wb_has_16a_cable_limit = None + # use_adi_as_fallback = not self._is_core_wallbox + # if Tag.CLL.key in self.bridge._versions: + # ccl_obj = self.bridge._versions.none_null_get(Tag.CLL.key, {}) + # if len(ccl_obj) > 0: + # ccl_cable_limit_val = ccl_obj.get("cableCurrentLimit", "-1") + # try: + # ccl_cable_limit_int = int(ccl_cable_limit_val) + # if ccl_cable_limit_int > 0: + # _LOGGER.debug(f"read_versions(): read CLL:cableCurrentLimit: '{ccl_cable_limit_val}'") + # wb_has_16a_cable_limit = ccl_cable_limit_int <= 16 + # use_adi_as_fallback = False + # else: + # _LOGGER.debug(f"read_versions(): no 'cableCurrentLimit' found in CLL object, but there IS content: {ccl_obj} - will not enforce the 16A CABLE limit") + # # 20266/10/28 this is astounding for me - as reported in #176 a 'go-eCharger_V5' running + # # FW '59.4' does NOT contain the key 'cableCurrentLimit' - I even installed the '59.4' FW + # # on my Flex and there the key is still present also in that 'old' FW... so I must assume, + # # that ONLY a FLEX Wallbox will contain this 'cableCurrentLimit' key :-/ + # wb_has_16a_cable_limit = False + # use_adi_as_fallback = False + # + # except BaseException as exc: + # _LOGGER.debug(f"read_versions(): try to handle CLL:cableCurrentLimit caused: {type(exc).__name__} - {exc}") + # else: + # _LOGGER.debug(f"read_versions(): CLL object is empty! {ccl_obj} (or was not present) - we probably check ADI key...") + # + # # checking the cbl value + # # if wb_has_16a_cable_limit is None and Tag.CBL in self.bridge._versions: + # # cbl_value = int(self.bridge._versions.none_null_get(Tag.CBL.key, 9999999)) + # # if cbl_value < 9999999: + # # _LOGGER.debug(f"read_versions(): CBL fallback -> read value is '{cbl_value}'") + # # wb_has_16a_cable_limit = cbl_value <= 16 + # + # # so the cll AND the cbl check did not work... + # if wb_has_16a_cable_limit is None: + # if use_adi_as_fallback: + # wb_has_16a_cable_limit = self.bridge._versions.none_null_get(Tag.ADI.key, False) + # _LOGGER.debug(f"read_versions(): USE Fallback to API-ADI key in order to set a possible 16A limit - with the result ADI is: {wb_has_16a_cable_limit} (or not present)") + # + # # if we haven't seen any 'wb_has_16a_cable_limit' yet, then we ENFORCE the limit + # # since I assume it's better to have a limit before doing any harm... (sorry 22Kw Owners) + # if wb_has_16a_cable_limit is None: + # _LOGGER.debug(f"read_versions(): enforcing 'wb_has_16a_cable_limit=True' cause previously the code did not set any value") + # wb_has_16a_cable_limit = True a = self._config_entry.data.get(CONF_11KWLIMIT, False) b = self.bridge._versions.none_null_get(Tag.VAR.key, -1) == 11 - self.limit_to16a = a or b or wb_has_16a_cable_limit + self.limit_to16a = a or b #or wb_has_16a_cable_limit if (self.limit_to16a): - _LOGGER.info(f"read_versions(): Charger LIMIT to 16A is active [CONF_11KWLIMIT: {a}, VAR==11: {b}, DETECTED CABLE-LIMIT: {wb_has_16a_cable_limit}]") + _LOGGER.info(f"read_versions(): Charger LIMIT to 16A is active [CONF_11KWLIMIT: {a}, VAR==11: {b}]")#, DETECTED CABLE-LIMIT: {wb_has_16a_cable_limit}]") else: - _LOGGER.info(f"read_versions(): Charger NO LIMIT detected [CONF_11KWLIMIT: {a}, VAR==11: {b}, DETECTED CABLE-LIMIT: {wb_has_16a_cable_limit}]") + _LOGGER.info(f"read_versions(): Charger NO LIMIT detected [CONF_11KWLIMIT: {a}, VAR==11: {b}]")#, DETECTED CABLE-LIMIT: {wb_has_16a_cable_limit}]") else: # no additional controller stuff... but we need to init some variables self.limit_to16a = False @@ -840,11 +760,11 @@ async def read_versions(self): return True - async def check_for_16a_limit(self, hass, entry_id): - _LOGGER.debug(f"check relevant entities for 16A limit... in 15sec") - await asyncio.sleep(15) - _LOGGER.debug(f"check relevant entities for 16A limit NOW!") - await check_and_write_to_16a(hass=hass, config_entry_id=entry_id, bridge=self.bridge, is_lbgroup_id_set=self.is_loadbalancer_group_id_set) + # async def check_for_16a_limit(self, hass, entry_id): + # _LOGGER.debug(f"check relevant entities for 16A limit... in 15sec") + # await asyncio.sleep(15) + # _LOGGER.debug(f"check relevant entities for 16A limit NOW!") + # await check_and_write_to_16a(hass=hass, config_entry_id=entry_id, bridge=self.bridge, is_lbgroup_id_set=self.is_loadbalancer_group_id_set) def is_valid_charger_entity(self, description: EntityDescription) -> bool: if description.key == Tag.TMA.key and hasattr(description, "idx"): diff --git a/custom_components/goecharger_api2/manifest.json b/custom_components/goecharger_api2/manifest.json index 8ca2f3f..52c13e4 100644 --- a/custom_components/goecharger_api2/manifest.json +++ b/custom_components/goecharger_api2/manifest.json @@ -9,5 +9,5 @@ "iot_class": "local_push", "issue_tracker": "https://github.com/marq24/ha-goecharger-api2/issues", "requirements": ["msgpack>=1.1.2"], - "version": "2026.10.8" + "version": "2026.11.0" } diff --git a/custom_components/goecharger_api2/pygoecharger_ha/const.py b/custom_components/goecharger_api2/pygoecharger_ha/const.py index 57c4264..491b7ba 100644 --- a/custom_components/goecharger_api2/pygoecharger_ha/const.py +++ b/custom_components/goecharger_api2/pygoecharger_ha/const.py @@ -20,7 +20,7 @@ FILTER_SYSTEMS: Final = "oem,sse,typ,var,hai,cae" -FILTER_VERSIONS_NOCARDS: Final = f"ccrv,typ,log,fwc,fwv,var,cll,adi" +FILTER_VERSIONS_NOCARDS: Final = f"ccrv,typ,log,fwc,fwv,var" FILTER_VERSIONS: Final = f"{FILTER_VERSIONS_NOCARDS},{FILTER_CARDS_ID_CLASSIC},{FILTER_CARDS_ID_FWV60}" FILTER_MIN_STATES: Final = "car,modelStatus,err,nrg,tcl,tma,trx" FILTER_IDS_ADDON: Final = ",pakku,ppv,pgrid" diff --git a/tests/integration/test_coordinator_runtime.py b/tests/integration/test_coordinator_runtime.py index 26be57a..4389dcd 100644 --- a/tests/integration/test_coordinator_runtime.py +++ b/tests/integration/test_coordinator_runtime.py @@ -15,7 +15,6 @@ from homeassistant.exceptions import ConfigEntryNotReady from homeassistant.helpers import device_registry as dr -from custom_components.goecharger_api2 import check_and_write_to_16a from custom_components.goecharger_api2.const import DOMAIN from custom_components.goecharger_api2.pygoecharger_ha import NonNullDict from custom_components.goecharger_api2.pygoecharger_ha.keys import Tag @@ -92,78 +91,6 @@ def _current_entity(key, max_value=16, **kwargs): return SimpleNamespace(**values) -async def _run_limiter(hass, entities, read_result, lb_group=False): - bridge = SimpleNamespace(_read_filtered_data=AsyncMock(return_value=read_result), - write_value_to_key=AsyncMock()) - with patch(f"{PKG}.entity_reg.async_entries_for_config_entry", return_value=entities): - await check_and_write_to_16a(hass, "entry", bridge, lb_group) - return bridge - - -async def test_limiter_lowers_a_value_above_16_a(hass): - bridge = await _run_limiter(hass, [_current_entity("amp")], {"amp": 20}) - bridge.write_value_to_key.assert_awaited_once_with("amp", 16) - - -async def test_limiter_leaves_values_up_to_16_a_alone(hass): - bridge = await _run_limiter(hass, [_current_entity("amp")], {"amp": 16}) - bridge.write_value_to_key.assert_not_awaited() - - -async def test_limiter_lowers_a_value_inside_an_object(hass): - entities = [_current_entity("lot_amp"), _current_entity("lot_sta")] - bridge = await _run_limiter(hass, entities, {"lot": {"amp": 32, "sta": 10}}) - bridge.write_value_to_key.assert_awaited_once_with("lot", {"amp": 16, "sta": 10}) - assert bridge._read_filtered_data.await_args.kwargs["filters"] == "lot" - - -async def test_limiter_skips_the_load_balancer_value_when_a_group_is_set(hass): - bridge = await _run_limiter(hass, [_current_entity("lot_amp"), _current_entity("amp")], - {"lot": {"amp": 32}, "amp": 20}, lb_group=True) - bridge.write_value_to_key.assert_awaited_once_with("amp", 16) - - -async def test_limiter_ignores_entities_that_do_not_count(hass): - entities = [ - _current_entity("a", max_value=32), - _current_entity("b", original_device_class=None), - _current_entity("c", disabled=True), - _current_entity("d", orphaned_timestamp=123.0), - _current_entity("e", capabilities={}), - ] - bridge = await _run_limiter(hass, entities, {}) - bridge._read_filtered_data.assert_not_awaited() - - -async def test_limiter_asks_for_a_shared_key_only_once(hass): - bridge = await _run_limiter(hass, [_current_entity("amp"), _current_entity("amp")], {"amp": 10}) - assert bridge._read_filtered_data.await_args.kwargs["filters"] == "amp" - - -async def test_limiter_ignores_values_that_are_neither_numbers_nor_objects(hass): - bridge = await _run_limiter(hass, [_current_entity("amp"), _current_entity("fna")], {"amp": 20, "fna": "name"}) - bridge.write_value_to_key.assert_awaited_once_with("amp", 16) - - -async def test_limiter_survives_a_failing_read(hass, caplog): - bridge = SimpleNamespace(_read_filtered_data=AsyncMock(side_effect=RuntimeError("boom")), - write_value_to_key=AsyncMock()) - with patch(f"{PKG}.entity_reg.async_entries_for_config_entry", return_value=[_current_entity("amp")]): - await check_and_write_to_16a(hass, "entry", bridge, False) - assert "Error while forcing 16A settings" in caplog.text - bridge.write_value_to_key.assert_not_awaited() - - -async def test_limiter_task_waits_and_then_runs_the_check(hass): - coordinator = make_coordinator(hass, data={}) - coordinator.is_loadbalancer_group_id_set = True - with patch(f"{PKG}.asyncio.sleep", AsyncMock()) as sleep, \ - patch(f"{PKG}.check_and_write_to_16a", AsyncMock()) as check: - await coordinator.check_for_16a_limit(hass, "entry-id") - sleep.assert_awaited_once_with(15) - check.assert_awaited_once_with(hass=hass, config_entry_id="entry-id", bridge=coordinator.bridge, is_lbgroup_id_set=True) - - # ----------------------------------------------------------------------------- device detection (read_versions) def _versions_coordinator(hass, versions, **kwargs): @@ -222,13 +149,13 @@ async def test_type_falls_back_to_the_type_stored_at_setup(hass): async def test_unreadable_cable_limit_on_a_core_wallbox_limits_to_16_a(hass): coordinator = _versions_coordinator(hass, {"typ": "go-eCharger_Phoenix", "cll": {"cableCurrentLimit": "n/a"}}) await coordinator.read_versions() - assert coordinator.limit_to16a is True + assert coordinator.limit_to16a is False async def test_unreadable_cable_limit_on_another_wallbox_uses_the_adapter_flag(hass): coordinator = _versions_coordinator(hass, {"typ": "go-eCharger_V4", "cll": {"cableCurrentLimit": "n/a"}, "adi": True}) await coordinator.read_versions() - assert coordinator.limit_to16a is True + assert coordinator.limit_to16a is False async def test_empty_cll_object_falls_back_to_the_adapter_flag(hass): diff --git a/tests/integration/test_services_and_setup.py b/tests/integration/test_services_and_setup.py index 9747781..e5b3552 100644 --- a/tests/integration/test_services_and_setup.py +++ b/tests/integration/test_services_and_setup.py @@ -227,10 +227,9 @@ async def test_registry_helpers_ignore_unknown_entries(hass): def setup_patches(): with patch.object(GoeChargerDataUpdateCoordinator, "_async_update_data", AsyncMock(return_value={"car": 1})), \ patch.object(GoeChargerDataUpdateCoordinator, "read_versions", AsyncMock(return_value=True)), \ - patch.object(GoeChargerDataUpdateCoordinator, "check_for_16a_limit", AsyncMock()) as limiter, \ patch.object(GoeChargerDataUpdateCoordinator, "start_websocket_and_wait_for_first_data", AsyncMock(return_value=True)) as ws_start, \ patch.object(GoeChargerDataUpdateCoordinator, "start_watchdog", AsyncMock()) as watchdog: - yield SimpleNamespace_(limiter=limiter, ws_start=ws_start, watchdog=watchdog) + yield SimpleNamespace_(ws_start=ws_start, watchdog=watchdog) class SimpleNamespace_: @@ -288,18 +287,6 @@ async def test_watchdog_waits_for_home_assistant_to_finish_starting(hass, setup_ setup_patches.watchdog.assert_awaited_once() -async def test_limited_charger_starts_the_16_a_task(hass, setup_patches): - async def limited(self): - self.limit_to16a = True - return True - - entry = make_entry(hass) - with patch.object(GoeChargerDataUpdateCoordinator, "read_versions", limited): - await _setup(hass, entry) - await hass.async_block_till_done() - setup_patches.limiter.assert_awaited_once() - - async def test_unknown_language_falls_back_to_english_texts(hass, setup_patches): from homeassistant.exceptions import ConfigEntryNotReady hass.config.language = "xx" diff --git a/tests/integration/test_versions_16a.py b/tests/integration/test_versions_16a.py index 3cd4ee9..3cbc2ab 100644 --- a/tests/integration/test_versions_16a.py +++ b/tests/integration/test_versions_16a.py @@ -30,13 +30,13 @@ def _cll(**fields): CASES = [ # id, device, extra versions, option, expected ("cable_limit_32", NON_CORE, _cll(cableCurrentLimit=32), False, False), - ("cable_limit_16", NON_CORE, _cll(cableCurrentLimit=16), False, True), - ("cable_limit_6", NON_CORE, _cll(cableCurrentLimit=6), False, True), + ("cable_limit_16", NON_CORE, _cll(cableCurrentLimit=16), False, False), + ("cable_limit_6", NON_CORE, _cll(cableCurrentLimit=6), False, False), ("variant_11kw_always_limits", NON_CORE, {"var": 11, **_cll(cableCurrentLimit=32)}, False, True), ("user_option_forces_limit", NON_CORE, _cll(cableCurrentLimit=32), True, True), - ("no_cll_adapter_in_use", NON_CORE, {"adi": True}, False, True), + ("no_cll_adapter_in_use", NON_CORE, {"adi": True}, False, False), ("no_cll_no_adapter", NON_CORE, {"adi": False}, False, False), - ("core_without_cll_is_limited", CORE, {}, False, True), + ("core_without_cll_is_limited", CORE, {}, False, False), # current behaviour since 98af3e0 (issue #176): cll present but no cableCurrentLimit -> no limit ("cll_without_cable_limit_non_core", NON_CORE, _cll(currentLimitMax=32), False, False), ("cll_without_cable_limit_core", CORE, _cll(currentLimitMax=32), False, False), diff --git a/tests/integration/test_versions_transport_parity.py b/tests/integration/test_versions_transport_parity.py deleted file mode 100644 index 68b35da..0000000 --- a/tests/integration/test_versions_transport_parity.py +++ /dev/null @@ -1,41 +0,0 @@ -"""Bridge.read_versions: the same device answer must lead to the same `_versions` via HTTP and via WebSocket. - -Background is issue #176: on a charger with firmware 59.4 the `cll` object contained `cableCurrentLimit` -when read via the HTTP API, but not when read via the WebSocket. The bridge only passes the data on, so -these tests pin that it does not change the content depending on the transport. They cannot show what a -real charger sends over each transport; that needs a read-only comparison on the device. -""" -import pytest - -from custom_components.goecharger_api2.pygoecharger_ha import NonNullDict -from custom_components.goecharger_api2.pygoecharger_ha.const import FILTER_VERSIONS - -from .factories import make_bridge - -STATUS = "http://192.0.2.10/api/status" - -PAYLOADS = [ - pytest.param({"typ": "go-eCharger_V4", "fwv": "59.4", "var": 22, - "cll": {"cableCurrentLimit": 32, "currentLimitMax": 32}}, id="cll_with_cable_limit"), - pytest.param({"typ": "go-eCharger_V4", "fwv": "59.4", "var": 22, - "cll": {"currentLimitMax": 32, "requestedCurrent": 16}}, id="cll_without_cable_limit"), - pytest.param({"typ": "go-eCharger_V4", "fwv": "59.4", "var": 22}, id="no_cll"), -] - - -def test_cll_is_part_of_the_versions_filter(): - assert "cll" in FILTER_VERSIONS.split(",") - - -@pytest.mark.parametrize("payload", PAYLOADS) -async def test_versions_are_identical_via_http_and_websocket(hass, aioclient_mock, payload): - aioclient_mock.get(STATUS, json=payload) - via_http = make_bridge(hass) - await via_http.read_versions() - - via_ws = make_bridge(hass, websocket=True) - via_ws._ws_states = {**payload, "unrelated": 1} - await via_ws.read_versions() - - assert isinstance(via_http._versions, NonNullDict) and isinstance(via_ws._versions, NonNullDict) - assert dict(via_http._versions) == dict(via_ws._versions) == payload From 4cc2b59a1e60f65833b82b709c7d6353ee5d97ec Mon Sep 17 00:00:00 2001 From: marq24 Date: Sun, 1 Nov 2026 16:07:03 +0100 Subject: [PATCH 24/24] ask heavy users for support --- README.md | 3 +- custom_components/goecharger_api2/__init__.py | 127 ++++++++++++++---- .../goecharger_api2/pygoecharger_ha/const.py | 4 + docs/PVSURPLUS.md | 23 +++- docs/SUPPORT.md | 62 +++++++++ 5 files changed, 183 insertions(+), 36 deletions(-) create mode 100644 docs/SUPPORT.md diff --git a/README.md b/README.md index c5fa1f2..59b2155 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,8 @@ Support for all go-eCharger Wallboxes & go-eController's supporting the APIv2 or a WebSocket connection. This also included other branded Wallboxes that are based on the go-eCharger hardware (like the Fronius Wattpilot) — as long as they support the WebSocket or HTTP APIv2 connection. +[![hacs_badge][hacsbadge]][hacs] [![hainstall][hainstallbadge]][hainstall] [![Wero][werobadge]][wero] [![wise][wisebadge]][wise] [![Revolut][revolutbadge]][revolut] [![PayPal][paypalbadge]][paypal] [![github][ghsbadge]][ghs] [![BuyMeCoffee][buymecoffeebadge]][buymecoffee] + > [!WARNING] > ## Disclaimer > Please be aware that we are developing this integration to the best of our knowledge and belief, but can't give a guarantee. Therefore, use this integration **at your own risk**. @@ -19,7 +21,6 @@ So it's either enabled HTTP v2 API or password to use the websocket [[see instru > [!IMPORTANT] > __Please note__, _that this integration is __not official__, nor is it supported by the go-e manufacture from Austria. I (marq24) am not affiliated with go-e.com in any way. This integration is based on the go-e API and the available go-e API documentation. (and some reverse engineering)_. -[![hacs_badge][hacsbadge]][hacs] [![hainstall][hainstallbadge]][hainstall] [![Wero][werobadge]][wero] [![wise][wisebadge]][wise] [![Revolut][revolutbadge]][revolut] [![PayPal][paypalbadge]][paypal] [![github][ghsbadge]][ghs] [![BuyMeCoffee][buymecoffeebadge]][buymecoffee] ## Latest successfully tested go-eCharger Firmware Versions / go-eController Firmware Versions diff --git a/custom_components/goecharger_api2/__init__.py b/custom_components/goecharger_api2/__init__.py index 3011d92..9c062d6 100644 --- a/custom_components/goecharger_api2/__init__.py +++ b/custom_components/goecharger_api2/__init__.py @@ -20,11 +20,11 @@ CONF_DELAY, EVENT_HOMEASSISTANT_STARTED ) +from homeassistant.components import persistent_notification from homeassistant.core import HomeAssistant, Event, SupportsResponse, CoreState from homeassistant.exceptions import ConfigEntryNotReady from homeassistant.helpers import ( config_validation as config_val, - entity_registry as entity_reg, device_registry as device_reg ) from homeassistant.helpers.aiohttp_client import async_get_clientsession @@ -70,6 +70,7 @@ COMMUNICATION_MODE_WEBSOCKET: Final = "WEBSOCKET" COMMUNICATION_MODE_HTTPGET: Final = "HTTPGET" ACTIVE_CHARGER_COUNTER: Final = "active_charger_counter" +IGNORE_CONFIG_ENTRY_UPDATES = False async def async_migrate_entry(hass: HomeAssistant, config_entry: ConfigEntry): if config_entry.version == 1: @@ -106,13 +107,60 @@ async def async_setup(hass: HomeAssistant, config: dict): # pylint: disable=unu return True -async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry): - if DOMAIN not in hass.data: - the_integration = await async_get_integration(hass, DOMAIN) - intg_version = the_integration.version if the_integration is not None else "UNKNOWN" - _LOGGER.info(STARTUP_MESSAGE % intg_version) - hass.data.setdefault(DOMAIN, {"manifest_version": intg_version}) +def handle_heavy_user(hass: HomeAssistant, lang_map): + async def check_count_of_running_integrations(hass: HomeAssistant): + try: + _LOGGER.debug(f"check_count_of_running_integrations(): init") + await asyncio.sleep(120) + _LOGGER.debug(f"check_count_of_running_integrations(): ready to start") + loaded_entries = hass.config_entries.async_loaded_entries(DOMAIN) + if len(loaded_entries) > 8: + last_notify_ts = int(loaded_entries[0].data.get("heavy_usage_notification", 0)) + elapsed = time.time() - last_notify_ts + if elapsed > (60 * 24 * 60 * 60): + try: + notify_id = f"{DOMAIN}_heavy_user_notification" + persistent_notification.async_create(hass, + title=lang_map["heavy_user_title"], + message=lang_map["heavy_user_msg"], + notification_id=notify_id) + + all_entries = hass.config_entries.async_entries(DOMAIN) + global IGNORE_CONFIG_ENTRY_UPDATES + IGNORE_CONFIG_ENTRY_UPDATES = True + ts = int(time.time()) + for a_entry in all_entries: + new_data = {**a_entry.data} + new_data["heavy_usage_notification"] = ts + hass.config_entries.async_update_entry(a_entry, data=new_data) + IGNORE_CONFIG_ENTRY_UPDATES = False + async def auto_dismiss(delay_seconds: int): + try: + await asyncio.sleep(delay_seconds) + except asyncio.CancelledError: + _LOGGER.debug(f"auto_dismiss() will be cancelled...") + await hass.services.async_call("persistent_notification", "dismiss", {"notification_id": notify_id}) + + # show the message for 4h ater the start - then auto cancel it + a_task = hass.async_create_task(auto_dismiss(4*60*60)) + hass.data[DOMAIN]["checker_task"] = a_task + + except BaseException as e: + _LOGGER.debug(f"persistent_notification() caused {type(e).__name__} - {e}", stack_info=True) + + else: + _LOGGER.debug(f"persistent_notification() just notified less then 30 days ago") + else: + _LOGGER.debug(f"persistent_notification() loaded entries is: {loaded_entries}") + + except asyncio.CancelledError: + _LOGGER.debug(f"check_count_of_running_integrations() will be cancelled...") + + task = hass.async_create_task(check_count_of_running_integrations(hass)) + hass.data[DOMAIN]["checker_task"] = task + +async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry): # ok starting the init sequence... lang = hass.config.language.lower() if lang in TRANSLATIONS: @@ -120,6 +168,13 @@ async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry): else: lang_map = TRANSLATIONS["en"] + if DOMAIN not in hass.data: + the_integration = await async_get_integration(hass, DOMAIN) + intg_version = the_integration.version if the_integration is not None else "UNKNOWN" + _LOGGER.info(STARTUP_MESSAGE % intg_version) + hass.data.setdefault(DOMAIN, {"manifest_version": intg_version}) + handle_heavy_user(hass, lang_map) + coordinator = GoeChargerDataUpdateCoordinator(hass, config_entry) a_pwd = config_entry.data.get(CONF_PASSWORD, None) if a_pwd is not None and len(a_pwd.strip()) > 0: @@ -185,30 +240,43 @@ async def async_unload_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> unload_ok = await hass.config_entries.async_unload_platforms(config_entry, PLATFORMS) if unload_ok: - if DOMAIN in hass.data and config_entry.entry_id in hass.data[DOMAIN]: - coordinator = hass.data[DOMAIN][config_entry.entry_id] - coordinator.cancel_existing_debouncer() - coordinator.stop_watchdog() - coordinator.clear_data() - hass.data[DOMAIN].pop(config_entry.entry_id) - if coordinator.intg_type == INTG_TYPE.CHARGER.value: - hass.data[DOMAIN][ACTIVE_CHARGER_COUNTER] = hass.data[DOMAIN][ACTIVE_CHARGER_COUNTER] - 1 - remove_config_entry_from_service(config_entry) - - # ONLY remove the SERVICES if this is the LAST ACTIVE config_entry that will be unloaded! - if hass.data[DOMAIN][ACTIVE_CHARGER_COUNTER] == 0: - _LOGGER.info(f"async_unload_entry(): Unloading last active go-eCharger - will remove SERVICES") - hass.services.async_remove(DOMAIN, SERVICE_SET_PV_DATA) - hass.services.async_remove(DOMAIN, SERVICE_STOP_CHARGING) - hass.services.async_remove(DOMAIN, SERVICE_SET_TEMPORARY_CURRENT) + if DOMAIN in hass.data: + if config_entry.entry_id in hass.data[DOMAIN]: + coordinator = hass.data[DOMAIN][config_entry.entry_id] + coordinator.cancel_existing_debouncer() + coordinator.stop_watchdog() + coordinator.clear_data() + hass.data[DOMAIN].pop(config_entry.entry_id) + if coordinator.intg_type == INTG_TYPE.CHARGER.value: + hass.data[DOMAIN][ACTIVE_CHARGER_COUNTER] = hass.data[DOMAIN][ACTIVE_CHARGER_COUNTER] - 1 + remove_config_entry_from_service(config_entry) + + # ONLY remove the SERVICES if this is the LAST ACTIVE config_entry that will be unloaded! + if hass.data[DOMAIN][ACTIVE_CHARGER_COUNTER] == 0: + _LOGGER.info(f"async_unload_entry(): Unloading last active go-eCharger - will remove SERVICES") + hass.services.async_remove(DOMAIN, SERVICE_SET_PV_DATA) + hass.services.async_remove(DOMAIN, SERVICE_STOP_CHARGING) + hass.services.async_remove(DOMAIN, SERVICE_SET_TEMPORARY_CURRENT) + + # if there is a 'active checker task', we cancel it... (and do not care that + # this task will be "killed" when the first integration entry will be + # unloaded) + if "checker_task" in hass.data[DOMAIN]: + task = hass.data[DOMAIN]["checker_task"] + if not task.done(): + task.cancel() + hass.data[DOMAIN].pop("checker_task") return unload_ok async def entry_update_listener(hass: HomeAssistant, config_entry: ConfigEntry) -> None: - _LOGGER.debug(f"entry_update_listener(): called for entry: {config_entry.entry_id}") - await hass.config_entries.async_reload(config_entry.entry_id) - + global IGNORE_CONFIG_ENTRY_UPDATES + if not IGNORE_CONFIG_ENTRY_UPDATES: + _LOGGER.debug(f"entry_update_listener(): called for entry: {config_entry.entry_id}") + await hass.config_entries.async_reload(config_entry.entry_id) + else: + _LOGGER.debug(f"entry_update_listener(): IGNORE for entry: {config_entry.entry_id} (IGNORE_CONFIG_ENTRY_UPDATES=True)") class GoeChargerDataUpdateCoordinator(DataUpdateCoordinator): @@ -396,8 +464,11 @@ async def start_websocket_and_wait_for_first_data(self, is_integration_init:bool _LOGGER.debug(f"start_websocket_and_wait_for_first_data(): task created {self._ws_start_task.get_coro()}") async_call_later(self.hass, 10, self.call_later_update_device_registry) - - _LOGGER.info(f"Essential device data is available after WebSocket connection has been established - that's just so GREAT! Available keys: {list(self.data.keys())} so let's move on...") + if self.data is not None and hasattr(self.data, "keys"): + l_data = list(self.data.keys()) + else: + l_data = "[self.data is NONE?! - or no 'keys()']" + _LOGGER.info(f"Essential device data is available after WebSocket connection has been established - that's just so GREAT! Available keys: {l_data} so let's move on...") return True async def start_watchdog(self, event=None): diff --git a/custom_components/goecharger_api2/pygoecharger_ha/const.py b/custom_components/goecharger_api2/pygoecharger_ha/const.py index 491b7ba..d5af386 100644 --- a/custom_components/goecharger_api2/pygoecharger_ha/const.py +++ b/custom_components/goecharger_api2/pygoecharger_ha/const.py @@ -110,6 +110,8 @@ class CT_VALUES(Enum): TRANSLATIONS: Final = { "de": { + "heavy_user_title": "Möchtest Du nicht die Entwicklung unterstützen?", + "heavy_user_msg": "Du hast mehr als 8 aktive 'go-e APIv2 Connect' Integrationseinträge. Als intensiver Nutzer solltest Du Dir überlegen, ob Du die Weiterentwicklung und Pflege nicht unterstützen möchtest?\n\nBitte besuche das [Projekt go-e APIv2 Connect auf GitHub](https://github.com/marq24/ha-goecharger-api2/blob/main/docs/SUPPORT.md), um mehr zu erfahren.\n\nDanke für Deine Aufmerksamkeit @marq24", "coord_null_data": "Es konnten keine Daten abgerufen werden. Bitte prüfe Dein Home Assistant System Protokoll auf mögliche Fehlermeldungen der Integration.", "coord_no_device_data": "Es konnten keine Daten zu Deinem konfigurierten Gerät abgerufen werden. Bitte prüfe Dein Home Assistant System Protokoll auf mögliche Fehlermeldungen der Integration.", "websocket_start_failed": "Der Websocket konnte innerhalb von einer Minute nicht gestartet werden. Bitte prüfe Dein Home Assistant System Protokoll auf mögliche Fehlermeldungen der Integration.", @@ -284,6 +286,8 @@ class CT_VALUES(Enum): "cll_unsymetrycurrentlimit": "Stromlimit Asymmetrie" }, "en": { + "heavy_user_title": "Wouldn't you like to support the development ?", + "heavy_user_msg": "You have more than 8 active 'go-e APIv2 Connect' integration entries. As a heavy user, you should consider supporting its further development and maintenance.\n\nPlease visit the [project go-e APIv2 Connect at GitHub](https://github.com/marq24/ha-goecharger-api2/blob/main/docs/SUPPORT.md) to learn more.\n\nThanks for your attention @marq24", "coord_null_data": "Coordinator could not provided any data. Please check your Home Assistant system log for possible error messages.", "coord_no_device_data": "Coordinator could not fetch essential information from your configured device. Please check your Home Assistant system log for possible error messages.", "websocket_start_failed": "The Websocket could not be started within one minute. Please check your Home Assistant system log for possible error messages.", diff --git a/docs/PVSURPLUS.md b/docs/PVSURPLUS.md index 335ef3e..19fde41 100644 --- a/docs/PVSURPLUS.md +++ b/docs/PVSURPLUS.md @@ -1,8 +1,7 @@ -# PV Surplus Charging with the go-eCharger +# PV Surplus Charging with the go-eCharger -![logo](https://github.com/marq24/ha-goecharger-api2/raw/main/logo.png) +[![hacs_badge][hacsbadge]][hacs] [![hainstall][hainstallbadge]][hainstall] [![Wero][werobadge]][wero] [![wise][wisebadge]][wise] [![Revolut][revolutbadge]][revolut] [![PayPal][paypalbadge]][paypal] [![github][ghsbadge]][ghs] [![BuyMeCoffee][buymecoffeebadge]][buymecoffee] - [![Wero][werobadge]][wero] [![Revolut][revolutbadge]][revolut] [![PayPal][paypalbadge]][paypal] [![github][ghsbadge]][ghs] [![BuyMeCoffee][buymecoffeebadge]][buymecoffee] ## Enable PV Surplus Charging via HA automation @@ -184,20 +183,30 @@ action: mode: single ``` + +[hacs]: https://hacs.xyz +[hacsbadge]: https://img.shields.io/badge/HACS-Default-blue?style=for-the-badge&logo=homeassistantcommunitystore&logoColor=ccc + +[hainstall]: https://my.home-assistant.io/redirect/config_flow_start/?domain=goecharger_api2 +[hainstallbadge]: https://img.shields.io/badge/dynamic/json?style=for-the-badge&logo=home-assistant&logoColor=ccc&label=usage&suffix=%20installs&cacheSeconds=15600&url=https://analytics.home-assistant.io/custom_integrations.json&query=$.goecharger_api2.total + [ghs]: https://github.com/sponsors/marq24 [ghsbadge]: https://img.shields.io/github/sponsors/marq24?style=for-the-badge&logo=github&logoColor=ccc&link=https%3A%2F%2Fgithub.com%2Fsponsors%2Fmarq24&label=Sponsors [buymecoffee]: https://www.buymeacoffee.com/marquardt24 -[buymecoffeebadge]: https://img.shields.io/badge/buy%20me%20a-coffee-blue.svg?style=for-the-badge&logo=buymeacoffee&logoColor=ccc +[buymecoffeebadge]: https://img.shields.io/badge/buy%20me%20a-coffee-blue.svg?style=for-the-badge&logo=buymeacoffee&logoColor=ccc&link=https%3A%2F%2Fwww.buymeacoffee.com%2Fmarquardt24 [buymecoffee2]: https://buymeacoffee.com/marquardt24/membership -[buymecoffeebadge2]: https://img.shields.io/badge/coffee-subs-blue.svg?style=for-the-badge&logo=buymeacoffee&logoColor=ccc +[buymecoffeebadge2]: https://img.shields.io/badge/coffee-subs-blue.svg?style=for-the-badge&logo=buymeacoffee&logoColor=ccc&link=https%3A%2F%2Fbuymeacoffee.com%2Fmarquardt24%2Fmembership [paypal]: https://paypal.me/marq24 -[paypalbadge]: https://img.shields.io/badge/paypal-me-blue.svg?style=for-the-badge&logo=paypal&logoColor=ccc +[paypalbadge]: https://img.shields.io/badge/paypal-me-blue.svg?style=for-the-badge&logo=paypal&logoColor=ccc&link=https%3A%2F%2Fpaypal.me%2Fmarq24 [wero]: https://share.weropay.eu/p/1/c/6O371wjUW5 [werobadge]: https://img.shields.io/badge/_wero-me_-blue.svg?style=for-the-badge&logo=data:image/svg%2bxml;base64,PHN2ZwogICByb2xlPSJpbWciCiAgIHZpZXdCb3g9IjAgMCA0Mi4wNDY1MDEgNDAuODg2NyIKICAgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIgo+CiAgPGcKICAgICBjbGlwLXBhdGg9InVybCgjY2xpcDApIgogICAgIHRyYW5zZm9ybT0idHJhbnNsYXRlKC01Ny4zODE4KSI+CiAgICA8cGF0aAogICAgICAgZD0ibSA3OC40MDUxLDMwLjM1NzQgYyAwLDAgLTAuMDE4NSwwIC0wLjAyNzgsMCAtNC4zMTg0LDAgLTcuMzQ2MiwtMi41NzY5IC04LjY0NjEsLTUuOTg4NyBIIDk5LjA2OTggQyA5OS4zMDU3LDIzLjA4NDkgOTkuNDI4MywyMS43NzExIDk5LjQyODMsMjAuNDQxIDk5LjQyODMsOS43NTY3MyA5MS43Mzc1LDAuMDEzODc4NyA3OC40MDUxLDAgdiAxMC41MjcgYyA0LjM0MzksMC4wMTE2IDcuMzQxNiwyLjU4MzcgOC42Mjc2LDUuOTg4NyBoIC0yOS4yOTcgYyAtMC4yMzM2LDEuMjgzNyAtMC4zNTM5LDIuNTk3NiAtMC4zNTM5LDMuOTI3NiAwLDEwLjY5MTMgNy43MDAyLDIwLjQ0MzQgMjAuOTk1NSwyMC40NDM0IDAuMDA5MywwIDAuMDE4NSwwIDAuMDI3OCwwIHYgLTEwLjUyNyB6IgogICAgICAgZmlsbD0iI0NDQ0NDQyIvPgogICAgPHBhdGgKICAgICAgIGQ9Im0gNzguMzc3NCw0MC44ODQ0IGMgMC40NTEsMCAwLjg5NTEsLTAuMDEzOSAxLjMzNDYsLTAuMDM0NyAyLjcwMTcsLTAuMTM2NSA1LjE1MzUsLTAuNjgwMSA3LjMzOTMsLTEuNTU2NyAyLjE4NTgsLTAuODc2NyA0LjEwNTcsLTIuMDgxOCA1LjczODcsLTMuNTM5MSAxLjYzMywtMS40NTczIDIuOTgxNSwtMy4xNjQzIDQuMDI3LC01LjA0NDkgMC45NTA2LC0xLjcwOTQgMS42NDQ1LC0zLjU1OTkgMi4wNzk0LC01LjQ5MTMgSCA4Ni42NzIgYyAtMC4yNDk4LDAuNTE1OCAtMC41NDEzLDEuMDA4NSAtMC44NzQ0LDEuNDY4OCAtMC40NTU2LDAuNjI5MSAtMC45ODk5LDEuMjAwNSAtMS41OTYsMS42OTMyIC0wLjYwNiwwLjQ5MjcgLTEuMjg2LDAuOTA5IC0yLjAzNTQsMS4yMzA2IC0wLjc0OTUsMC4zMjE1IC0xLjU2NiwwLjU0ODIgLTIuNDQ5NSwwLjY2MTUgLTAuNDMwMywwLjA1NTUgLTAuODc0NCwwLjA4NzkgLTEuMzM0NywwLjA4NzkgLTIuNzUwMiwwIC00Ljk3NzYsLTEuMDQ3OCAtNi41NjY3LC0yLjY4NzggbCAtNy45NDc2LDcuOTQ3OCBjIDMuNTM2NiwzLjIyOTIgOC40NDI2LDUuMjY0NyAxNC41MTY2LDUuMjY0NyB6IgogICAgICAgZmlsbD0idXJsKCNwYWludDApIgogICAgICAgc3R5bGU9ImZpbGw6dXJsKCNwYWludDApIiAvPgogICAgPHBhdGgKICAgICAgIGQ9Ik0gNzguMzc3NywwIEMgNjcuMTAxNiwwIDU5Ljg1MDIsNy4wMTMzNyA1Ny45MDcyLDE1LjY2OTEgSCA3MC4wOTcgYyAxLjQ1NzIsLTIuOTgxNyA0LjMyNzcsLTUuMTQyMSA4LjI4MDcsLTUuMTQyMSAzLjE1MDMsMCA1LjU5NTIsMS4zNDYyIDcuMTkzNSwzLjM4MTggTCA5My41OTA1LDUuODg5MiBDIDkwLjAwNzYsMi4zMDE1NSA4NC44NTY1LDAuMDAyMzEzMTIgNzguMzc1MywwLjAwMjMxMzEyIFoiCiAgICAgICBmaWxsPSJ1cmwoI3BhaW50MSkiCiAgICAgICBzdHlsZT0iZmlsbDp1cmwoI3BhaW50MSkiIC8+CiAgPC9nPgogIDxkZWZzPgogICAgPGxpbmVhckdyYWRpZW50CiAgICAgICBpZD0icGFpbnQwIgogICAgICAgeDE9IjkyLjc0MzY5OCIKICAgICAgIHkxPSIxOC4wMjYxOTkiCiAgICAgICB4Mj0iNzQuNzU0NTAxIgogICAgICAgeTI9IjQwLjMxMDIiCiAgICAgICBncmFkaWVudFVuaXRzPSJ1c2VyU3BhY2VPblVzZSI+CiAgICAgIDxzdG9wCiAgICAgICAgIG9mZnNldD0iMC4wMiIKICAgICAgICAgc3RvcC1jb2xvcj0iI0NDQ0NDQyIKICAgICAgICAgc3RvcC1vcGFjaXR5PSIwIi8+CiAgICAgIDxzdG9wCiAgICAgICAgIG9mZnNldD0iMC4zOSIKICAgICAgICAgc3RvcC1jb2xvcj0iI0NDQ0NDQyIKICAgICAgICAgc3RvcC1vcGFjaXR5PSIwLjY2Ii8+CiAgICAgIDxzdG9wCiAgICAgICAgIG9mZnNldD0iMC42OCIKICAgICAgICAgc3RvcC1jb2xvcj0iI0NDQ0NDQyIvPgogICAgPC9saW5lYXJHcmFkaWVudD4KICAgIDxsaW5lYXJHcmFkaWVudAogICAgICAgaWQ9InBhaW50MSIKICAgICAgIHgxPSI2MS4yNzA0MDEiCiAgICAgICB5MT0iMjMuMDE3Nzk5IgogICAgICAgeDI9Ijc5Ljc1NDUwMSIKICAgICAgIHkyPSI0LjUzNDI5OTkiCiAgICAgICBncmFkaWVudFVuaXRzPSJ1c2VyU3BhY2VPblVzZSI+CiAgICAgIDxzdG9wCiAgICAgICAgIG9mZnNldD0iMC4wMiIKICAgICAgICAgc3RvcC1jb2xvcj0iI0NDQ0NDQyIKICAgICAgICAgc3RvcC1vcGFjaXR5PSIwIi8+CiAgICAgIDxzdG9wCiAgICAgICAgIG9mZnNldD0iMC4zOSIKICAgICAgICAgc3RvcC1jb2xvcj0iI0NDQ0NDQyIKICAgICAgICAgc3RvcC1vcGFjaXR5PSIwLjY2Ii8+CiAgICAgIDxzdG9wCiAgICAgICAgIG9mZnNldD0iMC42OCIKICAgICAgICAgc3RvcC1jb2xvcj0iI0NDQ0NDQyIvPgogICAgPC9saW5lYXJHcmFkaWVudD4KICAgIDxjbGlwUGF0aAogICAgICAgaWQ9ImNsaXAwIj4KICAgICAgPHJlY3QKICAgICAgICAgd2lkdGg9IjE3Ny45MSIKICAgICAgICAgaGVpZ2h0PSI0MSIKICAgICAgICAgZmlsbD0iI2ZmZmZmZiIKICAgICAgICAgeD0iMCIKICAgICAgICAgeT0iMCIgLz4KICAgIDwvY2xpcFBhdGg+CiAgPC9kZWZzPgo8L3N2Zz4= [revolut]: https://revolut.me/marq24 -[revolutbadge]: https://img.shields.io/badge/_revolut-me_-blue.svg?style=for-the-badge&logo=revolut&logoColor=ccc \ No newline at end of file +[revolutbadge]: https://img.shields.io/badge/_revolut-me_-blue.svg?style=for-the-badge&logo=revolut&logoColor=ccc&link=https%3A%2F%2Frevolut.me%2Fmarq24 + +[wise]: https://wise.com/pay/me/matthiasm1046 +[wisebadge]: https://img.shields.io/badge/_wise-me_-blue.svg?style=for-the-badge&logo=wise&logoColor=ccc&link=https%3A%2F%2Fwise.com%2Fpay%2Fme%2Fmatthiasm1046 \ No newline at end of file diff --git a/docs/SUPPORT.md b/docs/SUPPORT.md new file mode 100644 index 0000000..a0b0a03 --- /dev/null +++ b/docs/SUPPORT.md @@ -0,0 +1,62 @@ +# Support the maintainance & development + +[![hacs_badge][hacsbadge]][hacs] [![hainstall][hainstallbadge]][hainstall] + +--- + +Either you just found this page manually (quite unlikely) or you have been directed from your Home Assistant to this page. + +When you are **directed from your Home Assistant**, then you have currently **more than 6 active running configurations** for this integration running. This makes you a heavy user of this integration and I would kindly ask you **to support the maintenance and development**. + +You have different options to do that: + +### Become a monthly supporter +- [![github][ghsbadge]][ghs] +- [![BuyMeCoffee2][buymecoffeebadge2]][buymecoffee2] + +### or a one time support via: +- [![Wero][werobadge]][wero] +- [![Revolut][revolutbadge]][revolut] +- [![BuyMeCoffee][buymecoffeebadge]][buymecoffee] +- [![wise][wisebadge]][wise] +- [![PayPal][paypalbadge]][paypal] + +If you have already supported the development in the past - never mind - THANKS for your support! + + + +--- + +[hacs]: https://hacs.xyz +[hacsbadge]: https://img.shields.io/badge/HACS-Default-blue?style=for-the-badge&logo=homeassistantcommunitystore&logoColor=ccc + +[hainstall]: https://my.home-assistant.io/redirect/config_flow_start/?domain=goecharger_api2 +[hainstallbadge]: https://img.shields.io/badge/dynamic/json?style=for-the-badge&logo=home-assistant&logoColor=ccc&label=usage&suffix=%20installs&cacheSeconds=15600&url=https://analytics.home-assistant.io/custom_integrations.json&query=$.goecharger_api2.total + +[ghs]: https://github.com/sponsors/marq24 +[ghsbadge]: https://img.shields.io/github/sponsors/marq24?style=for-the-badge&logo=github&logoColor=ccc&link=https%3A%2F%2Fgithub.com%2Fsponsors%2Fmarq24&label=Sponsors + +[buymecoffee]: https://www.buymeacoffee.com/marquardt24 +[buymecoffeebadge]: https://img.shields.io/badge/buy%20me%20a-coffee-blue.svg?style=for-the-badge&logo=buymeacoffee&logoColor=ccc&link=https%3A%2F%2Fwww.buymeacoffee.com%2Fmarquardt24 + +[buymecoffee2]: https://buymeacoffee.com/marquardt24/membership +[buymecoffeebadge2]: https://img.shields.io/badge/coffee-subs-blue.svg?style=for-the-badge&logo=buymeacoffee&logoColor=ccc&link=https%3A%2F%2Fbuymeacoffee.com%2Fmarquardt24%2Fmembership + +[paypal]: https://paypal.me/marq24 +[paypalbadge]: https://img.shields.io/badge/paypal-me-blue.svg?style=for-the-badge&logo=paypal&logoColor=ccc&link=https%3A%2F%2Fpaypal.me%2Fmarq24 + +[wero]: https://share.weropay.eu/p/1/c/6O371wjUW5 +[werobadge]: https://img.shields.io/badge/_wero-me_-blue.svg?style=for-the-badge&logo=data:image/svg%2bxml;base64,PHN2ZwogICByb2xlPSJpbWciCiAgIHZpZXdCb3g9IjAgMCA0Mi4wNDY1MDEgNDAuODg2NyIKICAgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIgo+CiAgPGcKICAgICBjbGlwLXBhdGg9InVybCgjY2xpcDApIgogICAgIHRyYW5zZm9ybT0idHJhbnNsYXRlKC01Ny4zODE4KSI+CiAgICA8cGF0aAogICAgICAgZD0ibSA3OC40MDUxLDMwLjM1NzQgYyAwLDAgLTAuMDE4NSwwIC0wLjAyNzgsMCAtNC4zMTg0LDAgLTcuMzQ2MiwtMi41NzY5IC04LjY0NjEsLTUuOTg4NyBIIDk5LjA2OTggQyA5OS4zMDU3LDIzLjA4NDkgOTkuNDI4MywyMS43NzExIDk5LjQyODMsMjAuNDQxIDk5LjQyODMsOS43NTY3MyA5MS43Mzc1LDAuMDEzODc4NyA3OC40MDUxLDAgdiAxMC41MjcgYyA0LjM0MzksMC4wMTE2IDcuMzQxNiwyLjU4MzcgOC42Mjc2LDUuOTg4NyBoIC0yOS4yOTcgYyAtMC4yMzM2LDEuMjgzNyAtMC4zNTM5LDIuNTk3NiAtMC4zNTM5LDMuOTI3NiAwLDEwLjY5MTMgNy43MDAyLDIwLjQ0MzQgMjAuOTk1NSwyMC40NDM0IDAuMDA5MywwIDAuMDE4NSwwIDAuMDI3OCwwIHYgLTEwLjUyNyB6IgogICAgICAgZmlsbD0iI0NDQ0NDQyIvPgogICAgPHBhdGgKICAgICAgIGQ9Im0gNzguMzc3NCw0MC44ODQ0IGMgMC40NTEsMCAwLjg5NTEsLTAuMDEzOSAxLjMzNDYsLTAuMDM0NyAyLjcwMTcsLTAuMTM2NSA1LjE1MzUsLTAuNjgwMSA3LjMzOTMsLTEuNTU2NyAyLjE4NTgsLTAuODc2NyA0LjEwNTcsLTIuMDgxOCA1LjczODcsLTMuNTM5MSAxLjYzMywtMS40NTczIDIuOTgxNSwtMy4xNjQzIDQuMDI3LC01LjA0NDkgMC45NTA2LC0xLjcwOTQgMS42NDQ1LC0zLjU1OTkgMi4wNzk0LC01LjQ5MTMgSCA4Ni42NzIgYyAtMC4yNDk4LDAuNTE1OCAtMC41NDEzLDEuMDA4NSAtMC44NzQ0LDEuNDY4OCAtMC40NTU2LDAuNjI5MSAtMC45ODk5LDEuMjAwNSAtMS41OTYsMS42OTMyIC0wLjYwNiwwLjQ5MjcgLTEuMjg2LDAuOTA5IC0yLjAzNTQsMS4yMzA2IC0wLjc0OTUsMC4zMjE1IC0xLjU2NiwwLjU0ODIgLTIuNDQ5NSwwLjY2MTUgLTAuNDMwMywwLjA1NTUgLTAuODc0NCwwLjA4NzkgLTEuMzM0NywwLjA4NzkgLTIuNzUwMiwwIC00Ljk3NzYsLTEuMDQ3OCAtNi41NjY3LC0yLjY4NzggbCAtNy45NDc2LDcuOTQ3OCBjIDMuNTM2NiwzLjIyOTIgOC40NDI2LDUuMjY0NyAxNC41MTY2LDUuMjY0NyB6IgogICAgICAgZmlsbD0idXJsKCNwYWludDApIgogICAgICAgc3R5bGU9ImZpbGw6dXJsKCNwYWludDApIiAvPgogICAgPHBhdGgKICAgICAgIGQ9Ik0gNzguMzc3NywwIEMgNjcuMTAxNiwwIDU5Ljg1MDIsNy4wMTMzNyA1Ny45MDcyLDE1LjY2OTEgSCA3MC4wOTcgYyAxLjQ1NzIsLTIuOTgxNyA0LjMyNzcsLTUuMTQyMSA4LjI4MDcsLTUuMTQyMSAzLjE1MDMsMCA1LjU5NTIsMS4zNDYyIDcuMTkzNSwzLjM4MTggTCA5My41OTA1LDUuODg5MiBDIDkwLjAwNzYsMi4zMDE1NSA4NC44NTY1LDAuMDAyMzEzMTIgNzguMzc1MywwLjAwMjMxMzEyIFoiCiAgICAgICBmaWxsPSJ1cmwoI3BhaW50MSkiCiAgICAgICBzdHlsZT0iZmlsbDp1cmwoI3BhaW50MSkiIC8+CiAgPC9nPgogIDxkZWZzPgogICAgPGxpbmVhckdyYWRpZW50CiAgICAgICBpZD0icGFpbnQwIgogICAgICAgeDE9IjkyLjc0MzY5OCIKICAgICAgIHkxPSIxOC4wMjYxOTkiCiAgICAgICB4Mj0iNzQuNzU0NTAxIgogICAgICAgeTI9IjQwLjMxMDIiCiAgICAgICBncmFkaWVudFVuaXRzPSJ1c2VyU3BhY2VPblVzZSI+CiAgICAgIDxzdG9wCiAgICAgICAgIG9mZnNldD0iMC4wMiIKICAgICAgICAgc3RvcC1jb2xvcj0iI0NDQ0NDQyIKICAgICAgICAgc3RvcC1vcGFjaXR5PSIwIi8+CiAgICAgIDxzdG9wCiAgICAgICAgIG9mZnNldD0iMC4zOSIKICAgICAgICAgc3RvcC1jb2xvcj0iI0NDQ0NDQyIKICAgICAgICAgc3RvcC1vcGFjaXR5PSIwLjY2Ii8+CiAgICAgIDxzdG9wCiAgICAgICAgIG9mZnNldD0iMC42OCIKICAgICAgICAgc3RvcC1jb2xvcj0iI0NDQ0NDQyIvPgogICAgPC9saW5lYXJHcmFkaWVudD4KICAgIDxsaW5lYXJHcmFkaWVudAogICAgICAgaWQ9InBhaW50MSIKICAgICAgIHgxPSI2MS4yNzA0MDEiCiAgICAgICB5MT0iMjMuMDE3Nzk5IgogICAgICAgeDI9Ijc5Ljc1NDUwMSIKICAgICAgIHkyPSI0LjUzNDI5OTkiCiAgICAgICBncmFkaWVudFVuaXRzPSJ1c2VyU3BhY2VPblVzZSI+CiAgICAgIDxzdG9wCiAgICAgICAgIG9mZnNldD0iMC4wMiIKICAgICAgICAgc3RvcC1jb2xvcj0iI0NDQ0NDQyIKICAgICAgICAgc3RvcC1vcGFjaXR5PSIwIi8+CiAgICAgIDxzdG9wCiAgICAgICAgIG9mZnNldD0iMC4zOSIKICAgICAgICAgc3RvcC1jb2xvcj0iI0NDQ0NDQyIKICAgICAgICAgc3RvcC1vcGFjaXR5PSIwLjY2Ii8+CiAgICAgIDxzdG9wCiAgICAgICAgIG9mZnNldD0iMC42OCIKICAgICAgICAgc3RvcC1jb2xvcj0iI0NDQ0NDQyIvPgogICAgPC9saW5lYXJHcmFkaWVudD4KICAgIDxjbGlwUGF0aAogICAgICAgaWQ9ImNsaXAwIj4KICAgICAgPHJlY3QKICAgICAgICAgd2lkdGg9IjE3Ny45MSIKICAgICAgICAgaGVpZ2h0PSI0MSIKICAgICAgICAgZmlsbD0iI2ZmZmZmZiIKICAgICAgICAgeD0iMCIKICAgICAgICAgeT0iMCIgLz4KICAgIDwvY2xpcFBhdGg+CiAgPC9kZWZzPgo8L3N2Zz4= + +[revolut]: https://revolut.me/marq24 +[revolutbadge]: https://img.shields.io/badge/_revolut-me_-blue.svg?style=for-the-badge&logo=revolut&logoColor=ccc&link=https%3A%2F%2Frevolut.me%2Fmarq24 + +[wise]: https://wise.com/pay/me/matthiasm1046 +[wisebadge]: https://img.shields.io/badge/_wise-me_-blue.svg?style=for-the-badge&logo=wise&logoColor=ccc&link=https%3A%2F%2Fwise.com%2Fpay%2Fme%2Fmatthiasm1046 \ No newline at end of file