# HG changeset patch # User Louis Opter # Date 1476490224 25200 # Node ID ada36e135d0d06248306fac5b67b4ffd47429752 # Parent 019c6e75f61daa0a5e99dbb2f66bb70764d81d78 Wip, break things appart diff -r 019c6e75f61d -r ada36e135d0d add_monolight.patch --- a/add_monolight.patch Fri Oct 14 12:47:34 2016 -0700 +++ b/add_monolight.patch Fri Oct 14 17:10:24 2016 -0700 @@ -1,5 +1,5 @@ # HG changeset patch -# Parent 045e51ef0bec937287f1e5b88bb87d8218a488a9 +# Parent 703102c9539ab4968c7cde3313c2f33ce0070a8a Start an experimental GUI for a Monome 128 Varibright Written in Python >= 3.5. @@ -12,12 +12,12 @@ ^build ^pcaps +.*\.egg-info$ -diff --git a/monolight/monolight/__init__.py b/monolight/monolight/__init__.py +diff --git a/apps/monolight/monolight/__init__.py b/apps/monolight/monolight/__init__.py new file mode 100644 -diff --git a/monolight/monolight/cli.py b/monolight/monolight/cli.py +diff --git a/apps/monolight/monolight/cli.py b/apps/monolight/monolight/cli.py new file mode 100644 --- /dev/null -+++ b/monolight/monolight/cli.py ++++ b/apps/monolight/monolight/cli.py @@ -0,0 +1,101 @@ +# Copyright (c) 2016, Louis Opter +# @@ -120,10 +120,271 @@ + + serialosc.disconnect() + loop.run_until_complete(lightsd.close()) -diff --git a/monolight/monolight/lightsc/__init__.py b/monolight/monolight/lightsc/__init__.py +diff --git a/apps/monolight/monolight/osc.py b/apps/monolight/monolight/osc.py +new file mode 100644 +--- /dev/null ++++ b/apps/monolight/monolight/osc.py +@@ -0,0 +1,45 @@ ++# Copyright (c) 2016, Louis Opter ++# ++# This file is part of lightsd. ++# ++# lightsd is free software: you can redistribute it and/or modify ++# it under the terms of the GNU General Public License as published by ++# the Free Software Foundation, either version 3 of the License, or ++# (at your option) any later version. ++# ++# lightsd is distributed in the hope that it will be useful, ++# but WITHOUT ANY WARRANTY; without even the implied warranty of ++# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++# GNU General Public License for more details. ++# ++# You should have received a copy of the GNU General Public License ++# along with lightsd. If not, see . ++ ++import monome ++ ++MONOME_KEYPRESS_DOWN = 1 ++MONOME_KEYPRESS_UP = 0 ++MONOME_KEYPRESS_STATES = frozenset({ ++ MONOME_KEYPRESS_DOWN, ++ MONOME_KEYPRESS_UP, ++}) ++ ++ ++class MonomeApplication(monome.Monome): ++ ++ def __init__(self, keypress_callback): ++ self._keypress_callback = keypress_callback ++ monome.Monome.__init__(self, "/monolight") ++ ++ def ready(self): ++ self.led_all(0) ++ ++ def grid_key(self, x, y, s): ++ self._keypress_callback(x, y, s) ++ ++ ++def monome_apply(serialosc, method, *args, **kwargs): ++ for device in serialosc.app_instances.values(): ++ for app in device: ++ if isinstance(app, MonomeApplication): ++ method(app, *args, **kwargs) +diff --git a/apps/monolight/monolight/ui.py b/apps/monolight/monolight/ui.py new file mode 100644 --- /dev/null -+++ b/monolight/monolight/lightsc/__init__.py ++++ b/apps/monolight/monolight/ui.py +@@ -0,0 +1,114 @@ ++# Copyright (c) 2016, Louis Opter ++# ++# This file is part of lightsd. ++# ++# lightsd is free software: you can redistribute it and/or modify ++# it under the terms of the GNU General Public License as published by ++# the Free Software Foundation, either version 3 of the License, or ++# (at your option) any later version. ++# ++# lightsd is distributed in the hope that it will be useful, ++# but WITHOUT ANY WARRANTY; without even the implied warranty of ++# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++# GNU General Public License for more details. ++# ++# You should have received a copy of the GNU General Public License ++# along with lightsd. If not, see . ++ ++import asyncio ++import collections ++import monome ++import logging ++ ++from .osc import ( ++ MONOME_KEYPRESS_DOWN, ++ monome_apply, ++) ++from .lightsc.commands import ( ++ SetLightFromHSBK, ++ PowerOff, ++ PowerOn, ++ PowerToggle, ++) ++ ++logger = logging.getLogger("monolight.ui") ++ ++_event_queue = None ++ ++_KeyPress = collections.namedtuple("_KeyPress", ("x", "y", "state")) ++ ++_STOP_SENTINEL = object() ++ ++ ++def draw(serialosc): ++ buf = monome.LedBuffer(8, 8) ++ buf.led_set(0, 0, 1) ++ for x in range(0, 5): ++ buf.led_set(x, 7, 1) ++ monome_apply(serialosc, buf.render) ++ ++ ++def hide(serialosc): ++ monome_apply(serialosc, monome.Monome.led_all, 0) ++ ++ ++async def start(loop, lightsd, serialosc): ++ global _event_queue ++ ++ _event_queue = asyncio.Queue() ++ ++ hidden = True ++ ++ while True: ++ keypress = await _event_queue.get() ++ if keypress is _STOP_SENTINEL: ++ hide(serialosc) ++ _event_queue = None ++ return ++ ++ if not hidden: ++ draw(serialosc) ++ ++ logger.info("keypress: x={}, y={}, state={}".format(*keypress)) ++ ++ if keypress.state != MONOME_KEYPRESS_DOWN: ++ continue ++ if keypress.y != 7 and keypress.y != 0: ++ continue ++ if keypress.x == 0: ++ if keypress.y == 0: ++ hidden = not hidden ++ if hidden: ++ hide(serialosc) ++ continue ++ await lightsd.apply(PowerOff(["*"])) ++ if keypress.y != 7: ++ continue ++ if keypress.x == 1: ++ await lightsd.apply(PowerOn(["*"])) ++ elif keypress.x == 2: ++ await lightsd.apply(PowerToggle(["neko"])) ++ elif keypress.x == 3: ++ await lightsd.apply(PowerToggle(["fugu"])) ++ elif keypress.x == 4: ++ async with lightsd.batch() as batch: ++ batch.apply(SetLightFromHSBK( ++ ["#tower"], 37.469443, 1.0, 0.25, 3500, 600 ++ )) ++ batch.apply(SetLightFromHSBK( ++ ["fugu", "buzz"], 47.469443, 0.2, 0.2, 3500, 600 ++ )) ++ batch.apply(SetLightFromHSBK( ++ ["candle"], 47.469443, 0.2, 0.15, 3500, 600 ++ )) ++ batch.apply(PowerOn(["#br"])) ++ ++ ++def stop(): ++ if _event_queue is not None: ++ _event_queue.put_nowait(_STOP_SENTINEL) ++ ++ ++def submit_keypress(x, y, state): ++ if _event_queue is not None: ++ _event_queue.put_nowait(_KeyPress(x, y, state)) +diff --git a/apps/monolight/setup.py b/apps/monolight/setup.py +new file mode 100644 +--- /dev/null ++++ b/apps/monolight/setup.py +@@ -0,0 +1,52 @@ ++# Copyright (c) 2016, Louis Opter ++# ++# This file is part of lighstd. ++# ++# lighstd is free software: you can redistribute it and/or modify ++# it under the terms of the GNU General Public License as published by ++# the Free Software Foundation, either version 3 of the License, or ++# (at your option) any later version. ++# ++# lighstd is distributed in the hope that it will be useful, ++# but WITHOUT ANY WARRANTY; without even the implied warranty of ++# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++# GNU General Public License for more details. ++# ++# You should have received a copy of the GNU General Public License ++# along with lighstd. If not, see . ++ ++import setuptools ++ ++version = "0.0.1.dev0" ++ ++setuptools.setup( ++ name="monolight", ++ version=version, ++ description="A Monome UI to control smart bulbs using lightsd", ++ author="Louis Opter", ++ author_email="louis@opter.org", ++ packages=setuptools.find_packages(exclude=['tests', 'tests.*']), ++ include_package_data=True, ++ entry_points={ ++ "console_scripts": [ ++ "monolight = monolight.cli:main", ++ ], ++ }, ++ install_requires=[ ++ "click~=6.6", ++ "pymonome~=0.8.2", ++ ], ++ tests_require=[ ++ "doubles~=1.1.3", ++ "freezegun~=0.3.5", ++ "pytest~=3.0", ++ ], ++ extras_require={ ++ "dev": [ ++ "flake8", ++ "ipython", ++ "pdbpp", ++ "pep8", ++ ], ++ }, ++) +diff --git a/clients/lightsd-python/README.rst b/clients/lightsd-python/README.rst +new file mode 100644 +--- /dev/null ++++ b/clients/lightsd-python/README.rst +@@ -0,0 +1,30 @@ ++A Python client to control your smart bulbs through lightsd ++=========================================================== ++ ++lightsd_ is a daemon (background service) to control your LIFX_ WiFi "smart" ++bulbs. This package allows you to make RPC calls to lightsd to control your ++light bulbs from Python. It is built on top of the ``asyncio`` module and ++requires Python ≥ 3.5: ++ ++.. code-block:: python ++ ++ import asyncio ++ ++ from lightsd import create_lightsd_connection ++ from lightsd.commands import PowerOff, PowerOn, SetLightFromHSBK ++ ++ async def example(): ++ client = await create_lightsd_connection("unix:///run/lightsd/socket") ++ async with client.batch() as batch: ++ batch.apply(PowerOn(targets=["*"])) ++ batch.apply(SetLightFromHSBK(["*"], 0., 1., 1., 3500, transition=600)) ++ client.apply(PowerOff(["*"])) ++ await client.close() ++ ++ loop = asyncio.get_event_loop() ++ loop.run_until_complete(loop.create_task(example())) ++ ++.. _lightsd: https://www.lightsd.io/ ++.. _LIFX: http://lifx.co/ ++ ++.. vim: set tw=80 spelllang=en spell: +diff --git a/clients/lightsd-python/lightsd/__init__.py b/clients/lightsd-python/lightsd/__init__.py +new file mode 100644 +--- /dev/null ++++ b/clients/lightsd-python/lightsd/__init__.py @@ -0,0 +1,21 @@ +# Copyright (c) 2016, Louis Opter +# @@ -142,83 +403,14 @@ +# You should have received a copy of the GNU General Public License +# along with lightsd. If not, see . + -+from .lightsc import ( # noqa ++from .client import ( # noqa + LightsClient, + create_lightsd_connection, +) -diff --git a/monolight/monolight/lightsc/commands.py b/monolight/monolight/lightsc/commands.py +diff --git a/clients/lightsd-python/lightsd/client.py b/clients/lightsd-python/lightsd/client.py new file mode 100644 --- /dev/null -+++ b/monolight/monolight/lightsc/commands.py -@@ -0,0 +1,64 @@ -+# Copyright (c) 2016, Louis Opter -+# -+# This file is part of lightsd. -+# -+# lightsd is free software: you can redistribute it and/or modify -+# it under the terms of the GNU General Public License as published by -+# the Free Software Foundation, either version 3 of the License, or -+# (at your option) any later version. -+# -+# lightsd is distributed in the hope that it will be useful, -+# but WITHOUT ANY WARRANTY; without even the implied warranty of -+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -+# GNU General Public License for more details. -+# -+# You should have received a copy of the GNU General Public License -+# along with lightsd. If not, see . -+ -+ -+class Command: -+ -+ METHOD = None -+ -+ def __init__(self, *args): -+ self.params = args -+ -+ -+class SetLightFromHSBK(Command): -+ -+ METHOD = "set_light_from_hsbk" -+ -+ def __init__(self, targets, h, s, b, k, transition): -+ Command.__init__(self, targets, h, s, b, k, transition) -+ -+ -+class GetLightState(Command): -+ -+ METHOD = "get_light_state" -+ -+ def __init__(self, targets): -+ Command.__init__(self, targets) -+ -+ -+class PowerOff(Command): -+ -+ METHOD = "power_off" -+ -+ def __init__(self, targets): -+ Command.__init__(self, targets) -+ -+ -+class PowerOn(Command): -+ -+ METHOD = "power_on" -+ -+ def __init__(self, targets): -+ Command.__init__(self, targets) -+ -+ -+class PowerToggle(Command): -+ -+ METHOD = "power_toggle" -+ -+ def __init__(self, targets): -+ Command.__init__(self, targets) -diff --git a/monolight/monolight/lightsc/lightsc.py b/monolight/monolight/lightsc/lightsc.py -new file mode 100644 ---- /dev/null -+++ b/monolight/monolight/lightsc/lightsc.py ++++ b/clients/lightsd-python/lightsd/client.py @@ -0,0 +1,248 @@ +# Copyright (c) 2016, Louis Opter +# @@ -468,61 +660,11 @@ + c = LightsClient(url, loop=loop) + await c.connect() + return c -diff --git a/monolight/monolight/osc.py b/monolight/monolight/osc.py +diff --git a/clients/lightsd-python/lightsd/commands.py b/clients/lightsd-python/lightsd/commands.py new file mode 100644 --- /dev/null -+++ b/monolight/monolight/osc.py -@@ -0,0 +1,45 @@ -+# Copyright (c) 2016, Louis Opter -+# -+# This file is part of lightsd. -+# -+# lightsd is free software: you can redistribute it and/or modify -+# it under the terms of the GNU General Public License as published by -+# the Free Software Foundation, either version 3 of the License, or -+# (at your option) any later version. -+# -+# lightsd is distributed in the hope that it will be useful, -+# but WITHOUT ANY WARRANTY; without even the implied warranty of -+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -+# GNU General Public License for more details. -+# -+# You should have received a copy of the GNU General Public License -+# along with lightsd. If not, see . -+ -+import monome -+ -+MONOME_KEYPRESS_DOWN = 1 -+MONOME_KEYPRESS_UP = 0 -+MONOME_KEYPRESS_STATES = frozenset({ -+ MONOME_KEYPRESS_DOWN, -+ MONOME_KEYPRESS_UP, -+}) -+ -+ -+class MonomeApplication(monome.Monome): -+ -+ def __init__(self, keypress_callback): -+ self._keypress_callback = keypress_callback -+ monome.Monome.__init__(self, "/monolight") -+ -+ def ready(self): -+ self.led_all(0) -+ -+ def grid_key(self, x, y, s): -+ self._keypress_callback(x, y, s) -+ -+ -+def monome_apply(serialosc, method, *args, **kwargs): -+ for device in serialosc.app_instances.values(): -+ for app in device: -+ if isinstance(app, MonomeApplication): -+ method(app, *args, **kwargs) -diff --git a/monolight/monolight/ui.py b/monolight/monolight/ui.py -new file mode 100644 ---- /dev/null -+++ b/monolight/monolight/ui.py -@@ -0,0 +1,114 @@ ++++ b/clients/lightsd-python/lightsd/commands.py +@@ -0,0 +1,64 @@ +# Copyright (c) 2016, Louis Opter +# +# This file is part of lightsd. @@ -540,108 +682,58 @@ +# You should have received a copy of the GNU General Public License +# along with lightsd. If not, see . + -+import asyncio -+import collections -+import monome -+import logging ++ ++class Command: + -+from .osc import ( -+ MONOME_KEYPRESS_DOWN, -+ monome_apply, -+) -+from .lightsc.commands import ( -+ SetLightFromHSBK, -+ PowerOff, -+ PowerOn, -+ PowerToggle, -+) ++ METHOD = None + -+logger = logging.getLogger("monolight.ui") -+ -+_event_queue = None -+ -+_KeyPress = collections.namedtuple("_KeyPress", ("x", "y", "state")) -+ -+_STOP_SENTINEL = object() ++ def __init__(self, *args): ++ self.params = args + + -+def draw(serialosc): -+ buf = monome.LedBuffer(8, 8) -+ buf.led_set(0, 0, 1) -+ for x in range(0, 5): -+ buf.led_set(x, 7, 1) -+ monome_apply(serialosc, buf.render) ++class SetLightFromHSBK(Command): ++ ++ METHOD = "set_light_from_hsbk" ++ ++ def __init__(self, targets, h, s, b, k, transition): ++ Command.__init__(self, targets, h, s, b, k, transition) + + -+def hide(serialosc): -+ monome_apply(serialosc, monome.Monome.led_all, 0) ++class GetLightState(Command): ++ ++ METHOD = "get_light_state" ++ ++ def __init__(self, targets): ++ Command.__init__(self, targets) + + -+async def start(loop, lightsd, serialosc): -+ global _event_queue -+ -+ _event_queue = asyncio.Queue() -+ -+ hidden = True -+ -+ while True: -+ keypress = await _event_queue.get() -+ if keypress is _STOP_SENTINEL: -+ hide(serialosc) -+ _event_queue = None -+ return -+ -+ if not hidden: -+ draw(serialosc) -+ -+ logger.info("keypress: x={}, y={}, state={}".format(*keypress)) ++class PowerOff(Command): + -+ if keypress.state != MONOME_KEYPRESS_DOWN: -+ continue -+ if keypress.y != 7 and keypress.y != 0: -+ continue -+ if keypress.x == 0: -+ if keypress.y == 0: -+ hidden = not hidden -+ if hidden: -+ hide(serialosc) -+ continue -+ await lightsd.apply(PowerOff(["*"])) -+ if keypress.y != 7: -+ continue -+ if keypress.x == 1: -+ await lightsd.apply(PowerOn(["*"])) -+ elif keypress.x == 2: -+ await lightsd.apply(PowerToggle(["neko"])) -+ elif keypress.x == 3: -+ await lightsd.apply(PowerToggle(["fugu"])) -+ elif keypress.x == 4: -+ async with lightsd.batch() as batch: -+ batch.apply(SetLightFromHSBK( -+ ["#tower"], 37.469443, 1.0, 0.25, 3500, 600 -+ )) -+ batch.apply(SetLightFromHSBK( -+ ["fugu", "buzz"], 47.469443, 0.2, 0.2, 3500, 600 -+ )) -+ batch.apply(SetLightFromHSBK( -+ ["candle"], 47.469443, 0.2, 0.15, 3500, 600 -+ )) -+ batch.apply(PowerOn(["#br"])) ++ METHOD = "power_off" ++ ++ def __init__(self, targets): ++ Command.__init__(self, targets) + + -+def stop(): -+ if _event_queue is not None: -+ _event_queue.put_nowait(_STOP_SENTINEL) ++class PowerOn(Command): ++ ++ METHOD = "power_on" ++ ++ def __init__(self, targets): ++ Command.__init__(self, targets) + + -+def submit_keypress(x, y, state): -+ if _event_queue is not None: -+ _event_queue.put_nowait(_KeyPress(x, y, state)) -diff --git a/monolight/setup.py b/monolight/setup.py ++class PowerToggle(Command): ++ ++ METHOD = "power_toggle" ++ ++ def __init__(self, targets): ++ Command.__init__(self, targets) +diff --git a/clients/lightsd-python/setup.py b/clients/lightsd-python/setup.py new file mode 100644 --- /dev/null -+++ b/monolight/setup.py -@@ -0,0 +1,52 @@ ++++ b/clients/lightsd-python/setup.py +@@ -0,0 +1,56 @@ +# Copyright (c) 2016, Louis Opter +# +# This file is part of lighstd. @@ -663,10 +755,14 @@ + +version = "0.0.1.dev0" + ++with open("README.rst", "r") as fp: ++ long_description = fp.read() ++ +setuptools.setup( -+ name="monolight", ++ name="lightsd", + version=version, -+ description="A Monome UI to control smart bulbs using lightsd", ++ description="A client to interact with lighsd ", ++ long_description=long_description, + author="Louis Opter", + author_email="louis@opter.org", + packages=setuptools.find_packages(exclude=['tests', 'tests.*']), diff -r 019c6e75f61d -r ada36e135d0d add_slides.patch --- a/add_slides.patch Fri Oct 14 12:47:34 2016 -0700 +++ b/add_slides.patch Fri Oct 14 17:10:24 2016 -0700 @@ -1692,7 +1692,7 @@ new file mode 100644 --- /dev/null +++ b/slides/33c3/33c3.tex -@@ -0,0 +1,15 @@ +@@ -0,0 +1,28 @@ +\documentclass[xcolor={usenames,svgnames}]{beamer} + +\usepackage[american]{babel} @@ -1707,6 +1707,19 @@ + +\begin{frame}\titlepage\end{frame} + ++%%% Supporting more bulbs products %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% ++ ++% I think this is a pretty obvious idea, avoid getting locked with one ++% manufacturer or product. ++ ++% lightsd has been architectured around a minimal set of bulb commands, meaning ++% that while abstractions/integrations are missing to make lightsd work with ++% other bulbs, it at least relies on a very limited set of commands (get/set ++% the state of the bulb and that's about it). For example lightsd can totally ++% implement transitions internally. ++ ++% Modules that would need heavy changes to support other bulb models: ++ +\end{document} diff --git a/slides/33c3/CMakeLists.txt b/slides/33c3/CMakeLists.txt new file mode 100644