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