Implement the Right button on the bedroom remote
This commit is contained in:
parent
b9fc8bfaea
commit
d3a00b8a77
5 changed files with 74 additions and 64 deletions
|
|
@ -10,6 +10,7 @@ type RemoteInteraction =
|
||||||
| RemotePressedOnButton
|
| RemotePressedOnButton
|
||||||
| RemotePressedOffButton
|
| RemotePressedOffButton
|
||||||
| RemotePressedLeftButton
|
| RemotePressedLeftButton
|
||||||
|
| RemotePressedRightButton
|
||||||
|
|
||||||
type HumanInteraction =
|
type HumanInteraction =
|
||||||
| LightPoweredOn of Light
|
| LightPoweredOn of Light
|
||||||
|
|
@ -49,9 +50,7 @@ type FakeLight(light: Light) =
|
||||||
member _.SetBrightness(newBrightness: byte) =
|
member _.SetBrightness(newBrightness: byte) =
|
||||||
if hasPower then
|
if hasPower then
|
||||||
brightness <- newBrightness
|
brightness <- newBrightness
|
||||||
|
state <- true
|
||||||
if (lightProps light).Bulb = IkeaBulb then
|
|
||||||
state <- true
|
|
||||||
|
|
||||||
member _.SetColor(newColor: Color) =
|
member _.SetColor(newColor: Color) =
|
||||||
if hasPower then
|
if hasPower then
|
||||||
|
|
@ -139,6 +138,11 @@ type FakeHome() =
|
||||||
Payload = @"{ ""action"": ""arrow_left_click"" }" }
|
Payload = @"{ ""action"": ""arrow_left_click"" }" }
|
||||||
|> ReceivedZigbeeEvent
|
|> ReceivedZigbeeEvent
|
||||||
|> onEventPublished.Trigger
|
|> onEventPublished.Trigger
|
||||||
|
| RemoteInteraction RemotePressedRightButton ->
|
||||||
|
{ Topic = $"zigbee2mqtt/{remoteControlFriendlyName.Get}"
|
||||||
|
Payload = @"{ ""action"": ""arrow_right_click"" }" }
|
||||||
|
|> ReceivedZigbeeEvent
|
||||||
|
|> onEventPublished.Trigger
|
||||||
| TimeChanged newTime -> newTime |> Event.TimeChanged |> onEventPublished.Trigger
|
| TimeChanged newTime -> newTime |> Event.TimeChanged |> onEventPublished.Trigger
|
||||||
|
|
||||||
type FakeHome with
|
type FakeHome with
|
||||||
|
|
@ -147,19 +151,6 @@ type FakeHome with
|
||||||
member this.LightsThatAreOn =
|
member this.LightsThatAreOn =
|
||||||
this.LightStates |> Seq.filter (snd >> _.IsOn) |> Seq.toList
|
this.LightStates |> Seq.filter (snd >> _.IsOn) |> Seq.toList
|
||||||
|
|
||||||
member this.NonRemotelyControlledLightStates =
|
|
||||||
this.LightStates
|
|
||||||
|> Seq.filter (fun (light, _) ->
|
|
||||||
light = LivingRoomWallLamp
|
|
||||||
|| light = LivingRoomFloorLamp
|
|
||||||
|| light = BathroomCeilingLamp)
|
|
||||||
|> Seq.toList
|
|
||||||
|
|
||||||
member this.RemotelyControlledLightStates =
|
|
||||||
this.LightStates
|
|
||||||
|> Seq.filter (fun (light, _) -> light = RightBedroomLamp || light = LeftBedroomLamp)
|
|
||||||
|> Seq.toList
|
|
||||||
|
|
||||||
member this.Label =
|
member this.Label =
|
||||||
this.LightsThatAreOn
|
this.LightsThatAreOn
|
||||||
|> Seq.map (fun (light, state) -> $"{(lightProps light).FriendlyName.Get}: {state}")
|
|> Seq.map (fun (light, state) -> $"{(lightProps light).FriendlyName.Get}: {state}")
|
||||||
|
|
|
||||||
|
|
@ -36,7 +36,11 @@ let private genHumanInteraction =
|
||||||
|> Gen.map Interaction.HumanInteraction
|
|> Gen.map Interaction.HumanInteraction
|
||||||
|
|
||||||
let private genRemoteInteraction =
|
let private genRemoteInteraction =
|
||||||
Gen.elements [ RemotePressedOnButton; RemotePressedOffButton; RemotePressedLeftButton ]
|
Gen.elements
|
||||||
|
[ RemotePressedOnButton
|
||||||
|
RemotePressedOffButton
|
||||||
|
RemotePressedLeftButton
|
||||||
|
RemotePressedRightButton ]
|
||||||
|> Gen.map RemoteInteraction
|
|> Gen.map RemoteInteraction
|
||||||
|
|
||||||
let private genInteraction =
|
let private genInteraction =
|
||||||
|
|
|
||||||
|
|
@ -82,55 +82,59 @@ type NightLightTests() =
|
||||||
|> Prop.trivial (fakeHome.LightsThatAreOn.Length = 0)
|
|> Prop.trivial (fakeHome.LightsThatAreOn.Length = 0)
|
||||||
|
|
||||||
[<Property(Arbitrary = [| typeof<ArbitraryInteractions> |])>]
|
[<Property(Arbitrary = [| typeof<ArbitraryInteractions> |])>]
|
||||||
let ``All non-remotely controlled lights with power should be on`` (interactions: Interaction list) =
|
let ``All lights with power should have the correct state`` (interactions: Interaction list) =
|
||||||
let fakeHome = createFakeHomeWithNightLightAndInteract interactions
|
let fakeHome = createFakeHomeWithNightLightAndInteract interactions
|
||||||
|
|
||||||
let nonRemotelyControlledLightsWithPower =
|
let lightsWithPower =
|
||||||
fakeHome.NonRemotelyControlledLightStates
|
fakeHome.LightStates
|
||||||
|> Seq.filter (fun (light, _) -> doesLightHavePowerAfterInteractions light interactions)
|
|> Seq.filter (fun (light, _) -> doesLightHavePowerAfterInteractions light interactions)
|
||||||
|> Seq.toList
|
|> Seq.toList
|
||||||
|
|
||||||
nonRemotelyControlledLightsWithPower
|
let lastBedroomRemoteInteraction =
|
||||||
|> Seq.forall (snd >> _.IsOn)
|
interactions
|
||||||
|> Prop.collect $"{nonRemotelyControlledLightsWithPower.Length} non-remotely controlled light(s) with power"
|
|> 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 =
|
||||||
|
hasNewDayStartedSince interactions lastBedroomRemoteInteraction
|
||||||
|
|
||||||
|
let hasPressedRight =
|
||||||
|
interactions
|
||||||
|
|> Seq.exists (function
|
||||||
|
| Interaction.RemoteInteraction RemotePressedRightButton -> true
|
||||||
|
| _ -> false)
|
||||||
|
|
||||||
|
let isExpectedOn light =
|
||||||
|
match light with
|
||||||
|
| LeftBedroomLamp
|
||||||
|
| RightBedroomLamp ->
|
||||||
|
if newDayStartedSinceBedroomRemote then
|
||||||
|
true
|
||||||
|
else
|
||||||
|
match lastBedroomRemoteInteraction with
|
||||||
|
| Some(_, RemotePressedOffButton) -> false
|
||||||
|
| Some(_, RemotePressedLeftButton) -> light = LeftBedroomLamp
|
||||||
|
| Some(_, RemotePressedOnButton) -> true
|
||||||
|
| Some(_, RemotePressedRightButton) -> failwith "unexpected"
|
||||||
|
| None -> true
|
||||||
|
| LivingRoomWallLamp
|
||||||
|
| LivingRoomFloorLamp -> not hasPressedRight
|
||||||
|
| BathroomCeilingLamp -> true
|
||||||
|
|
||||||
|
lightsWithPower
|
||||||
|
|> Seq.forall (fun (light, state) -> state.IsOn = isExpectedOn light)
|
||||||
|
|> Prop.collect $"last bedroom remote interaction is {lastBedroomRemoteInteraction |> Option.map snd}"
|
||||||
|
|> Prop.collect $"pressed right: {hasPressedRight}"
|
||||||
|
|> Prop.collect $"{lightsWithPower.Length} light(s) with power"
|
||||||
|
|> Prop.classify newDayStartedSinceBedroomRemote "new day since bedroom remote"
|
||||||
|> Prop.label fakeHome.Label
|
|> Prop.label fakeHome.Label
|
||||||
|> Prop.trivial (nonRemotelyControlledLightsWithPower.Length = 0)
|
|> Prop.trivial (lightsWithPower.Length = 0)
|
||||||
|
|
||||||
[<Property(Arbitrary = [| typeof<ArbitraryInteractions> |])>]
|
|
||||||
let ``All remotely-controlled lights with power should have the correct state`` (interactions: Interaction list) =
|
|
||||||
let fakeHome = createFakeHomeWithNightLightAndInteract interactions
|
|
||||||
|
|
||||||
let remotelyControlledLightsWithPower =
|
|
||||||
fakeHome.RemotelyControlledLightStates
|
|
||||||
|> Seq.filter (fun (light, _) -> doesLightHavePowerAfterInteractions light interactions)
|
|
||||||
|> Seq.toList
|
|
||||||
|
|
||||||
let allOn (ls: (Light * LightState) seq) = ls |> Seq.forall (snd >> _.IsOn)
|
|
||||||
let allOff (ls: (Light * LightState) seq) = ls |> Seq.forall (snd >> _.IsOff)
|
|
||||||
|
|
||||||
let controlledByLeft ls =
|
|
||||||
ls |> Seq.filter (fun (light, _) -> light = LeftBedroomLamp)
|
|
||||||
|
|
||||||
let controlledByRight ls =
|
|
||||||
ls |> Seq.filter (fun (light, _) -> light = RightBedroomLamp)
|
|
||||||
|
|
||||||
let maybeLastRemoteInteraction = tryGetLastRemoteInteraction interactions
|
|
||||||
|
|
||||||
let hasNewDayStartedSinceThen =
|
|
||||||
hasNewDayStartedSince interactions maybeLastRemoteInteraction
|
|
||||||
|
|
||||||
if hasNewDayStartedSinceThen then
|
|
||||||
remotelyControlledLightsWithPower |> allOn
|
|
||||||
else
|
|
||||||
match maybeLastRemoteInteraction with
|
|
||||||
| Some(_, RemotePressedOnButton) -> remotelyControlledLightsWithPower |> allOn
|
|
||||||
| Some(_, RemotePressedOffButton) -> remotelyControlledLightsWithPower |> allOff
|
|
||||||
| Some(_, RemotePressedLeftButton) ->
|
|
||||||
remotelyControlledLightsWithPower |> controlledByLeft |> allOn
|
|
||||||
&& remotelyControlledLightsWithPower |> controlledByRight |> allOff
|
|
||||||
| None -> remotelyControlledLightsWithPower |> allOn
|
|
||||||
|> Prop.collect $"last remote interaction is {maybeLastRemoteInteraction |> Option.map snd}"
|
|
||||||
|> Prop.collect $"{remotelyControlledLightsWithPower.Length} remotely controlled light(s) with power"
|
|
||||||
|> Prop.classify hasNewDayStartedSinceThen "new day has started since then"
|
|
||||||
|> Prop.label fakeHome.Label
|
|
||||||
|> Prop.trivial (remotelyControlledLightsWithPower.Length = 0)
|
|
||||||
|
|
|
||||||
|
|
@ -52,7 +52,11 @@ let internal createOrUpdateNightLightState
|
||||||
brightness.Scale(getAlarmWeight time)
|
brightness.Scale(getAlarmWeight time)
|
||||||
else
|
else
|
||||||
brightness
|
brightness
|
||||||
State = if alarm then On else previousState })
|
State =
|
||||||
|
if alarm && (light = RightBedroomLamp || light = LeftBedroomLamp) then
|
||||||
|
On
|
||||||
|
else
|
||||||
|
previousState })
|
||||||
|> Map.ofSeq
|
|> Map.ofSeq
|
||||||
|
|
||||||
{ Time = time
|
{ Time = time
|
||||||
|
|
@ -117,6 +121,11 @@ type NightLightStateMachine private (maybeState: NightLightState option) =
|
||||||
|> withAlarmOff
|
|> withAlarmOff
|
||||||
|> withStateFor RightBedroomLamp Off
|
|> withStateFor RightBedroomLamp Off
|
||||||
|> withStateFor LeftBedroomLamp On
|
|> withStateFor LeftBedroomLamp On
|
||||||
|
| PressedRight ->
|
||||||
|
currentState
|
||||||
|
|> withAlarmOff
|
||||||
|
|> withStateFor LivingRoomWallLamp Off
|
||||||
|
|> withStateFor LivingRoomFloorLamp Off
|
||||||
|
|
||||||
NightLightStateMachine(Some newNightLightState),
|
NightLightStateMachine(Some newNightLightState),
|
||||||
generateZigbeeCommandsForDifference (Some currentState) newNightLightState
|
generateZigbeeCommandsForDifference (Some currentState) newNightLightState
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ type Action =
|
||||||
| PressedOn
|
| PressedOn
|
||||||
| PressedOff
|
| PressedOff
|
||||||
| PressedLeft
|
| PressedLeft
|
||||||
|
| PressedRight
|
||||||
|
|
||||||
type ZigbeeEvent =
|
type ZigbeeEvent =
|
||||||
| DeviceAnnounce of DeviceFriendlyName
|
| DeviceAnnounce of DeviceFriendlyName
|
||||||
|
|
@ -37,6 +38,7 @@ let parseZigbeeEvent (message: Message) =
|
||||||
| Some(JsonValue.String "on") -> Ok(ButtonPress PressedOn)
|
| Some(JsonValue.String "on") -> Ok(ButtonPress PressedOn)
|
||||||
| Some(JsonValue.String "off") -> Ok(ButtonPress PressedOff)
|
| Some(JsonValue.String "off") -> Ok(ButtonPress PressedOff)
|
||||||
| Some(JsonValue.String "arrow_left_click") -> Ok(ButtonPress PressedLeft)
|
| Some(JsonValue.String "arrow_left_click") -> Ok(ButtonPress PressedLeft)
|
||||||
|
| Some(JsonValue.String "arrow_right_click") -> Ok(ButtonPress PressedRight)
|
||||||
| Some _ -> Error InvalidActionField
|
| Some _ -> Error InvalidActionField
|
||||||
| None -> Error MissingActionField
|
| None -> Error MissingActionField
|
||||||
| _ -> return! Error <| UnknownTopic message.Topic
|
| _ -> return! Error <| UnknownTopic message.Topic
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue