Merge pull request #1 from Autonomy-Logic/origin/RTOP-23-modularization-code-RestAPI

[RTOP-23]  Modularization code rest api
This commit is contained in:
Thiago Alves
2025-07-10 13:44:48 -04:00
committed by GitHub
8 changed files with 198 additions and 22 deletions
+20 -1
View File
@@ -19,6 +19,25 @@ webserver/build
obj/
utils/dnp3_src/CMakeFiles/
webserver/scripts/ethercat
utils/
# Build artifacts
install_log.txt
start_openplc.sh
webserver/core/*.c
webserver/core/*.cpp
webserver/core/*.h
webserver/core/*.o
webserver/core/VARIABLES.csv
webserver/core/glue_generator
webserver/core/openplc
webserver/iec2c
webserver/st_optimizer
webserver/active_program
utils/libmodbus_src/src/.libs/*
utils/libmodbus_src/tests/.libs/*
webserver/scripts/openplc_driver
webserver/scripts/openplc_platform
# Runtime directories
.venv
.venv
+1 -1
View File
@@ -1 +1 @@
890590.st
Dockerfile
+15 -18
View File
@@ -114,9 +114,9 @@ class runtime:
compilation_status_str = ""
# Extract debug information from program
f = open('./st_files/' + st_file, "r")
combined_lines = f.read()
f.close()
with open('./st_files/' + st_file, "r") as f:
combined_lines = f.read()
combined_lines = combined_lines.split('\n')
program_lines = []
c_debug_lines = []
@@ -132,9 +132,9 @@ class runtime:
# Could not find debug info on program uploaded
if os.path.isfile('./st_files/' + st_file + '.dbg'):
# Debugger info exists on file - open it
f = open('./st_files/' + st_file + '.dbg', "r")
c_debug = f.read()
f.close()
with open('./st_files/' + st_file + '.dbg', "r") as f:
c_debug = f.read()
else:
# No debug info... probably a program generated from the old editor. Use the blank debug info just to compile the program
f = open('./core/debug.blank', "r")
@@ -142,9 +142,8 @@ class runtime:
f.close()
# Write c_debug file
f = open('./core/debug.cpp', "w")
f.write(c_debug)
f.close()
with open('./core/debug.cpp', "w") as f:
f.write(c_debug)
# Start compilation
a = subprocess.Popen(['./scripts/compile_program.sh', str(st_file)], stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
@@ -155,17 +154,15 @@ class runtime:
c_debug = '\n'.join(c_debug_lines)
# Write c_debug file
f = open('./core/debug.cpp', "w")
f.write(c_debug)
f.close()
with open('./core/debug.cpp', "w") as f:
f.write(c_debug)
#Write program and debug files
f = open('./st_files/' + st_file, "w")
f.write(program)
f.close()
f = open('./st_files/' + st_file + '.dbg', "w")
f.write(c_debug)
f.close()
with open('./st_files/' + st_file, "w") as f:
f.write(program)
with open('./st_files/' + st_file + '.dbg', "w") as f:
f.write(c_debug)
# Start compilation
a = subprocess.Popen(['./scripts/compile_program.sh', str(st_file)], stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
+51
View File
@@ -0,0 +1,51 @@
# restblueprint.py
from flask import Blueprint, jsonify, request
from typing import Callable, Optional
# Define the Blueprint
restapi_bp = Blueprint('restapi_blueprint', __name__)
# Global variable to store the single callback for this blueprint
_handler_callback_get: Optional[Callable[[str, dict], dict]] = None
_handler_callback_post: Optional[Callable[[str, dict], dict]] = None
def register_callback_get(callback: Callable[[str, dict], dict]):
"""Registers the business logic callback function."""
global _handler_callback_get
_handler_callback_get = callback
print("GET Callback registered successfully for rest_blueprint!")
def register_callback_post(callback: Callable[[str, dict], dict]):
"""Registers the business logic callback function."""
global _handler_callback_post
_handler_callback_post = callback
print("POST Callback registered successfully for rest_blueprint!")
@restapi_bp.route("/<command>", methods=["GET"])
def restapi_plc_get(command):
if _handler_callback_get is None:
return jsonify({"error": "No handler registered"}), 500
try:
data = request.args.to_dict()
result = _handler_callback_get(command, data)
return jsonify(result), 200
except Exception as e:
print(f"Error in restapi_plc_get: {e}")
return jsonify({"error": str(e)}), 500
@restapi_bp.route("/<command>", methods=["POST"])
def restapi_plc_post(command):
if _handler_callback_post is None:
return jsonify({"error": "No handler registered"}), 500
try:
# TODO validate file and limit size
data = request.get_json(silent=True) or {}
result = _handler_callback_post(command, data)
return jsonify(result), 200
except Exception as e:
print(f"Error in restapi_plc_post: {e}")
return jsonify({"error": str(e)}), 500
+1 -1
View File
@@ -1 +1 @@
rpi
blank_linux
+1 -1
View File
@@ -1 +1 @@
rpi
linux
+31
View File
@@ -0,0 +1,31 @@
FROM debian:bullseye-20240722
COPY . /workdir
WORKDIR /workdir
RUN mkdir /docker_persistent
VOLUME /docker_persistent
# setup docker
RUN ./install.sh docker \
&& touch /docker_persistent/mbconfig.cfg \
&& touch /docker_persistent/persistent.file \
&& mkdir /docker_persistent/st_files \
&& cp /workdir/webserver/openplc.db /docker_persistent/openplc.db \
&& mv /workdir/webserver/openplc.db /workdir/webserver/openplc_default.db \
&& cp /workdir/webserver/dnp3.cfg /docker_persistent/dnp3.cfg \
&& mv /workdir/webserver/dnp3.cfg /workdir/webserver/dnp3_default.cfg \
&& cp -r /workdir/webserver/st_files/ /docker_persistent/st_files/ \
&& mv /workdir/webserver/st_files /workdir/webserver/st_files_default \
&& cp /workdir/webserver/active_program /docker_persistent/active_program \
&& mv /workdir/webserver/active_program /workdir/webserver/active_program_default \
&& ln -s /docker_persistent/mbconfig.cfg /workdir/webserver/mbconfig.cfg \
&& ln -s /docker_persistent/persistent.file /workdir/webserver/persistent.file \
&& ln -s /docker_persistent/openplc.db /workdir/webserver/openplc.db \
&& ln -s /docker_persistent/dnp3.cfg /workdir/webserver/dnp3.cfg \
&& ln -s /docker_persistent/st_files /workdir/webserver/st_files \
&& ln -s /docker_persistent/active_program /workdir/webserver/active_program
ENTRYPOINT ["./start_openplc.sh"]
+78
View File
@@ -17,6 +17,7 @@ import mimetypes
import flask
import flask_login
from restapi import restapi_bp, register_callback_get, register_callback_post
app = flask.Flask(__name__)
app.secret_key = str(os.urandom(16))
@@ -25,6 +26,77 @@ login_manager.init_app(app)
openplc_runtime = openplc.runtime()
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.
"""
# TODO logging debug level
print(f"GET | [{__name__}] Received argument: {argument}, data: {data}")
if argument == "start-plc":
openplc_runtime.start_runtime()
return {"status": "runtime started"}
elif argument == "stop-plc":
openplc_runtime.stop_runtime()
return {"status": "runtime stop"}
elif argument == "runtime-logs":
logs = openplc_runtime.logs()
return {"runtime-logs": logs}
elif argument == "compilation-status":
status = openplc_runtime.is_compiling
return {"is-compiling": status}
elif argument == "compilation-logs":
logs = openplc_runtime.compilation_status()
return {"compilation-logs": logs}
elif argument == "status":
return {"current_status": "operational", "details": data}
elif argument == "ping":
return {"status": "pong"}
else:
return {"error": "Unknown argument"}
# file upload POST handler
def restapi_callback_post(argument: str, data: dict) -> dict:
# TODO logging debug level
print(f"POST | [{__name__}] Received argument: {argument}, data: {data}")
if argument == "upload-file":
try:
# TODO validate filename, content and size
st_file = flask.request.files['file']
print(st_file.filename)
st_file.save(f"st_files/{st_file.filename}")
return {"UploadFile": "Success"}
except:
return {"UploadFile": "Fail"}
elif argument == "compile-program":
if (openplc_runtime.status() == "Compiling"):
return {"RuntimeStatus": "Compiling"}
try:
# TODO return compilation result and validate filename
# st_file = flask.request.args.get('file')
st_file = flask.request.files['file']
# print(f"st_files/{st_file.filename}")
openplc_runtime.compile_program(f"{st_file.filename}")
return {"CompilationStatus": "Program Compiled"}
except Exception as e:
return {"CompilationStatus": e}
else:
return {"PostError": "Unknown argument"}
class User(flask_login.UserMixin):
pass
@@ -852,6 +924,7 @@ def upload_program():
if (prog_file.filename == ''):
return draw_blank_page() + "<h2>Error</h2><p>You need to select a file to be uploaded!<br><br>Use the back-arrow on your browser to return</p></div></div></div></body></html>"
# TODO realocate to another function
filename = str(random.randint(1,1000000)) + ".st"
prog_file.save(os.path.join('st_files', filename))
@@ -2494,6 +2567,11 @@ def main():
print("Starting the web interface...")
if __name__ == '__main__':
# rest api register
app.register_blueprint(restapi_bp, url_prefix='/api')
register_callback_get(restapi_callback_get)
register_callback_post(restapi_callback_post)
#Load information about current program on the openplc_runtime object
file = open("active_program", "r")
st_file = file.read()