Mercurial > louis > mq > lightsd
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: