Initial commit
This commit is contained in:
219
backend/.gitignore
vendored
Normal file
219
backend/.gitignore
vendored
Normal file
@@ -0,0 +1,219 @@
|
|||||||
|
# Byte-compiled / optimized / DLL files
|
||||||
|
__pycache__/
|
||||||
|
*.py[codz]
|
||||||
|
*$py.class
|
||||||
|
|
||||||
|
# C extensions
|
||||||
|
*.so
|
||||||
|
|
||||||
|
# Distribution / packaging
|
||||||
|
.Python
|
||||||
|
build/
|
||||||
|
develop-eggs/
|
||||||
|
dist/
|
||||||
|
downloads/
|
||||||
|
eggs/
|
||||||
|
.eggs/
|
||||||
|
lib/
|
||||||
|
lib64/
|
||||||
|
parts/
|
||||||
|
sdist/
|
||||||
|
var/
|
||||||
|
wheels/
|
||||||
|
share/python-wheels/
|
||||||
|
*.egg-info/
|
||||||
|
.installed.cfg
|
||||||
|
*.egg
|
||||||
|
MANIFEST
|
||||||
|
|
||||||
|
# PyInstaller
|
||||||
|
# Usually these files are written by a python script from a template
|
||||||
|
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
||||||
|
*.manifest
|
||||||
|
*.spec
|
||||||
|
|
||||||
|
# Installer logs
|
||||||
|
pip-log.txt
|
||||||
|
pip-delete-this-directory.txt
|
||||||
|
|
||||||
|
# Unit test / coverage reports
|
||||||
|
htmlcov/
|
||||||
|
.tox/
|
||||||
|
.nox/
|
||||||
|
.coverage
|
||||||
|
.coverage.*
|
||||||
|
.cache
|
||||||
|
nosetests.xml
|
||||||
|
coverage.xml
|
||||||
|
*.cover
|
||||||
|
*.py.cover
|
||||||
|
.hypothesis/
|
||||||
|
.pytest_cache/
|
||||||
|
cover/
|
||||||
|
|
||||||
|
# Translations
|
||||||
|
*.mo
|
||||||
|
*.pot
|
||||||
|
|
||||||
|
# Django stuff:
|
||||||
|
*.log
|
||||||
|
local_settings.py
|
||||||
|
db.sqlite3
|
||||||
|
db.sqlite3-journal
|
||||||
|
|
||||||
|
# Flask stuff:
|
||||||
|
instance/
|
||||||
|
.webassets-cache
|
||||||
|
|
||||||
|
# Scrapy stuff:
|
||||||
|
.scrapy
|
||||||
|
|
||||||
|
# Sphinx documentation
|
||||||
|
docs/_build/
|
||||||
|
|
||||||
|
# PyBuilder
|
||||||
|
.pybuilder/
|
||||||
|
target/
|
||||||
|
|
||||||
|
# Jupyter Notebook
|
||||||
|
.ipynb_checkpoints
|
||||||
|
|
||||||
|
# IPython
|
||||||
|
profile_default/
|
||||||
|
ipython_config.py
|
||||||
|
|
||||||
|
# pyenv
|
||||||
|
# For a library or package, you might want to ignore these files since the code is
|
||||||
|
# intended to run in multiple environments; otherwise, check them in:
|
||||||
|
# .python-version
|
||||||
|
|
||||||
|
# pipenv
|
||||||
|
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
|
||||||
|
# However, in case of collaboration, if having platform-specific dependencies or dependencies
|
||||||
|
# having no cross-platform support, pipenv may install dependencies that don't work, or not
|
||||||
|
# install all needed dependencies.
|
||||||
|
# Pipfile.lock
|
||||||
|
|
||||||
|
# UV
|
||||||
|
# Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control.
|
||||||
|
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
||||||
|
# commonly ignored for libraries.
|
||||||
|
# uv.lock
|
||||||
|
|
||||||
|
# poetry
|
||||||
|
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
|
||||||
|
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
||||||
|
# commonly ignored for libraries.
|
||||||
|
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
|
||||||
|
# poetry.lock
|
||||||
|
# poetry.toml
|
||||||
|
|
||||||
|
# pdm
|
||||||
|
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
|
||||||
|
# pdm recommends including project-wide configuration in pdm.toml, but excluding .pdm-python.
|
||||||
|
# https://pdm-project.org/en/latest/usage/project/#working-with-version-control
|
||||||
|
# pdm.lock
|
||||||
|
# pdm.toml
|
||||||
|
.pdm-python
|
||||||
|
.pdm-build/
|
||||||
|
|
||||||
|
# pixi
|
||||||
|
# Similar to Pipfile.lock, it is generally recommended to include pixi.lock in version control.
|
||||||
|
# pixi.lock
|
||||||
|
# Pixi creates a virtual environment in the .pixi directory, just like venv module creates one
|
||||||
|
# in the .venv directory. It is recommended not to include this directory in version control.
|
||||||
|
.pixi
|
||||||
|
|
||||||
|
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
|
||||||
|
__pypackages__/
|
||||||
|
|
||||||
|
# Celery stuff
|
||||||
|
celerybeat-schedule
|
||||||
|
celerybeat.pid
|
||||||
|
|
||||||
|
# Redis
|
||||||
|
*.rdb
|
||||||
|
*.aof
|
||||||
|
*.pid
|
||||||
|
|
||||||
|
# RabbitMQ
|
||||||
|
mnesia/
|
||||||
|
rabbitmq/
|
||||||
|
rabbitmq-data/
|
||||||
|
|
||||||
|
# ActiveMQ
|
||||||
|
activemq-data/
|
||||||
|
|
||||||
|
# SageMath parsed files
|
||||||
|
*.sage.py
|
||||||
|
|
||||||
|
# Environments
|
||||||
|
.env
|
||||||
|
.envrc
|
||||||
|
.venv
|
||||||
|
env/
|
||||||
|
venv/
|
||||||
|
ENV/
|
||||||
|
env.bak/
|
||||||
|
venv.bak/
|
||||||
|
|
||||||
|
# Spyder project settings
|
||||||
|
.spyderproject
|
||||||
|
.spyproject
|
||||||
|
|
||||||
|
# Rope project settings
|
||||||
|
.ropeproject
|
||||||
|
|
||||||
|
# mkdocs documentation
|
||||||
|
/site
|
||||||
|
|
||||||
|
# mypy
|
||||||
|
.mypy_cache/
|
||||||
|
.dmypy.json
|
||||||
|
dmypy.json
|
||||||
|
|
||||||
|
# Pyre type checker
|
||||||
|
.pyre/
|
||||||
|
|
||||||
|
# pytype static type analyzer
|
||||||
|
.pytype/
|
||||||
|
|
||||||
|
# Cython debug symbols
|
||||||
|
cython_debug/
|
||||||
|
|
||||||
|
# PyCharm
|
||||||
|
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
|
||||||
|
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
|
||||||
|
# and can be added to the global gitignore or merged into this file. For a more nuclear
|
||||||
|
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
||||||
|
# .idea/
|
||||||
|
|
||||||
|
# Abstra
|
||||||
|
# Abstra is an AI-powered process automation framework.
|
||||||
|
# Ignore directories containing user credentials, local state, and settings.
|
||||||
|
# Learn more at https://abstra.io/docs
|
||||||
|
.abstra/
|
||||||
|
|
||||||
|
# Visual Studio Code
|
||||||
|
# Visual Studio Code specific template is maintained in a separate VisualStudioCode.gitignore
|
||||||
|
# that can be found at https://github.com/github/gitignore/blob/main/Global/VisualStudioCode.gitignore
|
||||||
|
# and can be added to the global gitignore or merged into this file. However, if you prefer,
|
||||||
|
# you could uncomment the following to ignore the entire vscode folder
|
||||||
|
.vscode/
|
||||||
|
|
||||||
|
# Ruff stuff:
|
||||||
|
.ruff_cache/
|
||||||
|
|
||||||
|
# PyPI configuration file
|
||||||
|
.pypirc
|
||||||
|
|
||||||
|
# Marimo
|
||||||
|
marimo/_static/
|
||||||
|
marimo/_lsp/
|
||||||
|
__marimo__/
|
||||||
|
|
||||||
|
# Streamlit
|
||||||
|
.streamlit/secrets.toml
|
||||||
|
|
||||||
|
static/*
|
||||||
|
certificates/*
|
||||||
24
backend/globalConfigs.py
Normal file
24
backend/globalConfigs.py
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
from pathlib import Path
|
||||||
|
import socket
|
||||||
|
|
||||||
|
# Certificate settings
|
||||||
|
CERT_BASE = Path(__file__).parent
|
||||||
|
CERT = Path(CERT_BASE / f"certificates/peer-certificate.der")
|
||||||
|
PRIVATE_KEY = Path(CERT_BASE / f"certificates/peer-private-key.pem")
|
||||||
|
HOST_NAME = socket.gethostname()
|
||||||
|
CLIENT_APP_URI = f"urn:{HOST_NAME}:client:hmi"
|
||||||
|
|
||||||
|
# OPC UA settings
|
||||||
|
OPC_UA_URL = "opc.tcp://192.168.0.148:4840"
|
||||||
|
OPC_UA_USER = "Administrator"
|
||||||
|
OPC_UA_PW = "11sep8819"
|
||||||
|
NAMESPACE = "urn:BeckhoffAutomation:Ua:PLC1"
|
||||||
|
|
||||||
|
EVENT_LOGGER_NODE_ID = "ns=8;s=eventlogger"
|
||||||
|
TC_EVENT_DATATYPE_NODE_ID = "ns=5;i=4000"
|
||||||
|
|
||||||
|
CHECK_INTERVAL = 1.0 # seconds
|
||||||
|
RECONNECT_INTERVAL = 5.0 # seconds
|
||||||
|
|
||||||
|
# Publishing interval to the clients (rate limiting)
|
||||||
|
PUBLISH_INTERVAL = 0.2 # 200 ms
|
||||||
12
backend/globalData.py
Normal file
12
backend/globalData.py
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
import asyncio
|
||||||
|
from asyncua import Client
|
||||||
|
from asyncua.common.subscription import Subscription
|
||||||
|
|
||||||
|
opc_ua_client: Client
|
||||||
|
clients = {} # {WebSocket: set(subscribed_tags)}
|
||||||
|
node_storage = {}
|
||||||
|
event_storage = {}
|
||||||
|
queue = asyncio.Queue(maxsize=10000)
|
||||||
|
event_queue = asyncio.Queue(maxsize=1000)
|
||||||
|
subscription_handler: Subscription
|
||||||
|
subscribed_nodes = set()
|
||||||
115
backend/main.py
Normal file
115
backend/main.py
Normal file
@@ -0,0 +1,115 @@
|
|||||||
|
from fastapi import FastAPI
|
||||||
|
from routers import websocket
|
||||||
|
from fastapi.middleware.cors import CORSMiddleware
|
||||||
|
|
||||||
|
import asyncio
|
||||||
|
import msgpack
|
||||||
|
|
||||||
|
#from globalData import node_storage, clients
|
||||||
|
import globalData as gd
|
||||||
|
import globalConfigs as gc
|
||||||
|
from opcuaHandler import connection_watcher2
|
||||||
|
|
||||||
|
update_buffer = {}
|
||||||
|
|
||||||
|
async def data_consumer(queue):
|
||||||
|
"""Asynchronous consumer that stores the latest values."""
|
||||||
|
global update_buffer
|
||||||
|
|
||||||
|
while True:
|
||||||
|
nodeid, val = await queue.get()
|
||||||
|
gd.node_storage[nodeid] = val
|
||||||
|
update_buffer[nodeid] = val
|
||||||
|
queue.task_done()
|
||||||
|
|
||||||
|
|
||||||
|
async def event_consumer(queue):
|
||||||
|
while True:
|
||||||
|
node_id, event_data = await queue.get()
|
||||||
|
if node_id in gd.event_storage:
|
||||||
|
event_data.Message = gd.event_storage[node_id].Message
|
||||||
|
gd.event_storage[node_id].update(event_data)
|
||||||
|
else:
|
||||||
|
gd.event_storage[node_id] = event_data
|
||||||
|
|
||||||
|
print(gd.event_storage)
|
||||||
|
|
||||||
|
for ws in list(gd.clients):
|
||||||
|
print("Sending event:", {"e": {node_id: event_data}})
|
||||||
|
await ws.send_bytes(msgpack.packb({"e": {node_id: event_data}}))
|
||||||
|
queue.task_done()
|
||||||
|
|
||||||
|
|
||||||
|
async def broadcast_deltas(changes: dict):
|
||||||
|
payload = msgpack.packb({"u": changes})
|
||||||
|
for ws, subscribed_tags in list(gd.clients.items()):
|
||||||
|
relevant = {tag: val for tag, val in changes.items() if tag in subscribed_tags}
|
||||||
|
if relevant:
|
||||||
|
await ws.send_bytes(msgpack.packb({"u": relevant}))
|
||||||
|
|
||||||
|
|
||||||
|
async def broadcast_loop():
|
||||||
|
global update_buffer
|
||||||
|
while True:
|
||||||
|
await asyncio.sleep(gc.PUBLISH_INTERVAL)
|
||||||
|
|
||||||
|
if not update_buffer:
|
||||||
|
continue
|
||||||
|
|
||||||
|
changes = dict(update_buffer)
|
||||||
|
update_buffer.clear()
|
||||||
|
|
||||||
|
for ws, subscribed_tags in list(gd.clients.items()):
|
||||||
|
relevant = {tag: val for tag, val in changes.items() if tag in subscribed_tags}
|
||||||
|
if relevant:
|
||||||
|
await ws.send_bytes(msgpack.packb({"u": relevant}))
|
||||||
|
|
||||||
|
|
||||||
|
async def lifespan(app: FastAPI):
|
||||||
|
"""
|
||||||
|
FastAPI lifespan context — creates the client and starts the watcher.loading="warning"
|
||||||
|
"""
|
||||||
|
#await initOPCUAConnection()
|
||||||
|
|
||||||
|
# Launch the background watcher task for the opc ua connection
|
||||||
|
watcher_task = asyncio.create_task(connection_watcher2())
|
||||||
|
|
||||||
|
# Lunch data consumer task for node value changed events
|
||||||
|
consumer_task = asyncio.create_task(data_consumer(gd.queue))
|
||||||
|
event_consumer_task = asyncio.create_task(event_consumer(gd.event_queue))
|
||||||
|
broadcast_task = asyncio.create_task(broadcast_loop())
|
||||||
|
|
||||||
|
|
||||||
|
yield # Application runs while this yields
|
||||||
|
|
||||||
|
# Cleanup on shutdown
|
||||||
|
watcher_task.cancel()
|
||||||
|
broadcast_task.cancel()
|
||||||
|
consumer_task.cancel()
|
||||||
|
event_consumer_task.cancel()
|
||||||
|
try:
|
||||||
|
await watcher_task
|
||||||
|
await consumer_task
|
||||||
|
await broadcast_task
|
||||||
|
await event_consumer_task
|
||||||
|
await gd.opc_ua_client.disconnect()
|
||||||
|
except asyncio.CancelledError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
app = FastAPI(lifespan=lifespan)
|
||||||
|
origins = [
|
||||||
|
"http://localhost:3000",
|
||||||
|
"http://127.0.0.1:3000",
|
||||||
|
]
|
||||||
|
app.add_middleware(
|
||||||
|
CORSMiddleware,
|
||||||
|
allow_origins=origins,
|
||||||
|
allow_credentials=True,
|
||||||
|
allow_methods=["*"], # Erlaubt alle HTTP-Methoden (GET, POST, etc.)
|
||||||
|
allow_headers=["*"], # Erlaubt alle Header
|
||||||
|
)
|
||||||
|
app.include_router(websocket.router)
|
||||||
|
|
||||||
|
# Serve the built frontend
|
||||||
|
#app.mount("/assets", StaticFiles(directory="static/assets"), name="assets")
|
||||||
13
backend/minimal_test.py
Normal file
13
backend/minimal_test.py
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
from asyncua import Client
|
||||||
|
import asyncio
|
||||||
|
|
||||||
|
OPC_UA_URL = "opc.tcp://192.168.0.148:4840"
|
||||||
|
|
||||||
|
async def main():
|
||||||
|
client = Client(OPC_UA_URL)
|
||||||
|
endpoints = await client.connect_and_get_server_endpoints()
|
||||||
|
for e in endpoints:
|
||||||
|
print(e.EndpointUrl, e.SecurityPolicyUri, e.SecurityMode)
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
asyncio.run(main())
|
||||||
14
backend/models.py
Normal file
14
backend/models.py
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
from pydantic import BaseModel
|
||||||
|
from asyncua import ua
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
|
class SemiAutoControl(BaseModel):
|
||||||
|
string: int
|
||||||
|
module: int
|
||||||
|
unit: int
|
||||||
|
enable: bool
|
||||||
|
|
||||||
|
class WriteNodeCommand(BaseModel):
|
||||||
|
nodeId: str
|
||||||
|
nodeValue: Any
|
||||||
|
nodeType: ua.VariantType
|
||||||
235
backend/nodeList.txt
Normal file
235
backend/nodeList.txt
Normal file
@@ -0,0 +1,235 @@
|
|||||||
|
# Semi auto mode control string 1
|
||||||
|
ns=4;s=GVL_SCADA.stSemiAutoBatteryEnable[0].stSemiAutoModul1.xSemiAutoEnableUnit1
|
||||||
|
ns=4;s=GVL_SCADA.stSemiAutoBatteryEnable[0].stSemiAutoModul1.xSemiAutoEnableUnit2
|
||||||
|
ns=4;s=GVL_SCADA.stSemiAutoBatteryEnable[0].stSemiAutoModul1.xSemiAutoEnableUnit3
|
||||||
|
ns=4;s=GVL_SCADA.stSemiAutoBatteryEnable[0].stSemiAutoModul1.xSemiAutoEnableUnit4
|
||||||
|
ns=4;s=GVL_SCADA.stSemiAutoBatteryEnable[0].stSemiAutoModul2.xSemiAutoEnableUnit1
|
||||||
|
ns=4;s=GVL_SCADA.stSemiAutoBatteryEnable[0].stSemiAutoModul2.xSemiAutoEnableUnit2
|
||||||
|
ns=4;s=GVL_SCADA.stSemiAutoBatteryEnable[0].stSemiAutoModul2.xSemiAutoEnableUnit3
|
||||||
|
ns=4;s=GVL_SCADA.stSemiAutoBatteryEnable[0].stSemiAutoModul2.xSemiAutoEnableUnit4
|
||||||
|
ns=4;s=GVL_SCADA.stSemiAutoBatteryEnable[0].stSemiAutoModul3.xSemiAutoEnableUnit1
|
||||||
|
ns=4;s=GVL_SCADA.stSemiAutoBatteryEnable[0].stSemiAutoModul3.xSemiAutoEnableUnit2
|
||||||
|
ns=4;s=GVL_SCADA.stSemiAutoBatteryEnable[0].stSemiAutoModul3.xSemiAutoEnableUnit3
|
||||||
|
ns=4;s=GVL_SCADA.stSemiAutoBatteryEnable[0].stSemiAutoModul3.xSemiAutoEnableUnit4
|
||||||
|
|
||||||
|
# Semi auto mode control string 2
|
||||||
|
ns=4;s=GVL_SCADA.stSemiAutoBatteryEnable[1].stSemiAutoModul1.xSemiAutoEnableUnit1
|
||||||
|
ns=4;s=GVL_SCADA.stSemiAutoBatteryEnable[1].stSemiAutoModul1.xSemiAutoEnableUnit2
|
||||||
|
ns=4;s=GVL_SCADA.stSemiAutoBatteryEnable[1].stSemiAutoModul1.xSemiAutoEnableUnit3
|
||||||
|
ns=4;s=GVL_SCADA.stSemiAutoBatteryEnable[1].stSemiAutoModul1.xSemiAutoEnableUnit4
|
||||||
|
ns=4;s=GVL_SCADA.stSemiAutoBatteryEnable[1].stSemiAutoModul2.xSemiAutoEnableUnit1
|
||||||
|
ns=4;s=GVL_SCADA.stSemiAutoBatteryEnable[1].stSemiAutoModul2.xSemiAutoEnableUnit2
|
||||||
|
ns=4;s=GVL_SCADA.stSemiAutoBatteryEnable[1].stSemiAutoModul2.xSemiAutoEnableUnit3
|
||||||
|
ns=4;s=GVL_SCADA.stSemiAutoBatteryEnable[1].stSemiAutoModul2.xSemiAutoEnableUnit4
|
||||||
|
ns=4;s=GVL_SCADA.stSemiAutoBatteryEnable[1].stSemiAutoModul3.xSemiAutoEnableUnit1
|
||||||
|
ns=4;s=GVL_SCADA.stSemiAutoBatteryEnable[1].stSemiAutoModul3.xSemiAutoEnableUnit2
|
||||||
|
ns=4;s=GVL_SCADA.stSemiAutoBatteryEnable[1].stSemiAutoModul3.xSemiAutoEnableUnit3
|
||||||
|
ns=4;s=GVL_SCADA.stSemiAutoBatteryEnable[1].stSemiAutoModul3.xSemiAutoEnableUnit4
|
||||||
|
|
||||||
|
# Auto control mode
|
||||||
|
ns=4;s=GVL_SCADA.eCurrentControlMode
|
||||||
|
ns=4;s=GVL_SCADA.xCanChangeControlMode
|
||||||
|
|
||||||
|
# String 2 - Modul 2 - Unit 1
|
||||||
|
# Spannungsmessung
|
||||||
|
ns=4;s=GVL_SCADA.stHMIInterface[1].stHMIInterfaceModule2.stHMIInterfaceUnit1.stE31.rValue
|
||||||
|
ns=4;s=GVL_SCADA.stHMIInterface[1].stHMIInterfaceModule2.stHMIInterfaceUnit1.stE31.sUnit
|
||||||
|
|
||||||
|
# Druck segment
|
||||||
|
ns=4;s=GVL_SCADA.stHMIInterface[1].stHMIInterfaceModule2.stHMIInterfaceUnit1.stP11.rValue
|
||||||
|
ns=4;s=GVL_SCADA.stHMIInterface[1].stHMIInterfaceModule2.stHMIInterfaceUnit1.stP11.sUnit
|
||||||
|
ns=4;s=GVL_SCADA.stHMIInterface[1].stHMIInterfaceModule2.stHMIInterfaceUnit1.stP21.rValue
|
||||||
|
ns=4;s=GVL_SCADA.stHMIInterface[1].stHMIInterfaceModule2.stHMIInterfaceUnit1.stP21.sUnit
|
||||||
|
|
||||||
|
# Druck tank
|
||||||
|
ns=4;s=GVL_SCADA.stHMIInterface[1].stHMIInterfaceModule2.stHMIInterfaceUnit1.stP12.rValue
|
||||||
|
ns=4;s=GVL_SCADA.stHMIInterface[1].stHMIInterfaceModule2.stHMIInterfaceUnit1.stP12.sUnit
|
||||||
|
ns=4;s=GVL_SCADA.stHMIInterface[1].stHMIInterfaceModule2.stHMIInterfaceUnit1.stP22.rValue
|
||||||
|
ns=4;s=GVL_SCADA.stHMIInterface[1].stHMIInterfaceModule2.stHMIInterfaceUnit1.stP22.sUnit
|
||||||
|
|
||||||
|
# Temperatur
|
||||||
|
ns=4;s=GVL_SCADA.stHMIInterface[1].stHMIInterfaceModule2.stHMIInterfaceUnit1.stT11.rValue
|
||||||
|
ns=4;s=GVL_SCADA.stHMIInterface[1].stHMIInterfaceModule2.stHMIInterfaceUnit1.stT11.sUnit
|
||||||
|
ns=4;s=GVL_SCADA.stHMIInterface[1].stHMIInterfaceModule2.stHMIInterfaceUnit1.stT21.rValue
|
||||||
|
ns=4;s=GVL_SCADA.stHMIInterface[1].stHMIInterfaceModule2.stHMIInterfaceUnit1.stT21.sUnit
|
||||||
|
|
||||||
|
# Ventile
|
||||||
|
ns=4;s=GVL_SCADA.stHMIInterface[1].stHMIInterfaceModule2.stHMIInterfaceUnit1.stNS12.stOpenButton.eFeedback
|
||||||
|
ns=4;s=GVL_SCADA.stHMIInterface[1].stHMIInterfaceModule2.stHMIInterfaceUnit1.stNS12.stOpenButton.xRelease
|
||||||
|
ns=4;s=GVL_SCADA.stHMIInterface[1].stHMIInterfaceModule2.stHMIInterfaceUnit1.stNS12.stCloseButton.eFeedback
|
||||||
|
ns=4;s=GVL_SCADA.stHMIInterface[1].stHMIInterfaceModule2.stHMIInterfaceUnit1.stNS12.stCloseButton.xRelease
|
||||||
|
|
||||||
|
ns=4;s=GVL_SCADA.stHMIInterface[1].stHMIInterfaceModule2.stHMIInterfaceUnit1.stNS22.stOpenButton.eFeedback
|
||||||
|
ns=4;s=GVL_SCADA.stHMIInterface[1].stHMIInterfaceModule2.stHMIInterfaceUnit1.stNS22.stOpenButton.xRelease
|
||||||
|
ns=4;s=GVL_SCADA.stHMIInterface[1].stHMIInterfaceModule2.stHMIInterfaceUnit1.stNS22.stCloseButton.eFeedback
|
||||||
|
ns=4;s=GVL_SCADA.stHMIInterface[1].stHMIInterfaceModule2.stHMIInterfaceUnit1.stNS22.stCloseButton.xRelease
|
||||||
|
|
||||||
|
# Pumpen
|
||||||
|
ns=4;s=GVL_SCADA.stHMIInterface[1].stHMIInterfaceModule2.stHMIInterfaceUnit1.stNS11.stSetpoint.rValue
|
||||||
|
ns=4;s=GVL_SCADA.stHMIInterface[1].stHMIInterfaceModule2.stHMIInterfaceUnit1.stNS11.stProcessValue.rValue
|
||||||
|
ns=4;s=GVL_SCADA.stHMIInterface[1].stHMIInterfaceModule2.stHMIInterfaceUnit1.stNS11.stProcessValue.sUnit
|
||||||
|
ns=4;s=GVL_SCADA.stHMIInterface[1].stHMIInterfaceModule2.stHMIInterfaceUnit1.stNS11.stStartButton.eFeedback
|
||||||
|
ns=4;s=GVL_SCADA.stHMIInterface[1].stHMIInterfaceModule2.stHMIInterfaceUnit1.stNS11.stStartButton.xRelease
|
||||||
|
ns=4;s=GVL_SCADA.stHMIInterface[1].stHMIInterfaceModule2.stHMIInterfaceUnit1.stNS11.stStopButton.eFeedback
|
||||||
|
ns=4;s=GVL_SCADA.stHMIInterface[1].stHMIInterfaceModule2.stHMIInterfaceUnit1.stNS11.stStopButton.xRelease
|
||||||
|
|
||||||
|
ns=4;s=GVL_SCADA.stHMIInterface[1].stHMIInterfaceModule2.stHMIInterfaceUnit1.stNS21.stSetpoint.rValue
|
||||||
|
ns=4;s=GVL_SCADA.stHMIInterface[1].stHMIInterfaceModule2.stHMIInterfaceUnit1.stNS21.stProcessValue.rValue
|
||||||
|
ns=4;s=GVL_SCADA.stHMIInterface[1].stHMIInterfaceModule2.stHMIInterfaceUnit1.stNS21.stProcessValue.sUnit
|
||||||
|
ns=4;s=GVL_SCADA.stHMIInterface[1].stHMIInterfaceModule2.stHMIInterfaceUnit1.stNS21.stStartButton.eFeedback
|
||||||
|
ns=4;s=GVL_SCADA.stHMIInterface[1].stHMIInterfaceModule2.stHMIInterfaceUnit1.stNS21.stStartButton.xRelease
|
||||||
|
ns=4;s=GVL_SCADA.stHMIInterface[1].stHMIInterfaceModule2.stHMIInterfaceUnit1.stNS21.stStopButton.eFeedback
|
||||||
|
ns=4;s=GVL_SCADA.stHMIInterface[1].stHMIInterfaceModule2.stHMIInterfaceUnit1.stNS21.stStopButton.xRelease
|
||||||
|
|
||||||
|
# String 2 - Modul 2 - Unit 2
|
||||||
|
# Spannungsmessung
|
||||||
|
ns=4;s=GVL_SCADA.stHMIInterface[1].stHMIInterfaceModule2.stHMIInterfaceUnit2.stE31.rValue
|
||||||
|
ns=4;s=GVL_SCADA.stHMIInterface[1].stHMIInterfaceModule2.stHMIInterfaceUnit2.stE31.sUnit
|
||||||
|
|
||||||
|
# Druck segment
|
||||||
|
ns=4;s=GVL_SCADA.stHMIInterface[1].stHMIInterfaceModule2.stHMIInterfaceUnit2.stP11.rValue
|
||||||
|
ns=4;s=GVL_SCADA.stHMIInterface[1].stHMIInterfaceModule2.stHMIInterfaceUnit2.stP11.sUnit
|
||||||
|
ns=4;s=GVL_SCADA.stHMIInterface[1].stHMIInterfaceModule2.stHMIInterfaceUnit2.stP21.rValue
|
||||||
|
ns=4;s=GVL_SCADA.stHMIInterface[1].stHMIInterfaceModule2.stHMIInterfaceUnit2.stP21.sUnit
|
||||||
|
|
||||||
|
# Druck tank
|
||||||
|
ns=4;s=GVL_SCADA.stHMIInterface[1].stHMIInterfaceModule2.stHMIInterfaceUnit2.stP12.rValue
|
||||||
|
ns=4;s=GVL_SCADA.stHMIInterface[1].stHMIInterfaceModule2.stHMIInterfaceUnit2.stP12.sUnit
|
||||||
|
ns=4;s=GVL_SCADA.stHMIInterface[1].stHMIInterfaceModule2.stHMIInterfaceUnit2.stP22.rValue
|
||||||
|
ns=4;s=GVL_SCADA.stHMIInterface[1].stHMIInterfaceModule2.stHMIInterfaceUnit2.stP22.sUnit
|
||||||
|
|
||||||
|
# Temperatur
|
||||||
|
ns=4;s=GVL_SCADA.stHMIInterface[1].stHMIInterfaceModule2.stHMIInterfaceUnit2.stT11.rValue
|
||||||
|
ns=4;s=GVL_SCADA.stHMIInterface[1].stHMIInterfaceModule2.stHMIInterfaceUnit2.stT11.sUnit
|
||||||
|
ns=4;s=GVL_SCADA.stHMIInterface[1].stHMIInterfaceModule2.stHMIInterfaceUnit2.stT21.rValue
|
||||||
|
ns=4;s=GVL_SCADA.stHMIInterface[1].stHMIInterfaceModule2.stHMIInterfaceUnit2.stT21.sUnit
|
||||||
|
|
||||||
|
# Ventile
|
||||||
|
ns=4;s=GVL_SCADA.stHMIInterface[1].stHMIInterfaceModule2.stHMIInterfaceUnit2.stNS12.stOpenButton.eFeedback
|
||||||
|
ns=4;s=GVL_SCADA.stHMIInterface[1].stHMIInterfaceModule2.stHMIInterfaceUnit2.stNS12.stOpenButton.xRelease
|
||||||
|
ns=4;s=GVL_SCADA.stHMIInterface[1].stHMIInterfaceModule2.stHMIInterfaceUnit2.stNS12.stCloseButton.eFeedback
|
||||||
|
ns=4;s=GVL_SCADA.stHMIInterface[1].stHMIInterfaceModule2.stHMIInterfaceUnit2.stNS12.stCloseButton.xRelease
|
||||||
|
|
||||||
|
ns=4;s=GVL_SCADA.stHMIInterface[1].stHMIInterfaceModule2.stHMIInterfaceUnit2.stNS22.stOpenButton.eFeedback
|
||||||
|
ns=4;s=GVL_SCADA.stHMIInterface[1].stHMIInterfaceModule2.stHMIInterfaceUnit2.stNS22.stOpenButton.xRelease
|
||||||
|
ns=4;s=GVL_SCADA.stHMIInterface[1].stHMIInterfaceModule2.stHMIInterfaceUnit2.stNS22.stCloseButton.eFeedback
|
||||||
|
ns=4;s=GVL_SCADA.stHMIInterface[1].stHMIInterfaceModule2.stHMIInterfaceUnit2.stNS22.stCloseButton.xRelease
|
||||||
|
|
||||||
|
# Pumpen
|
||||||
|
ns=4;s=GVL_SCADA.stHMIInterface[1].stHMIInterfaceModule2.stHMIInterfaceUnit2.stNS11.stSetpoint.rValue
|
||||||
|
ns=4;s=GVL_SCADA.stHMIInterface[1].stHMIInterfaceModule2.stHMIInterfaceUnit2.stNS11.stProcessValue.rValue
|
||||||
|
ns=4;s=GVL_SCADA.stHMIInterface[1].stHMIInterfaceModule2.stHMIInterfaceUnit2.stNS11.stProcessValue.sUnit
|
||||||
|
ns=4;s=GVL_SCADA.stHMIInterface[1].stHMIInterfaceModule2.stHMIInterfaceUnit2.stNS11.stStartButton.eFeedback
|
||||||
|
ns=4;s=GVL_SCADA.stHMIInterface[1].stHMIInterfaceModule2.stHMIInterfaceUnit2.stNS11.stStartButton.xRelease
|
||||||
|
ns=4;s=GVL_SCADA.stHMIInterface[1].stHMIInterfaceModule2.stHMIInterfaceUnit2.stNS11.stStopButton.eFeedback
|
||||||
|
ns=4;s=GVL_SCADA.stHMIInterface[1].stHMIInterfaceModule2.stHMIInterfaceUnit2.stNS11.stStopButton.xRelease
|
||||||
|
|
||||||
|
ns=4;s=GVL_SCADA.stHMIInterface[1].stHMIInterfaceModule2.stHMIInterfaceUnit2.stNS21.stSetpoint.rValue
|
||||||
|
ns=4;s=GVL_SCADA.stHMIInterface[1].stHMIInterfaceModule2.stHMIInterfaceUnit2.stNS21.stProcessValue.rValue
|
||||||
|
ns=4;s=GVL_SCADA.stHMIInterface[1].stHMIInterfaceModule2.stHMIInterfaceUnit2.stNS21.stProcessValue.sUnit
|
||||||
|
ns=4;s=GVL_SCADA.stHMIInterface[1].stHMIInterfaceModule2.stHMIInterfaceUnit2.stNS21.stStartButton.eFeedback
|
||||||
|
ns=4;s=GVL_SCADA.stHMIInterface[1].stHMIInterfaceModule2.stHMIInterfaceUnit2.stNS21.stStartButton.xRelease
|
||||||
|
ns=4;s=GVL_SCADA.stHMIInterface[1].stHMIInterfaceModule2.stHMIInterfaceUnit2.stNS21.stStopButton.eFeedback
|
||||||
|
ns=4;s=GVL_SCADA.stHMIInterface[1].stHMIInterfaceModule2.stHMIInterfaceUnit2.stNS21.stStopButton.xRelease
|
||||||
|
|
||||||
|
# String 2 - Modul 2 - Unit 3
|
||||||
|
# Spannungsmessung
|
||||||
|
ns=4;s=GVL_SCADA.stHMIInterface[1].stHMIInterfaceModule2.stHMIInterfaceUnit3.stE31.rValue
|
||||||
|
ns=4;s=GVL_SCADA.stHMIInterface[1].stHMIInterfaceModule2.stHMIInterfaceUnit3.stE31.sUnit
|
||||||
|
|
||||||
|
# Druck segment
|
||||||
|
ns=4;s=GVL_SCADA.stHMIInterface[1].stHMIInterfaceModule2.stHMIInterfaceUnit3.stP11.rValue
|
||||||
|
ns=4;s=GVL_SCADA.stHMIInterface[1].stHMIInterfaceModule2.stHMIInterfaceUnit3.stP11.sUnit
|
||||||
|
ns=4;s=GVL_SCADA.stHMIInterface[1].stHMIInterfaceModule2.stHMIInterfaceUnit3.stP21.rValue
|
||||||
|
ns=4;s=GVL_SCADA.stHMIInterface[1].stHMIInterfaceModule2.stHMIInterfaceUnit3.stP21.sUnit
|
||||||
|
|
||||||
|
# Druck tank
|
||||||
|
ns=4;s=GVL_SCADA.stHMIInterface[1].stHMIInterfaceModule2.stHMIInterfaceUnit3.stP12.rValue
|
||||||
|
ns=4;s=GVL_SCADA.stHMIInterface[1].stHMIInterfaceModule2.stHMIInterfaceUnit3.stP12.sUnit
|
||||||
|
ns=4;s=GVL_SCADA.stHMIInterface[1].stHMIInterfaceModule2.stHMIInterfaceUnit3.stP22.rValue
|
||||||
|
ns=4;s=GVL_SCADA.stHMIInterface[1].stHMIInterfaceModule2.stHMIInterfaceUnit3.stP22.sUnit
|
||||||
|
|
||||||
|
# Temperatur
|
||||||
|
ns=4;s=GVL_SCADA.stHMIInterface[1].stHMIInterfaceModule2.stHMIInterfaceUnit3.stT11.rValue
|
||||||
|
ns=4;s=GVL_SCADA.stHMIInterface[1].stHMIInterfaceModule2.stHMIInterfaceUnit3.stT11.sUnit
|
||||||
|
ns=4;s=GVL_SCADA.stHMIInterface[1].stHMIInterfaceModule2.stHMIInterfaceUnit3.stT21.rValue
|
||||||
|
ns=4;s=GVL_SCADA.stHMIInterface[1].stHMIInterfaceModule2.stHMIInterfaceUnit3.stT21.sUnit
|
||||||
|
|
||||||
|
# Ventile
|
||||||
|
ns=4;s=GVL_SCADA.stHMIInterface[1].stHMIInterfaceModule2.stHMIInterfaceUnit3.stNS12.stOpenButton.eFeedback
|
||||||
|
ns=4;s=GVL_SCADA.stHMIInterface[1].stHMIInterfaceModule2.stHMIInterfaceUnit3.stNS12.stOpenButton.xRelease
|
||||||
|
ns=4;s=GVL_SCADA.stHMIInterface[1].stHMIInterfaceModule2.stHMIInterfaceUnit3.stNS12.stCloseButton.eFeedback
|
||||||
|
ns=4;s=GVL_SCADA.stHMIInterface[1].stHMIInterfaceModule2.stHMIInterfaceUnit3.stNS12.stCloseButton.xRelease
|
||||||
|
|
||||||
|
ns=4;s=GVL_SCADA.stHMIInterface[1].stHMIInterfaceModule2.stHMIInterfaceUnit3.stNS22.stOpenButton.eFeedback
|
||||||
|
ns=4;s=GVL_SCADA.stHMIInterface[1].stHMIInterfaceModule2.stHMIInterfaceUnit3.stNS22.stOpenButton.xRelease
|
||||||
|
ns=4;s=GVL_SCADA.stHMIInterface[1].stHMIInterfaceModule2.stHMIInterfaceUnit3.stNS22.stCloseButton.eFeedback
|
||||||
|
ns=4;s=GVL_SCADA.stHMIInterface[1].stHMIInterfaceModule2.stHMIInterfaceUnit3.stNS22.stCloseButton.xRelease
|
||||||
|
|
||||||
|
# Pumpen
|
||||||
|
ns=4;s=GVL_SCADA.stHMIInterface[1].stHMIInterfaceModule2.stHMIInterfaceUnit3.stNS11.stSetpoint.rValue
|
||||||
|
ns=4;s=GVL_SCADA.stHMIInterface[1].stHMIInterfaceModule2.stHMIInterfaceUnit3.stNS11.stProcessValue.rValue
|
||||||
|
ns=4;s=GVL_SCADA.stHMIInterface[1].stHMIInterfaceModule2.stHMIInterfaceUnit3.stNS11.stProcessValue.sUnit
|
||||||
|
ns=4;s=GVL_SCADA.stHMIInterface[1].stHMIInterfaceModule2.stHMIInterfaceUnit3.stNS11.stStartButton.eFeedback
|
||||||
|
ns=4;s=GVL_SCADA.stHMIInterface[1].stHMIInterfaceModule2.stHMIInterfaceUnit3.stNS11.stStartButton.xRelease
|
||||||
|
ns=4;s=GVL_SCADA.stHMIInterface[1].stHMIInterfaceModule2.stHMIInterfaceUnit3.stNS11.stStopButton.eFeedback
|
||||||
|
ns=4;s=GVL_SCADA.stHMIInterface[1].stHMIInterfaceModule2.stHMIInterfaceUnit3.stNS11.stStopButton.xRelease
|
||||||
|
|
||||||
|
ns=4;s=GVL_SCADA.stHMIInterface[1].stHMIInterfaceModule2.stHMIInterfaceUnit3.stNS21.stSetpoint.rValue
|
||||||
|
ns=4;s=GVL_SCADA.stHMIInterface[1].stHMIInterfaceModule2.stHMIInterfaceUnit3.stNS21.stProcessValue.rValue
|
||||||
|
ns=4;s=GVL_SCADA.stHMIInterface[1].stHMIInterfaceModule2.stHMIInterfaceUnit3.stNS21.stProcessValue.sUnit
|
||||||
|
ns=4;s=GVL_SCADA.stHMIInterface[1].stHMIInterfaceModule2.stHMIInterfaceUnit3.stNS21.stStartButton.eFeedback
|
||||||
|
ns=4;s=GVL_SCADA.stHMIInterface[1].stHMIInterfaceModule2.stHMIInterfaceUnit3.stNS21.stStartButton.xRelease
|
||||||
|
ns=4;s=GVL_SCADA.stHMIInterface[1].stHMIInterfaceModule2.stHMIInterfaceUnit3.stNS21.stStopButton.eFeedback
|
||||||
|
ns=4;s=GVL_SCADA.stHMIInterface[1].stHMIInterfaceModule2.stHMIInterfaceUnit3.stNS21.stStopButton.xRelease
|
||||||
|
|
||||||
|
# String 2 - Modul 2 - Unit 4
|
||||||
|
# Spannungsmessung
|
||||||
|
ns=4;s=GVL_SCADA.stHMIInterface[1].stHMIInterfaceModule2.stHMIInterfaceUnit4.stE31.rValue
|
||||||
|
ns=4;s=GVL_SCADA.stHMIInterface[1].stHMIInterfaceModule2.stHMIInterfaceUnit4.stE31.sUnit
|
||||||
|
|
||||||
|
# Druck segment
|
||||||
|
ns=4;s=GVL_SCADA.stHMIInterface[1].stHMIInterfaceModule2.stHMIInterfaceUnit4.stP11.rValue
|
||||||
|
ns=4;s=GVL_SCADA.stHMIInterface[1].stHMIInterfaceModule2.stHMIInterfaceUnit4.stP11.sUnit
|
||||||
|
ns=4;s=GVL_SCADA.stHMIInterface[1].stHMIInterfaceModule2.stHMIInterfaceUnit4.stP21.rValue
|
||||||
|
ns=4;s=GVL_SCADA.stHMIInterface[1].stHMIInterfaceModule2.stHMIInterfaceUnit4.stP21.sUnit
|
||||||
|
|
||||||
|
# Druck tank
|
||||||
|
ns=4;s=GVL_SCADA.stHMIInterface[1].stHMIInterfaceModule2.stHMIInterfaceUnit4.stP12.rValue
|
||||||
|
ns=4;s=GVL_SCADA.stHMIInterface[1].stHMIInterfaceModule2.stHMIInterfaceUnit4.stP12.sUnit
|
||||||
|
ns=4;s=GVL_SCADA.stHMIInterface[1].stHMIInterfaceModule2.stHMIInterfaceUnit4.stP22.rValue
|
||||||
|
ns=4;s=GVL_SCADA.stHMIInterface[1].stHMIInterfaceModule2.stHMIInterfaceUnit4.stP22.sUnit
|
||||||
|
|
||||||
|
# Temperatur
|
||||||
|
ns=4;s=GVL_SCADA.stHMIInterface[1].stHMIInterfaceModule2.stHMIInterfaceUnit4.stT11.rValue
|
||||||
|
ns=4;s=GVL_SCADA.stHMIInterface[1].stHMIInterfaceModule2.stHMIInterfaceUnit4.stT11.sUnit
|
||||||
|
ns=4;s=GVL_SCADA.stHMIInterface[1].stHMIInterfaceModule2.stHMIInterfaceUnit4.stT21.rValue
|
||||||
|
ns=4;s=GVL_SCADA.stHMIInterface[1].stHMIInterfaceModule2.stHMIInterfaceUnit4.stT21.sUnit
|
||||||
|
|
||||||
|
# Ventile
|
||||||
|
ns=4;s=GVL_SCADA.stHMIInterface[1].stHMIInterfaceModule2.stHMIInterfaceUnit4.stNS12.stOpenButton.eFeedback
|
||||||
|
ns=4;s=GVL_SCADA.stHMIInterface[1].stHMIInterfaceModule2.stHMIInterfaceUnit4.stNS12.stOpenButton.xRelease
|
||||||
|
ns=4;s=GVL_SCADA.stHMIInterface[1].stHMIInterfaceModule2.stHMIInterfaceUnit4.stNS12.stCloseButton.eFeedback
|
||||||
|
ns=4;s=GVL_SCADA.stHMIInterface[1].stHMIInterfaceModule2.stHMIInterfaceUnit4.stNS12.stCloseButton.xRelease
|
||||||
|
|
||||||
|
ns=4;s=GVL_SCADA.stHMIInterface[1].stHMIInterfaceModule2.stHMIInterfaceUnit4.stNS22.stOpenButton.eFeedback
|
||||||
|
ns=4;s=GVL_SCADA.stHMIInterface[1].stHMIInterfaceModule2.stHMIInterfaceUnit4.stNS22.stOpenButton.xRelease
|
||||||
|
ns=4;s=GVL_SCADA.stHMIInterface[1].stHMIInterfaceModule2.stHMIInterfaceUnit4.stNS22.stCloseButton.eFeedback
|
||||||
|
ns=4;s=GVL_SCADA.stHMIInterface[1].stHMIInterfaceModule2.stHMIInterfaceUnit4.stNS22.stCloseButton.xRelease
|
||||||
|
|
||||||
|
# Pumpen
|
||||||
|
ns=4;s=GVL_SCADA.stHMIInterface[1].stHMIInterfaceModule2.stHMIInterfaceUnit4.stNS11.stSetpoint.rValue
|
||||||
|
ns=4;s=GVL_SCADA.stHMIInterface[1].stHMIInterfaceModule2.stHMIInterfaceUnit4.stNS11.stProcessValue.rValue
|
||||||
|
ns=4;s=GVL_SCADA.stHMIInterface[1].stHMIInterfaceModule2.stHMIInterfaceUnit4.stNS11.stProcessValue.sUnit
|
||||||
|
ns=4;s=GVL_SCADA.stHMIInterface[1].stHMIInterfaceModule2.stHMIInterfaceUnit4.stNS11.stStartButton.eFeedback
|
||||||
|
ns=4;s=GVL_SCADA.stHMIInterface[1].stHMIInterfaceModule2.stHMIInterfaceUnit4.stNS11.stStartButton.xRelease
|
||||||
|
ns=4;s=GVL_SCADA.stHMIInterface[1].stHMIInterfaceModule2.stHMIInterfaceUnit4.stNS11.stStopButton.eFeedback
|
||||||
|
ns=4;s=GVL_SCADA.stHMIInterface[1].stHMIInterfaceModule2.stHMIInterfaceUnit4.stNS11.stStopButton.xRelease
|
||||||
|
|
||||||
|
ns=4;s=GVL_SCADA.stHMIInterface[1].stHMIInterfaceModule2.stHMIInterfaceUnit4.stNS21.stSetpoint.rValue
|
||||||
|
ns=4;s=GVL_SCADA.stHMIInterface[1].stHMIInterfaceModule2.stHMIInterfaceUnit4.stNS21.stProcessValue.rValue
|
||||||
|
ns=4;s=GVL_SCADA.stHMIInterface[1].stHMIInterfaceModule2.stHMIInterfaceUnit4.stNS21.stProcessValue.sUnit
|
||||||
|
ns=4;s=GVL_SCADA.stHMIInterface[1].stHMIInterfaceModule2.stHMIInterfaceUnit4.stNS21.stStartButton.eFeedback
|
||||||
|
ns=4;s=GVL_SCADA.stHMIInterface[1].stHMIInterfaceModule2.stHMIInterfaceUnit4.stNS21.stStartButton.xRelease
|
||||||
|
ns=4;s=GVL_SCADA.stHMIInterface[1].stHMIInterfaceModule2.stHMIInterfaceUnit4.stNS21.stStopButton.eFeedback
|
||||||
|
ns=4;s=GVL_SCADA.stHMIInterface[1].stHMIInterfaceModule2.stHMIInterfaceUnit4.stNS21.stStopButton.xRelease
|
||||||
45
backend/oldCode.py
Normal file
45
backend/oldCode.py
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
#from fastapi.responses import StreamingResponse, FileResponse
|
||||||
|
|
||||||
|
# @app.get("/events")
|
||||||
|
# async def sse_endpoint():
|
||||||
|
# """SSE endpoint to stream OPC UA updates"""
|
||||||
|
# return StreamingResponse(event_stream(), media_type="text/event-stream")
|
||||||
|
|
||||||
|
# Serve index.html
|
||||||
|
#@app.get("/")
|
||||||
|
#async def serve_frontend():
|
||||||
|
# return FileResponse("static/index.html")
|
||||||
|
|
||||||
|
# @app.get("/modes/semiAuto/{strng}")
|
||||||
|
# async def semiAutoStringEventStream(strng):
|
||||||
|
# return StreamingResponse(strng_semi_auto_data_stream(), media_type="text/event-stream")
|
||||||
|
|
||||||
|
# async def event_stream():
|
||||||
|
# # Send current value immediately if available
|
||||||
|
# if latest_value["value"] is not None:
|
||||||
|
# yield f"data: {json.dumps(latest_value)}\n\n"
|
||||||
|
|
||||||
|
# while True:
|
||||||
|
# await new_value_event.wait() # wait for new value
|
||||||
|
# data = json.dumps(latest_value)
|
||||||
|
# yield f"data: {data}\n\n"
|
||||||
|
# new_value_event.clear() # reset event and wait for next update
|
||||||
|
|
||||||
|
# async def strng_semi_auto_data_stream():
|
||||||
|
# while True:
|
||||||
|
# await new_value_event.wait()
|
||||||
|
# data
|
||||||
|
|
||||||
|
# @app.get("/getMode")
|
||||||
|
# async def set_mode(request: Request):
|
||||||
|
# client = request.app.state.opcua_client
|
||||||
|
# nsidx = request.app.state.ns_idx
|
||||||
|
# #node = client.get_node(f"ns={nsidx};s=GVL_SCADA.eRequestedControlMode")
|
||||||
|
# #node = client.get_node(f"ns={nsidx};s=GVL_SCADA.eCurrentControlMode")
|
||||||
|
# try:
|
||||||
|
# node = client.get_node(f"ns={nsidx};s=GVL_SCADA.stHMIInterface[0].stHMIInterfaceModule3.stHMIInterfaceUnit1.stP11.rValue")
|
||||||
|
# value = await node.read_value()
|
||||||
|
# print(value)
|
||||||
|
# return {"node_id": "GVL_SCADA.eCurrentControlMode", "value": value}
|
||||||
|
# except:
|
||||||
|
# return {"node_id": "GVL_SCADA.eCurrentControlMode", "value": 0.0}
|
||||||
193
backend/opcuaHandler.py
Normal file
193
backend/opcuaHandler.py
Normal file
@@ -0,0 +1,193 @@
|
|||||||
|
import os
|
||||||
|
|
||||||
|
from asyncua import Client, ua, Node
|
||||||
|
from asyncua.crypto.security_policies import SecurityPolicyBasic256Sha256
|
||||||
|
from asyncua.crypto.cert_gen import setup_self_signed_certificate
|
||||||
|
from cryptography.x509.oid import ExtendedKeyUsageOID
|
||||||
|
|
||||||
|
import asyncio
|
||||||
|
|
||||||
|
import globalConfigs as gc
|
||||||
|
import globalData as gd
|
||||||
|
|
||||||
|
|
||||||
|
new_value_event = asyncio.Event() # signals when a new value arrives
|
||||||
|
|
||||||
|
session_id = 0
|
||||||
|
|
||||||
|
class SubHandler:
|
||||||
|
"""
|
||||||
|
Subscription Handler. To receive events from server for a subscription
|
||||||
|
This class is just a sample class. Whatever class having these methods can be used
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, queue, event_queue):
|
||||||
|
self.queue = queue
|
||||||
|
self.event_queue = event_queue
|
||||||
|
|
||||||
|
def datachange_notification(self, node: Node, val, data):
|
||||||
|
"""
|
||||||
|
called for every datachange notification from server
|
||||||
|
"""
|
||||||
|
self.queue.put_nowait((node.nodeid.to_string(), val))
|
||||||
|
|
||||||
|
def event_notification(self, event: ua.EventNotificationList):
|
||||||
|
"""
|
||||||
|
called for every event notification from server
|
||||||
|
"""
|
||||||
|
# print("=== Event empfangen ===")
|
||||||
|
# print("NodeId:", event.NodeId.Identifier)
|
||||||
|
# print("Severity:", event.Severity)
|
||||||
|
# print("Message:", event.Message.Text)
|
||||||
|
# print("ActiveState:", getattr(event, "ActiveState/Id"))
|
||||||
|
# print("ActiveStateTransTime:", getattr(event, "ActiveState/TransitionTime"))
|
||||||
|
# print("ConfirmedState:", getattr(event, "ConfirmedState/Id"))
|
||||||
|
# print("ConfirmedStateTransTime:", getattr(event, "ConfirmedState/TransitionTime"))
|
||||||
|
# print("Retain:", event.Retain)
|
||||||
|
# print("------------------------")
|
||||||
|
|
||||||
|
event_data = {
|
||||||
|
"Severity": event.Severity,
|
||||||
|
"Message": event.Message.Text,
|
||||||
|
"ActiveState": getattr(event, "ActiveState/Id"),
|
||||||
|
"ConfirmedState": getattr(event, "ConfirmedState/Id"),
|
||||||
|
"ActiveStateTransTime": getattr(event, "ActiveState/TransitionTime").isoformat(),
|
||||||
|
"ConfirmedStateTransTime": getattr(event, "ConfirmedState/TransitionTime").isoformat(),
|
||||||
|
"Retain": event.Retain
|
||||||
|
}
|
||||||
|
self.event_queue.put_nowait((event.NodeId.Identifier, event_data))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def status_change_notification(self, status: ua.StatusChangeNotification):
|
||||||
|
"""
|
||||||
|
called for every status change notification from server
|
||||||
|
"""
|
||||||
|
print("status_notification %s", status)
|
||||||
|
|
||||||
|
|
||||||
|
async def connection_watcher2():
|
||||||
|
|
||||||
|
await initOPCUAConnection()
|
||||||
|
|
||||||
|
connected = False
|
||||||
|
|
||||||
|
while not connected:
|
||||||
|
try:
|
||||||
|
# In the first round we can use the normal connect
|
||||||
|
await gd.opc_ua_client.connect()
|
||||||
|
connected = True
|
||||||
|
except Exception as e:
|
||||||
|
print(e)
|
||||||
|
connected = False
|
||||||
|
print(f"Reconnecting in {gc.RECONNECT_INTERVAL} seconds")
|
||||||
|
await asyncio.sleep(gc.RECONNECT_INTERVAL)
|
||||||
|
|
||||||
|
if connected:
|
||||||
|
await createSubscriptions()
|
||||||
|
|
||||||
|
# Periodically check for connection
|
||||||
|
while True:
|
||||||
|
if connected:
|
||||||
|
try:
|
||||||
|
await gd.opc_ua_client.check_connection()
|
||||||
|
await asyncio.sleep(gc.RECONNECT_INTERVAL)
|
||||||
|
except ua.uaerrors._auto.BadConnectionClosed:
|
||||||
|
connected = False
|
||||||
|
gd.opc_ua_client.disconnect_sessionless()
|
||||||
|
print(f"Reconnecting in {gc.RECONNECT_INTERVAL} seconds")
|
||||||
|
await asyncio.sleep(gc.RECONNECT_INTERVAL)
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
await gd.opc_ua_client.connect_sessionless()
|
||||||
|
result = await gd.opc_ua_client.uaclient.activate_session()
|
||||||
|
print(result)
|
||||||
|
connected = True
|
||||||
|
except Exception:
|
||||||
|
print(f"Reconnecting in {gc.RECONNECT_INTERVAL} seconds")
|
||||||
|
await asyncio.sleep(gc.RECONNECT_INTERVAL)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
async def connection_watcher():
|
||||||
|
"""
|
||||||
|
Background task to keep checking OPC UA client connection.
|
||||||
|
Reconnects automatically if disconnected.
|
||||||
|
"""
|
||||||
|
while True:
|
||||||
|
try:
|
||||||
|
gd.opc_ua_client = Client(url=gc.OPC_UA_URL)
|
||||||
|
|
||||||
|
gd.opc_ua_client.application_uri = gc.CLIENT_APP_URI
|
||||||
|
await gd.opc_ua_client.set_security(
|
||||||
|
SecurityPolicyBasic256Sha256,
|
||||||
|
certificate=str(gc.CERT),
|
||||||
|
private_key=str(gc.PRIVATE_KEY)
|
||||||
|
)
|
||||||
|
gd.opc_ua_client.set_user(gc.OPC_UA_USER)
|
||||||
|
gd.opc_ua_client.set_password(gc.OPC_UA_PW)
|
||||||
|
|
||||||
|
async with gd.opc_ua_client:
|
||||||
|
print("Connected")
|
||||||
|
#print("Creating subscriptions")
|
||||||
|
#await createSubscriptions()
|
||||||
|
gd.subscription_handler = await gd.opc_ua_client.create_subscription(250, SubHandler(gd.queue, gd.event_queue))
|
||||||
|
event_logger_node = gd.opc_ua_client.get_node("ns=8;s=eventlogger")
|
||||||
|
await gd.subscription_handler.subscribe_events(event_logger_node, evtypes=[ua.ObjectIds.BaseEventType, "ns=5;i=4000"])
|
||||||
|
print("Registered events")
|
||||||
|
while True:
|
||||||
|
await asyncio.sleep(gc.CHECK_INTERVAL)
|
||||||
|
await gd.opc_ua_client.check_connection() # Throws a exception if connection is lost
|
||||||
|
except ua.uaerrors._auto.BadConnectionClosed:
|
||||||
|
print("Lost connection")
|
||||||
|
print(f"Reconnecting in {gc.RECONNECT_INTERVAL} seconds")
|
||||||
|
await asyncio.sleep(gc.RECONNECT_INTERVAL)
|
||||||
|
except Exception as e:
|
||||||
|
print(e)
|
||||||
|
break
|
||||||
|
|
||||||
|
|
||||||
|
async def initOPCUAConnection():
|
||||||
|
if (not os.path.isfile(gc.CERT)) or (not os.path.isfile(gc.PRIVATE_KEY)):
|
||||||
|
await setup_self_signed_certificate(
|
||||||
|
gc.PRIVATE_KEY,
|
||||||
|
gc.CERT,
|
||||||
|
gc.CLIENT_APP_URI,
|
||||||
|
gc.HOST_NAME,
|
||||||
|
[ExtendedKeyUsageOID.CLIENT_AUTH],
|
||||||
|
{
|
||||||
|
"countryName": "DE",
|
||||||
|
"stateOrProvinceName": "NRW",
|
||||||
|
"localityName": "HMI backend",
|
||||||
|
"organizationName": "Heisig GmbH",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
gd.opc_ua_client = Client(url=gc.OPC_UA_URL)
|
||||||
|
gd.opc_ua_client.application_uri = gc.CLIENT_APP_URI
|
||||||
|
|
||||||
|
await gd.opc_ua_client.set_security(
|
||||||
|
SecurityPolicyBasic256Sha256,
|
||||||
|
certificate=str(gc.CERT),
|
||||||
|
private_key=str(gc.PRIVATE_KEY)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
gd.opc_ua_client.set_user(gc.OPC_UA_USER)
|
||||||
|
gd.opc_ua_client.set_password(gc.OPC_UA_PW)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
async def createSubscriptions():
|
||||||
|
# sub_param = ua.CreateSubscriptionParameters()
|
||||||
|
# sub_param.RequestedPublishingInterval = gc.PUBLISH_INTERVAL
|
||||||
|
# sub_param.RequestedMaxKeepAliveCount = 5
|
||||||
|
# sub_param.RequestedLifetimeCount = 10
|
||||||
|
# sub_param.MaxNotificationsPerPublish = 0
|
||||||
|
|
||||||
|
gd.subscription_handler = await gd.opc_ua_client.create_subscription(200, SubHandler(gd.queue, gd.event_queue))
|
||||||
|
|
||||||
|
# Create an event subscription
|
||||||
|
event_logger_node = gd.opc_ua_client.get_node(gc.EVENT_LOGGER_NODE_ID)
|
||||||
|
await gd.subscription_handler.subscribe_events(event_logger_node, evtypes=[ua.ObjectIds.BaseEventType, gc.TC_EVENT_DATATYPE_NODE_ID])
|
||||||
BIN
backend/requirements.txt
Normal file
BIN
backend/requirements.txt
Normal file
Binary file not shown.
0
backend/routers/__init__.py
Normal file
0
backend/routers/__init__.py
Normal file
63
backend/routers/websocket.py
Normal file
63
backend/routers/websocket.py
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
from fastapi import WebSocket, APIRouter
|
||||||
|
import msgpack
|
||||||
|
import globalData as gd
|
||||||
|
|
||||||
|
from asyncua import ua
|
||||||
|
from models import WriteNodeCommand
|
||||||
|
|
||||||
|
router = APIRouter()
|
||||||
|
|
||||||
|
@router.websocket("/ws")
|
||||||
|
async def websocket_endpoint(ws: WebSocket):
|
||||||
|
global clients
|
||||||
|
await ws.accept()
|
||||||
|
gd.clients[ws] = set()
|
||||||
|
|
||||||
|
try:
|
||||||
|
while True:
|
||||||
|
# empfange binäre Daten vom Client
|
||||||
|
raw = await ws.receive_bytes()
|
||||||
|
data = msgpack.unpackb(raw, raw=False)
|
||||||
|
|
||||||
|
# Subscription handling
|
||||||
|
if "subscribe" in data:
|
||||||
|
gd.clients[ws].update(data["subscribe"])
|
||||||
|
diff_set = gd.subscribed_nodes.symmetric_difference(data["subscribe"])
|
||||||
|
nodes = [gd.opc_ua_client.get_node(f"{nid}") for nid in diff_set]
|
||||||
|
await gd.subscription_handler.subscribe_data_change(nodes)
|
||||||
|
|
||||||
|
initial_values = {
|
||||||
|
nid: val
|
||||||
|
for nid in gd.node_storage
|
||||||
|
if (val := gd.node_storage.get(nid)) is not None
|
||||||
|
}
|
||||||
|
|
||||||
|
initial_events = {
|
||||||
|
nid: val
|
||||||
|
for nid in gd.event_storage
|
||||||
|
if (val := gd.event_storage.get(nid)) is not None
|
||||||
|
}
|
||||||
|
|
||||||
|
# if initial_values:
|
||||||
|
await ws.send_bytes(msgpack.packb({"initial": {"values": initial_values, "events": initial_events}}))
|
||||||
|
|
||||||
|
elif "unsubscribe" in data:
|
||||||
|
gd.clients[ws].pop(data["unsubscribe"])
|
||||||
|
|
||||||
|
elif "command" in data:
|
||||||
|
await handle_command(data["command"])
|
||||||
|
except Exception:
|
||||||
|
if ws in gd.clients:
|
||||||
|
del gd.clients[ws]
|
||||||
|
|
||||||
|
async def handle_command(cmd):
|
||||||
|
try:
|
||||||
|
wnc = WriteNodeCommand.model_validate(cmd)
|
||||||
|
node = gd.opc_ua_client.get_node(cmd["nodeId"])
|
||||||
|
variantType = ua.VariantType(wnc.nodeType)
|
||||||
|
if variantType == ua.VariantType.Float:
|
||||||
|
wnc.nodeValue = float(wnc.nodeValue)
|
||||||
|
dv = ua.DataValue(ua.Variant(Value=wnc.nodeValue, VariantType=variantType))
|
||||||
|
await node.write_value(value=dv)
|
||||||
|
except Exception as e:
|
||||||
|
print(e)
|
||||||
4
frontend/.browserslistrc
Normal file
4
frontend/.browserslistrc
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
> 1%
|
||||||
|
last 2 versions
|
||||||
|
not dead
|
||||||
|
not ie 11
|
||||||
5
frontend/.editorconfig
Normal file
5
frontend/.editorconfig
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
[*.{js,jsx,ts,tsx,vue}]
|
||||||
|
indent_style = space
|
||||||
|
indent_size = 2
|
||||||
|
trim_trailing_whitespace = true
|
||||||
|
insert_final_newline = true
|
||||||
22
frontend/.gitignore
vendored
Normal file
22
frontend/.gitignore
vendored
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
.DS_Store
|
||||||
|
node_modules
|
||||||
|
/dist
|
||||||
|
|
||||||
|
# local env files
|
||||||
|
.env.local
|
||||||
|
.env.*.local
|
||||||
|
|
||||||
|
# Log files
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
pnpm-debug.log*
|
||||||
|
|
||||||
|
# Editor directories and files
|
||||||
|
.idea
|
||||||
|
.vscode
|
||||||
|
*.suo
|
||||||
|
*.ntvs*
|
||||||
|
*.njsproj
|
||||||
|
*.sln
|
||||||
|
*.sw?
|
||||||
79
frontend/README.md
Normal file
79
frontend/README.md
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
# Vuetify (Default)
|
||||||
|
|
||||||
|
This is the official scaffolding tool for Vuetify, designed to give you a head start in building your new Vuetify application. It sets up a base template with all the necessary configurations and standard directory structure, enabling you to begin development without the hassle of setting up the project from scratch.
|
||||||
|
|
||||||
|
## ❗️ Important Links
|
||||||
|
|
||||||
|
- 📄 [Docs](https://vuetifyjs.com/)
|
||||||
|
- 🚨 [Issues](https://issues.vuetifyjs.com/)
|
||||||
|
- 🏬 [Store](https://store.vuetifyjs.com/)
|
||||||
|
- 🎮 [Playground](https://play.vuetifyjs.com/)
|
||||||
|
- 💬 [Discord](https://community.vuetifyjs.com)
|
||||||
|
|
||||||
|
## 💿 Install
|
||||||
|
|
||||||
|
Set up your project using your preferred package manager. Use the corresponding command to install the dependencies:
|
||||||
|
|
||||||
|
| Package Manager | Command |
|
||||||
|
|---------------------------------------------------------------|----------------|
|
||||||
|
| [yarn](https://yarnpkg.com/getting-started) | `yarn install` |
|
||||||
|
| [npm](https://docs.npmjs.com/cli/v7/commands/npm-install) | `npm install` |
|
||||||
|
| [pnpm](https://pnpm.io/installation) | `pnpm install` |
|
||||||
|
| [bun](https://bun.sh/#getting-started) | `bun install` |
|
||||||
|
|
||||||
|
After completing the installation, your environment is ready for Vuetify development.
|
||||||
|
|
||||||
|
## ✨ Features
|
||||||
|
|
||||||
|
- 🖼️ **Optimized Front-End Stack**: Leverage the latest Vue 3 and Vuetify 3 for a modern, reactive UI development experience. [Vue 3](https://v3.vuejs.org/) | [Vuetify 3](https://vuetifyjs.com/en/)
|
||||||
|
- 🗃️ **State Management**: Integrated with [Pinia](https://pinia.vuejs.org/), the intuitive, modular state management solution for Vue.
|
||||||
|
- 🚦 **Routing and Layouts**: Utilizes Vue Router for SPA navigation and vite-plugin-vue-layouts for organizing Vue file layouts. [Vue Router](https://router.vuejs.org/) | [vite-plugin-vue-layouts](https://github.com/JohnCampionJr/vite-plugin-vue-layouts)
|
||||||
|
- ⚡ **Next-Gen Tooling**: Powered by Vite, experience fast cold starts and instant HMR (Hot Module Replacement). [Vite](https://vitejs.dev/)
|
||||||
|
- 🧩 **Automated Component Importing**: Streamline your workflow with unplugin-vue-components, automatically importing components as you use them. [unplugin-vue-components](https://github.com/antfu/unplugin-vue-components)
|
||||||
|
|
||||||
|
These features are curated to provide a seamless development experience from setup to deployment, ensuring that your Vuetify application is both powerful and maintainable.
|
||||||
|
|
||||||
|
## 💡 Usage
|
||||||
|
|
||||||
|
This section covers how to start the development server and build your project for production.
|
||||||
|
|
||||||
|
### Starting the Development Server
|
||||||
|
|
||||||
|
To start the development server with hot-reload, run the following command. The server will be accessible at [http://localhost:3000](http://localhost:3000):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
yarn dev
|
||||||
|
```
|
||||||
|
|
||||||
|
(Repeat for npm, pnpm, and bun with respective commands.)
|
||||||
|
|
||||||
|
> Add NODE_OPTIONS='--no-warnings' to suppress the JSON import warnings that happen as part of the Vuetify import mapping. If you are on Node [v21.3.0](https://nodejs.org/en/blog/release/v21.3.0) or higher, you can change this to NODE_OPTIONS='--disable-warning=5401'. If you don't mind the warning, you can remove this from your package.json dev script.
|
||||||
|
|
||||||
|
### Building for Production
|
||||||
|
|
||||||
|
To build your project for production, use:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
yarn build
|
||||||
|
```
|
||||||
|
|
||||||
|
(Repeat for npm, pnpm, and bun with respective commands.)
|
||||||
|
|
||||||
|
Once the build process is completed, your application will be ready for deployment in a production environment.
|
||||||
|
|
||||||
|
## 💪 Support Vuetify Development
|
||||||
|
|
||||||
|
This project is built with [Vuetify](https://vuetifyjs.com/en/), a UI Library with a comprehensive collection of Vue components. Vuetify is an MIT licensed Open Source project that has been made possible due to the generous contributions by our [sponsors and backers](https://vuetifyjs.com/introduction/sponsors-and-backers/). If you are interested in supporting this project, please consider:
|
||||||
|
|
||||||
|
- [Requesting Enterprise Support](https://support.vuetifyjs.com/)
|
||||||
|
- [Sponsoring John on Github](https://github.com/users/johnleider/sponsorship)
|
||||||
|
- [Sponsoring Kael on Github](https://github.com/users/kaelwd/sponsorship)
|
||||||
|
- [Supporting the team on Open Collective](https://opencollective.com/vuetify)
|
||||||
|
- [Becoming a sponsor on Patreon](https://www.patreon.com/vuetify)
|
||||||
|
- [Becoming a subscriber on Tidelift](https://tidelift.com/subscription/npm/vuetify)
|
||||||
|
- [Making a one-time donation with Paypal](https://paypal.me/vuetify)
|
||||||
|
|
||||||
|
## 📑 License
|
||||||
|
[MIT](http://opensource.org/licenses/MIT)
|
||||||
|
|
||||||
|
Copyright (c) 2016-present Vuetify, LLC
|
||||||
25
frontend/components.d.ts
vendored
Normal file
25
frontend/components.d.ts
vendored
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
/* eslint-disable */
|
||||||
|
// @ts-nocheck
|
||||||
|
// biome-ignore lint: disable
|
||||||
|
// oxlint-disable
|
||||||
|
// ------
|
||||||
|
// Generated by unplugin-vue-components
|
||||||
|
// Read more: https://github.com/vuejs/core/pull/3399
|
||||||
|
|
||||||
|
export {}
|
||||||
|
|
||||||
|
/* prettier-ignore */
|
||||||
|
declare module 'vue' {
|
||||||
|
export interface GlobalComponents {
|
||||||
|
AppHeader: typeof import('./src/components/AppHeader.vue')['default']
|
||||||
|
Atmo: typeof import('./src/components/Atmo.vue')['default']
|
||||||
|
ControlButton: typeof import('./src/components/BaseComponents/ControlButton.vue')['default']
|
||||||
|
Gasflow: typeof import('./src/components/Gasflow.vue')['default']
|
||||||
|
ManualMode: typeof import('./src/components/ManualMode.vue')['default']
|
||||||
|
MotorControl: typeof import('./src/components/BaseComponents/MotorControl.vue')['default']
|
||||||
|
ProcessValue: typeof import('./src/components/BaseComponents/ProcessValue.vue')['default']
|
||||||
|
RouterLink: typeof import('vue-router')['RouterLink']
|
||||||
|
RouterView: typeof import('vue-router')['RouterView']
|
||||||
|
ValveControl: typeof import('./src/components/BaseComponents/ValveControl.vue')['default']
|
||||||
|
}
|
||||||
|
}
|
||||||
3
frontend/eslint.config.js
Normal file
3
frontend/eslint.config.js
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
//import vuetify from 'eslint-config-vuetify'
|
||||||
|
|
||||||
|
//export default vuetify()
|
||||||
13
frontend/index.html
Normal file
13
frontend/index.html
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<link rel="icon" href="/favicon.ico">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Welcome to Vuetify 3</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="app"></div>
|
||||||
|
<script type="module" src="/src/main.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
20
frontend/jsconfig.json
Normal file
20
frontend/jsconfig.json
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"allowJs": true,
|
||||||
|
"target": "es5",
|
||||||
|
"module": "esnext",
|
||||||
|
"baseUrl": "./",
|
||||||
|
"moduleResolution": "bundler",
|
||||||
|
"paths": {
|
||||||
|
"@/*": [
|
||||||
|
"src/*"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"lib": [
|
||||||
|
"esnext",
|
||||||
|
"dom",
|
||||||
|
"dom.iterable",
|
||||||
|
"scripthost"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
5722
frontend/package-lock.json
generated
Normal file
5722
frontend/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
32
frontend/package.json
Normal file
32
frontend/package.json
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
{
|
||||||
|
"name": "frontend_v2",
|
||||||
|
"private": true,
|
||||||
|
"type": "module",
|
||||||
|
"version": "0.0.0",
|
||||||
|
"scripts": {
|
||||||
|
"dev": "vite",
|
||||||
|
"build": "vite build",
|
||||||
|
"preview": "vite preview",
|
||||||
|
"lint": "eslint . --fix"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@fontsource/roboto": "5.2.7",
|
||||||
|
"@mdi/font": "7.4.47",
|
||||||
|
"@ygoe/msgpack": "^1.0.3",
|
||||||
|
"pinia": "^3.0.4",
|
||||||
|
"vue": "^3.5.21",
|
||||||
|
"vue-router": "^4.5.1",
|
||||||
|
"vuetify": "^3.10.1"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@vitejs/plugin-vue": "^6.0.1",
|
||||||
|
"eslint": "^9.35.0",
|
||||||
|
"eslint-config-vuetify": "^4.2.0",
|
||||||
|
"sass-embedded": "^1.92.1",
|
||||||
|
"unplugin-fonts": "^1.4.0",
|
||||||
|
"unplugin-vue-components": "^29.0.0",
|
||||||
|
"unplugin-vue-router": "^0.15.0",
|
||||||
|
"vite": "^7.1.5",
|
||||||
|
"vite-plugin-vuetify": "^2.1.2"
|
||||||
|
}
|
||||||
|
}
|
||||||
BIN
frontend/public/favicon.ico
Normal file
BIN
frontend/public/favicon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 15 KiB |
27
frontend/src/App.vue
Normal file
27
frontend/src/App.vue
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
<template>
|
||||||
|
<v-app>
|
||||||
|
<app-header />
|
||||||
|
<v-main>
|
||||||
|
<router-view />
|
||||||
|
</v-main>
|
||||||
|
</v-app>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { mapStores } from 'pinia'
|
||||||
|
import AppHeader from '@/components/AppHeader.vue'
|
||||||
|
import { useOpcuaStore } from '@/stores/opcua'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'App',
|
||||||
|
components: {
|
||||||
|
AppHeader,
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
...mapStores(useOpcuaStore),
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.opcuaStore.connect()
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
BIN
frontend/src/assets/logo.png
Normal file
BIN
frontend/src/assets/logo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 12 KiB |
6
frontend/src/assets/logo.svg
Normal file
6
frontend/src/assets/logo.svg
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
<svg width="512" height="512" viewBox="0 0 512 512" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M261.126 140.65L164.624 307.732L256.001 466L377.028 256.5L498.001 47H315.192L261.126 140.65Z" fill="#1697F6"/>
|
||||||
|
<path d="M135.027 256.5L141.365 267.518L231.64 111.178L268.731 47H256H14L135.027 256.5Z" fill="#AEDDFF"/>
|
||||||
|
<path d="M315.191 47C360.935 197.446 256 466 256 466L164.624 307.732L315.191 47Z" fill="#1867C0"/>
|
||||||
|
<path d="M268.731 47C76.0026 47 141.366 267.518 141.366 267.518L268.731 47Z" fill="#7BC6FF"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 526 B |
36
frontend/src/components/AppHeader.vue
Normal file
36
frontend/src/components/AppHeader.vue
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
<template>
|
||||||
|
<v-app-bar title="Nikon SLM - Polaris">
|
||||||
|
<control-button nodeId="ns=4;s=GVL_SCADA.stConfirmAlarms">
|
||||||
|
Ack Alarms
|
||||||
|
</control-button>
|
||||||
|
<!-- <v-btn @click.prevent="changeTheme" icon="mdi-theme-light-dark"></v-btn> -->
|
||||||
|
<v-app-bar-nav-icon><v-icon>mdi-account</v-icon></v-app-bar-nav-icon>
|
||||||
|
</v-app-bar>
|
||||||
|
<v-navigation-drawer permanent>
|
||||||
|
<v-list density="compact" nav>
|
||||||
|
<v-list-item prepend-icon="mdi-home-city" title="Home" value="home" to="/"></v-list-item>
|
||||||
|
<v-list-item prepend-icon="mdi-hand-back-left" title="Manual" value="manual"></v-list-item>
|
||||||
|
<v-list-item prepend-icon="mdi-bell" title="Alerts" value="alerts"></v-list-item>
|
||||||
|
<v-list-item prepend-icon="mdi-cog" title="Settings" value="settings"></v-list-item>
|
||||||
|
</v-list>
|
||||||
|
</v-navigation-drawer>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import ControlButton from '@/components/BaseComponents/ControlButton.vue'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'AppHeader',
|
||||||
|
data() {
|
||||||
|
return {}
|
||||||
|
},
|
||||||
|
components: {
|
||||||
|
ControlButton,
|
||||||
|
},
|
||||||
|
// methods: {
|
||||||
|
// changeTheme() {
|
||||||
|
// this.$vuetify.theme.toggle()
|
||||||
|
// },
|
||||||
|
// },
|
||||||
|
}
|
||||||
|
</script>
|
||||||
53
frontend/src/components/Atmo.vue
Normal file
53
frontend/src/components/Atmo.vue
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
<template>
|
||||||
|
<v-sheet
|
||||||
|
class="d-flex flex-column pa-4 ga-4"
|
||||||
|
border="solid md"
|
||||||
|
rounded="lg"
|
||||||
|
width="300"
|
||||||
|
>
|
||||||
|
<v-btn :to="detailsLink" variant="flat" size="x-large">Atmosphere</v-btn>
|
||||||
|
<v-divider></v-divider>
|
||||||
|
<v-sheet class="d-flex flex-column">
|
||||||
|
<v-row>
|
||||||
|
<v-col cols="6"><control-button :nodeId="atStartButton" block>Start</control-button></v-col>
|
||||||
|
<v-col cols="6"><control-button :nodeId="atStopButton" block>Stop</control-button></v-col>
|
||||||
|
</v-row>
|
||||||
|
</v-sheet>
|
||||||
|
</v-sheet>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { mapStores } from 'pinia'
|
||||||
|
import { useOpcuaStore } from '@/stores/opcua'
|
||||||
|
import ControlButton from './BaseComponents/ControlButton.vue'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'Atmocontrol',
|
||||||
|
props: {
|
||||||
|
nodeId: {
|
||||||
|
type: String,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
detailsLink: {
|
||||||
|
type: String
|
||||||
|
},
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
...mapStores(useOpcuaStore),
|
||||||
|
atStartButton() {
|
||||||
|
return `${this.nodeId}.stOpenAllValvesButton`
|
||||||
|
},
|
||||||
|
atStopButton() {
|
||||||
|
return `${this.nodeId}.stCloseAllValvesButton`
|
||||||
|
},
|
||||||
|
},
|
||||||
|
components: {
|
||||||
|
ControlButton,
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
mounted() {
|
||||||
|
this.opcuaStore.subscribe([this.atStartButton, this.atStopButton])
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
</script>
|
||||||
51
frontend/src/components/BaseComponents/ControlButton.vue
Normal file
51
frontend/src/components/BaseComponents/ControlButton.vue
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
<template>
|
||||||
|
<v-btn
|
||||||
|
:disabled="!this.opcuaStore.values[this.cbReleaseNodeId]"
|
||||||
|
:class="{ 'bg-primary': isActive }"
|
||||||
|
@click="onClick"
|
||||||
|
>
|
||||||
|
<slot></slot>
|
||||||
|
</v-btn>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { mapStores } from 'pinia'
|
||||||
|
import { useOpcuaStore } from '@/stores/opcua'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'ControlButton',
|
||||||
|
computed: {
|
||||||
|
...mapStores(useOpcuaStore),
|
||||||
|
cbFeedbackNodeId() {
|
||||||
|
return `${this.nodeId}.eFeedback`
|
||||||
|
},
|
||||||
|
cbReleaseNodeId() {
|
||||||
|
return `${this.nodeId}.xRelease`
|
||||||
|
},
|
||||||
|
cbRequestNodeId() {
|
||||||
|
return `${this.nodeId}.xRequest`
|
||||||
|
},
|
||||||
|
isActive() {
|
||||||
|
return this.opcuaStore.values[this.cbFeedbackNodeId]
|
||||||
|
},
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
nodeId: {
|
||||||
|
type: String,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
onClick() {
|
||||||
|
this.opcuaStore.sendCommand({
|
||||||
|
nodeId: this.cbRequestNodeId,
|
||||||
|
nodeValue: true,
|
||||||
|
nodeType: this.opcuaStore.opcuaDatatypes.Boolean,
|
||||||
|
})
|
||||||
|
},
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.opcuaStore.subscribe([this.cbFeedbackNodeId, this.cbReleaseNodeId])
|
||||||
|
},
|
||||||
|
}
|
||||||
|
</script>
|
||||||
149
frontend/src/components/BaseComponents/MotorControl.vue
Normal file
149
frontend/src/components/BaseComponents/MotorControl.vue
Normal file
@@ -0,0 +1,149 @@
|
|||||||
|
<template>
|
||||||
|
<v-card :title="motorName" max-width="300" w-auto>
|
||||||
|
<v-card-text>
|
||||||
|
<v-row justify="center">
|
||||||
|
<v-col cols="6">
|
||||||
|
<v-text-field
|
||||||
|
v-model="choosenSetpoint"
|
||||||
|
:rules="checkSetpoint"
|
||||||
|
:reverse="true"
|
||||||
|
:disabled="opcuaStore.values[currentControlModeNodeId] != 2"
|
||||||
|
:prefix="opcuaStore.values[spUnitNodeId]"
|
||||||
|
@focus="$event.target.select()"
|
||||||
|
@keyup.enter="onSetpointEnter"
|
||||||
|
label="Setpoint"
|
||||||
|
></v-text-field>
|
||||||
|
</v-col>
|
||||||
|
<v-col cols="6">
|
||||||
|
<h1>/ {{ processValue }}</h1>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
<v-row>
|
||||||
|
<v-col cols="6">
|
||||||
|
<control-button block :node-id="automaticModeCBNodeId">
|
||||||
|
Auto
|
||||||
|
</control-button>
|
||||||
|
</v-col>
|
||||||
|
<v-col cosl="6">
|
||||||
|
<control-button block :node-id="manualModeCBNodeId">
|
||||||
|
Manual
|
||||||
|
</control-button>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
<v-row>
|
||||||
|
<v-col cols="6">
|
||||||
|
<control-button block :node-id="startCBNodeId">
|
||||||
|
Start
|
||||||
|
</control-button>
|
||||||
|
</v-col>
|
||||||
|
<v-col cosl="6">
|
||||||
|
<control-button block :node-id="stopCBNodeId"> Stop </control-button>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
</v-card-text>
|
||||||
|
</v-card>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { mapStores } from 'pinia'
|
||||||
|
import { useOpcuaStore } from '@/stores/opcua'
|
||||||
|
import ControlButton from './ControlButton.vue'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'MotorControl',
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
choosenSetpoint: 0,
|
||||||
|
checkSetpoint: [
|
||||||
|
(value) => {
|
||||||
|
if (
|
||||||
|
value <= this.opcuaStore.values[this.spMaxValueNodeId] &&
|
||||||
|
value >= this.opcuaStore.values[this.spMinValueNodeId]
|
||||||
|
) {
|
||||||
|
return true
|
||||||
|
} else {
|
||||||
|
return `Setpoint must be <= ${this.opcuaStore.values[this.spMaxValueNodeId]} and >= ${this.opcuaStore.values[this.spMinValueNodeId]}`
|
||||||
|
}
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
...mapStores(useOpcuaStore),
|
||||||
|
automaticModeCBNodeId() {
|
||||||
|
return `${this.nodeId}.stAutomaticButton`
|
||||||
|
},
|
||||||
|
manualModeCBNodeId() {
|
||||||
|
return `${this.nodeId}.stManualButton`
|
||||||
|
},
|
||||||
|
startCBNodeId() {
|
||||||
|
return `${this.nodeId}.stStartButton`
|
||||||
|
},
|
||||||
|
stopCBNodeId() {
|
||||||
|
return `${this.nodeId}.stStopButton`
|
||||||
|
},
|
||||||
|
spNodeId() {
|
||||||
|
return `${this.nodeId}.stSetpoint.rValue`
|
||||||
|
},
|
||||||
|
currentControlModeNodeId() {
|
||||||
|
return `${this.nodeId}.iCurrentMode`
|
||||||
|
},
|
||||||
|
spMinValueNodeId() {
|
||||||
|
return `${this.nodeId}.stSetpoint.rMin`
|
||||||
|
},
|
||||||
|
spMaxValueNodeId() {
|
||||||
|
return `${this.nodeId}.stSetpoint.rMax`
|
||||||
|
},
|
||||||
|
spUnitNodeId() {
|
||||||
|
return `${this.nodeId}.stSetpoint.sUnit`
|
||||||
|
},
|
||||||
|
pvUnitNodeId() {
|
||||||
|
return `${this.nodeId}.stProcessValue.sUnit`
|
||||||
|
},
|
||||||
|
pvNodeId() {
|
||||||
|
return `${this.nodeId}.stProcessValue.rValue`
|
||||||
|
},
|
||||||
|
processValue() {
|
||||||
|
const value = Number(this.opcuaStore.values[this.pvNodeId]).toFixed(this.numDecimals)
|
||||||
|
const unit = this.opcuaStore.values[this.pvUnitNodeId]
|
||||||
|
return `${value} ${unit}`
|
||||||
|
},
|
||||||
|
},
|
||||||
|
components: {
|
||||||
|
ControlButton,
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
nodeId: {
|
||||||
|
type: String,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
motorName: {
|
||||||
|
type: String,
|
||||||
|
},
|
||||||
|
numDecimals: {
|
||||||
|
type: Number,
|
||||||
|
default: 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
onSetpointEnter(event) {
|
||||||
|
event.target.blur()
|
||||||
|
this.opcuaStore.sendCommand({
|
||||||
|
nodeId: this.spNodeId,
|
||||||
|
nodeValue: this.choosenSetpoint,
|
||||||
|
nodeType: this.opcuaStore.opcuaDatatypes.Float,
|
||||||
|
})
|
||||||
|
},
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.opcuaStore.subscribe([
|
||||||
|
this.currentControlModeNodeId,
|
||||||
|
this.spMinValueNodeId,
|
||||||
|
this.spMaxValueNodeId,
|
||||||
|
this.spUnitNodeId,
|
||||||
|
this.pvUnitNodeId,
|
||||||
|
this.pvNodeId,
|
||||||
|
])
|
||||||
|
},
|
||||||
|
}
|
||||||
|
</script>
|
||||||
43
frontend/src/components/BaseComponents/ProcessValue.vue
Normal file
43
frontend/src/components/BaseComponents/ProcessValue.vue
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
<template>
|
||||||
|
<p class="text-right">{{ formattedString }}</p>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { mapStores } from 'pinia'
|
||||||
|
import { useOpcuaStore } from '@/stores/opcua'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'ProcessValue',
|
||||||
|
computed: {
|
||||||
|
...mapStores(useOpcuaStore),
|
||||||
|
formattedString() {
|
||||||
|
const value = Number(this.opcuaStore.values[this.pvValueNModeId]).toFixed(this.numDecimals)
|
||||||
|
const unit = this.opcuaStore.values[this.pvUnitNModeId]
|
||||||
|
return `${value} ${unit}`
|
||||||
|
},
|
||||||
|
pvValueNModeId() {
|
||||||
|
return `${this.nodeId}.rValue`
|
||||||
|
},
|
||||||
|
pvUnitNModeId() {
|
||||||
|
return `${this.nodeId}.sUnit`
|
||||||
|
},
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
numDecimals: {
|
||||||
|
type: Number,
|
||||||
|
default: 1,
|
||||||
|
},
|
||||||
|
nodeId: {
|
||||||
|
type: String,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.opcuaStore.subscribe([this.pvValueNModeId, this.pvUnitNModeId])
|
||||||
|
},
|
||||||
|
// unmounted() {
|
||||||
|
// console.log("Unsubscribed");
|
||||||
|
// this.opcuaStore.unsubscribe([this.nodeIdValue, this.nodeIdUnit]);
|
||||||
|
// },
|
||||||
|
}
|
||||||
|
</script>
|
||||||
65
frontend/src/components/BaseComponents/ValveControl.vue
Normal file
65
frontend/src/components/BaseComponents/ValveControl.vue
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
<template>
|
||||||
|
<v-card :title="valveName">
|
||||||
|
<v-card-text>
|
||||||
|
<v-row>
|
||||||
|
<v-col cols="6">
|
||||||
|
<control-button block :nodeId="automaticModeCBNodeId">
|
||||||
|
Auto
|
||||||
|
</control-button>
|
||||||
|
</v-col>
|
||||||
|
<v-col cosl="6">
|
||||||
|
<control-button block :nodeId="manualModeCBNodeId">
|
||||||
|
Manual
|
||||||
|
</control-button>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
<v-row>
|
||||||
|
<v-col cols="6">
|
||||||
|
<control-button block :nodeId="openCBNodeId"> Open </control-button>
|
||||||
|
</v-col>
|
||||||
|
<v-col>
|
||||||
|
<control-button block :nodeId="closeCBNodeId">
|
||||||
|
Close
|
||||||
|
</control-button>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
</v-card-text>
|
||||||
|
</v-card>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { mapStores } from 'pinia'
|
||||||
|
import { useOpcuaStore } from '@/stores/opcua'
|
||||||
|
import ControlButton from './ControlButton.vue'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'ValveControl',
|
||||||
|
computed: {
|
||||||
|
...mapStores(useOpcuaStore),
|
||||||
|
automaticModeCBNodeId() {
|
||||||
|
return `${this.nodeId}.stAutomaticButton`
|
||||||
|
},
|
||||||
|
manualModeCBNodeId() {
|
||||||
|
return `${this.nodeId}.stManualButton`
|
||||||
|
},
|
||||||
|
openCBNodeId() {
|
||||||
|
return `${this.nodeId}.stOpenButton`
|
||||||
|
},
|
||||||
|
closeCBNodeId() {
|
||||||
|
return `${this.nodeId}.stCloseButton`
|
||||||
|
},
|
||||||
|
},
|
||||||
|
components: {
|
||||||
|
ControlButton,
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
nodeId: {
|
||||||
|
type: String,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
valveName: {
|
||||||
|
type: String,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
</script>
|
||||||
82
frontend/src/components/Gasflow.vue
Normal file
82
frontend/src/components/Gasflow.vue
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
<template>
|
||||||
|
<v-sheet
|
||||||
|
class="d-flex flex-column pa-4 ga-4"
|
||||||
|
border="solid md"
|
||||||
|
rounded="lg"
|
||||||
|
width="300"
|
||||||
|
>
|
||||||
|
<v-btn :to="detailsLink" variant="flat" size="x-large">Gasflow</v-btn>
|
||||||
|
<v-divider></v-divider>
|
||||||
|
<v-sheet class="d-flex flex-column">
|
||||||
|
<v-row>
|
||||||
|
<v-col cols="6"><h4 class="text-right">Flow:</h4></v-col>
|
||||||
|
<v-col cols="6"><process-value :nodeId="gfFlowNodeId"/></v-col>
|
||||||
|
</v-row>
|
||||||
|
<v-row>
|
||||||
|
<v-col cols="6"><h4 class="text-right">Temp:</h4></v-col>
|
||||||
|
<v-col cols="6"><process-value :nodeId="gfTempNodeId"/></v-col>
|
||||||
|
</v-row>
|
||||||
|
</v-sheet>
|
||||||
|
<v-divider></v-divider>
|
||||||
|
<v-sheet class="d-flex flex-column">
|
||||||
|
<v-row>
|
||||||
|
<v-col cols="6"><control-button :nodeId="gfAutoButton" block>Automatic</control-button></v-col>
|
||||||
|
<v-col cols="6"><control-button :nodeId="gfManualButton" block>Manual</control-button></v-col>
|
||||||
|
</v-row>
|
||||||
|
<v-row>
|
||||||
|
<v-col cols="6"><control-button :nodeId="gfStartButton" block>Start</control-button></v-col>
|
||||||
|
<v-col cols="6"><control-button :nodeId="gfStopButton" block>Stop</control-button></v-col>
|
||||||
|
</v-row>
|
||||||
|
</v-sheet>
|
||||||
|
</v-sheet>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { mapStores } from 'pinia'
|
||||||
|
import { useOpcuaStore } from '@/stores/opcua'
|
||||||
|
import ControlButton from './BaseComponents/ControlButton.vue'
|
||||||
|
import ProcessValue from './BaseComponents/ProcessValue.vue'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'Gasflow',
|
||||||
|
props: {
|
||||||
|
nodeId: {
|
||||||
|
type: String,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
detailsLink: {
|
||||||
|
type: String
|
||||||
|
},
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
...mapStores(useOpcuaStore),
|
||||||
|
gfFlowNodeId() {
|
||||||
|
return `${this.nodeId}.stFlowrateSensor`
|
||||||
|
},
|
||||||
|
gfTempNodeId() {
|
||||||
|
return `${this.nodeId}.stTempSensor`
|
||||||
|
},
|
||||||
|
gfAutoButton() {
|
||||||
|
return `${this.nodeId}.stAutomaticButton`
|
||||||
|
},
|
||||||
|
gfManualButton() {
|
||||||
|
return `${this.nodeId}.stManualButton`
|
||||||
|
},
|
||||||
|
gfStartButton() {
|
||||||
|
return `${this.nodeId}.stStartButton`
|
||||||
|
},
|
||||||
|
gfStopButton() {
|
||||||
|
return `${this.nodeId}.stStopButton`
|
||||||
|
},
|
||||||
|
},
|
||||||
|
components: {
|
||||||
|
ControlButton,
|
||||||
|
ProcessValue,
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
mounted() {
|
||||||
|
this.opcuaStore.subscribe([this.gfFlowNodeId, this.gfTempNodeId, this.gfAutoButton, this.gfManualButton, this.gfStartButton, this.gfStopButton])
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
</script>
|
||||||
26
frontend/src/main.js
Normal file
26
frontend/src/main.js
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
/**
|
||||||
|
* main.js
|
||||||
|
*
|
||||||
|
* Bootstraps Vuetify and other plugins then mounts the App`
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Plugins
|
||||||
|
import { registerPlugins } from '@/plugins'
|
||||||
|
|
||||||
|
// Components
|
||||||
|
import App from './App.vue'
|
||||||
|
|
||||||
|
// Composables
|
||||||
|
import { createApp } from 'vue'
|
||||||
|
import { createPinia } from 'pinia'
|
||||||
|
|
||||||
|
// Styles
|
||||||
|
import 'unfonts.css'
|
||||||
|
|
||||||
|
const pinia = createPinia()
|
||||||
|
const app = createApp(App)
|
||||||
|
|
||||||
|
registerPlugins(app)
|
||||||
|
|
||||||
|
app.use(pinia)
|
||||||
|
app.mount('#app')
|
||||||
3
frontend/src/plugins/README.md
Normal file
3
frontend/src/plugins/README.md
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
# Plugins
|
||||||
|
|
||||||
|
Plugins are a way to extend the functionality of your Vue application. Use this folder for registering plugins that you want to use globally.
|
||||||
15
frontend/src/plugins/index.js
Normal file
15
frontend/src/plugins/index.js
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
/**
|
||||||
|
* plugins/index.js
|
||||||
|
*
|
||||||
|
* Automatically included in `./src/main.js`
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Plugins
|
||||||
|
import vuetify from './vuetify'
|
||||||
|
import router from '@/router'
|
||||||
|
|
||||||
|
export function registerPlugins (app) {
|
||||||
|
app
|
||||||
|
.use(vuetify)
|
||||||
|
.use(router)
|
||||||
|
}
|
||||||
19
frontend/src/plugins/vuetify.js
Normal file
19
frontend/src/plugins/vuetify.js
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
/**
|
||||||
|
* plugins/vuetify.js
|
||||||
|
*
|
||||||
|
* Framework documentation: https://vuetifyjs.com`
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Styles
|
||||||
|
import '@mdi/font/css/materialdesignicons.css'
|
||||||
|
import 'vuetify/styles'
|
||||||
|
|
||||||
|
// Composables
|
||||||
|
import { createVuetify } from 'vuetify'
|
||||||
|
|
||||||
|
// https://vuetifyjs.com/en/introduction/why-vuetify/#feature-guides
|
||||||
|
export default createVuetify({
|
||||||
|
theme: {
|
||||||
|
defaultTheme: 'system',
|
||||||
|
},
|
||||||
|
})
|
||||||
55
frontend/src/router/index.js
Normal file
55
frontend/src/router/index.js
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
/**
|
||||||
|
* router/index.ts
|
||||||
|
*
|
||||||
|
* Automatic routes for `./src/pages/*.vue`
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Composables
|
||||||
|
import { createRouter, createWebHistory } from 'vue-router'
|
||||||
|
// import { routes } from 'vue-router/auto-routes'
|
||||||
|
import Home from '@/views/HomeView.vue'
|
||||||
|
//import { components } from 'vuetify/dist/vuetify.js'
|
||||||
|
import AtmosphereManualView from '@/views/AtmosphereManualView.vue'
|
||||||
|
import GasflowManualView from '@/views/GasflowManualView.vue'
|
||||||
|
|
||||||
|
|
||||||
|
const routes = [
|
||||||
|
{
|
||||||
|
path: '/',
|
||||||
|
component: Home,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/manual/gf',
|
||||||
|
component: GasflowManualView,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/manual/atmo',
|
||||||
|
component: AtmosphereManualView,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
const router = createRouter({
|
||||||
|
history: createWebHistory(import.meta.env.BASE_URL),
|
||||||
|
routes,
|
||||||
|
})
|
||||||
|
|
||||||
|
// Workaround for https://github.com/vitejs/vite/issues/11804
|
||||||
|
router.onError((err, to) => {
|
||||||
|
if (err?.message?.includes?.('Failed to fetch dynamically imported module')) {
|
||||||
|
if (localStorage.getItem('vuetify:dynamic-reload')) {
|
||||||
|
console.error('Dynamic import error, reloading page did not fix it', err)
|
||||||
|
} else {
|
||||||
|
console.log('Reloading page to fix dynamic import error')
|
||||||
|
localStorage.setItem('vuetify:dynamic-reload', 'true')
|
||||||
|
location.assign(to.fullPath)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
console.error(err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
router.isReady().then(() => {
|
||||||
|
localStorage.removeItem('vuetify:dynamic-reload')
|
||||||
|
})
|
||||||
|
|
||||||
|
export default router
|
||||||
169
frontend/src/stores/opcua.js
Normal file
169
frontend/src/stores/opcua.js
Normal file
@@ -0,0 +1,169 @@
|
|||||||
|
import { defineStore } from "pinia";
|
||||||
|
import msgpack from "@ygoe/msgpack";
|
||||||
|
|
||||||
|
export const useOpcuaStore = defineStore("opcua", {
|
||||||
|
state: () => ({
|
||||||
|
values: {},
|
||||||
|
opcuaEvents: new Map(),
|
||||||
|
ws: null,
|
||||||
|
connected: false,
|
||||||
|
subscriptions: new Set(),
|
||||||
|
opcuaDatatypes: {
|
||||||
|
Null: 0,
|
||||||
|
Boolean: 1,
|
||||||
|
SByte: 2,
|
||||||
|
Byte: 3,
|
||||||
|
Int16: 4,
|
||||||
|
UInt16: 5,
|
||||||
|
Int32: 6,
|
||||||
|
UInt32: 7,
|
||||||
|
Int64: 8,
|
||||||
|
UInt64: 9,
|
||||||
|
Float: 10,
|
||||||
|
Double: 11,
|
||||||
|
String: 12,
|
||||||
|
DateTime: 13,
|
||||||
|
Guid: 14,
|
||||||
|
ByteString: 15,
|
||||||
|
XmlElement: 16,
|
||||||
|
NodeId: 17,
|
||||||
|
ExpandedNodeId: 18,
|
||||||
|
StatusCode: 19,
|
||||||
|
QualifiedName: 20,
|
||||||
|
LocalizedText: 21,
|
||||||
|
ExtensionObject: 22,
|
||||||
|
DataValue: 23,
|
||||||
|
Variant: 24,
|
||||||
|
DiagnosticInfo: 25,
|
||||||
|
},
|
||||||
|
reconnectTimeout: null,
|
||||||
|
}),
|
||||||
|
|
||||||
|
actions: {
|
||||||
|
connect() {
|
||||||
|
this.ws = new WebSocket("ws://127.0.0.1:8000/ws");
|
||||||
|
this.ws.binaryType = "arraybuffer";
|
||||||
|
|
||||||
|
this.ws.onopen = () => {
|
||||||
|
this.connected = true;
|
||||||
|
// Initiale Subscriptions senden
|
||||||
|
const msg = msgpack.encode({
|
||||||
|
subscribe: Array.from(this.subscriptions),
|
||||||
|
});
|
||||||
|
this.ws.send(msg);
|
||||||
|
};
|
||||||
|
|
||||||
|
this.ws.onmessage = (event) => {
|
||||||
|
const data = msgpack.decode(new Uint8Array(event.data));
|
||||||
|
|
||||||
|
if (data.initial) {
|
||||||
|
Object.entries(data.initial.values).forEach(([tag, val]) => {
|
||||||
|
this.values[tag] = val;
|
||||||
|
});
|
||||||
|
Object.entries(data.initial.events).forEach(([tag, val]) => {
|
||||||
|
this.opcuaEvents.set(tag, val);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data.u) {
|
||||||
|
Object.entries(data.u).forEach(([nodeId, val]) => {
|
||||||
|
this.values[nodeId] = val;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data.e) {
|
||||||
|
console.log(data.e);
|
||||||
|
Object.entries(data.e).forEach(([nodeId, event]) => {
|
||||||
|
// Event is still relevant
|
||||||
|
if (event.Retain) {
|
||||||
|
let entry = {};
|
||||||
|
|
||||||
|
// If we already have an event with the same nodeId
|
||||||
|
if (this.opcuaEvents.has(nodeId)) {
|
||||||
|
entry = this.opcuaEventsget(nodeId);
|
||||||
|
entry.type = event.Severity;
|
||||||
|
//entry.message = event.message;
|
||||||
|
// check if it is still active
|
||||||
|
if (event.ActiveState) {
|
||||||
|
entry.raised = new Date(event.ActiveStateTransTime).toLocaleString("de-DE");
|
||||||
|
} else {
|
||||||
|
// If not, write the time to cleared
|
||||||
|
entry.cleared = new Date(event.ActiveStateTransTime).toLocaleString("de-DE");
|
||||||
|
}
|
||||||
|
if (event.ConfirmedState) {
|
||||||
|
entry.confirmed = new Date(event.ConfirmedStateTransTime).toLocaleString("de-DE");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
entry = {
|
||||||
|
type: event.Severity,
|
||||||
|
message: event.Message,
|
||||||
|
raised: new Date(event.ActiveStateTransTime).toLocaleString("de-DE"),
|
||||||
|
cleared: event.ActiveState ? "" : new Date(event.ActiveStateTransTime).toLocaleString("de-DE"),
|
||||||
|
confirmed: event.ConfirmedState ? new Date(event.ConfirmedStateTransTime).toLocaleString("de-DE") : "",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
this.opcuaEvents.set(nodeId, entry);
|
||||||
|
} else {
|
||||||
|
this.opcuaEvents.delete(nodeId);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Object.entries(data.e).forEach(([nodeId, val]) => {
|
||||||
|
// Event is not yet cleared
|
||||||
|
// if (val.Retain)
|
||||||
|
// {
|
||||||
|
// console.log(val)
|
||||||
|
// //this.opcuaEvents.set(nodeId) = val;
|
||||||
|
// }
|
||||||
|
// else
|
||||||
|
// {
|
||||||
|
// if (this.opcuaEvents.has(nodeId)) {
|
||||||
|
// this.opcuaEvents.delete(nodeId);
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
this.ws.onclose = () => {
|
||||||
|
//this.reconnect();
|
||||||
|
this.connected = false;
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
reconnect() {
|
||||||
|
if (this.reconnectTimeout) return; // Verhindert mehrfachen Reconnect
|
||||||
|
this.reconnectTimeout = setTimeout(() => {
|
||||||
|
this.reconnectTimeout = null;
|
||||||
|
this.connect();
|
||||||
|
}, 3000);
|
||||||
|
},
|
||||||
|
|
||||||
|
subscribe(tags) {
|
||||||
|
tags.forEach((tag) => this.subscriptions.add(tag));
|
||||||
|
if (this.connected && this.ws) {
|
||||||
|
const msg = msgpack.encode({ subscribe: tags });
|
||||||
|
this.ws.send(msg);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
unsubscribe(tags) {
|
||||||
|
tags.forEach((tag) => this.subscriptions.delete(tag));
|
||||||
|
if (this.connected && this.ws) {
|
||||||
|
const msg = msgpack.encode({ unsubscribe: tags });
|
||||||
|
this.ws.send(msg);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
sendCommand(command) {
|
||||||
|
if (this.ws && this.connected) {
|
||||||
|
const msg = msgpack.encode({ command: command });
|
||||||
|
this.ws.send(msg);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
getValue(tag) {
|
||||||
|
return this.values[tag];
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
3
frontend/src/styles/README.md
Normal file
3
frontend/src/styles/README.md
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
# Styles
|
||||||
|
|
||||||
|
This directory is for configuring the styles of the application.
|
||||||
10
frontend/src/styles/settings.scss
Normal file
10
frontend/src/styles/settings.scss
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
/**
|
||||||
|
* src/styles/settings.scss
|
||||||
|
*
|
||||||
|
* Configures SASS variables and Vuetify overwrites
|
||||||
|
*/
|
||||||
|
|
||||||
|
// https://vuetifyjs.com/features/sass-variables/`
|
||||||
|
// @use 'vuetify/settings' with (
|
||||||
|
// $color-pack: false
|
||||||
|
// );
|
||||||
42
frontend/src/views/AtmosphereManualView.vue
Normal file
42
frontend/src/views/AtmosphereManualView.vue
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
<template>
|
||||||
|
<v-container class="d-flex">
|
||||||
|
<v-row>
|
||||||
|
<v-col cols="3">
|
||||||
|
<valve-control
|
||||||
|
valve-name="QM01"
|
||||||
|
node-id="ns=4;s=GVL_SCADA.stPolarisHMIInterface.stAtmoTemp.stBeforePCValve"
|
||||||
|
/>
|
||||||
|
</v-col>
|
||||||
|
<v-col cols="3">
|
||||||
|
<valve-control
|
||||||
|
valve-name="QM02"
|
||||||
|
node-id="ns=4;s=GVL_SCADA.stPolarisHMIInterface.stAtmoTemp.stBeforePfmValveHMI"
|
||||||
|
/>
|
||||||
|
</v-col>
|
||||||
|
<v-col cols="3">
|
||||||
|
<valve-control
|
||||||
|
valve-name="QM09"
|
||||||
|
node-id="ns=4;s=GVL_SCADA.stPolarisHMIInterface.stAtmoTemp.stAfterPfmValveHMI"
|
||||||
|
/>
|
||||||
|
</v-col>
|
||||||
|
<v-col cols="3">
|
||||||
|
<valve-control
|
||||||
|
valve-name="QM40"
|
||||||
|
node-id="ns=4;s=GVL_SCADA.stPolarisHMIInterface.stAtmoTemp.stInertAndPurgeOutletValve"
|
||||||
|
/>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
</v-container>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import ValveControl from '@/components/BaseComponents/ValveControl.vue'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'AtmoManualView',
|
||||||
|
components: {
|
||||||
|
ValveControl,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
30
frontend/src/views/GasflowManualView.vue
Normal file
30
frontend/src/views/GasflowManualView.vue
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
<template>
|
||||||
|
<v-container class="d-flex ga-6">
|
||||||
|
<v-row>
|
||||||
|
<v-col cols="3">
|
||||||
|
<v-sheet class="d-flex flex-column">
|
||||||
|
<v-row>
|
||||||
|
<v-col cols="6"><h4 class="text-right">Flow:</h4></v-col>
|
||||||
|
<v-col cols="6"><process-value nodeId="ns=4;s=GVL_SCADA.stPolarisHMIInterface.stGasFlow.stFlowrateSensor"/></v-col>
|
||||||
|
</v-row>
|
||||||
|
</v-sheet>
|
||||||
|
</v-col>
|
||||||
|
<v-col cols="3">
|
||||||
|
<motor-control nodeId="ns=4;s=GVL_SCADA.stPolarisHMIInterface.stGasFlow.stHMIBlower"/>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
</v-container>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import ValveControl from '@/components/BaseComponents/ValveControl.vue'
|
||||||
|
import MotorControl from '@/components/BaseComponents/MotorControl.vue'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'GFManualView',
|
||||||
|
components: {
|
||||||
|
MotorControl,
|
||||||
|
ValveControl,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
</script>
|
||||||
22
frontend/src/views/HomeView.vue
Normal file
22
frontend/src/views/HomeView.vue
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
<template>
|
||||||
|
<v-container class="d-flex ga-6">
|
||||||
|
<gasflow detailsLink="/manual/gf" nodeId="ns=4;s=GVL_SCADA.stPolarisHMIInterface.stGasFlow" />
|
||||||
|
<atmo detailsLink="/manual/atmo" nodeId="ns=4;s=GVL_SCADA.stPolarisHMIInterface.stAtmoTemp" />
|
||||||
|
</v-container>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import Gasflow from '@/components/Gasflow.vue';
|
||||||
|
import Atmo from '@/components/Atmo.vue';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'ManualView',
|
||||||
|
components: {
|
||||||
|
Gasflow,
|
||||||
|
Atmo,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
|
||||||
46
frontend/typed-router.d.ts
vendored
Normal file
46
frontend/typed-router.d.ts
vendored
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
/* eslint-disable */
|
||||||
|
/* prettier-ignore */
|
||||||
|
// @ts-nocheck
|
||||||
|
// Generated by unplugin-vue-router. ‼️ DO NOT MODIFY THIS FILE ‼️
|
||||||
|
// It's recommended to commit this file.
|
||||||
|
// Make sure to add this file to your tsconfig.json file as an "includes" or "files" entry.
|
||||||
|
|
||||||
|
declare module 'vue-router/auto-routes' {
|
||||||
|
import type {
|
||||||
|
RouteRecordInfo,
|
||||||
|
ParamValue,
|
||||||
|
ParamValueOneOrMore,
|
||||||
|
ParamValueZeroOrMore,
|
||||||
|
ParamValueZeroOrOne,
|
||||||
|
} from 'vue-router'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Route name map generated by unplugin-vue-router
|
||||||
|
*/
|
||||||
|
export interface RouteNamedMap {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Route file to route info map by unplugin-vue-router.
|
||||||
|
* Used by the volar plugin to automatically type useRoute()
|
||||||
|
*
|
||||||
|
* Each key is a file path relative to the project root with 2 properties:
|
||||||
|
* - routes: union of route names of the possible routes when in this page (passed to useRoute<...>())
|
||||||
|
* - views: names of nested views (can be passed to <RouterView name="...">)
|
||||||
|
*
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
export interface _RouteFileInfoMap {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a union of possible route names in a certain route component file.
|
||||||
|
* Used by the volar plugin to automatically type useRoute()
|
||||||
|
*
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
export type _RouteNamesForFilePath<FilePath extends string> =
|
||||||
|
_RouteFileInfoMap extends Record<FilePath, infer Info>
|
||||||
|
? Info['routes']
|
||||||
|
: keyof RouteNamedMap
|
||||||
|
}
|
||||||
66
frontend/vite.config.mjs
Normal file
66
frontend/vite.config.mjs
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
// Plugins
|
||||||
|
import Components from 'unplugin-vue-components/vite'
|
||||||
|
import Vue from '@vitejs/plugin-vue'
|
||||||
|
import Vuetify, { transformAssetUrls } from 'vite-plugin-vuetify'
|
||||||
|
import Fonts from 'unplugin-fonts/vite'
|
||||||
|
import VueRouter from 'unplugin-vue-router/vite'
|
||||||
|
|
||||||
|
// Utilities
|
||||||
|
import { defineConfig } from 'vite'
|
||||||
|
import { fileURLToPath, URL } from 'node:url'
|
||||||
|
|
||||||
|
// https://vitejs.dev/config/
|
||||||
|
export default defineConfig({
|
||||||
|
plugins: [
|
||||||
|
VueRouter(),
|
||||||
|
Vue({
|
||||||
|
template: { transformAssetUrls },
|
||||||
|
}),
|
||||||
|
// https://github.com/vuetifyjs/vuetify-loader/tree/master/packages/vite-plugin#readme
|
||||||
|
Vuetify({
|
||||||
|
autoImport: true,
|
||||||
|
styles: {
|
||||||
|
configFile: 'src/styles/settings.scss',
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
Components(),
|
||||||
|
Fonts({
|
||||||
|
fontsource: {
|
||||||
|
families: [
|
||||||
|
{
|
||||||
|
name: 'Roboto',
|
||||||
|
weights: [100, 300, 400, 500, 700, 900],
|
||||||
|
styles: ['normal', 'italic'],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
optimizeDeps: {
|
||||||
|
exclude: [
|
||||||
|
'vuetify',
|
||||||
|
'vue-router',
|
||||||
|
'unplugin-vue-router/runtime',
|
||||||
|
'unplugin-vue-router/data-loaders',
|
||||||
|
'unplugin-vue-router/data-loaders/basic',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
define: { 'process.env': {} },
|
||||||
|
resolve: {
|
||||||
|
alias: {
|
||||||
|
'@': fileURLToPath(new URL('src', import.meta.url)),
|
||||||
|
},
|
||||||
|
extensions: [
|
||||||
|
'.js',
|
||||||
|
'.json',
|
||||||
|
'.jsx',
|
||||||
|
'.mjs',
|
||||||
|
'.ts',
|
||||||
|
'.tsx',
|
||||||
|
'.vue',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
server: {
|
||||||
|
port: 3000,
|
||||||
|
},
|
||||||
|
})
|
||||||
Reference in New Issue
Block a user