Fix REST API errors and make it conform with v4 API

This commit is contained in:
Thiago Alves
2025-10-07 14:22:04 -04:00
parent cd369af331
commit 228f60965d
2 changed files with 96 additions and 74 deletions
+21 -16
View File
@@ -3,7 +3,7 @@ import subprocess
import socket
import errno
import time
from threading import Thread
from threading import Thread, Lock
from queue import Queue, Empty
import os.path
@@ -70,15 +70,16 @@ class NonBlockingStreamReader:
class UnexpectedEndOfStream(Exception): pass
class runtime:
project_file = ""
project_name = ""
project_description = ""
compilation_status_str = ""
compilation_error_str = ""
compilation_object = None
compilation_error = None
runtime_status = "Stopped"
def __init__(self):
self.project_file = ""
self.project_name = ""
self.project_description = ""
self.compilation_status_str = ""
self.compilation_error_str = ""
self.compilation_object = None
self.compilation_error = None
self.runtime_status = "Stopped"
def start_runtime(self):
if (self.status() == "Stopped"):
self.theprocess = subprocess.Popen(['./core/openplc']) # XXX: iPAS
@@ -148,9 +149,12 @@ class runtime:
f.write(c_debug)
# Start compilation
a = subprocess.Popen(['./scripts/compile_program.sh', str(st_file)], stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
self.compilation_object = NonBlockingStreamReader(a.stdout)
self.compilation_error = NonBlockingStreamReader(a.stderr)
try:
a = subprocess.Popen(['./scripts/compile_program.sh', str(st_file)], stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
self.compilation_object = NonBlockingStreamReader(a.stdout)
# self.compilation_error = NonBlockingStreamReader(a.stderr)
except Exception as e:
print(f"Error starting compilation: {e}")
else:
# Debug info was extracted from program
program = '\n'.join(program_lines)
@@ -170,7 +174,7 @@ class runtime:
# Start compilation
a = subprocess.Popen(['./scripts/compile_program.sh', str(st_file)], stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
self.compilation_object = NonBlockingStreamReader(a.stdout)
self.compilation_error = NonBlockingStreamReader(a.stderr)
# self.compilation_error = NonBlockingStreamReader(a.stderr)
def compilation_status(self):
while self.compilation_object != None:
@@ -188,8 +192,9 @@ class runtime:
def status(self):
try:
if (self.compilation_object.end_of_stream == False):
return "Compiling"
if (self.compilation_object != None):
if (self.compilation_object.end_of_stream == False):
return "Compiling"
except Exception as e:
print(f"Error checking compilation status: {e}")
+75 -58
View File
@@ -23,6 +23,9 @@ import flask_login
from credentials import CertGen
from restapi import app_restapi, restapi_bp, db, register_callback_get, register_callback_post
from dataclasses import dataclass, field
from enum import Enum, auto
app = flask.Flask(__name__)
app.secret_key = str(os.urandom(16))
@@ -49,97 +52,111 @@ def restapi_callback_get(argument: str, data: dict) -> dict:
This is the central callback function that handles the logic
based on the 'argument' from the URL and 'data' from the request.
"""
global openplc_runtime
logger.debug(f"GET | Received argument: {argument}, data: {data}")
if argument == "start-plc":
monitor.stop_monitor()
openplc_runtime.start_runtime()
time.sleep(1)
configure_runtime()
return {"status": "runtime started"}
monitor.cleanup()
monitor.parse_st(openplc_runtime.project_file)
return {"status": "START:OK"}
elif argument == "stop-plc":
openplc_runtime.stop_runtime()
return {"status": "runtime stop"}
time.sleep(1)
monitor.stop_monitor()
return {"status": "STOP:OK"}
elif argument == "runtime-logs":
logs = openplc_runtime.logs()
return {"runtime-logs": logs}
elif argument == "compilation-status":
try:
logs = openplc_runtime.compilation_status()
_logs = logs
except Exception as e:
logger.error(f"Error retrieving compilation logs: {e}")
_logs = str(e)
status = 'IDLE'
logs = openplc_runtime.compilation_status()
exit_code = 0
status = _logs
if status is not str:
_status = "No compilation in progress"
if "Compilation finished successfully!" in status:
_status = "Success"
_error = "No error"
elif "Compilation finished with errors!" in status:
_status = "Error"
_error = openplc_runtime.get_compilation_error()
logs = str(logs)
if "Compilation finished successfully!" in logs:
status = 'SUCCESS'
exit_code = 0
elif "Compilation finished with errors!" in logs:
status = 'FAILED'
exit_code = 1
else:
_status = "Compiling"
_error = openplc_runtime.get_compilation_error()
logger.debug(f"Compilation status: {_status}, logs: {_logs}", extra={"error": _error})
status = 'COMPILING'
exit_code = 0
return {"status": _status, "logs": _logs, "error": _error}
return {
"status": status,
"logs": logs.splitlines(),
"exit_code": exit_code
}
elif argument == "status":
return {"current_status": "operational", "details": data}
if openplc_runtime.status() == "Running":
return {"status" : "STATUS:RUNNING"}
elif openplc_runtime.status() == "Compiling":
return {"status" : "STATUS:EMPTY"}
elif openplc_runtime.status() == "Stopped":
return {"status" : "STATUS:STOPPED"}
else:
return {"status" : "STATUS:UNKNOWN"}
elif argument == "ping":
return {"status": "pong"}
return {"status": "PING:OK"}
else:
return {"error": "Unknown argument"}
# file upload POST handler
def restapi_callback_post(argument: str, data: dict) -> dict:
global openplc_runtime
logger.debug(f"POST | Received argument: {argument}, data: {data}")
if argument == "upload-file":
try:
# validate filename
if 'file' not in flask.request.files:
return {"UploadFileFail": "No file part in the request"}
st_file = flask.request.files['file']
# validate file size
if st_file.content_length > 32 * 1024 * 1024: # 32 MB limit
return {"UploadFileFail": "File is too large"}
if openplc_runtime.status() == "Compiling":
return {"UploadFileFail": "Runtime is compiling another program, please wait", "CompilationStatus": "COMPILING"}
# replace program file on database
# validate filename
if 'file' not in flask.request.files:
return {"UploadFileFail": "No file part in the request", "CompilationStatus": "FAILED"}
st_file = flask.request.files['file']
# validate file size
if st_file.content_length > 32 * 1024 * 1024: # 32 MB limit
return {"UploadFileFail": "File is too large", "CompilationStatus": "FAILED"}
# replace program file on database
database = "openplc.db"
conn = create_connection(database)
if (conn != None):
try:
database = "openplc.db"
conn = create_connection(database)
logger.info(f"{database} connected")
if (conn != None):
try:
cur = conn.cursor()
cur.execute("SELECT * FROM Programs WHERE Name = 'webserver_program'")
row = cur.fetchone()
cur.close()
except Exception as e:
return {"UploadFileFail": e}
cur = conn.cursor()
cur.execute("SELECT * FROM Programs WHERE Name = 'webserver_program'")
row = cur.fetchone()
cur.close()
conn.close()
filename = str(row[3])
st_file.save(os.path.join('st_files', filename))
openplc_runtime.project_name = str(row[1])
openplc_runtime.project_description = str(row[2])
openplc_runtime.project_file = str(row[3])
except Exception as e:
return {"UploadFileFail": f"Error connecting to the database: {e}"}
return {"UploadFileFail": e, "CompilationStatus": "FAILED"}
else:
return {"UploadFileFail": f"Error connecting to the database", "CompilationStatus": "FAILED"}
filename = str(row[3])
st_file.save(f"st_files/{filename}")
except Exception as e:
return {"UploadFileFail": e}
if (openplc_runtime.status() == "Compiling"):
return {"RuntimeStatus": "Compiling"}
try:
openplc_runtime.compile_program(f"{filename}")
return {"CompilationStatus": "Starting program compilation"}
except Exception as e:
return {"CompilationStatusFail": e}
# Compile new program
delete_persistent_file()
openplc_runtime.compile_program(filename)
return {"UploadFileFail": "", "CompilationStatus": "COMPILING"}
else:
return {"PostRequestError": "Unknown argument"}