Store a lot less state

This commit is contained in:
Sven van Heugten 2026-03-15 11:18:17 +01:00
parent 3507385d6c
commit 3913522cc3
3 changed files with 49 additions and 70 deletions

View file

@ -36,12 +36,17 @@ let doesLightHavePowerAfterInteractions light interactions =
|> Seq.tryLast |> Seq.tryLast
|> Option.defaultValue false |> Option.defaultValue false
let tryGetLastRemoteInteraction interactions = let tryGetLastBedroomRemoteInteraction interactions =
interactions interactions
|> Seq.indexed |> Seq.indexed
|> Seq.choose (fun interaction -> |> Seq.choose (fun (index, interaction) ->
match interaction with 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) | _ -> None)
|> Seq.tryLast |> Seq.tryLast

View file

@ -41,13 +41,13 @@ type NightLightTests() =
|> Prop.label fakeHome.Label |> Prop.label fakeHome.Label
|> Prop.trivial (fakeHome.LightsThatAreOn.Length = 0) |> Prop.trivial (fakeHome.LightsThatAreOn.Length = 0)
[<Property(Arbitrary = [| typeof<ArbitraryInteractions> |])>] [<Property(Arbitrary = [| typeof<ArbitraryInteractions> |], MaxTest = 500)>]
let ``All lights should either be off or have a brightness that fits its color`` (interactions: Interaction list) = let ``All lights should either be off or have a brightness that fits its color`` (interactions: Interaction list) =
let fakeHome = createFakeHomeWithNightLightAndInteract interactions let fakeHome = createFakeHomeWithNightLightAndInteract interactions
let time = getTimeAfterInteractions interactions |> _.TimeOfDay let time = getTimeAfterInteractions interactions |> _.TimeOfDay
let alarm = let alarm =
hasNewDayStartedSince interactions (tryGetLastRemoteInteraction interactions) hasNewDayStartedSince interactions (tryGetLastBedroomRemoteInteraction interactions)
&& startOfDay <= time && startOfDay <= time
&& time <= endOfAlarm && time <= endOfAlarm
@ -81,7 +81,7 @@ type NightLightTests() =
|> Prop.label fakeHome.Label |> Prop.label fakeHome.Label
|> Prop.trivial (fakeHome.LightsThatAreOn.Length = 0) |> Prop.trivial (fakeHome.LightsThatAreOn.Length = 0)
[<Property(Arbitrary = [| typeof<ArbitraryInteractions> |])>] [<Property(Arbitrary = [| typeof<ArbitraryInteractions> |], MaxTest = 500)>]
let ``All lights with power should have the correct state`` (interactions: Interaction list) = let ``All lights with power should have the correct state`` (interactions: Interaction list) =
let fakeHome = createFakeHomeWithNightLightAndInteract interactions let fakeHome = createFakeHomeWithNightLightAndInteract interactions
@ -90,19 +90,7 @@ type NightLightTests() =
|> Seq.filter (fun (light, _) -> doesLightHavePowerAfterInteractions light interactions) |> Seq.filter (fun (light, _) -> doesLightHavePowerAfterInteractions light interactions)
|> Seq.toList |> Seq.toList
let lastBedroomRemoteInteraction = let lastBedroomRemoteInteraction = tryGetLastBedroomRemoteInteraction interactions
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 newDayStartedSinceBedroomRemote = let newDayStartedSinceBedroomRemote =
hasNewDayStartedSince interactions lastBedroomRemoteInteraction hasNewDayStartedSince interactions lastBedroomRemoteInteraction

View file

@ -24,64 +24,43 @@ let internal generateZigbeeCommandsToFixLight (light: Light) (desiredLightSettin
type internal NightLightState = type internal NightLightState =
{ Time: DateTime { Time: DateTime
Alarm: bool Alarm: bool
LightToLightSettings: Map<Light, LightSettings> } LightToManualState: Map<Light, State> }
let internal createOrUpdateNightLightState let internal computeLightSettings (light: Light) (nightLightState: NightLightState) =
(time: DateTime) let partOfDay = getPartOfDay nightLightState.Time
(alarm: bool)
(maybeOldLightToLightSettings: Map<Light, LightSettings> option)
=
let partOfDay = getPartOfDay time
let lightToLightSettings =
lights
|> Seq.map (fun light ->
let color, brightness = let color, brightness =
getDesiredMood (lightProps light).Room partOfDay getDesiredMood (lightProps light).Room partOfDay
|> getDesiredColorAndBrightness (lightProps light).Bulb |> getDesiredColorAndBrightness (lightProps light).Bulb
let previousState =
maybeOldLightToLightSettings
|> Option.map (fun lightToLightSettings -> lightToLightSettings[light].State)
|> Option.defaultValue On
light,
{ Color = color { Color = color
Brightness = Brightness =
if alarm && (light = RightBedroomLamp || light = LeftBedroomLamp) then if nightLightState.Alarm && (light = RightBedroomLamp || light = LeftBedroomLamp) then
brightness.Scale(getAlarmWeight time) brightness.Scale(getAlarmWeight nightLightState.Time)
else else
brightness brightness
State = State =
if alarm && (light = RightBedroomLamp || light = LeftBedroomLamp) then if nightLightState.Alarm && (light = RightBedroomLamp || light = LeftBedroomLamp) then
On On
else else
previousState }) nightLightState.LightToManualState[light] }
|> Map.ofSeq
{ Time = time
Alarm = alarm
LightToLightSettings = lightToLightSettings }
let internal withStateFor (light: Light) (state: State) (oldNightLightState: NightLightState) = let internal withStateFor (light: Light) (state: State) (oldNightLightState: NightLightState) =
let oldState = oldNightLightState.LightToLightSettings[light] { oldNightLightState with
LightToManualState = Map.add light state oldNightLightState.LightToManualState }
createOrUpdateNightLightState
oldNightLightState.Time
oldNightLightState.Alarm
(Map.add light { oldState with State = state } oldNightLightState.LightToLightSettings
|> Some)
let internal withAlarmOff (oldNightLightState: NightLightState) = let internal withAlarmOff (oldNightLightState: NightLightState) =
createOrUpdateNightLightState oldNightLightState.Time false (Some oldNightLightState.LightToLightSettings) { oldNightLightState with
Alarm = false }
let internal generateZigbeeCommandsForDifference (maybeBefore: NightLightState option) (after: NightLightState) = let internal generateZigbeeCommandsForDifference (maybeBefore: NightLightState option) (after: NightLightState) =
after.LightToLightSettings lights
|> Seq.collect (fun (KeyValue(light, newState)) -> |> Seq.collect (fun light ->
let oldState = maybeBefore |> Option.map _.LightToLightSettings[light] let oldLightSettings = maybeBefore |> Option.map (computeLightSettings light)
let newLightSettings = after |> computeLightSettings light
if oldState <> Some newState then if oldLightSettings <> Some newLightSettings then
generateZigbeeCommandsToFixLight light after.LightToLightSettings[light] generateZigbeeCommandsToFixLight light newLightSettings
else else
Seq.empty) Seq.empty)
@ -101,7 +80,10 @@ type NightLightStateMachine private (maybeState: NightLightState option) =
this, this,
match maybeLight with match maybeLight with
| Some light -> generateZigbeeCommandsToFixLight light currentState.LightToLightSettings[light] | Some light ->
currentState
|> computeLightSettings light
|> generateZigbeeCommandsToFixLight light
| None -> Seq.empty | None -> Seq.empty
| ButtonPress action -> | ButtonPress action ->
let newNightLightState = let newNightLightState =
@ -123,7 +105,6 @@ type NightLightStateMachine private (maybeState: NightLightState option) =
|> withStateFor LeftBedroomLamp On |> withStateFor LeftBedroomLamp On
| PressedRight -> | PressedRight ->
currentState currentState
|> withAlarmOff
|> withStateFor LivingRoomWallLamp Off |> withStateFor LivingRoomWallLamp Off
|> withStateFor LivingRoomFloorLamp Off |> withStateFor LivingRoomFloorLamp Off
@ -143,7 +124,12 @@ type NightLightStateMachine private (maybeState: NightLightState option) =
|| maybeCurrentState |> Option.map _.Alarm |> Option.defaultValue false || maybeCurrentState |> Option.map _.Alarm |> Option.defaultValue false
let newNightLightState = 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 return
NightLightStateMachine(Some newNightLightState), NightLightStateMachine(Some newNightLightState),