Suchinthana commited on
Commit
f994803
·
1 Parent(s): 93832c5

Add CSV-driven OPC UA server with Docker

Browse files

Add an async OPC UA server (csv_opcua_server.py) that reads data.csv and publishes each CSV row as OPC UA variables at a configurable interval, plus a simple HTTP health endpoint on port 8080. Add a Dockerfile to build the image (python:3.11-slim, installs gcc for binary deps), copies requirements and app files, and exposes ports 4840 and 8080. Include a sample data.csv and a requirements.txt listing dependencies. Note: requirements.txt contains a typo ('requestsc' should be 'requests').

Files changed (4) hide show
  1. Dockerfile +26 -0
  2. csv_opcua_server.py +114 -0
  3. data.csv +6 -0
  4. requirements.txt +4 -0
Dockerfile ADDED
@@ -0,0 +1,26 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Use lightweight Python image
2
+ FROM python:3.11-slim
3
+
4
+ # Set working directory
5
+ WORKDIR /app
6
+
7
+ # Install system dependencies (optional but safe for pandas)
8
+ RUN apt-get update && apt-get install -y \
9
+ gcc \
10
+ && rm -rf /var/lib/apt/lists/*
11
+
12
+ # Copy only requirements first (better layer caching)
13
+ COPY requirements.txt .
14
+
15
+ # Install Python dependencies
16
+ RUN pip install --no-cache-dir -r requirements.txt
17
+
18
+ # Copy application files
19
+ COPY . .
20
+
21
+ # Expose ports
22
+ EXPOSE 4840
23
+ EXPOSE 8080
24
+
25
+ # Run the app
26
+ CMD ["python", "csv_opcua_server.py"]
csv_opcua_server.py ADDED
@@ -0,0 +1,114 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import asyncio
2
+ import threading
3
+ from http.server import HTTPServer, BaseHTTPRequestHandler
4
+
5
+ import pandas as pd
6
+ import requests
7
+ from asyncua import Server, ua
8
+
9
+ # =========================
10
+ # CONFIGURATION
11
+ # =========================
12
+ BIND_IP = "0.0.0.0"
13
+ OPCUA_PORT = 4840
14
+ HTTP_PORT = 8080
15
+ CSV_FILE = "data.csv"
16
+ ROW_INTERVAL = 2 # seconds
17
+
18
+ # =========================
19
+ # PUBLIC IP DETECTION
20
+ # =========================
21
+ def get_public_ip():
22
+ try:
23
+ return requests.get("https://ipinfo.io/ip", timeout=5).text.strip()
24
+ except Exception:
25
+ return "127.0.0.1"
26
+
27
+ PUBLIC_IP = get_public_ip()
28
+
29
+ ENDPOINT = f"opc.tcp://{BIND_IP}:{OPCUA_PORT}/csv-opcua-server/"
30
+ PUBLIC_ENDPOINT = f"opc.tcp://{PUBLIC_IP}:{OPCUA_PORT}/csv-opcua-server/"
31
+
32
+ # =========================
33
+ # SIMPLE HTTP SERVER
34
+ # =========================
35
+ class HealthHandler(BaseHTTPRequestHandler):
36
+ def do_GET(self):
37
+ self.send_response(200)
38
+ self.send_header("Content-type", "text/plain")
39
+ self.end_headers()
40
+ self.wfile.write(b"OK - Server is reachable via browser\n")
41
+
42
+ def log_message(self, format, *args):
43
+ return
44
+
45
+ def start_http_server():
46
+ httpd = HTTPServer((BIND_IP, HTTP_PORT), HealthHandler)
47
+ print(f"HTTP server running on http://{PUBLIC_IP}:{HTTP_PORT}")
48
+ httpd.serve_forever()
49
+
50
+ # =========================
51
+ # MAIN ASYNC OPC UA SERVER
52
+ # =========================
53
+ async def main():
54
+ # Load CSV
55
+ df = pd.read_csv(CSV_FILE)
56
+ if df.empty:
57
+ raise ValueError("CSV file is empty")
58
+
59
+ # Create OPC UA server
60
+ server = Server()
61
+ await server.init()
62
+ server.set_endpoint(ENDPOINT)
63
+ server.set_server_name("Public CSV OPC UA Async Server")
64
+
65
+ uri = "http://example.org/public-csv-opcua"
66
+ idx = await server.register_namespace(uri)
67
+
68
+ objects = server.nodes.objects
69
+ csv_obj = await objects.add_object(idx, "CSV_Data")
70
+
71
+ # Create variables
72
+ variables = {}
73
+ for col in df.columns:
74
+ var = await csv_obj.add_variable(
75
+ idx,
76
+ col,
77
+ float(df[col].iloc[0]),
78
+ ua.VariantType.Double,
79
+ )
80
+ await var.set_writable()
81
+ variables[col] = var
82
+
83
+ print("====================================")
84
+ print(" OPC UA SERVER RUNNING (ASYNC)")
85
+ print(f" Internal bind : {ENDPOINT}")
86
+ print(f" Public access : {PUBLIC_ENDPOINT}")
87
+ print(f" Publishing 1 row every {ROW_INTERVAL} seconds")
88
+ print("====================================")
89
+
90
+ row_index = 0
91
+ row_count = len(df)
92
+
93
+ async with server:
94
+ while True:
95
+ row = df.iloc[row_index]
96
+ print(f"Publishing row {row_index + 1}/{row_count}")
97
+
98
+ for col, var in variables.items():
99
+ value = float(row[col])
100
+ await var.write_value(value)
101
+ print(f" {col} = {value}")
102
+
103
+ row_index = (row_index + 1) % row_count
104
+ await asyncio.sleep(ROW_INTERVAL)
105
+
106
+ # =========================
107
+ # START EVERYTHING
108
+ # =========================
109
+ if __name__ == "__main__":
110
+ # Start HTTP server in background thread
111
+ threading.Thread(target=start_http_server, daemon=True).start()
112
+
113
+ # Start async OPC UA server
114
+ asyncio.run(main())
data.csv ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ rpm,xtilt,db
2
+ 750.2,0.12,22.0
3
+ 751.2,0.25,23.0
4
+ 752.2,0.22,22.1
5
+ 751.5,0.15,22.5
6
+ 750.1,0.14,22.1
requirements.txt ADDED
@@ -0,0 +1,4 @@
 
 
 
 
 
1
+ asyncua
2
+ pandas
3
+ requestsc
4
+