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

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