Skip to content

Add Bitvis Power Hub integration - #165457

Draft
MandusBorjesson wants to merge 2 commits into
home-assistant:devfrom
MandusBorjesson:bitvis-powerhub-ha
Draft

Add Bitvis Power Hub integration#165457
MandusBorjesson wants to merge 2 commits into
home-assistant:devfrom
MandusBorjesson:bitvis-powerhub-ha

Conversation

@MandusBorjesson

@MandusBorjesson MandusBorjesson commented Mar 13, 2026

Copy link
Copy Markdown

Breaking change

Proposed change

Type of change

  • Dependency upgrade
  • Bugfix (non-breaking change which fixes an issue)
  • New integration (thank you!)
  • New feature (which adds functionality to an existing integration)
  • Deprecation (breaking change to happen in the future)
  • Breaking change (fix/feature causing existing functionality to break)
  • Code quality improvements to existing code or addition of tests

Additional information

Checklist

  • I understand the code I am submitting and can explain how it works.
  • The code change is tested and works locally.
  • Local tests pass. Your PR cannot be merged unless tests pass
  • There is no commented out code in this PR.
  • I have followed the development checklist
  • I have followed the perfect PR recommendations
  • The code has been formatted using Ruff (ruff format homeassistant tests)
  • Tests have been added to verify that the new code works.
  • Any generated code has been carefully reviewed for correctness and compliance with project standards.

If user exposed functionality or configuration variables are added/changed:

If the code communicates with devices, web services, or third-party tools:

To help with the load of incoming pull requests:

Copilot AI review requested due to automatic review settings March 13, 2026 12:51

@home-assistant home-assistant Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @MandusBorjesson

It seems you haven't yet signed a CLA. Please do so here.

Once you do that we will be able to review and accept this pull request.

Thanks!

@home-assistant

Copy link
Copy Markdown
Contributor

Please take a look at the requested changes, and use the Ready for review button when you are done, thanks 👍

Learn more about our pull request process.

@home-assistant home-assistant Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @MandusBorjesson

It seems you haven't yet signed a CLA. Please do so here.

Once you do that we will be able to review and accept this pull request.

Thanks!

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a new Home Assistant core integration for the Bitvis Power Hub, exposing power/energy and diagnostic sensors via a local UDP push data path, with config flow + zeroconf discovery and accompanying tests.

Changes:

  • Introduces the bitvis integration (config flow, UDP coordinator/protocol, sensors, diagnostics, translations, manifest, quality scale).
  • Adds a full test suite for config flow, coordinator/protocol, sensors, and diagnostics.
  • Wires the integration into HA metadata/infra (requirements, generated discovery/config-flow registries, strict typing, codeowners).

Reviewed changes

Copilot reviewed 21 out of 24 changed files in this pull request and generated 13 comments.

Show a summary per file
File Description
tests/components/bitvis/test_sensor.py Validates sensor entity creation, availability, values, and device info.
tests/components/bitvis/test_init.py Tests setup/unload behavior and retry on startup failure.
tests/components/bitvis/test_diagnostics.py Tests diagnostics payload structure with/without device diagnostics.
tests/components/bitvis/test_coordinator.py Exercises UDP protocol parsing/dispatch + coordinator watchdog and data setters.
tests/components/bitvis/test_config_flow.py Covers user + zeroconf config flow paths, errors, and duplicates.
tests/components/bitvis/conftest.py Provides integration setup fixtures and a default mock config entry.
tests/components/bitvis/init.py Marks the Bitvis tests package.
requirements_test_all.txt Adds bitvis-protobuf for tests.
requirements_all.txt Adds bitvis-protobuf runtime dependency.
mypy.ini Enables strict-ish mypy settings for the new integration package.
homeassistant/generated/zeroconf.py Registers _powerhub._udp.local. zeroconf type to bitvis.
homeassistant/generated/integrations.json Adds bitvis integration metadata (hub/local_push/config_flow).
homeassistant/generated/config_flows.py Registers bitvis as having a config flow.
homeassistant/components/bitvis/strings.json Adds config flow strings + entity translation strings.
homeassistant/components/bitvis/sensor.py Defines measurement + diagnostic sensors backed by the coordinator.
homeassistant/components/bitvis/quality_scale.yaml Declares integration quality scale status (targeting silver).
homeassistant/components/bitvis/manifest.json Declares integration manifest, requirements, and zeroconf type.
homeassistant/components/bitvis/diagnostics.py Adds diagnostics endpoint support for the integration.
homeassistant/components/bitvis/coordinator.py Implements UDP listener/protocol + watchdog for push updates.
homeassistant/components/bitvis/const.py Adds integration constants (domain, defaults, watchdog interval).
homeassistant/components/bitvis/config_flow.py Implements user + zeroconf config flow and UDP port binding test.
homeassistant/components/bitvis/init.py Sets up/unloads the config entry and starts/stops the coordinator.
CODEOWNERS Assigns owners for the integration and its tests.
.strict-typing Adds the integration to the strict-typing list.

Comment thread homeassistant/components/bitvis/__init__.py Outdated
Comment thread homeassistant/components/bitvis/coordinator.py Outdated
Comment thread homeassistant/components/bitvis/sensor.py
Comment thread homeassistant/components/bitvis/diagnostics.py Outdated
Comment thread homeassistant/components/bitvis/const.py Outdated
Comment thread homeassistant/components/bitvis/coordinator.py Outdated
Comment thread homeassistant/components/bitvis/coordinator.py Outdated
Comment thread homeassistant/components/bitvis/config_flow.py Outdated
Comment thread homeassistant/components/bitvis/sensor.py
Comment thread homeassistant/components/bitvis/config_flow.py Outdated
@MandusBorjesson
MandusBorjesson marked this pull request as ready for review March 16, 2026 13:15
Copilot AI review requested due to automatic review settings March 16, 2026 13:15
@home-assistant
home-assistant Bot dismissed stale reviews from themself March 16, 2026 13:15

Stale

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a new bitvis integration to Home Assistant Core for the Bitvis Power Hub, including UDP push handling, config flow + zeroconf discovery, sensors, and diagnostics, along with generated metadata and a full test suite.

Changes:

  • Introduces the Bitvis integration (config flow, coordinator/UDP protocol, sensors, diagnostics, translations, manifest, quality scale).
  • Registers zeroconf discovery and generated integration metadata.
  • Adds comprehensive tests and updates requirements/typing configuration for the new domain.

Reviewed changes

Copilot reviewed 21 out of 24 changed files in this pull request and generated 14 comments.

Show a summary per file
File Description
homeassistant/components/bitvis/init.py Integration setup/unload wiring using a coordinator
homeassistant/components/bitvis/config_flow.py User + zeroconf config flow with a UDP port bind check
homeassistant/components/bitvis/coordinator.py UDP listener/protocol, coordinator storage, watchdog for availability
homeassistant/components/bitvis/sensor.py Sample + diagnostic sensors backed by the coordinator
homeassistant/components/bitvis/diagnostics.py Diagnostics output with redaction
homeassistant/components/bitvis/strings.json UI strings for config flow + entity translations
homeassistant/components/bitvis/manifest.json Declares domain metadata, requirements, zeroconf
homeassistant/components/bitvis/quality_scale.yaml Integration quality scale checklist
homeassistant/generated/{config_flows.py,integrations.json,zeroconf.py} Generated registration for config flows/integration/zeroconf
tests/components/bitvis/* New test suite + fixtures for the integration
requirements_all.txt / requirements_test_all.txt Adds bitvis-protobuf==0.1.2
mypy.ini / .strict-typing Enables stricter typing checks for bitvis
CODEOWNERS Adds code ownership entries for the new integration

Comment thread homeassistant/components/bitvis/coordinator.py Outdated
Comment thread tests/components/bitvis/test_coordinator.py Outdated
Comment thread tests/components/bitvis/test_coordinator.py Outdated
Comment thread tests/components/bitvis/test_coordinator.py Outdated
Comment thread tests/components/bitvis/test_diagnostics.py Outdated
Comment thread tests/components/bitvis/test_sensor.py Outdated
Comment thread tests/components/bitvis/test_sensor.py Outdated
Comment thread tests/components/bitvis/test_sensor.py
Comment thread homeassistant/components/bitvis/coordinator.py Outdated
Comment thread homeassistant/components/bitvis/coordinator.py Outdated
Copilot AI review requested due to automatic review settings March 16, 2026 14:57

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a new Home Assistant integration for the Bitvis Power Hub (local UDP push), including config flow + Zeroconf discovery, sensors, diagnostics, and a full test suite.

Changes:

  • Introduces the bitvis integration (manifest, config flow, coordinator/UDP protocol, sensors, diagnostics, translations).
  • Adds comprehensive tests for config flow, coordinator behavior, sensors, init/unload, and diagnostics.
  • Wires the integration into generated registries, strict typing/mypy config, CODEOWNERS, and requirements.

Reviewed changes

Copilot reviewed 21 out of 24 changed files in this pull request and generated 6 comments.

Show a summary per file
File Description
homeassistant/components/bitvis/__init__.py Sets up/unloads the config entry and forwards platforms.
homeassistant/components/bitvis/config_flow.py User + Zeroconf config flow with UDP port bind pre-check.
homeassistant/components/bitvis/const.py Integration constants (domain, defaults, watchdog interval).
homeassistant/components/bitvis/coordinator.py UDP listener/protocol, data storage, watchdog for staleness.
homeassistant/components/bitvis/sensor.py Sample + diagnostic sensor entities and entity descriptions.
homeassistant/components/bitvis/diagnostics.py Diagnostics payload generation + redaction.
homeassistant/components/bitvis/manifest.json Declares integration metadata, requirements, zeroconf type.
homeassistant/components/bitvis/strings.json UI strings for the config flow and sensors.
homeassistant/components/bitvis/quality_scale.yaml Quality scale self-assessment checklist.
tests/components/bitvis/conftest.py Common fixtures and integration setup helper.
tests/components/bitvis/test_config_flow.py Config flow coverage for user + zeroconf paths.
tests/components/bitvis/test_coordinator.py Coordinator/protocol unit tests + watchdog behavior.
tests/components/bitvis/test_diagnostics.py Diagnostics output + redaction assertions.
tests/components/bitvis/test_init.py Setup/unload behavior and retry on startup failure.
tests/components/bitvis/test_sensor.py Ensures expected entities and state behavior with/without data.
tests/components/bitvis/__init__.py Test package marker.
homeassistant/generated/config_flows.py Registers bitvis as a config-flow integration.
homeassistant/generated/integrations.json Adds integration metadata to generated registry.
homeassistant/generated/zeroconf.py Maps _powerhub._udp.local. to the bitvis domain.
requirements_all.txt Adds runtime dependency bitvis-protobuf==0.1.2.
requirements_test_all.txt Adds test dependency bitvis-protobuf==0.1.2.
mypy.ini Enables strict mypy settings for homeassistant.components.bitvis.*.
.strict-typing Marks homeassistant.components.bitvis.* as strict-typing.
CODEOWNERS Adds codeowners for the integration and its tests.

Comment thread homeassistant/components/bitvis/coordinator.py Outdated
Comment thread homeassistant/components/bitvis/diagnostics.py Outdated
Comment thread homeassistant/components/bitvis/coordinator.py Outdated
Comment thread homeassistant/components/bitvis/coordinator.py Outdated
Comment thread homeassistant/components/bitvis/coordinator.py Outdated
Comment thread tests/components/bitvis/test_coordinator.py Outdated
Copilot AI review requested due to automatic review settings March 17, 2026 08:04

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 21 out of 24 changed files in this pull request and generated 4 comments.

Comment thread tests/components/bitvis/test_coordinator.py
Comment thread tests/components/bitvis/test_config_flow.py Outdated
Comment thread tests/components/bitvis/test_init.py
Comment thread homeassistant/components/bitvis/coordinator.py

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 21 out of 24 changed files in this pull request and generated 1 comment.

Comment thread homeassistant/components/bitvis/coordinator.py Outdated

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 21 out of 24 changed files in this pull request and generated no new comments.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 21 out of 24 changed files in this pull request and generated 2 comments.

Comment thread homeassistant/components/bitvis/coordinator.py Outdated
Comment thread homeassistant/components/bitvis/coordinator.py Outdated
Comment thread homeassistant/components/bitvis/config_flow.py
Comment on lines +82 to +85
domain_data = hass.data.setdefault(DOMAIN, {})
if "listener_registry" not in domain_data:
domain_data["listener_registry"] = BitvisListenerRegistry()
registry: BitvisListenerRegistry = domain_data["listener_registry"]

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. We don't have to put the data in hass.data in a dict, we can just store the listener directly in hass.data[DOMAIN]
  2. Can we use a HassKey to make hass.data[DOMAIN] typed?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure thing, that makes sense 👍

"""Coordinator to manage data updates from UDP packets."""

def __init__(
self, hass: HomeAssistant, config_entry: ConfigEntry, host: str, port: int

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we use the extended config entry

Comment on lines +153 to +184
async def _async_resolve_host(self) -> set[str]:
"""Resolve the configured host to a set of IP addresses.

Only IP addresses are registered with the shared listener. If the host is
not already an IP literal and cannot be resolved, treat this as a setup
error so Home Assistant can retry the config entry later.
"""
ips: set[str] = set()

# If the configured host is already an IP address, always include it.
try:
ipaddress.ip_address(self.host)
except ValueError:
pass
else:
ips.add(self.host)

loop = asyncio.get_running_loop()
try:
addrinfo = await loop.getaddrinfo(self.host, None)
except OSError:
_LOGGER.debug("Could not resolve host %s to IP addresses", self.host)
else:
for *_, sockaddr in addrinfo:
ips.add(sockaddr[0])

if not ips:
raise UpdateFailed(
f"Could not resolve Bitvis Power Hub host {self.host!r} to an IP address"
)

return ips

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is something for the library instead

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree, moved it.

) from err

self._watchdog_task = asyncio.create_task(self._async_watchdog())
_LOGGER.info(

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we don't have to log this on info imo

Comment on lines +563 to +564
if self.coordinator.data.sample is None:
return None

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if self.coordinator.data.sample is None:
return None

If this was the case, the entity would be unavailable

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're correct, but mypy doesn't understand this and won't lets me pass CI without the check.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you can always add if TYPE_CHECKING: with an assert that it's not None

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's a nice way to do it 👍

return DeviceInfo(
identifiers={(DOMAIN, device_identifier)},
connections={(CONNECTION_NETWORK_MAC, mac_address)} if mac_address else set(),
name=title,

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the entry title is the default for every device

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

True, removing.

return (
super().available
and self.coordinator.data.sample is not None
and self.entity_description.exists_fn(self.coordinator.data.sample.sample)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

so we're now checking for if an data point exists on runtime, but we still create the entity, is that correct? If we don't get a certain data type, shouldn't we avoid creating that entity at all?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You are correct. There are a few things that complicate the "create-only-when-available" idea:

  1. We don't know what entities will be available when we create the device.
  2. Some meters don't send all of their data points in every message.

This handling didn't reflect the latter case too well either. I have removed the exist_fn entirely. From what I can tell, is should make handling of "sparse" data points better.

"""Initialize the sensor."""
super().__init__(coordinator)
self.entity_description = description
self._device_identifier = entry.unique_id or entry.entry_id

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why no unique id sometimes?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It should never be, removed the fallback and added an assertion instead.

Comment on lines +578 to +583
CoordinatorEntity[BitvisDataUpdateCoordinator], SensorEntity
):
"""Representation of a Bitvis diagnostic sensor."""

entity_description: BitvisDiagnosticSensorEntityDescription
_attr_has_entity_name = True

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Considered creating a base sensor?

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 21 out of 24 changed files in this pull request and generated 1 comment.

Comment thread tests/components/bitvis/test_coordinator.py Outdated
@MandusBorjesson

Copy link
Copy Markdown
Author

@joostlek I think I have addressed all of you active comments, please have a look 👍 I also found that boot time was calculated incorrectly, so that has also been fixed.

Comment on lines +27 to +58
Skips the check when HA already owns a shared listener on this port (a
second device on the same port is valid — it will share the existing socket).
Raises OSError if the port is unavailable (e.g. already in use by another
process) or invalid.
"""
if async_get_listener_registry(hass).has_listener(port):
return

loop = asyncio.get_running_loop()
transports: list[asyncio.DatagramTransport] = []
bind_errors: list[Exception] = []
for family, local_addr in (
(socket.AF_INET6, ("::", port)),
(socket.AF_INET, ("0.0.0.0", port)),
):
try:
transport, _ = await loop.create_datagram_endpoint(
asyncio.DatagramProtocol,
local_addr=local_addr,
family=family,
)
except (OSError, ValueError) as err:
bind_errors.append(err)
else:
assert isinstance(transport, asyncio.DatagramTransport)
transports.append(transport)

if not transports:
raise OSError("UDP port is unavailable or invalid") from bind_errors[0]

for transport in transports:
transport.close()

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think

    loop = asyncio.get_running_loop()
    transports: list[asyncio.DatagramTransport] = []
    bind_errors: list[Exception] = []
    for family, local_addr in (
        (socket.AF_INET6, ("::", port)),
        (socket.AF_INET, ("0.0.0.0", port)),
    ):
        try:
            transport, _ = await loop.create_datagram_endpoint(
                asyncio.DatagramProtocol,
                local_addr=local_addr,
                family=family,
            )

Is device specific

)
resolved_host = host

await self.async_set_unique_id(format_unique_id(resolved_host, port))

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this almost looks like you're putting a host and port together as unique id, but that is not acceptable as a unique id

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're right, you left a comment on this in an earlier review but I would like some input on what's the most reasonable way forwards:

#165457 (comment)

The device does broadcast its MAC address as part of its diagnostics so I could use that instead. However, diagnostics are only published it hourly so waiting for it at this point isn't really feasible. Any other ideas? From what I can tell, entry_id might be a sensible option in this case.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Basically that's also up to you, to us but also yourself. What does the Bitvis exposes that can be considered unique? An HomeID? ClientID? DeviceID? InstallationID? Think something along that way. :)

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As mentioned, we have a MAC address in the diagnostics message. At the point of adding the device we're unfortunately going in a bit blind as there is no way of polling the device for information.

I suppose it would be possible to use it. But in that case I would first add the device with some generic ID and then somehow migrate it once I receive the MAC? Other options would include serial numbers transmitted by the meter the device is connected to, but we run into the same issue with information not being available during adding the device. In addition, energy meters are not exactly consistent in what information they transmit, some may not send anything at all. So MAC is probably my best bet.

Thoughts?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So may I ask why we don't have the information yet? Like I don't mind having logic in the config flow that goes further than just checking credentials.

The idea behind this check is that whenever someone has this set up, that they can't set up the same device twice. And if we use a temporary generic ID, then we can't compare an entry that was migrated to that ID to the generic ID

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We only require an IP address and port from the user when adding the device, so we won't have more to go on without further introspection. The device itself knows very little about the facility and mostly acts as a data pipe to a backend. I have a few ideas on how we could get more info, maybe the two of you can chime in? Here are my thoughts:

  1. Add MAC to telemetry data
    • Meaning it is sent every 10 seconds rather than hourly. Is waiting in this function acceptable?
  2. Query the MAC
    • For example using the getmac library
    • Should be fairly quick and easy, this is the option I'm leaning towards
  3. Lean into the non-uniqueness
    • Any normal installation would only ever have one of these devices, so having a predictable (essentially hard-coded) unique ID might be favorable as it would prevent adding multiple devices.
    • From what I can tell, this is not how the unique ID is intended to be used and it artificially limits multi-device installations so I don't like it...

Thoughts?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@joostlek This should be addressed now. Instead of using the IP/port combo I look up the MAC address of the device and use that to create the unique ID. Please have a look.

{
vol.Required(CONF_HOST): cv.string,
vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port,
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the name should not be asked as a parameter, only helpers are allowed to ask for a user defined name

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Noted, and fixed 👍

Comment on lines +223 to +229
self.data = BitvisData()
self.last_update_success = False
self.async_update_listeners()

async def _async_update_data(self) -> BitvisData:
"""Return current data (updates are push-based via UDP datagrams)."""
return self.data

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why does self.data reset?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The device pushes data every 1-10 seconds (depending on the energy meter its connected to). The idea with the watchdog was to catch cases where, for whatever reason, data is no longer received and make sure the data is marked as unavailable.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Still not resolved

Comment on lines +563 to +564
if self.coordinator.data.sample is None:
return None

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you can always add if TYPE_CHECKING: with an assert that it's not None

Comment thread homeassistant/components/bitvis/sensor.py

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 21 out of 24 changed files in this pull request and generated 1 comment.

Comment thread homeassistant/components/bitvis/__init__.py

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, we should keep diagnostics for a later PR to keep the initial PR smaller

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 21 out of 24 changed files in this pull request and generated 3 comments.

Comment thread tests/components/bitvis/test_coordinator.py Outdated
Comment thread homeassistant/components/bitvis/const.py
Comment thread homeassistant/components/bitvis/sensor.py
@MandusBorjesson

Copy link
Copy Markdown
Author

@joostlek @erwindouna checking in here. I've updated the PR and I believe all active threads should be resolved, with the exception of the ongoing UUID discussion that is awaiting your feedback.

Please have a look, we are eager to keep this moving :)

Comment on lines +32 to +34
if unload_ok := await hass.config_entries.async_unload_platforms(entry, _PLATFORMS):
await entry.runtime_data.async_stop()
return unload_ok

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if unload_ok := await hass.config_entries.async_unload_platforms(entry, _PLATFORMS):
await entry.runtime_data.async_stop()
return unload_ok
return await hass.config_entries.async_unload_platforms(entry, _PLATFORMS)

class BitvisConfigFlow(ConfigFlow, domain=DOMAIN):
"""Handle a config flow for Bitvis Power Hub."""

VERSION = 1

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
VERSION = 1

It will default to 1, for a new integration. Can be left out for now, until there are migrations in place. :)

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, I had no idea :)

Comment on lines +28 to +32

Skips the check when HA already owns a shared listener on this port (a
second device on the same port is valid — it will share the existing socket).
Raises OSError if the port is unavailable (e.g. already in use by another
process) or invalid.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
Skips the check when HA already owns a shared listener on this port (a
second device on the same port is validit will share the existing socket).
Raises OSError if the port is unavailable (e.g. already in use by another
process) or invalid.

)

assert result["type"] == FlowResultType.FORM
assert result["errors"] == {"base": "cannot_connect"}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should also test that we can recover from failure. So basically when a failure happened, restore it and demonstrate we can successfully create a new entry.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That makes sense, added tests for this and the zeroconf path.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing these for the general form, still. We need to be thorough. :)

)

assert result["type"] == FlowResultType.ABORT
assert result["reason"] == "cannot_connect"

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Demonstrate we can recover.

"""Test that confirming a zeroconf discovery creates a config entry."""
result = await hass.config_entries.flow.async_init(
DOMAIN,
context={"source": config_entries.SOURCE_ZEROCONF},

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
context={"source": config_entries.SOURCE_ZEROCONF},
context={"source": SOURCE_ZEROCONF},

async def test_user_form_cannot_connect(hass: HomeAssistant) -> None:
"""Test that a port binding failure surfaces a cannot_connect error."""
result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": config_entries.SOURCE_USER}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
DOMAIN, context={"source": config_entries.SOURCE_USER}
DOMAIN, context={"source": SOURCE_USER}

async def test_user_form_create_entry(hass: HomeAssistant) -> None:
"""Test creating an entry via user flow."""
result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": config_entries.SOURCE_USER}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use the direct constants, please.

Suggested change
DOMAIN, context={"source": config_entries.SOURCE_USER}
DOMAIN, context={"source": SOURCE_USER}

return dt_util.utcnow().replace(microsecond=0) - timedelta(seconds=uptime_s)


@dataclass

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe expand this with kw_only and frozen, if possible?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can easily do kw_only, but frozen is currently a bit difficult since the data is updated throughout the file.


self.async_update_listeners()

async def _async_watchdog(self) -> None:

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's the rationale of this and why not rely on what the data coordinator returns? Feel a bit double from what the data update coordinator responsibility is supposed to do, unless I completely misunderstood your intention here. :)

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since the integration is push-based, with data arriving asynchronously. Data may suddenly stop arriving (the device is rebooting, lost Wi-Fi connection, etc.) so the idea with the watchdog is to detect these cases and mark the data as unavailable until it starts appearing again.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Then why is the polling not disabled, according to: https://developers.home-assistant.io/docs/integration_fetching_data/#push-vs-poll? :)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I genuinely have mixed feelings of building a dedicated watchdog for this, although I understand what you're saying. Maybe another reviewer has a different opinion.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The push/poll error would be rookie error 😅 Quite a bit to get into with all the HA classes and helpers. I think I got it right now, please have a look.

I'm not opposed to removing the watchdog, maybe its a bit overkill and strange. Having the data just go static is fine by me.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 18 out of 21 changed files in this pull request and generated 1 comment.

Comment thread tests/components/bitvis/test_coordinator.py Outdated

self.async_update_listeners()

async def _async_watchdog(self) -> None:

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Then why is the polling not disabled, according to: https://developers.home-assistant.io/docs/integration_fetching_data/#push-vs-poll? :)


self.async_update_listeners()

async def _async_watchdog(self) -> None:

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I genuinely have mixed feelings of building a dedicated watchdog for this, although I understand what you're saying. Maybe another reviewer has a different opinion.

Comment thread tests/components/bitvis/test_sensor.py Outdated


@pytest.mark.usefixtures("init_integration")
async def test_sensor_platform_creates_all_entities(

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please look into how to use the snapshot platforms, for instance as it can be seen in the Portainer integration. This will drastically save you loads of assert writing and compact the test for a platform.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, that's really nice. Good point!

)

assert result["type"] == FlowResultType.FORM
assert result["errors"] == {"base": "cannot_connect"}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing these for the general form, still. We need to be thorough. :)

assert result["reason"] == "already_configured"


async def test_zeroconf_shows_confirm_form(hass: HomeAssistant) -> None:

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I doubt a bit of the effectiveness of this test, it's fine to test the confirm, but maybe merge it in test that will run through the flow successfully.

assert result["reason"] == "cannot_connect"


async def test_zeroconf_confirm_cannot_connect_recovery(hass: HomeAssistant) -> None:

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please use the cannot_connect as the base. Now we have two tests, one stopped the desired test flow and one going through the desire test flow. :)

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think I got what you meant, simplified this (and the user_form one) so that each test does both the normal and recovery path through parametrization.

@MandusBorjesson

Copy link
Copy Markdown
Author

@erwindouna I believe all of your comments are addressed now, please have a look. I tore out the watchdog we were discussing in its entirety as I am more concerned with getting this integrated than having it in place. :)

@github-actions

Copy link
Copy Markdown

Check requirements

Checked at commit 6e7a413.

⚠️ Some checks require attention — see the details below.

Package Old New No Advisories Not Yanked Repo Public CI Upload Release Pipeline Security PR Link Async Safe
bitvis-protobuf 0.2.6
📦 bitvis-protobuf: 0.2.6
  • No Advisories: ✅ No active advisories reported by PyPI for version 0.2.6.
  • Not Yanked: ✅ Version 0.2.6 is a live (non-yanked) release.
  • Repo Public: ❌ PyPI does not advertise a source repository URL.
  • CI Upload: ✅ Trusted Publisher attestation found (GitHub).
  • Release Pipeline: ✅ OIDC via Trusted Publisher attestation (GitHub); automated CI upload verified by PyPI.
  • Security: ❌ No source repository URL on PyPI — source cannot be inspected.
  • PR Link: ❌ PyPI does not advertise a source repository URL.
  • Async Safe: ❌ PyPI does not advertise a source repository URL.

Generated by Check requirements (AW) · sonnet46 · 16.1 AIC · ⌖ 4.15 AIC · ⊞ 9.2K ·

@github-actions

Copy link
Copy Markdown

Check requirements

Checked at commit 400a580.

⚠️ Some checks require attention — see the details below.

Package Old New No Advisories Not Yanked Repo Public CI Upload Release Pipeline Security PR Link Async Safe
bitvis-protobuf 0.2.6
📦 bitvis-protobuf: 0.2.6
  • No Advisories: ✅ No active advisories reported by PyPI for version 0.2.6.
  • Not Yanked: ✅ Version 0.2.6 is a live (non-yanked) release.
  • Repo Public: ❌ PyPI does not advertise a source repository URL.
  • CI Upload: ✅ Trusted Publisher attestation found (GitHub).\n- Release Pipeline: ✅ OIDC via Trusted Publisher attestation (GitHub); automated CI upload verified by PyPI.
  • Security: ❌ No source repository URL on PyPI — source cannot be inspected.
  • PR Link: ❌ PyPI does not advertise a source repository URL.
  • Async Safe: ❌ PyPI does not advertise a source repository URL.

Generated by Check requirements (AW) · sonnet46 · 16.1 AIC · ⌖ 6.98 AIC · ⊞ 9.2K ·

@github-actions

Copy link
Copy Markdown

Check requirements

Checked at commit 1b4ab60.

⚠️ Some checks require attention — see the details below.

Package Old New No Advisories Not Yanked Repo Public CI Upload Release Pipeline Security PR Link Async Safe
bitvis-protobuf 0.2.7 ☑️ ⚠️
📦 bitvis-protobuf: 0.2.7
  • No Advisories: ✅ No active advisories reported by PyPI for version 0.2.7.
  • Not Yanked: ✅ Version 0.2.7 is a live (non-yanked) release.
  • Repo Public: ✅ https://github.com/bitvis-io/bitvis-protobuf is publicly accessible.
  • CI Upload: ✅ Trusted Publisher attestation found (GitHub).
  • Release Pipeline: ✅ OIDC via Trusted Publisher attestation (GitHub); automated CI upload verified by PyPI.
  • Security: ☑️ Baseline scan found nothing obvious in pyproject.toml, src/bitvis_protobuf/__init__.py, src/bitvis_protobuf/listener.py, src/bitvis_protobuf/utils.py, src/bitvis_protobuf/parse.py. This is not a security review — only the cheap checks were run.
  • PR Link: ❌ PR description must link to the source repository at https://github.com/bitvis-io/bitvis-protobuf. A PyPI page link is not sufficient.
  • Async Safe: ⚠️ socket.socket(), setsockopt(), and sock.bind() are called synchronously inside async def start() in listener.py:65-75. These are purely local kernel syscalls with no network round-trip; this is a one-shot pre-loop setup (not on the request/polling hot path) and follows the standard asyncio pattern for handing a pre-configured socket to create_datagram_endpoint.

Generated by Check requirements (AW) · sonnet46 · 35.2 AIC · ⌖ 4.3 AIC · ⊞ 9.2K ·

@MandusBorjesson

Copy link
Copy Markdown
Author

@joostlek @erwindouna I hope the two of you have had a pleasant summer, I brought this PR up-to-date with the latest dev branch all your comments should have been addressed. Please have a look :)

data_schema = vol.Schema(
{
vol.Required(CONF_HOST): cv.string,
vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port,

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can someone change the port?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not on the device itself, it will always broadcast on the same port. I left the option here to allow instances where a user has set up port forwarding between networks and the port may not correspond to what the device uses. A niche case, perhaps. I am fine with removing it if you think it should go.

Comment on lines +34 to +36
async def _async_get_device_unique_id(hass: HomeAssistant, host: str) -> str:
"""Resolve *host* and look up a MAC address for the config entry unique ID."""
return await hass.async_add_executor_job(get_mac_address_for_host, host)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We now use getmac for fetching the mac address. We should not use getmac. Getmac historically has caused issues with storing the wrong mac address and return the mac address of a switch in between. So we should use a different thing for unique id, and if there's not, we should use self._async_abort_entries_match

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ooh, that's interesting! I removed it and went with _async_abort_entries_match since we don't really have anything to go with.

Integration bitvis:

  • [ERROR] [CONFIG_FLOW] Config flows that are discoverable need to set a unique ID

hassfest wouldn't allow me to leave the ID empty, so I set it to the domain for now. That's probably not ideal but seeing as these devices are usually connected to a home energy meter, a user is unlikely to have more than one device in their installation.

Comment on lines +77 to +105
BitvisSensorEntityDescription(
key="phase_voltage_l1",
translation_key="phase_voltage_l1",
device_class=SensorDeviceClass.VOLTAGE,
native_unit_of_measurement=UnitOfElectricPotential.VOLT,
state_class=SensorStateClass.MEASUREMENT,
suggested_display_precision=1,
value_fn=lambda data: (
data.phase_voltage_l1_v if data.HasField("phase_voltage_l1_v") else None
),
),
BitvisSensorEntityDescription(
key="phase_voltage_l2",
translation_key="phase_voltage_l2",
device_class=SensorDeviceClass.VOLTAGE,
native_unit_of_measurement=UnitOfElectricPotential.VOLT,
state_class=SensorStateClass.MEASUREMENT,
suggested_display_precision=1,
value_fn=lambda data: (
data.phase_voltage_l2_v if data.HasField("phase_voltage_l2_v") else None
),
),
BitvisSensorEntityDescription(
key="phase_voltage_l3",
translation_key="phase_voltage_l3",
device_class=SensorDeviceClass.VOLTAGE,
native_unit_of_measurement=UnitOfElectricPotential.VOLT,
state_class=SensorStateClass.MEASUREMENT,
suggested_display_precision=1,

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you can use translation placeholders to avoid having 3 seperate translation keys

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right, that's clever! I could also use it for a lot of the other keys so the translations file is a bit slimmer now.


UPTIME_DESCRIPTION = SensorEntityDescription(
key="uptime",
translation_key="uptime",

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you can omit the translation key here as it will use the device class transaltion

Comment on lines +531 to +532
if self.coordinator.data.diagnostic is None:
return None

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is also in available and when available is False, native_value won't be queried, so you can remove this check as it will never be False

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right, that makes sense!

@github-actions

Copy link
Copy Markdown

Check requirements

Checked at commit 49acd6d.

⚠️ Some checks require attention — see the details below.

Package Old New No Advisories Not Yanked Repo Public CI Upload Release Pipeline Security PR Link Async Safe
bitvis-protobuf 0.2.7 ☑️ ⚠️
📦 bitvis-protobuf: 0.2.7
  • No Advisories: ✅ No active advisories reported by PyPI for version 0.2.7.
  • Not Yanked: ✅ Version 0.2.7 is a live (non-yanked) release.
  • Repo Public: ✅ https://github.com/bitvis-io/bitvis-protobuf is publicly accessible.
  • CI Upload: ✅ Trusted Publisher attestation found (GitHub).
  • Release Pipeline: ✅ OIDC via Trusted Publisher attestation (GitHub); automated CI upload verified by PyPI.
  • Security: ☑️ Baseline scan found nothing obvious in pyproject.toml, __init__.py, listener.py, utils.py, cli.py. This is not a security review — only the cheap checks were run.
  • PR Link: ✅ PR description links to https://github.com/bitvis-io/bitvis-protobuf and the 0.2.7 release tag.
  • Async Safe: ⚠️ sock.bind(local_addr) (a sync socket call) is invoked inside async def start at listener.py:76. This is a one-shot pre-loop setup operation (not on the datagram polling hot path), and passing a pre-bound socket to loop.create_datagram_endpoint is the standard asyncio pattern — it is unlikely to stall the loop in practice.

Generated by Check requirements (AW) · sonnet46 · 31.1 AIC · ⌖ 5.21 AIC · ⊞ 9.2K ·

@github-actions

Copy link
Copy Markdown

Check requirements

Checked at commit 5d91281.

All requirements checks passed. ✅

Package Old New No Advisories Not Yanked Repo Public CI Upload Release Pipeline Security PR Link Async Safe
bitvis-protobuf 0.2.7 ☑️
📦 bitvis-protobuf: 0.2.7
  • No Advisories: ✅ No active advisories reported by PyPI for version 0.2.7.
  • Not Yanked: ✅ Version 0.2.7 is a live (non-yanked) release.
  • Repo Public: ✅ https://github.com/bitvis-io/bitvis-protobuf is publicly accessible.
  • CI Upload: ✅ Trusted Publisher attestation found (GitHub).
  • Release Pipeline: ✅ OIDC via Trusted Publisher attestation (GitHub); automated CI upload verified by PyPI.
  • Security: ☑️ Baseline scan found nothing obvious in pyproject.toml, src/bitvis_protobuf/__init__.py, src/bitvis_protobuf/listener.py, src/bitvis_protobuf/utils.py. This is not a security review — only the cheap checks were run.
  • PR Link: ✅ PR description links to the source repository at https://github.com/bitvis-io/bitvis-protobuf (including release tag 0.2.7).
  • Async Safe: ✅ No blocking calls on the hot path. async_resolve_host and async_verify_udp_port_bindable use loop.getaddrinfo and loop.create_datagram_endpoint respectively. SharedListener.start() uses the standard asyncio pattern of creating a UDP socket synchronously then handing it to the event loop — one-shot setup, not a hot-path concern.

Generated by Check requirements (AW) · sonnet46 · 30.2 AIC · ⌖ 7.04 AIC · ⊞ 9.2K ·

@github-actions

Copy link
Copy Markdown

Check requirements

Checked at commit e48da62.

All requirements checks passed. ✅

Package Old New No Advisories Not Yanked Repo Public CI Upload Release Pipeline Security PR Link Async Safe
bitvis-protobuf 0.2.7 ☑️
📦 bitvis-protobuf: 0.2.7
  • No Advisories: ✅ No active advisories reported by PyPI for version 0.2.7.
  • Not Yanked: ✅ Version 0.2.7 is a live (non-yanked) release.
  • Repo Public: ✅ https://github.com/bitvis-io/bitvis-protobuf is publicly accessible.
  • CI Upload: ✅ Trusted Publisher attestation found (GitHub).
  • Release Pipeline: ✅ OIDC via Trusted Publisher attestation (GitHub); automated CI upload verified by PyPI.
  • Security: ☑️ Baseline scan found nothing obvious in pyproject.toml, src/bitvis_protobuf/__init__.py, src/bitvis_protobuf/listener.py, src/bitvis_protobuf/utils.py. This is not a security review — only the cheap checks were run.
  • PR Link: ✅ PR description links to https://github.com/bitvis-io/bitvis-protobuf (source repository).
  • Async Safe: ✅ No blocking calls reachable from public async def on the request/polling path. async_resolve_host and async_verify_udp_port_bindable use asyncio APIs (loop.getaddrinfo, loop.create_datagram_endpoint); socket creation in listener.py:start is a non-blocking kernel syscall, standard asyncio pattern.

Generated by Check requirements (AW) · sonnet46 · 34.5 AIC · ⌖ 5.26 AIC · ⊞ 9.2K ·

@MandusBorjesson

Copy link
Copy Markdown
Author

@joostlek I implemented your suggested changes. I also rebased and put all of the changes in a separate commit (e48da62). Please have a look :)

@joostlek joostlek left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Feel free to send me a message on Discord btw

host = discovery_info.host

await self.async_set_unique_id(_SINGLE_INSTANCE_UNIQUE_ID)
self._abort_if_unique_id_configured()

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So instead of setting this, you can set "single_config_entry": true in the manifest. Is the reason we can't have multiple entries because of UDP?

BitvisSensorEntityDescription(
key="power_active_l1_delivered_by_client",
translation_key="power_active_phase",
translation_placeholders={"direction": "export", "phase": "L1"},

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay so 2 things:

  1. I believe different countries have different ways to describe phases other than L, so let's make only the number a placeholder
  2. Now export is hardcoded, so we can't translate that anymore, while its something we want to translate

suggested_display_precision=2,
value_fn=lambda data: (
data.energy_active_delivered_by_client_kwh
if data.HasField("energy_active_delivered_by_client_kwh")

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do all devices have all fields? As in, are there devices that just have a subset of fields?

@MandusBorjesson

Copy link
Copy Markdown
Author

@joostlek I'm about to head out but I'll send you a ping on discord after the weekend, I appreciate you taking the time to keep on reviewing this!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants