155 lines
6.9 KiB
FSharp
155 lines
6.9 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
|
|
|> match
|
|
currentState.LightToManualState[LeftBedroomLamp],
|
|
currentState.LightToManualState[RightBedroomLamp]
|
|
with
|
|
| Off, _ -> withStateFor LeftBedroomLamp On >> withStateFor RightBedroomLamp On
|
|
| On, On -> withStateFor LeftBedroomLamp On >> withStateFor RightBedroomLamp Off
|
|
| On, Off -> withStateFor LeftBedroomLamp Off >> withStateFor RightBedroomLamp 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
|
|
| PressedLivingRoomOn ->
|
|
currentState
|
|
|> withStateFor LivingRoomWallLamp On
|
|
|> withStateFor LivingRoomFloorLamp On
|
|
| PressedLivingRoomOff ->
|
|
currentState
|
|
|> withStateFor LivingRoomWallLamp Off
|
|
|> withStateFor LivingRoomFloorLamp Off
|
|
|
|
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
|
|
}
|