Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add husqvarna automower ble integration #108326

Draft
wants to merge 1 commit into
base: dev
Choose a base branch
from

Conversation

alistair23
Copy link
Contributor

@alistair23 alistair23 commented Jan 18, 2024

Proposed change

This PR adds support for the Husqvarna Automower BLE integration.

The Husqvarna Automower BLE integration provides connectivity with Husqvarna Automowers lawn mowers via a local Bluetooth connection. This allows connecting and controlling an Automower without any accounts, cloud or network connection.

The integration is based on AutoMower-BLE, a unofficial reverse engineered Husqvarna Automower Connect BLE library.

The integration has been tested against a 305 Automower using a ESPHome Bluetooth proxy.

This is somewhat similar to #100569, except #100569 uses the cloud API while this integration uses the local BLE. Not all mowers support BLE and not all mowers support the cloud, so although there is overlap both integrations support different mowers.

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

  • 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.

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

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

  • The manifest file has all fields filled out correctly.
    Updated and included derived files by running: python3 -m script.hassfest.
  • New or updated dependencies have been added to requirements_all.txt.
    Updated by running python3 -m script.gen_requirements_all.
  • For the updated dependencies - a link to the changelog, or at minimum a diff between library versions is added to the PR description.
  • Untested files have been added to .coveragerc.

To help with the load of incoming pull requests:

Copy link

@home-assistant home-assistant bot left a comment

Choose a reason for hiding this comment

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

There is a merge conflict.

@home-assistant
Copy link

home-assistant bot commented Feb 1, 2024

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 marked this pull request as draft February 1, 2024 22:25
@alistair23 alistair23 force-pushed the alistair/husqvarna_automower_ble branch from b6a7da4 to 07c766e Compare February 1, 2024 22:38
@alistair23 alistair23 marked this pull request as ready for review February 1, 2024 22:45
@alistair23 alistair23 force-pushed the alistair/husqvarna_automower_ble branch from 07c766e to 3cd6a8e Compare February 4, 2024 22:29
@alistair23
Copy link
Contributor Author

@joostlek and @Thomas55555 maybe you can help get some traction on this

@joostlek
Copy link
Member

Never really worked a lot with bluetooth so I can't help you on that part

@Thomas55555
Copy link
Contributor

I'm also not, and I'm self starting to learn here. I just gave you some general review.

@alistair23 alistair23 force-pushed the alistair/husqvarna_automower_ble branch 2 times, most recently from bb611ce to 6b1c752 Compare February 13, 2024 11:34
@alistair23 alistair23 force-pushed the alistair/husqvarna_automower_ble branch from 6b1c752 to a54d106 Compare February 26, 2024 12:24
@alistair23 alistair23 force-pushed the alistair/husqvarna_automower_ble branch from a54d106 to c3699fc Compare March 13, 2024 11:53
@alistair23 alistair23 force-pushed the alistair/husqvarna_automower_ble branch 3 times, most recently from 034267c to 311df40 Compare March 25, 2024 11:56
@alistair23 alistair23 force-pushed the alistair/husqvarna_automower_ble branch from fe9ca25 to 5a18fe5 Compare May 17, 2024 12:42
@mkmer
Copy link
Contributor

mkmer commented May 17, 2024

I made quite a few changes to a local version trying to clean up the flow and startup. It's still not perfect, and I'm not sure i like how I've set the devices up with "current" values rather than "stored" values, but I think you may find it helpful:
https://github.com/mkmer/HomeAssistant_Core/tree/ble-Husq/homeassistant/components/husqvarna_automower_ble
Pay close attention to the __init__.py execution order - there is a specific order to starting the coordinator to make sure data is available when the entities start up.

@alistair23 alistair23 force-pushed the alistair/husqvarna_automower_ble branch 11 times, most recently from 7b1e851 to 3ef9a6d Compare May 20, 2024 00:32
Signed-off-by: Alistair Francis <alistair@alistair23.me>
@alistair23 alistair23 force-pushed the alistair/husqvarna_automower_ble branch from 7c7ebf7 to e2a9817 Compare May 20, 2024 11:01
@alistair23 alistair23 marked this pull request as ready for review May 20, 2024 11:23
@alistair23 alistair23 requested a review from elupus as a code owner May 20, 2024 11:23
@alistair23
Copy link
Contributor Author

Tests are passing and comments are addressed

@wtaferner
Copy link

Happy to see this PR and important feature to be added. Thx @alistair23 for the quite huge effort.

@emontnemery
Any chance that it might make it to the June release?

@emontnemery
Copy link
Contributor

comments are addressed

What about this comment though: #108326 (comment), users should not have to reload the integration to make it work. What happens if the automower is out of bluetooth range?

@mkmer
Copy link
Contributor

mkmer commented May 30, 2024

comments are addressed

What about this comment though: #108326 (comment), users should not have to reload the integration to make it work. What happens if the automower is out of bluetooth range?

I don't think it's possible to configure the integration if the automower is out of range (it won't / can't be discovered).
I believe we fixed the issue when it's already configured and out of range at startup, it automatically retries by raising ConfigEntryNotReady.

Comment on lines +38 to +44
assert result == snapshot

result = await hass.config_entries.flow.async_configure(
result["flow_id"],
user_input={"address": "00000000-0000-0000-0000-000000000001"},
)
assert result == snapshot
Copy link
Member

Choose a reason for hiding this comment

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

I dislike the use of snapshots here as you "hide" what you are actually testing

await hass.async_block_till_done(wait_background_tasks=True)

result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": config_entries.SOURCE_USER}
Copy link
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}

Personal ick

LOGGER.debug("connected and paired")

model = await mower.get_model()
LOGGER.info("Connected to Automower: %s", model)
Copy link
Member

Choose a reason for hiding this comment

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

Please don't log on info level

def __init__(
self,
hass: HomeAssistant,
logger: logging.Logger,
Copy link
Member

Choose a reason for hiding this comment

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

Why are you passing in a logger if you made one on line 24?

If you like, you can also create an integration level logger with LOGGER = logging.getLogger(__package__) in const.py

return data


class HusqvarnaAutomowerBleEntity(CoordinatorEntity[HusqvarnaCoordinator]):
Copy link
Member

Choose a reason for hiding this comment

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

Please move this to entity.py

Comment on lines +53 to +54
unique_id: str,
name: str,
Copy link
Member

Choose a reason for hiding this comment

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

Why don't we just pass in what we need and let the entity decide

) -> None:
"""Initialize the lawn mower."""
super().__init__(coordinator)
self._attr_name = name
Copy link
Member

Choose a reason for hiding this comment

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

The lawn mower is the main feature of the device, so the name of the lawn mower should be set to _attr_name = None. This way, the name of this entity is decided by the name of the device, which is consistent

[
AutomowerLawnMower(
coordinator,
"automower" + str(model) + "_" + str(address),
Copy link
Member

Choose a reason for hiding this comment

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

Also, for unique_id, address should be purely unique I assume? Reminder that unique_id is unique per platform per integration, so there's no need to prepend it with automower (if that's what you were trying to avoid)

Comment on lines +72 to +76
if state is None:
return None

if activity is None:
return None
Copy link
Member

Choose a reason for hiding this comment

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

can be combined

return

await self.coordinator.mower.mower_resume()
if self._attr_activity == LawnMowerActivity.DOCKED:
Copy link
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._attr_activity == LawnMowerActivity.DOCKED:
if self._attr_activity is LawnMowerActivity.DOCKED:

@home-assistant home-assistant bot marked this pull request as draft June 5, 2024 09:49
channel_id = random.randint(1, 0xFFFFFFFF)

try:
(manufacture, device_type, model) = await Mower(
Copy link
Contributor

@mkmer mkmer Jun 6, 2024

Choose a reason for hiding this comment

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

This call appears to return a bool

  File "/config/custom_components/husqvarna_automower_ble/config_flow.py", line 117, in async_step_user
    return await self.async_step_confirm()
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/config/custom_components/husqvarna_automower_ble/config_flow.py", line 81, in async_step_confirm
    (manufacture, device_type, model) = await Mower(
    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
TypeError: cannot unpack non-iterable bool object

https://github.com/alistair23/AutoMower-BLE/blob/4ff003166d2b7cfaa4f9f8adacc9d1dfd9accbe7/automower_ble/protocol.py#L459

@al31c0
Copy link

al31c0 commented Jun 28, 2024

I have been following this integration for a long time. Is it possible to have it in the release HA 2024.7?
@elupus @emontnemery

@wtaferner
Copy link

@al31c0
I guess there are some pending adaptations needed by @alistair23, but he seems busy or at least nothing moved lately here. I am also looking forward to seeing this one merged, but I guess as long as you do not contribute or be able to move it forward yourself, we can just cheer, be thankful and patient for things to happen at some point 🙏🏼

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.

None yet

8 participants