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

406 行
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 ActorCore(object):
  35. key = "actor_types"
  36. def __init__(self, cbpi):
  37. self.cbpi = cbpi
  38. self.cbpi.cache["actors"] = {}
  39. self.cbpi.cache[self.key] = {}
  40. def init(self):
  41. for key, value in self.cbpi.cache["actors"].iteritems():
  42. self.init_one(key)
  43. def init_one(self, id):
  44. try:
  45. actor = self.cbpi.cache["actors"][id]
  46. clazz = self.cbpi.cache[self.key].get(actor.type)["class"]
  47. cfg = actor.config.copy()
  48. cfg.update(dict(cbpi=self.cbpi, id=id))
  49. self.cbpi.cache["actors"][id].instance = clazz(**cfg)
  50. actor.state = 0
  51. actor.power = 100
  52. self.cbpi.emit("INIT_ACTOR", id=id)
  53. except Exception as e:
  54. self.cbpi.web.logger.error(e)
  55. def stop_one(self, id):
  56. self.cbpi.cache["actors"][id]["instance"].stop()
  57. self.cbpi.emit("STOP_ACTOR", id=id)
  58. def on(self, id, power=100):
  59. try:
  60. actor = self.cbpi.cache["actors"].get(int(id))
  61. actor.instance.on()
  62. actor.state = 1
  63. actor.power = power
  64. self.cbpi.ws_emit("SWITCH_ACTOR", actor)
  65. self.cbpi.emit("SWITCH_ACTOR_ON", id=id, power=power)
  66. return True
  67. except Exception as e:
  68. self.cbpi.logger.error(e)
  69. return False
  70. def off(self, id):
  71. try:
  72. actor = self.cbpi.cache["actors"].get(int(id))
  73. actor.instance.off()
  74. actor.state = 0
  75. self.cbpi.ws_emit("SWITCH_ACTOR", actor)
  76. self.cbpi.emit("SWITCH_ACTOR_OFF", id=id)
  77. return True
  78. except Exception as e:
  79. self.cbpi.logger.error(e)
  80. return False
  81. def toggle(self, id):
  82. if self.cbpi.cache.get("actors").get(id).state == 0:
  83. self.on(id)
  84. else:
  85. self.off(id)
  86. def power(self, id, power):
  87. try:
  88. actor = self.cbpi.cache["actors"].get(int(id))
  89. actor.instance.power(power)
  90. actor.power = power
  91. self.cbpi.ws_emit("SWITCH_ACTOR", actor)
  92. self.cbpi.emit("SWITCH_ACTOR_POWER_CHANGE", id=id, power=power)
  93. return True
  94. except Exception as e:
  95. self.cbpi.logger.error(e)
  96. return False
  97. def action(self, id, method, **data):
  98. self.cbpi.cache.get("actors").get(id).instance.__getattribute__(method)(**data)
  99. def toggle_timeout(self, id, seconds):
  100. def toggle( id, seconds):
  101. self.cbpi.cache.get("actors").get(int(id)).timer = int(time.time()) + int(seconds)
  102. self.toggle(int(id))
  103. self.cbpi.sleep(seconds)
  104. self.cbpi.cache.get("actors").get(int(id)).timer = None
  105. self.toggle(int(id))
  106. job = self.cbpi._socketio.start_background_task(target=toggle, id=id, seconds=seconds)
  107. def get_state(self, actor_id):
  108. pass
  109. class SensorCore(object):
  110. key = "sensor_types"
  111. def __init__(self, cbpi):
  112. self.cbpi = cbpi
  113. self.cbpi.cache["sensors"] = {}
  114. self.cbpi.cache["sensor_instances"] = {}
  115. self.cbpi.cache["sensor_types"] = {}
  116. def init(self):
  117. for key, value in self.cbpi.cache["sensors"].iteritems():
  118. self.init_one(key)
  119. def init_one(self, id):
  120. try:
  121. sensor = self.cbpi.cache["sensors"][id]
  122. clazz = self.cbpi.cache[self.key].get(sensor.type)["class"]
  123. cfg = sensor.config.copy()
  124. cfg.update(dict(cbpi=self.cbpi, id=id))
  125. self.cbpi.cache["sensors"][id].instance = clazz(**cfg)
  126. self.cbpi.cache["sensors"][id].instance.init()
  127. self.cbpi.emit("INIT_SENSOR", id=id)
  128. def job(obj):
  129. obj.execute()
  130. t = self.cbpi._socketio.start_background_task(target=job, obj=self.cbpi.cache["sensors"][id].instance)
  131. self.cbpi.emit("INIT_SENSOR", id=id)
  132. except Exception as e:
  133. print "ERROR"
  134. self.cbpi.web.logger.error(e)
  135. def stop_one(self, id):
  136. self.cbpi.cache["sensors"][id].instance.stop()
  137. self.cbpi.emit("STOP_SENSOR", id=id)
  138. def get_value(self, sensorid):
  139. try:
  140. return self.cbpi.cache["sensors"][sensorid].instance.value
  141. except:
  142. return None
  143. def get_state(self, actor_id):
  144. pass
  145. def write_log(self, id, value, prefix="sensor"):
  146. filename = "./logs/%s_%s.log" % (prefix, str(id))
  147. formatted_time = strftime("%Y-%m-%d %H:%M:%S", localtime())
  148. msg = str(formatted_time) + "," + str(value) + "\n"
  149. with open(filename, "a") as file:
  150. file.write(msg)
  151. def action(self, id, method, **data):
  152. self.cbpi.cache.get("sensors").get(id).instance.__getattribute__(method)(**data)
  153. class BrewingCore(object):
  154. def __init__(self, cbpi):
  155. self.cbpi = cbpi
  156. self.cbpi.cache["step_types"] = {}
  157. self.cbpi.cache["controller_types"] = {}
  158. def log_action(self, text):
  159. filename = "./logs/action.log"
  160. formatted_time = strftime("%Y-%m-%d %H:%M:%S", localtime())
  161. with open(filename, "a") as file:
  162. text = text.encode("utf-8")
  163. file.write("%s,%s\n" % (formatted_time, text))
  164. def get_controller(self, name):
  165. return self.cbpi.cache["controller_types"].get(name)
  166. def set_target_temp(self, id, temp):
  167. self.cbpi.cache.get("kettle")[id].target_temp = float(temp)
  168. Kettle.update(**self.cbpi.cache.get("kettle")[id].__dict__)
  169. self.cbpi.ws_emit("UPDATE_KETTLE_TARGET_TEMP", {"id": id, "target_temp": temp})
  170. self.cbpi.emit("SET_KETTLE_TARGET_TEMP", id=id, temp=temp)
  171. def toggle_automatic(self, id):
  172. kettle = self.cbpi.cache.get("kettle")[id]
  173. if kettle.state is False:
  174. # Start controller
  175. if kettle.logic is not None:
  176. cfg = kettle.config.copy()
  177. cfg.update(dict(api=self.cbpi, kettle_id=kettle.id, heater=kettle.heater, sensor=kettle.sensor))
  178. instance = self.get_controller(kettle.logic).get("class")(**cfg)
  179. instance.init()
  180. kettle.instance = instance
  181. def run(instance):
  182. instance.run()
  183. t = self.cbpi._socketio.start_background_task(target=run, instance=instance)
  184. kettle.state = not kettle.state
  185. self.cbpi.ws_emit("UPDATE_KETTLE", self.cbpi.cache.get("kettle").get(id))
  186. self.cbpi.emit("KETTLE_CONTROLLER_STARTED", id=id)
  187. else:
  188. # Stop controller
  189. kettle.instance.stop()
  190. kettle.state = not kettle.state
  191. self.cbpi.ws_emit("UPDATE_KETTLE", self.cbpi.cache.get("kettle").get(id))
  192. self.cbpi.emit("KETTLE_CONTROLLER_STOPPED", id=id)
  193. class FermentationCore(object):
  194. def __init__(self, cbpi):
  195. self.cbpi = cbpi
  196. self.cbpi.cache["fermenter"] = {}
  197. self.cbpi.cache["fermentation_controller_types"] = {}
  198. def get_controller(self, name):
  199. return self.cbpi.cache["fermentation_controller_types"].get(name)
  200. class Logger(object):
  201. def __init__(self, cbpi):
  202. self.cbpi = cbpi
  203. def error(self, msg, *args, **kwargs):
  204. self.cbpi.web.logger.error(msg, *args, **kwargs)
  205. def info(self, msg, *args, **kwargs):
  206. self.cbpi.web.logger.info(msg, *args, **kwargs)
  207. def debug(self, msg, *args, **kwargs):
  208. self.cbpi.web.logger.debug(msg, *args, **kwargs)
  209. def warning(self, msg, *args, **kwargs):
  210. self.cbpi.web.logger.warning(msg, *args, **kwargs)
  211. class CraftBeerPI(object):
  212. cache = {}
  213. eventbus = {}
  214. def __init__(self):
  215. self.cache["messages"] = []
  216. self.cache["version"] = "3.1"
  217. FORMAT = '%(asctime)-15s - %(levelname)s - %(message)s'
  218. logging.basicConfig(filename='./logs/app.log', level=logging.INFO, format=FORMAT)
  219. logging.getLogger('socketio').setLevel(logging.ERROR)
  220. logging.getLogger('engineio').setLevel(logging.ERROR)
  221. self.web = Flask(__name__)
  222. self.logger = Logger(self)
  223. self.logger.info("###Startup CraftBeerPi %s ###" % self.cache.get("version"))
  224. self.web.secret_key = 'Cr4ftB33rP1'
  225. self.web.json_encoder = ComplexEncoder
  226. self._socketio = SocketIO(self.web, json=json, logging=False)
  227. self.modules = {}
  228. self.addon = Addon(self)
  229. self.actor = ActorCore(self)
  230. self.sensor = SensorCore(self)
  231. self.brewing = BrewingCore(self)
  232. self.fermentation = FermentationCore(self)
  233. @self.web.route('/')
  234. def index():
  235. return redirect('ui')
  236. def run(self):
  237. self.__init_db()
  238. self.loadPlugins()
  239. self.addon.init()
  240. self.sensor.init()
  241. self.actor.init()
  242. self.beep()
  243. try:
  244. port = int(self.get_config_parameter('port', '5000'))
  245. except ValueError:
  246. port = 5000
  247. self._socketio.run(self.web, host='0.0.0.0', port=port)
  248. def beep(self):
  249. self.buzzer.beep()
  250. def sleep(self, seconds):
  251. self._socketio.sleep(seconds)
  252. def start_background_task(self, target, *args, **kwargs):
  253. self._socketio.start_background_task(target, *args, **kwargs)
  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.web.app_context():
  261. db = self.get_db()
  262. try:
  263. with self.web.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=None):
  294. cfg = self.cache["config"].get(key)
  295. if cfg is None:
  296. return default
  297. else:
  298. return cfg.value
  299. def set_config_parameter(self, name, value):
  300. from modules.config import Config
  301. with self.web.app_context():
  302. update_data = {"name": name, "value": value}
  303. self.cache.get("config")[name].__dict__.update(**update_data)
  304. c = Config.update(**update_data)
  305. self.ws_emit("UPDATE_CONFIG", c)
  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. if os.path.isdir("./modules/plugins/" + filename) is False:
  316. continue
  317. try:
  318. self.modules[filename] = import_module("modules.plugins.%s" % (filename))
  319. except Exception as e:
  320. self.notify("Failed to load plugin %s " % filename, str(e), type="danger", timeout=None)