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.

397 lines
13KB

  1. import json
  2. import logging
  3. import os
  4. import sqlite3
  5. import uuid
  6. from datetime import datetime
  7. from functools import wraps, update_wrapper
  8. from importlib import import_module
  9. from time import localtime, strftime
  10. import time
  11. from flask import Flask, redirect, json, g, make_response
  12. from flask_socketio import SocketIO
  13. from baseapi import *
  14. from db import DBModel
  15. from modules.core.basetypes import Sensor, Actor
  16. from modules.database.dbmodel import Kettle
  17. class ComplexEncoder(json.JSONEncoder):
  18. def default(self, obj):
  19. try:
  20. if isinstance(obj, DBModel):
  21. return obj.__dict__
  22. elif isinstance(obj, Actor):
  23. return {"state": obj.value}
  24. elif isinstance(obj, Sensor):
  25. return {"value": obj.value, "unit": obj.unit}
  26. elif hasattr(obj, "callback"):
  27. return obj()
  28. else:
  29. return None
  30. return None
  31. except TypeError as e:
  32. pass
  33. return None
  34. class Addon(object):
  35. def __init__(self, cbpi):
  36. self.step = StepAPI(cbpi)
  37. self.actor = ActorAPI(cbpi)
  38. self.sensor = SensorAPI(cbpi)
  39. self.kettle = KettleAPI(cbpi)
  40. self.fermenter = FermenterAPI(cbpi)
  41. self.core = CoreAPI(cbpi)
  42. def init(self):
  43. self.core.init()
  44. self.step.init()
  45. self.actor.init()
  46. self.sensor.init()
  47. # self.kettle.init()
  48. # self.fermenter.init()
  49. class ActorCore(object):
  50. key = "actor_types"
  51. def __init__(self, cbpi):
  52. self.cbpi = cbpi
  53. self.cbpi.cache["actors"] = {}
  54. self.cbpi.cache[self.key] = {}
  55. def init(self):
  56. for key, value in self.cbpi.cache["actors"].iteritems():
  57. self.init_one(key)
  58. def init_one(self, id):
  59. try:
  60. actor = self.cbpi.cache["actors"][id]
  61. clazz = self.cbpi.cache[self.key].get(actor.type)["class"]
  62. cfg = actor.config.copy()
  63. cfg.update(dict(cbpi=self.cbpi, id=id))
  64. self.cbpi.cache["actors"][id].instance = clazz(**cfg)
  65. actor.state = 0
  66. actor.power = 100
  67. self.cbpi.emit("INIT_ACTOR", id=id)
  68. except Exception as e:
  69. print e
  70. self.cbpi._app.logger.error(e)
  71. def stop_one(self, id):
  72. self.cbpi.cache["actors"][id]["instance"].stop()
  73. self.cbpi.emit("STOP_ACTOR", id=id)
  74. def on(self, id, power=100):
  75. try:
  76. actor = self.cbpi.cache["actors"].get(int(id))
  77. actor.instance.on()
  78. actor.state = 1
  79. actor.power = power
  80. self.cbpi.ws_emit("SWITCH_ACTOR", actor)
  81. self.cbpi.emit("SWITCH_ACTOR_ON", id=id, power=power)
  82. return True
  83. except Exception as e:
  84. print e
  85. return False
  86. def off(self, id):
  87. try:
  88. actor = self.cbpi.cache["actors"].get(int(id))
  89. actor.instance.off()
  90. actor.state = 0
  91. self.cbpi.ws_emit("SWITCH_ACTOR", actor)
  92. self.cbpi.emit("SWITCH_ACTOR_OFF", id=id)
  93. return True
  94. except Exception as e:
  95. print e
  96. return False
  97. def toggle(self, id):
  98. if self.cbpi.cache.get("actors").get(id).state == 0:
  99. self.on(id)
  100. else:
  101. self.off(id)
  102. def power(self, id, power):
  103. try:
  104. actor = self.cbpi.cache["actors"].get(int(id))
  105. actor.instance.power(power)
  106. actor.power = power
  107. self.cbpi.ws_emit("SWITCH_ACTOR", actor)
  108. self.cbpi.emit("SWITCH_ACTOR_POWER_CHANGE", id=id, power=power)
  109. return True
  110. except Exception as e:
  111. print e
  112. return False
  113. def action(self, id, method):
  114. self.cbpi.cache.get("actors").get(id).instance.__getattribute__(method)()
  115. def toggle_timeout(self, id, seconds):
  116. def toggle( id, seconds):
  117. self.cbpi.cache.get("actors").get(int(id)).timer = int(time.time()) + int(seconds)
  118. self.toggle(int(id))
  119. self.cbpi.sleep(seconds)
  120. self.cbpi.cache.get("actors").get(int(id)).timer = None
  121. self.toggle(int(id))
  122. job = self.cbpi._socketio.start_background_task(target=toggle, id=id, seconds=seconds)
  123. def get_state(self, actor_id):
  124. pass
  125. class SensorCore(object):
  126. key = "sensor_types"
  127. def __init__(self, cbpi):
  128. self.cbpi = cbpi
  129. self.cbpi.cache["sensors"] = {}
  130. self.cbpi.cache["sensor_instances"] = {}
  131. self.cbpi.cache["sensor_types"] = {}
  132. def init(self):
  133. for key, value in self.cbpi.cache["sensors"].iteritems():
  134. self.init_one(key)
  135. def init_one(self, id):
  136. try:
  137. sensor = self.cbpi.cache["sensors"][id]
  138. clazz = self.cbpi.cache[self.key].get(sensor.type)["class"]
  139. cfg = sensor.config.copy()
  140. cfg.update(dict(cbpi=self.cbpi, id=id))
  141. self.cbpi.cache["sensors"][id].instance = clazz(**cfg)
  142. self.cbpi.cache["sensors"][id].instance.init()
  143. self.cbpi.emit("INIT_SENSOR", id=id)
  144. def job(obj):
  145. obj.execute()
  146. t = self.cbpi._socketio.start_background_task(target=job, obj=self.cbpi.cache["sensors"][id].instance)
  147. self.cbpi.emit("INIT_SENSOR", id=id)
  148. except Exception as e:
  149. print "ERROR"
  150. self.cbpi._app.logger.error(e)
  151. def stop_one(self, id):
  152. self.cbpi.cache["sensors"][id].instance.stop()
  153. self.cbpi.emit("STOP_SENSOR", id=id)
  154. def get_value(self, sensorid):
  155. try:
  156. return self.cbpi.cache["sensors"][sensorid].instance.value
  157. except:
  158. return None
  159. def get_state(self, actor_id):
  160. pass
  161. def write_log(self, id, value, prefix="sensor"):
  162. filename = "./logs/%s_%s.log" % (prefix, str(id))
  163. formatted_time = strftime("%Y-%m-%d %H:%M:%S", localtime())
  164. msg = str(formatted_time) + "," + str(value) + "\n"
  165. with open(filename, "a") as file:
  166. file.write(msg)
  167. def action(self, id, method):
  168. self.cbpi.cache.get("sensors").get(id).instance.__getattribute__(method)()
  169. class BrewingCore(object):
  170. def __init__(self, cbpi):
  171. self.cbpi = cbpi
  172. self.cbpi.cache["step_types"] = {}
  173. self.cbpi.cache["controller_types"] = {}
  174. def log_action(self, text):
  175. filename = "./logs/action.log"
  176. formatted_time = strftime("%Y-%m-%d %H:%M:%S", localtime())
  177. with open(filename, "a") as file:
  178. text = text.encode("utf-8")
  179. file.write("%s,%s\n" % (formatted_time, text))
  180. def get_controller(self, name):
  181. return self.cbpi.cache["controller_types"].get(name)
  182. def set_target_temp(self, id, temp):
  183. self.cbpi.cache.get("kettle")[id].target_temp = float(temp)
  184. Kettle.update(**self.cbpi.cache.get("kettle")[id].__dict__)
  185. self.cbpi.ws_emit("UPDATE_KETTLE_TARGET_TEMP", {"id": id, "target_temp": temp})
  186. self.cbpi.emit("SET_KETTLE_TARGET_TEMP", id=id, temp=temp)
  187. def toggle_automatic(self, id):
  188. kettle = self.cbpi.cache.get("kettle")[id]
  189. if kettle.state is False:
  190. # Start controller
  191. if kettle.logic is not None:
  192. cfg = kettle.config.copy()
  193. cfg.update(dict(api=cbpi, kettle_id=kettle.id, heater=kettle.heater, sensor=kettle.sensor))
  194. instance = self.get_controller(kettle.logic).get("class")(**cfg)
  195. instance.init()
  196. kettle.instance = instance
  197. def run(instance):
  198. instance.run()
  199. t = self.cbpi._socketio.start_background_task(target=run, instance=instance)
  200. kettle.state = not kettle.state
  201. self.cbpi.ws_emit("UPDATE_KETTLE", cbpi.cache.get("kettle").get(id))
  202. self.cbpi.emit("KETTLE_CONTROLLER_STARTED", id=id)
  203. else:
  204. # Stop controller
  205. kettle.instance.stop()
  206. kettle.state = not kettle.state
  207. self.cbpi.ws_emit("UPDATE_KETTLE", cbpi.cache.get("kettle").get(id))
  208. self.cbpi.emit("KETTLE_CONTROLLER_STOPPED", id=id)
  209. class FermentationCore(object):
  210. def __init__(self, cbpi):
  211. self.cbpi = cbpi
  212. self.cbpi.cache["fermenter"] = {}
  213. self.cbpi.cache["fermentation_controller_types"] = {}
  214. def get_controller(self, name):
  215. return self.cbpi.cache["fermentation_controller_types"].get(name)
  216. class CraftBeerPI(object):
  217. cache = {}
  218. eventbus = {}
  219. def __init__(self):
  220. FORMAT = '%(asctime)-15s - %(levelname)s - %(message)s'
  221. logging.basicConfig(filename='./logs/app.log', level=logging.INFO, format=FORMAT)
  222. self.cache["messages"] = []
  223. self.cache["version"] = "3.1"
  224. self.modules = {}
  225. self.addon = Addon(self)
  226. self.actor = ActorCore(self)
  227. self.sensor = SensorCore(self)
  228. self.brewing = BrewingCore(self)
  229. self.fermentation = FermentationCore(self)
  230. self._app = Flask(__name__)
  231. self._app.secret_key = 'Cr4ftB33rP1'
  232. self._app.json_encoder = ComplexEncoder
  233. self._socketio = SocketIO(self._app, json=json, logging=False)
  234. @self._app.route('/')
  235. def index():
  236. return redirect('ui')
  237. def run(self):
  238. self.__init_db()
  239. self.loadPlugins()
  240. self.addon.init()
  241. self.sensor.init()
  242. self.actor.init()
  243. self.beep()
  244. try:
  245. port = int(cbpi.get_config_parameter('port', '5000'))
  246. except ValueError:
  247. port = 5000
  248. print port
  249. self._socketio.run(self._app, host='0.0.0.0', port=port)
  250. def beep(self):
  251. self.buzzer.beep()
  252. def sleep(self, seconds):
  253. self._socketio.sleep(seconds)
  254. def notify(self, headline, message, type="success", timeout=5000):
  255. msg = {"id": str(uuid.uuid1()), "type": type, "headline": headline, "message": message, "timeout": timeout}
  256. self.ws_emit("NOTIFY", msg)
  257. def ws_emit(self, key, data):
  258. self._socketio.emit(key, data, namespace='/brew')
  259. def __init_db(self, ):
  260. with self._app.app_context():
  261. db = self.get_db()
  262. try:
  263. with self._app.open_resource('../../config/schema.sql', mode='r') as f:
  264. db.cursor().executescript(f.read())
  265. db.commit()
  266. except Exception as e:
  267. pass
  268. def nocache(self, view):
  269. @wraps(view)
  270. def no_cache(*args, **kwargs):
  271. response = make_response(view(*args, **kwargs))
  272. response.headers['Last-Modified'] = datetime.now()
  273. response.headers[
  274. 'Cache-Control'] = 'no-store, no-cache, must-revalidate, post-check=0, pre-check=0, max-age=0'
  275. response.headers['Pragma'] = 'no-cache'
  276. response.headers['Expires'] = '-1'
  277. return response
  278. return update_wrapper(no_cache, view)
  279. def get_db(self):
  280. db = getattr(g, '_database', None)
  281. if db is None:
  282. def dict_factory(cursor, row):
  283. d = {}
  284. for idx, col in enumerate(cursor.description):
  285. d[col[0]] = row[idx]
  286. return d
  287. db = g._database = sqlite3.connect('craftbeerpi.db')
  288. db.row_factory = dict_factory
  289. return db
  290. def add_cache_callback(self, key, method):
  291. method.callback = True
  292. self.cache[key] = method
  293. def get_config_parameter(self, key, default):
  294. cfg = self.cache["config"].get(key)
  295. if cfg is None:
  296. return default
  297. else:
  298. return cfg.value
  299. def emit(self, key, **kwargs):
  300. if self.eventbus.get(key) is not None:
  301. for value in self.eventbus[key]:
  302. if value["async"] is False:
  303. value["function"](**kwargs)
  304. else:
  305. t = self.cbpi._socketio.start_background_task(target=value["function"], **kwargs)
  306. def loadPlugins(self):
  307. for filename in os.listdir("./modules/plugins"):
  308. if os.path.isdir("./modules/plugins/" + filename) is False:
  309. continue
  310. try:
  311. self.modules[filename] = import_module("modules.plugins.%s" % (filename))
  312. except Exception as e:
  313. self.notify("Failed to load plugin %s " % filename, str(e), type="danger", timeout=None)
  314. cbpi = CraftBeerPI()
  315. addon = cbpi.addon