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.

499 lines
18KB

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