@@ -80,24 +80,12 @@ func (h *Handlers) HandlePumpPub() turbostreams.HandlerFunc {
80
80
t := pumpPartial
81
81
h .r .MustHave (t )
82
82
return func (c * turbostreams.Context ) error {
83
- // Parse params
84
- id , err := parseID (c .Param ("id" ), "instrument" )
85
- if err != nil {
86
- return err
87
- }
88
- controllerID , err := parseID (c .Param ("controllerID" ), "controller" )
83
+ // Parse params & run queries
84
+ instrumentID , controllerID , pc , err := getPlanktoscopeClientForPub (c , h .pco )
89
85
if err != nil {
90
86
return err
91
87
}
92
88
93
- // Run queries
94
- pc , ok := h .pco .Get (controllerID )
95
- if ! ok {
96
- return errors .Errorf (
97
- "planktoscope client for controller %d on instrument %d not found" , controllerID , id ,
98
- )
99
- }
100
-
101
89
// Publish on MQTT update
102
90
for {
103
91
ctx := c .Context ()
@@ -111,7 +99,7 @@ func (h *Handlers) HandlePumpPub() turbostreams.HandlerFunc {
111
99
}
112
100
// We insert an empty Auth object because the MSG handler will add the auth object for each
113
101
// client
114
- message := replacePumpStream (id , controllerID , auth.Auth {}, pc )
102
+ message := replacePumpStream (instrumentID , controllerID , auth.Auth {}, pc )
115
103
c .Publish (message )
116
104
}
117
105
}
@@ -122,10 +110,6 @@ type PlanktoscopePumpViewAuthz struct {
122
110
Set bool
123
111
}
124
112
125
- type PlanktoscopeControllerViewAuthz struct {
126
- Pump PlanktoscopePumpViewAuthz
127
- }
128
-
129
113
func getPlanktoscopePumpViewAuthz (
130
114
ctx context.Context , id , controllerID int64 , a auth.Auth , azc * auth.AuthzChecker ,
131
115
) (authz PlanktoscopePumpViewAuthz , err error ) {
@@ -136,45 +120,13 @@ func getPlanktoscopePumpViewAuthz(
136
120
return authz , nil
137
121
}
138
122
139
- func getPlanktoscopeControllerViewAuthz (
140
- ctx context.Context , id , controllerID int64 , a auth.Auth , azc * auth.AuthzChecker ,
141
- ) (authz PlanktoscopeControllerViewAuthz , err error ) {
142
- if authz .Pump , err = getPlanktoscopePumpViewAuthz (ctx , id , controllerID , a , azc ); err != nil {
143
- return PlanktoscopeControllerViewAuthz {}, errors .Wrap (err , "couldn't check authz for pump" )
144
- }
145
- return authz , nil
146
- }
147
-
148
123
func (h * Handlers ) ModifyPumpMsgData () handling.DataModifier {
149
124
return func (
150
125
ctx context.Context , a auth.Auth , data map [string ]interface {},
151
126
) (modifications map [string ]interface {}, err error ) {
152
- instrumentID , ok := data ["InstrumentID" ]
153
- if ! ok {
154
- return nil , errors .New (
155
- "couldn't find instrument id from turbostreams message data to check authorizations" ,
156
- )
157
- }
158
- id , ok := instrumentID .(int64 )
159
- if ! ok {
160
- return nil , errors .Errorf (
161
- "instrument id has unexpected type %T in turbostreams message data for checking authorization" ,
162
- instrumentID ,
163
- )
164
- }
165
- controllerID , ok := data ["ControllerID" ]
166
- if ! ok {
167
- return nil , errors .Errorf (
168
- "couldn't find controller id for instrument %d from turbostreams message data to check authorizations" ,
169
- id ,
170
- )
171
- }
172
- cid , ok := controllerID .(int64 )
173
- if ! ok {
174
- return nil , errors .Errorf (
175
- "controller id has unexpected type %T in turbostreams message data for checking authorization" ,
176
- controllerID ,
177
- )
127
+ id , cid , err := getIDsForModificationMiddleware (data )
128
+ if err != nil {
129
+ return nil , err
178
130
}
179
131
modifications = make (map [string ]interface {})
180
132
if modifications ["Authorizations" ], err = getPlanktoscopePumpViewAuthz (
@@ -229,3 +181,258 @@ func (h *Handlers) HandlePumpPost() auth.HTTPHandlerFunc {
229
181
return c .Redirect (http .StatusSeeOther , fmt .Sprintf ("/instruments/%d" , id ))
230
182
}
231
183
}
184
+
185
+ // Camera
186
+
187
+ const cameraPartial = "instruments/planktoscope/camera.partial.tmpl"
188
+
189
+ func replaceCameraStream (
190
+ id , controllerID int64 , a auth.Auth , pc * planktoscope.Client ,
191
+ ) turbostreams.Message {
192
+ state := pc .GetState ()
193
+ return turbostreams.Message {
194
+ Action : turbostreams .ActionReplace ,
195
+ Target : fmt .Sprintf ("/instruments/%d/controllers/%d/camera" , id , controllerID ),
196
+ Template : cameraPartial ,
197
+ Data : map [string ]interface {}{
198
+ "InstrumentID" : id ,
199
+ "ControllerID" : controllerID ,
200
+ "CameraSettings" : state .CameraSettings ,
201
+ "Auth" : a ,
202
+ },
203
+ }
204
+ }
205
+
206
+ func handleCameraSettings (
207
+ isoRaw , shutterSpeedRaw ,
208
+ autoWhiteBalanceRaw , whiteBalanceRedGainRaw , whiteBalanceBlueGainRaw string ,
209
+ pc * planktoscope.Client ,
210
+ ) (err error ) {
211
+ var token mqtt.Token
212
+ // TODO: use echo's request binding functionality instead of strconv.ParseFloat
213
+ // TODO: perform input validation and handle invalid inputs
214
+ const uintBase = 10
215
+ const uintWidth = 64
216
+ iso , err := strconv .ParseUint (isoRaw , uintBase , uintWidth )
217
+ if err != nil {
218
+ return echo .NewHTTPError (http .StatusBadRequest , errors .Wrap (err , "couldn't parse iso" ))
219
+ }
220
+ shutterSpeed , err := strconv .ParseUint (shutterSpeedRaw , uintBase , uintWidth )
221
+ if err != nil {
222
+ return echo .NewHTTPError (http .StatusBadRequest , errors .Wrap (err , "couldn't parse shutter speed" ))
223
+ }
224
+
225
+ const floatWidth = 64
226
+ autoWhiteBalance := strings .ToLower (autoWhiteBalanceRaw ) == "true"
227
+ whiteBalanceRedGain , err := strconv .ParseFloat (whiteBalanceRedGainRaw , floatWidth )
228
+ if err != nil {
229
+ return echo .NewHTTPError (http .StatusBadRequest , errors .Wrap (
230
+ err , "couldn't parse white balance red gain" ,
231
+ ))
232
+ }
233
+ whiteBalanceBlueGain , err := strconv .ParseFloat (whiteBalanceBlueGainRaw , floatWidth )
234
+ if err != nil {
235
+ return echo .NewHTTPError (http .StatusBadRequest , errors .Wrap (
236
+ err , "couldn't parse white balance blue gain" ,
237
+ ))
238
+ }
239
+
240
+ if token , err = pc .SetCamera (
241
+ iso , shutterSpeed , autoWhiteBalance , whiteBalanceRedGain , whiteBalanceBlueGain ,
242
+ ); err != nil {
243
+ return err
244
+ }
245
+
246
+ stateUpdated := pc .CameraStateBroadcasted ()
247
+ // TODO: instead of waiting forever, have a timeout before redirecting and displaying a
248
+ // warning message that we haven't heard any camera settings updates from the planktoscope.
249
+ if token .Wait (); token .Error () != nil {
250
+ return token .Error ()
251
+ }
252
+ <- stateUpdated
253
+ return nil
254
+ }
255
+
256
+ func (h * Handlers ) HandleCameraPub () turbostreams.HandlerFunc {
257
+ t := cameraPartial
258
+ h .r .MustHave (t )
259
+ return func (c * turbostreams.Context ) error {
260
+ // Parse params & run queries
261
+ instrumentID , controllerID , pc , err := getPlanktoscopeClientForPub (c , h .pco )
262
+ if err != nil {
263
+ return err
264
+ }
265
+
266
+ // Publish on MQTT update
267
+ for {
268
+ ctx := c .Context ()
269
+ select {
270
+ case <- ctx .Done ():
271
+ return ctx .Err ()
272
+ case <- pc .CameraStateBroadcasted ():
273
+ if err := ctx .Err (); err != nil {
274
+ // Context was also canceled and it should have priority
275
+ return err
276
+ }
277
+ // We insert an empty Auth object because the MSG handler will add the auth object for each
278
+ // client
279
+ message := replaceCameraStream (instrumentID , controllerID , auth.Auth {}, pc )
280
+ c .Publish (message )
281
+ }
282
+ }
283
+ }
284
+ }
285
+
286
+ type PlanktoscopeCameraViewAuthz struct {
287
+ Set bool
288
+ }
289
+
290
+ func getPlanktoscopeCameraViewAuthz (
291
+ ctx context.Context , id , controllerID int64 , a auth.Auth , azc * auth.AuthzChecker ,
292
+ ) (authz PlanktoscopeCameraViewAuthz , err error ) {
293
+ path := fmt .Sprintf ("/instruments/%d/controllers/%d/camera" , id , controllerID )
294
+ if authz .Set , err = azc .Allow (ctx , a , path , http .MethodPost , nil ); err != nil {
295
+ return PlanktoscopeCameraViewAuthz {}, errors .Wrap (
296
+ err , "couldn't check authz for setting camera" ,
297
+ )
298
+ }
299
+ return authz , nil
300
+ }
301
+
302
+ func (h * Handlers ) ModifyCameraMsgData () handling.DataModifier {
303
+ return func (
304
+ ctx context.Context , a auth.Auth , data map [string ]interface {},
305
+ ) (modifications map [string ]interface {}, err error ) {
306
+ id , cid , err := getIDsForModificationMiddleware (data )
307
+ if err != nil {
308
+ return nil , err
309
+ }
310
+ modifications = make (map [string ]interface {})
311
+ if modifications ["Authorizations" ], err = getPlanktoscopeCameraViewAuthz (
312
+ ctx , id , cid , a , h .azc ,
313
+ ); err != nil {
314
+ return nil , errors .Wrapf (
315
+ err , "couldn't check authz for camera of controller %d of instrument %d" , cid , id ,
316
+ )
317
+ }
318
+ return modifications , nil
319
+ }
320
+ }
321
+
322
+ func (h * Handlers ) HandleCameraPost () auth.HTTPHandlerFunc {
323
+ t := cameraPartial
324
+ h .r .MustHave (t )
325
+ return func (c echo.Context , a auth.Auth ) error {
326
+ // Parse params
327
+ id , err := parseID (c .Param ("id" ), "instrument" )
328
+ if err != nil {
329
+ return err
330
+ }
331
+ controllerID , err := parseID (c .Param ("controllerID" ), "controller" )
332
+ if err != nil {
333
+ return err
334
+ }
335
+
336
+ // Run queries
337
+ pc , ok := h .pco .Get (id )
338
+ if ! ok {
339
+ return errors .Errorf (
340
+ "planktoscope client for controller %d on instrument %d not found" , id , controllerID ,
341
+ )
342
+ }
343
+ if err = handleCameraSettings (
344
+ c .FormValue ("iso" ), c .FormValue ("shutter-speed" ),
345
+ c .FormValue ("awb" ), c .FormValue ("wb-red" ), c .FormValue ("wb-blue" ), pc ,
346
+ ); err != nil {
347
+ return err
348
+ }
349
+
350
+ // We rely on Turbo Streams over websockets, so we return an empty response here to avoid a race
351
+ // condition of two Turbo Stream replace messages (where the one from this POST response could
352
+ // be stale and overwrite a fresher message over websockets by arriving later).
353
+ // FIXME: is there a cleaner way to avoid the race condition which would work even if the
354
+ // WebSocket connection is misbehaving?
355
+ if turbostreams .Accepted (c .Request ().Header ) {
356
+ return h .r .TurboStream (c .Response ())
357
+ }
358
+
359
+ // Redirect user
360
+ return c .Redirect (http .StatusSeeOther , fmt .Sprintf ("/instruments/%d" , id ))
361
+ }
362
+ }
363
+
364
+ // Controller
365
+
366
+ type PlanktoscopeControllerViewAuthz struct {
367
+ Pump PlanktoscopePumpViewAuthz
368
+ Camera PlanktoscopeCameraViewAuthz
369
+ }
370
+
371
+ func getPlanktoscopeControllerViewAuthz (
372
+ ctx context.Context , id , controllerID int64 , a auth.Auth , azc * auth.AuthzChecker ,
373
+ ) (authz PlanktoscopeControllerViewAuthz , err error ) {
374
+ if authz .Pump , err = getPlanktoscopePumpViewAuthz (ctx , id , controllerID , a , azc ); err != nil {
375
+ return PlanktoscopeControllerViewAuthz {}, errors .Wrap (err , "couldn't check authz for pump" )
376
+ }
377
+ if authz .Camera , err = getPlanktoscopeCameraViewAuthz (ctx , id , controllerID , a , azc ); err != nil {
378
+ return PlanktoscopeControllerViewAuthz {}, errors .Wrap (err , "couldn't check authz for camera" )
379
+ }
380
+ return authz , nil
381
+ }
382
+
383
+ func getIDsForModificationMiddleware (
384
+ data map [string ]interface {},
385
+ ) (instrumentID int64 , controllerID int64 , err error ) {
386
+ rawInstrumentID , ok := data ["InstrumentID" ]
387
+ if ! ok {
388
+ return 0 , 0 , errors .New (
389
+ "couldn't find instrument id from turbostreams message data to check authorizations" ,
390
+ )
391
+ }
392
+ instrumentID , ok = rawInstrumentID .(int64 )
393
+ if ! ok {
394
+ return 0 , 0 , errors .Errorf (
395
+ "instrument id has unexpected type %T in turbostreams message data for checking authorization" ,
396
+ rawInstrumentID ,
397
+ )
398
+ }
399
+ rawControllerID , ok := data ["ControllerID" ]
400
+ if ! ok {
401
+ return 0 , 0 , errors .Errorf (
402
+ "couldn't find controller id for instrument %d from turbostreams message data to check authorizations" ,
403
+ instrumentID ,
404
+ )
405
+ }
406
+ controllerID , ok = rawControllerID .(int64 )
407
+ if ! ok {
408
+ return 0 , 0 , errors .Errorf (
409
+ "controller id has unexpected type %T in turbostreams message data for checking authorization" ,
410
+ rawControllerID ,
411
+ )
412
+ }
413
+ return instrumentID , controllerID , nil
414
+ }
415
+
416
+ func getPlanktoscopeClientForPub (
417
+ c * turbostreams.Context , pco * planktoscope.Orchestrator ,
418
+ ) (instrumentID int64 , controllerID int64 , client * planktoscope.Client , err error ) {
419
+ // Parse params
420
+ instrumentID , err = parseID (c .Param ("id" ), "instrument" )
421
+ if err != nil {
422
+ return 0 , 0 , nil , err
423
+ }
424
+ controllerID , err = parseID (c .Param ("controllerID" ), "controller" )
425
+ if err != nil {
426
+ return 0 , 0 , nil , err
427
+ }
428
+
429
+ // Run queries
430
+ pc , ok := pco .Get (controllerID )
431
+ if ! ok {
432
+ return 0 , 0 , nil , errors .Errorf (
433
+ "planktoscope client for controller %d on instrument %d not found" ,
434
+ controllerID , instrumentID ,
435
+ )
436
+ }
437
+ return instrumentID , controllerID , pc , nil
438
+ }
0 commit comments