Nagios Plugins
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

__init__.py 12KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303
  1. import json
  2. import logging
  3. import mqtt
  4. import nagios
  5. import time
  6. from z_protocol import DID_ACTOR, DID_BATTERY_LEVEL, DID_FOLLOWS_SETPOINT, DID_HEARTBEAT, DID_LINKQUALITY
  7. try:
  8. from config import APP_NAME as ROOT_LOGGER_NAME
  9. except ImportError:
  10. ROOT_LOGGER_NAME = 'root'
  11. logger = logging.getLogger(ROOT_LOGGER_NAME).getChild(__name__)
  12. class base(object):
  13. KEY_BATTERY = 'battery'
  14. KEY_CURRENT_VALUE = None
  15. KEY_LINKQUALITY = 'linkquality'
  16. KEY_SETPOINT = None
  17. #
  18. FOLLOW_REQUEST_WARNING = 5 # Seconds, till warning comes up, if device does not follow the command
  19. FOLLOW_REQUEST_ERROR = 60 # Seconds, till error comes up, if device does not follow the command
  20. #
  21. BATTERY_LVL_WARNING = 15
  22. BATTERY_LVL_ERROR = 5
  23. #
  24. LINKQUALITY_WARNING = 50
  25. LINKQUALITY_ERROR = 25
  26. #
  27. LAST_MSG_WARNING = 6 * 60 * 60
  28. LAST_MSG_ERROR = 24 * 60 * 60
  29. def __init__(self, mqtt_client: mqtt.mqtt_client, topic):
  30. self.topic = topic
  31. #
  32. self.__unknown_tm__ = {}
  33. #
  34. mqtt_client.add_callback(topic, self.__rx__)
  35. mqtt_client.add_callback(topic + '/#', self.__rx__)
  36. #
  37. self.last_device_msg = None
  38. #
  39. self.__target_storage__ = {}
  40. self.__state_storage__ = {}
  41. def __rx__(self, client, userdata, message):
  42. try:
  43. payload = json.loads(message.payload)
  44. except json.decoder.JSONDecodeError:
  45. pass
  46. else:
  47. if type(payload) is dict:
  48. #
  49. # Device values
  50. #
  51. if message.topic == self.topic:
  52. for key in [self.KEY_BATTERY, self.KEY_CURRENT_VALUE, self.KEY_LINKQUALITY, self.KEY_SETPOINT]:
  53. if key in payload:
  54. self.state(key, payload[key])
  55. #
  56. # Device setpoint
  57. #
  58. if message.topic == self.topic + '/set' and self.KEY_SETPOINT in payload:
  59. self.target(self.KEY_SETPOINT, payload[self.KEY_SETPOINT])
  60. #
  61. # heartbeat
  62. #
  63. if message.topic == self.topic:
  64. self.last_device_msg = time.time()
  65. def target(self, key, value):
  66. tm_t, value_t = self.__target_storage__.get(key, (0, None))
  67. if value != value_t:
  68. self.__target_storage__[key] = time.time(), value
  69. logger.debug("Target value for device identified: %s: %s", key, repr(value))
  70. def state(self, key, value):
  71. self.__state_storage__[key] = time.time(), value
  72. logger.debug("Device state identified: %s: %s", key, repr(value))
  73. def status(self, key):
  74. #
  75. # ACTOR
  76. #
  77. if key == DID_ACTOR:
  78. return self.__nagios_return__(DID_ACTOR, nagios.Nagios.WARNING, "Not available for this device")
  79. #
  80. # HEARTBEAT
  81. #
  82. elif key == DID_HEARTBEAT:
  83. if self.last_device_msg is None:
  84. return self.__nagios_return__(DID_HEARTBEAT, nagios.Nagios.UNKNOWN, "Device exists, but no data received")
  85. else:
  86. dt = time.time() - self.last_device_msg
  87. dt_disp = dt / 60 / 60
  88. if dt > self.LAST_MSG_ERROR:
  89. return self.__nagios_return__(DID_HEARTBEAT, nagios.Nagios.ERROR, "Last message %.1fh ago" % dt_disp)
  90. elif dt > self.LAST_MSG_WARNING:
  91. return self.__nagios_return__(DID_HEARTBEAT, nagios.Nagios.WARNING, "Last message %.1fh ago" % dt_disp)
  92. else:
  93. return self.__nagios_return__(DID_HEARTBEAT, nagios.Nagios.OK, "Last message %.1fh ago" % dt_disp)
  94. #
  95. # FOLLOW SETPOINT
  96. #
  97. elif key == DID_FOLLOWS_SETPOINT:
  98. if self.KEY_SETPOINT is None:
  99. return self.__nagios_return__(DID_FOLLOWS_SETPOINT, nagios.Nagios.UNKNOWN, "Device exist, but does not follow any setpoint.", force=True)
  100. tm_s, value_s = self.__state_storage__.get(self.KEY_SETPOINT, (0, None))
  101. try:
  102. tm_t, value_t = self.__target_storage__[self.KEY_SETPOINT]
  103. except KeyError:
  104. if value_s is not None:
  105. return self.__nagios_return__(DID_FOLLOWS_SETPOINT, nagios.Nagios.OK, "Current temperature setpoint %.1f C, but never received a setpoint. That might be okay." % value_s)
  106. return self.__nagios_return__(DID_FOLLOWS_SETPOINT, nagios.Nagios.UNKNOWN, "Device exists, but no data received")
  107. else:
  108. tm = time.time()
  109. dt = tm - tm_t
  110. if value_t != value_s and dt > self.FOLLOW_REQUEST_ERROR:
  111. return self.__nagios_return__(DID_FOLLOWS_SETPOINT, nagios.Nagios.ERROR, "Requested setpoint %.1f C unequal valve setpoint %.1f C since %.1fmin" % (value_t, value_s, (time.time()-tm_s)/60))
  112. elif value_t != value_s and dt > self.FOLLOW_REQUEST_WARNING:
  113. return self.__nagios_return__(DID_FOLLOWS_SETPOINT, nagios.Nagios.WARNING, "Requested setpoint %.1f C unequal valve setpoint %.1f C since %.1fmin" % (value_t, value_s, (time.time()-tm_s)))
  114. return self.__nagios_return__(DID_FOLLOWS_SETPOINT, nagios.Nagios.OK, "Requested setpoint equal valve setpoint %.1f C" % value_s)
  115. #
  116. # BATTERY
  117. #
  118. elif key == DID_BATTERY_LEVEL:
  119. battery_lvl = self.__state_storage__.get(self.KEY_BATTERY, (0, None))[1]
  120. if battery_lvl is None:
  121. return self.__nagios_return__(DID_BATTERY_LEVEL, nagios.Nagios.UNKNOWN, "Device exists, but no data received or unknown monitoring")
  122. elif battery_lvl <= self.BATTERY_LVL_ERROR:
  123. return self.__nagios_return__(DID_BATTERY_LEVEL, nagios.Nagios.ERROR, "Battery level critical low (%.1f%%)" % battery_lvl)
  124. elif battery_lvl <= self.BATTERY_LVL_WARNING:
  125. return self.__nagios_return__(DID_BATTERY_LEVEL, nagios.Nagios.WARNING, "Battery level low (%.1f%%)" % battery_lvl)
  126. else:
  127. return self.__nagios_return__(DID_BATTERY_LEVEL, nagios.Nagios.OK, "Battery okay (%.1f%%)" % battery_lvl)
  128. #
  129. # LINKQUALITY
  130. #
  131. elif key == DID_LINKQUALITY:
  132. linkquality = self.__state_storage__.get(self.KEY_LINKQUALITY, (0, None))[1]
  133. if linkquality is None:
  134. return self.__nagios_return__(DID_LINKQUALITY, nagios.Nagios.UNKNOWN, "Device exists, but no data received or unknown monitoring")
  135. elif linkquality <= self.LINKQUALITY_ERROR:
  136. return self.__nagios_return__(DID_LINKQUALITY, nagios.Nagios.ERROR, "Linkquality critical low (%d)" % linkquality)
  137. elif linkquality <= self.LINKQUALITY_WARNING:
  138. return self.__nagios_return__(DID_LINKQUALITY, nagios.Nagios.WARNING, "Linkquality level low (%d)" % linkquality)
  139. else:
  140. return self.__nagios_return__(DID_LINKQUALITY, nagios.Nagios.OK, "Linkquality okay (%d)" % linkquality)
  141. def __nagios_return__(self, did, status, msg, force=False):
  142. tm = time.time()
  143. if did not in self.__unknown_tm__:
  144. self.__unknown_tm__[did] = None
  145. if status == nagios.Nagios.UNKNOWN and not force:
  146. if self.__unknown_tm__[did] is None:
  147. self.__unknown_tm__[did] = tm
  148. dt = tm - self.__unknown_tm__[did]
  149. if dt >= self.LAST_MSG_ERROR:
  150. status = nagios.Nagios.UNKNOWN
  151. elif dt >= self.LAST_MSG_WARNING:
  152. status = nagios.Nagios.WARNING
  153. else:
  154. status = nagios.Nagios.OK
  155. msg += " - since %.1fh" % (dt / 3600)
  156. else:
  157. self.__unknown_tm__[did] = None
  158. return {"status": status, "msg": msg}
  159. class group(object):
  160. def __init__(self, *args, **kwargs):
  161. pass
  162. class shelly_sw1(base):
  163. pass
  164. class tradfri_sw(base):
  165. pass
  166. class tradfri_sw_br(base):
  167. pass
  168. class tradfri_sw_br_ct(base):
  169. pass
  170. class tradfri_button(base):
  171. LAST_MSG_WARNING = 24 * 60 * 60
  172. LAST_MSG_ERROR = 48 * 60 * 60
  173. class livarno_sw_br_ct(base):
  174. pass
  175. class brennenstuhl_heatingvalve(base):
  176. BATTERY_LVL_WARNING = 4
  177. BATTERY_LVL_ERROR = 3
  178. #
  179. ACTOR_WARN_OFFSET = 1.5
  180. ACTOR_ERR_OFFSET = 2.5
  181. #
  182. KEY_SETPOINT = "current_heating_setpoint"
  183. KEY_CURRENT_VALUE = "local_temperature"
  184. def status(self, key):
  185. #
  186. # ACTOR
  187. #
  188. if key == DID_ACTOR:
  189. tm_s, value_s = self.__state_storage__.get(self.KEY_SETPOINT, (0, None))
  190. tm_c, value_c = self.__state_storage__.get(self.KEY_CURRENT_VALUE, (0, None))
  191. #
  192. if value_s is None or value_c is None:
  193. return self.__nagios_return__(DID_ACTOR, nagios.Nagios.UNKNOWN, "Device exists, but no data received")
  194. elif value_s <= 5:
  195. return self.__nagios_return__(DID_ACTOR, nagios.Nagios.OK, "No monitoring in Summer Mode")
  196. elif value_c > value_s + self.ACTOR_ERR_OFFSET:
  197. return self.__nagios_return__(DID_ACTOR, nagios.Nagios.ERROR, "Current Temperature much to high %.1f C > %.1f C" % (value_c, value_s))
  198. elif value_c > value_s + self.ACTOR_WARN_OFFSET:
  199. return self.__nagios_return__(DID_ACTOR, nagios.Nagios.WARNING, "Current Temperature to high %.1f C > %.1f C" % (value_c, value_s))
  200. else:
  201. return self.__nagios_return__(DID_ACTOR, nagios.Nagios.OK, "Current Temperature okay %.1f C > %.1f C" % (value_c, value_s))
  202. else:
  203. return super().status(key)
  204. class silvercrest_powerplug(base):
  205. pass
  206. class silvercrest_motion_sensor(base):
  207. pass
  208. class my_powerplug(base):
  209. pass
  210. class audio_status(base):
  211. pass
  212. class remote(base):
  213. pass
  214. class my_ambient(base):
  215. KEY_TEMPERATURE = 'temperature'
  216. KEY_PRESSURE = 'pressure'
  217. KEY_HUMIDITY = 'humidity'
  218. #
  219. LAST_MSG_WARNING = 20 * 60
  220. LAST_MSG_ERROR = 30 * 60
  221. def __init__(self, mqtt_client: mqtt.mqtt_client, topic):
  222. super().__init__(mqtt_client, topic)
  223. self.__last_value_rx__ = {
  224. self.KEY_HUMIDITY: None,
  225. self.KEY_PRESSURE: None,
  226. self.KEY_TEMPERATURE: None
  227. }
  228. def __rx__(self, client, userdata, message):
  229. try:
  230. payload = json.loads(message.payload)
  231. except json.decoder.JSONDecodeError:
  232. pass
  233. else:
  234. key = message.topic.split('/')[-1]
  235. if key in self.__last_value_rx__:
  236. self.__last_value_rx__[key] = time.time()
  237. return super().__rx__(client, userdata, message)
  238. def status(self, key):
  239. if key == DID_HEARTBEAT:
  240. status = nagios.Nagios.OK
  241. msg = ""
  242. for key in self.__last_value_rx__:
  243. last_value_rx = self.__last_value_rx__[key]
  244. if len(msg) != 0:
  245. msg += "; "
  246. if last_value_rx is None:
  247. target_status = nagios.Nagios.UNKNOWN
  248. msg += "%s (never)" % key
  249. else:
  250. dt = time.time() - last_value_rx
  251. msg += "%s (%.1f min ago)" % (key, dt / 60)
  252. if dt > self.LAST_MSG_ERROR:
  253. target_status = nagios.Nagios.ERROR
  254. elif dt > self.LAST_MSG_WARNING:
  255. target_status = nagios.Nagios.WARNING
  256. else:
  257. target_status = nagios.Nagios.OK
  258. if target_status > status:
  259. status = target_status
  260. return self.__nagios_return__(DID_HEARTBEAT, status, msg)
  261. else:
  262. return super().status(key)