Skip to content

Commit

Permalink
Replacing threading with asyncio
Browse files Browse the repository at this point in the history
  • Loading branch information
jamesridgway committed Jun 1, 2024
1 parent 5939b4a commit 596742c
Show file tree
Hide file tree
Showing 14 changed files with 73 additions and 82 deletions.
49 changes: 24 additions & 25 deletions devdeck/controls/clock_control.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import threading
import asyncio
from datetime import datetime
from time import sleep

from devdeck_core.controls.deck_control import DeckControl

Expand All @@ -9,34 +8,34 @@ class ClockControl(DeckControl):

def __init__(self, key_no, **kwargs):
super().__init__(key_no, **kwargs)
self.thread = None
self.running = False

def initialize(self):
self.thread = threading.Thread(target=self._update_display)
async def initialize(self):
asyncio.create_task(self.periodic_task(1, self._update_display))
self.running = True
self.thread.start()

def _update_display(self):
async def periodic_task(self, interval, task):
while self.running is True:
with self.deck_context() as context:
now = datetime.now()

with context.renderer() as r:
r.text(now.strftime("%H:%M"))\
.center_horizontally() \
.center_vertically(-100) \
.font_size(150)\
.end()
r.text(now.strftime("%a, %d %b")) \
.center_horizontally() \
.center_vertically(100) \
.font_size(75) \
.end()
sleep(1)

def dispose(self):
await task()
await asyncio.sleep(interval)

async def _update_display(self):
with self.deck_context() as context:
now = datetime.now()

with context.renderer() as r:
r.text(now.strftime("%H:%M")) \
.center_horizontally() \
.center_vertically(-100) \
.font_size(150) \
.end()
r.text(now.strftime("%a, %d %b")) \
.center_horizontally() \
.center_vertically(100) \
.font_size(75) \
.end()

async def dispose(self):
self.running = False
if self.thread:
self.thread.join()

4 changes: 2 additions & 2 deletions devdeck/controls/command_control.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,12 @@ def __init__(self, key_no, **kwargs):
self.__logger = logging.getLogger('devdeck')
super().__init__(key_no, **kwargs)

def initialize(self):
async def initialize(self):
with self.deck_context() as context:
with context.renderer() as r:
r.image(os.path.expanduser(self.settings['icon'])).end()

def pressed(self):
async def pressed(self):
try:
Popen(self.settings['command'], stdout=DEVNULL, stderr=DEVNULL)
except Exception as ex:
Expand Down
4 changes: 2 additions & 2 deletions devdeck/controls/mic_mute_control.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,12 @@ def __init__(self, key_no, **kwargs):
self.__logger = logging.getLogger('devdeck')
super().__init__(key_no, **kwargs)

def initialize(self):
async def initialize(self):
if self.pulse is None:
self.pulse = pulsectl.Pulse('MicMuteControl')
self.__render_icon()

def pressed(self):
async def pressed(self):
mic = self.__get_mic()
if mic is None:
return
Expand Down
4 changes: 2 additions & 2 deletions devdeck/controls/name_list_control.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,13 @@ def __init__(self, key_no, **kwargs):
self.name_index = 0
super().__init__(key_no, **kwargs)

def initialize(self):
async def initialize(self):
self.name_index = 0
with self.deck_context() as context:
with context.renderer() as r:
r.image(os.path.join(os.path.dirname(__file__), "../assets/font-awesome", 'users.png')).end()

def pressed(self):
async def pressed(self):
if 'names' not in self.settings or len(self.settings['names']) == 0:
return
with self.deck_context() as context:
Expand Down
4 changes: 2 additions & 2 deletions devdeck/controls/timer_control.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,12 @@ def __init__(self, key_no, **kwargs):
self.thread = None
super().__init__(key_no, **kwargs)

def initialize(self):
async def initialize(self):
with self.deck_context() as context:
with context.renderer() as r:
r.image(os.path.join(os.path.dirname(__file__), "../assets/font-awesome", 'stopwatch.png')).end()

def pressed(self):
async def pressed(self):
if self.start_time is None:
self.start_time = datetime.datetime.now()
self.thread = threading.Thread(target=self._update_display)
Expand Down
4 changes: 2 additions & 2 deletions devdeck/controls/volume_level_control.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,13 @@ def __init__(self, key_no, **kwargs):
self.__logger = logging.getLogger('devdeck')
super().__init__(key_no, **kwargs)

def initialize(self):
async def initialize(self):
if self.pulse is None:
self.pulse = pulsectl.Pulse('VolumeLevelControl')
self.volume = float(self.settings['volume']) / 100
self.__render_icon()

def pressed(self):
async def pressed(self):
output = self.__get_output()
if output is None:
return
Expand Down
4 changes: 2 additions & 2 deletions devdeck/controls/volume_mute_control.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,11 @@ def __init__(self, key_no, **kwargs):
self.__logger = logging.getLogger('devdeck')
super().__init__(key_no, **kwargs)

def initialize(self):
async def initialize(self):
self.pulse = pulsectl.Pulse()
self.__render_icon()

def pressed(self):
async def pressed(self):
output = self.__get_output()
if output is None:
return
Expand Down
8 changes: 4 additions & 4 deletions devdeck/deck_context.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,11 +30,11 @@ def set_key_image_native(self, key_no, icon):
image = PILHelper.to_native_format(self.__deck, icon)
self.__deck.set_key_image(key_no, image)

def set_active_deck(self, deck):
self.__devdeck.set_active_deck(deck)
async def set_active_deck(self, deck):
await self.__devdeck.set_active_deck(deck)

def pop_active_deck(self):
self.__devdeck.pop_active_deck()
async def pop_active_deck(self):
await self.__devdeck.pop_active_deck()

def renderer(self, key_no):
return RendererManager(key_no, self)
24 changes: 12 additions & 12 deletions devdeck/deck_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,38 +9,38 @@ def __init__(self, deck):
self.__deck = deck
self.__deck.set_brightness(50)
self.__deck.reset()
self.__deck.set_key_callback(self.key_callback)
self.__deck.set_key_callback_async(self.key_callback)
self.decks = []

def set_active_deck(self, deck):
async def set_active_deck(self, deck):
self.__logger.info("Setting active deck: %s", type(deck).__name__)
for deck_itr in self.decks:
deck_itr.clear_deck_context()
await deck_itr.clear_deck_context()
self.decks.append(deck)
self.get_active_deck().render(DeckContext(self, self.__deck))
await self.get_active_deck().render(DeckContext(self, self.__deck))

def get_active_deck(self):
if not self.decks:
return None
return self.decks[-1]

def pop_active_deck(self):
async def pop_active_deck(self):
if len(self.decks) == 1:
return
popped_deck = self.decks.pop()
self.__logger.info("Exiting deck: %s", type(popped_deck).__name__)
popped_deck.clear_deck_context()
self.get_active_deck().render(DeckContext(self, self.__deck))
await popped_deck.clear_deck_context()
await self.get_active_deck().render(DeckContext(self, self.__deck))

def key_callback(self, deck, key, state):
async def key_callback(self, deck, key, state):
if state:
self.get_active_deck().pressed(key)
await self.get_active_deck().pressed(key)
else:
self.get_active_deck().released(key)
await self.get_active_deck().released(key)

def close(self):
async def close(self):
keys = self.__deck.key_count()
for deck in self.decks:
deck.dispose()
await deck.dispose()
for key_no in range(keys):
self.__deck.set_key_image(key_no, None)
2 changes: 1 addition & 1 deletion devdeck/decks/single_page_deck_controller.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ def __init__(self, key_no, **kwargs):
self.settings = kwargs
super().__init__(key_no, **kwargs)

def initialize(self):
async def initialize(self):
with self.deck_context() as context:
with context.renderer() as r:
r.image(os.path.expanduser(self.settings['icon'])).end()
Expand Down
2 changes: 1 addition & 1 deletion devdeck/decks/volume_deck.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ def __init__(self, key_no, **kwargs):
self.__logger = logging.getLogger('devdeck')
super().__init__(key_no, **kwargs)

def initialize(self):
async def initialize(self):
self.pulse = pulsectl.Pulse()
self.__render_icon()

Expand Down
21 changes: 7 additions & 14 deletions devdeck/main.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import asyncio
import logging
import os
import sys
Expand All @@ -13,7 +14,7 @@
from devdeck.settings.validation_error import ValidationError


def main():
async def main():
os.makedirs(os.path.join(str(Path.home()), '.devdeck'), exist_ok=True)

root = logging.getLogger('devdeck')
Expand Down Expand Up @@ -76,22 +77,14 @@ def main():

# Instantiate deck
main_deck = deck_settings.deck_class()(None, **deck_settings.settings())
deck_manager.set_active_deck(main_deck)

for t in threading.enumerate():
if t is threading.currentThread():
continue

if t.is_alive():
try:
t.join()
except KeyboardInterrupt as ex:
deck_manager.close()
deck.close()
await deck_manager.set_active_deck(main_deck)
return deck_manager

if len(streamdecks) == 0:
root.info("No streamdecks detected, exiting.")


if __name__ == '__main__':
main()
loop = asyncio.get_event_loop()
loop.run_until_complete(main())
loop.run_forever()
1 change: 0 additions & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
assertpy
cerberus
devdeck-core==1.0.7
emoji
jsonschema
pillow
Expand Down
24 changes: 12 additions & 12 deletions tests/devdeck/test_deck_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,48 +8,48 @@
class TestDeckManager:
@mock.patch('StreamDeck.Devices.StreamDeck.StreamDeck')
@mock.patch('StreamDeck.Devices.StreamDeck.StreamDeck')
def test_set_active_deck(self, first_mock_deck, second_mock_deck):
async def test_set_active_deck(self, first_mock_deck, second_mock_deck):
dev_deck = DeckManager(first_mock_deck)

assert_that(dev_deck.get_active_deck()).is_none()

# Sets active deck
dev_deck.set_active_deck(first_mock_deck)
await dev_deck.set_active_deck(first_mock_deck)
assert_that(dev_deck.get_active_deck()).is_equal_to(first_mock_deck)

# Set active deck to another instance
dev_deck.set_active_deck(second_mock_deck)
await dev_deck.set_active_deck(second_mock_deck)
first_mock_deck.clear_deck_context.assert_called_once()
assert_that(dev_deck.get_active_deck()).is_equal_to(second_mock_deck)

@mock.patch('StreamDeck.Devices.StreamDeck.StreamDeck')
@mock.patch('StreamDeck.Devices.StreamDeck.StreamDeck')
def test_pop_active_deck(self, first_mock_deck, second_mock_deck):
async def test_pop_active_deck(self, first_mock_deck, second_mock_deck):
dev_deck = DeckManager(first_mock_deck)

# Two active decks and the second is active
dev_deck.set_active_deck(first_mock_deck)
dev_deck.set_active_deck(second_mock_deck)
await dev_deck.set_active_deck(first_mock_deck)
await dev_deck.set_active_deck(second_mock_deck)
assert_that(dev_deck.get_active_deck()).is_equal_to(second_mock_deck)

dev_deck.pop_active_deck()
await dev_deck.pop_active_deck()
second_mock_deck.clear_deck_context.assert_called_once()
assert_that(dev_deck.get_active_deck()).is_equal_to(first_mock_deck)

@mock.patch('StreamDeck.Devices.StreamDeck.StreamDeck')
def test_pop_active_deck_does_not_remove_root_deck(self, first_mock_deck):
async def test_pop_active_deck_does_not_remove_root_deck(self, first_mock_deck):
dev_deck = DeckManager(first_mock_deck)
dev_deck.set_active_deck(first_mock_deck)
await dev_deck.set_active_deck(first_mock_deck)
assert_that(dev_deck.get_active_deck()).is_equal_to(first_mock_deck)

# Root deck is still active even if a pop is attempted
dev_deck.pop_active_deck()
await dev_deck.pop_active_deck()
assert_that(dev_deck.get_active_deck()).is_equal_to(first_mock_deck)

@mock.patch('StreamDeck.Devices.StreamDeck.StreamDeck')
def test_key_callback_propogates_to_active_deck(self, first_mock_deck):
async def test_key_callback_propogates_to_active_deck(self, first_mock_deck):
dev_deck = DeckManager(first_mock_deck)
dev_deck.set_active_deck(first_mock_deck)
await dev_deck.set_active_deck(first_mock_deck)

# Pressed
dev_deck.key_callback(first_mock_deck, 12, True)
Expand Down

0 comments on commit 596742c

Please sign in to comment.