night-light/NightLight.Core/NightLightStateMachine.fs

142 lines
6 KiB
FSharp

module NightLight.Core.Core
open System
open NightLight.Core.Models
open NightLight.Core.PartsOfDay
open NightLight.Core.ZigbeeEvents
open NightLight.Core.ZigbeeCommands
open NightLight.Core.Moods
open FsToolkit.ErrorHandling
let internal tryFindLight friendlyName =
Seq.tryFind (fun light -> (lightProps light).FriendlyName = friendlyName) lights
let internal generateZigbeeCommandsToFixLight (light: Light) (desiredLightSettings: LightSettings) =
seq {
if desiredLightSettings.State = Off then
yield generateStateCommand desiredLightSettings.State light
if desiredLightSettings.State = On then
yield generateBrightnessCommand light desiredLightSettings.Brightness
yield generateColorCommand light desiredLightSettings.Color
}
type internal NightLightState =
{ Time: DateTime
Alarm: bool
LightToManualState: Map<Light, State> }
let internal computeLightSettings (light: Light) (nightLightState: NightLightState) =
let partOfDay = getPartOfDay nightLightState.Time
let color, brightness =
getDesiredMood (lightProps light).Room partOfDay
|> getDesiredColorAndBrightness (lightProps light).Bulb
{ 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) =
{ oldNightLightState with
LightToManualState = Map.add light state oldNightLightState.LightToManualState }
let internal withInvertedStateFor (light: Light) (oldNightLightState: NightLightState) =
oldNightLightState
|> withStateFor light (oldNightLightState.LightToManualState[light].Invert())
let internal withAlarmOff (oldNightLightState: NightLightState) =
{ oldNightLightState with
Alarm = false }
let internal generateZigbeeCommandsForDifference (maybeBefore: NightLightState option) (after: NightLightState) =
lights
|> Seq.collect (fun light ->
let oldLightSettings = maybeBefore |> Option.map (computeLightSettings light)
let newLightSettings = after |> computeLightSettings light
if oldLightSettings <> Some newLightSettings then
generateZigbeeCommandsToFixLight light newLightSettings
else
Seq.empty)
type NightLightStateMachine private (maybeState: NightLightState option) =
new() = NightLightStateMachine None
member this.OnEventReceived(event: Event) : Result<NightLightStateMachine * Message seq, OnEventReceivedError> =
result {
match event, maybeState with
| ReceivedZigbeeEvent payload, Some currentState ->
let! zigbeeEvent = parseZigbeeEvent payload |> Result.mapError ParseZigbeeEventError
return
match zigbeeEvent with
| DeviceAnnounce friendlyName ->
let maybeLight = tryFindLight friendlyName
this,
match maybeLight with
| Some light ->
currentState
|> computeLightSettings light
|> generateZigbeeCommandsToFixLight light
| None -> Seq.empty
| ButtonPress action ->
let newNightLightState =
match action with
| PressedOn ->
currentState
|> withAlarmOff
|> withStateFor RightBedroomLamp On
|> withStateFor LeftBedroomLamp On
| PressedOff ->
currentState
|> withAlarmOff
|> withStateFor RightBedroomLamp Off
|> withStateFor LeftBedroomLamp Off
| PressedLeft ->
currentState
|> withAlarmOff
|> withStateFor RightBedroomLamp Off
|> withStateFor LeftBedroomLamp On
| PressedRight ->
currentState
|> withInvertedStateFor LivingRoomWallLamp
|> withInvertedStateFor LivingRoomFloorLamp
NightLightStateMachine(Some newNightLightState),
generateZigbeeCommandsForDifference (Some currentState) newNightLightState
| TimeChanged newTime, maybeCurrentState ->
let alarm =
let newDayStarted =
let newPartOfDay = getPartOfDay newTime
let maybePreviousPartOfDay =
maybeCurrentState |> Option.map _.Time |> Option.map getPartOfDay
maybePreviousPartOfDay = Some Night && newPartOfDay = Day
newDayStarted
|| maybeCurrentState |> Option.map _.Alarm |> Option.defaultValue false
let newNightLightState =
{ Time = newTime
Alarm = alarm
LightToManualState =
maybeCurrentState
|> Option.map _.LightToManualState
|> Option.defaultValue (lights |> Seq.map (fun light -> light, On) |> Map.ofSeq) }
return
NightLightStateMachine(Some newNightLightState),
generateZigbeeCommandsForDifference maybeCurrentState newNightLightState
| _, None -> return! Error TimeIsUnknown
}