Skip to content

Commit d00c0ab

Browse files
committed
Use a single asyncio loop
1 parent c305f1d commit d00c0ab

1 file changed

Lines changed: 51 additions & 15 deletions

File tree

kasatk/__main__.py

Lines changed: 51 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -156,17 +156,17 @@ def pencil_icon(self):
156156

157157

158158
class BulbFrame(tkinter.Frame):
159-
def _hue_callback(self, event):
160-
asyncio.run(update_bulb(self.bulb, None, self.hue_slider.get()))
159+
async def _hue_callback(self):
160+
return await update_bulb(self.bulb, None, self.hue_slider.get())
161161

162-
def _saturation_callback(self, event):
163-
asyncio.run(update_bulb(self.bulb, saturation=self.saturation_slider.get()))
162+
async def _saturation_callback(self):
163+
return await update_bulb(self.bulb, saturation=self.saturation_slider.get())
164164

165-
def _brightness_callback(self, event):
166-
asyncio.run(update_bulb(self.bulb, self.brightness_slider.get()))
165+
async def _brightness_callback(self):
166+
return await update_bulb(self.bulb, self.brightness_slider.get())
167167

168168
@classmethod
169-
def for_bulb(cls, bulb: kasa.SmartBulb, config, *args, **kwargs):
169+
def for_bulb(cls, loop, bulb: kasa.SmartBulb, config, *args, **kwargs):
170170
"""Create a new bulb frame given a SmartBulb
171171
172172
... note:: I think using a classmethod here is a better approach than
@@ -182,19 +182,34 @@ def for_bulb(cls, bulb: kasa.SmartBulb, config, *args, **kwargs):
182182
self.hue_slider = tkinter.Scale(
183183
self, from_=0, to=360, orient=tkinter.HORIZONTAL
184184
)
185-
self.hue_slider.bind("<ButtonRelease-1>", self._hue_callback)
185+
self.hue_slider.bind(
186+
"<ButtonRelease-1>",
187+
lambda event, self=self, loop=loop: asyncio.run_coroutine_threadsafe(
188+
self._hue_callback(), loop
189+
),
190+
)
186191
self.hue_slider.set(self.bulb.hsv[0])
187192

188193
self.saturation_slider = tkinter.Scale(
189194
self, from_=0, to=100, orient=tkinter.HORIZONTAL
190195
)
191-
self.saturation_slider.bind("<ButtonRelease-1>", self._saturation_callback)
196+
self.saturation_slider.bind(
197+
"<ButtonRelease-1>",
198+
lambda event, self=self, loop=loop: asyncio.run_coroutine_threadsafe(
199+
self._saturation_callback(), loop
200+
),
201+
)
192202
self.saturation_slider.set(self.bulb.hsv[1])
193203

194204
self.brightness_slider = tkinter.Scale(
195205
self, from_=0, to=100, orient=tkinter.HORIZONTAL
196206
)
197-
self.brightness_slider.bind("<ButtonRelease-1>", self._brightness_callback)
207+
self.brightness_slider.bind(
208+
"<ButtonRelease-1>",
209+
lambda event, self=self, loop=loop: asyncio.run_coroutine_threadsafe(
210+
self._brightness_callback(), loop
211+
),
212+
)
198213
self.brightness_slider.set(self.bulb.brightness)
199214

200215
bulb_name = getattr(self.bulb, "alias", None) or self.bulb.mac
@@ -221,6 +236,17 @@ class KasaDevices(tkinter.Frame):
221236
def __init__(self, *args, **kwargs):
222237
super(self.__class__, self).__init__(*args, **kwargs)
223238

239+
# create an asyncio event loop running in a secondary thread
240+
def exception_handler(loop, context):
241+
loop.call_soon_threadsafe(
242+
logger.error, "Caught exception {}".format(context)
243+
)
244+
245+
self.event_loop = asyncio.new_event_loop()
246+
self.event_loop.set_exception_handler(exception_handler)
247+
self.event_thread = threading.Thread(target=self.event_loop.run_forever)
248+
self.event_thread.start()
249+
224250
self.device_lock = asyncio.Lock()
225251
# list of kasa devices
226252
self.kasa_devices = []
@@ -241,7 +267,7 @@ def _bulbs(self):
241267
async def update_widgets(self):
242268
for bulb in self._bulbs:
243269
if bulb.mac not in self.device_widgets:
244-
w = BulbFrame.for_bulb(bulb, self.config, master=self)
270+
w = BulbFrame.for_bulb(self.event_loop, bulb, self.config, master=self)
245271
w.pack()
246272
self.device_widgets[bulb.mac] = w
247273

@@ -273,12 +299,22 @@ async def clear_devices(self):
273299
self.kasa_devices.clear()
274300

275301
def start_refresh(self):
276-
def thread_loop():
277-
asyncio.run(self._do_refresh())
302+
"""Returns a *concurrent* future, rather than an *asyncio* future. You
303+
can block on the result from a *synchronous* thread using
304+
self.start_refresh().result().
278305
306+
:rtype: concurrent.futures.Future
307+
"""
279308
logger.info("KasaDevices.start_refresh() called")
280-
threading.Thread(target=thread_loop).start()
281-
logger.info("KasaDevices.start_refresh() completed")
309+
310+
async def call_later(coro, *args, **kwargs):
311+
# call later isn't particularly necessary, but by using it, out
312+
# exceptions will go to the asyncio exceptions handler
313+
self.event_loop.create_task(coro(*args, **kwargs))
314+
315+
return asyncio.run_coroutine_threadsafe(
316+
call_later(self._do_refresh), self.event_loop
317+
)
282318

283319

284320
def main():

0 commit comments

Comments
 (0)