您最多选择25个主题 主题必须以字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符

523 行
18KB

  1. import inspect
  2. from flask import make_response, g
  3. import datetime
  4. from datetime import datetime
  5. from time import localtime, strftime
  6. from functools import wraps, update_wrapper
  7. import requests
  8. import json
  9. from props import *
  10. from hardware import *
  11. import time
  12. import uuid
  13. import logging
  14. class NotificationAPI(object):
  15. pass
  16. class ActorAPI(object):
  17. def init_actors(self):
  18. self.app.logger.info("Init Actors")
  19. t = self.cache.get("actor_types")
  20. for key, value in t.iteritems():
  21. value.get("class").api = self
  22. value.get("class").init_global()
  23. for key in self.cache.get("actors"):
  24. self.init_actor(key)
  25. def init_actor(self, id):
  26. try:
  27. value = self.cache.get("actors").get(int(id))
  28. cfg = value.config.copy()
  29. cfg.update(dict(api=self, id=id, name=value.name))
  30. cfg.update(dict(api=self, id=id, name=value.name))
  31. clazz = self.cache.get("actor_types").get(value.type).get("class")
  32. value.instance = clazz(**cfg)
  33. value.instance.init()
  34. value.state = 0
  35. value.power = 100
  36. except Exception as e:
  37. self.notify("Actor Error", "Failed to setup actor %s. Please check the configuraiton" % value.name,
  38. type="danger", timeout=None)
  39. self.app.logger.error("Initializing of Actor %s failed" % id)
  40. def switch_actor_on(self, id, power=None):
  41. actor = self.cache.get("actors").get(id)
  42. if actor.state == 1:
  43. return
  44. actor.instance.on(power=power)
  45. actor.state = 1
  46. if power is not None:
  47. actor.power = power
  48. self.emit("SWITCH_ACTOR", actor)
  49. def actor_power(self, id, power=100):
  50. actor = self.cache.get("actors").get(id)
  51. actor.instance.set_power(power=power)
  52. actor.power = power
  53. self.emit("SWITCH_ACTOR", actor)
  54. def switch_actor_off(self, id):
  55. actor = self.cache.get("actors").get(id)
  56. if actor.state == 0:
  57. return
  58. actor.instance.off()
  59. actor.state = 0
  60. self.emit("SWITCH_ACTOR", actor)
  61. class SensorAPI(object):
  62. def init_sensors(self):
  63. '''
  64. Initialize all sensors
  65. :return:
  66. '''
  67. self.logger = logging.getLogger(__name__)
  68. self.app.logger.info("Init Sensors")
  69. t = self.cache.get("sensor_types")
  70. for key, value in t.iteritems():
  71. value.get("class").init_global()
  72. for key in self.cache.get("sensors"):
  73. self.init_sensor(key)
  74. def stop_sensor(self, id):
  75. try:
  76. self.cache.get("sensors").get(id).instance.stop()
  77. except Exception as e:
  78. self.app.logger.info("Stop Sensor Error")
  79. pass
  80. def init_sensor(self, id):
  81. '''
  82. initialize sensor by id
  83. :param id:
  84. :return:
  85. '''
  86. def start_active_sensor(instance):
  87. '''
  88. start active sensors as background job
  89. :param instance:
  90. :return:
  91. '''
  92. instance.execute()
  93. try:
  94. if id in self.cache.get("sensor_instances"):
  95. self.cache.get("sensor_instances").get(id).stop()
  96. value = self.cache.get("sensors").get(id)
  97. cfg = value.config.copy()
  98. cfg.update(dict(api=self, id=id, name=value.name))
  99. clazz = self.cache.get("sensor_types").get(value.type).get("class")
  100. value.instance = clazz(**cfg)
  101. value.instance.init()
  102. if isinstance(value.instance, SensorPassive):
  103. # Passive Sensors
  104. value.mode = "P"
  105. else:
  106. # Active Sensors
  107. value.mode = "A"
  108. t = self.socketio.start_background_task(target=start_active_sensor, instance=value.instance)
  109. except Exception as e:
  110. self.notify("Sensor Error", "Failed to setup Sensor %s. Please check the configuraiton" % value.name, type="danger", timeout=None)
  111. self.app.logger.error("Initializing of Sensor %s failed" % id)
  112. def receive_sensor_value(self, id, value):
  113. self.emit("SENSOR_UPDATE", self.cache.get("sensors")[id])
  114. self.save_to_file(id, value, "sensor")
  115. def save_to_file(self, id, value, prefix):
  116. sensor_name = "%s_%s" % (prefix, str(id))
  117. use_kairosdb = (self.cache["config"]["kairos_db"].__dict__["value"] == "YES")
  118. if use_kairosdb:
  119. self.write_to_tsdb(prefix, sensor_name, value)
  120. else:
  121. self.write_to_logfile(sensor_name, value)
  122. def write_to_logfile(self, sensor_name, value):
  123. filename = "./logs/%s.log" % sensor_name
  124. formatted_time = strftime("%Y-%m-%d %H:%M:%S", localtime())
  125. msg = str(formatted_time) + "," + str(value) + "\n"
  126. with open(filename, "a") as file:
  127. file.write(msg)
  128. def write_to_tsdb(self, prefix, sensor_name, value):
  129. kairosdb_server = "http://127.0.0.1:" + self.cache["config"]["kairos_db_port"].__dict__["value"]
  130. data = [
  131. dict(name="cbpi." + sensor_name, datapoints=[
  132. [int(round(time.time() * 1000)), value]
  133. ], tags={
  134. "cbpi": prefix,
  135. "brew": self.cache["active_brew"]
  136. })
  137. ]
  138. response = requests.post(kairosdb_server + "/api/v1/datapoints", json.dumps(data))
  139. if not response.ok:
  140. self.logger.warning("Failed to write time series entry for [%s]. Response [%s]", sensor_name, response)
  141. def log_action(self, text):
  142. use_kairosdb = (self.cache["config"]["kairos_db"].__dict__["value"] == "YES")
  143. if use_kairosdb:
  144. self.write_to_tsdb("action", "action", text)
  145. else:
  146. self.write_to_logfile("action", text)
  147. def shutdown_sensor(self, id):
  148. self.cache.get("sensors")[id].stop()
  149. def get_sensor_value(self, id):
  150. try:
  151. id = int(id)
  152. return float(self.cache.get("sensors")[id].instance.last_value)
  153. except Exception as e:
  154. return None
  155. class CacheAPI(object):
  156. def get_sensor(self, id):
  157. try:
  158. return self.cache["sensors"][id]
  159. except:
  160. return None
  161. def get_actor(self, id):
  162. try:
  163. return self.cache["actors"][id]
  164. except:
  165. return None
  166. class CraftBeerPi(ActorAPI, SensorAPI):
  167. cache = {
  168. "init": {},
  169. "config": {},
  170. "actor_types": {},
  171. "sensor_types": {},
  172. "sensors": {},
  173. "sensor_instances": {},
  174. "init": [],
  175. "background":[],
  176. "step_types": {},
  177. "controller_types": {},
  178. "messages": [],
  179. "plugins": {},
  180. "fermentation_controller_types": {},
  181. "fermenter_task": {},
  182. "active_brew": "none"
  183. }
  184. buzzer = None
  185. eventbus = {}
  186. # constructor
  187. def __init__(self, app, socketio):
  188. self.app = app
  189. self.socketio = socketio
  190. def emit(self, key, data):
  191. self.socketio.emit(key, data, namespace='/brew')
  192. def notify(self, headline, message, type="success", timeout=5000):
  193. self.beep()
  194. msg = {"id": str(uuid.uuid1()), "type": type, "headline": headline, "message": message, "timeout": timeout}
  195. self.emit_message(msg)
  196. def beep(self):
  197. if self.buzzer is not None:
  198. self.buzzer.beep()
  199. def add_cache_callback(self, key, method):
  200. method.callback = True
  201. self.cache[key] = method
  202. def get_config_parameter(self, key, default):
  203. cfg = self.cache.get("config").get(key)
  204. if cfg is None:
  205. return default
  206. else:
  207. return cfg.value
  208. def set_config_parameter(self, name, value):
  209. from modules.config import Config
  210. with self.app.app_context():
  211. update_data = {"name": name, "value": value}
  212. self.cache.get("config")[name].__dict__.update(**update_data)
  213. c = Config.update(**update_data)
  214. self.emit("UPDATE_CONFIG", c)
  215. def add_config_parameter(self, name, value, type, description, options=None):
  216. from modules.config import Config
  217. with self.app.app_context():
  218. c = Config.insert(**{"name":name, "value": value, "type": type, "description": description, "options": options})
  219. if self.cache.get("config") is not None:
  220. self.cache.get("config")[c.name] = c
  221. def clear_cache(self, key, is_array=False):
  222. if is_array:
  223. self.cache[key] = []
  224. else:
  225. self.cache[key] = {}
  226. # helper method for parsing props
  227. def __parseProps(self, key, cls):
  228. name = cls.__name__
  229. self.cache[key][name] = {"name": name, "class": cls, "properties": [], "actions": []}
  230. tmpObj = cls()
  231. members = [attr for attr in dir(tmpObj) if not callable(getattr(tmpObj, attr)) and not attr.startswith("__")]
  232. for m in members:
  233. if isinstance(tmpObj.__getattribute__(m), Property.Number):
  234. t = tmpObj.__getattribute__(m)
  235. self.cache[key][name]["properties"].append(
  236. {"name": m, "label": t.label, "type": "number", "configurable": t.configurable, "description": t.description, "default_value": t.default_value})
  237. elif isinstance(tmpObj.__getattribute__(m), Property.Text):
  238. t = tmpObj.__getattribute__(m)
  239. self.cache[key][name]["properties"].append(
  240. {"name": m, "label": t.label, "type": "text", "configurable": t.configurable, "default_value": t.default_value, "description": t.description})
  241. elif isinstance(tmpObj.__getattribute__(m), Property.Select):
  242. t = tmpObj.__getattribute__(m)
  243. self.cache[key][name]["properties"].append(
  244. {"name": m, "label": t.label, "type": "select", "configurable": True, "options": t.options, "description": t.description})
  245. elif isinstance(tmpObj.__getattribute__(m), Property.Actor):
  246. t = tmpObj.__getattribute__(m)
  247. self.cache[key][name]["properties"].append({"name": m, "label": t.label, "type": "actor", "configurable": t.configurable, "description": t.description})
  248. elif isinstance(tmpObj.__getattribute__(m), Property.Sensor):
  249. t = tmpObj.__getattribute__(m)
  250. self.cache[key][name]["properties"].append({"name": m, "label": t.label, "type": "sensor", "configurable": t.configurable, "description": t.description})
  251. elif isinstance(tmpObj.__getattribute__(m), Property.Kettle):
  252. t = tmpObj.__getattribute__(m)
  253. self.cache[key][name]["properties"].append({"name": m, "label": t.label, "type": "kettle", "configurable": t.configurable, "description": t.description})
  254. for name, method in cls.__dict__.iteritems():
  255. if hasattr(method, "action"):
  256. label = method.__getattribute__("label")
  257. self.cache[key][cls.__name__]["actions"].append({"method": name, "label": label})
  258. return cls
  259. def actor(self, cls):
  260. return self.__parseProps("actor_types", cls)
  261. def actor2(self, description="", power=True, **options):
  262. def decorator(f):
  263. print f()
  264. print f
  265. print options
  266. print description
  267. return f
  268. return decorator
  269. def sensor(self, cls):
  270. return self.__parseProps("sensor_types", cls)
  271. def controller(self, cls):
  272. return self.__parseProps("controller_types", cls)
  273. def fermentation_controller(self, cls):
  274. return self.__parseProps("fermentation_controller_types", cls)
  275. def get_controller(self, name):
  276. return self.cache["controller_types"].get(name)
  277. def get_fermentation_controller(self, name):
  278. return self.cache["fermentation_controller_types"].get(name)
  279. # Step action
  280. def action(self,label):
  281. def real_decorator(func):
  282. func.action = True
  283. func.label = label
  284. return func
  285. return real_decorator
  286. # step decorator
  287. def step(self, cls):
  288. key = "step_types"
  289. name = cls.__name__
  290. self.cache[key][name] = {"name": name, "class": cls, "properties": [], "actions": []}
  291. tmpObj = cls()
  292. members = [attr for attr in dir(tmpObj) if not callable(getattr(tmpObj, attr)) and not attr.startswith("__")]
  293. for m in members:
  294. if isinstance(tmpObj.__getattribute__(m), StepProperty.Number):
  295. t = tmpObj.__getattribute__(m)
  296. self.cache[key][name]["properties"].append({"name": m, "label": t.label, "type": "number", "configurable": t.configurable, "default_value": t.default_value, "description": t.description})
  297. elif isinstance(tmpObj.__getattribute__(m), StepProperty.Text):
  298. t = tmpObj.__getattribute__(m)
  299. self.cache[key][name]["properties"].append({"name": m, "label": t.label, "type": "text", "configurable": t.configurable, "default_value": t.default_value, "description": t.description})
  300. elif isinstance(tmpObj.__getattribute__(m), StepProperty.Select):
  301. t = tmpObj.__getattribute__(m)
  302. self.cache[key][name]["properties"].append({"name": m, "label": t.label, "type": "select", "configurable": True, "options": t.options, "description": t.description})
  303. elif isinstance(tmpObj.__getattribute__(m), StepProperty.Actor):
  304. t = tmpObj.__getattribute__(m)
  305. self.cache[key][name]["properties"].append({"name": m, "label": t.label, "type": "actor", "configurable": t.configurable, "description": t.description})
  306. elif isinstance(tmpObj.__getattribute__(m), StepProperty.Sensor):
  307. t = tmpObj.__getattribute__(m)
  308. self.cache[key][name]["properties"].append({"name": m, "label": t.label, "type": "sensor", "configurable": t.configurable, "description": t.description})
  309. elif isinstance(tmpObj.__getattribute__(m), StepProperty.Kettle):
  310. t = tmpObj.__getattribute__(m)
  311. self.cache[key][name]["properties"].append({"name": m, "label": t.label, "type": "kettle", "configurable": t.configurable, "description": t.description})
  312. for name, method in cls.__dict__.iteritems():
  313. if hasattr(method, "action"):
  314. label = method.__getattribute__("label")
  315. self.cache[key][cls.__name__]["actions"].append({"method": name, "label": label})
  316. return cls
  317. # Event Bus
  318. def event(self, name, async=False):
  319. def real_decorator(function):
  320. if self.eventbus.get(name) is None:
  321. self.eventbus[name] = []
  322. self.eventbus[name].append({"function": function, "async": async})
  323. def wrapper(*args, **kwargs):
  324. return function(*args, **kwargs)
  325. return wrapper
  326. return real_decorator
  327. def emit_message(self, message):
  328. self.emit_event(name="MESSAGE", message=message)
  329. def emit_event(self, name, **kwargs):
  330. for i in self.eventbus.get(name, []):
  331. if i["async"] is False:
  332. i["function"](**kwargs)
  333. else:
  334. t = self.socketio.start_background_task(target=i["function"], **kwargs)
  335. # initializer decorator
  336. def initalizer(self, order=0):
  337. def real_decorator(function):
  338. self.cache["init"].append({"function": function, "order": order})
  339. def wrapper(*args, **kwargs):
  340. return function(*args, **kwargs)
  341. return wrapper
  342. return real_decorator
  343. def try_catch(self, errorResult="ERROR"):
  344. def real_decorator(function):
  345. def wrapper(*args, **kwargs):
  346. try:
  347. return function(*args, **kwargs)
  348. except:
  349. self.app.logger.error("Exception in function %s. Return default %s" % (function.__name__, errorResult))
  350. return errorResult
  351. return wrapper
  352. return real_decorator
  353. def nocache(self, view):
  354. @wraps(view)
  355. def no_cache(*args, **kwargs):
  356. response = make_response(view(*args, **kwargs))
  357. response.headers['Last-Modified'] = datetime.now()
  358. response.headers[
  359. 'Cache-Control'] = 'no-store, no-cache, must-revalidate, post-check=0, pre-check=0, max-age=0'
  360. response.headers['Pragma'] = 'no-cache'
  361. response.headers['Expires'] = '-1'
  362. return response
  363. return update_wrapper(no_cache, view)
  364. def init_kettle(self, id):
  365. try:
  366. value = self.cache.get("kettle").get(id)
  367. value["state"] = False
  368. except:
  369. self.notify("Kettle Setup Faild", "Please check %s configuration" % value.name, type="danger", timeout=None)
  370. self.app.logger.error("Initializing of Kettle %s failed" % id)
  371. def run_init(self):
  372. '''
  373. call all initialziers after startup
  374. :return:
  375. '''
  376. self.app.logger.info("Invoke Init")
  377. self.cache["init"] = sorted(self.cache["init"], key=lambda k: k['order'])
  378. for i in self.cache.get("init"):
  379. self.app.logger.info("INITIALIZER - METHOD %s PAHT %s: " % (i.get("function").__name__, str(inspect.getmodule(i.get("function")).__file__) ))
  380. i.get("function")(self)
  381. def backgroundtask(self, key, interval, config_parameter=None):
  382. '''
  383. Background Task Decorator
  384. :param key:
  385. :param interval:
  386. :param config_parameter:
  387. :return:
  388. '''
  389. def real_decorator(function):
  390. self.cache["background"].append({"function": function, "key": key, "interval": interval, "config_parameter": config_parameter})
  391. def wrapper(*args, **kwargs):
  392. return function(*args, **kwargs)
  393. return wrapper
  394. return real_decorator
  395. def run_background_processes(self):
  396. '''
  397. call all background task after startup
  398. :return:
  399. '''
  400. self.app.logger.info("Start Background")
  401. def job(interval, method):
  402. while True:
  403. try:
  404. method(self)
  405. except Exception as e:
  406. self.app.logger.error("Exception" + method.__name__ + ": " + str(e))
  407. self.socketio.sleep(interval)
  408. for value in self.cache.get("background"):
  409. t = self.socketio.start_background_task(target=job, interval=value.get("interval"), method=value.get("function"))