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.

462 lines
15KB

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