Agent.py
10.2 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
VERSION = "0.3"
#from __future__ import absolute_import
import time
import datetime
import os
"""TODO:
- 1 log par agent + lastlog (30 dernières lignes)
- 1 table par agent
- table agents_log avec log minimum pour affichage dans dashboard, et ordre chrono intéressant pour suivi activité : nom agent, timestamp, message
- table agent_<agent-name>_vars : nom variable, value, desc
- table agents_commands : agent demandeur, agent dest, deposit_time (timestamp), durée validité (valeur par défaut), commande (format à définir), ack_time (timestamp)
(l'agent destinataire en profite pour supprimer les commandes périmées qui le concernent)
"""
# from django.core.exceptions import ObjectDoesNotExist
# from django.db.models import Q
from django.shortcuts import get_object_or_404
from django.conf import settings as djangosettings
import utils.Logger as L
from config.configpyros import ConfigPyros
"""
import observation_manager
import observation_manager.tasks
import scheduler
import scheduler.tasks as sched_task
import monitoring.tasks
import alert_manager.tasks
"""
# from common.models import *
from common.models import Config, Log, PlcDeviceStatus
from dashboard.views import get_sunelev
from devices.TelescopeRemoteControlDefault import TelescopeRemoteControlDefault
"""
from devices.CameraNIR import NIRCameraController
from devices.CameraVIS import VISCameraController
from devices.Dome import DomeController
from devices.PLC import PLCController
from devices.Telescope import TelescopeController
from majordome.MajordomeDecorators import *
from utils.JDManipulator import *
"""
from threading import Thread
DEBUG_FILE = False
log = L.setupLogger("MajordomeTaskLogger", "Majordome")
"""
Task to handle the execution of the program
check the environment status in database
check the devices status (telescope / cameras)
check if the last schedule made has to be planned
launch schedule's sequences
"""
class Agent:
# (EP) do this so that Majordome can be run from a thread, and called with thread.start():
# class Majordome(Task, Thread):
name = "Generic Agent"
FOR_REAL = True
mainloop_waittime = 3
subloop_waittime = 2
STATUS = None
MODE = None
config = None
# Statuses
STATUS_LAUNCH = "LAUNCHED"
STATUS_INIT = "INITIALIZING"
STATUS_MAIN_LOOP = "IN_MAIN_LOOP"
STATUS_PROCESS_LOOP = "IN_PROCESS_LOOP"
STATUS_EXIT = "EXITING"
# Modes
MODE_ACTIVE = "ACTIVE"
MODE_IDLE = "IDLE"
DEFAULT_CONFIG_FILE_NAME = "config_unit_simulunit1.xml"
CONFIG_DIR = "config"
def __init__(self, name:str=None, config_filename:str=None):
self.set_mode(self.MODE_IDLE)
self.set_status(self.STATUS_LAUNCH)
self.name = name
if not config_filename:
#config_filename = '/PROJECTS/GFT/SOFT/PYROS_SOFT/CURRENT/config/config_unit_simulunit1.xml'
config_filename = self.DEFAULT_CONFIG_FILE_NAME
config_abs_filename = os.path.abspath(self.CONFIG_DIR + os.sep + config_filename)
#print("current path", os.getcwd())
#print("this file path :", __file__)
#print("config file path is", config_filename)
# Instantiate an object for configuration
self.config = ConfigPyros(config_abs_filename)
if self.config.get_last_errno() != self.config.NO_ERROR:
raise Exception(f"Bad config file name '{config_abs_filename}', error code {str(self.config.get_last_errno())}")
def __str__(self):
return "I am agent " + self.name
def run(self, FOR_REAL: bool = True):
"""
FOR_REAL: set to False if you don't want Majordome to send commands to devices
"""
self.FOR_REAL = FOR_REAL
self.load_config()
#TODO: lire param start_mode (si ce param, alors changer le mode)
self.init()
'''
print()
print(self)
print("FOR REAL ?", self.FOR_REAL)
print("DB3 used is:", djangosettings.DATABASES["default"]["NAME"])
# SETUP
try:
self.config = get_object_or_404(Config, id=1)
# By default, set mode to SCHEDULER (False = REMOTE, which should never be the default)
self.config.global_mode = True
self.config.save()
# self.config = Config.objects.get(pk=1)
# self.config = Config.objects.get()[0]
except Exception as e:
# except Config.ObjectDoesNotExist:
print("Config read (or write) exception", str(e))
return -1
'''
# Main loop
while True:
self.set_status(self.STATUS_MAIN_LOOP)
"""
A chaque tour de boucle, remplir champ "iamalive" avec timestamp + durée validité (> temps iteration, n minutes par défaut)
+ nom agent dans table agents_survey (nom agent + mode + status + updated timestamp)
"""
print()
print("Starting main loop iteration...")
self.show_mode_and_status()
self.load_config()
self.update_db_survey()
self.read_db_commands()
'''
if self.config.majordome_state == "STOP":
break
if self.config.majordome_state == "RESTART":
self.config.majordome_restarted = True
self.closing_mode = self.config.majordome_state
self.config.majordome_state = "RUNNING"
self.config.save()
'''
# Sub-level loop (only if ACTIVE)
if self.is_active():
self.set_status(self.STATUS_PROCESS_LOOP)
self.core_process()
self.waitfor(self.mainloop_waittime)
print("Ending main loop iteration...")
#self.do_log(LOG_DEBUG, "Ending main loop iteration")
def waitfor(self, nbsec):
print(f"Now, waiting for {nbsec} seconds...")
time.sleep(nbsec)
def set_status(self, status:str):
print(f"Switching from status {self.STATUS} to status {status}")
self.STATUS = status
def set_mode(self, mode:str):
print(f"Switching from mode {self.MODE} to mode {mode}")
self.MODE = mode
def is_active(self):
return self.MODE == self.MODE_ACTIVE
def set_active(self):
self.set_mode(self.MODE_ACTIVE)
def set_idle(self):
self.MODE = self.MODE_IDLE
def show_mode_and_status(self):
print(f"CURRENT MODE is {self.MODE} (with status {self.STATUS})")
def die(self):
self.set_status(self.STATUS_EXIT)
"""
suspend/resume
"""
def suspend(self):
"""
TODO:
Mode IDLE (doit rester à l'écoute d'un resume,
et doit continuer à alimenter les tables pour informer de son état via tables agents_logs,
et lire table agents_command pour reprendre via resume,
et update la table agents_survey pour donner son status "idle"
"""
self.set_idle()
return True
def resume(self):
"""
Quit suspend() mode
"""
self.set_active()
return True
"""
=================================================================
Generic methods that may be specialized (overriden) by subclasses
=================================================================
"""
def init(self):
print("Initializing...")
self.set_status(self.STATUS_INIT)
def load_config(self):
"""
TODO:
only si date fichier xml changée => en RAM, un objet Config avec méthodes d'accès, appelle le parser de AK (classe Config.py indépendante)
"""
print("Loading the config file...")
#config_filename = 'c:/srv/develop/pyros/config/config_unit_simulunit1.xml'
#config.set_configfile(config_filename)
self.config.load()
# --- get the parameter value of <mount1/localization/home>
instantiation = 'mount1'
section = 'localization'
key = 'home'
paramvalue = self.config.get_paramvalue(instantiation, section, key)
print("paramvalue <{}/{}/{}> = {}".format(instantiation, section, key,paramvalue))
"""
# self.config = Config.objects.get(pk=1)
try:
self.config = get_object_or_404(Config, id=1)
# By default, set mode to SCHEDULER (False = REMOTE, which should never be the default)
# self.config.global_mode = True
# self.config.save()
# self.config = Config.objects.get(pk=1)
# self.config = Config.objects.get()[0]
except Exception as e:
# except Config.ObjectDoesNotExist:
# except Config.DoesNotExist:
print("Config read (or write) exception", str(e))
# return self.config
# return -1
return False
"""
def update_db_survey(self):
print("Updating the survey database table...")
def read_db_commands(self):
print("Looking for new commands from the database ...")
def do_log(self):
"""
log à 2 endroits ou 1 seul
- in file
- in db
"""
print("Logging data...")
def core_process(self):
"""
Sublevel Loop (only if ACTIVE) :
PLUS TARD, maybe :start_process_thread() dans un thread : ensuite, à chaque tour de boucle il regarde si c'est fini ou pas, et si fini recommence
"""
assert(self.is_active())
print("Starting the core process subloop...")
self.waitfor(self.subloop_waittime)
print("Ending core process subloop...")
"""
===================================
OLD FUNCTIONS TO BE REMOVED
===================================
"""
def _plc_is_not_auto(self):
if not self.plc_is_connected():
return True
# now, self.plc_status has been updated, so check it:
return self.plc_status.plc_mode != "AUTO"
def plc_is_auto(self):
return not self._plc_is_not_auto()
def plc_is_safe(self):
if not self.plc_is_connected():
return False
# now, self.plc_status has been updated, so check it:
return self.plc_status.is_safe
def is_night(self):
return get_sunelev() < -10