-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathbbqcli.ml
More file actions
641 lines (522 loc) · 16.9 KB
/
bbqcli.ml
File metadata and controls
641 lines (522 loc) · 16.9 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
(* bbqcli.ml -- CLI etc. for the WLANThermo API *)
let host_default = "wlanthermo"
let program_name = "bbqcli"
module WT = WLANThermo
let print_json j =
Yojson.Basic.pretty_to_string j |> print_endline
open Cmdliner
module type Common =
sig
val man_footer : Manpage.block list
val term : ThoCurl.options Term.t
end
module Common : Common =
struct
let man_footer =
[ `S Manpage.s_files;
`P "None, so far.";
`S Manpage.s_authors;
`P "Thorsten Ohl <ohl@physik.uni-wuerzburg.de>.";
`S Manpage.s_bugs;
`P "Report bugs to <ohl@physik.uni-wuerzburg.de>." ]
let docs = Manpage.s_common_options
let ssl_arg =
let doc = "Use SSL to connect to the host. \
This option should never be necessary or even used, \
because WLANThermo does not understand SSL." in
let env = Cmd.Env.info "WLANTHERMO_SSL" in
let open Arg in
value
& opt bool ~vopt:true false
& info ["s"; "ssl"] ~docv:"true/false" ~doc ~docs ~env
let host_arg =
let doc = "Connect to the host $(docv)." in
let env = Cmd.Env.info "WLANTHERMO_HOST" in
let open Arg in
value
& opt string host_default
& info ["H"; "host"] ~docv:"HOST" ~doc ~docs ~env
let verbose_arg =
let doc = "Be more verbose." in
let env = Cmd.Env.info "WLANTHERMO_VERBOSITY" in
let open Arg in
value
& opt int 0
& info ["v"; "verbosity"; "verbose"] ~docv:"VERBOSITY" ~doc ~docs ~env
let timeout_arg =
let doc = "Wait only $(docv) for response." in
let env = Cmd.Env.info "WLANTHERMO_TIMEOUT" in
let open Arg in
value
& opt (some int) None
& info ["T"; "timeout"] ~docv:"SECONDS" ~doc ~docs ~env
let term =
let open Term in
const
(fun ssl host verbosity timeout ->
{ ThoCurl.ssl; ThoCurl.host; ThoCurl.verbosity; ThoCurl.timeout })
$ ssl_arg
$ host_arg
$ verbose_arg
$ timeout_arg
end
let all_arg =
let doc = "Include the inactive channels." in
let open Arg in
value
& flag
& info ["a"; "all"] ~doc
module Channels : sig val term : int list Term.t end =
struct
(* int list list *)
let channels_arg =
let doc = "Select the channel(s) $(docv) (can be repeated)." in
let open Arg in
value
& opt_all (list int) []
& info ["c"; "channel"] ~docv:"N[,M...]" ~doc
(* (int * int) list list *)
let channel_ranges_arg =
let doc = "Select the channels in the range $(docv) (can be repeated)." in
let open Arg in
value
& opt_all (list (pair ~sep:'-' int int)) []
& info ["C"; "channels"] ~docv:"FROM-TO" ~doc
let term =
let open Term in
const
(fun channels channel_ranges ->
ThoList.merge_integer_ranges channels channel_ranges)
$ channels_arg
$ channel_ranges_arg
end
module type Unit_Cmd =
sig
val cmd : unit Cmd.t
end
module Temperature : Unit_Cmd =
struct
let man = [
`S Manpage.s_description;
`P "List the current temperature measurements \
and associated alarms." ] @ Common.man_footer
let print_temperatures ?all common channels =
WT.format_channels ?all common channels |> List.iter print_endline
let term =
let open Term in
const
(fun common all channels ->
print_temperatures ~all common channels)
$ Common.term
$ all_arg
$ Channels.term
let cmd =
Cmd.v (Cmd.info "temperature" ~man) term
end
module Rename : Unit_Cmd =
struct
let man = [
`S Manpage.s_description;
`P "Rename a channel." ] @ Common.man_footer
(* int *)
let channel_arg =
let doc = "The channel $(docv) to be renamed." in
let open Arg in
required
& pos 0 (some int) None
& info [] ~docv:"CH" ~doc
(* string *)
let name_arg =
let doc = "The new name $(docv)." in
let open Arg in
required
& pos 1 (some string) None
& info [] ~docv:"NAME" ~doc
let term =
let open Term in
const WT.rename_channel
$ Common.term
$ channel_arg
$ name_arg
let cmd =
Cmd.v (Cmd.info "rename" ~man) term
end
module Alarm : Unit_Cmd =
struct
let man = [
`S Manpage.s_description;
`P "Change temperature ranges and associated \
alarms." ] @ Common.man_footer
(* Put the long form of equivalent options last so that they are
used for the description of the default in the manpage. *)
let switch_list = [("+", WT.On); ("on", WT.On); ("-", WT.Off); ("off", WT.Off)]
let switch = Arg.enum switch_list
let switch_docv = String.concat "|" (List.map fst switch_list)
(* (float * float) option *)
let range_arg =
let doc = "Select the temperature range $(docv)." in
let open Arg in
value
& opt (some (pair ~sep:'-' float float)) None
& info ["t"; "temperature"; "temp"] ~docv:"MIN-MAX" ~doc
(* float option *)
let min_arg =
let doc = "Select the lower temperature limit $(docv). \
This takes precedence over the lower limit of a \
range specified in --temperature." in
let open Arg in
value
& opt (some float) None
& info ["m"; "min"] ~docv:"MIN" ~doc
(* float option *)
let max_arg =
let doc = "Select the upper temperature limit $(docv). \
This takes precedence over upper limit of a \
range specified in --temperature." in
let open Arg in
value
& opt (some float) None
& info ["M"; "max"] ~docv:"MAX" ~doc
(* WT.switch option *)
let push_arg =
let doc = "Switch the push alarm on/off." in
let open Arg in
value
& opt (some switch) ~vopt:(Some WT.On) None
& info ["p"; "push"] ~docv:switch_docv ~doc
(* WT.switch option *)
let beep_arg =
let doc = "Switch the beep alarm on/off." in
let open Arg in
value
& opt (some switch) ~vopt:(Some WT.On) None
& info ["b"; "beep"] ~docv:switch_docv ~doc
let term =
let open Term in
const
(fun common all channels range min max push beep ->
WT.update_channels common ~all ?range ?min ?max ?push ?beep channels)
$ Common.term
$ all_arg
$ Channels.term
$ range_arg
$ min_arg
$ max_arg
$ push_arg
$ beep_arg
let man = [
`S Manpage.s_description;
`P "Change the temperature limits and associated alarms \
on a WT Mini V3 using the HTTP API." ] @ Common.man_footer
let cmd =
Cmd.v (Cmd.info "alarm" ~man) term
end
module Pitmaster : Unit_Cmd =
struct
let man = [
`S Manpage.s_description;
`P "Print the pitmaster status." ] @ Common.man_footer
let term =
let open Term in
const (fun common -> WT.format_pitmasters common |> List.iter print_endline)
$ Common.term
let cmd =
Cmd.v (Cmd.info "pitmaster" ~man) term
end
module Control : Unit_Cmd =
struct
let man = [
`S Manpage.s_description;
`P "Modify the pitmaster status.";
`S Manpage.s_options;
`P "The options --recall, --auto, --manual and --off are \
evaluated in that order. For example, the command";
`Pre " bbqcli -a 99 -o";
`P "sets the target temperature to 99 degrees and switches \
the pitmaster off."] @ Common.man_footer
let channel_arg =
let doc = "Connect the pitmaster to the channel number $(docv)." in
let open Arg in
value
& opt (some int) None
& info ["c"; "channel"] ~docv:"CH" ~doc
let pitmaster_arg =
let doc = "Modify the pitmaster number $(docv). \
This is never needed if there is only \
a single pitmaster with number 0." in
let open Arg in
value
& opt int 0
& info ["p"; "pitmaster"] ~docv:"PM" ~doc
let recall_arg =
let doc = "Switch the pitmaster back to the last active state." in
let open Arg in
value
& flag
& info ["r"; "recall"] ~doc
let off_arg =
let doc = "Switch the pitmaster off." in
let open Arg in
value
& flag
& info ["o"; "off"] ~doc
let auto_arg =
let doc = "Switch the pitmaster in auto mode with \
target temperature $(docv). Negative values \
keep the old value unchanged." in
let open Arg in
value
& opt (some float) ~vopt:(Some (-1.)) None
& info ["a"; "auto"] ~docv:"T" ~doc
let manual_arg =
let doc = "Switch the pitmaster in manual mode with \
$(docv)% power. Negative values \
keep the old value unchanged." in
let open Arg in
value
& opt (some int) ~vopt:(Some (-1)) None
& info ["m"; "manual"] ~docv:"P" ~doc
let term =
let open Term in
const (fun common pitmaster channel recall off auto manual ->
WT.update_pitmaster common ?channel ~recall ~off ?auto ?manual pitmaster)
$ Common.term
$ pitmaster_arg
$ channel_arg
$ recall_arg
$ off_arg
$ auto_arg
$ manual_arg
let cmd =
Cmd.v (Cmd.info "control" ~man) term
end
module Info : Unit_Cmd =
struct
let man = [
`S Manpage.s_description;
`P "Echo the unformatted response to \"/info\". According to \
the developers, this is not meant to be parsed, but just as \
quick feedback." ] @ Common.man_footer
let term =
let open Term in
const (fun common -> WT.get_info common |> print_endline)
$ Common.term
let cmd =
Cmd.v (Cmd.info "info" ~man) term
end
module Data : Unit_Cmd =
struct
let man = [
`S Manpage.s_description;
`P "Echo the parsed and pretty printed JSON response to \"/data\". \
Currently, no processing is done. This will change in the \
future." ] @ Common.man_footer
let term =
let open Term in
const (fun common -> WT.get_data common |> print_json)
$ Common.term
let cmd =
Cmd.v (Cmd.info "data" ~man) term
end
module Settings : Unit_Cmd =
struct
let man = [
`S Manpage.s_description;
`P "Echo the parsed and pretty printed JSON response to \"/settings\". \
Currently, no processing is done. This will change in the \
future." ] @ Common.man_footer
let term =
let open Term in
const (fun common -> WT.get_settings common |> print_json)
$ Common.term
let cmd =
Cmd.v (Cmd.info "settings" ~man) term
end
module Battery : Unit_Cmd =
struct
let man = [
`S Manpage.s_description;
`P "Print the current changing status." ] @ Common.man_footer
let term =
let open Term in
const (fun common -> WT.format_battery common |> print_endline)
$ Common.term
let cmd =
Cmd.v (Cmd.info "battery" ~man) term
end
module Monitor : Unit_Cmd =
struct
let man = [
`S Manpage.s_description;
`P "Continuously monitor the WLANThermo." ] @ Common.man_footer
(* int *)
let number_arg =
let doc = "Stop after $(docv) measurements. \
A negative value or 0 will let the \
monitoring contine indefinitely." in
let open Arg in
value
& opt int 0
& info ["n"; "number"] ~docv:"N" ~doc
(* int *)
let wait_arg =
let doc = "Wait $(docv) seconds between measurements. \
A negative value or 0 will be mapped to 1." in
let env = Cmd.Env.info "WLANTHERMO_WAIT" in
let open Arg in
value
& opt int 10
& info ["w"; "wait"] ~docv:"SEC" ~doc ~env
type format =
| Time
| Date_Time
| Seconds
let format_list = [("time", Time); ("date-time", Date_Time); ("seconds", Seconds)]
let format_enum = Arg.enum format_list
let format_docv = String.concat "|" (List.map fst format_list)
(* string option *)
let format_arg =
let doc = "Select the format of the timestamp. One of
\"time\", \"date-time\" or \"seconds\"." in
let open Arg in
value
& opt (some format_enum) ~vopt:(Some Time) None
& info ["F"; "format"] ~docv:"FORMAT" ~doc
(* string option *)
let epoch_arg =
let doc = "Print time passed since $(docv). \
An empty string means now. Otherwise it must \
be given in the format \"HH:MM\" or \"HH:MM:SS\"." in
let open Arg in
value
& opt (some string) ~vopt:(Some "") None
& info ["E"; "epoch"] ~docv:"TIME" ~doc
(* Evaluate the ~number-th power
f (f (f ... (f initial)))
waiting ~wait(>0) seconds between evaluations. *)
let repeat ~wait ~number f initial =
let wait = max 1 wait
and number = max 0 number in
let rec repeat' n previous =
let state = f previous in
if n <> 1 then
begin
Unix.sleep wait;
(repeat' [@tailcall]) (max 0 (pred n)) state
end in
repeat' number initial
let decode_epoch epoch =
match String.lowercase_ascii epoch with
| "" | "n" | "no" | "now" -> ThoTime.now ()
| time -> ThoTime.of_string_time time
let decode_format_epoch = function
| (None | Some Time), None -> ThoTime.Time
| Some Seconds, None -> ThoTime.Seconds
| Some Date_Time, None -> ThoTime.Date_Time
| (None | Some Time), Some epoch ->
ThoTime.Time_since (decode_epoch epoch)
| Some Seconds, Some epoch -> ThoTime.Seconds_since (decode_epoch epoch)
| Some Date_Time, Some epoch ->
prerr_endline
(program_name ^
": the combination of --format=date-time with --epoch \
makes no sense, falling back to --format=time.");
flush stderr;
ThoTime.Time_since (decode_epoch epoch)
let term =
let open Term in
const
(fun common channels format epoch wait number ->
let format = decode_format_epoch (format, epoch) in
repeat ~wait ~number (WT.monitor_temperatures ~format common channels) ([], []))
$ Common.term
$ Channels.term
$ format_arg
$ epoch_arg
$ wait_arg
$ number_arg
let cmd =
Cmd.v (Cmd.info "monitor" ~man) term
end
module Chef : Unit_Cmd =
struct
let man = [
`S Manpage.s_description;
`P "Execute a recipe.";
`P "NB: This is purely experimental at the moment and only used \
for figuring out features, abstract and concrete syntax. \
Don't expect anything to work." ] @ Common.man_footer
(* string *)
let recipe_arg =
let doc = "Interpret the string $(docv) as recipe. \
Can be repeated, but each string must be a \
syntactically valid recipe." in
let open Arg in
value
& opt_all string []
& info ["R"; "Recipe"] ~docv:"RECIPE" ~doc
(* Wrap multiple lines. *)
let recipe_term =
let open Term in
const
(fun recipe ->
match recipe with
| [] -> None
| lines -> Some (String.concat "\n" lines))
$ recipe_arg
(* string *)
let recipe_file_arg =
let doc = "Interpret the contents of file $(docv) as recipe. \
Can be repeated, but each file must be a \
syntactically valid recipe." in
let open Arg in
value
& opt_all string []
& info ["r"; "recipe"] ~docv:"FILE" ~doc
let term =
let open Term in
const
(fun common recipes recipe_files ->
ignore common;
let recipe =
List.map Recipe.of_string recipes
@ List.map Recipe.of_file recipe_files in
List.iter Recipe.pretty_print recipe)
$ Common.term
$ recipe_arg
$ recipe_file_arg
let cmd =
Cmd.v (Cmd.info "chef" ~man) term
end
module Main : Unit_Cmd =
struct
let man = [
`S Manpage.s_description;
`P "Control a WLANThermo Mini V3 on the command line \
using the HTTP API.";
`S Manpage.s_examples;
`Pre " bbqcli alarm -C 3-5 -c 9 -t 80-110 -p on";
`P "Sets the temperature range on channels 3,4,5,9 \
to [80,110] and switches on the push alert.";
`Pre " bbqcli temperature -a";
`P "List the temperatures and limits for all channels, \
including the limits of disconnected channels.";
`Pre " bbqcli monitor -w 60";
`P "Monitor all temperatures every minute." ] @ Common.man_footer
let cmd =
Cmd.group
(Cmd.info "bbqcli" ~man)
[ Temperature.cmd;
Rename.cmd;
Alarm.cmd;
Pitmaster.cmd;
Control.cmd;
Monitor.cmd;
Chef.cmd;
Battery.cmd;
Data.cmd;
Settings.cmd;
Info.cmd ]
end
let () =
exit (Cmd.eval Main.cmd)