Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2e756178dc | ||
|
|
96e5919464 | ||
|
|
7c7a1b648e | ||
|
|
f0c97ed220 |
1
.gitignore
vendored
1
.gitignore
vendored
@ -8,3 +8,4 @@ thumbs.db
|
|||||||
#storage/profiles
|
#storage/profiles
|
||||||
#config.py
|
#config.py
|
||||||
.idea/*
|
.idea/*
|
||||||
|
.DS_Store
|
||||||
|
|||||||
@ -107,7 +107,7 @@ sim_R_ho_air = 0.05 # K/W " with internal air circulation
|
|||||||
|
|
||||||
temp_scale = "f" # c = Celsius | f = Fahrenheit - Unit to display
|
temp_scale = "f" # c = Celsius | f = Fahrenheit - Unit to display
|
||||||
time_scale_slope = "h" # s = Seconds | m = Minutes | h = Hours - Slope displayed in temp_scale per time_scale_slope
|
time_scale_slope = "h" # s = Seconds | m = Minutes | h = Hours - Slope displayed in temp_scale per time_scale_slope
|
||||||
time_scale_profile = "h" # s = Seconds | m = Minutes | h = Hours - Enter and view target time in time_scale_profile
|
time_scale_profile = "m" # s = Seconds | m = Minutes | h = Hours - Enter and view target time in time_scale_profile
|
||||||
|
|
||||||
# emergency shutoff the profile if this temp is reached or exceeded.
|
# emergency shutoff the profile if this temp is reached or exceeded.
|
||||||
# This just shuts off the profile. If your SSR is working, your kiln will
|
# This just shuts off the profile. If your SSR is working, your kiln will
|
||||||
|
|||||||
@ -8,7 +8,8 @@ import json
|
|||||||
import bottle
|
import bottle
|
||||||
import gevent
|
import gevent
|
||||||
import geventwebsocket
|
import geventwebsocket
|
||||||
#from bottle import post, get
|
|
||||||
|
# from bottle import post, get
|
||||||
from gevent.pywsgi import WSGIServer
|
from gevent.pywsgi import WSGIServer
|
||||||
from geventwebsocket.handler import WebSocketHandler
|
from geventwebsocket.handler import WebSocketHandler
|
||||||
from geventwebsocket import WebSocketError
|
from geventwebsocket import WebSocketError
|
||||||
@ -16,10 +17,11 @@ from geventwebsocket import WebSocketError
|
|||||||
try:
|
try:
|
||||||
sys.dont_write_bytecode = True
|
sys.dont_write_bytecode = True
|
||||||
import config
|
import config
|
||||||
|
|
||||||
sys.dont_write_bytecode = False
|
sys.dont_write_bytecode = False
|
||||||
except:
|
except:
|
||||||
print ("Could not import config file.")
|
print("Could not import config file.")
|
||||||
print ("Copy config.py.EXAMPLE to config.py and adapt it for your setup.")
|
print("Copy config.py.EXAMPLE to config.py and adapt it for your setup.")
|
||||||
exit(1)
|
exit(1)
|
||||||
|
|
||||||
logging.basicConfig(level=config.log_level, format=config.log_format)
|
logging.basicConfig(level=config.log_level, format=config.log_format)
|
||||||
@ -27,7 +29,7 @@ log = logging.getLogger("kiln-controller")
|
|||||||
log.info("Starting kiln controller")
|
log.info("Starting kiln controller")
|
||||||
|
|
||||||
script_dir = os.path.dirname(os.path.realpath(__file__))
|
script_dir = os.path.dirname(os.path.realpath(__file__))
|
||||||
sys.path.insert(0, script_dir + '/lib/')
|
sys.path.insert(0, script_dir + "/lib/")
|
||||||
profile_path = config.kiln_profiles_directory
|
profile_path = config.kiln_profiles_directory
|
||||||
|
|
||||||
from oven import SimulatedOven, RealOven, Profile
|
from oven import SimulatedOven, RealOven, Profile
|
||||||
@ -45,93 +47,99 @@ ovenWatcher = OvenWatcher(oven)
|
|||||||
# this ovenwatcher is used in the oven class for restarts
|
# this ovenwatcher is used in the oven class for restarts
|
||||||
oven.set_ovenwatcher(ovenWatcher)
|
oven.set_ovenwatcher(ovenWatcher)
|
||||||
|
|
||||||
@app.route('/')
|
|
||||||
def index():
|
|
||||||
return bottle.redirect('/picoreflow/index.html')
|
|
||||||
|
|
||||||
@app.get('/api/stats')
|
@app.route("/")
|
||||||
|
def index():
|
||||||
|
return bottle.redirect("/picoreflow/index.html")
|
||||||
|
|
||||||
|
|
||||||
|
@app.get("/api/stats")
|
||||||
def handle_api():
|
def handle_api():
|
||||||
log.info("/api/stats command received")
|
log.info("/api/stats command received")
|
||||||
if hasattr(oven,'pid'):
|
if hasattr(oven, "pid"):
|
||||||
if hasattr(oven.pid,'pidstats'):
|
if hasattr(oven.pid, "pidstats"):
|
||||||
return json.dumps(oven.pid.pidstats)
|
return json.dumps(oven.pid.pidstats)
|
||||||
|
|
||||||
|
|
||||||
@app.post('/api')
|
@app.post("/api")
|
||||||
def handle_api():
|
def handle_api():
|
||||||
log.info("/api is alive")
|
log.info("/api is alive")
|
||||||
|
|
||||||
|
|
||||||
# run a kiln schedule
|
# run a kiln schedule
|
||||||
if bottle.request.json['cmd'] == 'run':
|
if bottle.request.json["cmd"] == "run":
|
||||||
wanted = bottle.request.json['profile']
|
wanted = bottle.request.json["profile"]
|
||||||
log.info('api requested run of profile = %s' % wanted)
|
log.info("api requested run of profile = %s" % wanted)
|
||||||
|
|
||||||
# start at a specific minute in the schedule
|
# start at a specific minute in the schedule
|
||||||
# for restarting and skipping over early parts of a schedule
|
# for restarting and skipping over early parts of a schedule
|
||||||
startat = 0;
|
startat = 0
|
||||||
if 'startat' in bottle.request.json:
|
if "startat" in bottle.request.json:
|
||||||
startat = bottle.request.json['startat']
|
startat = bottle.request.json["startat"]
|
||||||
|
|
||||||
# get the wanted profile/kiln schedule
|
# get the wanted profile/kiln schedule
|
||||||
profile = find_profile(wanted)
|
profile = find_profile(wanted)
|
||||||
if profile is None:
|
if profile is None:
|
||||||
return { "success" : False, "error" : "profile %s not found" % wanted }
|
return {"success": False, "error": "profile %s not found" % wanted}
|
||||||
|
|
||||||
# FIXME juggling of json should happen in the Profile class
|
# FIXME juggling of json should happen in the Profile class
|
||||||
profile_json = json.dumps(profile)
|
profile_json = json.dumps(profile)
|
||||||
profile = Profile(profile_json)
|
profile = Profile(profile_json)
|
||||||
oven.run_profile(profile,startat=startat)
|
oven.run_profile(profile, startat=startat)
|
||||||
ovenWatcher.record(profile)
|
ovenWatcher.record(profile)
|
||||||
|
|
||||||
if bottle.request.json['cmd'] == 'stop':
|
if bottle.request.json["cmd"] == "stop":
|
||||||
log.info("api stop command received")
|
log.info("api stop command received")
|
||||||
oven.abort_run()
|
oven.abort_run()
|
||||||
|
|
||||||
if bottle.request.json['cmd'] == 'memo':
|
if bottle.request.json["cmd"] == "memo":
|
||||||
log.info("api memo command received")
|
log.info("api memo command received")
|
||||||
memo = bottle.request.json['memo']
|
memo = bottle.request.json["memo"]
|
||||||
log.info("memo=%s" % (memo))
|
log.info("memo=%s" % (memo))
|
||||||
|
|
||||||
# get stats during a run
|
# get stats during a run
|
||||||
if bottle.request.json['cmd'] == 'stats':
|
if bottle.request.json["cmd"] == "stats":
|
||||||
log.info("api stats command received")
|
log.info("api stats command received")
|
||||||
if hasattr(oven,'pid'):
|
if hasattr(oven, "pid"):
|
||||||
if hasattr(oven.pid,'pidstats'):
|
if hasattr(oven.pid, "pidstats"):
|
||||||
return json.dumps(oven.pid.pidstats)
|
return json.dumps(oven.pid.pidstats)
|
||||||
|
|
||||||
return { "success" : True }
|
return {"success": True}
|
||||||
|
|
||||||
|
|
||||||
def find_profile(wanted):
|
def find_profile(wanted):
|
||||||
'''
|
"""
|
||||||
given a wanted profile name, find it and return the parsed
|
given a wanted profile name, find it and return the parsed
|
||||||
json profile object or None.
|
json profile object or None.
|
||||||
'''
|
"""
|
||||||
#load all profiles from disk
|
# load all profiles from disk
|
||||||
profiles = get_profiles()
|
profiles = get_profiles()
|
||||||
json_profiles = json.loads(profiles)
|
json_profiles = json.loads(profiles)
|
||||||
|
|
||||||
# find the wanted profile
|
# find the wanted profile
|
||||||
for profile in json_profiles:
|
for profile in json_profiles:
|
||||||
if profile['name'] == wanted:
|
if profile["name"] == wanted:
|
||||||
return profile
|
return profile
|
||||||
return None
|
return None
|
||||||
|
|
||||||
@app.route('/picoreflow/:filename#.*#')
|
|
||||||
|
@app.route("/picoreflow/:filename#.*#")
|
||||||
def send_static(filename):
|
def send_static(filename):
|
||||||
log.debug("serving %s" % filename)
|
log.debug("serving %s" % filename)
|
||||||
return bottle.static_file(filename, root=os.path.join(os.path.dirname(os.path.realpath(sys.argv[0])), "public"))
|
return bottle.static_file(
|
||||||
|
filename,
|
||||||
|
root=os.path.join(os.path.dirname(os.path.realpath(sys.argv[0])), "public"),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def get_websocket_from_request():
|
def get_websocket_from_request():
|
||||||
env = bottle.request.environ
|
env = bottle.request.environ
|
||||||
wsock = env.get('wsgi.websocket')
|
wsock = env.get("wsgi.websocket")
|
||||||
if not wsock:
|
if not wsock:
|
||||||
abort(400, 'Expected WebSocket request.')
|
abort(400, "Expected WebSocket request.")
|
||||||
return wsock
|
return wsock
|
||||||
|
|
||||||
|
|
||||||
@app.route('/control')
|
@app.route("/control")
|
||||||
def handle_control():
|
def handle_control():
|
||||||
wsock = get_websocket_from_request()
|
wsock = get_websocket_from_request()
|
||||||
log.info("websocket (control) opened")
|
log.info("websocket (control) opened")
|
||||||
@ -143,7 +151,7 @@ def handle_control():
|
|||||||
msgdict = json.loads(message)
|
msgdict = json.loads(message)
|
||||||
if msgdict.get("cmd") == "RUN":
|
if msgdict.get("cmd") == "RUN":
|
||||||
log.info("RUN command received")
|
log.info("RUN command received")
|
||||||
profile_obj = msgdict.get('profile')
|
profile_obj = msgdict.get("profile")
|
||||||
if profile_obj:
|
if profile_obj:
|
||||||
profile_json = json.dumps(profile_obj)
|
profile_json = json.dumps(profile_obj)
|
||||||
profile = Profile(profile_json)
|
profile = Profile(profile_json)
|
||||||
@ -151,15 +159,15 @@ def handle_control():
|
|||||||
ovenWatcher.record(profile)
|
ovenWatcher.record(profile)
|
||||||
elif msgdict.get("cmd") == "SIMULATE":
|
elif msgdict.get("cmd") == "SIMULATE":
|
||||||
log.info("SIMULATE command received")
|
log.info("SIMULATE command received")
|
||||||
#profile_obj = msgdict.get('profile')
|
# profile_obj = msgdict.get('profile')
|
||||||
#if profile_obj:
|
# if profile_obj:
|
||||||
# profile_json = json.dumps(profile_obj)
|
# profile_json = json.dumps(profile_obj)
|
||||||
# profile = Profile(profile_json)
|
# profile = Profile(profile_json)
|
||||||
#simulated_oven = Oven(simulate=True, time_step=0.05)
|
# simulated_oven = Oven(simulate=True, time_step=0.05)
|
||||||
#simulation_watcher = OvenWatcher(simulated_oven)
|
# simulation_watcher = OvenWatcher(simulated_oven)
|
||||||
#simulation_watcher.add_observer(wsock)
|
# simulation_watcher.add_observer(wsock)
|
||||||
#simulated_oven.run_profile(profile)
|
# simulated_oven.run_profile(profile)
|
||||||
#simulation_watcher.record(profile)
|
# simulation_watcher.record(profile)
|
||||||
elif msgdict.get("cmd") == "STOP":
|
elif msgdict.get("cmd") == "STOP":
|
||||||
log.info("Stop command received")
|
log.info("Stop command received")
|
||||||
oven.abort_run()
|
oven.abort_run()
|
||||||
@ -169,7 +177,7 @@ def handle_control():
|
|||||||
log.info("websocket (control) closed")
|
log.info("websocket (control) closed")
|
||||||
|
|
||||||
|
|
||||||
@app.route('/storage')
|
@app.route("/storage")
|
||||||
def handle_storage():
|
def handle_storage():
|
||||||
wsock = get_websocket_from_request()
|
wsock = get_websocket_from_request()
|
||||||
log.info("websocket (storage) opened")
|
log.info("websocket (storage) opened")
|
||||||
@ -190,18 +198,18 @@ def handle_storage():
|
|||||||
wsock.send(get_profiles())
|
wsock.send(get_profiles())
|
||||||
elif msgdict.get("cmd") == "DELETE":
|
elif msgdict.get("cmd") == "DELETE":
|
||||||
log.info("DELETE command received")
|
log.info("DELETE command received")
|
||||||
profile_obj = msgdict.get('profile')
|
profile_obj = msgdict.get("profile")
|
||||||
if delete_profile(profile_obj):
|
if delete_profile(profile_obj):
|
||||||
msgdict["resp"] = "OK"
|
msgdict["resp"] = "OK"
|
||||||
wsock.send(json.dumps(msgdict))
|
wsock.send(json.dumps(msgdict))
|
||||||
#wsock.send(get_profiles())
|
# wsock.send(get_profiles())
|
||||||
elif msgdict.get("cmd") == "PUT":
|
elif msgdict.get("cmd") == "PUT":
|
||||||
log.info("PUT command received")
|
log.info("PUT command received")
|
||||||
profile_obj = msgdict.get('profile')
|
profile_obj = msgdict.get("profile")
|
||||||
#force = msgdict.get('force', False)
|
# force = msgdict.get('force', False)
|
||||||
force = True
|
force = True
|
||||||
if profile_obj:
|
if profile_obj:
|
||||||
#del msgdict["cmd"]
|
# del msgdict["cmd"]
|
||||||
if save_profile(profile_obj, force):
|
if save_profile(profile_obj, force):
|
||||||
msgdict["resp"] = "OK"
|
msgdict["resp"] = "OK"
|
||||||
else:
|
else:
|
||||||
@ -215,7 +223,7 @@ def handle_storage():
|
|||||||
log.info("websocket (storage) closed")
|
log.info("websocket (storage) closed")
|
||||||
|
|
||||||
|
|
||||||
@app.route('/config')
|
@app.route("/config")
|
||||||
def handle_config():
|
def handle_config():
|
||||||
wsock = get_websocket_from_request()
|
wsock = get_websocket_from_request()
|
||||||
log.info("websocket (config) opened")
|
log.info("websocket (config) opened")
|
||||||
@ -228,7 +236,7 @@ def handle_config():
|
|||||||
log.info("websocket (config) closed")
|
log.info("websocket (config) closed")
|
||||||
|
|
||||||
|
|
||||||
@app.route('/status')
|
@app.route("/status")
|
||||||
def handle_status():
|
def handle_status():
|
||||||
wsock = get_websocket_from_request()
|
wsock = get_websocket_from_request()
|
||||||
ovenWatcher.add_observer(wsock)
|
ovenWatcher.add_observer(wsock)
|
||||||
@ -245,31 +253,39 @@ def handle_status():
|
|||||||
def get_profiles():
|
def get_profiles():
|
||||||
try:
|
try:
|
||||||
profile_files = os.listdir(profile_path)
|
profile_files = os.listdir(profile_path)
|
||||||
|
profile_files.sort()
|
||||||
except:
|
except:
|
||||||
profile_files = []
|
profile_files = []
|
||||||
profiles = []
|
profiles = []
|
||||||
for filename in profile_files:
|
for filename in profile_files:
|
||||||
with open(os.path.join(profile_path, filename), 'r') as f:
|
if filename.startswith("._"):
|
||||||
profiles.append(json.load(f))
|
pass
|
||||||
|
else:
|
||||||
|
if filename.endswith(".json"):
|
||||||
|
with open(os.path.join(profile_path, filename), "r") as f:
|
||||||
|
profiles.append(json.load(f))
|
||||||
|
else:
|
||||||
|
pass
|
||||||
return json.dumps(profiles)
|
return json.dumps(profiles)
|
||||||
|
|
||||||
|
|
||||||
def save_profile(profile, force=False):
|
def save_profile(profile, force=False):
|
||||||
profile_json = json.dumps(profile)
|
profile_json = json.dumps(profile)
|
||||||
filename = profile['name']+".json"
|
filename = profile["name"] + ".json"
|
||||||
filepath = os.path.join(profile_path, filename)
|
filepath = os.path.join(profile_path, filename)
|
||||||
if not force and os.path.exists(filepath):
|
if not force and os.path.exists(filepath):
|
||||||
log.error("Could not write, %s already exists" % filepath)
|
log.error("Could not write, %s already exists" % filepath)
|
||||||
return False
|
return False
|
||||||
with open(filepath, 'w+') as f:
|
with open(filepath, "w+") as f:
|
||||||
f.write(profile_json)
|
f.write(profile_json)
|
||||||
f.close()
|
f.close()
|
||||||
log.info("Wrote %s" % filepath)
|
log.info("Wrote %s" % filepath)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
def delete_profile(profile):
|
def delete_profile(profile):
|
||||||
profile_json = json.dumps(profile)
|
profile_json = json.dumps(profile)
|
||||||
filename = profile['name']+".json"
|
filename = profile["name"] + ".json"
|
||||||
filepath = os.path.join(profile_path, filename)
|
filepath = os.path.join(profile_path, filename)
|
||||||
os.remove(filepath)
|
os.remove(filepath)
|
||||||
log.info("Deleted %s" % filepath)
|
log.info("Deleted %s" % filepath)
|
||||||
@ -277,11 +293,15 @@ def delete_profile(profile):
|
|||||||
|
|
||||||
|
|
||||||
def get_config():
|
def get_config():
|
||||||
return json.dumps({"temp_scale": config.temp_scale,
|
return json.dumps(
|
||||||
"time_scale_slope": config.time_scale_slope,
|
{
|
||||||
"time_scale_profile": config.time_scale_profile,
|
"temp_scale": config.temp_scale,
|
||||||
"kwh_rate": config.kwh_rate,
|
"time_scale_slope": config.time_scale_slope,
|
||||||
"currency_type": config.currency_type})
|
"time_scale_profile": config.time_scale_profile,
|
||||||
|
"kwh_rate": config.kwh_rate,
|
||||||
|
"currency_type": config.currency_type,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
@ -289,8 +309,7 @@ def main():
|
|||||||
port = config.listening_port
|
port = config.listening_port
|
||||||
log.info("listening on %s:%d" % (ip, port))
|
log.info("listening on %s:%d" % (ip, port))
|
||||||
|
|
||||||
server = WSGIServer((ip, port), app,
|
server = WSGIServer((ip, port), app, handler_class=WebSocketHandler)
|
||||||
handler_class=WebSocketHandler)
|
|
||||||
server.serve_forever()
|
server.serve_forever()
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -368,14 +368,14 @@ function saveProfile()
|
|||||||
}
|
}
|
||||||
|
|
||||||
function get_tick_size() {
|
function get_tick_size() {
|
||||||
switch(time_scale_profile){
|
//switch(time_scale_profile){
|
||||||
case "s":
|
// case "s":
|
||||||
return 1;
|
// return 1;
|
||||||
case "m":
|
// case "m":
|
||||||
return 60;
|
// return 60;
|
||||||
case "h":
|
// case "h":
|
||||||
return 3600;
|
// return 3600;
|
||||||
}
|
// }
|
||||||
return 3600;
|
return 3600;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -473,17 +473,17 @@ $(document).ready(function()
|
|||||||
{
|
{
|
||||||
console.log("Status Socket has been opened");
|
console.log("Status Socket has been opened");
|
||||||
|
|
||||||
$.bootstrapGrowl("<span class=\"glyphicon glyphicon-exclamation-sign\"></span>Getting data from server",
|
// $.bootstrapGrowl("<span class=\"glyphicon glyphicon-exclamation-sign\"></span>Getting data from server",
|
||||||
{
|
// {
|
||||||
ele: 'body', // which element to append to
|
// ele: 'body', // which element to append to
|
||||||
type: 'success', // (null, 'info', 'error', 'success')
|
// type: 'success', // (null, 'info', 'error', 'success')
|
||||||
offset: {from: 'top', amount: 250}, // 'top', or 'bottom'
|
// offset: {from: 'top', amount: 250}, // 'top', or 'bottom'
|
||||||
align: 'center', // ('left', 'right', or 'center')
|
// align: 'center', // ('left', 'right', or 'center')
|
||||||
width: 385, // (integer, or 'auto')
|
// width: 385, // (integer, or 'auto')
|
||||||
delay: 2500,
|
// delay: 2500,
|
||||||
allow_dismiss: true,
|
// allow_dismiss: true,
|
||||||
stackup_spacing: 10 // spacing between consecutively stacked growls.
|
// stackup_spacing: 10 // spacing between consecutively stacked growls.
|
||||||
});
|
// });
|
||||||
};
|
};
|
||||||
|
|
||||||
ws_status.onclose = function()
|
ws_status.onclose = function()
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user