component.py
21.4 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
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
import datetime
from threading import Thread, Event
from queue import Queue
import traceback
import time
import sys
import ast
try:
from .guitastrotools import GuitastroException
except:
from guitastrotools import GuitastroException
# #####################################################################
# #####################################################################
# #####################################################################
# Class Component
# #####################################################################
# #####################################################################
# #####################################################################
class ComponentDataBaseException(GuitastroException):
ERR_ARG0_SELF_MANDATORY = 0
ERR_ACTION_NOT_FOUND = 1
ERR_OPERATION_NOT_FOUND = 2
ERR_NOT_ENOUGH_PARAMETERS = 3
errors = [""]*4
errors[ERR_ARG0_SELF_MANDATORY] = "Parameter *arg must be passed at less with arg[0]=self during instanciation"
class ComponentDataBase(Thread, ComponentDataBaseException):
"""Thread to manage a simple database between threads
This simple database is useful to exchange data between thread started by a device manager.
Technically, the communication is done using a Queue.
The database is instanciated from the main thread (self):
::
from threading import Thread, Event
from queue import Queue
self.queue = Queue()
db = ComponentDataBase(self)
db.start()
The **self** passed to the database object allows to access to the Queue.
Do not forget to kill the thread when the use of the database is finished:
::
db.stop()
or
::
del db
The database is contituted by only one table.
The table is a dictionary.
Each entry of the table is an item of the dictionary.
When the communication is done with another thread that using self,
use the put method of the Queue.
For example to create or update a new entry:
::
# -- code of another thread which will be started by the main thread
class ComponentA(Thread):
def __init__(self, upperself):
Thread.__init__(self)
self._queue = upperself._queue
self._stopevent = Event()
def run(self):
try:
while True:
key = "my_param"
val = 10
self._queue.put(f"{key} = {val}")
time.sleep(1)
except:
traceback.print_exc(file=sys.stdout)
def stop(self):
self._stopevent.set()
def __del__(self):
self.stop()
# === Now consider the main thread ===
# -- code that starts the database in the main thread
self.queue = Queue()
db = ComponentDataBase(self)
db.start()
# -- code that starts the CompnentA in the main thread
compa = ComponentA(self)
compa.start()
# -- we kill all the threads after 10 seconds
time.sleep(10)
del compa
del db
This code fill the database every second with the item
"my_param" = 10.
Queries can be only done from the thread to the database.
When the communication is done with the parent thread
it is not need to use the Queue. In this condition,
use the query method. For example to create or update a new entry:
::
# === We are in the main thread ===
# -- code that starts the database in the main thread
self.queue = Queue()
db = ComponentDataBase(self)
db.start()
# -- code fill the database directly from the main thread
key = "my_param"
val = 10
db.query(key, val)
"""
def __init__(self, *args, **kwargs):
Thread.__init__(self)
na = len(args)
if na == 0:
raise ComponentDataBaseException(ComponentDataBaseException.ERR_ARG0_SELF_MANDATORY)
self._upself = args[0]
self._queue = self._upself._queue
self._stopevent = Event()
self._param = {}
def query(self, *args):
"""Method to access i/o to the database directly
::
db = ComponentDataBase()
To fill the dabase table table with with
db.param("mykey", 10)
"""
#print(f"QUERY received args={args}")
na = len(args)
# --- case no parameter. We return the dictionary
if na == 0:
return self._param
args0 = args[0]
# ---
if isinstance(args0, dict):
# --- case args[0] is a dictionary. We update the dictionary
keyval = args0
for key, val in keyval.items():
self._param[key] = val
elif na==1 and isinstance(args0, str):
# --- case args[0] is a key. We return only the val of the key
key = args0
elif na==2 and isinstance(args0, str):
# --- case args[0] is a key followed by a value. We update the val of the key
key = args0
val = self._upself._literal_eval(args[1])
self._param[key] = val
# --- We return only the val of the key
return self._param.get(key) # None if key is not in dict
def run(self):
"""Method to access i/o to the database with a queue
"""
while True:
try:
contents = self._queue.get()
#print(f"QUEUE received {contents}")
if isinstance(contents, dict) == True:
for key, val in contents.items():
self._param[key] = val
else:
k = contents.find("=")
if k>0:
kvs = contents.split("=")
if len(kvs)>1:
key = kvs[0].strip()
val = kvs[1].strip()
self._param[key] = self._upself._literal_eval(val)
self._queue.task_done()
time.sleep(0.1)
except:
traceback.print_exc(file=sys.stdout)
# ---
# The thread termined properly
def stop(self):
# --- stop any motion
self._stopevent.set()
def __del__(self):
self.stop()
class ComponentException(GuitastroException):
ERR_CHANNEL_NONE = 0
ERR_ACTION_NOT_FOUND = 1
ERR_OPERATION_NOT_FOUND = 2
ERR_NOT_ENOUGH_PARAMETERS = 3
errors = [""]*4
errors[ERR_CHANNEL_NONE] = "Communication channel is not defined. Use the method channel"
errors[ERR_ACTION_NOT_FOUND] = "Action not found"
errors[ERR_OPERATION_NOT_FOUND] = "Operation not found"
errors[ERR_NOT_ENOUGH_PARAMETERS] = "Not enough parameters"
class Component(ComponentException):
"""Component abstract class
Classes of the familly Component* provide uniform commands to
drive devices in GUITAstro.
The class Component provides common methods for all devices
but should not be used alone. It is an abstract class.
To drive devices as focusers, use the class of a component type
ComponentDetectorFocuser. The class ComponentDetectorFocuser
will inheritate the class Component.
The class ComponentDetectorFocuser implements methods
that can simulate a focuser. The simulation can be parametrized.
This class call some abstract methods for real devices
that will be overloaded when ComponentDetectorFocuser is inherited.
Now consider a real focuser as provided by DeltaTau manufacturer.
The class ComponentDetectorFocuserDeltatau implements the
concrete methods of native DeltaTau commands.
:Example:
::
>>> comp = Component()
A command of a component is constituted by:
* Action: A string word as GET, SET, DO
* Operation: A string word that dependis on the action
* Parameters: List or/and dict depending the action/operation
A command is sent to the component using the method command:
:Example:
::
>>> comp.command("DO", "RADEC_COORD")
As Component is an abstract class, Actions can be modified
by the class which inheritate Component.
**Parameters of SET**:
* variable : A string that designate a key in the internal dict of the Component.
* value : The value of the variable.
:Example:
::
>>> res = comp.command("SET", "target", 5000)
**Parameters of GET**:
* variable : A string that designate a key in the internal dict of the Component.
:Example:
::
>>> res = comp.command("GET","target")
**Parameters of DO**:
* operation : A string that will call a specific code for this operation.
* parameters : List of Dict of parameters needed for the operation.
Note that the parameters of an operation should be assigned by the Action SET
before calling DO.
:Example:
::
>>> comp.command("SET", "target", "RADEC 12h34m -05d23m")
>>> comp.command("DO", "RADEC_GOTO")
"""
_param = {}
_chan = None
_result = {}
_result['value'] = None
_result['errcode'] = -1
_result['errmsg'] = ""
_result['log'] = ""
_real = False
_verbose = 0
_name = "No name"
categories = ['MountPointing', 'DetectorFocuser']
# =====================================================================
# init
# =====================================================================
def __init__(self, *args, **kwargs):
# ---
self._queue = Queue()
self.database = ComponentDataBase(self)
self.database.start()
time.sleep(0.2)
# ---
self.init(*args, **kwargs)
def __repr__(self):
name = self.name
msg = f"==== Component: {name}\n\n"
msg += f" * Category: {self.category}\n"
msg += f"\n--- Component {name} communication:\n"
msg += f" * Real = {self.real}\n"
msg += f" * Channel = {self.channel}\n"
p = self.prop()
a = p['actions']
msg += f"\n--- Component {name} actions: {a}\n"
for keya in a:
if keya in p.keys():
print(f" * {keya=}")
o = p[keya]
for keyo, val in o.items():
if isinstance(val, dict):
so = val
for keyso, val in so.items():
msg += f" * Operation {keya} {keyo} {keyso} = {val}\n"
else:
msg += f" * Operation {keya} {keyo} = {val}\n"
msg += f"\n--- Component {name} log:\n"
msg += f" * Verbose = {self.verbose}\n"
msg += f" * Log = {self.log}\n"
msg += f"\n--- Component {name} database:\n"
db = self.database.query()
for key, val in db.items():
msg += f" * {key} = {val}\n"
return msg
def init(self, *args, **kwargs):
self._my_init1(*args, **kwargs)
self._my_init2(*args, **kwargs)
def _my_init1(self, *args, **kwargs):
# before instanciation of the simulator
pass
def _my_init2(self, *args, **kwargs):
# after instanciation of the simulator to configure it.
pass
# =====================================================================
# del
# =====================================================================
def __del__(self):
self.database.stop()
# =====================================================================
# properties: real, channel, category, action, verbose, log
# =====================================================================
def _get_name(self)->str:
"""Get the name of the component
"""
return self._name
def _set_name(self, name:str):
"""Set the name of the component
"""
self._name = name
name = property(_get_name, _set_name)
def _get_real(self)->bool:
"""Get is the component is real or not
"""
return self._real
def _set_real(self, truefalse: bool):
"""Set is the component is real or not
"""
self._real = truefalse
real = property(_get_real, _set_real)
def _get_channel(self):
"""Get the channel of communication
"""
return self._chan
def _set_channel(self, channel):
"""Set the channel of communication
The channel must be opened by the class Communication,
in an object Device, before the call of this method.
"""
self._chan = channel
channel = property(_get_channel, _set_channel)
def _get_category(self):
"""Return the catagory of the Component
"""
return self.prop()['category']
def _set_category(self, value):
pass
category = property(_get_category, _set_category)
def _get_actions(self):
"""Return the list of actions of the Component
"""
return list(self.prop()['actions'])
def _set_actions(self, value):
pass
actions = property(_get_actions, _set_actions)
def _get_verbose(self):
"""Get the verbose level for the returns of the Component
"""
if self._verbose == 0:
msg = "value"
if self._verbose == 1:
msg = "value, log"
return self._verbose, msg
def _set_verbose(self, value):
"""Set the verbose level for the returns of the Component
"""
try:
value = int(value)
except:
try:
if len(value) > 0:
value = int(value[0])
except:
value = 0
if 0 <= value <= 1:
self._verbose = value
verbose = property(_get_verbose, _set_verbose)
def _get_log(self):
"""Get the log for the returns of the Component
"""
return self._result['log']
def _set_log(self, msg):
"""Append msg to the log for the returns of the Component
"""
verb = self.verbose[0]
if verb == 1:
date_iso = datetime.datetime.utcnow().isoformat("T","milliseconds")
self._result['log'] += f"{date_iso} : " + msg + "\n"
def _del_log(self):
"""delete the log contents for the returns of the Component
"""
self._result['log'] = ""
log = property(_get_log, _set_log, _del_log)
# =====================================================================
# prop
# =====================================================================
def prop(self):
"""Component property abstract method
Please concrete this method.
"""
prop = {}
prop['category'] = 'Component'
prop['actions'] = ["DO", "GET", "SET"]
prop['DO'] = {}
prop['DO']['NATIVE'] = "The message is sent to the device with no transformation"
# just a dummy example
prop['DO']['NOTHING'] = "Nothing to do"
prop = self._my_prop(prop)
return prop
def _my_prop(self, prop:dict) -> dict:
"""Abstract method to be overriden
"""
return prop
# =====================================================================
# parameters (get/set param)
# =====================================================================
def parameters(self, action="", operation=""):
"""Get the list of operations for a given action or get the description of a given operation.
Args:
action: If not given, returns the list of Actions
"""
actions = self.actions
if action=="":
return actions
if action not in actions:
msg = f"{action} not found amongst {actions}"
raise ComponentException(ComponentException.ERR_ACTION_NOT_FOUND, msg)
# ---
operations = list(self.prop()[action].keys())
if operation=="":
return operations
if operation not in operations:
msg = f"{operation} not found amongst {operations}"
raise ComponentException(ComponentException.ERR_OPERATION_NOT_FOUND, msg)
value = self.prop()[action][operation]
return value
# =====================================================================
# command (execute an action)
# =====================================================================
def command(self, action:str, *args, **kwargs):
"""General method to send command.
Args:
action: A str amongst elements of the list returned by the method prop
*args: args[0] should be an operation (type str)
**kwargs: Dictionary of options associated to the operation
Returns:
The result of the command.
Use the method parameters(action) to get the available operations for a given action.
The kwargs are defined for each operation.
"""
result = None
action = action.upper()
actions = self.actions
if action not in actions:
msg = f"{action} not found amongst {actions}"
raise ComponentException(ComponentException.ERR_ACTION_NOT_FOUND, msg)
# ---
if action == "DO":
result = self._do(*args, **kwargs)
elif action == "GET":
result = self._get(*args, **kwargs)
elif action == "SET":
result = self._set(*args, **kwargs)
return result
# =====================================================================
# protected methods
# =====================================================================
def _verify_chan(self):
"""Check if the channel is opened before sending native commands
"""
pass
#if self._chan == None:
# raise ComponentException(ComponentException.ERR_CHANNEL_NONE)
def _verify_nargs(self, na, *args):
"""Check if the minimum number of elements of *args is correct
"""
if len(args) < na:
msg = f"At less {na} parameters must be given"
raise ComponentException(ComponentException.ERR_NOT_ENOUGH_PARAMETERS, msg)
def _literal_eval(self, node_or_string:str)->any:
"""Transform a string into the best type as possible
"""
try:
val = ast.literal_eval(node_or_string)
except:
val = node_or_string
return val
# =====================================================================
# fresult to manage log
# =====================================================================
def _fresult(self, value):
"""Formated result for returns
"""
res = ""
verb = self.verbose[0]
if verb == 0:
res = value
if verb == 1:
res = (value, self.log)
del(self.log)
return res
# =====================================================================
# Action set
# =====================================================================
def _set(self, *args, **kwargs):
args, kwargs = self._my_set(*args, **kwargs)
self._verify_nargs(2, *args)
key = args[0]
na = len(args)
value = args[1]
if na>2:
value = str(args[1])
for v in args[2:]:
value += " " + str(v)
print(f"value={value}")
self.database.query(key, value)
def _my_set(self, *args, **kwargs):
return args, kwargs
# =====================================================================
# Action get
# =====================================================================
def _get(self, *args, **kwargs):
if len(args)==0:
return self.database.query()
key = args[0]
param = self.database.query(key)
my_param = self._my_get(*args, **kwargs)
if my_param != None:
param = my_param
return param
def _my_get(self, *args, **kwargs):
return None
# =====================================================================
# Action do
# =====================================================================
def _do(self, *args, **kwargs):
result = None
self._verify_nargs(1, *args)
operation = args[0].upper()
operations = list(self.prop()['DO'].keys())
if operation not in operations:
msg = f"{operation} not found amongst {operations}"
raise ComponentException(ComponentException.ERR_OPERATION_NOT_FOUND, msg)
# ---
if operation == "NATIVE":
pass
if operation == "NOTHING":
pass
result = self._my_do(*args, **kwargs)
return self._fresult(result)
def _my_do(self, *args, **kwargs):
return None
# #####################################################################
# #####################################################################
# #####################################################################
# Main
# #####################################################################
# #####################################################################
# #####################################################################
if __name__ == "__main__":
default = 0
example = input(f"Select the example (0 to 0) ({default}) ")
try:
example = int(example)
except:
example = default
print("Example = {}".format(example))
if example == 0:
"""
Basic example
"""
comp = Component()