sensorman.py 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515
  1. '''
  2. Created on Nov 28, 2012
  3. @author: Repnikov Dmitry
  4. '''
  5. import os, logging, threading, re, copy
  6. from jsonrpc_server import JSONRPCServer
  7. from module_loader import ModuleLoader
  8. from helperBTE import HelperBTE
  9. from helperIPcamera import HelperIPcamera
  10. from message_queue import MessageQueue
  11. from helperSNMPSensors import HelperSNMPSensors
  12. from helperElectricityMeter import HelperElectricityMeter
  13. from helperDGS import HelperDGS
  14. from helperMPSU import HelperMPSU
  15. from helperInverPack import HelperInverPackMeter
  16. from helperCRS import HelperCRS
  17. from helperPulsar import HelperPulsar
  18. from helperUPKB import HelperUPKB
  19. from configman import make_copy
  20. from sensor_scheduler import SensorScheduler
  21. class SensorHolder:
  22. """ Class to hold sensor, cache and other """
  23. sensors_lock = threading.Lock() # Global lock for sensors
  24. def __init__(self, _sensor):
  25. self.state = None # Current state of this sensor
  26. self.value = None # Current value of this sensor
  27. self.sensor = _sensor
  28. def onChange(self):
  29. pass
  30. class SensorManager(JSONRPCServer, HelperPulsar, HelperBTE, HelperIPcamera, HelperSNMPSensors, HelperElectricityMeter, HelperDGS, HelperMPSU, HelperInverPackMeter, HelperCRS, HelperUPKB):
  31. """ Sensor manager. API for sensors storing and management. """
  32. JSONRPC_SERVER_ADDRESS = ("127.0.0.1", 4665)
  33. STATE_CHANGE_TRIGGER_PATH = ["notifications", "notifications", "3db09436-17a8-4b0e-b70c-93ceaf094cff", "trigger"]
  34. SENSOR_SCHEDULERS = ["DEFAULT", "1W", "CONMAN"]
  35. def __init__(self, _configman, _queue_event, _queue_action):
  36. self.logger = logging.getLogger(__name__)
  37. self.sensors = {}
  38. self.sensor_modules = ModuleLoader(__file__, "/../sensors/", "sensor_*", _recursive = True)
  39. self.event_modules = ModuleLoader(__file__, "/../events/", "event_*")
  40. self.configman = _configman
  41. self.queue_event = _queue_event
  42. self.queue_action = _queue_action
  43. self.deviceman = None
  44. self.sensor_schedulers = {}
  45. for name in self.SENSOR_SCHEDULERS:
  46. self.sensor_schedulers[name] = SensorScheduler(name)
  47. HelperBTE.__init__(self, _configman)
  48. HelperIPcamera.__init__(self, _configman)
  49. HelperSNMPSensors.__init__(self, _configman)
  50. HelperElectricityMeter.__init__(self, _configman)
  51. HelperDGS.__init__(self, _configman)
  52. HelperMPSU.__init__(self, _configman)
  53. HelperInverPackMeter.__init__(self, _configman)
  54. HelperCRS.__init__(self, _configman)
  55. HelperPulsar.__init__(self, _configman)
  56. HelperUPKB.__init__(self, _configman)
  57. JSONRPCServer.__init__(self)
  58. def set_device_man(self, _deviceman):
  59. self.deviceman = _deviceman
  60. try:
  61. HelperBTE.set_device_man(_deviceman)
  62. HelperDGS.set_device_man(_deviceman)
  63. HelperMPSU.set_device_man(_deviceman)
  64. HelperPulsar.set_device_man(_deviceman)
  65. except: self.logger.exception("Unable to set device manager instance")
  66. #try: self.MPSU_check({"alias":"serial232.0", "name":"/dev/ttyS1"}, 9600)
  67. #except: self.logger.exception("Unable to set check MPSU")
  68. def start_sensor(self, id):
  69. """ Start sensor thread, if any """
  70. try:
  71. self.logger.debug("Starting sensor module '"+id+"'")
  72. self.put_value_pair(id, (None, None), default=True)
  73. self.sensors[id].sensor.restart()
  74. except: self.logger.exception("Unable to start sensor '"+id+"'")
  75. def start_scheduler(self):
  76. try:
  77. for scheduler in self.sensor_schedulers.values():
  78. scheduler.start()
  79. except:
  80. self.logger.exception("start scheduler error")
  81. def register_polling_sensor(self, sensor):
  82. self.sensor_schedulers[sensor.get_scheduler_name()].register_sensor(sensor)
  83. def unregister_polling_sensor(self, sensor):
  84. self.sensor_schedulers[sensor.get_scheduler_name()].unregister_sensor(sensor)
  85. def list_sensor_ids(self):
  86. return self.sensors.keys()
  87. def find_sensor_by_class(self, class_name):
  88. """ Find sensor id by class name (can be regular expression) """
  89. SensorHolder.sensors_lock.acquire()
  90. res = []
  91. try:
  92. for id in self.sensors.keys():
  93. if re.match(class_name, self.sensors[id].sensor.__class__.__name__):
  94. res.append(id)
  95. except: self.logger.exception("Unable to find sensor by class '"+str(class_name)+"'")
  96. SensorHolder.sensors_lock.release()
  97. return res
  98. def find_sensor_by_group(self, group_name):
  99. """ Find sensor id by group name (can be regular expression) """
  100. SensorHolder.sensors_lock.acquire()
  101. res = []
  102. try:
  103. for id in self.sensors.keys():
  104. if re.match(group_name, self.sensors[id].sensor.group):
  105. res.append(id)
  106. except: self.logger.exception("Unable to find sensor by group '"+str(group_name)+"'")
  107. SensorHolder.sensors_lock.release()
  108. return res
  109. def get_available_classes(self):
  110. """ Return dictionary {"class_name":("description", [ids])} """
  111. res = {}
  112. self.logger.debug("get_available_classes start")
  113. SensorHolder.sensors_lock.acquire()
  114. try:
  115. sensor_classes = self.sensor_modules.class_by_regexp("Sensor[\w]+")
  116. for sensor_class in sensor_classes:
  117. sensor_ids = []
  118. for id in self.sensors.keys():
  119. if (not (id in sensor_ids)) and (self.sensors[id].sensor.__class__.__name__ == sensor_class.__name__):
  120. sensor_ids.append(id)
  121. res[sensor_class.__name__] = ( [sensor_class.__doc__, sensor_ids] )
  122. except: res = None
  123. SensorHolder.sensors_lock.release()
  124. self.logger.debug("get_available_classes end")
  125. return res
  126. def get_view_dict(self, id):
  127. """
  128. Returns view dictionary of sensor. This dictionary
  129. contains strings, that can be used in formatting.
  130. """
  131. try:
  132. return self.sensors[id].sensor.get_view_dict()
  133. except:
  134. return {}
  135. def get_view_dicts(self, ids):
  136. """ Returns view dictionaries of sensors. """
  137. res = {}
  138. try:
  139. for id in ids: res[id] = self.get_view_dict(id)
  140. return res
  141. except: return {}
  142. def get_view_dict_by_alias(self, alias):
  143. try: return self.get_sensor_by_alias(alias).get_view_dict()
  144. except: return {}
  145. def get_view_dicts_by_aliases(self, aliases):
  146. res = {}
  147. for alias in aliases:
  148. try: res[alias] = self.get_sensor_by_alias(alias).get_view_dict()
  149. except: self.logger.exception("get_sensor_by_alias exception")
  150. return res
  151. def get_all_sensors_views(self):
  152. res = {}
  153. for id in self.sensors.keys():
  154. res[id] = self.get_view_dict(id)
  155. return res
  156. def get_value_description(self, id, value):
  157. """ Returns full form of value description, include state """
  158. try: return self.sensors[id].sensor.get_value_descr(value)
  159. except: return {}
  160. def apply_view_dict(self, id, nview):
  161. """ Set dict nview to sensor (through SensorView class) """
  162. try: return self.sensors[id].sensor.apply_view_dict(nview)
  163. except: return False
  164. def apply_view_dict_by_alias(self, alias, nview):
  165. """ Set dict nview to sensor (through SensorView class) """
  166. try: return self.get_sensor_by_alias(alias).apply_view_dict(nview)
  167. except: return False
  168. def put_value_pair(self, id, pair, default = False, priority = None):
  169. """ Write value pair (value, state) to sensor corresponding files """
  170. """ default - if no value/state files were created, this pair be written as default """
  171. value, state = pair
  172. fRefreshValue, fRefreshSate = False, False
  173. SensorHolder.sensors_lock.acquire()
  174. if not (id in self.sensors.keys()):
  175. SensorHolder.sensors_lock.release()
  176. return
  177. try:
  178. if not default: old_value, old_state = self.get_value_pair(id, False)
  179. else: old_value, old_state = pair
  180. except:
  181. self.logger.exception("Exception while put state/value for sensor '"+str(id)+"'")
  182. SensorHolder.sensors_lock.release()
  183. return
  184. try:
  185. if (old_value != value) or (default): fRefreshValue, self.sensors[id].value = True, value
  186. if (old_state != state) or (default): fRefreshSate, self.sensors[id].state = True, state
  187. except:
  188. self.logger.exception("Exception while put state/value for sensor '"+str(id)+"'")
  189. SensorHolder.sensors_lock.release()
  190. return
  191. #fRefreshValue = fRefreshValue or self.sensors[id].sensor.history['any_value']
  192. fRefreshValue = fRefreshValue or (id in [
  193. 'DEAD0000-BEAF-DEAD-BEAF-EE0000000005',
  194. 'DEAD0000-BEAF-DEAD-BEAF-EE0000000006',
  195. 'DEAD0000-BEAF-DEAD-BEAF-EE00000000A5',
  196. 'DEAD0000-BEAF-DEAD-BEAF-EE00000000A6'
  197. ])
  198. #if id == '00000000-0000-0000-0000-000000000001':
  199. # self.logger.error("Value change of '"+id+"' detected: "+str(old_value)+" -> "+str(value) + " " + str(fRefreshValue) + " " + str(default))
  200. if fRefreshValue:
  201. #self.logger.error("Value change of '"+id+"' detected: "+str(old_value)+" -> "+str(value) + " " + str(fRefreshValue) + " " + str(default))
  202. if self.logger.level <= logging.DEBUG:
  203. self.logger.debug("Value change of '"+id+"' detected: "+str(old_value)+" -> "+str(value))
  204. if not default: self.create_event_custprior(priority, "EventValueChange", self.sensors[id].sensor, old_value, value, state)
  205. if fRefreshSate:
  206. if self.logger.level <= logging.DEBUG:
  207. self.logger.debug("State change of '"+id+"' detected: "+str(old_state)+" -> "+str(state))
  208. if not default: self.create_event_custprior(priority, "EventStateChange", self.sensors[id].sensor, old_state, state, value)
  209. if fRefreshValue or fRefreshSate: self.sensors[id].onChange()
  210. SensorHolder.sensors_lock.release()
  211. def get_value_pair(self, id, fLock = True):
  212. """ Read value pair (value, state) for sensor from corresponding files """
  213. value, state = None, None
  214. if fLock: SensorHolder.sensors_lock.acquire()
  215. try:
  216. if id in self.sensors.keys(): value, state = self.sensors[id].value, self.sensors[id].state
  217. finally:
  218. if fLock: SensorHolder.sensors_lock.release()
  219. return (value, state)
  220. def get_value_pairs(self, ids):
  221. """ Returns id:value/state pairs for list of ids """
  222. SensorHolder.sensors_lock.acquire()
  223. try: res = dict(zip(ids, map(lambda x: self.get_value_pair(x, False), ids)))
  224. except: res = {}
  225. SensorHolder.sensors_lock.release()
  226. return res
  227. def get_value_descrs(self, values):
  228. """ Returns id:description pairs for list of ids """
  229. SensorHolder.sensors_lock.acquire()
  230. try: res = dict(zip(values.keys(), map(lambda x: self.get_value_description(x, values[x]), values.keys())))
  231. except: res = {}
  232. SensorHolder.sensors_lock.release()
  233. return res
  234. def get_value_pair_and_descr(self, ids):
  235. """ Returns id:value/state/description pairs for list of ids """
  236. SensorHolder.sensors_lock.acquire()
  237. try:
  238. res = dict(zip(ids, map(lambda x: self.get_value_pair(x, False), ids)))
  239. res = dict(zip(ids, map(lambda x: (res[x][0], res[x][1], self.get_value_description(x, res[x][0])), ids )))
  240. except:
  241. self.logger.exception("Exception while get_value_pair_and_descr.")
  242. res = {}
  243. SensorHolder.sensors_lock.release()
  244. return res
  245. def get_sensor_by_id(self, id):
  246. return self.sensors[id].sensor
  247. def get_sensor_by_alias(self, alias):
  248. for sensor_holder in self.sensors.values():
  249. if sensor_holder.sensor.alias == alias:
  250. return sensor_holder.sensor
  251. raise Exception("sensor {0} not found".format(alias))
  252. def get_sensor_id_by_alias(self, alias):
  253. for sensor_holder in self.sensors.values():
  254. if sensor_holder.sensor.alias == alias:
  255. return sensor_holder.sensor.id
  256. raise Exception("sensor {0} not found".format(alias))
  257. def update_snmp_sensor(self, id):
  258. """ Immidiate update of snmp sensor. Blocking. """
  259. try: self.get_sensor_by_id(id).immidiate_update()
  260. except: self.logger.exception("Exception while update_snmp_sensor")
  261. """ ToDo: rename this to event_taker """
  262. def subscribe(self, _class, _event, _handler):
  263. """ Register new handler for some sensor """
  264. self.queue_event.subscribe(_class, _event, _handler)
  265. def action_taker(self, _class, _event, _handler):
  266. """ Register new handler for some sensor """
  267. self.queue_action.subscribe(_class, _event, _handler)
  268. def put_message(self, _queue, _class, _priority, *_args):
  269. """ Create action or event message for some subsystem. _class - string with event class name. """
  270. """ _args - will be translate to command. if [0] (sender) = None, we set it as self """
  271. if (len(_args) >= 1):
  272. if (len(_class) > 2) and (_class[-2:] != "\Z"): _class += "\Z"
  273. if self.logger.level <= logging.INFO:
  274. self.logger.info("Try to generate event '"+str(_class)+"' for '"+str(_args[0])+"'...")
  275. classes = self.event_modules.class_by_regexp(_class)
  276. if len(classes) == 1:
  277. if (len(_args) >= 1) and (_args[0] == None): _args = (self,)+_args[1:]
  278. try: _queue.put(classes[0](*_args))
  279. except: self.logger.exception("Exception while creating event class '"+_class+"'")
  280. else: self.logger.error("Loading event "+_class+" - FAIL, no or too many classes ("+str(classes)+")")
  281. else: self.logger.error("Not enough arguments to create event '"+_class+"' - FAIL")
  282. def create_event(self, _class, *_args):
  283. self.put_message(self.queue_event, _class, MessageQueue.PRIOR_LOWEST, *_args)
  284. def create_event_hiprior(self, _class, *_args):
  285. self.put_message(self.queue_event, _class, MessageQueue.PRIOR_HIGHEST, *_args)
  286. def create_event_custprior(self, _prior, _class, *_args):
  287. if _prior == None: _prior = MessageQueue.PRIOR_LOWEST
  288. self.put_message(self.queue_event, _class, _prior, *_args)
  289. def create_action(self, _class, *_args):
  290. self.put_message(self.queue_action, _class, MessageQueue.PRIOR_LOWEST, *_args)
  291. """ ToDo: maybe move this to some power-helper """
  292. def is_battery_present(self):
  293. try: return self.get_sensor_by_alias("BAT").is_battery_present()
  294. except: return False
  295. """ --- This two routines for compatibility, and for raw Event/Action class put --- """
  296. def put_action(self, action, priority = None):
  297. """ Put action element in queue """
  298. if priority == None: priority = MessageQueue.PRIOR_LOWEST
  299. self.queue_action.put(action, priority)
  300. def put_event(self, event, priority = None):
  301. """ Put event element in queue """
  302. if priority == None: priority = MessageQueue.PRIOR_LOWEST
  303. self.queue_event.put(event)
  304. def load_sensor(self, id, descr):
  305. """ Create sensor from configuration description and put it in self.sensors """
  306. """ WARNING! By default this function works only with NEW configuration. """
  307. """ For newly added sensor please use load_new_sensor, to save/deserialize configuration. """
  308. try:
  309. self.logger.info("Try to load sensor "+id+"...")
  310. sensor_classes = self.sensor_modules.class_by_regexp(descr["sensor_class"]+"\Z")
  311. if len(sensor_classes) == 1:
  312. try:
  313. self.logger.debug("Found sensor class '"+str(sensor_classes[0].__name__)+"' for sensor "+id+", creating...")
  314. self.sensors[id] = SensorHolder(sensor_classes[0](self.configman, self, id))
  315. return True
  316. except: self.logger.exception("Exception while creating sensor class '"+str(sensor_classes[0])+"', id "+id)
  317. else: self.logger.error("Loading sensor "+id+" - FAIL, no such class: "+descr["sensor_class"]+" or too many classes ("+str(sensor_classes)+")")
  318. except: self.logger.exception("Exception while searching for sensor with description '"+str(descr)+"'")
  319. return False
  320. def load_new_sensor(self, id, class_name):
  321. fake_descr = {"sensor_class":class_name}
  322. if self.load_sensor(id, fake_descr):
  323. self.start_sensor(id)
  324. self.configman.confirm_path(["sensors", id])
  325. return True
  326. return False
  327. def confirm_sensors(self):
  328. self.configman.confirm_path(["sensors"])
  329. def delete_sensor(self, id):
  330. result = False
  331. self.sensors[id].sensor.stop()
  332. SensorHolder.sensors_lock.acquire()
  333. try:
  334. self.sensors[id].sensor.delete()
  335. del self.sensors[id]
  336. except: self.logger.exception("Exception while delete sensor '"+str(id)+"'")
  337. finally: SensorHolder.sensors_lock.release()
  338. try:
  339. """ This must not be under lock """
  340. sensors = make_copy(self.configman.read_config(["sensors"]))
  341. del sensors[id]
  342. self.configman.write_config(["sensors"], sensors)
  343. result = True
  344. except: self.logger.exception("Exception while delete sensor '"+str(id)+"'")
  345. return result
  346. def delete_sensor_device(self, sensor_id):
  347. result = False
  348. ids = []
  349. try:
  350. device_sensors = [sensor for sensor in self.sensors[sensor_id].sensor.get_device_sensors()]
  351. self.sensors[sensor_id].sensor.remove_device()
  352. except:
  353. self.logger.exception("Exception while delete device '"+str(sensor_id)+"'")
  354. try:
  355. """ This must not be under lock """
  356. sensors = make_copy(self.configman.read_config(["sensors"]))
  357. aliases = []
  358. for sensor in device_sensors:
  359. try:
  360. self.logger.debug("delete sensor '"+str(sensor.id)+"'")
  361. aliases.append(sensor.alias)
  362. del sensors[sensor.id]
  363. del self.sensors[sensor.id]
  364. except:
  365. self.logger.exception("Exception while delete sensor '"+str(sensor.id)+"'")
  366. self.configman.write_config(["sensors"], sensors)
  367. try:
  368. self.set_send_inform_flag(aliases, False)
  369. except:
  370. self.logger.exception("can't set inform flag")
  371. result = True
  372. except: self.logger.exception("Exception while delete device '"+str(sensor_id)+"'")
  373. return result
  374. def get_controller_state(self):
  375. sensor = self.get_sensor_by_alias("CNT")
  376. value = self.get_value_pair(sensor.id)[0]
  377. return self.get_value_description(sensor.id, value).get("caption", "n/a")
  378. def get_Sig(self, teploAPI=None):
  379. def get_sensor_state(alias):
  380. return self.get_sensor_state_by_alias(alias) == 'almaj'
  381. events_functions = {
  382. 0: [lambda: get_sensor_state("VIBlis")],
  383. 1: [lambda: self.get_sensor_value_by_alias("teploUB.pwr") in ("DISCHARGING", "SHUTDOWN")],
  384. 2: [lambda: get_sensor_state("DI1")],
  385. 3: [lambda: get_sensor_state("DI2")],
  386. 4: [lambda: get_sensor_state("DI3")],
  387. 5: [lambda: get_sensor_state("DI4")],
  388. 6: [lambda: get_sensor_state("teploUB.extDS1")],
  389. 7: [lambda: teploAPI is not None and not teploAPI.check_serial_numbers()],
  390. }
  391. sig = 0
  392. for bit, func_list in events_functions.items():
  393. sig |= any(func() for func in func_list) << bit
  394. return sig
  395. def get_sensor_description_by_alias(self, alias): #description
  396. sensor = self.get_sensor_by_alias(alias)
  397. value = self.get_value_pair(sensor.id)[0]
  398. return self.get_value_description(sensor.id, value).get("caption", "n/a")
  399. def get_sensor_state_by_alias(self, alias):
  400. sensor = self.get_sensor_by_alias(alias)
  401. return self.get_value_pair(sensor.id)[1]
  402. def get_sensor_value_by_alias(self, alias):
  403. sensor = self.get_sensor_by_alias(alias)
  404. return self.get_value_pair(sensor.id)[0]
  405. def find_free_alias(self, pattern):
  406. """ ToDo: merge with same function of event monitor (load_sensors) and move to configman """
  407. res = set(range(1, 999))
  408. try:
  409. self.logger.debug("Search for free aliases...")
  410. all_sensors = self.configman.read_config(["sensors"])
  411. for sensor in all_sensors.keys():
  412. try:
  413. self.logger.debug("matching sensor ID \'{0}\' alias \'{1}\'".format(sensor, all_sensors[sensor]["alias"]))
  414. matched = re.match(pattern, all_sensors[sensor]["alias"])
  415. if matched:
  416. self.logger.debug("\tAllready have sensor ID '"+sensor+"'")
  417. num = int(matched.group("num"), 10)
  418. self.logger.debug("removing {0}".format(num))
  419. if num in res:
  420. res.remove(num)
  421. except:
  422. self.logger.exception("Exception while scan aliases '"+str(sensor)+"'")
  423. except:
  424. self.logger.exception("Exception while read sensors configuration")
  425. return res # This is bad - on exception will return full range
  426. def set_send_inform_flag(self, aliases, flag):
  427. try:
  428. change_state_trigger = self.configman.read_config(self.STATE_CHANGE_TRIGGER_PATH, copy_=True)
  429. for alias in aliases:
  430. if len(change_state_trigger) == 0:
  431. change_state_trigger.append("or")
  432. trigger = [
  433. "sensor_state",
  434. alias
  435. ]
  436. if flag:
  437. if trigger not in change_state_trigger:
  438. change_state_trigger.append(trigger)
  439. else:
  440. try:
  441. change_state_trigger.remove(trigger)
  442. except Exception as e: self.logger.warning("No sensor for trigger "+str(trigger))
  443. self.configman.write_config(self.STATE_CHANGE_TRIGGER_PATH, change_state_trigger)
  444. except:
  445. self.logger.exception("set_send_snmp_flag exception")