Mercurial > louis > mq > lightsd
changeset 484:8f5a1a4862dc
WIP on power_transition, a lot of changes from the week
author | Louis Opter <kalessin@kalessin.fr> |
---|---|
date | Sat, 09 Jul 2016 13:23:18 -0700 |
parents | 83736d21781d |
children | 0062162318d2 |
files | add_power_transition.patch |
diffstat | 1 files changed, 289 insertions(+), 90 deletions(-) [+] |
line wrap: on
line diff
--- a/add_power_transition.patch Sat Jul 02 22:53:58 2016 -0700 +++ b/add_power_transition.patch Sat Jul 09 13:23:18 2016 -0700 @@ -1,5 +1,5 @@ # HG changeset patch -# Parent e2823e109f6276343e856094843e659bff2a023e +# Parent 401bd90c164a23e6b23aa4842135d4ad142894b2 Add a transition argument to the power functions Unlike LIFX's implementation, lightsd will properly get the bulbs to @@ -59,7 +59,7 @@ new file mode 100644 --- /dev/null +++ b/core/effect.c -@@ -0,0 +1,213 @@ +@@ -0,0 +1,242 @@ +// Copyright (c) 2015, Louis Opter <kalessin@kalessin.fr> +// +// This file is part of lighstd. @@ -104,6 +104,9 @@ + + lgtd_info("ending effect %s, id=%p", effect->name, effect); + LIST_REMOVE(effect, link); ++ if (effect->duration_timer) { ++ lgtd_timer_stop(effect->duration_timer); ++ } + if (effect->timer) { + lgtd_timer_stop(effect->timer); + } @@ -113,15 +116,26 @@ +static void +lgtd_effect_duration_callback(struct lgtd_timer *timer, union lgtd_timer_ctx ctx) +{ ++ (void)timer; ++ + struct lgtd_effect *effect = (struct lgtd_effect *)ctx.as_ptr; + if (effect->duration_cb) { -+ lgtd_info( -+ "calling duration callback for effect %s, id=%p", -+ effect->name, effect -+ ); -+ effect->duration_cb(effect); ++ lgtd_time_mono_t now = lgtd_time_monotonic_msecs(); ++ if (effect->ends_at - now < LGTD_EFFECT_STALE_THRESHOLD_MS) { ++ lgtd_info( ++ "calling duration callback for effect %s, id=%p", ++ effect->name, effect ++ ); ++ effect->duration_cb(effect); ++ } else { ++ lgtd_warnx( ++ "not calling duration callback for stale effect %s created " ++ "%jums ago, id=%p, duration=%jums", ++ effect->name, now - effect->created_at, effect, ++ effect->ends_at - effect->created_at ++ ); ++ } + } -+ lgtd_timer_stop(timer); + lgtd_effect_stop(effect); +} + @@ -133,17 +147,25 @@ + struct lgtd_effect *effect = (struct lgtd_effect *)ctx.as_ptr; + + lgtd_time_mono_t now = lgtd_time_monotonic_msecs(); -+ // if the effect is finite, check that this callback isn't being called -+ // after the effect has ended (e.g: the computer went to sleep): -+ if (effect->duration && now > effect->created_at + effect->duration) { -+ lgtd_time_mono_t diff = now - effect->created_at + effect->duration; -+ lgtd_warnx( -+ "stopping stale periodic effect %s created %jums ago, id=%p, " -+ "duration=%dms", -+ effect->name, (uintmax_t)diff, effect, effect->duration -+ ); -+ lgtd_effect_stop(effect); -+ return; ++ if (effect->ends_at && now > effect->ends_at) { ++ // check if the effect is stale (e.g: the computer went to sleep) ++ // and stop it if it is the case: ++ if (effect->ends_at - now > LGTD_EFFECT_STALE_THRESHOLD_MS) { ++ lgtd_warnx( ++ "stopping stale periodic effect %s created %jums ago, " ++ "id=%p, duration=%jums", ++ effect->name, now - effect->created_at, effect, ++ effect->ends_at - effect->created_at ++ ); ++ lgtd_effect_stop(effect); ++ return; ++ } ++ // if there is a duration callback pending then stop the periodic ++ // timer but apply the effect one last time (we're not stale yet). ++ if (event_pending(effect->duration_timer->event, EV_TIMEOUT, NULL)) { ++ lgtd_timer_stop(effect->timer); ++ effect->timer = NULL; ++ } + } + + lgtd_info( @@ -177,7 +199,7 @@ + + effect->name = name; + effect->created_at = lgtd_time_monotonic_msecs(); -+ effect->duration = duration; ++ effect->ends_at = effect->created_at + duration; + effect->duration_cb = duration_cb; + effect->apply_cb = apply_cb; + effect->ctx = ctx; @@ -185,15 +207,14 @@ + + union lgtd_timer_ctx timer_ctx = { .as_ptr = effect }; + -+ struct lgtd_timer *duration_timer = NULL; + if (duration) { -+ duration_timer = lgtd_timer_start( ++ effect->duration_timer = lgtd_timer_start( + LGTD_TIMER_DEFAULT_FLAGS, + duration, + lgtd_effect_duration_callback, + timer_ctx + ); -+ if (!duration_timer) { ++ if (!effect->duration_timer) { + goto err; + } + } @@ -214,6 +235,7 @@ + ); + goto err; + } ++ timer_flags |= LGTD_TIMER_EVENT_PRIORITY_HIGHEST; + effect->timer = lgtd_timer_start( + timer_flags, timer_ms, lgtd_effect_timer_callback, timer_ctx + ); @@ -222,23 +244,29 @@ + } + if (duration) { + lgtd_info( -+ "starting effect %s, id=%p, tick=%dms, duration=%dms", -+ name, effect, timer_ms, duration ++ "starting effect %s, id=%p, created_at=%jums, " ++ "tick=%dms, duration=%dms", ++ name, effect, effect->created_at, timer_ms, duration + ); + } else { + lgtd_info( -+ "starting effect %s, id=%p, tick=%dms, duration=infinite", -+ name, effect, timer_ms ++ "starting effect %s, id=%p, created_a=%jums, " ++ "tick=%dms, duration=infinite", ++ name, effect, effect->created_at, timer_ms + ); + } + } else { // finite or instant effect + if (duration) { + lgtd_info( -+ "starting effect %s, id=%p, duration=%dms", name, effect, duration ++ "starting effect %s, id=%p, created_at=%jums, " ++ "duration=%dms", ++ name, effect, effect->created_at, duration + ); + } else { + lgtd_info( -+ "starting effect %s, id=%p, duration=instant", name, effect ++ "starting effect %s, id=%p, created_at=%jums, " ++ "duration=instant", ++ name, effect, effect->created_at + ); + } + if (effect->apply_cb) { @@ -256,12 +284,13 @@ + return effect; + +err: -+ if (duration_timer) { -+ lgtd_timer_stop(duration_timer); ++ if (effect->duration_timer) { ++ lgtd_timer_stop(effect->duration_timer); + } + if (effect->timer) { + lgtd_timer_stop(effect->timer); + } ++ LIST_REMOVE(effect, link); + free(effect); + return NULL; +} @@ -277,7 +306,7 @@ new file mode 100644 --- /dev/null +++ b/core/effect.h -@@ -0,0 +1,54 @@ +@@ -0,0 +1,59 @@ +// Copyright (c) 2015, Louis Opter <kalessin@kalessin.fr> +// +// This file is part of lighstd. @@ -306,7 +335,8 @@ + LIST_ENTRY(lgtd_effect) link; + const char *name; + lgtd_time_mono_t created_at; -+ int duration; ++ lgtd_time_mono_t ends_at; ++ struct lgtd_timer *duration_timer; + void (*duration_cb)(const struct lgtd_effect *); + struct lgtd_timer *timer; + void (*apply_cb)(const struct lgtd_effect *); @@ -315,6 +345,8 @@ +}; +LIST_HEAD(lgtd_effect_list, lgtd_effect); + ++enum { LGTD_EFFECT_STALE_THRESHOLD_MS = 2000 }; ++ +extern struct lgtd_effect_list lgtd_effects; + +static inline uintptr_t @@ -323,6 +355,8 @@ + return (uintptr_t)effect; +} + ++// if the duration and apply callback tick at the same time, the apply cb ++// will be executed first. +struct lgtd_effect *lgtd_effect_start(const char *, // name + int, // duration + void (*)(const struct lgtd_effect *), // duration cb @@ -585,7 +619,36 @@ #include "lightsd.h" struct lgtd_opts lgtd_opts = { -@@ -188,6 +189,7 @@ +@@ -107,7 +108,15 @@ + lgtd_configure_libevent(void) + { + event_set_log_callback(lgtd_libevent_log); ++#ifndef NDEBUG ++ event_enable_debug_mode(); ++#endif + lgtd_ev_base = event_base_new(); ++ if (lgtd_ev_base && ++ !event_base_priority_init(lgtd_ev_base, LGTD_EVENT_N_PRIORITIES)) { ++ return; ++ } ++ lgtd_err(1, "can't configure libevent"); + } + + static void +@@ -138,8 +147,10 @@ + lgtd_close_signal_handling(void) + { + for (int i = 0; i != LGTD_ARRAY_SIZE(lgtd_signals); i++) { +- event_del(lgtd_signal_evs[i]); +- event_free(lgtd_signal_evs[i]); ++ if (lgtd_signal_evs[i]) { ++ event_del(lgtd_signal_evs[i]); ++ event_free(lgtd_signal_evs[i]); ++ } + } + } + +@@ -188,11 +199,14 @@ lgtd_listen_close_all(); lgtd_command_pipe_close_all(); lgtd_client_close_all(); @@ -593,10 +656,34 @@ lgtd_lifx_broadcast_close(); lgtd_lifx_gateway_close_all(); lgtd_timer_stop_all(); + lgtd_close_signal_handling(); +- event_base_free(lgtd_ev_base); ++ if (lgtd_ev_base) { ++ event_base_free(lgtd_ev_base); ++ } + #if LIBEVENT_VERSION_NUMBER >= 0x02010100 + libevent_global_shutdown(); + #endif diff --git a/core/lightsd.h b/core/lightsd.h --- a/core/lightsd.h +++ b/core/lightsd.h -@@ -137,9 +137,9 @@ +@@ -97,6 +97,15 @@ + LGTD_ERR + }; + ++// XXX: Update enum lgtd_timer_flags in timer.h when updating this: ++enum lgtd_event_priority { ++ LGTD_EVENT_PRIORITY_HIGHEST = 0, ++ LGTD_EVENT_PRIORITY_LOWEST = 2, ++ // see libevent documentation: ++ LGTD_EVENT_N_PRIORITIES = LGTD_EVENT_PRIORITY_LOWEST + 1, ++ LGTD_EVENT_PRIORITY_DEFAULT = LGTD_EVENT_N_PRIORITIES / 2, ++}; ++ + enum { LGTD_ERROR_MSG_BUFSIZE = 2048 }; + + // FIXME: introspect sizeof(sockaddr_un.sun_path) with CMake to generate a +@@ -137,9 +146,9 @@ extern struct event_base *lgtd_ev_base; extern const char *lgtd_progname; @@ -824,6 +911,88 @@ // TODO: return that from the functions in there and handle it: enum lgtd_router_error { LGTD_ROUTER_INVALID_TARGET_ERROR, +diff --git a/core/timer.c b/core/timer.c +--- a/core/timer.c ++++ b/core/timer.c +@@ -21,6 +21,7 @@ + #include <stdbool.h> + #include <stdint.h> + #include <stdlib.h> ++#include <strings.h> + + #include <event2/event.h> + #include <event2/util.h> +@@ -42,6 +43,17 @@ + timer->callback(timer, timer->ctx); + } + ++static enum lgtd_event_priority ++lgtd_timer_flags_to_priority(int flags) ++{ ++ flags &= LGTD_TIMER_FLAGS_EVENT_PRIORITY_MASK; ++ if (!flags) { ++ return LGTD_EVENT_PRIORITY_DEFAULT; ++ } ++ ++ return INT_NBITS - ffs(flags); ++} ++ + struct lgtd_timer * + lgtd_timer_start(int flags, + int ms, +@@ -60,6 +72,8 @@ + timer->ctx = ctx; + LIST_INSERT_HEAD(&lgtd_timers, timer, link); + ++ enum lgtd_event_priority priority = lgtd_timer_flags_to_priority(flags); ++ lgtd_info("timer priority = %u", priority); + struct timeval tv = LGTD_MSECS_TO_TIMEVAL(ms); + timer->event = event_new( + lgtd_ev_base, +@@ -68,7 +82,9 @@ + lgtd_timer_callback, + timer + ); +- if (!timer->event || evtimer_add(timer->event, &tv)) { ++ if (!timer->event ++ || event_priority_set(timer->event, priority) ++ || evtimer_add(timer->event, &tv)) { + LIST_REMOVE(timer, link); + if (timer->event) { + event_free(timer->event); +diff --git a/core/timer.h b/core/timer.h +--- a/core/timer.h ++++ b/core/timer.h +@@ -33,10 +33,20 @@ + }; + LIST_HEAD(lgtd_timer_list, lgtd_timer); + ++#define INT_NBITS (sizeof(int) * 8) + enum lgtd_timer_flags { + LGTD_TIMER_DEFAULT_FLAGS = 0, + LGTD_TIMER_ACTIVATE_NOW = 1, + LGTD_TIMER_PERSISTENT = 1 << 1, ++ // XXX: or do the opposite (reserve the first 8 bits for those ++ // flags so we actually don't have to do any conversion): ++ LGTD_TIMER_EVENT_PRIORITY_LOWEST = 1 << (INT_NBITS - 3), ++ LGTD_TIMER_EVENT_PRIORITY_DEFAULT = 1 << (INT_NBITS - 2), ++ LGTD_TIMER_EVENT_PRIORITY_HIGHEST = 1 << (INT_NBITS - 1), ++}; ++enum { ++ LGTD_TIMER_FLAGS_EVENT_PRIORITY_MASK = ++ ~(LGTD_TIMER_EVENT_PRIORITY_LOWEST - 1) + }; + + // Activate the timer now, in other words make the callback pending: +@@ -70,7 +80,7 @@ + void lgtd_timer_stop_all(void); + // NOTE: if you start a persistent timer and don't keep track of it, make sure + // you don't end up in a callback using a context that has been freed. +-struct lgtd_timer *lgtd_timer_start(int, ++struct lgtd_timer *lgtd_timer_start(int, // flags + int, // ms + void (*)(struct lgtd_timer *, + union lgtd_timer_ctx), diff --git a/core/utils.c b/core/utils.c --- a/core/utils.c +++ b/core/utils.c @@ -894,7 +1063,7 @@ new file mode 100644 --- /dev/null +++ b/effects/power_transition.c -@@ -0,0 +1,305 @@ +@@ -0,0 +1,333 @@ +// Copyright (c) 2015, Louis Opter <kalessin@kalessin.fr> +// +// This file is part of lighstd. @@ -975,6 +1144,9 @@ + assert(effect); + + struct lgtd_effect_power_transition_ctx *ctx = effect->ctx.as_ptr; ++ lgtd_info( ++ "now = %ju, end_time=%ju", lgtd_time_monotonic_msecs(), ctx->end_time ++ ); + + struct lgtd_effect_power_transition_target *target; + SLIST_FOREACH(target, &ctx->targets, link) { @@ -1055,21 +1227,32 @@ + assert(effect); + + struct lgtd_effect_power_transition_ctx *ctx = effect->ctx.as_ptr; ++ lgtd_time_mono_t now = lgtd_time_monotonic_msecs(); ++ lgtd_time_mono_t next_tick = ( ++ now + LGTD_EFFECT_POWER_TRANSITION_TIMER_MS ++ ); + -+ switch (effect->apply_cnt) { -+ case 0: -+ lgtd_effect_power_transition_off_set_brightness_to_zero( -+ &ctx->targets, ctx->duration -+ ); -+ break; -+ case 1: ++ lgtd_info("now = %ju, next_tick = %ju, end_time=%ju", now, next_tick, ctx->end_time); ++ if (next_tick >= ctx->end_time) { + lgtd_effect_power_transition_off_power_off(&ctx->targets); -+ break; -+ default: -+ // we'll end up there if the transition was less long than -+ // LGTD_EFFECT_POWER_TRANSITION_OFF_RESTORE_LIGHT_COLOR_DELAY_MSECS -+ break; ++ return; + } ++ lgtd_effect_power_transition_off_set_brightness_to_zero( ++ &ctx->targets, ctx->end_time - lgtd_time_monotonic_msecs() ++ ); ++} ++ ++static void ++lgtd_effect_power_transition_on_duration_callback(const struct lgtd_effect *effect) ++{ ++ assert(effect); ++ ++ struct lgtd_effect_power_transition_ctx *ctx = effect->ctx.as_ptr; ++ lgtd_info( ++ "now = %ju, end_time=%ju", lgtd_time_monotonic_msecs(), ctx->end_time ++ ); ++ lgtd_effect_power_transition_clear_target_list(&ctx->targets); ++ free(ctx); +} + +static void @@ -1078,49 +1261,58 @@ + assert(effect); + + struct lgtd_effect_power_transition_ctx *ctx = effect->ctx.as_ptr; ++ uint32_t transition = LGTD_MIN( ++ UINT32_MAX, ctx->end_time - lgtd_time_monotonic_msecs() ++ ); + ++ lgtd_time_mono_t now = lgtd_time_monotonic_msecs(); ++ lgtd_time_mono_t next_tick = ( ++ now + LGTD_EFFECT_POWER_TRANSITION_TIMER_MS ++ ); ++ lgtd_info("now = %ju, next_tick = %ju, end_time=%ju", now, next_tick, ctx->end_time); + struct lgtd_effect_power_transition_target *target; + SLIST_FOREACH(target, &ctx->targets, link) { -+ struct lgtd_lifx_bulb *bulb = lgtd_lifx_bulb_get(target->device_id); -+ assert(bulb); -+ -+ if (bulb->state.power) { -+ continue; // skip the bulb since it's already on ++ struct lgtd_lifx_bulb *bulb; ++ uint8_t *device_id = target->device_id; ++ bulb = lgtd_effect_power_transition_off_possibly_get_bulb(device_id); ++ if (!bulb) { ++ continue; + } + -+ struct lgtd_lifx_packet_light_color pkt_set_brightness_to_zero = { -+ .hue = bulb->state.hue, -+ .saturation = bulb->state.saturation, -+ .brightness = 0, -+ .kelvin = bulb->state.kelvin -+ }; -+ lgtd_lifx_wire_encode_light_color(&pkt_set_brightness_to_zero); -+ lgtd_router_send_to_device( -+ bulb, LGTD_LIFX_SET_LIGHT_COLOR, &pkt_set_brightness_to_zero -+ ); ++ if (!bulb->expected_power_on) { ++ // only set the brightness to 0 if the bulbs is off at the ++ // beginning of the transition, otherwise it's gonna flicker: ++ struct lgtd_lifx_packet_light_color pkt_set_brightness_to_zero = { ++ .hue = bulb->state.hue, ++ .saturation = bulb->state.saturation, ++ .brightness = 0, ++ .kelvin = bulb->state.kelvin ++ }; ++ lgtd_lifx_wire_encode_light_color(&pkt_set_brightness_to_zero); ++ lgtd_router_send_to_device( ++ bulb, LGTD_LIFX_SET_LIGHT_COLOR, &pkt_set_brightness_to_zero ++ ); + -+ struct lgtd_lifx_packet_power pkt_power_on = { -+ .power = LGTD_LIFX_POWER_ON -+ }; -+ lgtd_router_send_to_device( -+ bulb, LGTD_LIFX_SET_POWER_STATE, &pkt_power_on -+ ); ++ struct lgtd_lifx_packet_power pkt_power_on = { ++ .power = LGTD_LIFX_POWER_ON ++ }; ++ lgtd_router_send_to_device( ++ bulb, LGTD_LIFX_SET_POWER_STATE, &pkt_power_on ++ ); ++ } + + struct lgtd_lifx_packet_light_color pkt_fade_out = { + .hue = bulb->state.hue, + .saturation = bulb->state.saturation, + .brightness = target->initial_brightness, + .kelvin = bulb->state.kelvin, -+ .transition = ctx->duration ++ .transition = transition, + }; + lgtd_lifx_wire_encode_light_color(&pkt_fade_out); + lgtd_router_send_to_device( + bulb, LGTD_LIFX_SET_LIGHT_COLOR, &pkt_fade_out + ); + } -+ -+ lgtd_effect_power_transition_clear_target_list(&ctx->targets); -+ free(ctx); +} + +const struct lgtd_effect * @@ -1131,20 +1323,17 @@ + assert(targets); + assert(duration >= 0); + ++ // prepare the list of targets and store it into the effect context: + struct lgtd_effect_power_transition_ctx *ctx = NULL; + struct lgtd_router_device_list *devices = NULL; -+ ++ ctx = calloc(1, sizeof(*ctx)); ++ if (!ctx) { ++ goto error; ++ } + devices = lgtd_router_targets_to_devices(targets); + if (!devices) { + goto error; + } -+ -+ ctx = calloc(1, sizeof(*ctx)); -+ if (!ctx) { -+ goto error; -+ } -+ ctx->duration = duration; -+ + while (!SLIST_EMPTY(devices)) { + struct lgtd_router_device *device = SLIST_FIRST(devices); + struct lgtd_lifx_bulb *bulb = device->device; @@ -1162,28 +1351,36 @@ + free(device); + } + free(devices); ++ devices = NULL; // let's not free that again if effect_start returns NULL + ++ // setup the effect: + const char *name = NULL; + void (*duration_cb)(const struct lgtd_effect *) = NULL; -+ int timer_flags = 0, timer_ms = 0; + void (*apply_cb)(const struct lgtd_effect *) = NULL; ++ lgtd_time_mono_t end_time = duration; + if (state == LGTD_EFFECT_POWER_TRANSITION_OFF) { + name = "power_transition[off]"; + apply_cb = lgtd_effect_power_transition_off_apply_callback; -+ timer_flags = LGTD_TIMER_ACTIVATE_NOW|LGTD_TIMER_PERSISTENT; -+ timer_ms = duration; + duration += + LGTD_EFFECT_POWER_TRANSITION_OFF_RESTORE_LIGHT_COLOR_DELAY_MSECS; + duration_cb = lgtd_effect_power_transition_off_duration_callback; + } else { + name = "power_transition[on]"; + apply_cb = lgtd_effect_power_transition_on_apply_callback; ++ duration_cb = lgtd_effect_power_transition_on_duration_callback; + } + union lgtd_effect_ctx effect_ctx = { .as_ptr = ctx }; + const struct lgtd_effect *effect = lgtd_effect_start( -+ name, duration, duration_cb, timer_flags, timer_ms, apply_cb, effect_ctx ++ name, ++ duration, ++ duration_cb, ++ LGTD_TIMER_ACTIVATE_NOW|LGTD_TIMER_PERSISTENT, ++ LGTD_EFFECT_POWER_TRANSITION_TIMER_MS, ++ apply_cb, ++ effect_ctx + ); + if (effect) { ++ ctx->end_time = effect->created_at + end_time; + return effect; + } + @@ -1204,7 +1401,7 @@ new file mode 100644 --- /dev/null +++ b/effects/power_transition.h -@@ -0,0 +1,51 @@ +@@ -0,0 +1,53 @@ +// Copyright (c) 2015, Louis Opter <kalessin@kalessin.fr> +// +// This file is part of lighstd. @@ -1232,9 +1429,12 @@ +// if you power off a bulb and set its color right away you'll see the bulb +// flicker, so let's delay the restore for a bit once the transition is done: +enum { ++ // the feels: http://diablo2.judgehype.com/screenshots/presentation/Erreursvf/baguette-magiques-en-os.jpg + LGTD_EFFECT_POWER_TRANSITION_OFF_RESTORE_LIGHT_COLOR_DELAY_MSECS = 250 +}; + ++enum { LGTD_EFFECT_POWER_TRANSITION_TIMER_MS = 250 }; ++ +struct lgtd_effect_power_transition_target { + SLIST_ENTRY(lgtd_effect_power_transition_target) link; + uint8_t device_id[LGTD_LIFX_ADDR_LENGTH]; @@ -1247,10 +1447,9 @@ + +struct lgtd_effect_power_transition_ctx { + struct lgtd_effect_power_transition_target_list targets; -+ // For power off transitions the effect duration might be a bit longer than -+ // the actual transition duration (see RESTORE_LIGHT_COLOR_DELAY_MSECS) -+ // hence why the context also contains the duration: -+ int duration; ++ // This doesn't include the delay to restore the light color after a ++ // power off: ++ lgtd_time_mono_t end_time; +}; + +const struct lgtd_effect *lgtd_effect_power_transition(const struct lgtd_proto_target_list *,