Merge pull request #168 from SamSkjord/master

profile filetype checks
This commit is contained in:
Jason Bruce 2024-07-02 13:56:42 -04:00 committed by GitHub
commit 2e756178dc
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 85 additions and 65 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

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