py-multitrack 0.1.0a1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
multitrack/Elements.py ADDED
@@ -0,0 +1,835 @@
1
+ import logging
2
+ from typing import Callable, Literal, Any
3
+ from .models import Element, EntityInfo, GeneralMetadata, ToggleMetadata, ChatboxMetadata, NumberMetadata, DropdownMetadata
4
+
5
+ log = logging.getLogger("MultitrackAPI")
6
+
7
+ class RemoteElements():
8
+ def __init__(self, verbose: bool = False):
9
+ self.verbose = verbose
10
+ self.elements: dict[str, Element] = {}
11
+
12
+ def list_elements(self):
13
+ elements: list[GeneralMetadata | type[GeneralMetadata]]= []
14
+ for e in self.elements.values():
15
+ elements.append(e.metadata)
16
+ return elements
17
+
18
+ # Decorators definitions
19
+ def button(
20
+ self,
21
+ path: str,
22
+ check_ack: Callable[[str], bool] | None = None,
23
+ role: Literal["operator", "admin"] = "operator",
24
+ method: Literal["GET", "POST"] = "POST",
25
+ group: str | None = None,
26
+ sensible: bool = False,
27
+ display_text: str | None = None,
28
+ tooltip: str | None = None
29
+ ):
30
+ """
31
+ Creates a button to execute serial commands.\n
32
+ ### Params
33
+ path: Name of the endpoint. Will then be accessible through `/action/{path}`. [REQUIRED].\n
34
+ role: Determine which role has access to this route.\n
35
+ method: Define the HTTP method to which the API binds the action.\n
36
+ check_ack: If specified, the SerialBridge will queue every other outgoing commands until the condition is fullfilled. Takes the read line as param.\n
37
+ group: Specify the UI group to which the action belongs.\n
38
+ sensible: Specify whether the action needs confirmation before executing.\n
39
+ display_text: Name to display on the UI. If None, capitalize the *label* and replace every "_" with whitespaces.\n
40
+ tooltip: Specify the on_hover tooltip.\n
41
+ ### Decorated params
42
+ e: Snapshot of the targetted EntityInfo from the Core at request time.\n
43
+ ### Decorated return
44
+ Your Serial command (as a *string*) for the specific *action*.
45
+ ### Example
46
+ ```
47
+ @app.remote.button(
48
+ path = "get_position",
49
+ check_ack = lambda x: "ACK,OK" in x or "ACK,NG" in x,
50
+ group = "Basic actions",
51
+ tooltip = "Ask for the position of the remote entity"
52
+ )
53
+ def get_position(e: EntityInfo):
54
+ return f'*SET,IDAS,TXSTAT,IND,{e.serial},{driver.local_id},24'
55
+ ```
56
+ """
57
+ def button(func: Callable[[EntityInfo], str]):
58
+ def nfunc(e: EntityInfo):
59
+ if self.verbose: log.info(f"Executed {func(e)}")
60
+ return func(e)
61
+ self.elements[path] = Element(
62
+ handler = nfunc,
63
+ check_ack = check_ack,
64
+ metadata = GeneralMetadata(
65
+ path=path,
66
+ element="button",
67
+ blocking=check_ack != None,
68
+ scope="remote",
69
+ role=role,
70
+ method=method,
71
+ group=group,
72
+ sensible=sensible,
73
+ display_text=display_text if display_text else path.replace("_", " ").capitalize(),
74
+ tooltip=tooltip
75
+ )
76
+ )
77
+ if self.verbose: log.info(f"{path} bound.")
78
+ return nfunc
79
+ return button
80
+
81
+ def toggle(
82
+ self,
83
+ path: str,
84
+ ref: str,
85
+ check_ack: Callable[[str], bool] | None = None,
86
+ role: Literal["operator", "admin"] = "operator",
87
+ method: Literal["GET", "POST"] = "POST",
88
+ group: str | None = None,
89
+ sensible: bool = False,
90
+ display_text: str | None = None,
91
+ tooltip: str | None = None,
92
+ colors: tuple[str, str] | None = None
93
+ ):
94
+ """
95
+ Creates a toggle button to execute serial commands.\n
96
+ ### Params
97
+ path: Name of the endpoint. Will then be accessible through `/action/{path}`. [REQUIRED].\n
98
+ ref: Value bound to the toggle's state. Check full documentation for more info. [REQUIRED]\n
99
+ role: Determine which role has access to this route.\n
100
+ method: Define the HTTP method to which the API binds the action.\n
101
+ check_ack: If specified, the SerialBridge will queue every other outgoing commands until the condition is fullfilled. Takes the read line as param.\n
102
+ group: Specify the UI group to which the action belongs.\n
103
+ sensible: Specify whether the action needs confirmation before executing.\n
104
+ display_text: Name to display on the UI. If None, capitalize the *label* and replace every "_" with whitespaces.\n
105
+ tooltip: Specify the on_hover tooltip.\n
106
+ colors: HEX values for the button's off/on colors respectively. If None, the Core will default to red/green.\n
107
+ ### Decorated params
108
+ e: Snapshot of the targetted EntityInfo from the Core at request time.\n
109
+ state: State of the *ref* from the Core at request time.\n
110
+ ### Decorated return
111
+ Your Serial command (as a *string*) for the specific *action*.
112
+ ### Example
113
+ ```
114
+ @app.remote.toggle(
115
+ path = "stun",
116
+ ref = "is_stun",
117
+ check_ack = lambda x: "ACK,OK" in x or "ACK,NG" in x,
118
+ group = "Basic actions",
119
+ tooltip = "Toggle the STUN status of a radio."
120
+ )
121
+ def toggle_stun(e: EntityInfo, state: bool):
122
+ if state:
123
+ return f'*SET,IDAS,TXREVIVE,IND,{e.serial},{driver.local_id}'
124
+ else:
125
+ return f'*SET,IDAS,TXSTUN,IND,{e.serial},{driver.local_id}'
126
+ ```
127
+ """
128
+ def toggle(func: Callable[[EntityInfo, bool], str]):
129
+ def nfunc(e: EntityInfo, state: bool):
130
+ if self.verbose: log.info(f"Executed {func(e, state)}")
131
+ return func(e, state)
132
+ self.elements[path] = Element(
133
+ handler = nfunc,
134
+ check_ack = check_ack,
135
+ metadata = ToggleMetadata(
136
+ path=path,
137
+ element="toggle",
138
+ blocking=check_ack != None,
139
+ scope="remote",
140
+ role=role,
141
+ method=method,
142
+ group=group,
143
+ sensible=sensible,
144
+ display_text=display_text if display_text else path.replace("_", " ").capitalize(),
145
+ tooltip=tooltip,
146
+ ref=ref,
147
+ colors=colors
148
+ )
149
+ )
150
+ if self.verbose: log.info(f"{path} bound.")
151
+ return nfunc
152
+ return toggle
153
+
154
+ def chatbox(
155
+ self,
156
+ path: str,
157
+ ref: str = "general",
158
+ check_ack: Callable[[str], bool] | None = None,
159
+ role: Literal["operator", "admin"] = "operator",
160
+ method: Literal["GET", "POST"] = "POST",
161
+ group: str | None = None,
162
+ sensible: bool = False,
163
+ display_text: str | None = None,
164
+ tooltip: str | None = None
165
+ ):
166
+ """
167
+ Creates a chatbox (input and history) to handle text messages. You can specify a *ref* to filter where the messages come from/go to if your device supports multiple "channel-like" messaging.\n
168
+ ### Params
169
+ path: Name of the endpoint. Will then be accessible through `/action/{path}`. [REQUIRED].\n
170
+ ref: Value bound to the origin/destination of the messages. Check full documentation for more info.\n
171
+ role: Determine which role has access to this route.\n
172
+ method: Define the HTTP method to which the API binds the action.\n
173
+ check_ack: If specified, the SerialBridge will queue every other outgoing commands until the condition is fullfilled. Takes the read line as param.\n
174
+ group: Specify the UI group to which the action belongs.\n
175
+ sensible: Specify whether the action needs confirmation before executing.\n
176
+ display_text: Name to display on the UI. If None, capitalize the *label* and replace every "_" with whitespaces.\n
177
+ tooltip: Specify the on_hover tooltip.\n
178
+ ### Decorated params
179
+ e: Snapshot of the targetted EntityInfo from the Core at request time.\n
180
+ msg: Message to send.\n
181
+ ### Decorated return
182
+ Your Serial command (as a *string*) for the specific *action*.
183
+ ### Example
184
+ ```
185
+ @app.remote.chatbox(
186
+ path = "send_message",
187
+ check_ack = lambda x: "ACK,OK" in x or "ACK,NG" in x,
188
+ tooltip = "Send a message to a remote entity"
189
+ )
190
+ def send_message(e: EntityInfo, msg: str):
191
+ return f'*SET,IDAS,TXMSG,IND,{e.serial},{driver.local_id},MSG,"{msg.replace("\"", "\'")}"'
192
+ ```
193
+ """
194
+ def chatbox(func: Callable[[EntityInfo, str], str]):
195
+ def nfunc(e: EntityInfo, msg: str):
196
+ if self.verbose: log.info(f"Executed {func(e, msg)}")
197
+ return func(e, msg)
198
+ self.elements[path] = Element(
199
+ handler = nfunc,
200
+ check_ack = check_ack,
201
+ metadata = ChatboxMetadata(
202
+ path=path,
203
+ element="chatbox",
204
+ blocking=check_ack != None,
205
+ scope="remote",
206
+ role=role,
207
+ method=method,
208
+ group=group,
209
+ sensible=sensible,
210
+ display_text=display_text if display_text else path.replace("_", " ").capitalize(),
211
+ tooltip=tooltip,
212
+ ref=ref
213
+ )
214
+ )
215
+ if self.verbose: log.info(f"{path} bound.")
216
+ return nfunc
217
+ return chatbox
218
+
219
+ def text(
220
+ self,
221
+ path: str,
222
+ check_ack: Callable[[str], bool] | None = None,
223
+ role: Literal["operator", "admin"] = "operator",
224
+ method: Literal["GET", "POST"] = "POST",
225
+ group: str | None = None,
226
+ sensible: bool = False,
227
+ display_text: str | None = None,
228
+ tooltip: str | None = None
229
+ ):
230
+ """
231
+ Creates a text input to send texts to a remote entity.\n
232
+ For managing messages with history, you'd want to use a Chatbox instead.\n
233
+ ### Params
234
+ path: Name of the endpoint. Will then be accessible through `/action/{path}`. [REQUIRED].\n
235
+ role: Determine which role has access to this route.\n
236
+ method: Define the HTTP method to which the API binds the action.\n
237
+ check_ack: If specified, the SerialBridge will queue every other outgoing commands until the condition is fullfilled. Takes the read line as param.\n
238
+ group: Specify the UI group to which the action belongs.\n
239
+ sensible: Specify whether the action needs confirmation before executing.\n
240
+ display_text: Name to display on the UI. If None, capitalize the *label* and replace every "_" with whitespaces.\n
241
+ tooltip: Specify the on_hover tooltip.\n
242
+ ### Decorated params
243
+ e: Snapshot of the targetted EntityInfo from the Core at request time.\n
244
+ msg: Message to send.\n
245
+ ### Decorated return
246
+ Your Serial command (as a *string*) for the specific *action*.
247
+ ### Example
248
+ ```
249
+ @app.remote.text(
250
+ path = "set_friendly_name",
251
+ check_ack = lambda x: "ACK,OK" in x or "ACK,NG" in x,
252
+ tooltip = "Set a remote friendly name."
253
+ )
254
+ def set_friendly_name(e: EntityInfo, msg: str):
255
+ return f'*SET,IDAS,TXFNAME,IND,{e.serial},{driver.local_id},MSG,"{msg.replace("\"", "\'")}"'
256
+ ```
257
+ """
258
+ def text(func: Callable[[EntityInfo, str], str]):
259
+ def nfunc(e: EntityInfo, msg: str):
260
+ if self.verbose: log.info(f"Executed {func(e, msg)}")
261
+ return func(e, msg)
262
+ self.elements[path] = Element(
263
+ handler = nfunc,
264
+ check_ack = check_ack,
265
+ metadata = GeneralMetadata(
266
+ path=path,
267
+ element="text",
268
+ blocking=check_ack != None,
269
+ scope="remote",
270
+ role=role,
271
+ method=method,
272
+ group=group,
273
+ sensible=sensible,
274
+ display_text=display_text if display_text else path.replace("_", " ").capitalize(),
275
+ tooltip=tooltip,
276
+ )
277
+ )
278
+ if self.verbose: log.info(f"{path} bound.")
279
+ return nfunc
280
+ return text
281
+
282
+ def number(
283
+ self,
284
+ path: str,
285
+ ref: str,
286
+ vartype: Literal["int", "float"],
287
+ min_max: tuple[int | float, int | float] | None = None,
288
+ check_ack: Callable[[str], bool] | None = None,
289
+ role: Literal["operator", "admin"] = "operator",
290
+ method: Literal["GET", "POST"] = "POST",
291
+ group: str | None = None,
292
+ sensible: bool = False,
293
+ display_text: str | None = None,
294
+ tooltip: str | None = None
295
+ ):
296
+ """
297
+ Creates a number input to send numeric values.\n
298
+ Can be either int or float.\n
299
+ ### Params
300
+ path: Name of the endpoint. Will then be accessible through `/action/{path}`. [REQUIRED].\n
301
+ ref: Value bound to the number input. Check full documentation for more info. [REQUIRED].\n
302
+ vartype: Specify whether it's an int or a float. [REQUIRED].\n
303
+ min_max: Minimum and maximum values (respectively) for the number input.\n
304
+ role: Determine which role has access to this route.\n
305
+ method: Define the HTTP method to which the API binds the action.\n
306
+ check_ack: If specified, the SerialBridge will queue every other outgoing commands until the condition is fullfilled. Takes the read line as param.\n
307
+ group: Specify the UI group to which the action belongs.\n
308
+ sensible: Specify whether the action needs confirmation before executing.\n
309
+ display_text: Name to display on the UI. If None, capitalize the *label* and replace every "_" with whitespaces.\n
310
+ tooltip: Specify the on_hover tooltip.\n
311
+ ### Decorated params
312
+ e: Snapshot of the targetted EntityInfo from the Core at request time.\n
313
+ value: Value sent.\n
314
+ ### Decorated return
315
+ Your Serial command (as a *string*) for the specific *action*.
316
+ ### Example
317
+ ```
318
+ @app.remote.number(
319
+ path = "set_channel",
320
+ ref = "channel",
321
+ vartype = "int",
322
+ min_max = (1, 16),
323
+ check_ack = lambda x: "ACK,OK" in x or "ACK,NG" in x,
324
+ tooltip = "Set a remote channel."
325
+ )
326
+ def set_channel(e: EntityInfo, value: int | float):
327
+ return f'*SET,IDAS,TXCH,IND,{e.serial},{driver.local_id},CH,"{value}"'
328
+ ```
329
+ """
330
+ def number(func: Callable[[EntityInfo, int | float], str]):
331
+ def nfunc(e: EntityInfo, value: int | float):
332
+ if self.verbose: log.info(f"Executed {func(e, value)}")
333
+ return func(e, value)
334
+ self.elements[path] = Element(
335
+ handler = nfunc,
336
+ check_ack = check_ack,
337
+ metadata = NumberMetadata(
338
+ path=path,
339
+ element="number",
340
+ blocking=check_ack != None,
341
+ scope="remote",
342
+ role=role,
343
+ method=method,
344
+ group=group,
345
+ sensible=sensible,
346
+ display_text=display_text if display_text else path.replace("_", " ").capitalize(),
347
+ tooltip=tooltip,
348
+ ref=ref,
349
+ vartype=vartype,
350
+ min_max=min_max
351
+ )
352
+ )
353
+ if self.verbose: log.info(f"{path} bound.")
354
+ return nfunc
355
+ return number
356
+
357
+ def dropdown(
358
+ self,
359
+ path: str,
360
+ ref: str,
361
+ values: list[str],
362
+ check_ack: Callable[[str], bool] | None = None,
363
+ role: Literal["operator", "admin"] = "operator",
364
+ method: Literal["GET", "POST"] = "POST",
365
+ group: str | None = None,
366
+ sensible: bool = False,
367
+ display_text: str | None = None,
368
+ tooltip: str | None = None
369
+ ):
370
+ """
371
+ Creates a dropdown that restrict the user to the specified options.\n
372
+ ### Params
373
+ path: Name of the endpoint. Will then be accessible through `/action/{path}`. [REQUIRED].\n
374
+ ref: Value bound to the dropdown. Check full documentation for more info. [REQUIRED].\n
375
+ values: List of values (str) the dropdown has. [REQUIRED].\n
376
+ role: Determine which role has access to this route.\n
377
+ method: Define the HTTP method to which the API binds the action.\n
378
+ check_ack: If specified, the SerialBridge will queue every other outgoing commands until the condition is fullfilled. Takes the read line as param.\n
379
+ group: Specify the UI group to which the action belongs.\n
380
+ sensible: Specify whether the action needs confirmation before executing.\n
381
+ display_text: Name to display on the UI. If None, capitalize the *label* and replace every "_" with whitespaces.\n
382
+ tooltip: Specify the on_hover tooltip.\n
383
+ ### Decorated params
384
+ e: Snapshot of the targetted EntityInfo from the Core at request time.\n
385
+ value: Value sent.\n
386
+ ### Decorated return
387
+ Your Serial command (as a *string*) for the specific *action*.
388
+ ### Example
389
+ ```
390
+ @app.remote.dropdown(
391
+ path = "set_channel",
392
+ ref = "channel",
393
+ values = ["2", "8", "24"]
394
+ check_ack = lambda x: "ACK,OK" in x or "ACK,NG" in x,
395
+ tooltip = "Set a remote channel."
396
+ )
397
+ def set_channel(e: EntityInfo, value: str):
398
+ return f'*SET,IDAS,TXCH,IND,{e.serial},{driver.local_id},CH,"{value}"'
399
+ ```
400
+ """
401
+ def dropdown(func: Callable[[EntityInfo, str], str]):
402
+ def nfunc(e: EntityInfo, value: str):
403
+ if self.verbose: log.info(f"Executed {func(e, value)}")
404
+ return func(e, value)
405
+ self.elements[path] = Element(
406
+ handler = nfunc,
407
+ check_ack = check_ack,
408
+ metadata = DropdownMetadata(
409
+ path=path,
410
+ element="dropdown",
411
+ blocking=check_ack != None,
412
+ scope="remote",
413
+ role=role,
414
+ method=method,
415
+ group=group,
416
+ sensible=sensible,
417
+ display_text=display_text if display_text else path.replace("_", " ").capitalize(),
418
+ tooltip=tooltip,
419
+ ref=ref,
420
+ values=values
421
+ )
422
+ )
423
+ if self.verbose: log.info(f"{path} bound.")
424
+ return nfunc
425
+ return dropdown
426
+
427
+ class LocalElements():
428
+ def __init__(self, verbose: bool = False):
429
+ self.verbose = verbose
430
+ self.elements: dict[str, Element] = {}
431
+
432
+ def list_elements(self):
433
+ elements: list[GeneralMetadata | type[GeneralMetadata]]= []
434
+ for e in self.elements.values():
435
+ elements.append(e.metadata)
436
+ return elements
437
+
438
+ # Decorators definitions
439
+ def button(
440
+ self,
441
+ path: str,
442
+ check_ack: Callable[[str], bool] | None = None,
443
+ role: Literal["operator", "admin"] = "operator",
444
+ method: Literal["GET", "POST"] = "POST",
445
+ group: str | None = None,
446
+ sensible: bool = False,
447
+ display_text: str | None = None,
448
+ tooltip: str | None = None
449
+ ):
450
+ """
451
+ Creates a button to execute serial commands.\n
452
+ ### Params
453
+ path: Name of the endpoint. Will then be accessible through `/action/{path}`. [REQUIRED].\n
454
+ role: Determine which role has access to this route.\n
455
+ method: Define the HTTP method to which the API binds the action.\n
456
+ check_ack: If specified, the SerialBridge will queue every other outgoing commands until the condition is fullfilled. Takes the read line as param.\n
457
+ group: Specify the UI group to which the action belongs.\n
458
+ sensible: Specify whether the action needs confirmation before executing.\n
459
+ display_text: Name to display on the UI. If None, capitalize the *label* and replace every "_" with whitespaces.\n
460
+ tooltip: Specify the on_hover tooltip.\n
461
+ ### Decorated params
462
+ None
463
+ ### Decorated return
464
+ Your Serial command (as a *string*) for the specific *action*.
465
+ ### Example
466
+ ```
467
+ @app.local.button(
468
+ path = "get_channel",
469
+ group = "Basic actions",
470
+ tooltip = "Ask for the channel."
471
+ )
472
+ def get_id():
473
+ return '*GET,MCH,SEL'
474
+ ```
475
+ """
476
+ def button(func: Callable[[], str]):
477
+ def nfunc():
478
+ if self.verbose: log.info(f"Executed {func()}")
479
+ return func()
480
+ self.elements[path] = Element(
481
+ handler = nfunc,
482
+ check_ack = check_ack,
483
+ metadata = GeneralMetadata(
484
+ path=path,
485
+ element="button",
486
+ blocking=check_ack != None,
487
+ scope="local",
488
+ role=role,
489
+ method=method,
490
+ group=group,
491
+ sensible=sensible,
492
+ display_text=display_text if display_text else path.replace("_", " ").capitalize(),
493
+ tooltip=tooltip
494
+ )
495
+ )
496
+ if self.verbose: log.info(f"{path} bound.")
497
+ return nfunc
498
+ return button
499
+
500
+ def toggle(
501
+ self,
502
+ path: str,
503
+ ref: str,
504
+ check_ack: Callable[[str], bool] | None = None,
505
+ role: Literal["operator", "admin"] = "operator",
506
+ method: Literal["GET", "POST"] = "POST",
507
+ group: str | None = None,
508
+ sensible: bool = False,
509
+ display_text: str | None = None,
510
+ tooltip: str | None = None,
511
+ colors: tuple[str, str] | None = None
512
+ ):
513
+ """
514
+ Creates a toggle button to execute serial commands.\n
515
+ ### Params
516
+ path: Name of the endpoint. Will then be accessible through `/action/{path}`. [REQUIRED].\n
517
+ ref: Value bound to the toggle's state. Check full documentation for more info. [REQUIRED]\n
518
+ role: Determine which role has access to this route.\n
519
+ method: Define the HTTP method to which the API binds the action.\n
520
+ check_ack: If specified, the SerialBridge will queue every other outgoing commands until the condition is fullfilled. Takes the read line as param.\n
521
+ group: Specify the UI group to which the action belongs.\n
522
+ sensible: Specify whether the action needs confirmation before executing.\n
523
+ display_text: Name to display on the UI. If None, capitalize the *label* and replace every "_" with whitespaces.\n
524
+ tooltip: Specify the on_hover tooltip.\n
525
+ colors: HEX values for the button's off/on colors respectively. If None, the Core will default to red/green.\n
526
+ ### Decorated params
527
+ state: State of the *ref* from the Core at request time.\n
528
+ ### Decorated return
529
+ Your Serial command (as a *string*) for the specific *action*.
530
+ ### Example
531
+ ```
532
+ @app.local.toggle(
533
+ path = "ptt",
534
+ ref = "is_ptt_on",
535
+ group = "Basic actions",
536
+ tooltip = "Toggle the PTT."
537
+ )
538
+ def toggle_ptt(state: bool):
539
+ if state:
540
+ return f'*SET,PTT,OFF'
541
+ else:
542
+ return f'*SET,PTT,ON'
543
+ ```
544
+ """
545
+ def toggle(func: Callable[[bool], str]):
546
+ def nfunc(state: bool):
547
+ if self.verbose: log.info(f"Executed {func(state)}")
548
+ return func(state)
549
+ self.elements[path] = Element(
550
+ handler = nfunc,
551
+ check_ack = check_ack,
552
+ metadata = ToggleMetadata(
553
+ path=path,
554
+ element="toggle",
555
+ blocking=check_ack != None,
556
+ scope="local",
557
+ role=role,
558
+ method=method,
559
+ group=group,
560
+ sensible=sensible,
561
+ display_text=display_text if display_text else path.replace("_", " ").capitalize(),
562
+ tooltip=tooltip,
563
+ ref=ref,
564
+ colors=colors
565
+ )
566
+ )
567
+ if self.verbose: log.info(f"{path} bound.")
568
+ return nfunc
569
+ return toggle
570
+
571
+ def chatbox(
572
+ self,
573
+ path: str,
574
+ ref: str = "general",
575
+ check_ack: Callable[[str], bool] | None = None,
576
+ role: Literal["operator", "admin"] = "operator",
577
+ method: Literal["GET", "POST"] = "POST",
578
+ group: str | None = None,
579
+ sensible: bool = False,
580
+ display_text: str | None = None,
581
+ tooltip: str | None = None
582
+ ):
583
+ """
584
+ Creates a chatbox (input and history) to handle text messages. You can specify a *ref* to filter where the messages come from/go to if your device supports multiple "channel-like" messaging.\n
585
+ ### Params
586
+ path: Name of the endpoint. Will then be accessible through `/action/{path}`. [REQUIRED].\n
587
+ ref: Value bound to the origin/destination of the messages. Check full documentation for more info.\n
588
+ role: Determine which role has access to this route.\n
589
+ method: Define the HTTP method to which the API binds the action.\n
590
+ check_ack: If specified, the SerialBridge will queue every other outgoing commands until the condition is fullfilled. Takes the read line as param.\n
591
+ group: Specify the UI group to which the action belongs.\n
592
+ sensible: Specify whether the action needs confirmation before executing.\n
593
+ display_text: Name to display on the UI. If None, capitalize the *label* and replace every "_" with whitespaces.\n
594
+ tooltip: Specify the on_hover tooltip.\n
595
+ ### Decorated params
596
+ msg: Message to send.\n
597
+ ### Decorated return
598
+ Your Serial command (as a *string*) for the specific *action*.
599
+ ### Example
600
+ ```
601
+ @app.local.chatbox(
602
+ path = "send_command",
603
+ check_ack = lambda x: "ACK,OK" in x or "ACK,NG" in x,
604
+ tooltip = "Send a command to the local radio."
605
+ )
606
+ def send_command(msg: str):
607
+ return msg
608
+ ```
609
+ """
610
+ def chatbox(func: Callable[[str], str]):
611
+ def nfunc(msg: str):
612
+ if self.verbose: log.info(f"Executed {func(msg)}")
613
+ return func(msg)
614
+ self.elements[path] = Element(
615
+ handler = nfunc,
616
+ check_ack = check_ack,
617
+ metadata = ChatboxMetadata(
618
+ path=path,
619
+ element="chatbox",
620
+ blocking=check_ack != None,
621
+ scope="local",
622
+ role=role,
623
+ method=method,
624
+ group=group,
625
+ sensible=sensible,
626
+ display_text=display_text if display_text else path.replace("_", " ").capitalize(),
627
+ tooltip=tooltip,
628
+ ref=ref
629
+ )
630
+ )
631
+ if self.verbose: log.info(f"{path} bound.")
632
+ return nfunc
633
+ return chatbox
634
+
635
+ def text(
636
+ self,
637
+ path: str,
638
+ check_ack: Callable[[str], bool] | None = None,
639
+ role: Literal["operator", "admin"] = "operator",
640
+ method: Literal["GET", "POST"] = "POST",
641
+ group: str | None = None,
642
+ sensible: bool = False,
643
+ display_text: str | None = None,
644
+ tooltip: str | None = None
645
+ ):
646
+ """
647
+ Creates a text input to send texts to a remote entity.\n
648
+ For managing messages with history, you'd want to use a Chatbox instead.\n
649
+ ### Params
650
+ path: Name of the endpoint. Will then be accessible through `/action/{path}`. [REQUIRED].\n
651
+ role: Determine which role has access to this route.\n
652
+ method: Define the HTTP method to which the API binds the action.\n
653
+ check_ack: If specified, the SerialBridge will queue every other outgoing commands until the condition is fullfilled. Takes the read line as param.\n
654
+ group: Specify the UI group to which the action belongs.\n
655
+ sensible: Specify whether the action needs confirmation before executing.\n
656
+ display_text: Name to display on the UI. If None, capitalize the *label* and replace every "_" with whitespaces.\n
657
+ tooltip: Specify the on_hover tooltip.\n
658
+ ### Decorated params
659
+ msg: Message to send.\n
660
+ ### Decorated return
661
+ Your Serial command (as a *string*) for the specific *action*.
662
+ ### Example
663
+ ```
664
+ @app.local.text(
665
+ path = "set_ui_text",
666
+ tooltip = "Set the radio UI text."
667
+ )
668
+ def set_ui_text(msg: str):
669
+ return f'*SET,UI,TEXT,{msg}'
670
+ ```
671
+ """
672
+ def text(func: Callable[[str], str]):
673
+ def nfunc(msg: str):
674
+ if self.verbose: log.info(f"Executed {func(msg)}")
675
+ return func(msg)
676
+ self.elements[path] = Element(
677
+ handler = nfunc,
678
+ check_ack = check_ack,
679
+ metadata = GeneralMetadata(
680
+ path=path,
681
+ element="text",
682
+ blocking=check_ack != None,
683
+ scope="local",
684
+ role=role,
685
+ method=method,
686
+ group=group,
687
+ sensible=sensible,
688
+ display_text=display_text if display_text else path.replace("_", " ").capitalize(),
689
+ tooltip=tooltip,
690
+ )
691
+ )
692
+ if self.verbose: log.info(f"{path} bound.")
693
+ return nfunc
694
+ return text
695
+
696
+ def number(
697
+ self,
698
+ path: str,
699
+ ref: str,
700
+ vartype: Literal["int", "float"],
701
+ min_max: tuple[int | float, int | float] | None = None,
702
+ check_ack: Callable[[str], bool] | None = None,
703
+ role: Literal["operator", "admin"] = "operator",
704
+ method: Literal["GET", "POST"] = "POST",
705
+ group: str | None = None,
706
+ sensible: bool = False,
707
+ display_text: str | None = None,
708
+ tooltip: str | None = None
709
+ ):
710
+ """
711
+ Creates a number input to send numeric values.\n
712
+ Can be either int or float.\n
713
+ ### Params
714
+ path: Name of the endpoint. Will then be accessible through `/action/{path}`. [REQUIRED].\n
715
+ ref: Value bound to the number input. Check full documentation for more info. [REQUIRED].\n
716
+ vartype: Specify whether it's an int or a float. [REQUIRED].\n
717
+ min_max: Minimum and maximum values (respectively) for the number input.\n
718
+ role: Determine which role has access to this route.\n
719
+ method: Define the HTTP method to which the API binds the action.\n
720
+ check_ack: If specified, the SerialBridge will queue every other outgoing commands until the condition is fullfilled. Takes the read line as param.\n
721
+ group: Specify the UI group to which the action belongs.\n
722
+ sensible: Specify whether the action needs confirmation before executing.\n
723
+ display_text: Name to display on the UI. If None, capitalize the *label* and replace every "_" with whitespaces.\n
724
+ tooltip: Specify the on_hover tooltip.\n
725
+ ### Decorated params
726
+ value: Value sent.\n
727
+ ### Decorated return
728
+ Your Serial command (as a *string*) for the specific *action*.
729
+ ### Example
730
+ ```
731
+ @app.local.number(
732
+ path = "set_channel",
733
+ ref = "channel",
734
+ vartype = "int",
735
+ min_max = (1, 16),
736
+ tooltip = "Set the local channel."
737
+ )
738
+ def set_channel(value: int | float):
739
+ return f'*SET,MCH,SEL,{value}'
740
+ ```
741
+ """
742
+ def number(func: Callable[[int | float], str]):
743
+ def nfunc(value: int | float):
744
+ if self.verbose: log.info(f"Executed {func(value)}")
745
+ return func(value)
746
+ self.elements[path] = Element(
747
+ handler = nfunc,
748
+ check_ack = check_ack,
749
+ metadata = NumberMetadata(
750
+ path=path,
751
+ element="number",
752
+ blocking=check_ack != None,
753
+ scope="local",
754
+ role=role,
755
+ method=method,
756
+ group=group,
757
+ sensible=sensible,
758
+ display_text=display_text if display_text else path.replace("_", " ").capitalize(),
759
+ tooltip=tooltip,
760
+ ref=ref,
761
+ vartype=vartype,
762
+ min_max=min_max
763
+ )
764
+ )
765
+ if self.verbose: log.info(f"{path} bound.")
766
+ return nfunc
767
+ return number
768
+
769
+ def dropdown(
770
+ self,
771
+ path: str,
772
+ ref: str,
773
+ values: list[str],
774
+ check_ack: Callable[[str], bool] | None = None,
775
+ role: Literal["operator", "admin"] = "operator",
776
+ method: Literal["GET", "POST"] = "POST",
777
+ group: str | None = None,
778
+ sensible: bool = False,
779
+ display_text: str | None = None,
780
+ tooltip: str | None = None
781
+ ):
782
+ """
783
+ Creates a dropdown that restrict the user to specific options.\n
784
+ ### Params
785
+ path: Name of the endpoint. Will then be accessible through `/action/{path}`. [REQUIRED].\n
786
+ ref: Value bound to the dropdown. Check full documentation for more info. [REQUIRED].\n
787
+ values: List of values (str) the dropdown has. [REQUIRED].\n
788
+ role: Determine which role has access to this route.\n
789
+ method: Define the HTTP method to which the API binds the action.\n
790
+ check_ack: If specified, the SerialBridge will queue every other outgoing commands until the condition is fullfilled. Takes the read line as param.\n
791
+ group: Specify the UI group to which the action belongs.\n
792
+ sensible: Specify whether the action needs confirmation before executing.\n
793
+ display_text: Name to display on the UI. If None, capitalize the *label* and replace every "_" with whitespaces.\n
794
+ tooltip: Specify the on_hover tooltip.\n
795
+ ### Decorated params
796
+ value: Value sent.\n
797
+ ### Decorated return
798
+ Your Serial command (as a *string*) for the specific *action*.
799
+ ### Example
800
+ ```
801
+ @app.local.dropdown(
802
+ path = "set_channel",
803
+ ref = "channel",
804
+ values = ["2", "8", "24"],
805
+ tooltip = "Set the local channel."
806
+ )
807
+ def set_channel(value: str):
808
+ return f'*SET,MCH,SEL,{value}'
809
+ ```
810
+ """
811
+ def dropdown(func: Callable[[str], str]):
812
+ def nfunc(value: str):
813
+ if self.verbose: log.info(f"Executed {func(value)}")
814
+ return func(value)
815
+ self.elements[path] = Element(
816
+ handler = nfunc,
817
+ check_ack = check_ack,
818
+ metadata = DropdownMetadata(
819
+ path=path,
820
+ element="dropdown",
821
+ blocking=check_ack != None,
822
+ scope="local",
823
+ role=role,
824
+ method=method,
825
+ group=group,
826
+ sensible=sensible,
827
+ display_text=display_text if display_text else path.replace("_", " ").capitalize(),
828
+ tooltip=tooltip,
829
+ ref=ref,
830
+ values=values
831
+ )
832
+ )
833
+ if self.verbose: log.info(f"{path} bound.")
834
+ return nfunc
835
+ return dropdown