From 3913522cc3e59b62b01d96e231501b434831ee5a Mon Sep 17 00:00:00 2001 From: Sven van Heugten Date: Sun, 15 Mar 2026 11:18:17 +0100 Subject: [PATCH] Store a lot less state --- .../InteractionListHelpers.fs | 11 ++- NightLight.Core.Tests/NightLightTests.fs | 20 +---- NightLight.Core/NightLightStateMachine.fs | 88 ++++++++----------- 3 files changed, 49 insertions(+), 70 deletions(-) diff --git a/NightLight.Core.Tests/InteractionListHelpers.fs b/NightLight.Core.Tests/InteractionListHelpers.fs index ca2198d..8d7f8ae 100644 --- a/NightLight.Core.Tests/InteractionListHelpers.fs +++ b/NightLight.Core.Tests/InteractionListHelpers.fs @@ -36,12 +36,17 @@ let doesLightHavePowerAfterInteractions light interactions = |> Seq.tryLast |> Option.defaultValue false -let tryGetLastRemoteInteraction interactions = +let tryGetLastBedroomRemoteInteraction interactions = interactions |> Seq.indexed - |> Seq.choose (fun interaction -> + |> Seq.choose (fun (index, interaction) -> match interaction with - | index, Interaction.RemoteInteraction remoteInteraction -> Some(index, remoteInteraction) + | Interaction.RemoteInteraction remoteInteraction -> + match remoteInteraction with + | RemotePressedOnButton + | RemotePressedOffButton + | RemotePressedLeftButton -> Some(index, remoteInteraction) + | RemotePressedRightButton -> None | _ -> None) |> Seq.tryLast diff --git a/NightLight.Core.Tests/NightLightTests.fs b/NightLight.Core.Tests/NightLightTests.fs index 99f323b..14e89c5 100644 --- a/NightLight.Core.Tests/NightLightTests.fs +++ b/NightLight.Core.Tests/NightLightTests.fs @@ -41,13 +41,13 @@ type NightLightTests() = |> Prop.label fakeHome.Label |> Prop.trivial (fakeHome.LightsThatAreOn.Length = 0) - [ |])>] + [ |], MaxTest = 500)>] let ``All lights should either be off or have a brightness that fits its color`` (interactions: Interaction list) = let fakeHome = createFakeHomeWithNightLightAndInteract interactions let time = getTimeAfterInteractions interactions |> _.TimeOfDay let alarm = - hasNewDayStartedSince interactions (tryGetLastRemoteInteraction interactions) + hasNewDayStartedSince interactions (tryGetLastBedroomRemoteInteraction interactions) && startOfDay <= time && time <= endOfAlarm @@ -81,7 +81,7 @@ type NightLightTests() = |> Prop.label fakeHome.Label |> Prop.trivial (fakeHome.LightsThatAreOn.Length = 0) - [ |])>] + [ |], MaxTest = 500)>] let ``All lights with power should have the correct state`` (interactions: Interaction list) = let fakeHome = createFakeHomeWithNightLightAndInteract interactions @@ -90,19 +90,7 @@ type NightLightTests() = |> Seq.filter (fun (light, _) -> doesLightHavePowerAfterInteractions light interactions) |> Seq.toList - let lastBedroomRemoteInteraction = - interactions - |> Seq.indexed - |> Seq.choose (fun (index, interaction) -> - match interaction with - | Interaction.RemoteInteraction remoteInteraction -> - match remoteInteraction with - | RemotePressedOnButton - | RemotePressedOffButton - | RemotePressedLeftButton -> Some(index, remoteInteraction) - | RemotePressedRightButton -> None - | _ -> None) - |> Seq.tryLast + let lastBedroomRemoteInteraction = tryGetLastBedroomRemoteInteraction interactions let newDayStartedSinceBedroomRemote = hasNewDayStartedSince interactions lastBedroomRemoteInteraction diff --git a/NightLight.Core/NightLightStateMachine.fs b/NightLight.Core/NightLightStateMachine.fs index 5fd91a5..bbab1d8 100644 --- a/NightLight.Core/NightLightStateMachine.fs +++ b/NightLight.Core/NightLightStateMachine.fs @@ -24,64 +24,43 @@ let internal generateZigbeeCommandsToFixLight (light: Light) (desiredLightSettin type internal NightLightState = { Time: DateTime Alarm: bool - LightToLightSettings: Map } + LightToManualState: Map } -let internal createOrUpdateNightLightState - (time: DateTime) - (alarm: bool) - (maybeOldLightToLightSettings: Map option) - = - let partOfDay = getPartOfDay time +let internal computeLightSettings (light: Light) (nightLightState: NightLightState) = + let partOfDay = getPartOfDay nightLightState.Time - let lightToLightSettings = - lights - |> Seq.map (fun light -> - let color, brightness = - getDesiredMood (lightProps light).Room partOfDay - |> getDesiredColorAndBrightness (lightProps light).Bulb + let color, brightness = + getDesiredMood (lightProps light).Room partOfDay + |> getDesiredColorAndBrightness (lightProps light).Bulb - let previousState = - maybeOldLightToLightSettings - |> Option.map (fun lightToLightSettings -> lightToLightSettings[light].State) - |> Option.defaultValue On - - light, - { Color = color - Brightness = - if alarm && (light = RightBedroomLamp || light = LeftBedroomLamp) then - brightness.Scale(getAlarmWeight time) - else - brightness - State = - if alarm && (light = RightBedroomLamp || light = LeftBedroomLamp) then - On - else - previousState }) - |> Map.ofSeq - - { Time = time - Alarm = alarm - LightToLightSettings = lightToLightSettings } + { Color = color + Brightness = + if nightLightState.Alarm && (light = RightBedroomLamp || light = LeftBedroomLamp) then + brightness.Scale(getAlarmWeight nightLightState.Time) + else + brightness + State = + if nightLightState.Alarm && (light = RightBedroomLamp || light = LeftBedroomLamp) then + On + else + nightLightState.LightToManualState[light] } let internal withStateFor (light: Light) (state: State) (oldNightLightState: NightLightState) = - let oldState = oldNightLightState.LightToLightSettings[light] - - createOrUpdateNightLightState - oldNightLightState.Time - oldNightLightState.Alarm - (Map.add light { oldState with State = state } oldNightLightState.LightToLightSettings - |> Some) + { oldNightLightState with + LightToManualState = Map.add light state oldNightLightState.LightToManualState } let internal withAlarmOff (oldNightLightState: NightLightState) = - createOrUpdateNightLightState oldNightLightState.Time false (Some oldNightLightState.LightToLightSettings) + { oldNightLightState with + Alarm = false } let internal generateZigbeeCommandsForDifference (maybeBefore: NightLightState option) (after: NightLightState) = - after.LightToLightSettings - |> Seq.collect (fun (KeyValue(light, newState)) -> - let oldState = maybeBefore |> Option.map _.LightToLightSettings[light] + lights + |> Seq.collect (fun light -> + let oldLightSettings = maybeBefore |> Option.map (computeLightSettings light) + let newLightSettings = after |> computeLightSettings light - if oldState <> Some newState then - generateZigbeeCommandsToFixLight light after.LightToLightSettings[light] + if oldLightSettings <> Some newLightSettings then + generateZigbeeCommandsToFixLight light newLightSettings else Seq.empty) @@ -101,7 +80,10 @@ type NightLightStateMachine private (maybeState: NightLightState option) = this, match maybeLight with - | Some light -> generateZigbeeCommandsToFixLight light currentState.LightToLightSettings[light] + | Some light -> + currentState + |> computeLightSettings light + |> generateZigbeeCommandsToFixLight light | None -> Seq.empty | ButtonPress action -> let newNightLightState = @@ -123,7 +105,6 @@ type NightLightStateMachine private (maybeState: NightLightState option) = |> withStateFor LeftBedroomLamp On | PressedRight -> currentState - |> withAlarmOff |> withStateFor LivingRoomWallLamp Off |> withStateFor LivingRoomFloorLamp Off @@ -143,7 +124,12 @@ type NightLightStateMachine private (maybeState: NightLightState option) = || maybeCurrentState |> Option.map _.Alarm |> Option.defaultValue false let newNightLightState = - createOrUpdateNightLightState newTime alarm (maybeCurrentState |> Option.map _.LightToLightSettings) + { Time = newTime + Alarm = alarm + LightToManualState = + maybeCurrentState + |> Option.map _.LightToManualState + |> Option.defaultValue (lights |> Seq.map (fun light -> light, On) |> Map.ofSeq) } return NightLightStateMachine(Some newNightLightState),