Mercurial > louis > mq > lightsd
comparison add_monolight.patch @ 502:a78f7f19d40f
keep breaking things
author | Louis Opter <kalessin@kalessin.fr> |
---|---|
date | Sun, 23 Oct 2016 16:35:02 -0700 |
parents | c38b0b9612cd |
children | 84c8260875c2 |
comparison
equal
deleted
inserted
replaced
501:c38b0b9612cd | 502:a78f7f19d40f |
---|---|
1 # HG changeset patch | 1 # HG changeset patch |
2 # Parent 7e908ae2e0c7088791fb4a442d86d7687a52b956 | 2 # Parent b6aea48f2f5da909251852b9864d616b3e2fe7de |
3 Start an experimental GUI for a Monome 128 Varibright | 3 Start an experimental GUI for a Monome 128 Varibright |
4 | 4 |
5 Written in Python >= 3.5. | 5 Written in Python >= 3.5. |
6 | 6 |
7 diff --git a/.hgignore b/.hgignore | 7 diff --git a/.hgignore b/.hgignore |
16 new file mode 100644 | 16 new file mode 100644 |
17 diff --git a/apps/monolight/monolight/cli.py b/apps/monolight/monolight/cli.py | 17 diff --git a/apps/monolight/monolight/cli.py b/apps/monolight/monolight/cli.py |
18 new file mode 100644 | 18 new file mode 100644 |
19 --- /dev/null | 19 --- /dev/null |
20 +++ b/apps/monolight/monolight/cli.py | 20 +++ b/apps/monolight/monolight/cli.py |
21 @@ -0,0 +1,69 @@ | 21 @@ -0,0 +1,82 @@ |
22 +# Copyright (c) 2016, Louis Opter <louis@opter.org> | 22 +# Copyright (c) 2016, Louis Opter <louis@opter.org> |
23 +# | 23 +# |
24 +# This file is part of lightsd. | 24 +# This file is part of lightsd. |
25 +# | 25 +# |
26 +# lightsd is free software: you can redistribute it and/or modify | 26 +# lightsd is free software: you can redistribute it and/or modify |
48 +from . import osc | 48 +from . import osc |
49 +from . import ui | 49 +from . import ui |
50 + | 50 + |
51 +ENCODING = locale.getpreferredencoding() | 51 +ENCODING = locale.getpreferredencoding() |
52 + | 52 + |
53 +logging.basicConfig(level=logging.INFO) | |
54 + | |
55 + | 53 + |
56 +@click.command() | 54 +@click.command() |
57 +@click.option("--serialoscd-host", default="127.0.0.1") | 55 +@click.option("--serialoscd-host", default="127.0.0.1") |
58 +@click.option("--serialoscd-port", type=click.IntRange(0, 2**16 - 1)) | 56 +@click.option("--serialoscd-port", type=click.IntRange(0, 2**16 - 1)) |
59 +@click.option("--lightsd-url") | 57 +@click.option("--monome-id", default="*", help="The id of the monome to use") |
60 +def main(serialoscd_host: str, serialoscd_port: int, lightsd_url: str): | 58 +@click.option( |
59 + "--lightsd-url", help="tcp+jsonrpc://host:port or unix+jsonrpc:///a/path" | |
60 +) | |
61 +def main( | |
62 + serialoscd_host: str, | |
63 + serialoscd_port: int, | |
64 + monome_id: str, | |
65 + lightsd_url: str, | |
66 +) -> None: | |
67 + logging.basicConfig(level=logging.INFO) | |
68 + | |
69 + # NOTE: this isn't good enough on Windows unless you pass --lightsd-url: | |
70 + # discovering lightsd's socket involves using asyncio.subprocess which | |
71 + # requires an IOCP event loop, which doesn't support UDP connections. | |
61 + loop = asyncio.get_event_loop() | 72 + loop = asyncio.get_event_loop() |
62 + | 73 + |
63 + monome_future = asyncio.Future() | 74 + # Connect to lightsd, serialoscd and wait for everything to come online: |
64 + grid = ui.MonomeGrid() | 75 + grid = ui.MonomeGrid() |
65 + | 76 + monome_ready = asyncio.Future() |
77 + App = functools.partial( | |
78 + osc.MonomeApplication, monome_ready, grid.submit_input | |
79 + ) | |
66 + tasks = asyncio.gather( | 80 + tasks = asyncio.gather( |
67 + loop.create_task(lightsc.create_async_lightsd_connection(lightsd_url)), | 81 + loop.create_task(lightsc.create_async_lightsd_connection(lightsd_url)), |
68 + loop.create_task(monome.create_serialosc_connection(functools.partial( | 82 + loop.create_task(monome.create_serialosc_connection({monome_id: App})), |
69 + osc.MonomeApplication, monome_future, grid.submit_input | 83 + asyncio.ensure_future(monome_ready) |
70 + ))), | |
71 + asyncio.ensure_future(monome_future) | |
72 + ) | 84 + ) |
73 + loop.run_until_complete(tasks) | 85 + loop.run_until_complete(tasks) |
86 + | |
74 + lightsd, serialosc, monome_app = tasks.result() | 87 + lightsd, serialosc, monome_app = tasks.result() |
75 + | 88 + # We don't need this anymore, MonomeGrid.monome maintains its own (UDP) |
76 + grid.set_monome(monome_app) | 89 + # connection once the Monome has been discovered: |
90 + serialosc.disconnect() | |
91 + grid.connect_monome(monome_app, loop) | |
92 + | |
93 + ui_task = ui.start(loop, lightsd, grid) | |
77 + | 94 + |
78 + if hasattr(loop, "add_signal_handler"): | 95 + if hasattr(loop, "add_signal_handler"): |
79 + for signum in (signal.SIGINT, signal.SIGTERM, signal.SIGQUIT): | 96 + for signum in (signal.SIGINT, signal.SIGTERM, signal.SIGQUIT): |
80 + loop.add_signal_handler(signum, ui.stop) | 97 + loop.add_signal_handler(signum, ui_task.cancel) |
81 + | |
82 + # TODO: make which monome instance to use something configurable | |
83 + ui_task = loop.create_task(ui.start(loop, lightsd, grid)) | |
84 + | 98 + |
85 + loop.run_until_complete(ui_task) | 99 + loop.run_until_complete(ui_task) |
86 + | 100 + |
87 + serialosc.disconnect() | 101 + grid.disconnect_monome() |
88 + loop.run_until_complete(lightsd.close()) | 102 + loop.run_until_complete(lightsd.close()) |
89 + | |
90 + loop.close() | 103 + loop.close() |
91 diff --git a/apps/monolight/monolight/osc.py b/apps/monolight/monolight/osc.py | 104 diff --git a/apps/monolight/monolight/osc.py b/apps/monolight/monolight/osc.py |
92 new file mode 100644 | 105 new file mode 100644 |
93 --- /dev/null | 106 --- /dev/null |
94 +++ b/apps/monolight/monolight/osc.py | 107 +++ b/apps/monolight/monolight/osc.py |
95 @@ -0,0 +1,66 @@ | 108 @@ -0,0 +1,80 @@ |
96 +# Copyright (c) 2016, Louis Opter <louis@opter.org> | 109 +# Copyright (c) 2016, Louis Opter <louis@opter.org> |
97 +# | 110 +# |
98 +# This file is part of lightsd. | 111 +# This file is part of lightsd. |
99 +# | 112 +# |
100 +# lightsd is free software: you can redistribute it and/or modify | 113 +# lightsd is free software: you can redistribute it and/or modify |
109 +# | 122 +# |
110 +# You should have received a copy of the GNU General Public License | 123 +# You should have received a copy of the GNU General Public License |
111 +# along with lightsd. If not, see <http://www.gnu.org/licenses/>. | 124 +# along with lightsd. If not, see <http://www.gnu.org/licenses/>. |
112 + | 125 + |
113 +import asyncio | 126 +import asyncio |
127 +import logging | |
114 +import monome | 128 +import monome |
115 + | 129 + |
116 +from enum import IntEnum | 130 +from enum import IntEnum |
117 +from typing import Callable | 131 +from typing import Callable |
132 + | |
133 +logger = logging.getLogger("monolight.osc") | |
118 + | 134 + |
119 + | 135 + |
120 +class MonomeKeyState(IntEnum): | 136 +class MonomeKeyState(IntEnum): |
121 + | 137 + |
122 + DOWN = 1 | 138 + DOWN = 1 |
145 + | 161 + |
146 +class MonomeApplication(monome.Monome): | 162 +class MonomeApplication(monome.Monome): |
147 + | 163 + |
148 + def __init__( | 164 + def __init__( |
149 + self, | 165 + self, |
150 + future: asyncio.Future, | 166 + ready_future: asyncio.Future, |
151 + keypress_callback: Callable[[int, int, int], None] | 167 + keypress_callback: Callable[[int, int, int], None] |
152 + ) -> None: | 168 + ) -> None: |
153 + monome.Monome.__init__(self, "/monolight") | 169 + monome.Monome.__init__(self, "/monolight") |
154 + future.set_result(self) | 170 + self._ready = False |
155 + self._keypress_callback = keypress_callback | 171 + self._ready_future = ready_future |
172 + self._keypress_cb = keypress_callback | |
156 + | 173 + |
157 + def ready(self) -> None: | 174 + def ready(self) -> None: |
175 + if self._ready_future.done(): | |
176 + logger.warning( | |
177 + "More than one monome was discovered, monolight will use the " | |
178 + "first one found, please pass --monome-id." | |
179 + ) | |
180 + return | |
181 + | |
158 + self.led_all(MonomeLedLevel.OFF) | 182 + self.led_all(MonomeLedLevel.OFF) |
183 + self._ready = True | |
184 + self._ready_future.set_result(self) | |
159 + | 185 + |
160 + def grid_key(self, x: int, y: int, s: int): | 186 + def grid_key(self, x: int, y: int, s: int): |
161 + self._keypress_callback(x, y, s) | 187 + if self._ready is True: |
188 + self._keypress_cb(x, y, s) | |
162 diff --git a/apps/monolight/monolight/types.py b/apps/monolight/monolight/types.py | 189 diff --git a/apps/monolight/monolight/types.py b/apps/monolight/monolight/types.py |
163 new file mode 100644 | 190 new file mode 100644 |
164 --- /dev/null | 191 --- /dev/null |
165 +++ b/apps/monolight/monolight/types.py | 192 +++ b/apps/monolight/monolight/types.py |
166 @@ -0,0 +1,18 @@ | 193 @@ -0,0 +1,18 @@ |
448 + self._blit(component) | 475 + self._blit(component) |
449 diff --git a/apps/monolight/monolight/ui/grid.py b/apps/monolight/monolight/ui/grid.py | 476 diff --git a/apps/monolight/monolight/ui/grid.py b/apps/monolight/monolight/ui/grid.py |
450 new file mode 100644 | 477 new file mode 100644 |
451 --- /dev/null | 478 --- /dev/null |
452 +++ b/apps/monolight/monolight/ui/grid.py | 479 +++ b/apps/monolight/monolight/ui/grid.py |
453 @@ -0,0 +1,37 @@ | 480 @@ -0,0 +1,49 @@ |
454 +# Copyright (c) 2016, Louis Opter <louis@opter.org> | 481 +# Copyright (c) 2016, Louis Opter <louis@opter.org> |
455 +# | 482 +# |
456 +# This file is part of lightsd. | 483 +# This file is part of lightsd. |
457 +# | 484 +# |
458 +# lightsd is free software: you can redistribute it and/or modify | 485 +# lightsd is free software: you can redistribute it and/or modify |
459 +# it under the terms of the GNU General Public License as published by | 486 +# it under the terms of the GNU General Public License as published by |
460 +# the Free Software Foundation, either version 3 of the License, or | 487 +# the Free Software Foundation, either version 3 of the License, or |
461 +# (at your option) any later version. | 488 +# (at your option) any later version. |
462 +# | 489 +# |
463 +# lightsd is distributed in the hope that it will be useful, | 490 +# lightsd is distributed in the hope that it will be useful, |
464 +# but WITHOUT ANY WARRANTY; without even the implied warranty of | 491 +# but WITHOUT ANY WARRANTY; without even the implied warranty of |
465 +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | 492 +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
466 +# GNU General Public License for more details. | 493 +# GNU General Public License for more details. |
467 +# | 494 +# |
468 +# You should have received a copy of the GNU General Public License | 495 +# You should have received a copy of the GNU General Public License |
469 +# along with lightsd. If not, see <http://www.gnu.org/licenses/>. | 496 +# along with lightsd. If not, see <http://www.gnu.org/licenses/>. |
497 + | |
498 +import asyncio | |
470 + | 499 + |
471 +from .. import osc | 500 +from .. import osc |
472 +from .components.layer import Layer | 501 +from .components.layer import Layer |
473 +from .types import Dimensions, Keypress, Position | 502 +from .types import Dimensions, Keypress, Position |
474 + | 503 + |
477 + | 506 + |
478 + def __init__(self) -> None: | 507 + def __init__(self) -> None: |
479 + self.size = None # type: Dimensions | 508 + self.size = None # type: Dimensions |
480 + self.layers = None # z-order, type: List[Layer] | 509 + self.layers = None # z-order, type: List[Layer] |
481 + self.monome = None # type: osc.MonomeApplication | 510 + self.monome = None # type: osc.MonomeApplication |
482 + | 511 + self.input_queue = None # type: asyncio.Queue |
483 + def set_monome(self, grid: osc.MonomeApplication) -> None: | 512 + |
484 + self.size = Dimensions(height=grid.height, width=grid.width) | 513 + def connect_monome( |
514 + self, | |
515 + monome: osc.MonomeApplication, | |
516 + loop: asyncio.AbstractEventLoop = None | |
517 + ) -> None: | |
518 + self.monome = monome | |
519 + self.size = Dimensions(height=monome.height, width=monome.width) | |
485 + self.layers = [Layer(self.size)] | 520 + self.layers = [Layer(self.size)] |
486 + self.grid = grid | 521 + self.input_queue = asyncio.Queue(loop=loop) |
522 + | |
523 + def disconnect_monome(self) -> None: | |
524 + self.monome.disconnect() | |
525 + self.size = self.layers = self.momome = None | |
487 + | 526 + |
488 + def submit_input(self, x: int, y: int, s: int) -> None: | 527 + def submit_input(self, x: int, y: int, s: int) -> None: |
489 + if self.layers: | 528 + if self.input_queue is not None: |
490 + self.layers[-1].submit_input(Keypress(Position(x, y), s)) | 529 + self.put_nowait(self.input_queue, Keypress(Position(x, y), s)) |
491 diff --git a/apps/monolight/monolight/ui/types.py b/apps/monolight/monolight/ui/types.py | 530 diff --git a/apps/monolight/monolight/ui/types.py b/apps/monolight/monolight/ui/types.py |
492 new file mode 100644 | 531 new file mode 100644 |
493 --- /dev/null | 532 --- /dev/null |
494 +++ b/apps/monolight/monolight/ui/types.py | 533 +++ b/apps/monolight/monolight/ui/types.py |
495 @@ -0,0 +1,57 @@ | 534 @@ -0,0 +1,57 @@ |
552 + return "{!r}, {}".format(self.position, self.state.name) | 591 + return "{!r}, {}".format(self.position, self.state.name) |
553 diff --git a/apps/monolight/monolight/ui/ui.py b/apps/monolight/monolight/ui/ui.py | 592 diff --git a/apps/monolight/monolight/ui/ui.py b/apps/monolight/monolight/ui/ui.py |
554 new file mode 100644 | 593 new file mode 100644 |
555 --- /dev/null | 594 --- /dev/null |
556 +++ b/apps/monolight/monolight/ui/ui.py | 595 +++ b/apps/monolight/monolight/ui/ui.py |
557 @@ -0,0 +1,124 @@ | 596 @@ -0,0 +1,46 @@ |
558 +# Copyright (c) 2016, Louis Opter <louis@opter.org> | 597 +# Copyright (c) 2016, Louis Opter <louis@opter.org> |
559 +# | 598 +# |
560 +# This file is part of lightsd. | 599 +# This file is part of lightsd. |
561 +# | 600 +# |
562 +# lightsd is free software: you can redistribute it and/or modify | 601 +# lightsd is free software: you can redistribute it and/or modify |
571 +# | 610 +# |
572 +# You should have received a copy of the GNU General Public License | 611 +# You should have received a copy of the GNU General Public License |
573 +# along with lightsd. If not, see <http://www.gnu.org/licenses/>. | 612 +# along with lightsd. If not, see <http://www.gnu.org/licenses/>. |
574 + | 613 + |
575 +import asyncio | 614 +import asyncio |
576 +import monome | |
577 +import logging | 615 +import logging |
578 + | 616 + |
579 +from typing import ( | |
580 + NamedTuple, | |
581 +) | |
582 + | |
583 +from lightsc import LightsClient | 617 +from lightsc import LightsClient |
584 +from lightsc.requests import ( | 618 + |
585 + SetLightFromHSBK, | 619 +from .grid import MonomeGrid |
586 + PowerOff, | |
587 + PowerOn, | |
588 + PowerToggle, | |
589 +) | |
590 +from ..osc import ( | |
591 + MONOME_KEYPRESS_DOWN, | |
592 + monome_apply, | |
593 +) | |
594 + | 620 + |
595 +logger = logging.getLogger("monolight.ui") | 621 +logger = logging.getLogger("monolight.ui") |
596 + | 622 + |
597 +_event_queue = None | 623 + |
598 + | 624 +async def _refresh( |
599 +_KeyPress = NamedTuple("_KeyPress", [("x", int), ("y", int), ("state", int)]) | 625 + loop: asyncio.AbstractEventLoop, lightsd: LightsClient, grid: MonomeGrid, |
600 + | 626 +) -> None: |
601 +_STOP_SENTINEL = object() | 627 + pass |
602 + | 628 + |
603 + | 629 + |
604 +def draw(serialosc: monome.SerialOsc): | 630 +async def _process_inputs( |
605 + buf = monome.LedBuffer(8, 8) | 631 + loop: asyncio.AbstractEventLoop, lightsd: LightsClient, grid: MonomeGrid, |
606 + buf.led_set(0, 0, 1) | 632 +) -> None: |
607 + for x in range(0, 6): | 633 + pass |
608 + buf.led_set(x, 7, 1) | 634 + |
609 + monome_apply(serialosc, buf.render) | 635 + |
610 + | 636 +def start( |
611 + | 637 + loop: asyncio.AbstractEventLoop, lightsd: LightsClient, grid: MonomeGrid, |
612 +def hide(serialosc: monome.SerialOsc): | 638 +) -> None: |
613 + monome_apply(serialosc, monome.Monome.led_all, 0) | 639 + return asyncio.gather( |
614 + | 640 + loop.create_task(_refresh(loop, lightsd, grid)), |
615 + | 641 + loop.create_task(_process_inputs(loop, lightsd, grid)) |
616 +async def start( | 642 + ) |
617 + loop: asyncio.AbstractEventLoop, | |
618 + lightsd: LightsClient, | |
619 + serialosc: monome.SerialOsc, | |
620 +): | |
621 + global _event_queue | |
622 + | |
623 + _event_queue = asyncio.Queue() | |
624 + | |
625 + hidden = True | |
626 + | |
627 + while True: | |
628 + keypress = await _event_queue.get() | |
629 + if keypress is _STOP_SENTINEL: | |
630 + hide(serialosc) | |
631 + _event_queue = None | |
632 + return | |
633 + | |
634 + if not hidden: | |
635 + draw(serialosc) | |
636 + | |
637 + logger.info("keypress: x={}, y={}, state={}".format(*keypress)) | |
638 + | |
639 + if keypress.state != MONOME_KEYPRESS_DOWN: | |
640 + continue | |
641 + if keypress.y != 7 and keypress.y != 0: | |
642 + continue | |
643 + if keypress.x == 0: | |
644 + if keypress.y == 0: | |
645 + hidden = not hidden | |
646 + if hidden: | |
647 + hide(serialosc) | |
648 + continue | |
649 + await lightsd.apply(PowerOff(["*"])) | |
650 + if keypress.y != 7: | |
651 + continue | |
652 + if keypress.x == 1: | |
653 + await lightsd.apply(PowerOn(["*"])) | |
654 + elif keypress.x == 2: | |
655 + await lightsd.apply(PowerToggle(["neko"])) | |
656 + elif keypress.x == 3: | |
657 + await lightsd.apply(PowerToggle(["fugu"])) | |
658 + elif keypress.x == 4: | |
659 + async with lightsd.batch() as batch: | |
660 + batch.apply(SetLightFromHSBK( | |
661 + ["#tower"], 37.469443, 1.0, 0.25, 3500, 600 | |
662 + )) | |
663 + batch.apply(SetLightFromHSBK( | |
664 + ["fugu", "buzz"], 47.469443, 0.2, 0.2, 3500, 600 | |
665 + )) | |
666 + batch.apply(SetLightFromHSBK( | |
667 + ["candle"], 47.469443, 0.2, 0.15, 3500, 600 | |
668 + )) | |
669 + batch.apply(PowerOn(["#br"])) | |
670 + elif keypress.x == 5: | |
671 + pass | |
672 + | |
673 + | |
674 +def stop(): | |
675 + if _event_queue is not None: | |
676 + _event_queue.put_nowait(_STOP_SENTINEL) | |
677 + | |
678 + | |
679 +def submit_keypress(x: int, y: int, state: int): | |
680 + if _event_queue is not None: | |
681 + _event_queue.put_nowait(_KeyPress(x, y, state)) | |
682 diff --git a/apps/monolight/setup.py b/apps/monolight/setup.py | 643 diff --git a/apps/monolight/setup.py b/apps/monolight/setup.py |
683 new file mode 100644 | 644 new file mode 100644 |
684 --- /dev/null | 645 --- /dev/null |
685 +++ b/apps/monolight/setup.py | 646 +++ b/apps/monolight/setup.py |
686 @@ -0,0 +1,52 @@ | 647 @@ -0,0 +1,52 @@ |