changeset 230:dae19f5f6e44

toggle command, misc stuff
author Louis Opter <kalessin@kalessin.fr>
date Sat, 08 Aug 2015 17:11:11 -0700
parents 1a2bd9fc41b1
children 8ebf9e9e06e6
files add_toggle.patch fix_a_snprintf_return_value_check.patch more_readme_and_doc_updates.patch series
diffstat 4 files changed, 566 insertions(+), 26 deletions(-) [+]
line wrap: on
line diff
--- a/add_toggle.patch	Sat Aug 08 14:37:39 2015 -0700
+++ b/add_toggle.patch	Sat Aug 08 17:11:11 2015 -0700
@@ -1,39 +1,97 @@
 # HG changeset patch
-# Parent  f53e8ce62db97a7c4f33e370bab4c0b243d91311
+# Parent  030a3143a5b087c2d7a65c32659135c63be00bb3
 Add the toggle command: turn on a bulb if it's off and vice-versa
 
 This lets you change the state of a bulb without having to query its
 state first, super useful with the command pipe.
 
+diff --git a/README.rst b/README.rst
+--- a/README.rst
++++ b/README.rst
+@@ -26,11 +26,11 @@
+ 
+ - power_off (with auto-retry);
+ - power_on (with auto-retry);
++- power_toggle (power on if off and vice-versa, with auto-retry);
+ - set_light_from_hsbk;
+ - set_waveform (change the light according to a function like SAW or SINE);
+ - get_light_state;
+-- tag/untag (group/ungroup bulbs together);
+-- toggle (power on if off and vice-versa, coming up).
++- tag/untag (group/ungroup bulbs together).
+ 
+ The JSON-RPC interface works on top of TCP/IPv4/v6, Unix sockets (coming up) or
+ over a command pipe (named pipe, see mkfifo(1)).
 diff --git a/core/jsonrpc.c b/core/jsonrpc.c
 --- a/core/jsonrpc.c
 +++ b/core/jsonrpc.c
-@@ -965,6 +965,19 @@
+@@ -938,44 +938,24 @@
+     );
+ }
+ 
+-static void
+-lgtd_jsonrpc_check_and_call_power_on(struct lgtd_client *client)
+-{
+-    struct lgtd_proto_target_list targets = SLIST_HEAD_INITIALIZER(&targets);
+-    bool ok = lgtd_jsonrpc_extract_target_list(&targets, client);
+-    if (!ok) {
+-        return;
+-    }
+-
+-    lgtd_proto_power_on(client, &targets);
+-    lgtd_proto_target_list_clear(&targets);
++#define CHECK_AND_CALL_TARGETS_ONLY_METHOD(proto_method)                       \
++static void                                                                    \
++lgtd_jsonrpc_check_and_call_##proto_method(struct lgtd_client *client)         \
++{                                                                              \
++    struct lgtd_proto_target_list targets = SLIST_HEAD_INITIALIZER(&targets);  \
++    bool ok = lgtd_jsonrpc_extract_target_list(&targets, client);              \
++    if (!ok) {                                                                 \
++        return;                                                                \
++    }                                                                          \
++                                                                               \
++    lgtd_proto_##proto_method(client, &targets);                               \
++    lgtd_proto_target_list_clear(&targets);                                    \
  }
  
+-static void
+-lgtd_jsonrpc_check_and_call_power_off(struct lgtd_client *client)
+-{
+-    struct lgtd_proto_target_list targets = SLIST_HEAD_INITIALIZER(&targets);
+-    bool ok = lgtd_jsonrpc_extract_target_list(&targets, client);
+-    if (!ok) {
+-        return;
+-    }
+-
+-    lgtd_proto_power_off(client, &targets);
+-    lgtd_proto_target_list_clear(&targets);
+-}
+-
+-static void
+-lgtd_jsonrpc_check_and_call_get_light_state(struct lgtd_client *client)
+-{
+-    struct lgtd_proto_target_list targets = SLIST_HEAD_INITIALIZER(&targets);
+-    bool ok = lgtd_jsonrpc_extract_target_list(&targets, client);
+-    if (!ok) {
+-        return;
+-    }
+-
+-    lgtd_proto_get_light_state(client, &targets);
+-    lgtd_proto_target_list_clear(&targets);
+-}
++CHECK_AND_CALL_TARGETS_ONLY_METHOD(power_on);
++CHECK_AND_CALL_TARGETS_ONLY_METHOD(power_off);
++CHECK_AND_CALL_TARGETS_ONLY_METHOD(power_toggle);
++CHECK_AND_CALL_TARGETS_ONLY_METHOD(get_light_state);
+ 
  static void
-+lgtd_jsonrpc_check_and_call_toggle(struct lgtd_client *client)
-+{
-+    struct lgtd_proto_target_list targets = SLIST_HEAD_INITIALIZER(&targets);
-+    bool ok = lgtd_jsonrpc_extract_target_list(&targets, client);
-+    if (!ok) {
-+        return;
-+    }
-+
-+    lgtd_proto_toggle(client, &targets);
-+    lgtd_proto_target_list_clear(&targets);
-+}
-+
-+static void
- lgtd_jsonrpc_check_and_call_get_light_state(struct lgtd_client *client)
- {
-     struct lgtd_proto_target_list targets = SLIST_HEAD_INITIALIZER(&targets);
-@@ -1074,6 +1087,10 @@
+ lgtd_jsonrpc_check_and_call_proto_tag_or_untag(struct lgtd_client *client,
+@@ -1074,6 +1054,10 @@
              lgtd_jsonrpc_check_and_call_power_off
          ),
          LGTD_JSONRPC_METHOD(
-+            "toggle", 1, // t
-+            lgtd_jsonrpc_check_and_call_toggle
++            "power_toggle", 1, // t
++            lgtd_jsonrpc_check_and_call_power_toggle
 +        ),
 +        LGTD_JSONRPC_METHOD(
              "set_light_from_hsbk", 6, // t, h, s, b, k, t
@@ -46,8 +104,8 @@
  }
  
  void
-+lgtd_proto_toggle(struct lgtd_client *client,
-+                  const struct lgtd_proto_target_list *targets)
++lgtd_proto_power_toggle(struct lgtd_client *client,
++                        const struct lgtd_proto_target_list *targets)
 +{
 +    assert(targets);
 +
@@ -93,7 +151,228 @@
                               int, float, int, bool);
  void lgtd_proto_power_on(struct lgtd_client *, const struct lgtd_proto_target_list *);
  void lgtd_proto_power_off(struct lgtd_client *, const struct lgtd_proto_target_list *);
-+void lgtd_proto_toggle(struct lgtd_client *, const struct lgtd_proto_target_list *);
++void lgtd_proto_power_toggle(struct lgtd_client *, const struct lgtd_proto_target_list *);
  void lgtd_proto_get_light_state(struct lgtd_client *, const struct lgtd_proto_target_list *);
  void lgtd_proto_tag(struct lgtd_client *, const struct lgtd_proto_target_list *, const char *);
  void lgtd_proto_untag(struct lgtd_client *, const struct lgtd_proto_target_list *, const char *);
+diff --git a/docs/protocol.rst b/docs/protocol.rst
+--- a/docs/protocol.rst
++++ b/docs/protocol.rst
+@@ -37,6 +37,10 @@
+ 
+    Power on the given bulb(s).
+ 
++.. function:: power_toggle(target)
++
++   Power on (if they are off) or power off (if they are on) the given bulb(s).
++
+ .. function:: set_light_from_hsbk(target, h, s, b, k, t)
+ 
+    :param float h: Hue from 0 to 360.
+diff --git a/examples/lightsc.py b/examples/lightsc.py
+--- a/examples/lightsc.py
++++ b/examples/lightsc.py
+@@ -1,7 +1,6 @@
+ #!/usr/bin/env python
+ 
+ import json
+-import pprint
+ import socket
+ import sys
+ import time
+@@ -69,6 +68,10 @@
+     return jsonrpc_call(socket, "power_off", {"target": target})
+ 
+ 
++def power_toggle(socket, target):
++    return jsonrpc_call(socket, "power_toggle", {"target": target})
++
++
+ def get_light_state(socket, target):
+     return jsonrpc_call(socket, "get_light_state", [target])
+ 
+diff --git a/tests/core/jsonrpc/test_jsonrpc_check_and_call_power_toggle.c b/tests/core/jsonrpc/test_jsonrpc_check_and_call_power_toggle.c
+new file mode 100644
+--- /dev/null
++++ b/tests/core/jsonrpc/test_jsonrpc_check_and_call_power_toggle.c
+@@ -0,0 +1,58 @@
++#include "jsonrpc.c"
++
++#include "mock_client_buf.h"
++
++#define MOCKED_LGTD_PROTO_POWER_TOGGLE
++#include "test_jsonrpc_utils.h"
++
++static bool power_toggle_called = false;
++
++void
++lgtd_proto_power_toggle(struct lgtd_client *client,
++                        const struct lgtd_proto_target_list *targets)
++{
++    if (!client) {
++        errx(1, "missing client!");
++    }
++
++    if (strcmp(SLIST_FIRST(targets)->target, "*")) {
++        errx(
++            1, "Invalid target [%s] (expected=[*])",
++            SLIST_FIRST(targets)->target
++        );
++    }
++    power_toggle_called = true;
++}
++
++int
++main(void)
++{
++    jsmntok_t tokens[32];
++    const char json[] = ("{"
++        "\"jsonrpc\": \"2.0\","
++        "\"method\": \"power_toggle\","
++        "\"params\": {\"target\": \"*\"},"
++        "\"id\": \"42\""
++    "}");
++    int parsed = parse_json(
++        tokens, LGTD_ARRAY_SIZE(tokens), json, sizeof(json)
++    );
++
++    bool ok;
++    struct lgtd_jsonrpc_request req = TEST_REQUEST_INITIALIZER;
++    struct lgtd_client client = {
++        .io = NULL, .current_request = &req, .json = json
++    };
++    ok = lgtd_jsonrpc_check_and_extract_request(&req, tokens, parsed, json);
++    if (!ok) {
++        errx(1, "can't parse request");
++    }
++
++    lgtd_jsonrpc_check_and_call_power_toggle(&client);
++
++    if (!power_toggle_called) {
++        errx(1, "lgtd_proto_power_toggle wasn't called");
++    }
++
++    return 0;
++}
+diff --git a/tests/core/jsonrpc/test_jsonrpc_utils.h b/tests/core/jsonrpc/test_jsonrpc_utils.h
+--- a/tests/core/jsonrpc/test_jsonrpc_utils.h
++++ b/tests/core/jsonrpc/test_jsonrpc_utils.h
+@@ -121,3 +121,13 @@
+     (void)tag_label;
+ }
+ #endif
++
++#ifndef MOCKED_LGTD_PROTO_POWER_TOGGLE
++void
++lgtd_proto_power_toggle(struct lgtd_client *client,
++                        const struct lgtd_proto_target_list *targets)
++{
++    (void)client;
++    (void)targets;
++}
++#endif
+diff --git a/tests/core/proto/test_proto_toggle.c b/tests/core/proto/test_proto_power_toggle.c
+rename from tests/core/proto/test_proto_toggle.c
+rename to tests/core/proto/test_proto_power_toggle.c
+--- a/tests/core/proto/test_proto_toggle.c
++++ b/tests/core/proto/test_proto_power_toggle.c
+@@ -128,7 +128,7 @@
+     struct lgtd_client client = { .io = FAKE_BUFFEREVENT };
+     struct lgtd_proto_target_list *targets = (void *)0x2a;
+ 
+-    lgtd_proto_toggle(&client, targets);
++    lgtd_proto_power_toggle(&client, targets);
+ 
+     const char expected[] = "true";
+ 
+diff --git a/tests/core/proto/test_proto_power_toggle_targets_to_device_fails.c b/tests/core/proto/test_proto_power_toggle_targets_to_device_fails.c
+new file mode 100644
+--- /dev/null
++++ b/tests/core/proto/test_proto_power_toggle_targets_to_device_fails.c
+@@ -0,0 +1,86 @@
++#include "proto.c"
++
++#include "mock_client_buf.h"
++#include "mock_daemon.h"
++#include "mock_gateway.h"
++#include "tests_utils.h"
++
++#define MOCKED_ROUTER_SEND_TO_DEVICE
++#define MOCKED_ROUTER_TARGETS_TO_DEVICES
++#define MOCKED_ROUTER_DEVICE_LIST_FREE
++#define MOCKED_CLIENT_SEND_ERROR
++#include "tests_proto_utils.h"
++
++static bool device_list_free_called = false;
++
++void
++lgtd_router_device_list_free(struct lgtd_router_device_list *devices)
++{
++    if (!devices) {
++        lgtd_errx(1, "the device list must be passed");
++    }
++
++    device_list_free_called = true;
++}
++
++struct lgtd_router_device_list *
++lgtd_router_targets_to_devices(const struct lgtd_proto_target_list *targets)
++{
++    if (targets != (void *)0x2a) {
++        lgtd_errx(1, "unexpected targets list");
++    }
++
++    return NULL;
++}
++
++static int router_send_to_device_call_count = 0;
++
++void
++lgtd_router_send_to_device(struct lgtd_lifx_bulb *bulb,
++                           enum lgtd_lifx_packet_type pkt_type,
++                           void *pkt)
++{
++    (void)bulb;
++    (void)pkt_type;
++    (void)pkt;
++
++    router_send_to_device_call_count++;
++}
++
++static int client_send_error_call_count = 0;
++
++void
++lgtd_client_send_error(struct lgtd_client *client,
++                       enum lgtd_client_error_code error,
++                       const char *msg)
++{
++    (void)client;
++    (void)error;
++    (void)msg;
++
++    client_send_error_call_count++;
++}
++
++int
++main(void)
++{
++    struct lgtd_client client = { .io = FAKE_BUFFEREVENT };
++    struct lgtd_proto_target_list *targets = (void *)0x2a;
++
++    lgtd_proto_power_toggle(&client, targets);
++
++    if (client_send_error_call_count != 1) {
++        errx(1, "lgtd_client_send_error called %d times (expected 1)",
++            client_send_error_call_count
++        );
++    }
++
++    if (router_send_to_device_call_count) {
++        errx(
++            1, "lgtd_router_send_to_device called %d times (expected 0)",
++            router_send_to_device_call_count
++        );
++    }
++
++    return 0;
++}
--- a/fix_a_snprintf_return_value_check.patch	Sat Aug 08 14:37:39 2015 -0700
+++ b/fix_a_snprintf_return_value_check.patch	Sat Aug 08 17:11:11 2015 -0700
@@ -1,6 +1,6 @@
 # HG changeset patch
 # Parent  c2a45e03f05e05e7c31723254ca63b82df551bda
-Fix an incorrectly check snprintf return value.
+Fix an incorrectly checked snprintf return value.
 
 In this case we would have returned some invalid output instead of
 skipping one bulb.
@@ -13,7 +13,174 @@
              bulb->state.label
          );
 -        if (written == sizeof(buf)) {
-+        if (written >= sizeof(buf)) {
++        if (written >= (int)sizeof(buf)) {
              lgtd_warnx(
                  "can't send state of bulb %s (%s) to client "
                  "[%s]:%hu: output buffer to small",
+diff --git a/tests/core/proto/test_proto_toggle.c b/tests/core/proto/test_proto_toggle.c
+new file mode 100644
+--- /dev/null
++++ b/tests/core/proto/test_proto_toggle.c
+@@ -0,0 +1,162 @@
++#include "proto.c"
++
++#include "mock_client_buf.h"
++#include "mock_daemon.h"
++#include "mock_gateway.h"
++#include "tests_utils.h"
++
++#define MOCKED_ROUTER_SEND_TO_DEVICE
++#define MOCKED_ROUTER_TARGETS_TO_DEVICES
++#define MOCKED_ROUTER_DEVICE_LIST_FREE
++#include "tests_proto_utils.h"
++
++static bool device_list_free_called = false;
++
++void
++lgtd_router_device_list_free(struct lgtd_router_device_list *devices)
++{
++    if (!devices) {
++        lgtd_errx(1, "the device list must be passed");
++    }
++
++    device_list_free_called = true;
++}
++
++struct lgtd_router_device_list *
++lgtd_router_targets_to_devices(const struct lgtd_proto_target_list *targets)
++{
++    if (targets != (void *)0x2a) {
++        lgtd_errx(1, "unexpected targets list");
++    }
++
++    static struct lgtd_router_device_list devices =
++        SLIST_HEAD_INITIALIZER(&devices);
++
++    static struct lgtd_lifx_gateway gw_bulb_1 = {
++        .bulbs = LIST_HEAD_INITIALIZER(&gw_bulb_1.bulbs)
++    };
++    static struct lgtd_lifx_bulb bulb_1 = {
++        .addr = { 1, 2, 3, 4, 5 },
++        .state = {
++            .hue = 0xaaaa,
++            .saturation = 0xffff,
++            .brightness = 0xbbbb,
++            .kelvin = 3600,
++            .label = "wave",
++            .power = LGTD_LIFX_POWER_ON,
++            .tags = 0
++        },
++        .gw = &gw_bulb_1
++    };
++    static struct lgtd_router_device device_1 = { .device = &bulb_1 };
++    SLIST_INSERT_HEAD(&devices, &device_1, link);
++
++    struct lgtd_lifx_tag *gw_2_tag_1 = lgtd_tests_insert_mock_tag("vapor");
++    struct lgtd_lifx_tag *gw_2_tag_2 = lgtd_tests_insert_mock_tag("d^-^b");
++    struct lgtd_lifx_tag *gw_2_tag_3 = lgtd_tests_insert_mock_tag("wave~");
++    static struct lgtd_lifx_gateway gw_bulb_2 = {
++        .bulbs = LIST_HEAD_INITIALIZER(&gw_bulb_2.bulbs),
++        .tag_ids = 0x7
++    };
++    lgtd_tests_add_tag_to_gw(gw_2_tag_1, &gw_bulb_2, 0);
++    lgtd_tests_add_tag_to_gw(gw_2_tag_2, &gw_bulb_2, 1);
++    lgtd_tests_add_tag_to_gw(gw_2_tag_3, &gw_bulb_2, 2);
++    static struct lgtd_lifx_bulb bulb_2 = {
++        .addr = { 5, 4, 3, 2, 1 },
++        .state = {
++            .hue = 0x0000,
++            .saturation = 0x0000,
++            .brightness = 0xffff,
++            .kelvin = 4000,
++            .label = "light",
++            .power = LGTD_LIFX_POWER_OFF,
++            .tags = 0x3
++        },
++        .gw = &gw_bulb_2
++    };
++    static struct lgtd_router_device device_2 = { .device = &bulb_2 };
++    SLIST_INSERT_HEAD(&devices, &device_2, link);
++
++    return &devices;
++}
++
++static int router_send_to_device_call_count = 0;
++
++void
++lgtd_router_send_to_device(struct lgtd_lifx_bulb *bulb,
++                           enum lgtd_lifx_packet_type pkt_type,
++                           void *pkt)
++{
++    if (!bulb) {
++        errx(1, "lgtd_router_send_to_device called without a device");
++    }
++
++    if (pkt_type != LGTD_LIFX_SET_POWER_STATE) {
++        errx(
++            1, "lgtd_router_send_to_device got packet type %#x (expected %#x)",
++            pkt_type, LGTD_LIFX_SET_POWER_STATE
++        );
++    }
++
++    if (!pkt) {
++        errx(1, "lgtd_router_send_to_device called without a packet");
++    }
++
++    struct lgtd_lifx_packet_power_state *payload = pkt;
++
++    if (!strcmp(bulb->state.label, "light")) {
++        if (payload->power != LGTD_LIFX_POWER_ON) {
++            errx(1, "bulb light should be turned off");
++        }
++    } else if (!strcmp(bulb->state.label, "wave")) {
++        if (payload->power != LGTD_LIFX_POWER_OFF) {
++            errx(1, "bulb wave should be turned on");
++        }
++    } else {
++        errx(
++            1, "lgtd_router_send_to_deviceg got an unknown bulb: %s",
++            bulb->state.label
++        );
++    }
++
++    router_send_to_device_call_count++;
++}
++
++int
++main(void)
++{
++    struct lgtd_client client = { .io = FAKE_BUFFEREVENT };
++    struct lgtd_proto_target_list *targets = (void *)0x2a;
++
++    lgtd_proto_toggle(&client, targets);
++
++    const char expected[] = "true";
++
++    if (client_write_buf_idx != sizeof(expected) - 1) {
++        errx(
++            1, "%d bytes written, expected %lu (got %.*s instead of %s)",
++            client_write_buf_idx, sizeof(expected) - 1UL,
++            client_write_buf_idx, client_write_buf, expected
++        );
++    }
++
++    if (memcmp(expected, client_write_buf, sizeof(expected) - 1)) {
++        errx(
++            1, "got %.*s instead of %s",
++            client_write_buf_idx, client_write_buf, expected
++        );
++    }
++
++    if (!device_list_free_called) {
++        errx(1, "the list of devices hasn't been freed");
++    }
++
++    if (router_send_to_device_call_count != 2) {
++        errx(
++            1, "lgtd_router_send_to_device called %d times (expected 2)",
++            router_send_to_device_call_count
++        );
++    }
++
++    return 0;
++}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/more_readme_and_doc_updates.patch	Sat Aug 08 17:11:11 2015 -0700
@@ -0,0 +1,93 @@
+# HG changeset patch
+# Parent  3e0bfd2efc8afcc44834b4ff006934f5c238c353
+Document tag/untag update known issues
+
+diff --git a/README.rst b/README.rst
+--- a/README.rst
++++ b/README.rst
+@@ -82,11 +82,11 @@
+    .../lightsd/build$ make -j5
+ 
+ Finally, to start lightsd with the jsonrpc interface listening on localhost
+-port 1234:
++port 1234 and a command pipe named lightsd.cmd:
+ 
+ ::
+ 
+-   .../lightsd/build$ core/lightsd -v info -l ::1:1234
++   .../lightsd/build$ core/lightsd -v info -l ::1:1234 -c lightsd.cmd
+ 
+ lightsd forks in the background by default, display running processes and check
+ how we are doing:
+@@ -106,18 +106,22 @@
+ Known issues
+ ------------
+ 
+-The grouping (tagging) code of the LIFX White 800 is bugged: after a tagging
+-operation the LIFX White 800 keep saying it has no tags. Reboot the bulb to make
+-the tags appears.
++The grouping (tagging) code of the LIFX White 800 (and presumably the Color 650
++as well) is bugged: after a tagging operation the LIFX White 800 keep saying it
++has no tags. Reboot the bulb to make the tags appears.
+ 
+ Power ON/OFF are the only commands with auto-retry, i.e: lightsd will keep
+ sending the command to the bulb until its state changes. This is not implemented
+ (yet) for ``set_light_from_hsbk``, ``set_waveform``, ``tag`` and ``untag``.
+ 
+-While lighsd appears to be pretty stable, if you want to run lightsd in the
+-background, I recommend doing so in a processor supervisor (e.g: Supervisor_)
+-that can restart lightsd if it crashes. Otherwise, please feel free to report
+-crashes to me.
++In general, crappy WiFi network with latency, jitter or packet loss are gonna be
++challenging until lightsd has an auto-retry mechanism, there is also room for
++optimizations in how lightsd communicates with the bulbs.
++
++While lightsd appears to be pretty stable, if you want to run lightsd in the
++background, I recommend doing so in a process supervisor (e.g: Supervisor_) that
++can restart lightsd if it crashes. Otherwise, please feel free to report crashes
++to me.
+ 
+ .. _Supervisor: http://www.supervisord.org/
+ 
+diff --git a/docs/protocol.rst b/docs/protocol.rst
+--- a/docs/protocol.rst
++++ b/docs/protocol.rst
+@@ -12,7 +12,7 @@
+ bulb(s) the operation should apply:
+ 
+ +-----------------------------+-----------------------------------------------+
+-| ``\*``                      | targets all bulbs                             |
++| ``*``                       | targets all bulbs                             |
+ +-----------------------------+-----------------------------------------------+
+ | ``#TagName``                | targets bulbs tagged with *TagName*           |
+ +-----------------------------+-----------------------------------------------+
+@@ -75,4 +75,29 @@
+     - power: boolean, true when the bulb is powered on false otherwise;
+     - tags: list of tags applied to the bulb (utf-8 encoded strings).
+ 
++.. function:: tag(target, label)
++
++   Tag (group) the given target bulb(s) with the given label (group name), then
++   label can be used as a target by prefixing it with ``#``.
++
++   To add a device to an existing "group" simply do:
++
++   ::
++
++      tag(["#myexistingtag", "bulbtoadd"], "myexistingtag")
++
++   .. note::
++
++      Notice how ``#`` is prepended to the tag label depending on whether it's
++      used as a target or a regular argument.
++
++.. function:: untag(target, label)
++
++   Remove the given tag from the given target bulb(s). To completely delete a
++   tag (group), simple do:
++
++   ::
++
++      untag("#myexistingtag", "myexistingtag")
++
+ .. vim: set tw=80 spelllang=en spell:
--- a/series	Sat Aug 08 14:37:39 2015 -0700
+++ b/series	Sat Aug 08 17:11:11 2015 -0700
@@ -1,2 +1,3 @@
 fix_a_snprintf_return_value_check.patch
 add_toggle.patch
+more_readme_and_doc_updates.patch