michielpost / Q42.HueApi

C# helper library to talk to the Philips Hue bridge
MIT License
409 stars 114 forks source link

Support transitioning the brightness separately from the colour. #174

Closed mgpreston closed 5 years ago

mgpreston commented 5 years ago

I have a particular scenario that needs the brightness of a light to be transitioned separately to the colour, e.g. transition from 0 to 1 brightness over 10 seconds, and transition from white to red over 2 seconds.

I couldn't find a clean way to implement this externally, so I made some changes to the Transition class to support the separate transitions. Its behaviour remains the same for any existing usage of the Transition class.

Additionally, the extra properties on Transition allow for reading the current progress of the transition. I'm using a custom SetState extension method that can combine an existing brightness transition with a new colour transition, or an existing colour transition with a new brightness transition.

For example, if brightness is being transitioned from 0 to 1 over 10 seconds, and it's currently 5 seconds in (at a brightness of 0.5), and the new transition is for colour to go from current to red over 2 seconds, the combined transition will be for the remainder of the brightness transition (from 0.5 to 1 over 5 seconds) as well as the colour transition:

public static void SetState(this EntertainmentLight light, CancellationToken cancellationToken, RGBColor? rgb = null, TimeSpan rgbTimeSpan = default, double? brightness = null, TimeSpan briTimeSpan = default, bool overwriteExistingTransition = true) {
    if (light == null) throw new ArgumentNullException(nameof(light));

    // No change in state required.
    if (!rgb.HasValue && !brightness.HasValue) return;

    var currentTransition = light.Transition;
    bool hasExistingTransition = currentTransition != null && !currentTransition.IsFinished;
    bool canCombineTransitions = hasExistingTransition && 
                                 (!rgb.HasValue || !brightness.HasValue) &&
                                 (!rgb.HasValue || !currentTransition.IsBrightnessFinished) &&
                                 (!brightness.HasValue || !currentTransition.IsRgbFinished);

    Transition transition;

    // If we can't combine the existing transition (if there is one) with the new transition, start a fresh transition.
    if (!canCombineTransitions || overwriteExistingTransition) {
        if (rgb.HasValue && brightness.HasValue) {
            transition= new Transition(rgb.Value, brightness.Value, rgbTimeSpan, briTimeSpan);
        } else if (rgb.HasValue) {
            transition = new Transition(rgb.Value, rgbTimeSpan);
        } else {
            transition = new Transition(brightness.Value, briTimeSpan);
        }
    } else {
        // A transition is currently in progress, which we need to combine with the colour or brightness that was specified.
        if (rgb.HasValue) {
            // Combine new colour transition with existing brightness transition.
            transition = new Transition(rgb.Value, currentTransition.TargetBri.Value, rgbTimeSpan, currentTransition.BrightnessRemainingTime);
        } else {
            // Combine new brightness transition with existing colour transition.
            transition = new Transition(currentTransition.TargetRgb.Value, brightness.Value, currentTransition.RgbRemainingTime, briTimeSpan);
        }
    }

    light.Transition = transition;
    transition.Start(light.State.RGBColor, light.State.Brightness, cancellationToken);
}

By default the SetState method has overwriteExistingTransition = true which maintains backwards compatibility with existing SetState usage, creating a fresh transition and discarding any existing transition, rather than potentially combining with the existing transition.

If you see this as having any value, you're welcome to add it. You may of course have better ideas for how to support these separate transitions.

Cheers, Mark

michielpost commented 5 years ago

Thanks for your contribution to the project! Looks great. I'm going to merge the PR, do some testing and will create a new release with your code in it.

michielpost commented 5 years ago

I published version Q42.HueApi.Entertainment 3.9.0 to NuGet, it includes this PR and the code from this conversation.