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 @@