Compare commits

...

4 Commits
main ... master

Author SHA1 Message Date
Jason Bruce
2e756178dc
Merge pull request #168 from SamSkjord/master
profile filetype checks
2024-07-02 13:56:42 -04:00
Sam
96e5919464 profile filetype checks
Added my Controller as a samba share so I could edit profile files remotely, as OSX is very aggressive about shoving ._ files where you don't want them,
I added a check for that and another check to make sure to only try and open files ending in .json
2024-01-23 14:23:57 +00:00
jbruce
7c7a1b648e remove the loading message 2022-12-16 14:21:37 -05:00
jbruce
f0c97ed220 make hours the tick size on the live graph always 2022-12-16 09:49:19 -05:00
4 changed files with 105 additions and 85 deletions

1
.gitignore vendored
View File

@ -8,3 +8,4 @@ thumbs.db
#storage/profiles
#config.py
.idea/*
.DS_Store

View File

@ -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
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.
# This just shuts off the profile. If your SSR is working, your kiln will

View File

@ -8,7 +8,8 @@ import json
import bottle
import gevent
import geventwebsocket
#from bottle import post, get
# from bottle import post, get
from gevent.pywsgi import WSGIServer
from geventwebsocket.handler import WebSocketHandler
from geventwebsocket import WebSocketError
@ -16,10 +17,11 @@ from geventwebsocket import WebSocketError
try:
sys.dont_write_bytecode = True
import config
sys.dont_write_bytecode = False
except:
print ("Could not import config file.")
print ("Copy config.py.EXAMPLE to config.py and adapt it for your setup.")
print("Could not import config file.")
print("Copy config.py.EXAMPLE to config.py and adapt it for your setup.")
exit(1)
logging.basicConfig(level=config.log_level, format=config.log_format)
@ -27,7 +29,7 @@ log = logging.getLogger("kiln-controller")
log.info("Starting kiln controller")
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
from oven import SimulatedOven, RealOven, Profile
@ -45,93 +47,99 @@ ovenWatcher = OvenWatcher(oven)
# this ovenwatcher is used in the oven class for restarts
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():
log.info("/api/stats command received")
if hasattr(oven,'pid'):
if hasattr(oven.pid,'pidstats'):
if hasattr(oven, "pid"):
if hasattr(oven.pid, "pidstats"):
return json.dumps(oven.pid.pidstats)
@app.post('/api')
@app.post("/api")
def handle_api():
log.info("/api is alive")
# run a kiln schedule
if bottle.request.json['cmd'] == 'run':
wanted = bottle.request.json['profile']
log.info('api requested run of profile = %s' % wanted)
if bottle.request.json["cmd"] == "run":
wanted = bottle.request.json["profile"]
log.info("api requested run of profile = %s" % wanted)
# start at a specific minute in the schedule
# for restarting and skipping over early parts of a schedule
startat = 0;
if 'startat' in bottle.request.json:
startat = bottle.request.json['startat']
startat = 0
if "startat" in bottle.request.json:
startat = bottle.request.json["startat"]
# get the wanted profile/kiln schedule
profile = find_profile(wanted)
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
profile_json = json.dumps(profile)
profile = Profile(profile_json)
oven.run_profile(profile,startat=startat)
oven.run_profile(profile, startat=startat)
ovenWatcher.record(profile)
if bottle.request.json['cmd'] == 'stop':
if bottle.request.json["cmd"] == "stop":
log.info("api stop command received")
oven.abort_run()
if bottle.request.json['cmd'] == 'memo':
if bottle.request.json["cmd"] == "memo":
log.info("api memo command received")
memo = bottle.request.json['memo']
memo = bottle.request.json["memo"]
log.info("memo=%s" % (memo))
# get stats during a run
if bottle.request.json['cmd'] == 'stats':
if bottle.request.json["cmd"] == "stats":
log.info("api stats command received")
if hasattr(oven,'pid'):
if hasattr(oven.pid,'pidstats'):
if hasattr(oven, "pid"):
if hasattr(oven.pid, "pidstats"):
return json.dumps(oven.pid.pidstats)
return { "success" : True }
return {"success": True}
def find_profile(wanted):
'''
"""
given a wanted profile name, find it and return the parsed
json profile object or None.
'''
#load all profiles from disk
"""
# load all profiles from disk
profiles = get_profiles()
json_profiles = json.loads(profiles)
# find the wanted profile
for profile in json_profiles:
if profile['name'] == wanted:
if profile["name"] == wanted:
return profile
return None
@app.route('/picoreflow/:filename#.*#')
@app.route("/picoreflow/:filename#.*#")
def send_static(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():
env = bottle.request.environ
wsock = env.get('wsgi.websocket')
wsock = env.get("wsgi.websocket")
if not wsock:
abort(400, 'Expected WebSocket request.')
abort(400, "Expected WebSocket request.")
return wsock
@app.route('/control')
@app.route("/control")
def handle_control():
wsock = get_websocket_from_request()
log.info("websocket (control) opened")
@ -143,7 +151,7 @@ def handle_control():
msgdict = json.loads(message)
if msgdict.get("cmd") == "RUN":
log.info("RUN command received")
profile_obj = msgdict.get('profile')
profile_obj = msgdict.get("profile")
if profile_obj:
profile_json = json.dumps(profile_obj)
profile = Profile(profile_json)
@ -151,15 +159,15 @@ def handle_control():
ovenWatcher.record(profile)
elif msgdict.get("cmd") == "SIMULATE":
log.info("SIMULATE command received")
#profile_obj = msgdict.get('profile')
#if profile_obj:
# profile_obj = msgdict.get('profile')
# if profile_obj:
# profile_json = json.dumps(profile_obj)
# profile = Profile(profile_json)
#simulated_oven = Oven(simulate=True, time_step=0.05)
#simulation_watcher = OvenWatcher(simulated_oven)
#simulation_watcher.add_observer(wsock)
#simulated_oven.run_profile(profile)
#simulation_watcher.record(profile)
# simulated_oven = Oven(simulate=True, time_step=0.05)
# simulation_watcher = OvenWatcher(simulated_oven)
# simulation_watcher.add_observer(wsock)
# simulated_oven.run_profile(profile)
# simulation_watcher.record(profile)
elif msgdict.get("cmd") == "STOP":
log.info("Stop command received")
oven.abort_run()
@ -169,7 +177,7 @@ def handle_control():
log.info("websocket (control) closed")
@app.route('/storage')
@app.route("/storage")
def handle_storage():
wsock = get_websocket_from_request()
log.info("websocket (storage) opened")
@ -190,18 +198,18 @@ def handle_storage():
wsock.send(get_profiles())
elif msgdict.get("cmd") == "DELETE":
log.info("DELETE command received")
profile_obj = msgdict.get('profile')
profile_obj = msgdict.get("profile")
if delete_profile(profile_obj):
msgdict["resp"] = "OK"
msgdict["resp"] = "OK"
wsock.send(json.dumps(msgdict))
#wsock.send(get_profiles())
# wsock.send(get_profiles())
elif msgdict.get("cmd") == "PUT":
log.info("PUT command received")
profile_obj = msgdict.get('profile')
#force = msgdict.get('force', False)
profile_obj = msgdict.get("profile")
# force = msgdict.get('force', False)
force = True
if profile_obj:
#del msgdict["cmd"]
# del msgdict["cmd"]
if save_profile(profile_obj, force):
msgdict["resp"] = "OK"
else:
@ -215,7 +223,7 @@ def handle_storage():
log.info("websocket (storage) closed")
@app.route('/config')
@app.route("/config")
def handle_config():
wsock = get_websocket_from_request()
log.info("websocket (config) opened")
@ -228,7 +236,7 @@ def handle_config():
log.info("websocket (config) closed")
@app.route('/status')
@app.route("/status")
def handle_status():
wsock = get_websocket_from_request()
ovenWatcher.add_observer(wsock)
@ -245,31 +253,39 @@ def handle_status():
def get_profiles():
try:
profile_files = os.listdir(profile_path)
profile_files.sort()
except:
profile_files = []
profiles = []
for filename in profile_files:
with open(os.path.join(profile_path, filename), 'r') as f:
profiles.append(json.load(f))
if filename.startswith("._"):
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)
def save_profile(profile, force=False):
profile_json = json.dumps(profile)
filename = profile['name']+".json"
filename = profile["name"] + ".json"
filepath = os.path.join(profile_path, filename)
if not force and os.path.exists(filepath):
log.error("Could not write, %s already exists" % filepath)
return False
with open(filepath, 'w+') as f:
with open(filepath, "w+") as f:
f.write(profile_json)
f.close()
log.info("Wrote %s" % filepath)
return True
def delete_profile(profile):
profile_json = json.dumps(profile)
filename = profile['name']+".json"
filename = profile["name"] + ".json"
filepath = os.path.join(profile_path, filename)
os.remove(filepath)
log.info("Deleted %s" % filepath)
@ -277,11 +293,15 @@ def delete_profile(profile):
def get_config():
return json.dumps({"temp_scale": config.temp_scale,
"time_scale_slope": config.time_scale_slope,
"time_scale_profile": config.time_scale_profile,
"kwh_rate": config.kwh_rate,
"currency_type": config.currency_type})
return json.dumps(
{
"temp_scale": config.temp_scale,
"time_scale_slope": config.time_scale_slope,
"time_scale_profile": config.time_scale_profile,
"kwh_rate": config.kwh_rate,
"currency_type": config.currency_type,
}
)
def main():
@ -289,8 +309,7 @@ def main():
port = config.listening_port
log.info("listening on %s:%d" % (ip, port))
server = WSGIServer((ip, port), app,
handler_class=WebSocketHandler)
server = WSGIServer((ip, port), app, handler_class=WebSocketHandler)
server.serve_forever()

View File

@ -368,14 +368,14 @@ function saveProfile()
}
function get_tick_size() {
switch(time_scale_profile){
case "s":
return 1;
case "m":
return 60;
case "h":
return 3600;
}
//switch(time_scale_profile){
// case "s":
// return 1;
// case "m":
// return 60;
// case "h":
// return 3600;
// }
return 3600;
}
@ -473,17 +473,17 @@ $(document).ready(function()
{
console.log("Status Socket has been opened");
$.bootstrapGrowl("<span class=\"glyphicon glyphicon-exclamation-sign\"></span>Getting data from server",
{
ele: 'body', // which element to append to
type: 'success', // (null, 'info', 'error', 'success')
offset: {from: 'top', amount: 250}, // 'top', or 'bottom'
align: 'center', // ('left', 'right', or 'center')
width: 385, // (integer, or 'auto')
delay: 2500,
allow_dismiss: true,
stackup_spacing: 10 // spacing between consecutively stacked growls.
});
// $.bootstrapGrowl("<span class=\"glyphicon glyphicon-exclamation-sign\"></span>Getting data from server",
// {
// ele: 'body', // which element to append to
// type: 'success', // (null, 'info', 'error', 'success')
// offset: {from: 'top', amount: 250}, // 'top', or 'bottom'
// align: 'center', // ('left', 'right', or 'center')
// width: 385, // (integer, or 'auto')
// delay: 2500,
// allow_dismiss: true,
// stackup_spacing: 10 // spacing between consecutively stacked growls.
// });
};
ws_status.onclose = function()