Skip to content

Add ws66i core integration#56094

Merged
bdraco merged 14 commits intohome-assistant:devfrom
ssaenger:ws66i
May 8, 2022
Merged

Add ws66i core integration#56094
bdraco merged 14 commits intohome-assistant:devfrom
ssaenger:ws66i

Conversation

@ssaenger
Copy link
Copy Markdown
Contributor

@ssaenger ssaenger commented Sep 11, 2021

Proposed change

I've added a new core integration called ws66i. This is based off of the https://www.home-assistant.io/integrations/monoprice integration but uses an updated amplifier that enables control via a different protocol (Telnet). Created and added new dependency https://pypi.org/project/pyws66i/ to manage the device interface.

WS66i product page: https://www.soundavo.com/products/ws-66i

This is my first time working with Python. I'm a Bluetooth Software Engineer and I program in C mainly.

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)
  • 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
  • The code has been formatted using Black (black --fast 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. NOT SURE WHAT IT IS ASKING
  • Untested files have been added to .coveragerc. ALL MY FILES HAVE BEEN TESTED

The integration reached or maintains the following Integration Quality Scale:

  • No score or internal
  • 🥈 Silver
  • 🥇 Gold
  • 🏆 Platinum

To help with the load of incoming pull requests:

@ssaenger
Copy link
Copy Markdown
Contributor Author

@etsinko, @OnFreund, can either of you take a look at my PR?

@OnFreund
Copy link
Copy Markdown
Contributor

Looks like a cool device (super expensive though).
It's going to take me a while to go over this, and I wouldn't want to be a bottleneck, but I will start with one comment - translations happen automatically, you shouldn't commit the language files.

@ssaenger
Copy link
Copy Markdown
Contributor Author

I wasn't sure about translations; I thought they were created manually. I included them because it is the same as the monoprice integration, except I swapped "port" with "ip address". Those will have to be checked... Or I just remove them altogether.

@OnFreund
Copy link
Copy Markdown
Contributor

OnFreund commented Sep 30, 2021

You should remove all the translation files (but keep strings.json)

@ssaenger
Copy link
Copy Markdown
Contributor Author

ssaenger commented Dec 5, 2021

Can we run automated tests to verify my PR meets all requirements?

@ssaenger
Copy link
Copy Markdown
Contributor Author

Thanks for the review @bdraco. I pushed changes based on it.

@bdraco bdraco self-requested a review December 16, 2021 01:07
@bdraco
Copy link
Copy Markdown
Member

bdraco commented Mar 21, 2022

I tried making the restore service more robust by remembering what the snapshot status was between HA restarts, however the data in hass.data is wiped out between restarts. Is there any place to store data persistently for integrations?

I suggest removing the services for now and working through them in a followup PR.

https://developers.home-assistant.io/docs/creating_component_code_review?_highlight=small&_highlight=pos#5-make-your-pull-request-as-small-as-possible

@ssaenger
Copy link
Copy Markdown
Contributor Author

Sorry, I was getting ahead of myself. I was trying to continue to make improvements during a PR and that just makes it more difficult for you, the reviewer.

About custom services, these services should stay however; they are vital components to the integration. A basic automation will, for example, listen for an event, such as a doorbell rang event, snapshot the zones, play a notification indicating someone is at the door, and then restore the zones. In this use case, the snapshot will no longer be needed because a new snapshot will be taken whenever a notification needs to be played. Come to think of it, snapshots don't need to be in non-volatile memory at all.

I won't be making any more changes. In a future PR, I'll look into discovery. I hope this is acceptable.

@ssaenger
Copy link
Copy Markdown
Contributor Author

ssaenger commented Apr 5, 2022

Updated to use pyws66i v1.1.

Reviewed the quality scale and upgraded my integration to Silver.

@bdraco
Copy link
Copy Markdown
Member

bdraco commented Apr 5, 2022

LGTM once #56094 (comment) is addressed

@bdraco bdraco merged commit 5e737bf into home-assistant:dev May 8, 2022
@ssaenger ssaenger deleted the ws66i branch May 9, 2022 04:59
Copy link
Copy Markdown
Member

@MartinHjelmare MartinHjelmare left a comment

Choose a reason for hiding this comment

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

Please address the comments in a new PR. Thanks!

return unload_ok


async def _update_listener(hass: HomeAssistant, entry: ConfigEntry):
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 return value typing. Please type the whole signature when adding type annotations.

}


async def validate_input(hass: core.HomeAssistant, input_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.

Please add type annotations to the whole signature.

await hass.async_add_executor_job(ws66i.open)
# No exception. run a simple test to make sure we opened correct port
# Test on FIRST_ZONE because this zone will always be valid
ret_val = await hass.async_add_executor_job(ws66i.zone_status, FIRST_ZONE)
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 combine multiple calls that need to run in the executor into one function that we schedule once on the executor. Switching context is expensive.

zones=zones,
)

def shutdown(event):
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.

If close is async safe we should decorate the callback with @callback.

try:
info = await validate_input(self.hass, user_input)
# Data is valid. Add default values for options flow.
return self.async_create_entry(
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 only wrap the line that can raise in the try... except block. We can use an else: block if needed.

"unknown": "[%key:common::config_flow::error::unknown%]"
},
"abort": {
"already_configured": "[%key:common::config_flow::abort::already_configured_device%]"
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 abort reason is never used. We don't abort for any reason currently in the config flow.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Do I update the translation file as well?

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 run the translations develop script if you want to test the flow manually. It's not needed to update the translation files for merging a PR. Our infrastructure will update the translation files separately by fetching from Lokalise.


async def test_form(hass):
"""Test we get the form."""
await setup.async_setup_component(hass, "persistent_notification", {})
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 need to set up the persistent_notification integration anymore.

await hass.services.async_call(DOMAIN, name, service_data=data, blocking=True)


async def test_cannot_connect(hass):
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 test should move to a test_init.py module.

hass, SERVICE_SELECT_SOURCE, {"entity_id": ZONE_1_ID, "source": "three"}
)

# await coordinator.async_refresh()
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.

Commented code. Please remove it.

hass, SERVICE_SELECT_SOURCE, {"entity_id": ZONE_1_ID, "source": "one"}
)

ws66i_data = hass.data[DOMAIN][config_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.

Please don't access integration details like hass.data or the coordinator in the test. Move time forward to make the coordinator refresh data.

core/tests/common.py

Lines 375 to 379 in f50681e

@ha.callback
def async_fire_time_changed(
hass: HomeAssistant, datetime_: datetime = None, fire_all: bool = False
) -> None:
"""Fire a time changed event."""

https://developers.home-assistant.io/docs/development_testing#writing-tests-for-integrations

Comment on lines +71 to +81
platform.async_register_entity_service(
SERVICE_SNAPSHOT,
{},
"snapshot",
)

platform.async_register_entity_service(
SERVICE_RESTORE,
{},
"async_restore",
)
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.

Came here from the Docs PR: Why does this integration provide its own logic for handling creating snapshot/restoring of entity state?

We have scenes in Home Assistant providing that functionality.

Copy link
Copy Markdown
Contributor Author

@ssaenger ssaenger May 10, 2022

Choose a reason for hiding this comment

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

I have these two services because I modeled this integration after the monoprice integration.

I was just playing with scenes after you mentioned this. I couldn't see a way for the scene to save the state of the entity at the time of an automation being triggered. A use case of the snapshots/restore is:

  1. User is listening to background music in the kitchen on source 3, volume 10
  2. Doorbell is rung
  3. An automation is triggered
    a. The snapshot service is called to save the state
    b. A scene is loaded, which sets the source to 4, volume to 20
    c. A media file is played ("ding-dong" notification sound)
    d. The restore service is called to revert state back to source 3, volume 10

The user is notified that someone is at the door, and the background music resumes. Can this behavior be replicated without these services?

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.

That is exactly the services scene provides and was created for. Scenes allow you to capture states and restore states (it actually uses state restore under the hood).

For example, you could create a snapshot of a speaker, before doing anything:

- service: scene.create
  data:
    scene_id: snapshot_of_sonos_office
    snapshot_entities:
      - media_player.sonos_office

Next, do the stuff one wants... and after that sequence is done, restore the previous snapped scene:

- service: scene.turn_on
  target:
    entity_id: scene.snapshot_of_sonos_office

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Perfect! Thanks for the thorough response. Those custom services are not needed. I'll remove them and add that change to the PR that @MartinHjelmare is requesting.

@ssaenger
Copy link
Copy Markdown
Contributor Author

@MartinHjelmare , yours and Franck's requested changes are in PR #71717

@github-actions github-actions bot locked and limited conversation to collaborators May 13, 2022
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants