changeset 420:8d707d43a54d

Finish the patches on the documentation and lightsc.py
author Louis Opter <kalessin@kalessin.fr>
date Mon, 04 Jan 2016 16:11:47 +0100
parents b6ec1933ec85
children fc431cab531c
files doc_semver.patch fix_lightscpy_readloop.patch series
diffstat 3 files changed, 0 insertions(+), 270 deletions(-) [+]
line wrap: on
line diff
--- a/doc_semver.patch	Mon Jan 04 15:34:40 2016 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,24 +0,0 @@
-# HG changeset patch
-# Parent  bfa59be534ab65b5caff5b25d70a1d9fa961bfbb
-Document semver a little bit better
-
-diff --git a/docs/changelog.rst b/docs/changelog.rst
---- a/docs/changelog.rst
-+++ b/docs/changelog.rst
-@@ -1,7 +1,15 @@
- Changelog
- =========
- 
--lightsd uses `semantic versionning <http://semver.org/>`_.
-+lightsd uses `semantic versionning <http://semver.org/>`_, here is the summary:
-+
-+Given a version number MAJOR.MINOR.PATCH:
-+
-+- MAJOR version gets incremented when you may need to modify your lightsd
-+  configuration to keep your setup running;
-+- MINOR version gets incremented when functionality or substantial improvements
-+  are added in a backwards-compatible manner;
-+- PATCH version gets incremented for backwards-compatible bug fixes.
- 
- 1.1.2 (2015-11-30)
- ------------------
--- a/fix_lightscpy_readloop.patch	Mon Jan 04 15:34:40 2016 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,244 +0,0 @@
-# HG changeset patch
-# Parent  d65cba5a051329c02b6d339ff3ba0685c8a94376
-Fix lightsc.py's read loop and document it
-
-It now handles arbitrarily large and partial responses properly.
-
-diff --git a/docs/protocol.rst b/docs/protocol.rst
---- a/docs/protocol.rst
-+++ b/docs/protocol.rst
-@@ -1,7 +1,12 @@
- The lights daemon protocol
- ==========================
- 
--The lightsd protocol is implemented on top of `JSON-RPC 2.0`_.
-+The lightsd protocol is implemented on top of `JSON-RPC 2.0`_. This section
-+covers the available methods and how to target bulbs.
-+
-+Since lightsd implements JSON-RPC without any kind of framing like it usually is
-+the case (using HTTP), this section also explains how to implement your own
-+lightsd client in `Writing a client for lightsd`_.
- 
- .. _JSON-RPC 2.0: http://www.jsonrpc.org/specification
- 
-@@ -19,7 +24,7 @@
- | ``#TagName``                | targets bulbs tagged with *TagName*            |
- +-----------------------------+------------------------------------------------+
- | ``124f31a5``                | directly target the bulb with the given id     |
--|                             | (mac addr, see below)                          |
-+|                             | (that's the bulb mac address, see below)       |
- +-----------------------------+------------------------------------------------+
- | ``label``                   | directly target the bulb with the given Label  |
- +-----------------------------+------------------------------------------------+
-@@ -144,9 +149,105 @@
- 
-       untag("#myexistingtag", "myexistingtag")
- 
-+Writing a client for lightsd
-+----------------------------
-+
-+lightsd does JSON-RPC directly over TCP, requests and responses aren't framed in
-+any way like it is usually done by using HTTP.
-+
-+This means that you will very likely need to write a JSON-RPC client
-+specifically for lightsd. You're actually encouraged to do that as lightsd will
-+probably augment JSON-RPC via lightsd specific `JSON-RPC extensions`_ in the
-+future.
-+
-+.. _JSON-RPC extensions: http://www.jsonrpc.org/specification#extensions
-+
-+JSON-RPC over TCP
-+~~~~~~~~~~~~~~~~~
-+
-+JSON-RPC works in a request/response fashion: the socket (network connection) is
-+never used in a full-duplex fashion (data never flows in both direction at the
-+same time):
-+
-+#. Write (send) a request on the socket;
-+#. Read (receive) the response on the socket;
-+#. Repeat.
-+
-+Writing the request is easy: do successive write (send) calls until you have
-+successfully sent the whole request. The next step (reading/receiving) is a bit
-+more complex. And that said, if the response isn't useful to you, you can ask
-+lightsd to omit it by turning your request into a `notification`_: if you remove
-+the JSON-RPC id, then you can just send your requests (now notifications) on the
-+socket in a fire and forget fashion.
-+
-+.. _notification: http://www.jsonrpc.org/specification#notification
-+
-+Otherwise to successfully read and decode JSON-RPC over TCP you will need to
-+implement your own read loop, the algorithm follows. It focuses on the low-level
-+details, adapt it for the language and platform you are using:
-+
-+#. Prepare an empty buffer that you can grow, we will accumulate received data
-+   in it;
-+#. Start an infinite loop and start a read (receive) for a chunk of data (e.g:
-+   4KiB), accumulate the received data in the previous buffer, then try to
-+   interpret the data as JSON:
-+
-+   - if valid JSON can be decoded then break out of the loop;
-+   - else data is missing and continue the loop;
-+#. Decode the JSON data.
-+
-+Here is a complete Python 3 request/response example:
-+
-+.. code-block:: python
-+   :linenos:
-+
-+   import json
-+   import socket
-+   import uuid
-+
-+   READ_SIZE = 4096
-+   ENCODING = "utf-8"
-+
-+   # Connect to lightsd, here using an Unix socket. The rest of the example is
-+   # valid for TCP sockets too. Replace /run/lightsd/socket by the output of:
-+   # echo $(lightsd --rundir)/socket
-+   lightsd_socket = socket.socket(socket.AF_UNIX)
-+   lightsd_socket.connect("/run/lightsd/socket")
-+   lightsd_socket.settimeout(2)  # seconds
-+
-+   # Prepare the request:
-+   request = json.dumps({
-+       "method": "get_light_state",
-+       "params": ["*"],
-+       "jsonrpc": "2.0",
-+       "id": str(uuid.uuid4()),
-+   }).encode(ENCODING, "surrogateescape")
-+
-+   # Send it:
-+   lightsd_socket.sendall(request)
-+
-+   # Prepare an empty buffer to accumulate the received data:
-+   response = bytearray()
-+   while True:
-+       # Read a chunk of data, and accumulate it in the response buffer:
-+       response += lightsd_socket.recv(READ_SIZE)
-+       try:
-+           # Try to load the received the data, we ignore encoding errors
-+           # since we only wanna know if the received data is complete.
-+           json.loads(response.decode(ENCODING, "ignore"))
-+           break  # Decoding was successful, we have received everything.
-+       except Exception:
-+           continue  # Decoding failed, data must be missing.
-+
-+   response = response.decode(ENCODING, "surrogateescape")
-+   print(json.loads(response))
-+
- Notes
-------
-+~~~~~
- 
-+- Use an incremental JSON parser if you have one handy: for responses multiple
-+  times the size of your receive window it will let you avoid decoding the whole
-+  response at each iteration of the read loop;
- - lightsd supports batch JSON-RPC requests, use them!
- 
- .. vim: set tw=80 spelllang=en spell:
-diff --git a/examples/lightsc.py b/examples/lightsc.py
---- a/examples/lightsc.py
-+++ b/examples/lightsc.py
-@@ -42,8 +42,28 @@
- 
- class LightsClient:
- 
--    def __init__(self, url):
-+    READ_SIZE = 4096
-+    TIMEOUT = 2  # seconds
-+    ENCODING = "utf-8"
-+
-+    class Error(Exception):
-+        pass
-+
-+    class TimeoutError(Error):
-+        pass
-+
-+    class JSONError(Error):
-+
-+        def __init__(self, response):
-+            self._response = response
-+
-+        def __str__(self):
-+            return "received invalid JSON: {}".format(self._response)
-+
-+    def __init__(self, url, encoding=ENCODING, timeout=TIMEOUT,
-+                 read_size=READ_SIZE):
-         self.url = url
-+        self.encoding = encoding
- 
-         parts = urllib.parse.urlparse(args.url)
- 
-@@ -55,7 +75,9 @@
-             self._socket = socket.create_connection((parts.hostname, parts.port))
-         else:
-             raise ValueError("Unsupported url {}".format(url))
-+        self._socket.settimeout(timeout)
- 
-+        self._read_size = read_size
-         self._pipeline = []
-         self._batch = False
- 
-@@ -75,15 +97,24 @@
-         }
- 
-     def _execute_payload(self, payload):
--        self._socket.send(json.dumps(payload).encode("utf-8"))
--        # FIXME: proper read loop
--        response = self._socket.recv(64 * 1024).decode("utf-8")
-+        response = bytearray()
-         try:
--            response = json.loads(response)
--        except Exception:
--            print("received invalid json: {}".format(response))
-+            payload = json.dumps(payload)
-+            payload = payload.encode(self.encoding, "surrogateescape")
-+            self._socket.sendall(payload)
- 
--        return response
-+            while True:
-+                response += self._socket.recv(self._read_size)
-+                try:
-+                    return json.loads(response.decode(
-+                        self.encoding, "surrogateescape"
-+                    ))
-+                except Exception:
-+                    continue
-+        except socket.timeout:
-+            if not response:
-+                raise self.TimeoutError
-+            raise self.JSONError(response)
- 
-     def _jsonrpc_call(self, method, params):
-         payload = self._make_payload(method, params)
-@@ -199,11 +230,6 @@
- 
- def _drop_to_shell(lightsc):
-     c = lightsc  # noqa
--    nb = "d073d501a0d5"  # noqa
--    fugu = "d073d500603b"  # noqa
--    neko = "d073d5018fb6"  # noqa
--    middle = "d073d502e530"  # noqa
--
-     banner = (
-         "Connected to {}, use the variable c to interact with your "
-         "bulbs:\n\n>>> r = c.get_light_state(\"*\")".format(c.url)
-@@ -227,7 +253,7 @@
-         lightsdrundir = subprocess.check_output(["lightsd", "--rundir"])
-     except Exception as ex:
-         print(
--            "Couldn't infer lightsd's runtime directory is lightsd installed? "
-+            "Couldn't infer lightsd's runtime directory, is lightsd installed? "
-             "({})\nTrying build/socket...".format(ex),
-             file=sys.stderr
-         )
-@@ -238,7 +264,7 @@
-         lightsdrundir = lightsdrundir.decode(encoding).strip()
- 
-     parser = argparse.ArgumentParser(
--        description="lightsc.py is an interactive lightsd Python client"
-+        description="Interactive lightsd Python client"
-     )
-     parser.add_argument(
-         "-u", "--url", type=str,
--- a/series	Mon Jan 04 15:34:40 2016 +0100
+++ b/series	Mon Jan 04 16:11:47 2016 +0100
@@ -1,8 +1,6 @@
-doc_semver.patch
 optional_jsonrpc_args.patch
 network_discovery.patch
 dont_use_ev_assign.patch
-fix_lightscpy_readloop.patch
 add_power_transition.patch
 open_gateway_on_any_bulb_response.patch #+future
 make_gateway_write_callbacks_low_priority.patch #+future