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

403 行
13KB

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