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 flic smart button component #4681

Merged
merged 15 commits into from
Dec 6, 2016
Merged

Add flic smart button component #4681

merged 15 commits into from
Dec 6, 2016

Conversation

soldag
Copy link
Contributor

@soldag soldag commented Dec 3, 2016

Description:
This component let users use flic buttons to trigger automations.

Related issue (if applicable): fixes #

Pull request in home-assistant.github.io with documentation (if applicable): home-assistant/home-assistant.io#1527

Example entry for configuration.yaml (if applicable):

binary_sensor:
  - platform: flic

Checklist:

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

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

  • Local tests with tox run successfully. Your PR cannot be merged unless tests pass
  • New dependencies have been added to the REQUIREMENTS variable (example).
  • New dependencies are only imported inside functions that use them (example).
  • New dependencies have been added to requirements_all.txt by running script/gen_requirements_all.py.
  • New files were added to .coveragerc.

If the code does not interact with devices:

  • Local tests with tox run successfully. Your PR cannot be merged unless tests pass
  • Tests have been added to verify that the new code works.

@mention-bot
Copy link

@soldag, thanks for your PR! By analyzing the history of the files in this pull request, we identified @balloob, @fabaff and @robbiet480 to be potential reviewers.

Copy link
Member

@balloob balloob left a comment

Choose a reason for hiding this comment

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

Alright, so this looks pretty good.

I couldn't follow all the code. It looks like it is callback based so that's good. Please ensure that you're not doing any I/O inside the a coroutine.


def new_button_callback(address):
"""Setup newly verified button as device in home assistant."""
hass.loop.create_task(async_setup_button(hass, config,
Copy link
Member

Choose a reason for hiding this comment

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

Please use hass.add_job

import pyflic

# Get event loop
loop = asyncio.get_event_loop()
Copy link
Member

Choose a reason for hiding this comment

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

Preferred to use

loop = hass.loop

if not was_queued:
# Set state
self._is_down = click_type == pyflic.ClickType.ButtonDown
self.update_ha_state()
Copy link
Member

Choose a reason for hiding this comment

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

Please use self.schedule_update_ha_state()


if self._is_down:
self._last_down = now
else:
Copy link
Member

Choose a reason for hiding this comment

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

Instead of this else, include a return in the if statement above and you don't have to indent the other code that much

"""Fire click event depending on click type."""
import pyflic

if not was_queued:
Copy link
Member

Choose a reason for hiding this comment

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

Prefer to use a guard clause

if was_queued:
    return

# Set state

if result == pyflic.ScanWizardResult.WizardSuccess:
_LOGGER.info("Found new button (%s)", address)
elif result != pyflic.ScanWizardResult.WizardFailedTimeout:
_LOGGER.info("Failed to connect to button (%s). Reason: %s",
Copy link
Member

Choose a reason for hiding this comment

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

Should this be _LOGGER.warning ?

if auto_scan:
scan_client = pyflic.FlicClient(host, port)
start_scanning(hass, config, async_add_entities, scan_client)
loop.run_in_executor(None, scan_client.handle_events)
Copy link
Member

Choose a reason for hiding this comment

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

Will this call ever finish ? Or will it occupy an executor thread forever?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Same as above.


main_client.on_new_verified_button = new_button_callback
hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, main_client.close)
loop.run_in_executor(None, main_client.handle_events)
Copy link
Member

Choose a reason for hiding this comment

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

Will this call ever finish ? Or will it occupy an executor thread forever ?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

No, FlicClient.handle_events() has to run the entire time in background. It is a main loop of the pyflic lib that never exits, unless the socket to the flic service is closed.

@balloob balloob self-assigned this Dec 4, 2016
@soldag
Copy link
Contributor Author

soldag commented Dec 4, 2016

@balloob Thanks :)

Yes, the library is callback based. The only method that is doing I/O is FlicClient.handle_events()as already mentioned in the comments.

@fabaff fabaff mentioned this pull request Dec 5, 2016
6 tasks
@emlove
Copy link
Contributor

emlove commented Dec 5, 2016

Related question here, mostly for @balloob or other maintainers. Does it make sense for hass to have a stateless button component instead of using the binary_sensor component? In this case we're setting the state based on when the button is down, but it's only a momentary change. The useful feature exposed by this platform is the click events. Looking forward to other platforms like Amazon dash, we may not get this state information, and only get a single trigger.

Use library method to determine click type. Merge three click events into single one with click_type parameter.
@soldag
Copy link
Contributor Author

soldag commented Dec 5, 2016

@armills Thanks for your work, even if I was a little bit faster 😜
Anyway, I had a look at your commit and updated my component. I didn't realize that there is a on_button_single_or_double_click_or_hold method in fliclib and had built it myself, but thanks to you it is now used. 😆

@emlove
Copy link
Contributor

emlove commented Dec 5, 2016

@soldag Looks great! Yeah, they don't show it in their examples. I just stumbled across it when looking through the library. At least it wasn't for nothing!

Can you take a look at my other two notes? They're just some smaller questions I had.

@soldag
Copy link
Contributor Author

soldag commented Dec 5, 2016

@armills Where did you take this notes? I couldn't find them.

@emlove
Copy link
Contributor

emlove commented Dec 5, 2016

@soldag
Copy link
Contributor Author

soldag commented Dec 5, 2016

Yeah, I thought so, but there is nothing but the code.

@emlove
Copy link
Contributor

emlove commented Dec 5, 2016

OK, thanks Github. I'll just put them here.

  1. Instead of CONF_AUTO_SCAN, does it make sense to use CONF_DISCOVERY from homeassistant.const, just so we're using existing conventions?

  2. In the auto scan logic, can you just pass the main_client to start_scanning instead of creating a new client?

EDIT: Also, just noting here that this PR works on my system.

@soldag
Copy link
Contributor Author

soldag commented Dec 5, 2016

These suggestions make totally sense. I updated the code 😉

PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Optional(CONF_HOST, default='localhost'): cv.string,
vol.Optional(CONF_PORT, default=5551): cv.port,
vol.Optional(CONF_AUTO_SCAN, default=True): cv.boolean,
Copy link
Contributor

Choose a reason for hiding this comment

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

Does it make sense to use CONF_DISCOVERY from homeassistant.const here?

auto_scan = config.get(CONF_AUTO_SCAN)
if auto_scan:
scan_client = pyflic.FlicClient(host, port)
start_scanning(hass, config, async_add_entities, scan_client)
Copy link
Contributor

Choose a reason for hiding this comment

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

Why are you creating a new FlicClient here instead of passing the main_client?


# Initialize connection channel
self._channel = pyflic.ButtonConnectionChannel(self._address)
self._channel.on_button_up_or_down = self._button_up_down
Copy link
Contributor

Choose a reason for hiding this comment

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

A possible alternative here is to make use of on_button_single_or_double_click_or_hold to fire button click events, and only use on_button_up_or_down for state. That way we're making better use of the upstream logic rather than re-implementing it.

@balloob
Copy link
Member

balloob commented Dec 6, 2016

@armills I think that a button component would be overkill here as there is no generic interface to abstract out, we're just firing events. Things like this should probably just be their own component (we can still categorize them as button on the HA site to help people discover them). Then again, I don't mind it being a binary sensor either.

For now I will merge this PR as it all works.

@balloob balloob merged commit 81d38c3 into home-assistant:dev Dec 6, 2016
@emlove
Copy link
Contributor

emlove commented Dec 6, 2016

@balloob OK, makes sense. Thanks for the reply!

@fabianbergmark
Copy link

You should also use the other click callbacks, i.e. on_button_click_or_hold and on_button_single_or_double_click. Also, queued events might be of interest to some users so I suggest not discarding them and instead include the time_diff in the event data.

@soldag
Copy link
Contributor Author

soldag commented Dec 8, 2016

@fabianbergmark What's the use of the other click callbacks? I thought the others are used to only listen to specific events and as I want to listen to all types of click events, on_button_single_or_double_click_or_hold is the only one I need.

The point about queued events makes sense and I will update the component code.

@fabianbergmark
Copy link

The logic behind the click callbacks is pretty smart, but not very well explained. Say you are not interested in double clicks but only click and hold. If you click the button twice fast, the on_button_click_or_hold would fire twice with a click event, while the on_button_single_or_double_click_or_hold would only fire once with a double click event. This is very useful for applications where you want to click fast but still have a secondary hold trigger.

@emlove
Copy link
Contributor

emlove commented Dec 8, 2016

I opened a PR to support queued events here: #4822

@soldag soldag deleted the flic branch December 8, 2016 23:38
@soldag
Copy link
Contributor Author

soldag commented Dec 9, 2016

@fabianbergmark Okay, now I understand 👍
I am now making use of this by giving the user the possibility to ignore individual click types. Depending on this selection the appropriate callback is registered. See PR #4827 .

@AntonMeier
Copy link

One last note on the click topic is that if you discard the double click event then you will get lower latency on the click event since the "logic" does not have to wait for a possible second click in a double click sequence.

@Emill
Copy link

Emill commented Jan 23, 2017

Hi. I don't think it should be default to constantly scan in the background.
That means someone can stand outside the window and easily connect a button into your home automation system...
A less important point is that scanning takes up more radio time which could increase latency for other Flics as well as lowering the WiFi throughput, as well as consuming more power.

@emlove
Copy link
Contributor

emlove commented Jan 23, 2017

You can disable the scan by setting discovery: false in your config. It is enabled by default because at least one button needs to be discovered.

As far as security concerns go, barring an unknown vulnerability in flicd, the worst an attacker could do is a DOS, which they can probably do anyway by saturating the Bluetooth frequency.

The scanning for new buttons is passive. The daemon is listening for advertisement packets from the buttons. It isn't actively polling the network, so there shouldn't be an impact on radio usage. The only change when turning off discovery is that the daemon ignores advertisements from unknown buttons.

@Emill
Copy link

Emill commented Jan 23, 2017

Yes it can be disabled, but I don't want it to be default true since most users will just leave with the default settings. I would rather have some default mode which scans only while there is currently no button paired with the daemon.

I don't know if you got my point with the security problem. If you always run the scan wizard that means that anyone can stand outside the window and pair Flic buttons so they are now trusted by the daemon without the user noticing anything. If you open the Bluetooth settings on your smartphone and you spontaneously get a popup asking if it's OK to pair with some random device you've never seen before, you usually press NO, right?

If you run a Raspberry Pi 3, then the WiFi chip and Bluetooth chip is the same thus it also shares the same radio, which can only do one thing at a time. When you run a Bluetooth scan, you reserve radio usage in certain time frames which blocks out all other traffic during that time window. Probably most users won't notice anything but I did that test once on a smartphone and the network speed indeed became slower when a Bluetooth scan was active.

@Emill
Copy link

Emill commented Jan 24, 2017

You must also check the status code of when the wizard completes. If the bluetooth dongle is currently unplugged, it will complete immediately with status WizardBluetoothUnavailable. With this current implementation that leads to an infinite busy loop. Instead, if you get WizardBluetoothUnavailable you should not try again but rather upon receiving EvtBluetoothControllerStateChange with state = Attached, then it is ok to try again.

@emlove
Copy link
Contributor

emlove commented Jan 24, 2017

That's a good point. Can you open two issues on GitHub for these comments so they can be discussed in a clean thread? You could also just open a PR for the bluetooth unavailable if you want to write it up and test it.

@home-assistant home-assistant locked and limited conversation to collaborators Apr 30, 2017
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

7 participants