50ButtonsEach / fliclib-linux-hci

Flic SDK for Linux
305 stars 55 forks source link

How to handle events #6

Closed RobZe89 closed 7 years ago

RobZe89 commented 8 years ago

Dear team

Glad to see, that there is a new linux release for the flic button. I'm using the old fliclib quite a long time with the known problems. My problem now is that I'm not as good as many of you. I want to migrate to the new version, but I didn't get to find out the way to handle the events coming from the flic button. Maybe some of you can show / write a sample for the 3 types of "actions", short, double, long click on a flic button. Like in this comment for the old fliclib.

It could be in python or nodejs, doesn't matter.

Best Regards Rob

Emill commented 8 years ago

Hi.

As a reference, look at https://github.com/50ButtonsEach/fliclib-linux-hci/blob/master/clientlib/nodejs/example.js.

cc.on("buttonUpOrDown", function(clickType, wasQueued, timeDiff) {
    console.log(bdAddr + " " + clickType + " " + (wasQueued ? "wasQueued" : "notQueued") + " " + timeDiff + " seconds ago");
});

You can change that to

cc.on("buttonSingleOrDoubleClickOrHold", function(clickType, wasQueued, timeDiff) {
    console.log(bdAddr + " " + clickType + " " + (wasQueued ? "wasQueued" : "notQueued") + " " + timeDiff + " seconds ago");
});

to instead listen to single click / double click / hold.

write an if-statement for the clickType parameter. It can have three different values: "ButtonSingleClick", "ButtonDoubleClick" and "ButtonHold".

RobZe89 commented 8 years ago

Hello

Thank you very much for your help Emill. I worked it out and it is now working since a week without any issues. So happy to share my bit of code, maybe it will be useful for someone else.

You have to change the bdAddr to your flic button bluetooth adress. And decide which action will be taken on a single, double or long click. I have 2 buttons and both have the same action on a long click. I use some php script to do the action (for example turn on/off light with openhab or start / stop the music with my Synology NAS and the Audiostation).

function listenToButton(bdAddr) {
        var cc = new FlicConnectionChannel(bdAddr);
        client.addConnectionChannel(cc);
        cc.on("buttonSingleOrDoubleClickOrHold", function(clickType, wasQueued, timeDiff) {
                console.log(bdAddr + " " + clickType + " " + (wasQueued ? "wasQueued" : "notQueued") + " " + timeDiff + " seconds ago");
                if (clickType == 'ButtonSingleClick' && bdAddr == '80:e4:da:YY:XX:ZZ'){
                        request("http://10.x.x.xx/flic/xxx.php", function(error, response, body) {
                                console.log(body);
                        });
                }else if (clickType == 'ButtonDoubleClick' && bdAddr == '80:e4:da:YY:XX:ZZ'){
                        request("http://10.x.x.xx/flic/xxx.php", function(error, response, body) {
                                console.log(body);
                        });
                }else if (clickType == 'ButtonSingleClick' && bdAddr == '80:e4:da:11:22:33'){
                        request("http://10.x.x.xx/flic/xxx.php", function(error, response, body) {
                                console.log(body);
                        });
                }else if (clickType == 'ButtonDoubleClick' && bdAddr == '80:e4:da:11:22:33'){ 
                        request("http://10.x.x.xx/flic/xxx.php", function(error, response, body) {
                                console.log(body);
                        });
                }else{
                        //Longclick
                        request("http://10.x.x.xx/flic/xxx.php", function(error, response, body) {
                                console.log(body);
                        });
                }
        });
        cc.on("connectionStatusChanged", function(connectionStatus, disconnectReason) {
                console.log(bdAddr + " " + connectionStatus + (connectionStatus == "Disconnected" ? " " + disconnectReason : ""));
        });
}

Of course you can define other actions or add more buttons. If you want use the request function it should be included in the begining of the file with

var request = require('request');
DIYtechie commented 7 years ago

Hi all

I am very new to programming, but really want to use my Raspberry Pi 3 with flic. So far I have managed to connect a flic button and run the test_client.py which shows button up or down. I also found Python code on GutHub to have my Pi control WeMo, Hue and Sonos and can confirm that it works.

I would like to write a script similar to RobZe89's above, but I spent all day yesterday getting almost nowhere. I read the Fliclib socket protocol documentation, the Flic client library for python and looked at RobZe89's script, but was not able to make a Python version.

I was able to modify the test_client.py to the following script which pauses my Sonos speaker when a button is pressed:

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

from soco import SoCo

if __name__ == '__main__':
    sonos = SoCo('192.168.1.74')

import fliclib

client = fliclib.FlicClient("localhost")

def got_button(bd_addr):
    cc = fliclib.ButtonConnectionChannel(bd_addr)
    cc.on_button_single_or_double_click_or_hold = \
        lambda channel, click_type, was_queued, time_diff: \
            sonos.pause()
    cc.on_connection_status_changed = \
        lambda channel, connection_status, disconnect_reason: \
            print(channel.bd_addr + " " + str(connection_status) + (" " + str(disconnect_reason) if connection_status == fliclib.ConnectionStatus.Disconnected else ""))
    client.add_connection_channel(cc)

def got_info(items):
    print(items)
    for bd_addr in items["bd_addr_of_verified_buttons"]:
        got_button(bd_addr)

client.get_info(got_info)

client.on_new_verified_button = got_button

client.handle_events()

But I know that this is not the proper way to do it and it does not trigger different actions for different buttons and different clicktypes like RobZe89's above. Also I don't know how to add multiple actions to one event.

Can anyone help me write a Python script that does this?

I believe that other newbies would find this helpful as well :)

Emill commented 7 years ago

Instead of using lambda functions (which is ok for a short function), assign a real function instead, like

def some_handler(channel, click_type, was_queued, time_diff):
    sonos.pause()
    some_other_thing()

and in got_button:

cc.on_button_single_or_double_click_or_hold = some_handler
DIYtechie commented 7 years ago

Thank you for the swift reply, Emill :) My main problem is, however, getting the "if" statements to work, so I can have different actions for different click types and different buttons, like in RobZe89's example.

I guess the logic should be something like:

If bdAddr is xx:xx:xx:xx:xx:xx and clickType is ButtonSingleClick
    then run sonos_volume_up_script.py and print("Volume turned up")

If bdAddr is xx:xx:xx:xx:xx:xx and clickType is ButtonDoubleClick
    then run sonos_volume_down_script.py and print("Volume turned down")

If bdAddr is yy:yy:yy:yy:yy:yy and clicktype is ButtonHold
    then run hue_lights_script.py and print("Hue lights dimmed to 50%")

But I can't get this part to work and I'm not sure where to put it. Should it be part of the some_handler function? like:

def some_handler(channel, click_type, was_queued, time_diff):
       If bdAddr is xx:xx:xx:xx:xx:xx and clickType is ButtonSingleClick
           then run sonos_volume_up_script.py and print("Volume turned up")
Emill commented 7 years ago

Try something like this for volume up:

def some_handler(channel, click_type, was_queued, time_diff):
    if channel.bd_addr == 'xx:xx:xx:xx:xx:xx' and click_type == fliclib.ClickType.ButtonSingleClick:
        execfile('sonos_volume_up_script.py')
        print('Volume turned up')
DIYtechie commented 7 years ago

It works! Thank you so much, @Emill

varesed commented 7 years ago

I have "raspberry PI 3b" and 2 flic buttons, but cannot get it to work :( Managed to get Flic buttons work with the "new_scan_wizard.py" and saved and connected. I need 2 buttons to start 2 different scripts, but I cannot get it work

Can anyone please share your full script where 2 flic buttons work. This sadly does not help me because I am too newb:

def some_handler(channel, click_type, was_queued, time_diff): if channel.bd_addr == 'xx:xx:xx:xx:xx:xx' and click_type == fliclib.ClickType.ButtonSingleClick: execfile('sonos_volume_up_script.py') print('Volume turned up')

Emill commented 7 years ago

Maybe something like this, where xx.. and yy.. are replaced with the different addresses.

def some_handler(channel, click_type, was_queued, time_diff):
    if channel.bd_addr == 'xx:xx:xx:xx:xx:xx' and click_type == fliclib.ClickType.ButtonSingleClick:
        execfile('sonos_volume_up_script.py')
        print('Volume turned up')
    if channel.bd_addr == 'yy:yy:yy:yy:yy:yy' and click_type == fliclib.ClickType.ButtonSingleClick:
        execfile('other_script.py')
        print('Other script executed')
varesed commented 7 years ago

Thank you very much @Emill

DIYtechie commented 7 years ago

In case any newbies (such as myself) want a template, here is a basic one that works:

#!/usr/bin/env /usr/bin/python3
# -*- coding: utf-8 -*-

import fliclib

client = fliclib.FlicClient("localhost")

MyButton1 = '80:e4:da:71:xx:xx' #white flic button in bedroom
MyButton2 = '80:e4:da:71:xx:xy' #turquoise flic button in bedroom
MyButton3 = '80:e4:da:71:xx:yy' #black flic button in kitchen
MyButton4 = '80:e4:da:71:xy:yy' #white flic button in living room

def got_button(bd_addr):
    cc = fliclib.ButtonConnectionChannel(bd_addr)
    cc.on_button_single_or_double_click_or_hold = some_handler
    cc.on_connection_status_changed = \
        lambda channel, connection_status, disconnect_reason: \
                        print(channel.bd_addr + " " + str(connection_status) + (" " + str(disconnect_reason) if connection_status == fliclib.ConnectionStatus.Disconnected else ""))
    client.add_connection_channel(cc)

def got_info(items):
    print(items)
    for bd_addr in items["bd_addr_of_verified_buttons"]:
        got_button(bd_addr)

def some_handler(channel, click_type, was_queued, time_diff):
    if channel.bd_addr == MyButton1:
            try:
                    if click_type == fliclib.ClickType.ButtonSingleClick:
                        print("ButtonSingleClick has not been assigned an action")                        

                    if click_type == fliclib.ClickType.ButtonDoubleClick:
                        print("ButtonDoubleClick has not been assigned an action")

                    if click_type == fliclib.ClickType.ButtonHold:
                        print("ButtonHold has not been assigned an action")

            except Exception:
                    import datetime
                    print('An error occured: {:%Y-%m-%d %H:%M:%S}'.format(datetime.datetime.now()))

    if channel.bd_addr == MyButton2:
            try:
                    if click_type == fliclib.ClickType.ButtonSingleClick:
                        print("ButtonSingleClick has not been assigned an action")                        

                    if click_type == fliclib.ClickType.ButtonDoubleClick:
                        print("ButtonDoubleClick has not been assigned an action")

                    if click_type == fliclib.ClickType.ButtonHold:
                        print("ButtonHold has not been assigned an action")

            except Exception:
                    import datetime
                    print('An error occured: {:%Y-%m-%d %H:%M:%S}'.format(datetime.datetime.now()))

client.get_info(got_info)

client.on_new_verified_button = got_button

client.handle_events()

And here is the client script that I use everyday to control Sonos, Hue and the WeMo insight switch: (can most likely be improved, but it works well)

#!/usr/bin/env /usr/bin/python3
# -*- coding: utf-8 -*-
from soco import SoCo

#if __name__ == '__main__':
sonos = SoCo('192.168.3.13') # IP of Sonos speaker

from phue import Bridge

b = Bridge('192.168.3.23') # IP of Hue bridge

import fliclib

client = fliclib.FlicClient("localhost")

MyButton1 = '80:e4:da:71:xx:xx' #white flic button in bedroom
MyButton2 = '80:e4:da:71:xx:xy' #turquoise flic button in bedroom
MyButton3 = '80:e4:da:71:xx:yy' #black flic button in kitchen
MyButton4 = '80:e4:da:71:xy:yy' #white flic button in living room

def got_button(bd_addr):
    cc = fliclib.ButtonConnectionChannel(bd_addr)
    cc.on_button_single_or_double_click_or_hold = some_handler
    cc.on_connection_status_changed = \
        lambda channel, connection_status, disconnect_reason: \
                        print(channel.bd_addr + " " + str(connection_status) + (" " + str(disconnect_reason) if connection_status == fliclib.ConnectionStatus.Disconnected else ""))
    client.add_connection_channel(cc)

def got_info(items):
    print(items)
    for bd_addr in items["bd_addr_of_verified_buttons"]:
        got_button(bd_addr)

def some_handler(channel, click_type, was_queued, time_diff):

    #volume control
    if channel.bd_addr == MyButton1:
            try:
                    if click_type == fliclib.ClickType.ButtonSingleClick:
                        old = sonos.volume
                        new = old + 5
                        sonos.volume = new
                        print ("Volume increased to: " + str(sonos.volume) + " / 50" )

                    if click_type == fliclib.ClickType.ButtonDoubleClick:
                        old = sonos.volume
                        new = old - 5
                        sonos.volume = new
                        print ("Volume decreased to: " + str(sonos.volume) + " / 50" )

                    if click_type == fliclib.ClickType.ButtonHold:
                        print("ButtonHold has not been assigned an action")
            except Exception:
                    import datetime
                    print('An error occured (sonos volume): {:%Y-%m-%d %H:%M:%S}'.format(datetime.datetime.now()))

    #sonos play/pause/next/prev
    if channel.bd_addr == MyButton2:
            try:
                    if click_type == fliclib.ClickType.ButtonSingleClick:
                        transport_info = sonos.get_current_transport_info()
                        if transport_info['current_transport_state'] == 'PLAYING':
                            sonos.pause()
                            track = sonos.get_current_track_info()
                            print ("'" + track['title'] + "'" + " is paused")
                        else:
                            sonos.play()
                            track = sonos.get_current_track_info()
                            print ("Now playing: " + "'" + track['title'] + "'")

                    if click_type == fliclib.ClickType.ButtonDoubleClick:
                        sonos.next()
                        track = sonos.get_current_track_info()
                        print ("Skipping forward to " + "'" + track['title'] + "'")

                    if click_type == fliclib.ClickType.ButtonHold:
                        sonos.previous()
                        track = sonos.get_current_track_info()
                        print ("Skipping back to " + "'" + track['title'] + "'")

            except Exception:
                    import datetime
                    print('An error occurred (sonos play/pause/next/prev): {:%Y-%m-%d %H:%M:%S}'.format(datetime.datetime.now()))

    #light control 1
    if channel.bd_addr == MyButton3:
            if click_type == fliclib.ClickType.ButtonSingleClick:
                        if b.get_light( 1,'on') is False and b.get_light( 2,'on') is False and b.get_light( 4,'on') is False:
                            b.set_group(1, 'on', True)
                            import os
                            os.system('wemo switch "WeMo" on')
                            print("Living room lights on")
                        else:
                            b.set_group(1, 'on', False)
                            import os
                            os.system('wemo switch "WeMo" off')
                            print("Living room lights off")

                    if click_type == fliclib.ClickType.ButtonDoubleClick:
                        import os
                        os.system('wemo switch "WeMo" toggle')
                        print("WeMo switch is: ")
                        os.system('wemo -v switch "WeMo" status')

                    if click_type == fliclib.ClickType.ButtonHold:
                        if b.get_light(4,'on') is False:
                            b.set_light(4, 'on', True)
                            print("Table lamp on")
                        else:
                            b.set_light(4, 'on', False)
                            print("Table lamp off")

    #light control 2
    if channel.bd_addr == MyButton4:
            if click_type == fliclib.ClickType.ButtonSingleClick:
                        if b.get_light( 1,'on') is False and b.get_light( 2,'on') is False and b.get_light( 4,'on') is False:
                            b.set_group(1, 'on', True)
                            import os
                            os.system('wemo switch "WeMo" on')
                            print("Living room lights on")
                        else:
                            b.set_group(1, 'on', False)
                            import os
                            os.system('wemo switch "WeMo" off')
                            print("Living room lights off")

                    if click_type == fliclib.ClickType.ButtonDoubleClick:
                        import os
                        os.system('wemo switch "WeMo" toggle')
                        print("WeMo switch is: ")
                        os.system('wemo -v switch "WeMo" status')

                    if click_type == fliclib.ClickType.ButtonHold:
                        if b.get_light(4,'on') is False:
                            b.set_light(4, 'on', True)
                            print("Table lamp on")
                        else:
                            b.set_light(4, 'on', False)
                            print("Table lamp off")

client.get_info(got_info)

client.on_new_verified_button = got_button

client.handle_events()

Error handling (try-except) is necessary when using with sonos, since sending the "next" command will otherwise exit the script, if your sonos is at the end of a playlist and repeat is turned off.

I had difficulties installing the Phue and SoCo library correctly in the python 3 folder, so I had to copy the libraries to the same folder as my client script to get it to work. Now it works and it is awesome :)

guussie commented 7 years ago

Thanks for all this good stuff, but I'm afraid you're all going to fast for me.

I am trying to run this on a C.H.I.P. I was able to install Python 3.4 and actually with to it:

chip@kitchenchip:~/fliclib-linux-hci-master/clientlib/python$ python --version Python 3.4.2

But when I run python test_client.py I get he following, and the execution seems to hang:

chip@kitchenchip:~/fliclib-linux-hci-master/clientlib/python$ python test_client.py OrderedDict([('bluetooth_controller_state', <BluetoothControllerState.Attached: 2>), ('my_bd_addr', 'cc:79:df:1e:c3:d9'), ('my_bd_addr_type', <BdAddrType.PublicBdAddrType: 0>), ('max_pending_connections', 32), ('max_concurrently_connected_buttons', -1), ('current_pending_connections', 0), ('currently_no_space_for_new_connection', 0), ('nb_verified_buttons', 1), ('bd_addr_of_verified_buttons', ['80:e4:da:72:91:86'])])

If anyone can give a step-by-step example on how to make this work that would be greatly appreciated.

My objective os to control a Hue lamp and to control a Particle Photon.

Thanks!

tonyjoy1992 commented 6 years ago

Hi all, I am trying to integrate sonos with flic button just like @PiRune did. And I am using a web interface for this purpose using flask web framework. But I am stuck here with flic discovery and displaying the flic details. Can anyone help me to integrate flic with flask ?. Thanks in advance!

fabianbergmark commented 6 years ago

@tonyjoy1992 This is for issues regarding the fliclib. If you have a specific issue with the fliclib feel free to create a separate issue, but if you just want help with coding this is the wrong place to ask :)

Emill commented 6 years ago

We're unfamiliar with Flask so unfortunately we can't help you there. But since we're just a lib you should be able to execute the code from any kind of Python program.