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 #storage/profiles
#config.py #config.py
.idea/* .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 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

View File

@ -8,6 +8,7 @@ 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
@ -16,6 +17,7 @@ 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.")
@ -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,33 +47,34 @@ 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)
@ -84,54 +87,59 @@ def handle_api():
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)
@ -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,14 +198,14 @@ 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:
@ -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("._"):
pass
else:
if filename.endswith(".json"):
with open(os.path.join(profile_path, filename), "r") as f:
profiles.append(json.load(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(
{
"temp_scale": config.temp_scale,
"time_scale_slope": config.time_scale_slope, "time_scale_slope": config.time_scale_slope,
"time_scale_profile": config.time_scale_profile, "time_scale_profile": config.time_scale_profile,
"kwh_rate": config.kwh_rate, "kwh_rate": config.kwh_rate,
"currency_type": config.currency_type}) "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()

View File

@ -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()