Skip to content

Commit 312ece4

Browse files
committed
feat: UI for update interval configuration
1 parent d14e01d commit 312ece4

File tree

4 files changed

+115
-56
lines changed

4 files changed

+115
-56
lines changed

RELEASE_NOTES.md

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
1-
## 1.4.0 - 26 Dec 2022
1+
## 1.4.0 - 2 Jan 2023
2+
- Added configurable interval for Switch action state auto-update #7
3+
- Removed analytics #20
4+
- Removed warning response when Homebridge does not change state immediately
5+
- Auth token caching
26
- F# SDK aligned with [Changes in Stream Deck 6.0](https://developer.elgato.com/documentation/stream-deck/sdk/changelog/)
3-
- Removed analytics
4-
- Dependencies update
7+
- Dependencies update (Fable 4, React 18, Feliz 2, Elmish 4 and more)
58

69
## 1.3.1 - 27 Feb 2022
710
- Fix version in manifest.json

src/StreamDeck.Homebridge/Domain.fs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ type GlobalSettings = {
66
Host: string
77
UserName: string
88
Password: string
9+
UpdateInterval: int
910
}
1011

1112
type ActionSetting = {

src/StreamDeck.Homebridge/PiView.fs

Lines changed: 32 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ let init isDevMode =
5454
Host = "http://192.168.0.55:8581"
5555
UserName = "admin"
5656
Password = "admin"
57+
UpdateInterval = 5
5758
}
5859
Client = Error null
5960
IsLoading = Ok false
@@ -508,15 +509,43 @@ let render (model: PiModel) (dispatch: PiMsg -> unit) =
508509
prop.value model.ServerInfo.Password
509510
prop.required true
510511
prop.onChange(fun value ->
511-
dispatch
512-
<| PiMsg.UpdateServerInfo
512+
let settings =
513513
{ model.ServerInfo with
514514
Password = value
515-
})
515+
}
516+
517+
dispatch <| PiMsg.UpdateServerInfo settings)
518+
]
519+
]
520+
]
521+
522+
Html.div [
523+
prop.className SdPi.Item
524+
prop.children [
525+
Html.div [ prop.className SdPi.ItemLabel; prop.text "Update" ]
526+
Html.select [
527+
prop.classes [ SdPi.ItemValue; "select" ]
528+
prop.value model.ServerInfo.UpdateInterval
529+
prop.onChange(fun (value: string) ->
530+
let settings =
531+
{ model.ServerInfo with
532+
UpdateInterval = int(value)
533+
}
534+
535+
dispatch <| PiMsg.UpdateServerInfo settings)
536+
prop.children [
537+
Html.option [ prop.value "0"; prop.text "Never" ]
538+
Html.option [ prop.value "1"; prop.text "Every second" ]
539+
Html.option [ prop.value "2"; prop.text "Every 2 seconds" ]
540+
Html.option [ prop.value "5"; prop.text "Every 5 seconds" ]
541+
Html.option [ prop.value "10"; prop.text "Every 10 seconds" ]
542+
Html.option [ prop.value "60"; prop.text "Every minute" ]
543+
]
516544
]
517545
]
518546
]
519547

548+
520549
Html.div [
521550
prop.className SdPi.Item
522551
prop.type' "button"

src/StreamDeck.Homebridge/PluginAgent.fs

Lines changed: 76 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,15 @@ module StreamDeck.Homebridge.PluginAgent
33
open Browser.Dom
44
open StreamDeck.SDK
55
open StreamDeck.SDK.PluginModel
6+
open System
67

78
type PluginInnerState = {
89
replyAgent: MailboxProcessor<PluginOutEvent>
910
client: Client.HomebridgeClient option
11+
lastCharacteristicUpdate: DateTime
1012
characteristics: Map<string * string, Client.AccessoryServiceCharacteristic>
1113
visibleActions: Map<string, Domain.ActionSetting * int option>
14+
updateInterval: int
1215
timerId: float option
1316
}
1417

@@ -39,15 +42,12 @@ let processKeyUp (state: PluginInnerState) (event: Dto.Event) (payload: Dto.Acti
3942
let targetValue = 1 - currentValue
4043

4144
match! client.SetAccessoryCharacteristic accessoryId characteristicType targetValue with
42-
| Ok accessory' ->
43-
let ch' = accessory' |> PiView.getCharacteristic characteristicType
44-
let currentValue' = ch'.value.Value :?> int
45-
46-
if currentValue = currentValue' then
47-
state.replyAgent.Post <| PluginOutEvent.ShowAlert event.context
48-
else
49-
state.replyAgent.Post
50-
<| PluginOutEvent.SetState(event.context, currentValue')
45+
| Ok accessory ->
46+
let ch' = accessory |> PiView.getCharacteristic characteristicType
47+
let updatedValue = ch'.value.Value :?> int
48+
49+
if targetValue = updatedValue then
50+
state.replyAgent.Post <| PluginOutEvent.ShowOk event.context
5151
| Error e -> onError e
5252
| _ -> onError $"Cannot find characteristic by id '{accessoryId}, {characteristicType}'."
5353
| _ -> onError "Action is not properly configured"
@@ -66,33 +66,45 @@ let processKeyUp (state: PluginInnerState) (event: Dto.Event) (payload: Dto.Acti
6666
let ch = accessory |> PiView.getCharacteristic characteristicType
6767
let currentValue = ch.value.Value :?> float
6868

69-
if abs(targetValue - currentValue) > 1e-8 then
70-
state.replyAgent.Post <| PluginOutEvent.ShowAlert event.context
71-
else
69+
if abs(targetValue - currentValue) < 1e-8 then
7270
state.replyAgent.Post <| PluginOutEvent.ShowOk event.context
7371
| Error e -> onError e
7472
| _ -> onError "Action is not properly configured"
7573
| _ -> onError $"Action {event.action} is not yet supported"
7674
}
7775

78-
let updateState(state: PluginInnerState) = async {
79-
let! accessories =
80-
state.client
81-
|> Option.map(fun client -> client.GetAccessories())
82-
|> Option.defaultValue(async { return Error("Homedbridge client is not set yet") })
83-
84-
let characteristics =
85-
match accessories with
86-
| Error _ -> state.characteristics
87-
| Ok(accessories) ->
88-
accessories
89-
|> Array.collect(fun accessory ->
90-
accessory.serviceCharacteristics
91-
|> Array.map(fun characteristic ->
92-
let key = accessory.uniqueId, characteristic.``type``
93-
key, characteristic))
94-
|> Map.ofArray
76+
let updateAccessories(state: PluginInnerState) = async {
77+
let now = DateTime.Now
78+
79+
if now - state.lastCharacteristicUpdate < TimeSpan.FromSeconds 1.0 then
80+
return state
81+
else
82+
let! accessories =
83+
state.client
84+
|> Option.map(fun client -> client.GetAccessories())
85+
|> Option.defaultValue(async { return Error("Homedbridge client is not set yet") })
86+
87+
let characteristics =
88+
match accessories with
89+
| Error _ -> state.characteristics
90+
| Ok(accessories) ->
91+
accessories
92+
|> Array.collect(fun accessory ->
93+
accessory.serviceCharacteristics
94+
|> Array.map(fun characteristic ->
95+
let key = accessory.uniqueId, characteristic.``type``
96+
key, characteristic))
97+
|> Map.ofArray
98+
99+
return
100+
{ state with
101+
characteristics = characteristics
102+
lastCharacteristicUpdate = now
103+
}
104+
}
95105

106+
let updateActions(state: PluginInnerState) = async {
107+
let! state = updateAccessories state
96108

97109
let visibleActions =
98110
state.visibleActions
@@ -103,7 +115,7 @@ let updateState(state: PluginInnerState) = async {
103115
CharacteristicType = Some characteristicType
104116
},
105117
Some actionState ->
106-
match characteristics |> Map.tryFind(accessoryId, characteristicType) with
118+
match state.characteristics |> Map.tryFind(accessoryId, characteristicType) with
107119
| Some(ch) when ch.value.IsSome ->
108120
let chValue = ch.value.Value :?> int
109121

@@ -117,14 +129,32 @@ let updateState(state: PluginInnerState) = async {
117129

118130
return
119131
{ state with
120-
characteristics = characteristics
121132
visibleActions = visibleActions
122133
}
123134
}
124135

136+
125137
let createPluginAgent() : MailboxProcessor<PluginInEvent> =
126138
let mutable agent: MailboxProcessor<PluginInEvent> option = None
127139

140+
let updateTimer(state: PluginInnerState) =
141+
if state.timerId.IsSome then
142+
window.clearTimeout(state.timerId.Value)
143+
144+
let timerId =
145+
if state.updateInterval = 0 then
146+
None
147+
else
148+
window.setInterval(
149+
(fun _ -> agent.Value.Post(PluginInEvent.SystemDidWakeUp)),
150+
1000 * state.updateInterval,
151+
[||]
152+
)
153+
|> Some
154+
155+
{ state with timerId = timerId }
156+
157+
128158
agent <-
129159
MailboxProcessor.Start(fun inbox ->
130160
let rec idle() = async {
@@ -137,8 +167,10 @@ let createPluginAgent() : MailboxProcessor<PluginInEvent> =
137167
let state = {
138168
replyAgent = replyAgent
139169
client = None
170+
lastCharacteristicUpdate = DateTime.MinValue
140171
characteristics = Map.empty
141172
visibleActions = Map.empty
173+
updateInterval = 0
142174
timerId = None
143175
}
144176

@@ -155,23 +187,27 @@ let createPluginAgent() : MailboxProcessor<PluginInEvent> =
155187
match msg with
156188
| PluginInEvent.DidReceiveGlobalSettings settings ->
157189
let state =
158-
{ state with
159-
client =
160-
Domain.tryParse<Domain.GlobalSettings>(settings)
161-
|> Option.map(Client.HomebridgeClient)
162-
}
190+
match Domain.tryParse<Domain.GlobalSettings>(settings) with
191+
| Some(settings) ->
192+
{ state with
193+
client = Some(Client.HomebridgeClient settings)
194+
updateInterval = settings.UpdateInterval
195+
}
196+
|> updateTimer
197+
| _ -> state
163198

164199
return! loop state
165200
| PluginInEvent.KeyUp(event, payload) ->
166-
let! state = updateState state
201+
let! state = updateActions state
167202
do! processKeyUp state event payload
168-
// TODO: update state of changed action
169203
return! loop state
170204
| PluginInEvent.SystemDidWakeUp ->
171205
// Fake action triggered by timer to update buttons state
172-
let! state = updateState state
206+
let! state = updateActions state
173207
return! loop state
174208
| PluginInEvent.WillAppear(event, payload) ->
209+
let! state = updateActions state
210+
175211
let state =
176212
{ state with
177213
visibleActions =
@@ -180,18 +216,8 @@ let createPluginAgent() : MailboxProcessor<PluginInEvent> =
180216
state.visibleActions
181217
|> Map.add event.context (actionSetting, payload.state)
182218
| _ -> state.visibleActions
183-
timerId =
184-
match state.timerId with
185-
| Some _ -> state.timerId
186-
| None ->
187-
Some(
188-
window.setInterval(
189-
(fun _ -> agent.Value.Post(PluginInEvent.SystemDidWakeUp)),
190-
5_000,
191-
[||]
192-
)
193-
)
194219
}
220+
|> updateTimer
195221

196222
return! loop state
197223
| PluginInEvent.WillDisappear(event, _) ->

0 commit comments

Comments
 (0)