init
This commit is contained in:
parent
9da539c273
commit
ceeb86ef1f
18
api/Dockerfile
Normal file
18
api/Dockerfile
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
# Base image
|
||||||
|
FROM python:3.10-slim
|
||||||
|
|
||||||
|
# Set working directory
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
# Copy files
|
||||||
|
COPY . .
|
||||||
|
|
||||||
|
# Install dependencies
|
||||||
|
RUN pip install --no-cache-dir -r requirements.txt
|
||||||
|
|
||||||
|
# Expose port for Flask
|
||||||
|
EXPOSE 40000
|
||||||
|
|
||||||
|
# Start the Flask app
|
||||||
|
CMD ["python", "pi_opcua_client.py"]
|
||||||
|
|
||||||
128
api/pi_opcua_client.py
Normal file
128
api/pi_opcua_client.py
Normal file
@ -0,0 +1,128 @@
|
|||||||
|
from flask import Flask, request, jsonify
|
||||||
|
from flask_cors import CORS
|
||||||
|
from opcua import Client, ua
|
||||||
|
from opcua.ua import Variant, VariantType
|
||||||
|
import logging
|
||||||
|
|
||||||
|
# Disable certificate check (not secure - use only in development!)
|
||||||
|
#from opcua.crypto import certificate_handler
|
||||||
|
#certificate_handler.CertificateHandler.validate = lambda self, cert: True
|
||||||
|
#from opcua.common import certificate_store
|
||||||
|
#certificate_store.CertificateStore.check_certificate = lambda self, cert: True
|
||||||
|
|
||||||
|
# Configure logging
|
||||||
|
logging.basicConfig(level=logging.DEBUG)
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
app = Flask(__name__)
|
||||||
|
CORS(app)
|
||||||
|
|
||||||
|
variable_mapping = {
|
||||||
|
"IN_Bit_Esterni": {"node_id": "ns=4;s=IN_Bit_Esterni", "type": VariantType.UInt32},
|
||||||
|
"IN_Prossimo_Formato": {"node_id": "ns=4;s=IN_Prossimo_Formato", "type": VariantType.UInt16},
|
||||||
|
"IN_Prossimo_Lotto": {"node_id": "ns=4;s=IN_Prossimo_Lotto", "type": VariantType.String},
|
||||||
|
"IN_Target_Produzione": {"node_id": "ns=4;s=IN_Target_Produzione", "type": VariantType.UInt32},
|
||||||
|
"OUT_Bit_Esterni": {"node_id": "ns=4;s=OUT_Bit_Esterni", "type": VariantType.UInt32},
|
||||||
|
"OUT_Ricetta_in_Uso": {"node_id": "ns=4;s=OUT_Ricetta_in_Uso", "type": VariantType.String},
|
||||||
|
"OUT_Velocita_Istantanea": {"node_id": "ns=4;s=OUT_Velocita_Istantanea", "type": VariantType.String},
|
||||||
|
"OUT_Vel_Effettiva": {"node_id": "ns=4;s=OUT_Vel_Effettiva", "type": VariantType.String},
|
||||||
|
"OUT_Vel_Impostata": {"node_id": "ns=4;s=OUT_Vel_Impostata", "type": VariantType.String},
|
||||||
|
"OUT_Contapezzi_Parziale": {"node_id": "ns=4;s=OUT_Contapezzi_Parziale", "type": VariantType.UInt32},
|
||||||
|
"OUT_Contapezzi_Totale": {"node_id": "ns=4;s=OUT_Contapezzi_Totale", "type": VariantType.UInt32},
|
||||||
|
"OUT_Lotto_Attuale":{"node_id":"ns=4;s=OUT_Lotto_Attuale","type":VariantType.String}
|
||||||
|
}
|
||||||
|
|
||||||
|
def opcua_request(server_url, callback):
|
||||||
|
""" Connects to an OPC UA server, executes a callback, then disconnects. """
|
||||||
|
logger.debug(f"Attempting to connect to OPC UA server: {server_url}")
|
||||||
|
client = Client(server_url)
|
||||||
|
try:
|
||||||
|
client.connect()
|
||||||
|
logger.debug("Successfully connected to OPC UA server")
|
||||||
|
return callback(client)
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"OPC UA error: {e}")
|
||||||
|
return {"error": str(e)}, 500
|
||||||
|
finally:
|
||||||
|
client.disconnect()
|
||||||
|
logger.debug("Disconnected from OPC UA server")
|
||||||
|
|
||||||
|
@app.route('/write_multiple', methods=['POST'])
|
||||||
|
def write_multiple_values():
|
||||||
|
""" Write multiple values to OPC UA variables on a specified server. """
|
||||||
|
data = request.get_json()
|
||||||
|
server_url = data.get('server_url')
|
||||||
|
variables = data.get('variables')
|
||||||
|
|
||||||
|
if not server_url or not variables:
|
||||||
|
return jsonify({"error": "server_url and variables are required"}), 400
|
||||||
|
|
||||||
|
results = []
|
||||||
|
errors = []
|
||||||
|
|
||||||
|
def write_callback(client):
|
||||||
|
for variable_name, value in variables.items():
|
||||||
|
variable_info = variable_mapping.get(variable_name)
|
||||||
|
if not variable_info:
|
||||||
|
errors.append({"variable": variable_name, "error": "Invalid variable name"})
|
||||||
|
continue
|
||||||
|
|
||||||
|
try:
|
||||||
|
node = client.get_node(variable_info["node_id"])
|
||||||
|
|
||||||
|
# Ensure correct data type
|
||||||
|
expected_type = variable_info["type"]
|
||||||
|
|
||||||
|
if expected_type == VariantType.UInt32:
|
||||||
|
converted_value = ua.Variant(int(value), ua.VariantType.UInt32)
|
||||||
|
elif expected_type == VariantType.UInt16:
|
||||||
|
converted_value = ua.Variant(int(value), ua.VariantType.UInt16)
|
||||||
|
elif expected_type == VariantType.String:
|
||||||
|
converted_value = ua.Variant(str(value), ua.VariantType.String)
|
||||||
|
elif expected_type == VariantType.Boolean:
|
||||||
|
converted_value = ua.Variant(bool(value), ua.VariantType.Boolean)
|
||||||
|
elif expected_type == VariantType.Float:
|
||||||
|
converted_value = ua.Variant(float(value), ua.VariantType.Float)
|
||||||
|
else:
|
||||||
|
errors.append({"variable": variable_name, "error": "Unsupported data type"})
|
||||||
|
continue
|
||||||
|
|
||||||
|
node.set_value(ua.DataValue(converted_value))
|
||||||
|
|
||||||
|
results.append({"variable": variable_name, "status": "success"})
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Write error for {variable_name}: {e}")
|
||||||
|
errors.append({"variable": variable_name, "error": f"Failed to write {variable_name}. {str(e)}"})
|
||||||
|
|
||||||
|
if errors:
|
||||||
|
return jsonify({"results": results, "errors": errors}), 207 # Multi-status response
|
||||||
|
return jsonify({"results": results})
|
||||||
|
|
||||||
|
return opcua_request(server_url, write_callback)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@app.route('/variables', methods=['GET'])
|
||||||
|
def get_all_variables():
|
||||||
|
""" Read all OPC UA variables from a specified server, skipping missing ones. """
|
||||||
|
server_url = request.args.get('server_url')
|
||||||
|
|
||||||
|
if not server_url:
|
||||||
|
return jsonify({"error": "server_url is required"}), 400
|
||||||
|
|
||||||
|
def variables_callback(client):
|
||||||
|
all_variables = {}
|
||||||
|
for var_name, var_info in variable_mapping.items():
|
||||||
|
try:
|
||||||
|
node = client.get_node(var_info["node_id"])
|
||||||
|
all_variables[var_name] = node.get_value()
|
||||||
|
except Exception as e:
|
||||||
|
logger.warning(f"Skipping missing variable: {var_name}")
|
||||||
|
continue # Skip missing variables
|
||||||
|
|
||||||
|
return jsonify(all_variables)
|
||||||
|
|
||||||
|
return opcua_request(server_url, variables_callback)
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
app.run(host='0.0.0.0', port=40000)
|
||||||
4
api/requirements.txt
Normal file
4
api/requirements.txt
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
flask
|
||||||
|
flask-cors
|
||||||
|
opcua
|
||||||
|
|
||||||
9
docker-compose.yml
Normal file
9
docker-compose.yml
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
services:
|
||||||
|
api:
|
||||||
|
build: ./api # Path to the API folder
|
||||||
|
# ports:
|
||||||
|
# - "40000:40000
|
||||||
|
network_mode: "host"
|
||||||
|
restart: always
|
||||||
|
environment:
|
||||||
|
- FLASK_ENV=production
|
||||||
Loading…
Reference in New Issue
Block a user