Closed jeremyprz closed 2 years ago
I believe I've figured out what I was doing wrong (subscribing to device instead of button), but I don't have a solution quite yet. When I discover a solution, I'll post here and resolve.
Because of the way asyncio works, your code immediately stops listening for events after registering for them. You need to use asyncio.sleep instead.
Thanks! That feedback got me a long way.
I'm seeing button-presses reliably, but the callbacks aren't working quite yet.
Now seeing this, with the code below. I am seeing the button-presses, but the callbacks still aren't routing back. Trying to whittle it down to the bare essentials.
15-Feb-22 18:31:53 - FOUND Pico Button: key='101', name='Kitchen_OliotPico' id='0'
15-Feb-22 18:31:53 - Added subscriber to button with device_id: 101 button_id: 0
15-Feb-22 18:31:53 - dumping button: {'device_id': '102', 'current_state': 'Release', 'button_number': 1, 'name': 'Kitchen_OliotPico', 'type': 'Pico3ButtonRaiseLower', 'model': 'PJ2-3BRL-GXX-X01', 'serial': 66761298}
15-Feb-22 18:31:53 - FOUND Pico Button: key='102', name='Kitchen_OliotPico' id='1'
15-Feb-22 18:31:53 - Added subscriber to button with device_id: 102 button_id: 1
15-Feb-22 18:31:53 - dumping button: {'device_id': '103', 'current_state': 'Release', 'button_number': 2, 'name': 'Kitchen_OliotPico', 'type': 'Pico3ButtonRaiseLower', 'model': 'PJ2-3BRL-GXX-X01', 'serial': 66761298}
15-Feb-22 18:31:53 - FOUND Pico Button: key='103', name='Kitchen_OliotPico' id='2'
15-Feb-22 18:31:53 - Added subscriber to button with device_id: 103 button_id: 2
15-Feb-22 18:31:53 - dumping button: {'device_id': '104', 'current_state': 'Release', 'button_number': 3, 'name': 'Kitchen_OliotPico', 'type': 'Pico3ButtonRaiseLower', 'model': 'PJ2-3BRL-GXX-X01', 'serial': 66761298}
15-Feb-22 18:31:53 - FOUND Pico Button: key='104', name='Kitchen_OliotPico' id='3'
15-Feb-22 18:31:53 - Added subscriber to button with device_id: 104 button_id: 3
15-Feb-22 18:31:53 - dumping button: {'device_id': '105', 'current_state': 'Release', 'button_number': 4, 'name': 'Kitchen_OliotPico', 'type': 'Pico3ButtonRaiseLower', 'model': 'PJ2-3BRL-GXX-X01', 'serial': 66761298}
15-Feb-22 18:31:53 - FOUND Pico Button: key='105', name='Kitchen_OliotPico' id='4'
15-Feb-22 18:31:53 - Added subscriber to button with device_id: 105 button_id: 4
15-Feb-22 18:31:53 - sleeping...
15-Feb-22 18:31:57 - received for subscription d9ed7cbf-9a38-42d6-b642-ab930256160b: {'CommuniqueType': 'UpdateResponse', 'Header': {'MessageBodyType': 'OneButtonStatusEvent', 'StatusCode': '200 OK', 'Url': '/button/101/status/event'}, 'Body': {'ButtonStatus': {'Button': {'href': '/button/101'}, 'ButtonEvent': {'EventType': 'Press'}}}}
15-Feb-22 18:31:57 - Handling button status: Response(Header=ResponseHeader(StatusCode=ResponseStatus(200, 'OK'), Url='/button/101/status/event', MessageBodyType='OneButtonStatusEvent'), CommuniqueType='UpdateResponse', Body={'ButtonStatus': {'Button': {'href': '/button/101'}, 'ButtonEvent': {'EventType': 'Press'}}})
15-Feb-22 18:31:57 - received for subscription d9ed7cbf-9a38-42d6-b642-ab930256160b: {'CommuniqueType': 'UpdateResponse', 'Header': {'MessageBodyType': 'OneButtonStatusEvent', 'StatusCode': '200 OK', 'Url': '/button/101/status/event'}, 'Body': {'ButtonStatus': {'Button': {'href': '/button/101'}, 'ButtonEvent': {'EventType': 'Release'}}}}
15-Feb-22 18:31:57 - Handling button status: Response(Header=ResponseHeader(StatusCode=ResponseStatus(200, 'OK'), Url='/button/101/status/event', MessageBodyType='OneButtonStatusEvent'), CommuniqueType='UpdateResponse', Body={'ButtonStatus': {'Button': {'href': '/button/101'}, 'ButtonEvent': {'EventType': 'Release'}}})
15-Feb-22 18:31:58 - sleeping...
#!/usr/bin/env python3
import sys
import logging
import asyncio
from oliot.config import Config
logging.basicConfig(
format='%(asctime)s - %(message)s',
datefmt='%d-%b-%y %H:%M:%S',
level=logging.DEBUG,
stream=sys.stdout
)
log = logging.getLogger(__name__)
config = Config()
def example_callback(arg1: [str]) -> None:
log.info("Received callback: {}".format(arg1))
async def mainloop():
# `Smartbridge` provides an API for interacting with the Caséta bridge.
bridge = config.lutron.bridge(bridgeId="dh-lutron")
await bridge.connect()
buttons = bridge.get_buttons()
if len(buttons) <= 0:
log.info("no buttons!")
else:
for key in buttons:
button = buttons[key]
log.info("dumping button: {}".format(button))
if 'name' in button:
name = button['name']
fqn = name.split("_")
if len(fqn) > 1 and fqn[0] == 'Kitchen' and fqn[1] == 'OliotPico':
b_num = button['button_number']
log.info("FOUND Pico Button: key='{}', name='{}' id='{}'".format(key, name, b_num))
bridge.add_button_subscriber(b_num, example_callback)
log.info("Added subscriber to button with device_id: {} button_id: {}".format(key, b_num))
while True:
log.info("sleeping...")
await asyncio.sleep(5)
# Because pylutron_caseta uses asyncio,
# it must be run within the context of an asyncio event loop.
loop = asyncio.get_event_loop()
loop.run_until_complete(mainloop())
Update the code below works. Output below. Thanks @mdonoughe !
Probably would be good to add similar code to examples/readme for those of us stumbling through python as a non-primary language.
15-Feb-22 18:49:34 - button: {'device_id': '101', 'current_state': 'Release', 'button_number': 0, 'name': 'Kitchen_OliotPico', 'type': 'Pico3ButtonRaiseLower', 'model': 'PJ2-3BRL-GXX-X01', 'serial': 66761298}
15-Feb-22 18:49:34 - FOUND Pico Button: key='101' b_num='0'
15-Feb-22 18:49:34 - Added subscriber to button with device_id: 101 button_id: 0
15-Feb-22 18:49:34 - button: {'device_id': '102', 'current_state': 'Release', 'button_number': 1, 'name': 'Kitchen_OliotPico', 'type': 'Pico3ButtonRaiseLower', 'model': 'PJ2-3BRL-GXX-X01', 'serial': 66761298}
15-Feb-22 18:49:34 - FOUND Pico Button: key='102' b_num='1'
15-Feb-22 18:49:34 - Added subscriber to button with device_id: 102 button_id: 1
15-Feb-22 18:49:34 - button: {'device_id': '103', 'current_state': 'Release', 'button_number': 2, 'name': 'Kitchen_OliotPico', 'type': 'Pico3ButtonRaiseLower', 'model': 'PJ2-3BRL-GXX-X01', 'serial': 66761298}
15-Feb-22 18:49:34 - FOUND Pico Button: key='103' b_num='2'
15-Feb-22 18:49:34 - Added subscriber to button with device_id: 103 button_id: 2
15-Feb-22 18:49:34 - button: {'device_id': '104', 'current_state': 'Release', 'button_number': 3, 'name': 'Kitchen_OliotPico', 'type': 'Pico3ButtonRaiseLower', 'model': 'PJ2-3BRL-GXX-X01', 'serial': 66761298}
15-Feb-22 18:49:34 - FOUND Pico Button: key='104' b_num='3'
15-Feb-22 18:49:34 - Added subscriber to button with device_id: 104 button_id: 3
15-Feb-22 18:49:34 - button: {'device_id': '105', 'current_state': 'Release', 'button_number': 4, 'name': 'Kitchen_OliotPico', 'type': 'Pico3ButtonRaiseLower', 'model': 'PJ2-3BRL-GXX-X01', 'serial': 66761298}
15-Feb-22 18:49:34 - FOUND Pico Button: key='105' b_num='4'
15-Feb-22 18:49:34 - Added subscriber to button with device_id: 105 button_id: 4
15-Feb-22 18:49:34 - sleeping...
15-Feb-22 18:49:36 - received for subscription e9d2786d-2409-4b61-99d0-7cd6e4c94601: {'CommuniqueType': 'UpdateResponse', 'Header': {'MessageBodyType': 'OneButtonStatusEvent', 'StatusCode': '200 OK', 'Url': '/button/101/status/event'}, 'Body': {'ButtonStatus': {'Button': {'href': '/button/101'}, 'ButtonEvent': {'EventType': 'Press'}}}}
15-Feb-22 18:49:36 - Handling button status: Response(Header=ResponseHeader(StatusCode=ResponseStatus(200, 'OK'), Url='/button/101/status/event', MessageBodyType='OneButtonStatusEvent'), CommuniqueType='UpdateResponse', Body={'ButtonStatus': {'Button': {'href': '/button/101'}, 'ButtonEvent': {'EventType': 'Press'}}})
15-Feb-22 18:49:36 - Received callback: Press
15-Feb-22 18:49:36 - received for subscription e9d2786d-2409-4b61-99d0-7cd6e4c94601: {'CommuniqueType': 'UpdateResponse', 'Header': {'MessageBodyType': 'OneButtonStatusEvent', 'StatusCode': '200 OK', 'Url': '/button/101/status/event'}, 'Body': {'ButtonStatus': {'Button': {'href': '/button/101'}, 'ButtonEvent': {'EventType': 'Release'}}}}
15-Feb-22 18:49:36 - Handling button status: Response(Header=ResponseHeader(StatusCode=ResponseStatus(200, 'OK'), Url='/button/101/status/event', MessageBodyType='OneButtonStatusEvent'), CommuniqueType='UpdateResponse', Body={'ButtonStatus': {'Button': {'href': '/button/101'}, 'ButtonEvent': {'EventType': 'Release'}}})
15-Feb-22 18:49:36 - Received callback: Release
15-Feb-22 18:49:39 - sleeping...
#!/usr/bin/env python3
import sys
import logging
import asyncio
from oliot.config import Config
logging.basicConfig(
format='%(asctime)s - %(message)s',
datefmt='%d-%b-%y %H:%M:%S',
level=logging.DEBUG,
stream=sys.stdout
)
log = logging.getLogger(__name__)
config = Config()
def example_callback(arg1: [str]) -> None:
log.info("Received callback: {}".format(arg1))
async def mainloop():
bridge = config.lutron.bridge(bridgeId="dh-lutron")
await bridge.connect()
buttons = bridge.get_buttons()
assert(len(buttons) > 0)
for key in buttons:
button = buttons[key]
log.info("button: {}".format(button))
if 'name' in button and button['name'] == 'Kitchen_OliotPico':
b_num = button['button_number']
log.info("FOUND Pico Button: key='{}' b_num='{}'".format(key, b_num))
bridge.add_button_subscriber(key, example_callback)
log.info("Added subscriber to button with device_id: {} button_id: {}".format(key, b_num))
while True:
log.info("sleeping...")
await asyncio.sleep(5)
# Because pylutron_caseta uses asyncio,
# it must be run within the context of an asyncio event loop.
loop = asyncio.get_event_loop()
loop.run_until_complete(mainloop())
I noticed this part def example_callback(arg1: [str]) -> None:
should be def example_callback(arg1: str) -> None:
. It doesn't make a difference when you run the code, but if you run type checking tools with your current code you should get an error about the type of the callback not matching the type of the parameter to add_button_subscriber
. In some languages, a function signature type will look like Callable<param0, param1, param2, ret>
, but in Python it looks like Callable[[param0, param1, param2], ret]
.
Probably my failure to read/understand the line of code in smartbridge.py
def add_button_subscriber(self, button_id: str, callback_: Callable[[str], None]):
Great python library @gurumitts , @mdonoughe ! It literally has taken me ~6 hours to go from an amazon delivery of a Lutron starter pack to full integration into my existing homebrew IoT solution. In one button press of a pico I can trigger a sequence that sets a hue scene, turns on a bunch of WeMo switches, and sets a Lutron Caseta scene. I still have some work to do, but the button-callbacks from this library are going to make my automation child/guest friendly. Thanks!
here's my final fully functional prototype for anyone interested. Ignore the wonky JSON wrapping/unwrapping ... its just temporary.
#!/usr/bin/env python3
import sys
import logging
import asyncio
import requests
import json
from oliot.config import Config
logging.basicConfig(
format='%(asctime)s - %(message)s',
datefmt='%d-%b-%y %H:%M:%S',
level=logging.DEBUG,
stream=sys.stdout
)
log = logging.getLogger(__name__)
config = Config()
actions = {"0": "bright", "1": "night", "2": "off", "3": "none", "4": "none"}
class Handler:
def __init__(self, device_id, device_name, button_num):
self.device_id = device_id
self.device_name = device_name
self.button_num = button_num
def callback(self, action_ids: str) -> None:
log.info("Received callback: {}[{}] => {}"
.format(self.device_name, self.button_num, action_ids))
try:
action = actions[str(self.button_num)]
body_d = {'type': 'actions', 'version': '1.0', 'payload': [{'vendor': 'OrganL', 'action': 'sequence', 'args': {'sequence': ['dh--living-room--{0}'.format(action)]}}]}
body_s = json.dumps(body_d)
body_j = json.loads(body_s)
payload = body_j['payload']
log.info("payload: {}".format(payload))
r = requests.post(
config.hub.actionsUrl(),
json=payload)
log.info("Response: {}".format(r.status_code))
except:
log.exception("fail")
async def mainloop():
bridge = config.lutron.bridge(bridgeId="dh-lutron")
await bridge.connect()
buttons = bridge.get_buttons()
assert(len(buttons) > 0)
for key in buttons:
button = buttons[key]
log.info("button: {}".format(button))
if 'name' in button and button['name'] == 'Kitchen_OliotPico':
b_num = button['button_number']
handler = Handler(key, button['name'], b_num)
log.info("FOUND Pico Button: key='{}' b_num='{}'".format(key, b_num))
bridge.add_button_subscriber(key, handler.callback)
log.info("Added subscriber to button with device_id: {} button_id: {}".format(key, b_num))
while True:
log.info("sleeping...")
await asyncio.sleep(5)
# Because pylutron_caseta uses asyncio,
# it must be run within the context of an asyncio event loop.
loop = asyncio.get_event_loop()
loop.run_until_complete(mainloop())
Closing out this issue. No code changes require within the library.
I have the following prototype code (below), which is able to communicate with my bridge, but the
add_subscriber
API appears to do nothing. Here is the tail end of the output (below). As can be seen - I'm able to communicate with the bridge correctly. but it seems theadd_subscriber
does nothing, or I have a bug in the way I'm using it.I am pushing the various buttons on the pico remote attempting to get the callback to fire, but nothing seems to happen.
I should mention - I'm not a python developer in my day-job, so there could be silly bugs due to my lack of understanding of some python basics.
Does the
add_subscriber
API work?