diff --git a/.drone.yml b/.drone.yml index 4f2b7a4f..211896ce 100644 --- a/.drone.yml +++ b/.drone.yml @@ -19,6 +19,13 @@ steps: commands: - export LOGLEVEL=warn - npm test + - node_modules/.bin/nyc report --reporter=json > coverage.json + +- name: codecov + image: robertstettner/drone-codecov + settings: + token: + from_secret: codecov_token - name: docker image: plugins/docker @@ -42,6 +49,17 @@ steps: auto_tag: true auto_tag_suffix: arm +- name: docker-test-pea + image: plugins/docker + settings: + dockerfile: Dockerfile.test-pea + username: + from_secret: docker_username + password: + from_secret: docker_password + repo: p2olab/test-pea + auto_tag: true + - name: notify image: drillster/drone-email settings: @@ -57,6 +75,6 @@ steps: --- kind: signature -hmac: 392e24d213749c7c035d18876b28896425a6f67b9ec7d8055d826f3880ad06b8 +hmac: af163dddfd72ca6aa1af521e412b641586f9bcb0c5be6cefd0cb33f01ea51255 ... diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 00000000..dd84ea78 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,38 @@ +--- +name: Bug report +about: Create a report to help us improve +title: '' +labels: '' +assignees: '' + +--- + +**Describe the bug** +A clear and concise description of what the bug is. + +**To Reproduce** +Steps to reproduce the behavior: +1. Go to '...' +2. Click on '....' +3. Scroll down to '....' +4. See error + +**Expected behavior** +A clear and concise description of what you expected to happen. + +**Screenshots** +If applicable, add screenshots to help explain your problem. + +**Desktop (please complete the following information):** + - OS: [e.g. iOS] + - Browser [e.g. chrome, safari] + - Version [e.g. 22] + +**Smartphone (please complete the following information):** + - Device: [e.g. iPhone6] + - OS: [e.g. iOS8.1] + - Browser [e.g. stock browser, safari] + - Version [e.g. 22] + +**Additional context** +Add any other context about the problem here. diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 00000000..bbcbbe7d --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,20 @@ +--- +name: Feature request +about: Suggest an idea for this project +title: '' +labels: '' +assignees: '' + +--- + +**Is your feature request related to a problem? Please describe.** +A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] + +**Describe the solution you'd like** +A clear and concise description of what you want to happen. + +**Describe alternatives you've considered** +A clear and concise description of any alternative solutions or features you've considered. + +**Additional context** +Add any other context or screenshots about the feature request here. diff --git a/.npmrc b/.npmrc deleted file mode 100644 index 339e0182..00000000 --- a/.npmrc +++ /dev/null @@ -1 +0,0 @@ -@plt:registry=https://registry.plt.et.tu-dresden.de:4873 \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index 4e25b0ef..a36d6b48 100644 --- a/Dockerfile +++ b/Dockerfile @@ -6,7 +6,6 @@ WORKDIR /app FROM base as dependencies COPY package.json . COPY package-lock.json . -COPY .npmrc . RUN npm install --prod ## Image for building diff --git a/Dockerfile.arm b/Dockerfile.arm index 358e7dad..7d832c6b 100644 --- a/Dockerfile.arm +++ b/Dockerfile.arm @@ -6,7 +6,6 @@ WORKDIR /app FROM base as dependencies COPY package.json . COPY package-lock.json . -COPY .npmrc . RUN npm install --prod ## Image for building diff --git a/Dockerfile.test-pea b/Dockerfile.test-pea new file mode 100644 index 00000000..2172ab2a --- /dev/null +++ b/Dockerfile.test-pea @@ -0,0 +1,26 @@ +# Docker Parent Image with Node and Typescript +FROM node:8-jessie as base +WORKDIR /app + +# image for runtime dependencies +FROM base as dependencies +COPY package.json . +COPY package-lock.json . +RUN npm install --prod + +## Image for building +FROM dependencies as build +COPY src src +COPY tsconfig.json . +RUN npm install +RUN npm run build + +# production image +FROM node:alpine +LABEL org.label-schema.name "test-pea" +LABEL org.label-schema.docker.cmd "docker run -d -p 4334:4334 p2olab/test-pea" + +COPY --from=dependencies /app/node_modules node_modules +COPY --from=build /app/build build +EXPOSE 4334 +ENTRYPOINT ["node", "build/src/testServer.js"] diff --git a/README.md b/README.md index 76aa9723..c5b826cf 100644 --- a/README.md +++ b/README.md @@ -1,14 +1,16 @@ # polaris-backend *Polaris* is a Process Orchestration Layer application for controlling process equipment assemblies (PEA) in the context of modular production in process industries. Thus, it follows the VDI/VDE/NAMUR 2658 standards. -*polaris-backend* is a NodeJs application which can be controlled via REST. Furthermore it provides recent state changes via websockets. +*polaris-backend* is a NodeJs application which can be controlled via REST. Furthermore it provides recent state changes via websockets. For testing and debugging purposes, polaris-backend provides also a testserver which behaves like a PEA. A HTML user interface for this project is provided via the [polaris-frontend](https://github.com/p2o-lab/polaris-frontend) project. -[![Build Status](https://cloud.drone.io/api/badges/p2o-lab/polaris-backend/status.svg?ref=refs/heads/develop)](https://cloud.drone.io/p2o-lab/polaris-backend) -[![Docker Badge](https://images.microbadger.com/badges/image/p2olab/polaris-backend.svg)](https://microbadger.com/images/p2olab/polaris-backend "Get your own image badge on microbadger.com") +[![Build Status](https://cloud.drone.io/api/badges/p2o-lab/polaris-backend/status.svg?ref=refs/heads/develop)](https://cloud.drone.io/p2o-lab/polaris-backend "Build status") +[![Docker Badge](https://img.shields.io/docker/pulls/p2olab/polaris-backend)](https://hub.docker.com/r/p2olab/polaris-backend "Docker image on docker.hub") +[![MicroBadger Size](https://images.microbadger.com/badges/version/p2olab/polaris-backend.svg)](https://microbadger.com/images/p2olab/polaris-backend "Get your own image badge on microbadger.com") [![Greenkeeper badge](https://badges.greenkeeper.io/p2o-lab/polaris-backend.svg)](https://greenkeeper.io/) [![CodeFactor](https://www.codefactor.io/repository/github/p2o-lab/polaris-backend/badge)](https://www.codefactor.io/repository/github/p2o-lab/polaris-backend) +[![codecov](https://codecov.io/gh/p2o-lab/polaris-backend/branch/develop/graph/badge.svg)](https://codecov.io/gh/p2o-lab/polaris-backend) ## Installation and Deployment @@ -30,37 +32,33 @@ or use ready binary (which should also be installed globally and in modules) ### Docker -Use docker ready image +Use ready docker image, which are automatically updated during drone.io integration ``` docker pull p2olab/polaris-backend docker run -d -p 3000:3000 p2olab/polaris-backend ``` -Update docker image -```bash -docker run deploy -``` -or make it manually -``` -docker build -t p2olab/polaris-backend . -docker push p2olab/polaris-backend -``` - -Update docker image for raspberry pi -``` -docker build -t p2olab/polaris-backend:latest-arm -f Dockerfile.arm . -docker push p2olab/polaris-backend:latest-arm -``` - - ## Usage After starting *polaris-backend* its REST interface is available under http://localhost:3000 -*polaris-backend* has several command line parameters: +*polaris-backend* has several command line parameters which are documented by calling `npm start -- -h` Its documentation is served by the application under the path **/doc** (e.g. http://localhost:3000/doc) + +## Test Process Equipment Assembly +Polaris can serve a test PEA with an OPC UA server. It has two services with some parameters and some other variables which change over time. You can start this testserver via +``` +npm run testserver +``` + +or via docker container +``` +docker run -p 4334:4334 p2olab/test-pea +``` + +The corresponding JSON file `assets/modules/module_testserver_1.0.0.json` can be directly loaded in Polaris. diff --git a/assets/modules/achema_demonstrator/module_merck_reactor.json b/assets/modules/achema_demonstrator/module_merck_reactor.json new file mode 100644 index 00000000..f6aa569d --- /dev/null +++ b/assets/modules/achema_demonstrator/module_merck_reactor.json @@ -0,0 +1,1859 @@ +{ + "modules": [ + { + "id": "React", + "opcua_server_url": "opc.tcp://10.6.51.22:4840", + "rest_api_port": "1201", + "services": [ + { + "name": "Fill_Service", + "communication": { + "OSLevel": { + "namespace_index": "http://www.siemens.com/simatic-s7-opcua", + "node_id": "\"Fill_Service\".\"MTP\".\"OSLevel\"", + "data_type": "Byte" + }, + "WQC": { + "namespace_index": "http://www.siemens.com/simatic-s7-opcua", + "node_id": "\"Fill_Service\".\"MTP\".\"WQC\"", + "data_type": "Byte" + }, + "OpMode": { + "namespace_index": "http://www.siemens.com/simatic-s7-opcua", + "node_id": "\"Fill_Service\".\"MTP\".\"OpMode\"", + "data_type": "UInt32" + }, + "CommandMan": { + "namespace_index": "http://www.siemens.com/simatic-s7-opcua", + "node_id": "\"Fill_Service\".\"MTP\".\"ControlOp\"", + "data_type": "UInt32" + }, + "StrategyMan": { + "namespace_index": "http://www.siemens.com/simatic-s7-opcua", + "node_id": "\"Fill_Service\".\"MTP\".\"StrategyOp\"", + "data_type": "UInt32" + }, + "CommandInt": { + "namespace_index": "http://www.siemens.com/simatic-s7-opcua", + "node_id": "\"Fill_Service\".\"MTP\".\"ControlInt\"", + "data_type": "UInt32" + }, + "StrategyInt": { + "namespace_index": "http://www.siemens.com/simatic-s7-opcua", + "node_id": "\"Fill_Service\".\"MTP\".\"StrategyInt\"", + "data_type": "UInt32" + }, + "CommandExt": { + "namespace_index": "http://www.siemens.com/simatic-s7-opcua", + "node_id": "\"Fill_Service\".\"MTP\".\"ControlExt\"", + "data_type": "UInt32" + }, + "StrategyExt": { + "namespace_index": "http://www.siemens.com/simatic-s7-opcua", + "node_id": "\"Fill_Service\".\"MTP\".\"StrategyExt\"", + "data_type": "UInt32" + }, + "State": { + "namespace_index": "http://www.siemens.com/simatic-s7-opcua", + "node_id": "\"Fill_Service\".\"MTP\".\"State\"", + "data_type": "UInt32" + }, + "CommandEnable": { + "namespace_index": "http://www.siemens.com/simatic-s7-opcua", + "node_id": "\"Fill_Service\".\"MTP\".\"ControlEnable\"", + "data_type": "UInt32" + }, + "CurrentStrategy": { + "namespace_index": "http://www.siemens.com/simatic-s7-opcua", + "node_id": "\"Fill_Service\".\"MTP\".\"CurrentStrategy\"", + "data_type": "UInt32" + } + }, + "strategies": [ + { + "id": "1", + "name": "Fill_Clearance_NB", + "default": true, + "sc": false, + "parameters": [] + }, + { + "id": "2", + "name": "Fill_Level_SB", + "default": false, + "sc": true, + "parameters": [] + } + ], + "parameters": [ + { + "name": "Fill_Level_Max", + "interface_class": "ExtAnaOp", + "communication": { + "WQC": { + "namespace_index": "http://www.siemens.com/simatic-s7-opcua", + "node_id": "\"Fill_Level_Max\".\"MTP\".\"WQC\"", + "data_type": "Byte" + }, + "OSLevel": { + "namespace_index": "http://www.siemens.com/simatic-s7-opcua", + "node_id": "\"Fill_Level_Max\".\"MTP\".\"OSLevel\"", + "data_type": "Byte" + }, + "VOut": { + "namespace_index": "http://www.siemens.com/simatic-s7-opcua", + "node_id": "\"Fill_Level_Max\".\"MTP\".\"VOut\"", + "data_type": "Float" + }, + "VSclMin": { + "namespace_index": "http://www.siemens.com/simatic-s7-opcua", + "node_id": "\"Fill_Level_Max\".\"MTP\".\"VSclMin\"", + "data_type": "Float" + }, + "VSclMax": { + "namespace_index": "http://www.siemens.com/simatic-s7-opcua", + "node_id": "\"Fill_Level_Max\".\"MTP\".\"VSclMax\"", + "data_type": "Float" + }, + "VUnit": { + "namespace_index": "http://www.siemens.com/simatic-s7-opcua", + "node_id": "\"Fill_Level_Max\".\"MTP\".\"VUnit\"", + "data_type": "Int16" + }, + "VMin": { + "namespace_index": "http://www.siemens.com/simatic-s7-opcua", + "node_id": "\"Fill_Level_Max\".\"MTP\".\"VMin\"", + "data_type": "Float" + }, + "VMax": { + "namespace_index": "http://www.siemens.com/simatic-s7-opcua", + "node_id": "\"Fill_Level_Max\".\"MTP\".\"VMax\"", + "data_type": "Float" + }, + "VExt": { + "namespace_index": "http://www.siemens.com/simatic-s7-opcua", + "node_id": "\"Fill_Level_Max\".\"MTP\".\"VExt\"", + "data_type": "Float" + }, + "VRbk": { + "namespace_index": "http://www.siemens.com/simatic-s7-opcua", + "node_id": "\"Fill_Level_Max\".\"MTP\".\"VRbk\"", + "data_type": "Float" + } + } + } + ] + }, + { + "name": "Aerate_Service", + "communication": { + "OSLevel": { + "namespace_index": "http://www.siemens.com/simatic-s7-opcua", + "node_id": "\"Aerate_Service\".\"MTP\".\"OSLevel\"", + "data_type": "Byte" + }, + "WQC": { + "namespace_index": "http://www.siemens.com/simatic-s7-opcua", + "node_id": "\"Aerate_Service\".\"MTP\".\"WQC\"", + "data_type": "Byte" + }, + "OpMode": { + "namespace_index": "http://www.siemens.com/simatic-s7-opcua", + "node_id": "\"Aerate_Service\".\"MTP\".\"OpMode\"", + "data_type": "UInt32" + }, + "CommandMan": { + "namespace_index": "http://www.siemens.com/simatic-s7-opcua", + "node_id": "\"Aerate_Service\".\"MTP\".\"ControlOp\"", + "data_type": "UInt32" + }, + "StrategyMan": { + "namespace_index": "http://www.siemens.com/simatic-s7-opcua", + "node_id": "\"Aerate_Service\".\"MTP\".\"StrategyOp\"", + "data_type": "UInt32" + }, + "CommandInt": { + "namespace_index": "http://www.siemens.com/simatic-s7-opcua", + "node_id": "\"Aerate_Service\".\"MTP\".\"ControlInt\"", + "data_type": "UInt32" + }, + "StrategyInt": { + "namespace_index": "http://www.siemens.com/simatic-s7-opcua", + "node_id": "\"Aerate_Service\".\"MTP\".\"StrategyInt\"", + "data_type": "UInt32" + }, + "CommandExt": { + "namespace_index": "http://www.siemens.com/simatic-s7-opcua", + "node_id": "\"Aerate_Service\".\"MTP\".\"ControlExt\"", + "data_type": "UInt32" + }, + "StrategyExt": { + "namespace_index": "http://www.siemens.com/simatic-s7-opcua", + "node_id": "\"Aerate_Service\".\"MTP\".\"StrategyExt\"", + "data_type": "UInt32" + }, + "State": { + "namespace_index": "http://www.siemens.com/simatic-s7-opcua", + "node_id": "\"Aerate_Service\".\"MTP\".\"State\"", + "data_type": "UInt32" + }, + "CommandEnable": { + "namespace_index": "http://www.siemens.com/simatic-s7-opcua", + "node_id": "\"Aerate_Service\".\"MTP\".\"ControlEnable\"", + "data_type": "UInt32" + }, + "CurrentStrategy": { + "namespace_index": "http://www.siemens.com/simatic-s7-opcua", + "node_id": "\"Aerate_Service\".\"MTP\".\"CurrentStrategy\"", + "data_type": "UInt32" + } + }, + "strategies": [ + { + "id": "1", + "name": "Aerate_Continous", + "default": true, + "sc": false, + "parameters": [ + { + "name": "Aerate_Period", + "interface_class": "ExtAnaOp", + "communication": { + "WQC": { + "namespace_index": "http://www.siemens.com/simatic-s7-opcua", + "node_id": "\"Aerate_Period\".\"MTP\".\"WQC\"", + "data_type": "Byte" + }, + "OSLevel": { + "namespace_index": "http://www.siemens.com/simatic-s7-opcua", + "node_id": "\"Aerate_Period\".\"MTP\".\"OSLevel\"", + "data_type": "Byte" + }, + "VOut": { + "namespace_index": "http://www.siemens.com/simatic-s7-opcua", + "node_id": "\"Aerate_Period\".\"MTP\".\"VOut\"", + "data_type": "Float" + }, + "VSclMin": { + "namespace_index": "http://www.siemens.com/simatic-s7-opcua", + "node_id": "\"Aerate_Period\".\"MTP\".\"VSclMin\"", + "data_type": "Float" + }, + "VSclMax": { + "namespace_index": "http://www.siemens.com/simatic-s7-opcua", + "node_id": "\"Aerate_Period\".\"MTP\".\"VSclMax\"", + "data_type": "Float" + }, + "VUnit": { + "namespace_index": "http://www.siemens.com/simatic-s7-opcua", + "node_id": "\"Aerate_Period\".\"MTP\".\"VUnit\"", + "data_type": "Int16" + }, + "VMin": { + "namespace_index": "http://www.siemens.com/simatic-s7-opcua", + "node_id": "\"Aerate_Period\".\"MTP\".\"VMin\"", + "data_type": "Float" + }, + "VMax": { + "namespace_index": "http://www.siemens.com/simatic-s7-opcua", + "node_id": "\"Aerate_Period\".\"MTP\".\"VMax\"", + "data_type": "Float" + }, + "VExt": { + "namespace_index": "http://www.siemens.com/simatic-s7-opcua", + "node_id": "\"Aerate_Period\".\"MTP\".\"VExt\"", + "data_type": "Float" + }, + "VRbk": { + "namespace_index": "http://www.siemens.com/simatic-s7-opcua", + "node_id": "\"Aerate_Period\".\"MTP\".\"VRbk\"", + "data_type": "Float" + } + } + } + ] + } + ], + "parameters": [] + }, + { + "name": "Stir_Service", + "communication": { + "OSLevel": { + "namespace_index": "http://www.siemens.com/simatic-s7-opcua", + "node_id": "\"Stir_Service\".\"MTP\".\"OSLevel\"", + "data_type": "Byte" + }, + "WQC": { + "namespace_index": "http://www.siemens.com/simatic-s7-opcua", + "node_id": "\"Stir_Service\".\"MTP\".\"WQC\"", + "data_type": "Byte" + }, + "OpMode": { + "namespace_index": "http://www.siemens.com/simatic-s7-opcua", + "node_id": "\"Stir_Service\".\"MTP\".\"OpMode\"", + "data_type": "UInt32" + }, + "CommandMan": { + "namespace_index": "http://www.siemens.com/simatic-s7-opcua", + "node_id": "\"Stir_Service\".\"MTP\".\"ControlOp\"", + "data_type": "UInt32" + }, + "StrategyMan": { + "namespace_index": "http://www.siemens.com/simatic-s7-opcua", + "node_id": "\"Stir_Service\".\"MTP\".\"StrategyOp\"", + "data_type": "UInt32" + }, + "CommandInt": { + "namespace_index": "http://www.siemens.com/simatic-s7-opcua", + "node_id": "\"Stir_Service\".\"MTP\".\"ControlInt\"", + "data_type": "UInt32" + }, + "StrategyInt": { + "namespace_index": "http://www.siemens.com/simatic-s7-opcua", + "node_id": "\"Stir_Service\".\"MTP\".\"StrategyInt\"", + "data_type": "UInt32" + }, + "CommandExt": { + "namespace_index": "http://www.siemens.com/simatic-s7-opcua", + "node_id": "\"Stir_Service\".\"MTP\".\"ControlExt\"", + "data_type": "UInt32" + }, + "StrategyExt": { + "namespace_index": "http://www.siemens.com/simatic-s7-opcua", + "node_id": "\"Stir_Service\".\"MTP\".\"StrategyExt\"", + "data_type": "UInt32" + }, + "State": { + "namespace_index": "http://www.siemens.com/simatic-s7-opcua", + "node_id": "\"Stir_Service\".\"MTP\".\"State\"", + "data_type": "UInt32" + }, + "CommandEnable": { + "namespace_index": "http://www.siemens.com/simatic-s7-opcua", + "node_id": "\"Stir_Service\".\"MTP\".\"ControlEnable\"", + "data_type": "UInt32" + }, + "CurrentStrategy": { + "namespace_index": "http://www.siemens.com/simatic-s7-opcua", + "node_id": "\"Stir_Service\".\"MTP\".\"CurrentStrategy\"", + "data_type": "UInt32" + } + }, + "strategies": [ + { + "id": "1", + "name": "Stirring_Continous", + "default": true, + "sc": false, + "parameters": [ + { + "name": "Stir_Rotation", + "interface_class": "ExtAnaOp", + "communication": { + "WQC": { + "namespace_index": "http://www.siemens.com/simatic-s7-opcua", + "node_id": "\"Stir_Rotation\".\"MTP\".\"WQC\"", + "data_type": "Byte" + }, + "OSLevel": { + "namespace_index": "http://www.siemens.com/simatic-s7-opcua", + "node_id": "\"Stir_Rotation\".\"MTP\".\"OSLevel\"", + "data_type": "Byte" + }, + "VOut": { + "namespace_index": "http://www.siemens.com/simatic-s7-opcua", + "node_id": "\"Stir_Rotation\".\"MTP\".\"VOut\"", + "data_type": "Float" + }, + "VSclMin": { + "namespace_index": "http://www.siemens.com/simatic-s7-opcua", + "node_id": "\"Stir_Rotation\".\"MTP\".\"VSclMin\"", + "data_type": "Float" + }, + "VSclMax": { + "namespace_index": "http://www.siemens.com/simatic-s7-opcua", + "node_id": "\"Stir_Rotation\".\"MTP\".\"VSclMax\"", + "data_type": "Float" + }, + "VUnit": { + "namespace_index": "http://www.siemens.com/simatic-s7-opcua", + "node_id": "\"Stir_Rotation\".\"MTP\".\"VUnit\"", + "data_type": "Int16" + }, + "VMin": { + "namespace_index": "http://www.siemens.com/simatic-s7-opcua", + "node_id": "\"Stir_Rotation\".\"MTP\".\"VMin\"", + "data_type": "Float" + }, + "VMax": { + "namespace_index": "http://www.siemens.com/simatic-s7-opcua", + "node_id": "\"Stir_Rotation\".\"MTP\".\"VMax\"", + "data_type": "Float" + }, + "VExt": { + "namespace_index": "http://www.siemens.com/simatic-s7-opcua", + "node_id": "\"Stir_Rotation\".\"MTP\".\"VExt\"", + "data_type": "Float" + }, + "VRbk": { + "namespace_index": "http://www.siemens.com/simatic-s7-opcua", + "node_id": "\"Stir_Rotation\".\"MTP\".\"VRbk\"", + "data_type": "Float" + } + } + } + ] + } + ], + "parameters": [ + { + "name": "Stir_Level_Min", + "interface_class": "ExtAnaOp", + "communication": { + "WQC": { + "namespace_index": "http://www.siemens.com/simatic-s7-opcua", + "node_id": "\"Stir_Level_Min\".\"MTP\".\"WQC\"", + "data_type": "Byte" + }, + "OSLevel": { + "namespace_index": "http://www.siemens.com/simatic-s7-opcua", + "node_id": "\"Stir_Level_Min\".\"MTP\".\"OSLevel\"", + "data_type": "Byte" + }, + "VOut": { + "namespace_index": "http://www.siemens.com/simatic-s7-opcua", + "node_id": "\"Stir_Level_Min\".\"MTP\".\"VOut\"", + "data_type": "Float" + }, + "VSclMin": { + "namespace_index": "http://www.siemens.com/simatic-s7-opcua", + "node_id": "\"Stir_Level_Min\".\"MTP\".\"VSclMin\"", + "data_type": "Float" + }, + "VSclMax": { + "namespace_index": "http://www.siemens.com/simatic-s7-opcua", + "node_id": "\"Stir_Level_Min\".\"MTP\".\"VSclMax\"", + "data_type": "Float" + }, + "VUnit": { + "namespace_index": "http://www.siemens.com/simatic-s7-opcua", + "node_id": "\"Stir_Level_Min\".\"MTP\".\"VUnit\"", + "data_type": "Int16" + }, + "VMin": { + "namespace_index": "http://www.siemens.com/simatic-s7-opcua", + "node_id": "\"Stir_Level_Min\".\"MTP\".\"VMin\"", + "data_type": "Float" + }, + "VMax": { + "namespace_index": "http://www.siemens.com/simatic-s7-opcua", + "node_id": "\"Stir_Level_Min\".\"MTP\".\"VMax\"", + "data_type": "Float" + }, + "VExt": { + "namespace_index": "http://www.siemens.com/simatic-s7-opcua", + "node_id": "\"Stir_Level_Min\".\"MTP\".\"VExt\"", + "data_type": "Float" + }, + "VRbk": { + "namespace_index": "http://www.siemens.com/simatic-s7-opcua", + "node_id": "\"Stir_Level_Min\".\"MTP\".\"VRbk\"", + "data_type": "Float" + } + } + } + ] + }, + { + "name": "Empty_Service", + "communication": { + "OSLevel": { + "namespace_index": "http://www.siemens.com/simatic-s7-opcua", + "node_id": "\"Empty_Service\".\"MTP\".\"OSLevel\"", + "data_type": "Byte" + }, + "WQC": { + "namespace_index": "http://www.siemens.com/simatic-s7-opcua", + "node_id": "\"Empty_Service\".\"MTP\".\"WQC\"", + "data_type": "Byte" + }, + "OpMode": { + "namespace_index": "http://www.siemens.com/simatic-s7-opcua", + "node_id": "\"Empty_Service\".\"MTP\".\"OpMode\"", + "data_type": "UInt32" + }, + "CommandMan": { + "namespace_index": "http://www.siemens.com/simatic-s7-opcua", + "node_id": "\"Empty_Service\".\"MTP\".\"ControlOp\"", + "data_type": "UInt32" + }, + "StrategyMan": { + "namespace_index": "http://www.siemens.com/simatic-s7-opcua", + "node_id": "\"Empty_Service\".\"MTP\".\"StrategyOp\"", + "data_type": "UInt32" + }, + "CommandInt": { + "namespace_index": "http://www.siemens.com/simatic-s7-opcua", + "node_id": "\"Empty_Service\".\"MTP\".\"ControlInt\"", + "data_type": "UInt32" + }, + "StrategyInt": { + "namespace_index": "http://www.siemens.com/simatic-s7-opcua", + "node_id": "\"Empty_Service\".\"MTP\".\"StrategyInt\"", + "data_type": "UInt32" + }, + "CommandExt": { + "namespace_index": "http://www.siemens.com/simatic-s7-opcua", + "node_id": "\"Empty_Service\".\"MTP\".\"ControlExt\"", + "data_type": "UInt32" + }, + "StrategyExt": { + "namespace_index": "http://www.siemens.com/simatic-s7-opcua", + "node_id": "\"Empty_Service\".\"MTP\".\"StrategyExt\"", + "data_type": "UInt32" + }, + "State": { + "namespace_index": "http://www.siemens.com/simatic-s7-opcua", + "node_id": "\"Empty_Service\".\"MTP\".\"State\"", + "data_type": "UInt32" + }, + "CommandEnable": { + "namespace_index": "http://www.siemens.com/simatic-s7-opcua", + "node_id": "\"Empty_Service\".\"MTP\".\"ControlEnable\"", + "data_type": "UInt32" + }, + "CurrentStrategy": { + "namespace_index": "http://www.siemens.com/simatic-s7-opcua", + "node_id": "\"Empty_Service\".\"MTP\".\"CurrentStrategy\"", + "data_type": "UInt32" + } + }, + "strategies": [ + { + "id": "1", + "name": "Empty_Full", + "default": true, + "sc": false, + "parameters": [ + { + "name": "Empty_VolFlow", + "interface_class": "ExtAnaOp", + "communication": { + "WQC": { + "namespace_index": "http://www.siemens.com/simatic-s7-opcua", + "node_id": "\"Empty_VolFlow\".\"MTP\".\"WQC\"", + "data_type": "Byte" + }, + "OSLevel": { + "namespace_index": "http://www.siemens.com/simatic-s7-opcua", + "node_id": "\"Empty_VolFlow\".\"MTP\".\"OSLevel\"", + "data_type": "Byte" + }, + "VOut": { + "namespace_index": "http://www.siemens.com/simatic-s7-opcua", + "node_id": "\"Empty_VolFlow\".\"MTP\".\"VOut\"", + "data_type": "Float" + }, + "VSclMin": { + "namespace_index": "http://www.siemens.com/simatic-s7-opcua", + "node_id": "\"Empty_VolFlow\".\"MTP\".\"VSclMin\"", + "data_type": "Float" + }, + "VSclMax": { + "namespace_index": "http://www.siemens.com/simatic-s7-opcua", + "node_id": "\"Empty_VolFlow\".\"MTP\".\"VSclMax\"", + "data_type": "Float" + }, + "VUnit": { + "namespace_index": "http://www.siemens.com/simatic-s7-opcua", + "node_id": "\"Empty_VolFlow\".\"MTP\".\"VUnit\"", + "data_type": "Int16" + }, + "VMin": { + "namespace_index": "http://www.siemens.com/simatic-s7-opcua", + "node_id": "\"Empty_VolFlow\".\"MTP\".\"VMin\"", + "data_type": "Float" + }, + "VMax": { + "namespace_index": "http://www.siemens.com/simatic-s7-opcua", + "node_id": "\"Empty_VolFlow\".\"MTP\".\"VMax\"", + "data_type": "Float" + }, + "VExt": { + "namespace_index": "http://www.siemens.com/simatic-s7-opcua", + "node_id": "\"Empty_VolFlow\".\"MTP\".\"VOp\"", + "data_type": "Float" + }, + "VRbk": { + "namespace_index": "http://www.siemens.com/simatic-s7-opcua", + "node_id": "\"Empty_VolFlow\".\"MTP\".\"VRbk\"", + "data_type": "Float" + } + } + } + ] + }, + { + "id": "2", + "name": "Empty_VolFlow", + "default": false, + "sc": false, + "parameters": [ + { + "name": "Empty_VolFlow", + "interface_class": "ExtAnaOp", + "communication": { + "WQC": { + "namespace_index": "http://www.siemens.com/simatic-s7-opcua", + "node_id": "\"Empty_VolFlow\".\"MTP\".\"WQC\"", + "data_type": "Byte" + }, + "OSLevel": { + "namespace_index": "http://www.siemens.com/simatic-s7-opcua", + "node_id": "\"Empty_VolFlow\".\"MTP\".\"OSLevel\"", + "data_type": "Byte" + }, + "VOut": { + "namespace_index": "http://www.siemens.com/simatic-s7-opcua", + "node_id": "\"Empty_VolFlow\".\"MTP\".\"VOut\"", + "data_type": "Float" + }, + "VSclMin": { + "namespace_index": "http://www.siemens.com/simatic-s7-opcua", + "node_id": "\"Empty_VolFlow\".\"MTP\".\"VSclMin\"", + "data_type": "Float" + }, + "VSclMax": { + "namespace_index": "http://www.siemens.com/simatic-s7-opcua", + "node_id": "\"Empty_VolFlow\".\"MTP\".\"VSclMax\"", + "data_type": "Float" + }, + "VUnit": { + "namespace_index": "http://www.siemens.com/simatic-s7-opcua", + "node_id": "\"Empty_VolFlow\".\"MTP\".\"VUnit\"", + "data_type": "Int16" + }, + "VMin": { + "namespace_index": "http://www.siemens.com/simatic-s7-opcua", + "node_id": "\"Empty_VolFlow\".\"MTP\".\"VMin\"", + "data_type": "Float" + }, + "VMax": { + "namespace_index": "http://www.siemens.com/simatic-s7-opcua", + "node_id": "\"Empty_VolFlow\".\"MTP\".\"VMax\"", + "data_type": "Float" + }, + "VExt": { + "namespace_index": "http://www.siemens.com/simatic-s7-opcua", + "node_id": "\"Empty_VolFlow\".\"MTP\".\"VOp\"", + "data_type": "Float" + }, + "VRbk": { + "namespace_index": "http://www.siemens.com/simatic-s7-opcua", + "node_id": "\"Empty_VolFlow\".\"MTP\".\"VRbk\"", + "data_type": "Float" + } + } + } + ] + } + ], + "parameters": [ + { + "name": "Empty_Level_Tank_DeadBand", + "interface_class": "ExtAnaOp", + "communication": { + "WQC": { + "namespace_index": "http://www.siemens.com/simatic-s7-opcua", + "node_id": "\"Empty_Level_Tank_DeadBand\".\"MTP\".\"WQC\"", + "data_type": "Byte" + }, + "OSLevel": { + "namespace_index": "http://www.siemens.com/simatic-s7-opcua", + "node_id": "\"Empty_Level_Tank_DeadBand\".\"MTP\".\"OSLevel\"", + "data_type": "Byte" + }, + "VOut": { + "namespace_index": "http://www.siemens.com/simatic-s7-opcua", + "node_id": "\"Empty_Level_Tank_DeadBand\".\"MTP\".\"VOut\"", + "data_type": "Float" + }, + "VSclMin": { + "namespace_index": "http://www.siemens.com/simatic-s7-opcua", + "node_id": "\"Empty_Level_Tank_DeadBand\".\"MTP\".\"VSclMin\"", + "data_type": "Float" + }, + "VSclMax": { + "namespace_index": "http://www.siemens.com/simatic-s7-opcua", + "node_id": "\"Empty_Level_Tank_DeadBand\".\"MTP\".\"VSclMax\"", + "data_type": "Float" + }, + "VUnit": { + "namespace_index": "http://www.siemens.com/simatic-s7-opcua", + "node_id": "\"Empty_Level_Tank_DeadBand\".\"MTP\".\"VUnit\"", + "data_type": "Int16" + }, + "VMin": { + "namespace_index": "http://www.siemens.com/simatic-s7-opcua", + "node_id": "\"Empty_Level_Tank_DeadBand\".\"MTP\".\"VMin\"", + "data_type": "Float" + }, + "VMax": { + "namespace_index": "http://www.siemens.com/simatic-s7-opcua", + "node_id": "\"Empty_Level_Tank_DeadBand\".\"MTP\".\"VMax\"", + "data_type": "Float" + }, + "VExt": { + "namespace_index": "http://www.siemens.com/simatic-s7-opcua", + "node_id": "\"Empty_Level_Tank_DeadBand\".\"MTP\".\"VExt\"", + "data_type": "Float" + }, + "VRbk": { + "namespace_index": "http://www.siemens.com/simatic-s7-opcua", + "node_id": "\"Empty_Level_Tank_DeadBand\".\"MTP\".\"VRbk\"", + "data_type": "Float" + } + } + } + ] + } + ], + "process_values": [ + { + "name": "AEM01_AnaDrv", + "communication": { + "WQC": { + "namespace_index": "http://www.siemens.com/simatic-s7-opcua", + "node_id": "\"AEM01\".\"MTP_AnaDrv\".\"WQC\"", + "data_type": "Byte" + }, + "OSLevel": { + "namespace_index": "http://www.siemens.com/simatic-s7-opcua", + "node_id": "\"AEM01\".\"MTP_AnaDrv\".\"OSLevel\"", + "data_type": "Byte" + }, + "OpMode": { + "namespace_index": "http://www.siemens.com/simatic-s7-opcua", + "node_id": "\"AEM01\".\"MTP_AnaDrv\".\"OpMode\"", + "data_type": "UInt32" + }, + "FwdEn": { + "namespace_index": "http://www.siemens.com/simatic-s7-opcua", + "node_id": "\"AEM01\".\"MTP_AnaDrv\".\"FwdEn\"", + "data_type": "Boolean" + }, + "RevEn": { + "namespace_index": "http://www.siemens.com/simatic-s7-opcua", + "node_id": "\"AEM01\".\"MTP_AnaDrv\".\"RevEn\"", + "data_type": "Boolean" + }, + "StopOp": { + "namespace_index": "http://www.siemens.com/simatic-s7-opcua", + "node_id": "\"AEM01\".\"MTP_AnaDrv\".\"StopOp\"", + "data_type": "Boolean" + }, + "FwdOp": { + "namespace_index": "http://www.siemens.com/simatic-s7-opcua", + "node_id": "\"AEM01\".\"MTP_AnaDrv\".\"FwdOp\"", + "data_type": "Boolean" + }, + "RevOp": { + "namespace_index": "http://www.siemens.com/simatic-s7-opcua", + "node_id": "\"AEM01\".\"MTP_AnaDrv\".\"RevOp\"", + "data_type": "Boolean" + }, + "StopLi": { + "namespace_index": "http://www.siemens.com/simatic-s7-opcua", + "node_id": "\"AEM01\".\"MTP_AnaDrv\".\"StopLi\"", + "data_type": "Boolean" + }, + "FwdLi": { + "namespace_index": "http://www.siemens.com/simatic-s7-opcua", + "node_id": "\"AEM01\".\"MTP_AnaDrv\".\"FwdLi\"", + "data_type": "Boolean" + }, + "RevLi": { + "namespace_index": "http://www.siemens.com/simatic-s7-opcua", + "node_id": "\"AEM01\".\"MTP_AnaDrv\".\"RevLi\"", + "data_type": "Boolean" + }, + "FwdCtrl": { + "namespace_index": "http://www.siemens.com/simatic-s7-opcua", + "node_id": "\"AEM01\".\"MTP_AnaDrv\".\"FwdCtrl\"", + "data_type": "Boolean" + }, + "RevCtrl": { + "namespace_index": "http://www.siemens.com/simatic-s7-opcua", + "node_id": "\"AEM01\".\"MTP_AnaDrv\".\"RevCtrl\"", + "data_type": "Boolean" + }, + "FwdFbkEn": { + "namespace_index": "http://www.siemens.com/simatic-s7-opcua", + "node_id": "\"AEM01\".\"MTP_AnaDrv\".\"FwdFbkEn\"", + "data_type": "Boolean" + }, + "FwdFbk": { + "namespace_index": "http://www.siemens.com/simatic-s7-opcua", + "node_id": "\"AEM01\".\"MTP_AnaDrv\".\"FwdFbk\"", + "data_type": "Boolean" + }, + "RevFbkEn": { + "namespace_index": "http://www.siemens.com/simatic-s7-opcua", + "node_id": "\"AEM01\".\"MTP_AnaDrv\".\"RevFbkEn\"", + "data_type": "Boolean" + }, + "RevFbk": { + "namespace_index": "http://www.siemens.com/simatic-s7-opcua", + "node_id": "\"AEM01\".\"MTP_AnaDrv\".\"RevFbk\"", + "data_type": "Boolean" + }, + "Trip": { + "namespace_index": "http://www.siemens.com/simatic-s7-opcua", + "node_id": "\"AEM01\".\"MTP_AnaDrv\".\"Trip\"", + "data_type": "Boolean" + }, + "Rpm": { + "namespace_index": "http://www.siemens.com/simatic-s7-opcua", + "node_id": "\"AEM01\".\"MTP_AnaDrv\".\"Rpm\"", + "data_type": "Float" + }, + "RpmSclMin": { + "namespace_index": "http://www.siemens.com/simatic-s7-opcua", + "node_id": "\"AEM01\".\"MTP_AnaDrv\".\"RpmSclMin\"", + "data_type": "Float" + }, + "RpmSclMax": { + "namespace_index": "http://www.siemens.com/simatic-s7-opcua", + "node_id": "\"AEM01\".\"MTP_AnaDrv\".\"RpmSclMax\"", + "data_type": "Float" + }, + "RpmUnit": { + "namespace_index": "http://www.siemens.com/simatic-s7-opcua", + "node_id": "\"AEM01\".\"MTP_AnaDrv\".\"RpmUnit\"", + "data_type": "Int16" + }, + "RpmMin": { + "namespace_index": "http://www.siemens.com/simatic-s7-opcua", + "node_id": "\"AEM01\".\"MTP_AnaDrv\".\"RpmMin\"", + "data_type": "Float" + }, + "RpmMax": { + "namespace_index": "http://www.siemens.com/simatic-s7-opcua", + "node_id": "\"AEM01\".\"MTP_AnaDrv\".\"RpmMax\"", + "data_type": "Float" + }, + "RpmExt": { + "namespace_index": "http://www.siemens.com/simatic-s7-opcua", + "node_id": "\"AEM01\".\"MTP_AnaDrv\".\"RpmExt\"", + "data_type": "Float" + }, + "RpmInt": { + "namespace_index": "http://www.siemens.com/simatic-s7-opcua", + "node_id": "\"AEM01\".\"MTP_AnaDrv\".\"RpmInt\"", + "data_type": "Float" + }, + "RpmRbk": { + "value": null + } + } + }, + { + "name": "Aerate_Period_AchemaAnaSP", + "communication": { + "WQC": { + "namespace_index": "http://www.siemens.com/simatic-s7-opcua", + "node_id": "\"Aerate_Period\".\"MTP\".\"WQC\"", + "data_type": "Byte" + }, + "OSLevel": { + "namespace_index": "http://www.siemens.com/simatic-s7-opcua", + "node_id": "\"Aerate_Period\".\"MTP\".\"OSLevel\"", + "data_type": "Byte" + }, + "VOut": { + "namespace_index": "http://www.siemens.com/simatic-s7-opcua", + "node_id": "\"Aerate_Period\".\"MTP\".\"VOut\"", + "data_type": "Float" + }, + "VSclMin": { + "namespace_index": "http://www.siemens.com/simatic-s7-opcua", + "node_id": "\"Aerate_Period\".\"MTP\".\"VSclMin\"", + "data_type": "Float" + }, + "VSclMax": { + "namespace_index": "http://www.siemens.com/simatic-s7-opcua", + "node_id": "\"Aerate_Period\".\"MTP\".\"VSclMax\"", + "data_type": "Float" + }, + "VUnit": { + "namespace_index": "http://www.siemens.com/simatic-s7-opcua", + "node_id": "\"Aerate_Period\".\"MTP\".\"VUnit\"", + "data_type": "Int16" + }, + "VMin": { + "namespace_index": "http://www.siemens.com/simatic-s7-opcua", + "node_id": "\"Aerate_Period\".\"MTP\".\"VMin\"", + "data_type": "Float" + }, + "VMax": { + "namespace_index": "http://www.siemens.com/simatic-s7-opcua", + "node_id": "\"Aerate_Period\".\"MTP\".\"VMax\"", + "data_type": "Float" + }, + "VExt": { + "namespace_index": "http://www.siemens.com/simatic-s7-opcua", + "node_id": "\"Aerate_Period\".\"MTP\".\"VExt\"", + "data_type": "Float" + }, + "VRbk": { + "namespace_index": "http://www.siemens.com/simatic-s7-opcua", + "node_id": "\"Aerate_Period\".\"MTP\".\"VRbk\"", + "data_type": "Float" + } + } + }, + { + "name": "AUT01_BinView", + "communication": { + "WQC": { + "namespace_index": "http://www.siemens.com/simatic-s7-opcua", + "node_id": "\"AUT01\".\"MTP_BinView\".\"WQC\"", + "data_type": "Byte" + }, + "OSLevel": { + "namespace_index": "http://www.siemens.com/simatic-s7-opcua", + "node_id": "\"AUT01\".\"MTP_BinView\".\"OSLevel\"", + "data_type": "Byte" + }, + "V": { + "namespace_index": "http://www.siemens.com/simatic-s7-opcua", + "node_id": "\"AUT01\".\"MTP_BinView\".\"V\"", + "data_type": "Boolean" + }, + "VState0": { + "namespace_index": "http://www.siemens.com/simatic-s7-opcua", + "node_id": "\"AUT01\".\"MTP_BinView\".\"VState0\"", + "data_type": "xs:string" + }, + "VState1": { + "namespace_index": "http://www.siemens.com/simatic-s7-opcua", + "node_id": "\"AUT01\".\"MTP_BinView\".\"VState1\"", + "data_type": "xs:string" + } + } + }, + { + "name": "Empty_Level_Tank_AchemaAnaSP", + "communication": { + "WQC": { + "namespace_index": "http://www.siemens.com/simatic-s7-opcua", + "node_id": "\"Empty_Level_Tank\".\"MTP\".\"WQC\"", + "data_type": "Byte" + }, + "OSLevel": { + "namespace_index": "http://www.siemens.com/simatic-s7-opcua", + "node_id": "\"Empty_Level_Tank\".\"MTP\".\"OSLevel\"", + "data_type": "Byte" + }, + "VOut": { + "namespace_index": "http://www.siemens.com/simatic-s7-opcua", + "node_id": "\"Empty_Level_Tank\".\"MTP\".\"VOut\"", + "data_type": "Float" + }, + "VSclMin": { + "namespace_index": "http://www.siemens.com/simatic-s7-opcua", + "node_id": "\"Empty_Level_Tank\".\"MTP\".\"VSclMin\"", + "data_type": "Float" + }, + "VSclMax": { + "namespace_index": "http://www.siemens.com/simatic-s7-opcua", + "node_id": "\"Empty_Level_Tank\".\"MTP\".\"VSclMax\"", + "data_type": "Float" + }, + "VUnit": { + "namespace_index": "http://www.siemens.com/simatic-s7-opcua", + "node_id": "\"Empty_Level_Tank\".\"MTP\".\"VUnit\"", + "data_type": "Int16" + }, + "VMin": { + "namespace_index": "http://www.siemens.com/simatic-s7-opcua", + "node_id": "\"Empty_Level_Tank\".\"MTP\".\"VMin\"", + "data_type": "Float" + }, + "VMax": { + "namespace_index": "http://www.siemens.com/simatic-s7-opcua", + "node_id": "\"Empty_Level_Tank\".\"MTP\".\"VMax\"", + "data_type": "Float" + }, + "VExt": { + "namespace_index": "http://www.siemens.com/simatic-s7-opcua", + "node_id": "\"Empty_Level_Tank\".\"MTP\".\"VExt\"", + "data_type": "Float" + }, + "VRbk": { + "namespace_index": "http://www.siemens.com/simatic-s7-opcua", + "node_id": "\"Empty_Level_Tank\".\"MTP\".\"VRbk\"", + "data_type": "Float" + } + } + }, + { + "name": "Empty_Level_Tank_DeadBand_AchemaAnaParam", + "communication": { + "WQC": { + "namespace_index": "http://www.siemens.com/simatic-s7-opcua", + "node_id": "\"Empty_Level_Tank_DeadBand\".\"MTP\".\"WQC\"", + "data_type": "Byte" + }, + "OSLevel": { + "namespace_index": "http://www.siemens.com/simatic-s7-opcua", + "node_id": "\"Empty_Level_Tank_DeadBand\".\"MTP\".\"OSLevel\"", + "data_type": "Byte" + }, + "VOut": { + "namespace_index": "http://www.siemens.com/simatic-s7-opcua", + "node_id": "\"Empty_Level_Tank_DeadBand\".\"MTP\".\"VOut\"", + "data_type": "Float" + }, + "VSclMin": { + "namespace_index": "http://www.siemens.com/simatic-s7-opcua", + "node_id": "\"Empty_Level_Tank_DeadBand\".\"MTP\".\"VSclMin\"", + "data_type": "Float" + }, + "VSclMax": { + "namespace_index": "http://www.siemens.com/simatic-s7-opcua", + "node_id": "\"Empty_Level_Tank_DeadBand\".\"MTP\".\"VSclMax\"", + "data_type": "Float" + }, + "VUnit": { + "namespace_index": "http://www.siemens.com/simatic-s7-opcua", + "node_id": "\"Empty_Level_Tank_DeadBand\".\"MTP\".\"VUnit\"", + "data_type": "Int16" + }, + "VMin": { + "namespace_index": "http://www.siemens.com/simatic-s7-opcua", + "node_id": "\"Empty_Level_Tank_DeadBand\".\"MTP\".\"VMin\"", + "data_type": "Float" + }, + "VMax": { + "namespace_index": "http://www.siemens.com/simatic-s7-opcua", + "node_id": "\"Empty_Level_Tank_DeadBand\".\"MTP\".\"VMax\"", + "data_type": "Float" + }, + "VExt": { + "namespace_index": "http://www.siemens.com/simatic-s7-opcua", + "node_id": "\"Empty_Level_Tank_DeadBand\".\"MTP\".\"VExt\"", + "data_type": "Float" + }, + "VRbk": { + "namespace_index": "http://www.siemens.com/simatic-s7-opcua", + "node_id": "\"Empty_Level_Tank_DeadBand\".\"MTP\".\"VRbk\"", + "data_type": "Float" + } + } + }, + { + "name": "Empty_VolFlow_AchemaAnaSP", + "communication": { + "WQC": { + "namespace_index": "http://www.siemens.com/simatic-s7-opcua", + "node_id": "\"Empty_VolFlow\".\"MTP\".\"WQC\"", + "data_type": "Byte" + }, + "OSLevel": { + "namespace_index": "http://www.siemens.com/simatic-s7-opcua", + "node_id": "\"Empty_VolFlow\".\"MTP\".\"OSLevel\"", + "data_type": "Byte" + }, + "VOut": { + "namespace_index": "http://www.siemens.com/simatic-s7-opcua", + "node_id": "\"Empty_VolFlow\".\"MTP\".\"VOut\"", + "data_type": "Float" + }, + "VSclMin": { + "namespace_index": "http://www.siemens.com/simatic-s7-opcua", + "node_id": "\"Empty_VolFlow\".\"MTP\".\"VSclMin\"", + "data_type": "Float" + }, + "VSclMax": { + "namespace_index": "http://www.siemens.com/simatic-s7-opcua", + "node_id": "\"Empty_VolFlow\".\"MTP\".\"VSclMax\"", + "data_type": "Float" + }, + "VUnit": { + "namespace_index": "http://www.siemens.com/simatic-s7-opcua", + "node_id": "\"Empty_VolFlow\".\"MTP\".\"VUnit\"", + "data_type": "Int16" + }, + "VMin": { + "namespace_index": "http://www.siemens.com/simatic-s7-opcua", + "node_id": "\"Empty_VolFlow\".\"MTP\".\"VMin\"", + "data_type": "Float" + }, + "VMax": { + "namespace_index": "http://www.siemens.com/simatic-s7-opcua", + "node_id": "\"Empty_VolFlow\".\"MTP\".\"VMax\"", + "data_type": "Float" + }, + "VExt": { + "namespace_index": "http://www.siemens.com/simatic-s7-opcua", + "node_id": "\"Empty_VolFlow\".\"MTP\".\"VOp\"", + "data_type": "Float" + }, + "VRbk": { + "namespace_index": "http://www.siemens.com/simatic-s7-opcua", + "node_id": "\"Empty_VolFlow\".\"MTP\".\"VRbk\"", + "data_type": "Float" + } + } + }, + { + "name": "Empty_Volume_AchemaAnaSP", + "communication": { + "WQC": { + "namespace_index": "http://www.siemens.com/simatic-s7-opcua", + "node_id": "\"Empty_Volume\".\"MTP\".\"WQC\"", + "data_type": "Byte" + }, + "OSLevel": { + "namespace_index": "http://www.siemens.com/simatic-s7-opcua", + "node_id": "\"Empty_Volume\".\"MTP\".\"OSLevel\"", + "data_type": "Byte" + }, + "VOut": { + "namespace_index": "http://www.siemens.com/simatic-s7-opcua", + "node_id": "\"Empty_Volume\".\"MTP\".\"VOut\"", + "data_type": "Float" + }, + "VSclMin": { + "namespace_index": "http://www.siemens.com/simatic-s7-opcua", + "node_id": "\"Empty_Volume\".\"MTP\".\"VSclMin\"", + "data_type": "Float" + }, + "VSclMax": { + "namespace_index": "http://www.siemens.com/simatic-s7-opcua", + "node_id": "\"Empty_Volume\".\"MTP\".\"VSclMax\"", + "data_type": "Float" + }, + "VUnit": { + "namespace_index": "http://www.siemens.com/simatic-s7-opcua", + "node_id": "\"Empty_Volume\".\"MTP\".\"VUnit\"", + "data_type": "Int16" + }, + "VMin": { + "namespace_index": "http://www.siemens.com/simatic-s7-opcua", + "node_id": "\"Empty_Volume\".\"MTP\".\"VMin\"", + "data_type": "Float" + }, + "VMax": { + "namespace_index": "http://www.siemens.com/simatic-s7-opcua", + "node_id": "\"Empty_Volume\".\"MTP\".\"VMax\"", + "data_type": "Float" + }, + "VExt": { + "namespace_index": "http://www.siemens.com/simatic-s7-opcua", + "node_id": "\"Empty_Volume\".\"MTP\".\"VExt\"", + "data_type": "Float" + }, + "VRbk": { + "namespace_index": "http://www.siemens.com/simatic-s7-opcua", + "node_id": "\"Empty_Volume\".\"MTP\".\"VRbk\"", + "data_type": "Float" + } + } + }, + { + "name": "Fill_Level_Max_AchemaAnaParam", + "communication": { + "WQC": { + "namespace_index": "http://www.siemens.com/simatic-s7-opcua", + "node_id": "\"Fill_Level_Max\".\"MTP\".\"WQC\"", + "data_type": "Byte" + }, + "OSLevel": { + "namespace_index": "http://www.siemens.com/simatic-s7-opcua", + "node_id": "\"Fill_Level_Max\".\"MTP\".\"OSLevel\"", + "data_type": "Byte" + }, + "VOut": { + "namespace_index": "http://www.siemens.com/simatic-s7-opcua", + "node_id": "\"Fill_Level_Max\".\"MTP\".\"VOut\"", + "data_type": "Float" + }, + "VSclMin": { + "namespace_index": "http://www.siemens.com/simatic-s7-opcua", + "node_id": "\"Fill_Level_Max\".\"MTP\".\"VSclMin\"", + "data_type": "Float" + }, + "VSclMax": { + "namespace_index": "http://www.siemens.com/simatic-s7-opcua", + "node_id": "\"Fill_Level_Max\".\"MTP\".\"VSclMax\"", + "data_type": "Float" + }, + "VUnit": { + "namespace_index": "http://www.siemens.com/simatic-s7-opcua", + "node_id": "\"Fill_Level_Max\".\"MTP\".\"VUnit\"", + "data_type": "Int16" + }, + "VMin": { + "namespace_index": "http://www.siemens.com/simatic-s7-opcua", + "node_id": "\"Fill_Level_Max\".\"MTP\".\"VMin\"", + "data_type": "Float" + }, + "VMax": { + "namespace_index": "http://www.siemens.com/simatic-s7-opcua", + "node_id": "\"Fill_Level_Max\".\"MTP\".\"VMax\"", + "data_type": "Float" + }, + "VExt": { + "namespace_index": "http://www.siemens.com/simatic-s7-opcua", + "node_id": "\"Fill_Level_Max\".\"MTP\".\"VExt\"", + "data_type": "Float" + }, + "VRbk": { + "namespace_index": "http://www.siemens.com/simatic-s7-opcua", + "node_id": "\"Fill_Level_Max\".\"MTP\".\"VRbk\"", + "data_type": "Float" + } + } + }, + { + "name": "Fill_Volume_AchemaAnaSP", + "communication": { + "WQC": { + "namespace_index": "http://www.siemens.com/simatic-s7-opcua", + "node_id": "\"Fill_Volume\".\"MTP\".\"WQC\"", + "data_type": "Byte" + }, + "OSLevel": { + "namespace_index": "http://www.siemens.com/simatic-s7-opcua", + "node_id": "\"Fill_Volume\".\"MTP\".\"OSLevel\"", + "data_type": "Byte" + }, + "VOut": { + "namespace_index": "http://www.siemens.com/simatic-s7-opcua", + "node_id": "\"Fill_Volume\".\"MTP\".\"VOut\"", + "data_type": "Float" + }, + "VSclMin": { + "namespace_index": "http://www.siemens.com/simatic-s7-opcua", + "node_id": "\"Fill_Volume\".\"MTP\".\"VSclMin\"", + "data_type": "Float" + }, + "VSclMax": { + "namespace_index": "http://www.siemens.com/simatic-s7-opcua", + "node_id": "\"Fill_Volume\".\"MTP\".\"VSclMax\"", + "data_type": "Float" + }, + "VUnit": { + "namespace_index": "http://www.siemens.com/simatic-s7-opcua", + "node_id": "\"Fill_Volume\".\"MTP\".\"VUnit\"", + "data_type": "Int16" + }, + "VMin": { + "namespace_index": "http://www.siemens.com/simatic-s7-opcua", + "node_id": "\"Fill_Volume\".\"MTP\".\"VMin\"", + "data_type": "Float" + }, + "VMax": { + "namespace_index": "http://www.siemens.com/simatic-s7-opcua", + "node_id": "\"Fill_Volume\".\"MTP\".\"VMax\"", + "data_type": "Float" + }, + "VExt": { + "namespace_index": "http://www.siemens.com/simatic-s7-opcua", + "node_id": "\"Fill_Volume\".\"MTP\".\"VExt\"", + "data_type": "Float" + }, + "VRbk": { + "namespace_index": "http://www.siemens.com/simatic-s7-opcua", + "node_id": "\"Fill_Volume\".\"MTP\".\"VRbk\"", + "data_type": "Float" + } + } + }, + { + "name": "MCL01_AnaView", + "communication": { + "WQC": { + "namespace_index": "http://www.siemens.com/simatic-s7-opcua", + "node_id": "\"MCL01\".\"MTP_AnaView\".\"WQC\"", + "data_type": "Byte" + }, + "OSLevel": { + "namespace_index": "http://www.siemens.com/simatic-s7-opcua", + "node_id": "\"MCL01\".\"MTP_AnaView\".\"OSLevel\"", + "data_type": "Byte" + }, + "V": { + "namespace_index": "http://www.siemens.com/simatic-s7-opcua", + "node_id": "\"MCL01\".\"MTP_AnaView\".\"V\"", + "data_type": "Float" + }, + "VSclMin": { + "namespace_index": "http://www.siemens.com/simatic-s7-opcua", + "node_id": "\"MCL01\".\"MTP_AnaView\".\"VSclMin\"", + "data_type": "Float" + }, + "VSclMax": { + "namespace_index": "http://www.siemens.com/simatic-s7-opcua", + "node_id": "\"MCL01\".\"MTP_AnaView\".\"VSclMax\"", + "data_type": "Float" + }, + "VUnit": { + "namespace_index": "http://www.siemens.com/simatic-s7-opcua", + "node_id": "\"MCL01\".\"MTP_AnaView\".\"VUnit\"", + "data_type": "Int16" + } + } + }, + { + "name": "MCL02_BinView", + "communication": { + "WQC": { + "namespace_index": "http://www.siemens.com/simatic-s7-opcua", + "node_id": "\"MCL02\".\"MTP_BinView\".\"WQC\"", + "data_type": "Byte" + }, + "OSLevel": { + "namespace_index": "http://www.siemens.com/simatic-s7-opcua", + "node_id": "\"MCL02\".\"MTP_BinView\".\"OSLevel\"", + "data_type": "Byte" + }, + "V": { + "namespace_index": "http://www.siemens.com/simatic-s7-opcua", + "node_id": "\"MCL02\".\"MTP_BinView\".\"V\"", + "data_type": "Boolean" + }, + "VState0": { + "namespace_index": "http://www.siemens.com/simatic-s7-opcua", + "node_id": "\"MCL02\".\"MTP_BinView\".\"VState0\"", + "data_type": "xs:string" + }, + "VState1": { + "namespace_index": "http://www.siemens.com/simatic-s7-opcua", + "node_id": "\"MCL02\".\"MTP_BinView\".\"VState1\"", + "data_type": "xs:string" + } + } + }, + { + "name": "MCT01_AnaView", + "communication": { + "WQC": { + "namespace_index": "http://www.siemens.com/simatic-s7-opcua", + "node_id": "\"MCT01\".\"MTP_AnaView\".\"WQC\"", + "data_type": "Byte" + }, + "OSLevel": { + "namespace_index": "http://www.siemens.com/simatic-s7-opcua", + "node_id": "\"MCT01\".\"MTP_AnaView\".\"OSLevel\"", + "data_type": "Byte" + }, + "V": { + "namespace_index": "http://www.siemens.com/simatic-s7-opcua", + "node_id": "\"MCT01\".\"MTP_AnaView\".\"V\"", + "data_type": "Float" + }, + "VSclMin": { + "namespace_index": "http://www.siemens.com/simatic-s7-opcua", + "node_id": "\"MCT01\".\"MTP_AnaView\".\"VSclMin\"", + "data_type": "Float" + }, + "VSclMax": { + "namespace_index": "http://www.siemens.com/simatic-s7-opcua", + "node_id": "\"MCT01\".\"MTP_AnaView\".\"VSclMax\"", + "data_type": "Float" + }, + "VUnit": { + "namespace_index": "http://www.siemens.com/simatic-s7-opcua", + "node_id": "\"MCT01\".\"MTP_AnaView\".\"VUnit\"", + "data_type": "Int16" + } + } + }, + { + "name": "MCT02_AnaView", + "communication": { + "WQC": { + "namespace_index": "http://www.siemens.com/simatic-s7-opcua", + "node_id": "\"MCT02\".\"MTP_AnaView\".\"WQC\"", + "data_type": "Byte" + }, + "OSLevel": { + "namespace_index": "http://www.siemens.com/simatic-s7-opcua", + "node_id": "\"MCT02\".\"MTP_AnaView\".\"OSLevel\"", + "data_type": "Byte" + }, + "V": { + "namespace_index": "http://www.siemens.com/simatic-s7-opcua", + "node_id": "\"MCT02\".\"MTP_AnaView\".\"V\"", + "data_type": "Float" + }, + "VSclMin": { + "namespace_index": "http://www.siemens.com/simatic-s7-opcua", + "node_id": "\"MCT02\".\"MTP_AnaView\".\"VSclMin\"", + "data_type": "Float" + }, + "VSclMax": { + "namespace_index": "http://www.siemens.com/simatic-s7-opcua", + "node_id": "\"MCT02\".\"MTP_AnaView\".\"VSclMax\"", + "data_type": "Float" + }, + "VUnit": { + "namespace_index": "http://www.siemens.com/simatic-s7-opcua", + "node_id": "\"MCT02\".\"MTP_AnaView\".\"VUnit\"", + "data_type": "Int16" + } + } + }, + { + "name": "MCT03_AnaView", + "communication": { + "WQC": { + "namespace_index": "http://www.siemens.com/simatic-s7-opcua", + "node_id": "\"MCT03\".\"MTP_AnaView\".\"WQC\"", + "data_type": "Byte" + }, + "OSLevel": { + "namespace_index": "http://www.siemens.com/simatic-s7-opcua", + "node_id": "\"MCT03\".\"MTP_AnaView\".\"OSLevel\"", + "data_type": "Byte" + }, + "V": { + "namespace_index": "http://www.siemens.com/simatic-s7-opcua", + "node_id": "\"MCT03\".\"MTP_AnaView\".\"V\"", + "data_type": "Float" + }, + "VSclMin": { + "namespace_index": "http://www.siemens.com/simatic-s7-opcua", + "node_id": "\"MCT03\".\"MTP_AnaView\".\"VSclMin\"", + "data_type": "Float" + }, + "VSclMax": { + "namespace_index": "http://www.siemens.com/simatic-s7-opcua", + "node_id": "\"MCT03\".\"MTP_AnaView\".\"VSclMax\"", + "data_type": "Float" + }, + "VUnit": { + "namespace_index": "http://www.siemens.com/simatic-s7-opcua", + "node_id": "\"MCT03\".\"MTP_AnaView\".\"VUnit\"", + "data_type": "Int16" + } + } + }, + { + "name": "MCT04_AnaView", + "communication": { + "WQC": { + "namespace_index": "http://www.siemens.com/simatic-s7-opcua", + "node_id": "\"MCT04\".\"MTP_AnaView\".\"WQC\"", + "data_type": "Byte" + }, + "OSLevel": { + "namespace_index": "http://www.siemens.com/simatic-s7-opcua", + "node_id": "\"MCT04\".\"MTP_AnaView\".\"OSLevel\"", + "data_type": "Byte" + }, + "V": { + "namespace_index": "http://www.siemens.com/simatic-s7-opcua", + "node_id": "\"MCT04\".\"MTP_AnaView\".\"V\"", + "data_type": "Float" + }, + "VSclMin": { + "namespace_index": "http://www.siemens.com/simatic-s7-opcua", + "node_id": "\"MCT04\".\"MTP_AnaView\".\"VSclMin\"", + "data_type": "Float" + }, + "VSclMax": { + "namespace_index": "http://www.siemens.com/simatic-s7-opcua", + "node_id": "\"MCT04\".\"MTP_AnaView\".\"VSclMax\"", + "data_type": "Float" + }, + "VUnit": { + "namespace_index": "http://www.siemens.com/simatic-s7-opcua", + "node_id": "\"MCT04\".\"MTP_AnaView\".\"VUnit\"", + "data_type": "Int16" + } + } + }, + { + "name": "MFH01_BinVlv", + "communication": { + "WQC": { + "namespace_index": "http://www.siemens.com/simatic-s7-opcua", + "node_id": "\"MFH01\".\"MTP_BinVlv\".\"WQC\"", + "data_type": "Byte" + }, + "OSLevel": { + "namespace_index": "http://www.siemens.com/simatic-s7-opcua", + "node_id": "\"MFH01\".\"MTP_BinVlv\".\"OSLevel\"", + "data_type": "Byte" + }, + "OpMode": { + "namespace_index": "http://www.siemens.com/simatic-s7-opcua", + "node_id": "\"MFH01\".\"MTP_BinVlv\".\"OpMode\"", + "data_type": "UInt32" + }, + "SafePos": { + "namespace_index": "http://www.siemens.com/simatic-s7-opcua", + "node_id": "\"MFH01\".\"MTP_BinVlv\".\"SafePos\"", + "data_type": "Boolean" + }, + "OpenFbkEn": { + "namespace_index": "http://www.siemens.com/simatic-s7-opcua", + "node_id": "\"MFH01\".\"MTP_BinVlv\".\"OpenFbkEn\"", + "data_type": "Boolean" + }, + "CloseFbkEn": { + "namespace_index": "http://www.siemens.com/simatic-s7-opcua", + "node_id": "\"MFH01\".\"MTP_BinVlv\".\"CloseFbkEn\"", + "data_type": "Boolean" + }, + "OpenOp": { + "namespace_index": "http://www.siemens.com/simatic-s7-opcua", + "node_id": "\"MFH01\".\"MTP_BinVlv\".\"OpenOp\"", + "data_type": "Boolean" + }, + "CloseOp": { + "namespace_index": "http://www.siemens.com/simatic-s7-opcua", + "node_id": "\"MFH01\".\"MTP_BinVlv\".\"CloseOp\"", + "data_type": "Boolean" + }, + "OpenLi": { + "namespace_index": "http://www.siemens.com/simatic-s7-opcua", + "node_id": "\"MFH01\".\"MTP_BinVlv\".\"OpenLi\"", + "data_type": "Boolean" + }, + "CloseLi": { + "namespace_index": "http://www.siemens.com/simatic-s7-opcua", + "node_id": "\"MFH01\".\"MTP_BinVlv\".\"CloseLi\"", + "data_type": "Boolean" + }, + "Ctrl": { + "namespace_index": "http://www.siemens.com/simatic-s7-opcua", + "node_id": "\"MFH01\".\"MTP_BinVlv\".\"Ctrl\"", + "data_type": "Boolean" + }, + "OpenFbk": { + "namespace_index": "http://www.siemens.com/simatic-s7-opcua", + "node_id": "\"MFH01\".\"MTP_BinVlv\".\"OpenFbk\"", + "data_type": "Boolean" + }, + "CloseFbk": { + "namespace_index": "http://www.siemens.com/simatic-s7-opcua", + "node_id": "\"MFH01\".\"MTP_BinVlv\".\"CloseFbk\"", + "data_type": "Boolean" + } + } + }, + { + "name": "MFH02_BinVlv", + "communication": { + "WQC": { + "namespace_index": "http://www.siemens.com/simatic-s7-opcua", + "node_id": "\"MFH02\".\"MTP_BinVlv\".\"WQC\"", + "data_type": "Byte" + }, + "OSLevel": { + "namespace_index": "http://www.siemens.com/simatic-s7-opcua", + "node_id": "\"MFH02\".\"MTP_BinVlv\".\"OSLevel\"", + "data_type": "Byte" + }, + "OpMode": { + "namespace_index": "http://www.siemens.com/simatic-s7-opcua", + "node_id": "\"MFH02\".\"MTP_BinVlv\".\"OpMode\"", + "data_type": "UInt32" + }, + "SafePos": { + "namespace_index": "http://www.siemens.com/simatic-s7-opcua", + "node_id": "\"MFH02\".\"MTP_BinVlv\".\"SafePos\"", + "data_type": "Boolean" + }, + "OpenFbkEn": { + "namespace_index": "http://www.siemens.com/simatic-s7-opcua", + "node_id": "\"MFH02\".\"MTP_BinVlv\".\"OpenFbkEn\"", + "data_type": "Boolean" + }, + "CloseFbkEn": { + "namespace_index": "http://www.siemens.com/simatic-s7-opcua", + "node_id": "\"MFH02\".\"MTP_BinVlv\".\"CloseFbkEn\"", + "data_type": "Boolean" + }, + "OpenOp": { + "namespace_index": "http://www.siemens.com/simatic-s7-opcua", + "node_id": "\"MFH02\".\"MTP_BinVlv\".\"OpenOp\"", + "data_type": "Boolean" + }, + "CloseOp": { + "namespace_index": "http://www.siemens.com/simatic-s7-opcua", + "node_id": "\"MFH02\".\"MTP_BinVlv\".\"CloseOp\"", + "data_type": "Boolean" + }, + "OpenLi": { + "namespace_index": "http://www.siemens.com/simatic-s7-opcua", + "node_id": "\"MFH02\".\"MTP_BinVlv\".\"OpenLi\"", + "data_type": "Boolean" + }, + "CloseLi": { + "namespace_index": "http://www.siemens.com/simatic-s7-opcua", + "node_id": "\"MFH02\".\"MTP_BinVlv\".\"CloseLi\"", + "data_type": "Boolean" + }, + "Ctrl": { + "namespace_index": "http://www.siemens.com/simatic-s7-opcua", + "node_id": "\"MFH02\".\"MTP_BinVlv\".\"Ctrl\"", + "data_type": "Boolean" + }, + "OpenFbk": { + "namespace_index": "http://www.siemens.com/simatic-s7-opcua", + "node_id": "\"MFH02\".\"MTP_BinVlv\".\"OpenFbk\"", + "data_type": "Boolean" + }, + "CloseFbk": { + "namespace_index": "http://www.siemens.com/simatic-s7-opcua", + "node_id": "\"MFH02\".\"MTP_BinVlv\".\"CloseFbk\"", + "data_type": "Boolean" + } + } + }, + { + "name": "MFH03_BinVlv", + "communication": { + "WQC": { + "namespace_index": "http://www.siemens.com/simatic-s7-opcua", + "node_id": "\"MFH03\".\"MTP_BinVlv\".\"WQC\"", + "data_type": "Byte" + }, + "OSLevel": { + "namespace_index": "http://www.siemens.com/simatic-s7-opcua", + "node_id": "\"MFH03\".\"MTP_BinVlv\".\"OSLevel\"", + "data_type": "Byte" + }, + "OpMode": { + "namespace_index": "http://www.siemens.com/simatic-s7-opcua", + "node_id": "\"MFH03\".\"MTP_BinVlv\".\"OpMode\"", + "data_type": "UInt32" + }, + "SafePos": { + "namespace_index": "http://www.siemens.com/simatic-s7-opcua", + "node_id": "\"MFH03\".\"MTP_BinVlv\".\"SafePos\"", + "data_type": "Boolean" + }, + "OpenFbkEn": { + "namespace_index": "http://www.siemens.com/simatic-s7-opcua", + "node_id": "\"MFH03\".\"MTP_BinVlv\".\"OpenFbkEn\"", + "data_type": "Boolean" + }, + "CloseFbkEn": { + "namespace_index": "http://www.siemens.com/simatic-s7-opcua", + "node_id": "\"MFH03\".\"MTP_BinVlv\".\"CloseFbkEn\"", + "data_type": "Boolean" + }, + "OpenOp": { + "namespace_index": "http://www.siemens.com/simatic-s7-opcua", + "node_id": "\"MFH03\".\"MTP_BinVlv\".\"OpenOp\"", + "data_type": "Boolean" + }, + "CloseOp": { + "namespace_index": "http://www.siemens.com/simatic-s7-opcua", + "node_id": "\"MFH03\".\"MTP_BinVlv\".\"CloseOp\"", + "data_type": "Boolean" + }, + "OpenLi": { + "namespace_index": "http://www.siemens.com/simatic-s7-opcua", + "node_id": "\"MFH03\".\"MTP_BinVlv\".\"OpenLi\"", + "data_type": "Boolean" + }, + "CloseLi": { + "namespace_index": "http://www.siemens.com/simatic-s7-opcua", + "node_id": "\"MFH03\".\"MTP_BinVlv\".\"CloseLi\"", + "data_type": "Boolean" + }, + "Ctrl": { + "namespace_index": "http://www.siemens.com/simatic-s7-opcua", + "node_id": "\"MFH03\".\"MTP_BinVlv\".\"Ctrl\"", + "data_type": "Boolean" + }, + "OpenFbk": { + "namespace_index": "http://www.siemens.com/simatic-s7-opcua", + "node_id": "\"MFH03\".\"MTP_BinVlv\".\"OpenFbk\"", + "data_type": "Boolean" + }, + "CloseFbk": { + "namespace_index": "http://www.siemens.com/simatic-s7-opcua", + "node_id": "\"MFH03\".\"MTP_BinVlv\".\"CloseFbk\"", + "data_type": "Boolean" + } + } + }, + { + "name": "Stir_Level_Min_AchemaAnaParam", + "communication": { + "WQC": { + "namespace_index": "http://www.siemens.com/simatic-s7-opcua", + "node_id": "\"Stir_Level_Min\".\"MTP\".\"WQC\"", + "data_type": "Byte" + }, + "OSLevel": { + "namespace_index": "http://www.siemens.com/simatic-s7-opcua", + "node_id": "\"Stir_Level_Min\".\"MTP\".\"OSLevel\"", + "data_type": "Byte" + }, + "VOut": { + "namespace_index": "http://www.siemens.com/simatic-s7-opcua", + "node_id": "\"Stir_Level_Min\".\"MTP\".\"VOut\"", + "data_type": "Float" + }, + "VSclMin": { + "namespace_index": "http://www.siemens.com/simatic-s7-opcua", + "node_id": "\"Stir_Level_Min\".\"MTP\".\"VSclMin\"", + "data_type": "Float" + }, + "VSclMax": { + "namespace_index": "http://www.siemens.com/simatic-s7-opcua", + "node_id": "\"Stir_Level_Min\".\"MTP\".\"VSclMax\"", + "data_type": "Float" + }, + "VUnit": { + "namespace_index": "http://www.siemens.com/simatic-s7-opcua", + "node_id": "\"Stir_Level_Min\".\"MTP\".\"VUnit\"", + "data_type": "Int16" + }, + "VMin": { + "namespace_index": "http://www.siemens.com/simatic-s7-opcua", + "node_id": "\"Stir_Level_Min\".\"MTP\".\"VMin\"", + "data_type": "Float" + }, + "VMax": { + "namespace_index": "http://www.siemens.com/simatic-s7-opcua", + "node_id": "\"Stir_Level_Min\".\"MTP\".\"VMax\"", + "data_type": "Float" + }, + "VExt": { + "namespace_index": "http://www.siemens.com/simatic-s7-opcua", + "node_id": "\"Stir_Level_Min\".\"MTP\".\"VExt\"", + "data_type": "Float" + }, + "VRbk": { + "namespace_index": "http://www.siemens.com/simatic-s7-opcua", + "node_id": "\"Stir_Level_Min\".\"MTP\".\"VRbk\"", + "data_type": "Float" + } + } + }, + { + "name": "Stir_Period_AchemaAnaSP", + "communication": { + "WQC": { + "namespace_index": "http://www.siemens.com/simatic-s7-opcua", + "node_id": "\"Stir_Period\".\"MTP\".\"WQC\"", + "data_type": "Byte" + }, + "OSLevel": { + "namespace_index": "http://www.siemens.com/simatic-s7-opcua", + "node_id": "\"Stir_Period\".\"MTP\".\"OSLevel\"", + "data_type": "Byte" + }, + "VOut": { + "namespace_index": "http://www.siemens.com/simatic-s7-opcua", + "node_id": "\"Stir_Period\".\"MTP\".\"VOut\"", + "data_type": "Float" + }, + "VSclMin": { + "namespace_index": "http://www.siemens.com/simatic-s7-opcua", + "node_id": "\"Stir_Period\".\"MTP\".\"VSclMin\"", + "data_type": "Float" + }, + "VSclMax": { + "namespace_index": "http://www.siemens.com/simatic-s7-opcua", + "node_id": "\"Stir_Period\".\"MTP\".\"VSclMax\"", + "data_type": "Float" + }, + "VUnit": { + "namespace_index": "http://www.siemens.com/simatic-s7-opcua", + "node_id": "\"Stir_Period\".\"MTP\".\"VUnit\"", + "data_type": "Int16" + }, + "VMin": { + "namespace_index": "http://www.siemens.com/simatic-s7-opcua", + "node_id": "\"Stir_Period\".\"MTP\".\"VMin\"", + "data_type": "Float" + }, + "VMax": { + "namespace_index": "http://www.siemens.com/simatic-s7-opcua", + "node_id": "\"Stir_Period\".\"MTP\".\"VMax\"", + "data_type": "Float" + }, + "VExt": { + "namespace_index": "http://www.siemens.com/simatic-s7-opcua", + "node_id": "\"Stir_Period\".\"MTP\".\"VExt\"", + "data_type": "Float" + }, + "VRbk": { + "namespace_index": "http://www.siemens.com/simatic-s7-opcua", + "node_id": "\"Stir_Period\".\"MTP\".\"VRbk\"", + "data_type": "Float" + } + } + }, + { + "name": "Stir_Rotation_AchemaAnaSP", + "communication": { + "WQC": { + "namespace_index": "http://www.siemens.com/simatic-s7-opcua", + "node_id": "\"Stir_Rotation\".\"MTP\".\"WQC\"", + "data_type": "Byte" + }, + "OSLevel": { + "namespace_index": "http://www.siemens.com/simatic-s7-opcua", + "node_id": "\"Stir_Rotation\".\"MTP\".\"OSLevel\"", + "data_type": "Byte" + }, + "VOut": { + "namespace_index": "http://www.siemens.com/simatic-s7-opcua", + "node_id": "\"Stir_Rotation\".\"MTP\".\"VOut\"", + "data_type": "Float" + }, + "VSclMin": { + "namespace_index": "http://www.siemens.com/simatic-s7-opcua", + "node_id": "\"Stir_Rotation\".\"MTP\".\"VSclMin\"", + "data_type": "Float" + }, + "VSclMax": { + "namespace_index": "http://www.siemens.com/simatic-s7-opcua", + "node_id": "\"Stir_Rotation\".\"MTP\".\"VSclMax\"", + "data_type": "Float" + }, + "VUnit": { + "namespace_index": "http://www.siemens.com/simatic-s7-opcua", + "node_id": "\"Stir_Rotation\".\"MTP\".\"VUnit\"", + "data_type": "Int16" + }, + "VMin": { + "namespace_index": "http://www.siemens.com/simatic-s7-opcua", + "node_id": "\"Stir_Rotation\".\"MTP\".\"VMin\"", + "data_type": "Float" + }, + "VMax": { + "namespace_index": "http://www.siemens.com/simatic-s7-opcua", + "node_id": "\"Stir_Rotation\".\"MTP\".\"VMax\"", + "data_type": "Float" + }, + "VExt": { + "namespace_index": "http://www.siemens.com/simatic-s7-opcua", + "node_id": "\"Stir_Rotation\".\"MTP\".\"VExt\"", + "data_type": "Float" + }, + "VRbk": { + "namespace_index": "http://www.siemens.com/simatic-s7-opcua", + "node_id": "\"Stir_Rotation\".\"MTP\".\"VRbk\"", + "data_type": "Float" + } + } + } + ] + } + ] +} diff --git a/assets/modules/modules_achema.json b/assets/modules/achema_demonstrator/modules_achema.json similarity index 98% rename from assets/modules/modules_achema.json rename to assets/modules/achema_demonstrator/modules_achema.json index 677867ab..abd0b2fd 100644 --- a/assets/modules/modules_achema.json +++ b/assets/modules/achema_demonstrator/modules_achema.json @@ -49,12 +49,12 @@ "node_id": "|var|WAGO 750-8202 PFC200 CS 2ETH RS.App_Dosing.Services.Fill.OpMode.binary", "data_type": "UInt32" }, - "ControlOp": { + "CommandMan": { "namespace_index": "CODESYSSPV3/3S/IecVarAccess", "node_id": "|var|WAGO 750-8202 PFC200 CS 2ETH RS.App_Dosing.Services.Fill.ControlOp", "data_type": "UInt32" }, - "StrategyOp": { + "StrategyMan": { "namespace_index": "CODESYSSPV3/3S/IecVarAccess", "node_id": "|var|WAGO 750-8202 PFC200 CS 2ETH RS.App_Dosing.Services.Fill.StrategyOp", "data_type": "UInt32" @@ -69,7 +69,7 @@ "node_id": "|var|WAGO 750-8202 PFC200 CS 2ETH RS.App_Dosing.Services.Fill.StrategyInt", "data_type": "UInt32" }, - "ControlExt": { + "CommandExt": { "namespace_index": "CODESYSSPV3/3S/IecVarAccess", "node_id": "|var|WAGO 750-8202 PFC200 CS 2ETH RS.App_Dosing.Services.Fill.ControlExt", "data_type": "UInt32" @@ -84,7 +84,7 @@ "node_id": "|var|WAGO 750-8202 PFC200 CS 2ETH RS.App_Dosing.Services.Fill.State.binary", "data_type": "UInt32" }, - "ControlEnable": { + "CommandEnable": { "namespace_index": "CODESYSSPV3/3S/IecVarAccess", "node_id": "|var|WAGO 750-8202 PFC200 CS 2ETH RS.App_Dosing.Services.Fill.ControlEnable.binary", "data_type": "UInt32" @@ -188,12 +188,12 @@ "node_id": "|var|WAGO 750-8202 PFC200 CS 2ETH RS.App_Dosing.Services.DoseAmount.OpMode.binary", "data_type": "UInt32" }, - "ControlOp": { + "CommandMan": { "namespace_index": "CODESYSSPV3/3S/IecVarAccess", "node_id": "|var|WAGO 750-8202 PFC200 CS 2ETH RS.App_Dosing.Services.DoseAmount.ControlOp", "data_type": "UInt32" }, - "StrategyOp": { + "StrategyMan": { "namespace_index": "CODESYSSPV3/3S/IecVarAccess", "node_id": "|var|WAGO 750-8202 PFC200 CS 2ETH RS.App_Dosing.Services.DoseAmount.StrategyOp", "data_type": "UInt32" @@ -208,7 +208,7 @@ "node_id": "|var|WAGO 750-8202 PFC200 CS 2ETH RS.App_Dosing.Services.DoseAmount.StrategyInt", "data_type": "UInt32" }, - "ControlExt": { + "CommandExt": { "namespace_index": "CODESYSSPV3/3S/IecVarAccess", "node_id": "|var|WAGO 750-8202 PFC200 CS 2ETH RS.App_Dosing.Services.DoseAmount.ControlExt", "data_type": "UInt32" @@ -223,7 +223,7 @@ "node_id": "|var|WAGO 750-8202 PFC200 CS 2ETH RS.App_Dosing.Services.DoseAmount.State.binary", "data_type": "UInt32" }, - "ControlEnable": { + "CommandEnable": { "namespace_index": "CODESYSSPV3/3S/IecVarAccess", "node_id": "|var|WAGO 750-8202 PFC200 CS 2ETH RS.App_Dosing.Services.DoseAmount.ControlEnable.binary", "data_type": "UInt32" @@ -376,12 +376,12 @@ "node_id": "|var|WAGO 750-8202 PFC200 CS 2ETH RS.App_Dosing.Services.DoseConti.OpMode.binary", "data_type": "UInt32" }, - "ControlOp": { + "CommandMan": { "namespace_index": "CODESYSSPV3/3S/IecVarAccess", "node_id": "|var|WAGO 750-8202 PFC200 CS 2ETH RS.App_Dosing.Services.DoseConti.ControlOp", "data_type": "UInt32" }, - "StrategyOp": { + "StrategyMan": { "namespace_index": "CODESYSSPV3/3S/IecVarAccess", "node_id": "|var|WAGO 750-8202 PFC200 CS 2ETH RS.App_Dosing.Services.DoseConti.StrategyOp", "data_type": "UInt32" @@ -396,7 +396,7 @@ "node_id": "|var|WAGO 750-8202 PFC200 CS 2ETH RS.App_Dosing.Services.DoseConti.StrategyInt", "data_type": "UInt32" }, - "ControlExt": { + "CommandExt": { "namespace_index": "CODESYSSPV3/3S/IecVarAccess", "node_id": "|var|WAGO 750-8202 PFC200 CS 2ETH RS.App_Dosing.Services.DoseConti.ControlExt", "data_type": "UInt32" @@ -411,7 +411,7 @@ "node_id": "|var|WAGO 750-8202 PFC200 CS 2ETH RS.App_Dosing.Services.DoseConti.State.binary", "data_type": "UInt32" }, - "ControlEnable": { + "CommandEnable": { "namespace_index": "CODESYSSPV3/3S/IecVarAccess", "node_id": "|var|WAGO 750-8202 PFC200 CS 2ETH RS.App_Dosing.Services.DoseConti.ControlEnable.binary", "data_type": "UInt32" @@ -508,12 +508,12 @@ "node_id": "|var|WAGO 750-8202 PFC200 CS 2ETH RS.App_Dosing.Services.Empty.OpMode.binary", "data_type": "UInt32" }, - "ControlOp": { + "CommandMan": { "namespace_index": "CODESYSSPV3/3S/IecVarAccess", "node_id": "|var|WAGO 750-8202 PFC200 CS 2ETH RS.App_Dosing.Services.Empty.ControlOp", "data_type": "UInt32" }, - "StrategyOp": { + "StrategyMan": { "namespace_index": "CODESYSSPV3/3S/IecVarAccess", "node_id": "|var|WAGO 750-8202 PFC200 CS 2ETH RS.App_Dosing.Services.Empty.StrategyOp", "data_type": "UInt32" @@ -528,7 +528,7 @@ "node_id": "|var|WAGO 750-8202 PFC200 CS 2ETH RS.App_Dosing.Services.Empty.StrategyInt", "data_type": "UInt32" }, - "ControlExt": { + "CommandExt": { "namespace_index": "CODESYSSPV3/3S/IecVarAccess", "node_id": "|var|WAGO 750-8202 PFC200 CS 2ETH RS.App_Dosing.Services.Empty.ControlExt", "data_type": "UInt32" @@ -543,7 +543,7 @@ "node_id": "|var|WAGO 750-8202 PFC200 CS 2ETH RS.App_Dosing.Services.Empty.State.binary", "data_type": "UInt32" }, - "ControlEnable": { + "CommandEnable": { "namespace_index": "CODESYSSPV3/3S/IecVarAccess", "node_id": "|var|WAGO 750-8202 PFC200 CS 2ETH RS.App_Dosing.Services.Empty.ControlEnable.binary", "data_type": "UInt32" @@ -1755,14 +1755,14 @@ "node_id": "\"Fill_Service\".\"MTP\".\"OpMode\"", "data_type": "UInt32" }, - "ControlOp": { + "CommandMan": { "namespace_index": "http://www.siemens.com/simatic-s7-opcua", - "node_id": "\"Fill_Service\".\"MTP\".\"ControlOp\"", + "node_id": "\"Fill_Service\".\"MTP\".\"CommandMan\"", "data_type": "UInt32" }, - "StrategyOp": { + "StrategyMan": { "namespace_index": "http://www.siemens.com/simatic-s7-opcua", - "node_id": "\"Fill_Service\".\"MTP\".\"StrategyOp\"", + "node_id": "\"Fill_Service\".\"MTP\".\"StrategyMan\"", "data_type": "UInt32" }, "ControlInt": { @@ -1775,9 +1775,9 @@ "node_id": "\"Fill_Service\".\"MTP\".\"StrategyInt\"", "data_type": "UInt32" }, - "ControlExt": { + "CommandExt": { "namespace_index": "http://www.siemens.com/simatic-s7-opcua", - "node_id": "\"Fill_Service\".\"MTP\".\"ControlExt\"", + "node_id": "\"Fill_Service\".\"MTP\".\"CommandExt\"", "data_type": "UInt32" }, "StrategyExt": { @@ -1790,9 +1790,9 @@ "node_id": "\"Fill_Service\".\"MTP\".\"State\"", "data_type": "UInt32" }, - "ControlEnable": { + "CommandEnable": { "namespace_index": "http://www.siemens.com/simatic-s7-opcua", - "node_id": "\"Fill_Service\".\"MTP\".\"ControlEnable\"", + "node_id": "\"Fill_Service\".\"MTP\".\"CommandEnable\"", "data_type": "UInt32" }, "CurrentStrategy": { @@ -1894,14 +1894,14 @@ "node_id": "\"Aerate_Service\".\"MTP\".\"OpMode\"", "data_type": "UInt32" }, - "ControlOp": { + "CommandMan": { "namespace_index": "http://www.siemens.com/simatic-s7-opcua", - "node_id": "\"Aerate_Service\".\"MTP\".\"ControlOp\"", + "node_id": "\"Aerate_Service\".\"MTP\".\"CommandMan\"", "data_type": "UInt32" }, - "StrategyOp": { + "StrategyMan": { "namespace_index": "http://www.siemens.com/simatic-s7-opcua", - "node_id": "\"Aerate_Service\".\"MTP\".\"StrategyOp\"", + "node_id": "\"Aerate_Service\".\"MTP\".\"StrategyMan\"", "data_type": "UInt32" }, "ControlInt": { @@ -1914,9 +1914,9 @@ "node_id": "\"Aerate_Service\".\"MTP\".\"StrategyInt\"", "data_type": "UInt32" }, - "ControlExt": { + "CommandExt": { "namespace_index": "http://www.siemens.com/simatic-s7-opcua", - "node_id": "\"Aerate_Service\".\"MTP\".\"ControlExt\"", + "node_id": "\"Aerate_Service\".\"MTP\".\"CommandExt\"", "data_type": "UInt32" }, "StrategyExt": { @@ -1929,9 +1929,9 @@ "node_id": "\"Aerate_Service\".\"MTP\".\"State\"", "data_type": "UInt32" }, - "ControlEnable": { + "CommandEnable": { "namespace_index": "http://www.siemens.com/simatic-s7-opcua", - "node_id": "\"Aerate_Service\".\"MTP\".\"ControlEnable\"", + "node_id": "\"Aerate_Service\".\"MTP\".\"CommandEnable\"", "data_type": "UInt32" }, "CurrentStrategy": { @@ -2026,14 +2026,14 @@ "node_id": "\"Stir_Service\".\"MTP\".\"OpMode\"", "data_type": "UInt32" }, - "ControlOp": { + "CommandMan": { "namespace_index": "http://www.siemens.com/simatic-s7-opcua", - "node_id": "\"Stir_Service\".\"MTP\".\"ControlOp\"", + "node_id": "\"Stir_Service\".\"MTP\".\"CommandMan\"", "data_type": "UInt32" }, - "StrategyOp": { + "StrategyMan": { "namespace_index": "http://www.siemens.com/simatic-s7-opcua", - "node_id": "\"Stir_Service\".\"MTP\".\"StrategyOp\"", + "node_id": "\"Stir_Service\".\"MTP\".\"StrategyMan\"", "data_type": "UInt32" }, "ControlInt": { @@ -2046,9 +2046,9 @@ "node_id": "\"Stir_Service\".\"MTP\".\"StrategyInt\"", "data_type": "UInt32" }, - "ControlExt": { + "CommandExt": { "namespace_index": "http://www.siemens.com/simatic-s7-opcua", - "node_id": "\"Stir_Service\".\"MTP\".\"ControlExt\"", + "node_id": "\"Stir_Service\".\"MTP\".\"CommandExt\"", "data_type": "UInt32" }, "StrategyExt": { @@ -2061,9 +2061,9 @@ "node_id": "\"Stir_Service\".\"MTP\".\"State\"", "data_type": "UInt32" }, - "ControlEnable": { + "CommandEnable": { "namespace_index": "http://www.siemens.com/simatic-s7-opcua", - "node_id": "\"Stir_Service\".\"MTP\".\"ControlEnable\"", + "node_id": "\"Stir_Service\".\"MTP\".\"CommandEnable\"", "data_type": "UInt32" }, "CurrentStrategy": { @@ -2215,14 +2215,14 @@ "node_id": "\"Empty_Service\".\"MTP\".\"OpMode\"", "data_type": "UInt32" }, - "ControlOp": { + "CommandMan": { "namespace_index": "http://www.siemens.com/simatic-s7-opcua", - "node_id": "\"Empty_Service\".\"MTP\".\"ControlOp\"", + "node_id": "\"Empty_Service\".\"MTP\".\"CommandMan\"", "data_type": "UInt32" }, - "StrategyOp": { + "StrategyMan": { "namespace_index": "http://www.siemens.com/simatic-s7-opcua", - "node_id": "\"Empty_Service\".\"MTP\".\"StrategyOp\"", + "node_id": "\"Empty_Service\".\"MTP\".\"StrategyMan\"", "data_type": "UInt32" }, "ControlInt": { @@ -2235,9 +2235,9 @@ "node_id": "\"Empty_Service\".\"MTP\".\"StrategyInt\"", "data_type": "UInt32" }, - "ControlExt": { + "CommandExt": { "namespace_index": "http://www.siemens.com/simatic-s7-opcua", - "node_id": "\"Empty_Service\".\"MTP\".\"ControlExt\"", + "node_id": "\"Empty_Service\".\"MTP\".\"CommandExt\"", "data_type": "UInt32" }, "StrategyExt": { @@ -2250,9 +2250,9 @@ "node_id": "\"Empty_Service\".\"MTP\".\"State\"", "data_type": "UInt32" }, - "ControlEnable": { + "CommandEnable": { "namespace_index": "http://www.siemens.com/simatic-s7-opcua", - "node_id": "\"Empty_Service\".\"MTP\".\"ControlEnable\"", + "node_id": "\"Empty_Service\".\"MTP\".\"CommandEnable\"", "data_type": "UInt32" }, "CurrentStrategy": { @@ -3635,12 +3635,12 @@ "node_id": "mtp:services:circulation:control:OpMode", "data_type": "UInt32" }, - "ControlOp": { + "CommandMan": { "namespace_index": "urn:ModuleTypePackage:DeviceId=1", "node_id": "mtp:services:circulation:control:ControlOp", "data_type": "UInt32" }, - "StrategyOp": { + "StrategyMan": { "namespace_index": "urn:ModuleTypePackage:DeviceId=1", "node_id": "mtp:services:circulation:control:StrategyOp", "data_type": "UInt32" @@ -3655,7 +3655,7 @@ "node_id": "mtp:services:circulation:control:StrategyInt", "data_type": "UInt32" }, - "ControlExt": { + "CommandExt": { "namespace_index": "urn:ModuleTypePackage:DeviceId=1", "node_id": "mtp:services:circulation:control:ControlExt", "data_type": "UInt32" @@ -3670,7 +3670,7 @@ "node_id": "mtp:services:circulation:control:State", "data_type": "UInt32" }, - "ControlEnable": { + "CommandEnable": { "namespace_index": "urn:ModuleTypePackage:DeviceId=1", "node_id": "mtp:services:circulation:control:ControlEnable", "data_type": "UInt32" @@ -3710,12 +3710,12 @@ "node_id": "mtp:services:tempering:control:OpMode", "data_type": "UInt32" }, - "ControlOp": { + "CommandMan": { "namespace_index": "urn:ModuleTypePackage:DeviceId=1", "node_id": "mtp:services:tempering:control:ControlOp", "data_type": "UInt32" }, - "StrategyOp": { + "StrategyMan": { "namespace_index": "urn:ModuleTypePackage:DeviceId=1", "node_id": "mtp:services:tempering:control:StrategyOp", "data_type": "UInt32" @@ -3730,7 +3730,7 @@ "node_id": "mtp:services:tempering:control:StrategyInt", "data_type": "UInt32" }, - "ControlExt": { + "CommandExt": { "namespace_index": "urn:ModuleTypePackage:DeviceId=1", "node_id": "mtp:services:tempering:control:ControlExt", "data_type": "UInt32" @@ -3745,7 +3745,7 @@ "node_id": "mtp:services:tempering:control:State", "data_type": "UInt32" }, - "ControlEnable": { + "CommandEnable": { "namespace_index": "urn:ModuleTypePackage:DeviceId=1", "node_id": "mtp:services:tempering:control:ControlEnable", "data_type": "UInt32" diff --git a/assets/modules/module_cps_edp_mixing.1.1.2.json b/assets/modules/cps_edp/module_cps_edp_mixing.1.1.2.json similarity index 100% rename from assets/modules/module_cps_edp_mixing.1.1.2.json rename to assets/modules/cps_edp/module_cps_edp_mixing.1.1.2.json diff --git a/assets/modules/module_cps_edp_reactor.1.1.2.json b/assets/modules/cps_edp/module_cps_edp_reactor.1.1.2.json similarity index 100% rename from assets/modules/module_cps_edp_reactor.1.1.2.json rename to assets/modules/cps_edp/module_cps_edp_reactor.1.1.2.json diff --git a/assets/modules/module_cif.json b/assets/modules/module_cif.json index 87b71163..0f6edd07 100644 --- a/assets/modules/module_cif.json +++ b/assets/modules/module_cif.json @@ -22,12 +22,12 @@ "node_id": "|var|WAGO 750-8202 PFC200 2ETH RS.Application.Test_Service.Service1.OpMode.binary", "data_type": "UInt64" }, - "ControlOp": { + "CommandMan": { "namespace_index": "CODESYSSPV3/3S/IecVarAccess", "node_id": "|var|WAGO 750-8202 PFC200 2ETH RS.Application.Test_Service.Service1.ControlOp", "data_type": "UInt64" }, - "StrategyOp": { + "StrategyMan": { "namespace_index": "CODESYSSPV3/3S/IecVarAccess", "node_id": "|var|WAGO 750-8202 PFC200 2ETH RS.Application.Test_Service.Service1.StrategyOp", "data_type": "UInt64" @@ -42,7 +42,7 @@ "node_id": "|var|WAGO 750-8202 PFC200 2ETH RS.Application.Test_Service.Service1.StrategyInt", "data_type": "UInt64" }, - "ControlExt": { + "CommandExt": { "namespace_index": "CODESYSSPV3/3S/IecVarAccess", "node_id": "|var|WAGO 750-8202 PFC200 2ETH RS.Application.Test_Service.Service1.ControlExt", "data_type": "UInt64" @@ -57,7 +57,7 @@ "node_id": "|var|WAGO 750-8202 PFC200 2ETH RS.Application.Test_Service.Service1.State.binary", "data_type": "UInt64" }, - "ControlEnable": { + "CommandEnable": { "namespace_index": "CODESYSSPV3/3S/IecVarAccess", "node_id": "|var|WAGO 750-8202 PFC200 2ETH RS.Application.Test_Service.Service1.ControlEnable.binary", "data_type": "UInt64" @@ -226,6 +226,9 @@ { "name": "Test_Service.Service2", "communication": { + "TagName": { + "value": "service2" + }, "OSLevel": { "namespace_index": "CODESYSSPV3/3S/IecVarAccess", "node_id": "|var|WAGO 750-8202 PFC200 2ETH RS.Application.Test_Service.Service2.OSLevel", @@ -241,12 +244,12 @@ "node_id": "|var|WAGO 750-8202 PFC200 2ETH RS.Application.Test_Service.Service2.OpMode.binary", "data_type": "UInt64" }, - "ControlOp": { + "CommandMan": { "namespace_index": "CODESYSSPV3/3S/IecVarAccess", "node_id": "|var|WAGO 750-8202 PFC200 2ETH RS.Application.Test_Service.Service2.ControlOp", "data_type": "UInt64" }, - "StrategyOp": { + "StrategyMan": { "namespace_index": "CODESYSSPV3/3S/IecVarAccess", "node_id": "|var|WAGO 750-8202 PFC200 2ETH RS.Application.Test_Service.Service2.StrategyOp", "data_type": "UInt64" @@ -261,7 +264,7 @@ "node_id": "|var|WAGO 750-8202 PFC200 2ETH RS.Application.Test_Service.Service2.StrategyInt", "data_type": "UInt64" }, - "ControlExt": { + "CommandExt": { "namespace_index": "CODESYSSPV3/3S/IecVarAccess", "node_id": "|var|WAGO 750-8202 PFC200 2ETH RS.Application.Test_Service.Service2.ControlExt", "data_type": "UInt64" @@ -276,7 +279,7 @@ "node_id": "|var|WAGO 750-8202 PFC200 2ETH RS.Application.Test_Service.Service2.State.binary", "data_type": "UInt64" }, - "ControlEnable": { + "CommandEnable": { "namespace_index": "CODESYSSPV3/3S/IecVarAccess", "node_id": "|var|WAGO 750-8202 PFC200 2ETH RS.Application.Test_Service.Service2.ControlEnable.binary", "data_type": "UInt64" @@ -460,12 +463,12 @@ "node_id": "|var|WAGO 750-8202 PFC200 2ETH RS.Application.Test_Service.Nachfuellen.OpMode.binary", "data_type": "UInt64" }, - "ControlOp": { + "CommandMan": { "namespace_index": "CODESYSSPV3/3S/IecVarAccess", "node_id": "|var|WAGO 750-8202 PFC200 2ETH RS.Application.Test_Service.Nachfuellen.ControlOp", "data_type": "UInt64" }, - "StrategyOp": { + "StrategyMan": { "namespace_index": "CODESYSSPV3/3S/IecVarAccess", "node_id": "|var|WAGO 750-8202 PFC200 2ETH RS.Application.Test_Service.Nachfuellen.StrategyOp", "data_type": "UInt64" @@ -480,7 +483,7 @@ "node_id": "|var|WAGO 750-8202 PFC200 2ETH RS.Application.Test_Service.Nachfuellen.StrategyInt", "data_type": "UInt64" }, - "ControlExt": { + "CommandExt": { "namespace_index": "CODESYSSPV3/3S/IecVarAccess", "node_id": "|var|WAGO 750-8202 PFC200 2ETH RS.Application.Test_Service.Nachfuellen.ControlExt", "data_type": "UInt64" @@ -495,7 +498,7 @@ "node_id": "|var|WAGO 750-8202 PFC200 2ETH RS.Application.Test_Service.Nachfuellen.State.binary", "data_type": "UInt64" }, - "ControlEnable": { + "CommandEnable": { "namespace_index": "CODESYSSPV3/3S/IecVarAccess", "node_id": "|var|WAGO 750-8202 PFC200 2ETH RS.Application.Test_Service.Nachfuellen.ControlEnable.binary", "data_type": "UInt64" @@ -591,12 +594,12 @@ "node_id": "|var|WAGO 750-8202 PFC200 2ETH RS.Application.Test_Service.Entleeren.OpMode.binary", "data_type": "UInt64" }, - "ControlOp": { + "CommandMan": { "namespace_index": "CODESYSSPV3/3S/IecVarAccess", "node_id": "|var|WAGO 750-8202 PFC200 2ETH RS.Application.Test_Service.Entleeren.ControlOp", "data_type": "UInt64" }, - "StrategyOp": { + "StrategyMan": { "namespace_index": "CODESYSSPV3/3S/IecVarAccess", "node_id": "|var|WAGO 750-8202 PFC200 2ETH RS.Application.Test_Service.Entleeren.StrategyOp", "data_type": "UInt64" @@ -611,7 +614,7 @@ "node_id": "|var|WAGO 750-8202 PFC200 2ETH RS.Application.Test_Service.Entleeren.StrategyInt", "data_type": "UInt64" }, - "ControlExt": { + "CommandExt": { "namespace_index": "CODESYSSPV3/3S/IecVarAccess", "node_id": "|var|WAGO 750-8202 PFC200 2ETH RS.Application.Test_Service.Entleeren.ControlExt", "data_type": "UInt64" @@ -626,7 +629,7 @@ "node_id": "|var|WAGO 750-8202 PFC200 2ETH RS.Application.Test_Service.Entleeren.State.binary", "data_type": "UInt64" }, - "ControlEnable": { + "CommandEnable": { "namespace_index": "CODESYSSPV3/3S/IecVarAccess", "node_id": "|var|WAGO 750-8202 PFC200 2ETH RS.Application.Test_Service.Entleeren.ControlEnable.binary", "data_type": "UInt64" @@ -698,12 +701,12 @@ "node_id": "|var|WAGO 750-8202 PFC200 2ETH RS.Application.Test_Service.Dosieren.OpMode.binary", "data_type": "UInt64" }, - "ControlOp": { + "CommandMan": { "namespace_index": "CODESYSSPV3/3S/IecVarAccess", "node_id": "|var|WAGO 750-8202 PFC200 2ETH RS.Application.Test_Service.Dosieren.ControlOp", "data_type": "UInt64" }, - "StrategyOp": { + "StrategyMan": { "namespace_index": "CODESYSSPV3/3S/IecVarAccess", "node_id": "|var|WAGO 750-8202 PFC200 2ETH RS.Application.Test_Service.Dosieren.StrategyOp", "data_type": "UInt64" @@ -718,7 +721,7 @@ "node_id": "|var|WAGO 750-8202 PFC200 2ETH RS.Application.Test_Service.Dosieren.StrategyInt", "data_type": "UInt64" }, - "ControlExt": { + "CommandExt": { "namespace_index": "CODESYSSPV3/3S/IecVarAccess", "node_id": "|var|WAGO 750-8202 PFC200 2ETH RS.Application.Test_Service.Dosieren.ControlExt", "data_type": "UInt64" @@ -733,7 +736,7 @@ "node_id": "|var|WAGO 750-8202 PFC200 2ETH RS.Application.Test_Service.Dosieren.State.binary", "data_type": "UInt64" }, - "ControlEnable": { + "CommandEnable": { "namespace_index": "CODESYSSPV3/3S/IecVarAccess", "node_id": "|var|WAGO 750-8202 PFC200 2ETH RS.Application.Test_Service.Dosieren.ControlEnable.binary", "data_type": "UInt64" @@ -829,12 +832,12 @@ "node_id": "|var|WAGO 750-8202 PFC200 2ETH RS.Application.Test_Service.Vorlegen.OpMode.binary", "data_type": "UInt64" }, - "ControlOp": { + "CommandMan": { "namespace_index": "CODESYSSPV3/3S/IecVarAccess", "node_id": "|var|WAGO 750-8202 PFC200 2ETH RS.Application.Test_Service.Vorlegen.ControlOp", "data_type": "UInt64" }, - "StrategyOp": { + "StrategyMan": { "namespace_index": "CODESYSSPV3/3S/IecVarAccess", "node_id": "|var|WAGO 750-8202 PFC200 2ETH RS.Application.Test_Service.Vorlegen.StrategyOp", "data_type": "UInt64" @@ -849,7 +852,7 @@ "node_id": "|var|WAGO 750-8202 PFC200 2ETH RS.Application.Test_Service.Vorlegen.StrategyInt", "data_type": "UInt64" }, - "ControlExt": { + "CommandExt": { "namespace_index": "CODESYSSPV3/3S/IecVarAccess", "node_id": "|var|WAGO 750-8202 PFC200 2ETH RS.Application.Test_Service.Vorlegen.ControlExt", "data_type": "UInt64" @@ -864,7 +867,7 @@ "node_id": "|var|WAGO 750-8202 PFC200 2ETH RS.Application.Test_Service.Vorlegen.State.binary", "data_type": "UInt64" }, - "ControlEnable": { + "CommandEnable": { "namespace_index": "CODESYSSPV3/3S/IecVarAccess", "node_id": "|var|WAGO 750-8202 PFC200 2ETH RS.Application.Test_Service.Vorlegen.ControlEnable.binary", "data_type": "UInt64" @@ -1168,6 +1171,7 @@ }, { "name": "Test_AnaView.L004", + "interface_class": "AnaView", "communication": { "WQC": { "namespace_index": "CODESYSSPV3/3S/IecVarAccess", @@ -1185,13 +1189,13 @@ "data_type": "Double" }, "VSclMin": { - "value": "0" + "value": 0 }, "VSclMax": { - "value": "35" + "value": "35.5" }, "VUnit": { - "value": "1038" + "value": 1038 } } }, @@ -2292,6 +2296,7 @@ }, { "name": "Sensoren.L001", + "interface_class": "AnaView", "communication": { "WQC": { "namespace_index": "CODESYSSPV3/3S/IecVarAccess", @@ -2341,7 +2346,7 @@ "value": "0" }, "VSclMax": { - "value": "2" + "value": 2.0 }, "VUnit": { "value": "1352" diff --git a/assets/modules/module_dosierer_1.1.0.json b/assets/modules/module_dosierer_1.1.0.json index 4e70f1c5..8685463b 100644 --- a/assets/modules/module_dosierer_1.1.0.json +++ b/assets/modules/module_dosierer_1.1.0.json @@ -2,7 +2,7 @@ "GenerationDate": "20190423T204609", "modules": [ { - "id": "CIF", + "id": "Dosierer", "opcua_server_url": "opc.tcp://10.6.51.200:4840", "rest_api_port": "1200", "services": [ @@ -54,7 +54,7 @@ "node_id": "|var|WAGO 750-8202 PFC200 2ETH RS.App_Dosing.Services.Fill.StrategyExt", "data_type": "UInt32" }, - "CurrentState": { + "State": { "namespace_index": "CODESYSSPV3/3S/IecVarAccess", "node_id": "|var|WAGO 750-8202 PFC200 2ETH RS.App_Dosing.Services.Fill.CurrentState.binary", "data_type": "UInt32" @@ -213,7 +213,7 @@ "node_id": "|var|WAGO 750-8202 PFC200 2ETH RS.App_Dosing.Services.DoseAmount.StrategyExt", "data_type": "UInt32" }, - "CurrentState": { + "State": { "namespace_index": "CODESYSSPV3/3S/IecVarAccess", "node_id": "|var|WAGO 750-8202 PFC200 2ETH RS.App_Dosing.Services.DoseAmount.CurrentState.binary", "data_type": "UInt32" @@ -441,7 +441,7 @@ "node_id": "|var|WAGO 750-8202 PFC200 2ETH RS.App_Dosing.Services.DoseConti.StrategyExt", "data_type": "UInt32" }, - "CurrentState": { + "State": { "namespace_index": "CODESYSSPV3/3S/IecVarAccess", "node_id": "|var|WAGO 750-8202 PFC200 2ETH RS.App_Dosing.Services.DoseConti.CurrentState.binary", "data_type": "UInt32" @@ -593,7 +593,7 @@ "node_id": "|var|WAGO 750-8202 PFC200 2ETH RS.App_Dosing.Services.Empty.StrategyExt", "data_type": "UInt32" }, - "CurrentState": { + "State": { "namespace_index": "CODESYSSPV3/3S/IecVarAccess", "node_id": "|var|WAGO 750-8202 PFC200 2ETH RS.App_Dosing.Services.Empty.CurrentState.binary", "data_type": "UInt32" diff --git a/assets/modules/module_testserver_1.0.0.json b/assets/modules/module_testserver_1.0.0.json index e5a58120..49d3284a 100644 --- a/assets/modules/module_testserver_1.0.0.json +++ b/assets/modules/module_testserver_1.0.0.json @@ -7,11 +7,11 @@ { "name": "Service1", "communication": { - "ControlOp": { + "CommandMan": { "namespace_index": "urn:NodeOPCUA-Server-default", "node_id": "Service1.Command" }, - "ControlExt": { + "CommandExt": { "namespace_index": "urn:NodeOPCUA-Server-default", "node_id": "Service1.Command" }, @@ -19,7 +19,7 @@ "namespace_index": "urn:NodeOPCUA-Server-default", "node_id": "Service1.State" }, - "ControlEnable": { + "CommandEnable": { "namespace_index": "urn:NodeOPCUA-Server-default", "node_id": "Service1.CommandEnable" }, @@ -27,7 +27,7 @@ "namespace_index": "urn:NodeOPCUA-Server-default", "node_id": "Service1.OpMode" }, - "StrategyOp": { + "StrategyMan": { "namespace_index": "urn:NodeOPCUA-Server-default", "node_id": "Service1.Strategy" }, @@ -67,8 +67,16 @@ "namespace_index": "urn:NodeOPCUA-Server-default", "node_id": "Service1.Parameter1.VExt" }, + "VSclMax": { + "namespace_index": "urn:NodeOPCUA-Server-default", + "node_id": "Service1.Parameter1.VSclMax" + }, + "VSclMin": { + "namespace_index": "urn:NodeOPCUA-Server-default", + "node_id": "Service1.Parameter1.VSclMin" + }, "VMax": { - "value": "urn:NodeOPCUA-Server-default", + "namespace_index": "urn:NodeOPCUA-Server-default", "node_id": "Service1.Parameter1.VSclMax" }, "VMin": { @@ -113,8 +121,16 @@ "namespace_index": "urn:NodeOPCUA-Server-default", "node_id": "Service1.Parameter2.VExt" }, + "VSclMax": { + "namespace_index": "urn:NodeOPCUA-Server-default", + "node_id": "Service1.Parameter2.VSclMax" + }, + "VSclMin": { + "namespace_index": "urn:NodeOPCUA-Server-default", + "node_id": "Service1.Parameter2.VSclMin" + }, "VMax": { - "value": "urn:NodeOPCUA-Server-default", + "namespace_index": "urn:NodeOPCUA-Server-default", "node_id": "Service1.Parameter2.VSclMax" }, "VMin": { @@ -156,6 +172,134 @@ } } } + ], + "process_values": [ + { + "name": "PvIn", + "interface_class": "ExtAnaOp", + "communication": { + "V": { + "namespace_index": "urn:NodeOPCUA-Server-default", + "node_id": "Service1.ProcessValueIn.V" + }, + "VRbk": { + "namespace_index": "urn:NodeOPCUA-Server-default", + "node_id": "Service1.ProcessValueIn.V" + }, + "VOut": { + "namespace_index": "urn:NodeOPCUA-Server-default", + "node_id": "Service1.ProcessValueIn.V" + }, + "VExt": { + "namespace_index": "urn:NodeOPCUA-Server-default", + "node_id": "Service1.ProcessValueIn.VExt" + }, + "VMax": { + "namespace_index": "urn:NodeOPCUA-Server-default", + "node_id": "Service1.ProcessValueIn.VSclMax" + }, + "VMin": { + "namespace_index": "urn:NodeOPCUA-Server-default", + "node_id": "Service1.ProcessValueIn.VSclMin" + }, + "VUnit": { + "namespace_index": "urn:NodeOPCUA-Server-default", + "node_id": "Service1.ProcessValueIn.VUnit" + }, + "WQC": { + "namespace_index": "urn:NodeOPCUA-Server-default", + "node_id": "Service1.ProcessValueIn.WQC" + }, + "OSLevel": { + "namespace_index": "urn:NodeOPCUA-Server-default", + "node_id": "Service1.ProcessValueIn.OSLevel" + } + } + }, + { + "name": "PvOut", + "interface_class": "ExtAnaOp", + "communication": { + "V": { + "namespace_index": "urn:NodeOPCUA-Server-default", + "node_id": "Service1.ProcessValueOut.V" + }, + "VRbk": { + "namespace_index": "urn:NodeOPCUA-Server-default", + "node_id": "Service1.ProcessValueOut.V" + }, + "VOut": { + "namespace_index": "urn:NodeOPCUA-Server-default", + "node_id": "Service1.ProcessValueOut.V" + }, + "VExt": { + "namespace_index": "urn:NodeOPCUA-Server-default", + "node_id": "Service1.ProcessValueOut.VExt" + }, + "VMax": { + "namespace_index": "urn:NodeOPCUA-Server-default", + "node_id": "Service1.ProcessValueOut.VSclMax" + }, + "VMin": { + "namespace_index": "urn:NodeOPCUA-Server-default", + "node_id": "Service1.ProcessValueOut.VSclMin" + }, + "VUnit": { + "namespace_index": "urn:NodeOPCUA-Server-default", + "node_id": "Service1.ProcessValueOut.VUnit" + }, + "WQC": { + "namespace_index": "urn:NodeOPCUA-Server-default", + "node_id": "Service1.ProcessValueOut.WQC" + }, + "OSLevel": { + "namespace_index": "urn:NodeOPCUA-Server-default", + "node_id": "Service1.ProcessValueOut.OSLevel" + } + } + }, + { + "name": "PvIntegral", + "interface_class": "ExtAnaOp", + "communication": { + "V": { + "namespace_index": "urn:NodeOPCUA-Server-default", + "node_id": "Service1.ProcessValueIntegral.V" + }, + "VRbk": { + "namespace_index": "urn:NodeOPCUA-Server-default", + "node_id": "Service1.ProcessValueIntegral.V" + }, + "VOut": { + "namespace_index": "urn:NodeOPCUA-Server-default", + "node_id": "Service1.ProcessValueIntegral.V" + }, + "VExt": { + "namespace_index": "urn:NodeOPCUA-Server-default", + "node_id": "Service1.ProcessValueIntegral.VExt" + }, + "VMax": { + "namespace_index": "urn:NodeOPCUA-Server-default", + "node_id": "Service1.ProcessValueIntegral.VSclMax" + }, + "VMin": { + "namespace_index": "urn:NodeOPCUA-Server-default", + "node_id": "Service1.ProcessValueIntegral.VSclMin" + }, + "VUnit": { + "namespace_index": "urn:NodeOPCUA-Server-default", + "node_id": "Service1.ProcessValueIntegral.VUnit" + }, + "WQC": { + "namespace_index": "urn:NodeOPCUA-Server-default", + "node_id": "Service1.ProcessValueIntegral.WQC" + }, + "OSLevel": { + "namespace_index": "urn:NodeOPCUA-Server-default", + "node_id": "Service1.ProcessValueIntegral.OSLevel" + } + } + } ] }, { @@ -184,8 +328,16 @@ "namespace_index": "urn:NodeOPCUA-Server-default", "node_id": "Service1.Parameter1.VExt" }, + "VSclMax": { + "namespace_index": "urn:NodeOPCUA-Server-default", + "node_id": "Service1.Parameter1.VSclMax" + }, + "VSclMin": { + "namespace_index": "urn:NodeOPCUA-Server-default", + "node_id": "Service1.Parameter1.VSclMin" + }, "VMax": { - "value": "urn:NodeOPCUA-Server-default", + "namespace_index": "urn:NodeOPCUA-Server-default", "node_id": "Service1.Parameter1.VSclMax" }, "VMin": { @@ -235,11 +387,11 @@ { "name": "Service2", "communication": { - "ControlOp": { + "CommandMan": { "namespace_index": "urn:NodeOPCUA-Server-default", "node_id": "Service2.Command" }, - "ControlExt": { + "CommandExt": { "namespace_index": "urn:NodeOPCUA-Server-default", "node_id": "Service2.Command" }, @@ -247,7 +399,7 @@ "namespace_index": "urn:NodeOPCUA-Server-default", "node_id": "Service2.State" }, - "ControlEnable": { + "CommandEnable": { "namespace_index": "urn:NodeOPCUA-Server-default", "node_id": "Service2.CommandEnable" }, @@ -255,7 +407,7 @@ "namespace_index": "urn:NodeOPCUA-Server-default", "node_id": "Service2.OpMode" }, - "StrategyOp": { + "StrategyMan": { "namespace_index": "urn:NodeOPCUA-Server-default", "node_id": "Service2.Strategy" }, diff --git a/assets/mtp/mtp_testlog.txt b/assets/mtp/mtp_testlog.txt deleted file mode 100644 index e2c2cd73..00000000 --- a/assets/mtp/mtp_testlog.txt +++ /dev/null @@ -1,59 +0,0 @@ - ✓ mtp manifest is well formed xml - ✓ mtp manifest is valid CAEX-file - ✓ [VDI 2658-1 (1)] mtp manifest contains exactly one InstanceHierarchy called 'ModuleTypePackage' - ✓ [VDI 2658-1 (2)] mtp manifest contains exactly one index of type 'MTPSUCLib/ModuleTypePackage' - ✗ [VDI 2658-1 (3-1)] index of mtp module contains attribute 'Version' - (in test file mtp.bats, line 35) -' failed$output == "true" ] - ✗ [VDI 2658-1 (3-2)] version of mtp module is valid - (in test file mtp.bats, line 44) -' failed$status -eq 0 ] - Version: - ✗ [VDI 2658-1 (4)] mtp manifest contains DocumentVersions - (in test file mtp.bats, line 51) -' failed$output == "true" ] - ✗ [VDI 2658-1 (4-1)] version of sheet 1 is valid - (in test file mtp.bats, line 57) -' failed$output == "true" ] - Version not found. - - [VDI 2658-1 (4-2)] version of sheet 2 is valid (skipped: not found) - - [VDI 2658-1 (4-3)] version of sheet 3 is valid (skipped: not found) - - [VDI 2658-1 (4-4)] version of sheet 4 is valid (skipped: not found) - ✓ [VDI 2658-1 (5)] mtp manifest contains exactly one element of type 'MTPSUCLib/CommunicationSet' - ✓ [VDI 2658-1 (6-1)] mtp manifest contains one or more elements of type 'MTPHMISUCLib/HMISet' - ✗ [VDI 2658-1 (6-1a)] corresponding document versions for HMISet exist - (in test file mtp.bats, line 139) -' failed-e "version.2" ] - ✓ [VDI 2658-1 (6-2)] mtp manifest contains one or more elements of type 'MTPServiceSUCLib/ServiceSet' - ✗ [VDI 2658-1 (6-2a)] corresponding document versions for ServiceSet exist - (in test file mtp.bats, line 159) -' failed-e "version.4" ] - corresponding document version(s) missing - ✓ [VDI 2658-1 (7)] each mtp manifest index contains exactly one ExternalDataCollector - ✓ [VDI 2658-1 (8)] each mtp manifest index refering to an external file is valid - ✓ [VDI 2658-1 (9)] each mtp manifest index refering to an internal InstanceHierachy is valid - ✓ [VDI 2658-1 (8+9)] each mtp manifest index is refering to either an external or internal InstanceHierachy - ✓ [VDI 2658-1 (10)] mtp manifest communication index contains exactly 2 elements of types InstanceList and SourceList - ✓ [VDI 2658-1 (11)] SourceList contains at least one element - ✓ [VDI 2658-1 (12)] SourceList does not contain elements without link - ✓ [VDI 2658-1 (13)] InstanceList contains at least one element - ✓ [VDI 2658-1 (14)] all links from InstanceList elements to SourceList elements are valid - - [VDI 2658-1 (15)] all static parameters in InstanceList are valid (skipped: not implemented yet) - ✓ mtp manifest contains opc ua endpoint info - ✓ opc ua endpoint info is valid URI - ✗ mtp manifest does not contain attributes without value - (in test file mtp.bats, line 322) -' failed$status -eq 1 ] - - ✓ Service RefIDs are valid - ✓ no invalid RefBaseSystemUnitPath - ✓ all VisualObjects contain Width, Height and Rotation - ✗ Service instances use correct variable names (the ones changed till freeze 2019-01) - (in test file mtp.bats, line 360) -' failed$status -eq 1 ] - 97e40aab-8beb-4929-bc73-7eabd75d1536 2a89decd-a5a7-49d6-97a1-78656cf42c77 ac594ae9-38a3-4c82-baaa-189e63aadf06 6c50663a-f853-4cd5-9031-34988c71db60 5605f3dc-1ea2-4fda-a553-051359663a07 5c79f7aa-e98b-4dc9-a43f-fcb7beddf892 1d9f77de-8ea5-452d-bdb2-f70afa487837 9723d4d9-e500-4b73-9e01-5a0095ccb154 82726946-f06b-4f61-857a-672d3a0c8c61 e9299dff-9994-4e8a-a802-a566989c9092 d9a9ba68-5507-42bf-bbf9-fc961f53a533 838749ca-21b3-4ae2-8566-f46009290219 dc7bf366-c7da-4f30-b67d-756db44f39fa - ✓ Services contain exactly one default strategy - ✓ ServiceParams contain VMan - ✓ cleanup - -36 tests, 8 failures, 4 skipped diff --git a/assets/recipes/test/recipe_testserver_1.0.0.json b/assets/recipes/test/recipe_testserver_1.0.0.json index 556757f0..963b1ecf 100644 --- a/assets/recipes/test/recipe_testserver_1.0.0.json +++ b/assets/recipes/test/recipe_testserver_1.0.0.json @@ -2,8 +2,23 @@ "version": "1.0.0", "name": "Test recipe for testserver module", "author": "Markus Graube", - "initial_step": "S1", + "initial_step": "S0.CheckInitialConditions", "steps": [ + { + "name": "S0.CheckInitialConditions", + "operations": [], + "transitions": [ + { + "next_step": "S1", + "condition": { + "type": "state", + "module": "CIF", + "service": "Service1", + "state": "idle" + } + } + ] + }, { "name": "S1", "operations": [ diff --git a/assets/virtualService/aggregatedService_moduletestserver.json b/assets/virtualService/aggregatedService_moduletestserver.json new file mode 100644 index 00000000..4e778ee9 --- /dev/null +++ b/assets/virtualService/aggregatedService_moduletestserver.json @@ -0,0 +1,64 @@ +{ + "version": "0.2.0", + "name": "DoseFill", + "type": "aggregatedService", + "description": "Combined Services from module test server", + "necessaryServices": [ + "PEA1.Service1", + "PEA2.Service1" + ], + "parameters": [ + { + "name": "CombinedParameter", + "type": "ExtAnaOp", + "default": "0", + "unit": "1038" + } + ], + "stateMachine": { + "starting": { + "initialTransition": "t1", + "states": [ + { + "id": "Init", + "operations": [ + { + "module": "PEA1", + "service": "Service1", + "command": "start", + "parameters": [ + { + "name": "Parameter001", + "value": "SetVolume" + } + ] + }, + { + "module": "PEA2", + "service": "Service1", + "command": "start" + } + ], + "nextTransitions": ["t2"] + } + ], + "transitions": [ + { + "id": "t1", + "nextStates": ["Init"] + }, + { + "id": "t2", + "condition": "PEA1.Service1.state==EXECUTE && PEA1.Service1.state==EXECUTE", + "nextStates": ["finished"] + } + ] + } + }, + "commandEnable": { + "start": "Dose.DoseAmount.commandEnable.start && React.Fill.commandEnable.start", + "pause": "Dose.DoseAmount.commandEnable.pause && React.Fill.commandEnable.pause", + "resume": "Dose.DoseAmount.commandEnable.resume && React.Fill.commandEnable.resume", + "complete": false + } +} diff --git a/assets/virtualService/timer.json b/assets/virtualService/timer.json new file mode 100644 index 00000000..45c0a178 --- /dev/null +++ b/assets/virtualService/timer.json @@ -0,0 +1,4 @@ +{ + "name": "timer1", + "type": "timer" +} \ No newline at end of file diff --git a/assets/virtualService/virtualService_achema_dose_fill.json b/assets/virtualService/virtualService_achema_dose_fill.json new file mode 100644 index 00000000..787a8a07 --- /dev/null +++ b/assets/virtualService/virtualService_achema_dose_fill.json @@ -0,0 +1,157 @@ +{ + "version": "0.2.0", + "name": "DoseFill", + "type": "aggregatedService", + "description": "Combined Dose from Dose PEA and fill from reactor", + "necessaryServices": [ + "Dose.DoseAmount", + "React.Fill_Service" + ], + "parameters": [ + { + "name": "SetVolume", + "type": "ExtAnaOp", + "default": "0", + "unit": "1038" + } + ], + "stateMachine": { + "starting": { + "initialTransition": "t1", + "states": [ + { + "id": "Init", + "operations": [ + { + "module": "Dose", + "service": "DoseAmount", + "command": "start", + "parameters": [ + { + "name": "SetVolume", + "value": "SetVolume" + } + ] + }, + { + "module": "React", + "service": "Fill_Service", + "command": "start" + } + ], + "nextTransitions": ["t2"] + } + ], + "transitions": [ + { + "id": "t1", + "nextStates": ["Init"] + }, + { + "id": "t2", + "condition": "Dose.DoseAmount.state==EXECUTE && React.Fill.state==EXECUTE", + "nextStates": ["finished"] + } + ] + }, + "pausing": { + "initialTransition": "t1", + "states": [ + { + "id": "Init", + "operations": [ + { + "module": "Dose", + "service": "DoseAmount", + "command": "pause" + }, + { + "module": "React", + "service": "Fill_Service", + "command": "pause" + } + ], + "nextTransitions": ["t2"] + } + ], + "transitions": [ + { + "id": "t1", + "nextStates": ["Init"] + }, + { + "id": "t2", + "nextStates": ["finished"] + } + ] + }, + "resuming": { + "initialTransition": "t1", + "states": [ + { + "id": "Init", + "operations": [ + { + "module": "Dose", + "service": "DoseAmount", + "command": "resume" + }, + { + "module": "React", + "service": "Fill_Service", + "command": "resume" + } + ], + "nextTransitions": ["t2"] + } + ], + "transitions": [ + { + "id": "t1", + "nextStates": ["Init"] + }, + { + "id": "t2", + "nextStates": ["finished"] + } + ] + }, + "completing": { + "initialTransition": "t1", + "states": [ + { + "id": "Init", + "operations": [ + { + "module": "Dose", + "service": "DoseAmount", + "command": "complete" + }, + { + "module": "React", + "service": "Fill_Service", + "command": "complete" + } + ], + "nextTransitions": ["t2"] + } + ], + "transitions": [ + { + "id": "t1", + "nextStates": ["Init"] + }, + { + "id": "t2", + "nextStates": ["finished"] + } + ] + } + }, + "commandEnable": { + "start": "Dose.DoseAmount.commandEnable.start && React.Fill.commandEnable.start", + "pause": "Dose.DoseAmount.commandEnable.pause && React.Fill.commandEnable.pause", + "resume": "Dose.DoseAmount.commandEnable.resume && React.Fill.commandEnable.resume", + "complete": false + } +} diff --git a/assets/virtualService/virtualService_achema_v0.1.0.json b/assets/virtualService/virtualService_achema_v0.1.0.json new file mode 100644 index 00000000..53c92869 --- /dev/null +++ b/assets/virtualService/virtualService_achema_v0.1.0.json @@ -0,0 +1,366 @@ +{ + "version": "0.2.0", + "name": "Recipe ACHEMA", + "description": "Advanced recipe for ACHEMA 2018 as virtual service", + "stateMachine": { + "starting": { + "initialTransition": "t1", + "states": [ + { + "id": "Init", + "operations": [ + { + "module": "Temper", + "service": "Tempering", + "strategy": "SetpointRampTempering", + "command": "start", + "parameter": [ + { + "name": "RampDuration", + "value": 60 + }, + { + "name": "RampEndtemperature", + "value": 20 + } + ] + }, + { + "module": "React", + "service": "Empty_Service", + "strategy": "Empty_Full", + "command": "start", + "parameter": [ + { + "name": "Empty_Level_Tank_DeadBand", + "value": 0.5 + }, + { + "name": "Empty_VolFlow", + "value": 3 + } + ] + } + ], + "nextTransitions": ["t2"] + } + ], + "transitions": [ + { + "id": "t1", + "condition": true, + "nextStates": ["Init"] + }, + { + "id": "t2", + "condition": { + "type": "state", + "module": "Temper", + "service": "Tempering", + "state": "running" + }, + "nextStates": ["finished"] + } + ] + }, + "execute": { + "initialTransition": "t1", + "states": [ + { + "id": "Vorlegen", + "operations": [ + { + "module": "React", + "service": "Empty_Service", + "command": "reset" + }, + { + "module": "Dose", + "service": "DoseAmount", + "command": "start", + "parameter": [ + { + "name": "SetVolume", + "value": 1 + }, + { + "name": "SetDuration", + "value": 30 + } + ] + }, + { + "module": "React", + "service": "Fill_Service", + "command": "start", + "parameters": [ + { + "name": "Fill_Level_Max", + "value": 1.5 + } + ] + }, + { + "module": "React", + "service": "Stir_Service", + "strategy": "Stirring_Continous", + "command": "start", + "parameter": [ + { + "name": "Stir_Rotation", + "value": 100 + } + ] + } + ], + "nextTransitions": ["t2"] + }, + { + "id": "Reagieren", + "operations": [ + { + "module": "Dose", + "service": "DoseAmount", + "command": "start", + "parameter": [ + { + "name": "SetVolume", + "value": "1" + }, + { + "name": "SetDuration", + "value": "60" + } + ] + }, + { + "module": "React", + "service": "Stir_Service", + "strategy": "Stirring_Continous", + "command": "restart", + "parameter": [ + { + "name": "Stir_Rotation", + "value": 200 + } + ] + }, + { + "module": "React", + "service": "Aerate_Service", + "strategy": "Aerate_Continous", + "command": "start" + }, + { + "module": "Temper", + "service": "Tempering", + "strategy": "SetpointRampTempering", + "parameter": [ + { + "name": "RampDuration", + "value": 60 + }, + { + "name": "RampEndtemperature", + "value": 21 + } + ] + } + ], + "nextTransitions": ["t3"] + }, + { + "id": "Ruhen", + "operations": [ + { + "module": "React", + "service": "Fill_Service", + "command": "complete" + } + ], + "nextTransitions": ["t4"] + }, + { + "id": "StopAerate", + "operations": [ + { + "module": "React", + "service": "Aerate_Service", + "command": "complete" + } + ], + "nextTransitions": ["t5"] + }, + { + "id": "Abkuehlen", + "operations": [ + { + "module": "Temper", + "service": "Tempering", + "strategy": "SetpointRampTempering", + "parameter": [ + { + "name": "RampDuration", + "value": 60 + }, + { + "name": "RampEndtemperature", + "value": 20 + } + ] + } + ], + "nextTransitions": ["t6"] + }, + { + "id": "StopCoolDown", + "operations": [ + { + "module": "Temper", + "service": "Tempering", + "command": "complete" + }, + { + "module": "React", + "service": "Stir_Service", + "command": "complete" + } + ], + "nextTransitions": ["t7"] + } + + ], + "transitions": [ + { + "id": "t1", + "nextStates": ["Vorlegen"] + }, + { + "id": "t2", + "condition": { + "type": "and", + "conditions": [ + { + "type": "state", + "module": "Dose", + "service": "DoseAmount", + "state": "completed" + }, + { + "type": "state", + "module": "Temper", + "service": "Tempering", + "state": "running" + } + ] + }, + "nextStates": ["Reagieren"] + }, + { + "id": "t3", + "condition": { + "type": "and", + "conditions": [ + { + "type": "state", + "module": "Dose", + "service": "DoseAmount", + "state": "idle" + }, + { + "type": "variable", + "module": "Temper", + "dataAssembly": "TI", + "operator": ">=", + "value": 20.95 + } + ] + }, + "nextStates": ["Ruhen"] + }, + { + "id": "t4", + "condition": { + "type": "time", + "duration": 20 + }, + "nextStates": ["StopAerate"] + }, + { + "id": "t5", + "condition": { + "type": "state", + "module": "React", + "service": "Aerate_Service", + "state": "completed" + }, + "nextStates": ["Abkuehlen"] + }, + { + "id": "t6", + "condition": { + "type": "variable", + "module": "Temper", + "dataAssembly": "TI", + "operator": "<=", + "value": 20.1 + }, + "nextStates": ["StopCoolDown"] + }, + { + "id": "t7", + "condition": { + "type": "state", + "module": "React", + "service": "Stir_Service", + "state": "idle" + }, + "nextStates": ["finished"] + } + ] + }, + "completing": { + "initialTransition": "t1", + "transitions": [ + { + "id": "t1", + "nextStates": ["Entleeren"] + }, + { + "id": "t2", + "condition": { + "type": "state", + "module": "React", + "service": "Empty_Service", + "state": "completed" + }, + "nextStates": ["finished"] + } + ], + "states": [ + { + "id": "Entleeren", + "operations": [ + { + "module": "React", + "service": "Empty_Service", + "strategy": "Empty_Full", + "command": "start" + } + ], + "nextTransitions": ["t2"] + } + ] + }, + "aborting": { + "initialTransition": "", + "transitions": [], + "states": [] + } + }, + "commandEnable": { + "start": true, + "pause": false, + "complete": false + } +} diff --git a/class-diagram.uxf b/class-diagram.uxf deleted file mode 100644 index 793536cf..00000000 --- a/class-diagram.uxf +++ /dev/null @@ -1,451 +0,0 @@ - - - 10 - - UMLClass - - 570 - 50 - 100 - 30 - - Manager - - - - UMLClass - - 530 - 180 - 100 - 110 - - Module --- -id -endpoint --- -connect() -disconnect() - - - - UMLGeneric - - 690 - 420 - 630 - 420 - - symbol=component -recipe - - - - UMLGeneric - - 410 - 20 - 640 - 330 - - symbol=component -core - - - - UMLClass - - 530 - 360 - 100 - 30 - - Service - - - - Relation - - 560 - 70 - 70 - 130 - - lt=<<<<- -m1=1 -m2=* - 50.0;10.0;10.0;110.0 - - - Relation - - 570 - 280 - 40 - 100 - - lt=<<<<- -m1=1 -m2=* - 10.0;10.0;10.0;80.0 - - - UMLClass - - 890 - 510 - 100 - 30 - - Recipe - - - - UMLClass - - 900 - 720 - 100 - 30 - - Operation - - - - UMLClass - - 1080 - 520 - 100 - 30 - - ScopeItem - - - - UMLClass - - 1390 - 410 - 100 - 30 - - Timer - - - - UMLClass - - 1420 - 280 - 100 - 30 - - /FunctionBlock/ - - - - UMLClass - - 1150 - 160 - 210 - 190 - - <<interface>> -ServiceInterface --- --- -+getServiceState() -+getControlOp() - - - - - Relation - - 620 - 170 - 550 - 220 - - lt=<<. - 530.0;10.0;10.0;200.0 - - - Relation - - 1350 - 170 - 90 - 140 - - lt=<<. - 10.0;10.0;70.0;120.0 - - - Relation - - 940 - 530 - 60 - 110 - - lt=<<<<- -m1=1 -m2=0..n - 10.0;10.0;10.0;90.0 - - - UMLClass - - 900 - 620 - 100 - 30 - - Step - - - - CustomElementImpl - - 0 - 850 - 111 - 71 - - // Modify the text below and -// observe the element preview. - -Hello, World! -Enjoy UMLet! - - //This is a tutorial for a self resizing component -//In addition you are able to resize at manually -//As soon as it is resized manually a new text is -//added to the property panel -allowResize(false); -setAutoresize(30,40,10); - -drawCircle(width/2,15,10); - -int y=45; -for(String textline : textlines) { - y += printCenter(textline,y); -} - - - CustomElementImpl - - 0 - 930 - 200 - 200 - - // Modify the text below and -// observe the element preview. - -Hello, World! -Enjoy UMLet! - - //This is a tutorial for a self resizing component -//In addition you are able to resize at manually -//As soon as it is resized manually a new text is -//added to the property panel -setAutoresize(20,20,10); - -int y=textHeight(); - -boolean center = true; -for(int i = 0; i < textlines.size(); i++) { - String textline = textlines.get(i); - if(textline.equals("--")) { - drawLineHorizontal((int) (y-textHeight()*0.7)); - center = false; - if (!isManualResized()) height -= (int) (textHeight()*0.75); - y += textHeight()*0.3; - } - else { - if (center) y += printCenter(textline,y); - else y += printLeft(textline,y); - } -} - -drawRectangle(0,0,onGrid(width),onGrid(height)); - - - Relation - - 940 - 640 - 30 - 100 - - lt=<<<<- - 10.0;10.0;10.0;80.0 - - - UMLClass - - 900 - 770 - 100 - 30 - - Parameter - - - - UMLClass - - 780 - 710 - 100 - 40 - - Transition - - - - Relation - - 820 - 640 - 150 - 90 - - lt=<<<<- - 130.0;10.0;10.0;70.0 - - - UMLClass - - 780 - 780 - 100 - 30 - - Condition - - - - Relation - - 820 - 740 - 30 - 60 - - lt=<<<<- - 10.0;10.0;10.0;40.0 - - - Relation - - 910 - 450 - 90 - 80 - - lt=<<<<- -playList - 10.0;10.0;30.0;60.0 - - - UMLClass - - 860 - 430 - 100 - 30 - - Player - - - - UMLClass - - 700 - 510 - 100 - 40 - - RecipeRun - - - - Relation - - 740 - 440 - 140 - 90 - - lt=<<<<- -currentRecipeRun -m1=1 -m2=1 - 120.0;10.0;10.0;70.0 - - - Relation - - 790 - 520 - 120 - 40 - - lt=<<<<- -m1=1 -m2=1 - 10.0;10.0;100.0;10.0 - - - Relation - - 620 - 70 - 310 - 380 - - lt=<<<<- - 10.0;10.0;290.0;360.0 - - - UMLClass - - 400 - 310 - 100 - 30 - - ProcessValue - - - - Relation - - 440 - 220 - 110 - 110 - - lt=<<<<- -m1=1 -m2=* - 90.0;10.0;10.0;90.0 - - - Relation - - 1430 - 300 - 50 - 130 - - lt=<<- - 30.0;10.0;10.0;110.0 - - diff --git a/classdiagram.puml b/classdiagram.puml deleted file mode 100644 index a2ad2d80..00000000 --- a/classdiagram.puml +++ /dev/null @@ -1,88 +0,0 @@ -@startuml - -class Manager -class Module { - id - endpoint - client: OPCUAClient -} -class Operation -class Parameter -class Player - -Player --> Recipe : playlist[] -class Recipe { - name - initial_step: Step -} -class RecipeRun -RecipeRun --> Recipe -class Service { - name: string - parameters: ServiceParameter[] -} -class ProcessValue -class Strategy -class Condition{ - {static} create() - {abstract} listen() - {abstract} clear() - json() -} - -class NotCondition -class AndCondition -class OrCondition -class StateCondition -class VariableCondition -class TimeCondition { - duration -} - -NotCondition --|> Condition -AndCondition --|> Condition -OrCondition --|> Condition -TimeCondition --|> Condition -VariableCondition --|> Condition -StateCondition --|> Condition - - -Manager --> Module : modules -Manager --> Recipe : recipes -Manager --> Player - -Module --> Service: services -Module --> ProcessValue : variables - -Service --> Strategy : strategies - -Recipe --> Step: steps -Step --> Operation : operations -Step --> Transition: transitions - -Transition --> Step : next_step -Transition --> Condition: condition - -Operation --> Service -Operation --> Parameter -Operation --> FunctionBlock - -class FunctionBlock { -} - -class Timer { -duration: number -} - -class Clock { -} - -class Storage { -} -class PIDController { -} -Timer --|> FunctionBlock -Clock --|> FunctionBlock -Storage --|> FunctionBlock -PIDController --> FunctionBlock -@enduml \ No newline at end of file diff --git a/doc/VirtualService.puml b/doc/VirtualService.puml new file mode 100644 index 00000000..2392b969 --- /dev/null +++ b/doc/VirtualService.puml @@ -0,0 +1,50 @@ +@startuml + +skinparam monochrome true +skinparam defaultFontName OpenSans + +hide empty fields +hide empty methods + +abstract class AbstractService { +setConfigurationParameter() +start() +stop() +reset() +...() +} + +Service <|-up- AbstractService +VirtualService <|-up- AbstractService + +Service o--"1..*" Strategy + +VirtualService -down-|> AggregatedService +VirtualService -down-|> Timer +VirtualService -down-|> PIDController +VirtualService -down-|> FurtherVirtualService + +class Module { +connect() +} + +Module o--"1..*" Service + +class Service { +setStrategy() +setStrategyParameter() +} + +abstract class VirtualService { +instantiate() +setStrategyParameter() +} + +class AggregatedService + +AggregatedService o--"1..*" AbstractService : necessaryServices > + +class Timer + +class PIDController +@enduml \ No newline at end of file diff --git a/doc/classdiagram.png b/doc/classdiagram.png new file mode 100644 index 00000000..534e316f Binary files /dev/null and b/doc/classdiagram.png differ diff --git a/doc/classdiagram.puml b/doc/classdiagram.puml new file mode 100644 index 00000000..288a96e1 --- /dev/null +++ b/doc/classdiagram.puml @@ -0,0 +1,81 @@ +@startuml +skinparam monochrome true +hide empty methods +hide empty fields + +class Manager +class Module { + id + endpoint + client: OPCUAClient +} + +class Service { + name: string + parameters: ServiceParameter[] +} +class ProcessValue +class Strategy + +package recipe { + class Operation + class Parameter + class Player + class Recipe { + name + initial_step: Step + } + class RecipeRun + class Condition{ + {static} create() + {abstract} listen() + {abstract} clear() + json() + } + 'class NotCondition + 'class AndCondition + 'class OrCondition + 'class StateCondition + 'class VariableCondition + 'class TimeCondition { + ' duration + '} + class Step + class Transition +} + +'NotCondition --|> Condition +'AndCondition --|> Condition +'OrCondition --|> Condition +'TimeCondition --|> Condition +'VariableCondition --|> Condition +'StateCondition --|> Condition + +RecipeRun --> Recipe +Player o-- Recipe : playlist + +Manager "1" o-- "*" Module +Manager "1" o-- "*" Recipe +Manager "1" o-- "*" RecipeRun +Manager "1" o-- "1" Player + +Module "1" o-- "*" Service +Module "1" o-- "*" ProcessValue + +Service "1" o-- "*" Strategy + +Recipe "1" o-- "*" Step +Step "1" o-- "*" Operation +Step "1" o-- "*" Transition + +Transition --> Step : nextStep +Transition --> Condition + +Operation --> Service +Operation --> Parameter +Operation --> VirtualService + +class VirtualService { +} + +@enduml \ No newline at end of file diff --git a/doc/componentdiagram.png b/doc/componentdiagram.png new file mode 100644 index 00000000..0d26412f Binary files /dev/null and b/doc/componentdiagram.png differ diff --git a/componentdiagram.puml b/doc/componentdiagram.puml similarity index 95% rename from componentdiagram.puml rename to doc/componentdiagram.puml index c948b631..cf28ee1c 100644 --- a/componentdiagram.puml +++ b/doc/componentdiagram.puml @@ -1,6 +1,5 @@ @startuml - - +skinparam monochrome true package "Module (PEA)" { [Services] -- [OPC UA Server] diff --git a/doc/petrinet-sample.png b/doc/petrinet-sample.png new file mode 100644 index 00000000..bda062fb Binary files /dev/null and b/doc/petrinet-sample.png differ diff --git a/doc/petrinet-sample.puml b/doc/petrinet-sample.puml new file mode 100644 index 00000000..fbf692dc --- /dev/null +++ b/doc/petrinet-sample.puml @@ -0,0 +1,59 @@ +@startuml + +skinparam monochrome true + +title ServiceState STARTING + + +(*) --> ===t1=== +note right +t1 + R1.Fill.state==IDLE + D1.DoseAmount.state=IDLE + T1.Temper.state==IDLE +end note + + +===t1=== --> startEverything +note right +Operations + R1.Fill.start() + D1.DoseAmount.start(100ml) + T1.Temper.start(30°C) +end note + +startEverything --> ==t2== +note right +t2 +end note + +==t2== --> waitForFillingAndTemperExecute +==t2== --> waitForDosingCompleted + + +waitForFillingAndTemperExecute --> ==t4== + +waitForDosingCompleted --> ==t3== +note right +t3 + D1.DoseAmount.state==COMPLETED +end note + +==t3== --> resetDose +note right +operations + D1.DoseAmount.reset() +end note + +resetDose --> ===t4=== +note right +t4 + D1.DoseAmount.state==IDLE && + R1.Fill.state==EXECUTE && + T1.Temper.state==EXECUTE +end note + + + +==t4== --> (*) +@enduml diff --git a/doc/petrinet-sample2.png b/doc/petrinet-sample2.png new file mode 100644 index 00000000..aacabbb0 Binary files /dev/null and b/doc/petrinet-sample2.png differ diff --git a/doc/petrinet-sample2.puml b/doc/petrinet-sample2.puml new file mode 100644 index 00000000..017894f9 --- /dev/null +++ b/doc/petrinet-sample2.puml @@ -0,0 +1,30 @@ +@startuml + +skinparam monochrome true + +title ServiceState STARTING + + +(*) --> ===t1=== +note right +t1 + D1.DoseAmount.state==IDLE + R1.Fill.state==IDLE +end note + +===t1=== --> state1 +note right +Operations: + D1.DoseAmount.start(SetVolume=SetVolume) + R1.Fill.start() +end note + +state1 --> ===t2=== +note right +t2 + D1.DoseAmount.state==EXECUTE && + R1.Fill.state==EXECUTE +end note + +===t2=== --> (*) +@enduml diff --git a/doc/petrinet.png b/doc/petrinet.png new file mode 100644 index 00000000..6b3e63ad Binary files /dev/null and b/doc/petrinet.png differ diff --git a/doc/petrinet.puml b/doc/petrinet.puml new file mode 100644 index 00000000..7afdf7c0 --- /dev/null +++ b/doc/petrinet.puml @@ -0,0 +1,40 @@ +@startuml + +skinparam monochrome true +hide methods +hide empty fields + +class AggregatedService + + + +class Petrinet { + +} + +class PNState + +class Operation { +module +service +strategy +command +parameters +} + +class PNTransition { +condition +} + +AggregatedService "1" *-- "16" Petrinet +Petrinet "1" *- "*" PNState +Petrinet "1" -- "1" PNTransition : > initialTransition + + + +PNState "1" *- "*" Operation + +PNState "1" *-- "*" PNTransition + +PNTransition "1" *-- "*" PNState +@enduml \ No newline at end of file diff --git a/doc/sequence.png b/doc/sequence.png new file mode 100644 index 00000000..895ebfac Binary files /dev/null and b/doc/sequence.png differ diff --git a/doc/sequence.puml b/doc/sequence.puml new file mode 100644 index 00000000..93deae2f --- /dev/null +++ b/doc/sequence.puml @@ -0,0 +1,43 @@ +@startuml + +skinparam monochrome true + +participant frontend +participant backend +participant PEA + +activate PEA + + +frontend -> backend: Load Module +activate backend +frontend <-- backend: loaded Module +deactivate backend + +frontend -> backend: Connect Module +activate backend +backend -> PEA: connect() +backend <-- PEA +frontend <-- backend +backend -> PEA: subscribe to all important variables +... + +loop +backend <- PEA: updates +frontend <- backend: Notification via websocket +end +frontend -> backend: Get specific information +frontend <-- backend: Direct response without communication to PEA + +... + +frontend -> backend: Disconnect module +backend -> PEA: disconnect() +backend <-- PEA +frontend <-- backend +deactivate backend + + +deactivate PEA + +@enduml \ No newline at end of file diff --git a/doc/virtualservice.png b/doc/virtualservice.png new file mode 100644 index 00000000..83869fcc Binary files /dev/null and b/doc/virtualservice.png differ diff --git a/doc/virtualservice.puml b/doc/virtualservice.puml new file mode 100644 index 00000000..5292b737 --- /dev/null +++ b/doc/virtualservice.puml @@ -0,0 +1,42 @@ +@startuml +skinparam monochrome true +hide methods +hide attributes + +abstract class BaseService + +class Service + +abstract class VirtualService + +class AggregatedService + +class Timer + +class PidController + +class Other + + + +BaseService <|-- Service +BaseService <|-- VirtualService + +VirtualService <|-- AggregatedService +VirtualService <|-- Timer +VirtualService <|-- PidController +VirtualService <|-- Other + +class VirtualServices + +class Manager +class Module + +Module *-- "*" Service +Manager o-- "*" Module +Manager o-- "1" VirtualServices +VirtualServices *-- "*" VirtualService + +AggregatedService o.. BaseService + +@enduml \ No newline at end of file diff --git a/nodemon.json b/nodemon.json deleted file mode 100644 index 7432e790..00000000 --- a/nodemon.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "watch": ["src"], - "ext": "ts", - "exec": "ts-node ./src/index.ts" -} diff --git a/package-lock.json b/package-lock.json index be6da9c3..c74d808b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,27 +1,27 @@ { "name": "@p2olab/polaris-backend", - "version": "1.8.3", + "version": "1.8.5", "lockfileVersion": 1, "requires": true, "dependencies": { "@babel/code-frame": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.0.0.tgz", - "integrity": "sha512-OfC2uemaknXr87bdLUkWog7nYuliM9Ij5HUcajsVcMCpQrcLmtxRbVFTIqmcSkSeYRBFBRxs2FiUqFJDLdiebA==", + "version": "7.5.5", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.5.5.tgz", + "integrity": "sha512-27d4lZoomVyo51VegxI20xZPuSHusqbQag/ztrBC7wegWoQ1nLREPVSKSW8byhTlzTKyNE4ifaTA6lCp7JjpFw==", "dev": true, "requires": { "@babel/highlight": "^7.0.0" } }, "@babel/generator": { - "version": "7.4.4", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.4.4.tgz", - "integrity": "sha512-53UOLK6TVNqKxf7RUh8NE851EHRxOOeVXKbK2bivdb+iziMyk03Sr4eaE9OELCbyZAAafAKPDwF2TPUES5QbxQ==", + "version": "7.5.5", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.5.5.tgz", + "integrity": "sha512-ETI/4vyTSxTzGnU2c49XHv2zhExkv9JHLTwDAFz85kmcwuShvYG2H08FwgIguQf4JC75CBnXAUM5PqeF4fj0nQ==", "dev": true, "requires": { - "@babel/types": "^7.4.4", + "@babel/types": "^7.5.5", "jsesc": "^2.5.1", - "lodash": "^4.17.11", + "lodash": "^4.17.13", "source-map": "^0.5.0", "trim-right": "^1.0.1" }, @@ -64,9 +64,9 @@ } }, "@babel/highlight": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.0.0.tgz", - "integrity": "sha512-UFMC4ZeFC48Tpvj7C8UgLvtkaUuovQX+5xNWrsIoMG8o2z+XFKjKaN9iVmS84dPwVN00W4wPmqvYoZF3EGAsfw==", + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.5.0.tgz", + "integrity": "sha512-7dV4eu9gBxoM0dAnj/BCFDW9LFU0zvTrkq0ugM7pnHEgguOEeOz1so2ZghEdzviYzQEED0r4EAgpsBChKy1TRQ==", "dev": true, "requires": { "chalk": "^2.0.0", @@ -75,9 +75,9 @@ } }, "@babel/parser": { - "version": "7.4.4", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.4.4.tgz", - "integrity": "sha512-5pCS4mOsL+ANsFZGdvNLybx4wtqAZJ0MJjMHxvzI3bvIsz6sQvzW8XX92EYIkiPtIvcfG3Aj+Ir5VNyjnZhP7w==", + "version": "7.5.5", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.5.5.tgz", + "integrity": "sha512-E5BN68cqR7dhKan1SfqgPGhQ178bkVKpXTPEXnFJBrEt8/DKRZlybmy+IgYLTeN7tp1R5Ccmbm2rBk17sHYU3g==", "dev": true }, "@babel/template": { @@ -92,20 +92,20 @@ } }, "@babel/traverse": { - "version": "7.4.4", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.4.4.tgz", - "integrity": "sha512-Gw6qqkw/e6AGzlyj9KnkabJX7VcubqPtkUQVAwkc0wUMldr3A/hezNB3Rc5eIvId95iSGkGIOe5hh1kMKf951A==", + "version": "7.5.5", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.5.5.tgz", + "integrity": "sha512-MqB0782whsfffYfSjH4TM+LMjrJnhCNEDMDIjeTpl+ASaUvxcjoiVCo/sM1GhS1pHOXYfWVCYneLjMckuUxDaQ==", "dev": true, "requires": { - "@babel/code-frame": "^7.0.0", - "@babel/generator": "^7.4.4", + "@babel/code-frame": "^7.5.5", + "@babel/generator": "^7.5.5", "@babel/helper-function-name": "^7.1.0", "@babel/helper-split-export-declaration": "^7.4.4", - "@babel/parser": "^7.4.4", - "@babel/types": "^7.4.4", + "@babel/parser": "^7.5.5", + "@babel/types": "^7.5.5", "debug": "^4.1.0", "globals": "^11.1.0", - "lodash": "^4.17.11" + "lodash": "^4.17.13" }, "dependencies": { "debug": { @@ -118,48 +118,86 @@ } }, "ms": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", - "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", "dev": true } } }, "@babel/types": { - "version": "7.4.4", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.4.4.tgz", - "integrity": "sha512-dOllgYdnEFOebhkKCjzSVFqw/PmmB8pH6RGOWkY4GsboQNd47b1fBThBSwlHAq9alF9vc1M3+6oqR47R50L0tQ==", + "version": "7.5.5", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.5.5.tgz", + "integrity": "sha512-s63F9nJioLqOlW3UkyMd+BYhXt44YuaFm/VV0VwuteqjYwRrObkU7ra9pY4wAJR3oXi8hJrMcrcJdO/HH33vtw==", "dev": true, "requires": { "esutils": "^2.0.2", - "lodash": "^4.17.11", + "lodash": "^4.17.13", "to-fast-properties": "^2.0.0" } }, + "@nodelib/fs.scandir": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.1.tgz", + "integrity": "sha512-NT/skIZjgotDSiXs0WqYhgcuBKhUMgfekCmCGtkUAiLqZdOnrdjmZr9wRl3ll64J9NF79uZ4fk16Dx0yMc/Xbg==", + "requires": { + "@nodelib/fs.stat": "2.0.1", + "run-parallel": "^1.1.9" + } + }, + "@nodelib/fs.stat": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.1.tgz", + "integrity": "sha512-+RqhBlLn6YRBGOIoVYthsG0J9dfpO79eJyN7BYBkZJtfqrBwf2KK+rD/M/yjZR6WBmIhAgOV7S60eCgaSWtbFw==" + }, + "@nodelib/fs.walk": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.2.tgz", + "integrity": "sha512-J/DR3+W12uCzAJkw7niXDcqcKBg6+5G5Q/ZpThpGNzAUz70eOR6RV4XnnSN01qHZiVl0eavoxJsBypQoKsV2QQ==", + "requires": { + "@nodelib/fs.scandir": "2.1.1", + "fastq": "^1.6.0" + } + }, "@p2olab/polaris-interface": { - "version": "1.23.0", - "resolved": "https://registry.npmjs.org/@p2olab/polaris-interface/-/polaris-interface-1.23.0.tgz", - "integrity": "sha512-Fx5CeRrHV+kvyp6BjvoeokcX5R1nHwUyKucYw4Joh08Sex4OAptlAN0diRLWRmbl+dn8qVLL4sTwLG8xlod2nA==" + "version": "1.27.0", + "resolved": "https://registry.npmjs.org/@p2olab/polaris-interface/-/polaris-interface-1.27.0.tgz", + "integrity": "sha512-XK9UIWlQF29qub/f0VkSfpm0RYSGBtZqNlUDicQfhGCb7iqnpG9u3U4tXlNUs6xpr4bLJVLTmPxbGJ6Cs5lpRA==" + }, + "@types/async": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@types/async/-/async-3.0.1.tgz", + "integrity": "sha512-fBq+1+btw/4CPGCpp3B05vUsslyikLUSUoZmFEjjHrUEPOisIVxUzGJFg1k43iWq9y0lC2UDqWAlg5ynUCwwSA==" }, "@types/body-parser": { - "version": "1.17.0", - "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.17.0.tgz", - "integrity": "sha512-a2+YeUjPkztKJu5aIF2yArYFQQp8d51wZ7DavSHjFuY1mqVgidGyzEQ41JIVNy82fXj8yPgy2vJmfIywgESW6w==", + "version": "1.17.1", + "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.17.1.tgz", + "integrity": "sha512-RoX2EZjMiFMjZh9lmYrwgoP9RTpAjSHiJxdp4oidAQVO02T7HER3xj9UKue5534ULWeqVEkujhWcyvUce+d68w==", "dev": true, "requires": { "@types/connect": "*", "@types/node": "*" } }, + "@types/bonjour": { + "version": "3.5.5", + "resolved": "https://registry.npmjs.org/@types/bonjour/-/bonjour-3.5.5.tgz", + "integrity": "sha512-4fHAqgL0SYHgDmdBrm201hUl7gIavk8KAtwBuDX9TjVdEZbUENdKmnago4GsmDO8QALMUE651F+nNpaCF+B4+A==", + "requires": { + "@types/node": "*" + } + }, "@types/chai": { - "version": "4.1.7", - "resolved": "https://registry.plt.et.tu-dresden.de:4873/@types%2fchai/-/chai-4.1.7.tgz", - "integrity": "sha512-2Y8uPt0/jwjhQ6EiluT0XCri1Dbplr0ZxfFXUz+ye13gaqE8u5gL5ppao1JrUYr9cIip5S6MvQzBS7Kke7U9VA==" + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.2.0.tgz", + "integrity": "sha512-zw8UvoBEImn392tLjxoavuonblX/4Yb9ha4KBU10FirCfwgzhKO0dvyJSF9ByxV1xK1r2AgnAi/tvQaLgxQqxA==", + "dev": true }, "@types/chai-as-promised": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/@types/chai-as-promised/-/chai-as-promised-7.1.0.tgz", - "integrity": "sha512-MFiW54UOSt+f2bRw8J7LgQeIvE/9b4oGvwU7XW30S9QGAiHGnU/fmiOprsyMkdmH2rl8xSPc0/yrQw8juXU6bQ==", + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/@types/chai-as-promised/-/chai-as-promised-7.1.2.tgz", + "integrity": "sha512-PO2gcfR3Oxa+u0QvECLe1xKXOqYTzCmWf0FhLhjREoW3fPAVamjihL7v1MOVLJLsnAMdLcjkfrs01yvDMwVK4Q==", + "dev": true, "requires": { "@types/chai": "*" } @@ -192,9 +230,9 @@ "dev": true }, "@types/cors": { - "version": "2.8.5", - "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.5.tgz", - "integrity": "sha512-GmK8AKu8i+s+EChK/uZ5IbrXPcPaQKWaNSGevDT/7o3gFObwSUQwqb1jMqxuo+YPvj0ckGzINI+EO7EHcmJjKg==", + "version": "2.8.6", + "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.6.tgz", + "integrity": "sha512-invOmosX0DqbpA+cE2yoHGUlF/blyf7nB0OGYBBiH27crcVm5NmFaZkLP4Ta1hGaesckCi5lVLlydNJCxkTOSg==", "dev": true, "requires": { "@types/express": "*" @@ -206,9 +244,9 @@ "integrity": "sha512-KEIlhXnIutzKwRbQkGWb/I4HFqBuUykAdHgDED6xqwXJfONCjF5VoE0cXEiurh3XauygxzeDzgtXUqvLkxFzzA==" }, "@types/express": { - "version": "4.16.1", - "resolved": "https://registry.npmjs.org/@types/express/-/express-4.16.1.tgz", - "integrity": "sha512-V0clmJow23WeyblmACoxbHBu2JKlE5TiIme6Lem14FnPW9gsttyHtk6wq7njcdIWH1njAaFgR8gW09lgY98gQg==", + "version": "4.17.1", + "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.1.tgz", + "integrity": "sha512-VfH/XCP0QbQk5B5puLqTLEeFgR8lfCJHZJKkInZ9mkYd+u8byX0kztXEQxEk4wZXJs8HI+7km2ALXjn4YKcX9w==", "dev": true, "requires": { "@types/body-parser": "*", @@ -217,9 +255,9 @@ } }, "@types/express-serve-static-core": { - "version": "4.16.5", - "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.16.5.tgz", - "integrity": "sha512-T8oej2B9TVALoCK4epWXRNWaFaqDyOaEQ3ntph+tSw5QoKnZfxgCXefIm3+nGp2XCRrkX4X7U5CSTMnwKJcOjQ==", + "version": "4.16.9", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.16.9.tgz", + "integrity": "sha512-GqpaVWR0DM8FnRUJYKlWgyARoBUAVfRIeVDZQKOttLFp5SmhhF9YFIYeTPwMd/AXfxlP7xVO2dj1fGu0Q+krKQ==", "dev": true, "requires": { "@types/node": "*", @@ -237,7 +275,7 @@ }, "@types/glob": { "version": "7.1.1", - "resolved": "https://registry.plt.et.tu-dresden.de:4873/@types%2fglob/-/glob-7.1.1.tgz", + "resolved": "https://registry.npmjs.org/@types/glob/-/glob-7.1.1.tgz", "integrity": "sha512-1Bh06cbWJUHMC97acuD6UMG29nMt0Aqz1vF3guLfG+kHHJhy3AyohZFFxYk2f7Q1SQIrNwvncxAE0N/9s70F2w==", "requires": { "@types/events": "*", @@ -256,14 +294,14 @@ }, "@types/highlight.js": { "version": "9.12.3", - "resolved": "https://registry.plt.et.tu-dresden.de:4873/@types%2fhighlight.js/-/highlight.js-9.12.3.tgz", + "resolved": "https://registry.npmjs.org/@types/highlight.js/-/highlight.js-9.12.3.tgz", "integrity": "sha512-pGF/zvYOACZ/gLGWdQH8zSwteQS1epp68yRcVLJMgUck/MjEn/FBYmPub9pXT8C1e4a8YZfHo1CKyV8q1vKUnQ==", "dev": true }, "@types/lodash": { - "version": "4.14.123", - "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.123.tgz", - "integrity": "sha512-pQvPkc4Nltyx7G1Ww45OjVqUsJP4UsZm+GWJpigXgkikZqJgRm4c48g027o6tdgubWHwFRF15iFd+Y4Pmqv6+Q==", + "version": "4.14.137", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.137.tgz", + "integrity": "sha512-g4rNK5SRKloO+sUGbuO7aPtwbwzMgjK+bm9BBhLD7jGUiGR7zhwYEhSln/ihgYQBeIJ5j7xjyaYzrWTcu3UotQ==", "dev": true }, "@types/lorem-ipsum": { @@ -273,7 +311,7 @@ }, "@types/marked": { "version": "0.4.2", - "resolved": "https://registry.plt.et.tu-dresden.de:4873/@types%2fmarked/-/marked-0.4.2.tgz", + "resolved": "https://registry.npmjs.org/@types/marked/-/marked-0.4.2.tgz", "integrity": "sha512-cDB930/7MbzaGF6U3IwSQp6XBru8xWajF5PV2YZZeV8DyiliTuld11afVztGI9+yJZ29il5E+NpGA6ooV/Cjkg==", "dev": true }, @@ -285,28 +323,41 @@ }, "@types/minimatch": { "version": "3.0.3", - "resolved": "https://registry.plt.et.tu-dresden.de:4873/@types%2fminimatch/-/minimatch-3.0.3.tgz", + "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.3.tgz", "integrity": "sha512-tHq6qdbT9U1IRSGf14CL0pUlULksvY9OZ+5eEgl1N7t+OA3tGvNpxJCzuKQlsNgCVwbAs670L1vcVQi8j9HjnA==" }, + "@types/mkdirp": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/@types/mkdirp/-/mkdirp-0.5.2.tgz", + "integrity": "sha512-U5icWpv7YnZYGsN4/cmh3WD2onMY0aJIiTE6+51TwJCttdHvtCYmkBNOobHlXwrJRL0nkH9jH4kD+1FAdMN4Tg==", + "requires": { + "@types/node": "*" + } + }, "@types/mocha": { - "version": "5.2.6", - "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-5.2.6.tgz", - "integrity": "sha512-1axi39YdtBI7z957vdqXI4Ac25e7YihYQtJa+Clnxg1zTJEaIRbndt71O3sP4GAMgiAm0pY26/b9BrY4MR/PMw==", + "version": "5.2.7", + "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-5.2.7.tgz", + "integrity": "sha512-NYrtPht0wGzhwe9+/idPaBB+TqkY9AhTvOLMkThm0IoEfLaiVQZwBwyJ5puCkO3AUCWrmcoePjp2mbFocKy4SQ==", "dev": true }, "@types/multer": { - "version": "1.3.7", - "resolved": "https://registry.npmjs.org/@types/multer/-/multer-1.3.7.tgz", - "integrity": "sha512-Lx4rNtGajRGtcVwJe1sKPAkAuBBWq8TOuimKJfOfK7ayY1Jc+18Lx00GjagLeIwaH2+OvFJvCv8tz+pvbt3OoA==", + "version": "1.3.9", + "resolved": "https://registry.npmjs.org/@types/multer/-/multer-1.3.9.tgz", + "integrity": "sha512-aKUwxl4yH87G8b3wgmruBVdOWiG9qqmc4Epjt5Uz3mqaMZwx3dkMm4tHn5nnwBk2NT+X4t5yADJiEv/NfMFQIg==", "dev": true, "requires": { "@types/express": "*" } }, "@types/node": { - "version": "12.0.8", - "resolved": "https://registry.npmjs.org/@types/node/-/node-12.0.8.tgz", - "integrity": "sha512-b8bbUOTwzIY3V5vDTY1fIJ+ePKDUBqt2hC2woVGotdQQhG/2Sh62HOKHrT7ab+VerXAcPyAiTEipPu/FsreUtg==" + "version": "12.7.5", + "resolved": "https://registry.npmjs.org/@types/node/-/node-12.7.5.tgz", + "integrity": "sha512-9fq4jZVhPNW8r+UYKnxF1e2HkDWOWKM5bC2/7c9wPV835I0aOrVbS/Hw/pWPk2uKrNXQqg9Z959Kz+IYDd5p3w==" + }, + "@types/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@types/once/-/once-1.4.0.tgz", + "integrity": "sha512-cnEvTAVVRqF6OQg/4SLnbxQ0slZJHqZQDve5BzGhcIQtuMpPv8T5QNS2cBPa/W0jTxciqwn7bmJAIGe/bOJ5Kw==" }, "@types/range-parser": { "version": "1.2.3", @@ -315,9 +366,9 @@ "dev": true }, "@types/serve-static": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.13.2.tgz", - "integrity": "sha512-/BZ4QRLpH/bNYgZgwhKEh+5AsboDBcUdlBYgzoLX0fpj3Y2gp6EApyOlM3bK53wQS/OE1SrdSYBAbux2D1528Q==", + "version": "1.13.3", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.13.3.tgz", + "integrity": "sha512-oprSwp094zOglVrXdlo/4bAHtKTAxX6VT8FOZlBKrmyLbNvE1zxZyJ6yikMVtHIvwP45+ZQGJn+FdXGKTozq0g==", "dev": true, "requires": { "@types/express-serve-static-core": "*", @@ -344,43 +395,51 @@ "@types/node": "*" } }, + "@types/underscore": { + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/@types/underscore/-/underscore-1.9.2.tgz", + "integrity": "sha512-KgOKTAD+9X+qvZnB5S1+onqKc4E+PZ+T6CM/NA5ohRPLHJXb+yCJMVf8pWOnvuBuKFNUAJW8N97IA6lba6mZGg==" + }, "@types/uuid": { - "version": "3.4.4", - "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-3.4.4.tgz", - "integrity": "sha512-tPIgT0GUmdJQNSHxp0X2jnpQfBSTfGxUMc/2CXBU2mnyTFVYVa2ojpoQ74w0U2yn2vw3jnC640+77lkFFpdVDw==", + "version": "3.4.5", + "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-3.4.5.tgz", + "integrity": "sha512-MNL15wC3EKyw1VLF+RoVO4hJJdk9t/Hlv3rt1OL65Qvuadm4BYo6g9ZJQqoq7X8NBFSsQXgAujWciovh2lpVjA==", "dev": true, "requires": { "@types/node": "*" } }, "@types/ws": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/@types/ws/-/ws-6.0.1.tgz", - "integrity": "sha512-EzH8k1gyZ4xih/MaZTXwT2xOkPiIMSrhQ9b8wrlX88L0T02eYsddatQlwVFlEPyEqV0ChpdpNnE51QPH6NVT4Q==", + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-6.0.3.tgz", + "integrity": "sha512-yBTM0P05Tx9iXGq00BbJPo37ox68R5vaGTXivs6RGh/BQ6QP5zqZDGWdAO6JbRE/iR1l80xeGAwCQS2nMV9S/w==", "dev": true, "requires": { - "@types/events": "*", "@types/node": "*" } }, "accepts": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.5.tgz", - "integrity": "sha1-63d99gEXI6OxTopywIBcjoZ0a9I=", - "requires": { - "mime-types": "~2.1.18", - "negotiator": "0.6.1" - } - }, - "ajv": { - "version": "6.10.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.10.0.tgz", - "integrity": "sha512-nffhOpkymDECQyR0mnsUtoCE8RlX38G0rYP+wgLWFyZuUyuuojSSvi/+euOiQBIn63whYwYVIIH1TvE3tu4OEg==", + "version": "1.3.7", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz", + "integrity": "sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA==", "requires": { - "fast-deep-equal": "^2.0.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" + "mime-types": "~2.1.24", + "negotiator": "0.6.2" + }, + "dependencies": { + "mime-db": { + "version": "1.40.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.40.0.tgz", + "integrity": "sha512-jYdeOMPy9vnxEqFRRo6ZvTZ8d9oPb+k18PKoYNYUe2stVEBPPwsln/qWzdbmaIvnhZ9v2P+CuecK+fpUfsV2mA==" + }, + "mime-types": { + "version": "2.1.24", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.24.tgz", + "integrity": "sha512-WaFHS3MCl5fapm3oLxU4eYDw77IQM2ACcxQ9RIxfaC3ooc6PFuBMGZZsYpvoXS5D5QTWPieo1jjLdAm3TBP3cQ==", + "requires": { + "mime-db": "1.40.0" + } + } } }, "ansi-colors": { @@ -392,7 +451,8 @@ "ansi-regex": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", - "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=" + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", + "dev": true }, "ansi-styles": { "version": "3.2.1", @@ -472,9 +532,9 @@ "dev": true }, "arg": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.0.tgz", - "integrity": "sha512-ZWc51jO3qegGkVh8Hwpv636EkbesNV5ZNQPCtRa+0qytRYPEs9IYT9qITY9buezqUH5uqyzlWLcufrzU2rffdg==", + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.1.tgz", + "integrity": "sha512-SlmP3fEA88MBv0PypnXZ8ZfJhwmDeIE3SP71j37AiXQBXYosPV0x6uISAaHYSlSVhmHOVkomen0tbGk6Anlebw==", "dev": true }, "argparse": { @@ -500,28 +560,15 @@ "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=" }, "array-union": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/array-union/-/array-union-1.0.2.tgz", - "integrity": "sha1-mjRBDk9OPaI96jdb5b5w8kd47Dk=", - "requires": { - "array-uniq": "^1.0.1" - } - }, - "array-uniq": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/array-uniq/-/array-uniq-1.0.3.tgz", - "integrity": "sha1-r2rId6Jcx/dOBYiUdThY39sk/bY=" + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", + "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==" }, "asn1": { "version": "0.1.11", "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.1.11.tgz", "integrity": "sha1-VZvhg3bQik7E2+gId9J4GGObLfc=" }, - "assert-plus": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", - "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" - }, "assertion-error": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", @@ -529,9 +576,9 @@ "dev": true }, "assign-deep": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/assign-deep/-/assign-deep-1.0.0.tgz", - "integrity": "sha512-wH7AeUhZk5j8E8B3pDJ/nmN2eOR+zPK47do3a4GKn5L7bMtKTqFTnvtxNoDg4+XgmF08euylwYwgFv4F0l38QQ==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/assign-deep/-/assign-deep-1.0.1.tgz", + "integrity": "sha512-CSXAX79mibneEYfqLT5FEmkqR5WXF+xDRjgQQuVf6wSCXCYU8/vHttPidNar7wJ5BFmKAo8Wei0rCtzb+M/yeA==", "requires": { "assign-symbols": "^2.0.2" } @@ -551,24 +598,15 @@ } }, "async-limiter": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.0.tgz", - "integrity": "sha512-jp/uFnooOiO+L211eZOoSyzpOITMXx1rBITauYykG3BRYPu8h0UcxsPNB04RR5vo4Tyz3+ay17tR6JVf9qzYWg==" + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.1.tgz", + "integrity": "sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ==" }, "asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=" - }, - "aws-sign2": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", - "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=" - }, - "aws4": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.8.0.tgz", - "integrity": "sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ==" + "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=", + "dev": true }, "backoff": { "version": "2.5.0", @@ -583,14 +621,6 @@ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" }, - "bcrypt-pbkdf": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", - "integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=", - "requires": { - "tweetnacl": "^0.14.3" - } - }, "better-assert": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/better-assert/-/better-assert-1.0.2.tgz", @@ -599,41 +629,21 @@ "callsite": "1.0.0" } }, - "bindings": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.2.1.tgz", - "integrity": "sha1-FK1hE4EtLTfXLme0ystLtyZQXxE=" - }, "body-parser": { - "version": "1.18.3", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.18.3.tgz", - "integrity": "sha1-WykhmP/dVTs6DyDe0FkrlWlVyLQ=", + "version": "1.19.0", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.0.tgz", + "integrity": "sha512-dhEPs72UPbDnAQJ9ZKMNTP6ptJaionhP5cBb541nXPlW60Jepo9RV/a4fX4XWW9CuFNK22krhrj1+rgzifNCsw==", "requires": { - "bytes": "3.0.0", + "bytes": "3.1.0", "content-type": "~1.0.4", "debug": "2.6.9", "depd": "~1.1.2", - "http-errors": "~1.6.3", - "iconv-lite": "0.4.23", + "http-errors": "1.7.2", + "iconv-lite": "0.4.24", "on-finished": "~2.3.0", - "qs": "6.5.2", - "raw-body": "2.3.3", - "type-is": "~1.6.16" - }, - "dependencies": { - "iconv-lite": { - "version": "0.4.23", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.23.tgz", - "integrity": "sha512-neyTUVFtahjf0mB3dZT77u+8O0QB89jFdnBkd5P1JgYPbPaia3gXXOVL2fq8VyU2gMMD7SaN7QukTB/pmXYvDA==", - "requires": { - "safer-buffer": ">= 2.1.2 < 3" - } - }, - "qs": { - "version": "6.5.2", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", - "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==" - } + "qs": "6.7.0", + "raw-body": "2.4.0", + "type-is": "~1.6.17" } }, "bomstrip": { @@ -670,6 +680,14 @@ "concat-map": "0.0.1" } }, + "braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "requires": { + "fill-range": "^7.0.1" + } + }, "browser-stdout": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", @@ -684,8 +702,7 @@ "buffer-from": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", - "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==", - "dev": true + "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==" }, "buffer-indexof": { "version": "1.1.1", @@ -704,9 +721,9 @@ "integrity": "sha1-dBxSFkaOrcRXsDQQEYrXfejB3bE=" }, "bytes": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", - "integrity": "sha1-0ygVQE1olpn4Wk6k+odV3ROpYEg=" + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz", + "integrity": "sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==" }, "caching-transform": { "version": "3.0.2", @@ -720,6 +737,11 @@ "write-file-atomic": "^2.4.2" } }, + "callbackify": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/callbackify/-/callbackify-1.1.0.tgz", + "integrity": "sha1-0qNphtKKppcUUmwREgm+65l50x4=" + }, "callsite": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/callsite/-/callsite-1.0.0.tgz", @@ -731,14 +753,9 @@ "integrity": "sha1-1UVjW+HjPFQmScaRc+Xeas+uNN0=", "dev": true }, - "caseless": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", - "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=" - }, "chai": { "version": "4.2.0", - "resolved": "https://registry.plt.et.tu-dresden.de:4873/chai/-/chai-4.2.0.tgz", + "resolved": "https://registry.npmjs.org/chai/-/chai-4.2.0.tgz", "integrity": "sha512-XQU3bhBukrOsQCuwZndwGcCVQHyZi53fQ6Ys1Fym7E4olpIqqZZhhoFJoaKVvV17lWQoXYwgWN2nF5crA8J2jw==", "dev": true, "requires": { @@ -754,6 +771,7 @@ "version": "7.1.1", "resolved": "https://registry.npmjs.org/chai-as-promised/-/chai-as-promised-7.1.1.tgz", "integrity": "sha512-azL6xMoi+uxu6z4rhWQ1jbdUhOMhis2PvscD/xjLqNMkv3BPPp2JyyuTHOrf9BOosGpNQ11v6BKv/g57RXbiaA==", + "dev": true, "requires": { "check-error": "^1.0.2" } @@ -786,7 +804,8 @@ "check-error": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.2.tgz", - "integrity": "sha1-V00xLt2Iu13YkS6Sht1sCu1KrII=" + "integrity": "sha1-V00xLt2Iu13YkS6Sht1sCu1KrII=", + "dev": true }, "cli-spinner": { "version": "0.2.10", @@ -812,6 +831,7 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/cliui/-/cliui-4.1.0.tgz", "integrity": "sha512-4FG+RSG9DL7uEwRUZXZn3SS34DiDPfzP0VOiEwtUWlE+AR2EIg+hSyvrIgUUfhdgR/UkAeW2QHgeP+hWrXs7jQ==", + "dev": true, "requires": { "string-width": "^2.1.1", "strip-ansi": "^4.0.0", @@ -822,6 +842,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "dev": true, "requires": { "ansi-regex": "^3.0.0" } @@ -831,7 +852,8 @@ "code-point-at": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", - "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=" + "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=", + "dev": true }, "color": { "version": "3.0.0", @@ -891,6 +913,7 @@ "version": "1.0.7", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.7.tgz", "integrity": "sha512-brWl9y6vOB1xYPZcpZde3N9zDByXTosAeMDo4p1wzo6UMOX4vumB+TP1RZ76sfE6Md68Q0NJSrE/gbezd4Ul+w==", + "dev": true, "requires": { "delayed-stream": "~1.0.0" } @@ -927,26 +950,6 @@ "chalk": "^2.4.1", "table-layout": "^0.4.3", "typical": "^2.6.1" - }, - "dependencies": { - "ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "requires": { - "color-convert": "^1.9.0" - } - }, - "chalk": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.1.tgz", - "integrity": "sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ==", - "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - } - } } }, "commander": { @@ -964,7 +967,8 @@ "component-emitter": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz", - "integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==" + "integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==", + "dev": true }, "concat-map": { "version": "0.0.1", @@ -972,9 +976,19 @@ "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" }, "content-disposition": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.2.tgz", - "integrity": "sha1-DPaLud318r55YcOoUXjLhdunjLQ=" + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.3.tgz", + "integrity": "sha512-ExO0774ikEObIAEV9kDo50o+79VCUdEB6n6lzKgGwupcVeRlhrj3qGAfwq8G6uBJjkqLrhT0qEYFcWng8z1z0g==", + "requires": { + "safe-buffer": "5.1.2" + }, + "dependencies": { + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + } + } }, "content-type": { "version": "1.0.4", @@ -991,9 +1005,9 @@ } }, "cookie": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.3.1.tgz", - "integrity": "sha1-5+Ch+e9DtMi6klxcWpboBtFoc7s=" + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.0.tgz", + "integrity": "sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg==" }, "cookie-signature": { "version": "1.0.6", @@ -1003,12 +1017,14 @@ "cookiejar": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/cookiejar/-/cookiejar-2.1.2.tgz", - "integrity": "sha512-Mw+adcfzPxcPeI+0WlvRrr/3lGVO0bD75SxX6811cxSh1Wbxx7xZBGK1eVtDf6si8rg2lhnUjsVLMFMfbRIuwA==" + "integrity": "sha512-Mw+adcfzPxcPeI+0WlvRrr/3lGVO0bD75SxX6811cxSh1Wbxx7xZBGK1eVtDf6si8rg2lhnUjsVLMFMfbRIuwA==", + "dev": true }, "core-util-is": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", - "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", + "dev": true }, "cors": { "version": "2.8.5", @@ -1036,6 +1052,7 @@ "version": "6.0.5", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", + "dev": true, "requires": { "nice-try": "^1.0.4", "path-key": "^2.0.1", @@ -1047,7 +1064,8 @@ "semver": { "version": "5.5.0", "resolved": "https://registry.npmjs.org/semver/-/semver-5.5.0.tgz", - "integrity": "sha512-4SJ3dm0WAwWy/NVeioZh5AntkdJoWKxHxcmyP622fOkgHa4z3R0TdBJICINyaSDE6uNwVc8gZr+ZinwZAH4xIA==" + "integrity": "sha512-4SJ3dm0WAwWy/NVeioZh5AntkdJoWKxHxcmyP622fOkgHa4z3R0TdBJICINyaSDE6uNwVc8gZr+ZinwZAH4xIA==", + "dev": true } } }, @@ -1056,23 +1074,6 @@ "resolved": "https://registry.npmjs.org/ctype/-/ctype-0.5.2.tgz", "integrity": "sha1-/oCR1Gijc6Cwyf+Lv7NCXACXOh0=" }, - "dashdash": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", - "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", - "requires": { - "assert-plus": "^1.0.0" - } - }, - "deasync": { - "version": "0.1.15", - "resolved": "https://registry.npmjs.org/deasync/-/deasync-0.1.15.tgz", - "integrity": "sha512-pxMaCYu8cQIbGkA4Y1R0PLSooPIpH1WgFBLeJ+zLxQgHfkZG86ViJSmZmONSjZJ/R3NjwkMcIWZAzpLB2G9/CA==", - "requires": { - "bindings": "~1.2.1", - "node-addon-api": "^1.6.0" - } - }, "debug": { "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", @@ -1123,28 +1124,27 @@ } }, "del": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/del/-/del-4.1.1.tgz", - "integrity": "sha512-QwGuEUouP2kVwQenAsOof5Fv8K9t3D8Ca8NxcXKrIpEHjTXK5J2nXLdP+ALI1cgv8wj7KuwBhTwBkOZSJKM5XQ==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/del/-/del-5.0.0.tgz", + "integrity": "sha512-TfU3nUY0WDIhN18eq+pgpbLY9AfL5RfiE9czKaTSolc6aK7qASXfDErvYgjV1UqCR4sNXDoxO0/idPmhDUt2Sg==", "requires": { - "@types/glob": "^7.1.1", - "globby": "^6.1.0", + "globby": "^10.0.0", "is-path-cwd": "^2.0.0", "is-path-in-cwd": "^2.0.0", "p-map": "^2.0.0", - "pify": "^4.0.1", "rimraf": "^2.6.3" } }, "delayed": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/delayed/-/delayed-1.0.1.tgz", - "integrity": "sha1-bMxRL1Mjbt8SSg/GYKA7G8bSEwY=" + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/delayed/-/delayed-2.0.0.tgz", + "integrity": "sha512-egVMG7oyLvt0hQTCFkk15ly065RcPYr4ePCjxETESVX5UJLe+kh9MGD8TFkDWHOhga/ljNPZE1AEM2/sL0KK0w==" }, "delayed-stream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=" + "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=", + "dev": true }, "depd": { "version": "1.1.2", @@ -1178,6 +1178,21 @@ "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==", "dev": true }, + "dir-glob": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", + "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", + "requires": { + "path-type": "^4.0.0" + }, + "dependencies": { + "path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==" + } + } + }, "dns-equal": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/dns-equal/-/dns-equal-1.0.0.tgz", @@ -1200,15 +1215,6 @@ "buffer-indexof": "^1.0.0" } }, - "ecc-jsbn": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", - "integrity": "sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=", - "requires": { - "jsbn": "~0.1.0", - "safer-buffer": "^2.1.0" - } - }, "ee-first": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", @@ -1237,6 +1243,7 @@ "version": "1.4.1", "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.1.tgz", "integrity": "sha512-1MkrZNvWTKCaigbn+W15elq2BB/L22nqrSY5DKlo3X6+vclJm8Bb5djXJBmEX6fS3+zCh/F4VBK5Z2KxJt4s2Q==", + "dev": true, "requires": { "once": "^1.4.0" } @@ -1247,6 +1254,11 @@ "integrity": "sha512-f2LZMYl1Fzu7YSBKg+RoROelpOaNrcGmE9AZubeDfrCEia483oW4MI4VyFd5VNHIgQ/7qm1I0wUHK1eJnn2y2w==", "dev": true }, + "env-paths": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.0.tgz", + "integrity": "sha512-6u0VYSCo/OW6IoD5WCLLy9JUGARbamfSavcNXry/eu8aHVFei6CD3Sw+VGX5alea1i9pgPHW0mbu6Xj0uBh7gA==" + }, "env-variable": { "version": "0.0.5", "resolved": "https://registry.npmjs.org/env-variable/-/env-variable-0.0.5.tgz", @@ -1316,9 +1328,9 @@ "dev": true }, "esutils": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.2.tgz", - "integrity": "sha1-Cr9PHKpbyx96nYrMbepPqqBLrJs=", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", "dev": true }, "etag": { @@ -1355,61 +1367,51 @@ } }, "expr-eval": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/expr-eval/-/expr-eval-1.2.2.tgz", - "integrity": "sha1-ixoWC4FOZ9p2UgB+JpNxSJUiHqA=" + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/expr-eval/-/expr-eval-2.0.1.tgz", + "integrity": "sha512-8RqKOoWUmMB1/UlQmiFnUTt/Esp177dSm/ATIff/w0M+PHK3uO+PBwC2+Q8mLyS9sYSRznlBTRl6D6iyUvtSTQ==" }, "express": { - "version": "4.16.4", - "resolved": "https://registry.npmjs.org/express/-/express-4.16.4.tgz", - "integrity": "sha512-j12Uuyb4FMrd/qQAm6uCHAkPtO8FDTRJZBDd5D2KOL2eLaz1yUNdUB/NOIyq0iU4q4cFarsUCrnFDPBcnksuOg==", + "version": "4.17.1", + "resolved": "https://registry.npmjs.org/express/-/express-4.17.1.tgz", + "integrity": "sha512-mHJ9O79RqluphRrcw2X/GTh3k9tVv8YcoyY4Kkh4WDMUYKRZUq0h1o0w2rrrxBqM7VoeUVqgb27xlEMXTnYt4g==", "requires": { - "accepts": "~1.3.5", + "accepts": "~1.3.7", "array-flatten": "1.1.1", - "body-parser": "1.18.3", - "content-disposition": "0.5.2", + "body-parser": "1.19.0", + "content-disposition": "0.5.3", "content-type": "~1.0.4", - "cookie": "0.3.1", + "cookie": "0.4.0", "cookie-signature": "1.0.6", "debug": "2.6.9", "depd": "~1.1.2", "encodeurl": "~1.0.2", "escape-html": "~1.0.3", "etag": "~1.8.1", - "finalhandler": "1.1.1", + "finalhandler": "~1.1.2", "fresh": "0.5.2", "merge-descriptors": "1.0.1", "methods": "~1.1.2", "on-finished": "~2.3.0", - "parseurl": "~1.3.2", + "parseurl": "~1.3.3", "path-to-regexp": "0.1.7", - "proxy-addr": "~2.0.4", - "qs": "6.5.2", - "range-parser": "~1.2.0", + "proxy-addr": "~2.0.5", + "qs": "6.7.0", + "range-parser": "~1.2.1", "safe-buffer": "5.1.2", - "send": "0.16.2", - "serve-static": "1.13.2", - "setprototypeof": "1.1.0", - "statuses": "~1.4.0", - "type-is": "~1.6.16", + "send": "0.17.1", + "serve-static": "1.14.1", + "setprototypeof": "1.1.1", + "statuses": "~1.5.0", + "type-is": "~1.6.18", "utils-merge": "1.0.1", "vary": "~1.1.2" }, "dependencies": { - "qs": { - "version": "6.5.2", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", - "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==" - }, "safe-buffer": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" - }, - "statuses": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.4.0.tgz", - "integrity": "sha512-zhSCtt8v2NDrRlPQpCNtw/heZLtfUDqxBM1udqikb/Hbk52LK4nQSwr10u77iopCW5LsyHpuXS0GnEc48mLeew==" } } }, @@ -1421,22 +1423,21 @@ "extend": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", - "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==" - }, - "extsprintf": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", - "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=" - }, - "fast-deep-equal": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz", - "integrity": "sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk=" + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", + "dev": true }, - "fast-json-stable-stringify": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz", - "integrity": "sha1-1RQsDK7msRifh9OnYREGT4bIu/I=" + "fast-glob": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.0.4.tgz", + "integrity": "sha512-wkIbV6qg37xTJwqSsdnIphL1e+LaGz4AIQqr00mIubMaEhv1/HEmJ0uuCGZRNRUkZZmOB5mJKO0ZUTVq+SxMQg==", + "requires": { + "@nodelib/fs.stat": "^2.0.1", + "@nodelib/fs.walk": "^1.2.1", + "glob-parent": "^5.0.0", + "is-glob": "^4.0.1", + "merge2": "^1.2.3", + "micromatch": "^4.0.2" + } }, "fast-safe-stringify": { "version": "2.0.6", @@ -1444,6 +1445,14 @@ "integrity": "sha512-q8BZ89jjc+mz08rSxROs8VsrBBcn1SIw1kq9NjolL509tkABRk9io01RAjSaEv1Xb2uFLt8VtRiZbGp5H8iDtg==", "dev": true }, + "fastq": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.6.0.tgz", + "integrity": "sha512-jmxqQ3Z/nXoeyDmWAzF9kH1aGZSis6e/SbfPmJpUnyZ0ogr6iscHQaml4wsEepEWSdtmpy+eVXmCRIMpxaXqOA==", + "requires": { + "reusify": "^1.0.0" + } + }, "fd-slicer": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz", @@ -1458,25 +1467,26 @@ "integrity": "sha512-lUGBnIamTAwk4znq5BcqsDaxSmZ9nDVJaij6NvRt/Tg4R69gERA+otPKbS86ROw9nxVMw2/mp1fnaiWqbs6Sdg==", "dev": true }, + "fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "requires": { + "to-regex-range": "^5.0.1" + } + }, "finalhandler": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.1.tgz", - "integrity": "sha512-Y1GUDo39ez4aHAw7MysnUD5JzYX+WaIj8I57kO3aEPT1fFRL4sr7mjei97FgnwhAyyzRYmQZaTHb2+9uZ1dPtg==", + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz", + "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==", "requires": { "debug": "2.6.9", "encodeurl": "~1.0.2", "escape-html": "~1.0.3", "on-finished": "~2.3.0", - "parseurl": "~1.3.2", - "statuses": "~1.4.0", + "parseurl": "~1.3.3", + "statuses": "~1.5.0", "unpipe": "~1.0.0" - }, - "dependencies": { - "statuses": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.4.0.tgz", - "integrity": "sha512-zhSCtt8v2NDrRlPQpCNtw/heZLtfUDqxBM1udqikb/Hbk52LK4nQSwr10u77iopCW5LsyHpuXS0GnEc48mLeew==" - } } }, "find-cache-dir": { @@ -1550,15 +1560,11 @@ } } }, - "forever-agent": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", - "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=" - }, "form-data": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", + "dev": true, "requires": { "asynckit": "^0.4.0", "combined-stream": "^1.0.6", @@ -1568,7 +1574,8 @@ "formidable": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/formidable/-/formidable-1.2.1.tgz", - "integrity": "sha512-Fs9VRguL0gqGHkXS5GQiMCr1VhZBxz0JnJs4JmMp/2jL18Fmbzvv7vOFRU+U8TBkHEE/CX1qDXzJplVULgsLeg==" + "integrity": "sha512-Fs9VRguL0gqGHkXS5GQiMCr1VhZBxz0JnJs4JmMp/2jL18Fmbzvv7vOFRU+U8TBkHEE/CX1qDXzJplVULgsLeg==", + "dev": true }, "forwarded": { "version": "0.1.2", @@ -1630,18 +1637,11 @@ "integrity": "sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ=", "dev": true }, - "getpass": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", - "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", - "requires": { - "assert-plus": "^1.0.0" - } - }, "glob": { "version": "7.1.2", "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==", + "dev": true, "requires": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", @@ -1651,6 +1651,14 @@ "path-is-absolute": "^1.0.0" } }, + "glob-parent": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.0.0.tgz", + "integrity": "sha512-Z2RwiujPRGluePM6j699ktJYxmPpJKCfpGA13jz2hmFZC7gKetzrWvg5KN3+OsIFmydGyZ1AVwERCq1w/ZZwRg==", + "requires": { + "is-glob": "^4.0.1" + } + }, "globals": { "version": "11.12.0", "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", @@ -1658,21 +1666,32 @@ "dev": true }, "globby": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/globby/-/globby-6.1.0.tgz", - "integrity": "sha1-9abXDoOV4hyFj7BInWTfAkJNUGw=", - "requires": { - "array-union": "^1.0.1", - "glob": "^7.0.3", - "object-assign": "^4.0.1", - "pify": "^2.0.0", - "pinkie-promise": "^2.0.0" + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/globby/-/globby-10.0.1.tgz", + "integrity": "sha512-sSs4inE1FB2YQiymcmTv6NWENryABjUNPeWhOvmn4SjtKybglsyPZxFB3U1/+L1bYi0rNZDqCLlHyLYDl1Pq5A==", + "requires": { + "@types/glob": "^7.1.1", + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.0.3", + "glob": "^7.1.3", + "ignore": "^5.1.1", + "merge2": "^1.2.3", + "slash": "^3.0.0" }, "dependencies": { - "pify": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", - "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=" + "glob": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.4.tgz", + "integrity": "sha512-hkLPepehmnKk41pUGm3sYxoFs/umurYfYJCerbXEyFIWcAzvpipAgVkBqqT9RBKMGjnq6kMuyYwha6csxbiM1A==", + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } } } }, @@ -1700,20 +1719,6 @@ "uglify-js": "^3.1.4" } }, - "har-schema": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", - "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=" - }, - "har-validator": { - "version": "5.1.3", - "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.3.tgz", - "integrity": "sha512-sNvOCzEQNr/qrvJgc3UG/kD4QtlHycrzwS+6mfTrrSq97BvaYcPZZI1ZSqGSPR73Cxn4LKTD4PttRwfU7jWq5g==", - "requires": { - "ajv": "^6.5.5", - "har-schema": "^2.0.0" - } - }, "has": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", @@ -1753,36 +1758,27 @@ "integrity": "sha512-+pMQUQCkl1DQ/Im/Y3wixzIr2j8+o8gRIcB3Dw5n5kcbPwPGEEde9hfX3HzRzR00pS/8NmSohSvYnhBs0jA4hw==" }, "highlight.js": { - "version": "9.15.6", - "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-9.15.6.tgz", - "integrity": "sha512-zozTAWM1D6sozHo8kqhfYgsac+B+q0PmsjXeyDrYIHHcBN0zTVT66+s2GW1GZv7DbyaROdLXKdabwS/WqPyIdQ==", + "version": "9.15.10", + "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-9.15.10.tgz", + "integrity": "sha512-RoV7OkQm0T3os3Dd2VHLNMoaoDVx77Wygln3n9l5YV172XonWG6rgQD3XnF/BuFFZw9A0TJgmMSO8FEWQgvcXw==", "dev": true }, "hosted-git-info": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.7.1.tgz", - "integrity": "sha512-7T/BxH19zbcCTa8XkMlbK5lTo1WtgkFi3GvdWEyNuc4Vex7/9Dqbnpsf4JMydcfj9HCg4zUWFTL3Za6lapg5/w==", + "version": "2.8.4", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.4.tgz", + "integrity": "sha512-pzXIvANXEFrc5oFFXRMkbLPQ2rXRoDERwDLyrcUxGhaZhgP54BBSl9Oheh7Vv0T090cszWBxPjkQQ5Sq1PbBRQ==", "dev": true }, "http-errors": { - "version": "1.6.3", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", - "integrity": "sha1-i1VoC7S+KDoLW/TqLjhYC+HZMg0=", + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.2.tgz", + "integrity": "sha512-uUQBt3H/cSIVfch6i1EuPNy/YsRSOUBXTVfZ+yR7Zjez3qjBz6i9+i4zjNaoqcoFVI4lQJ5plg63TvGfRSDCRg==", "requires": { "depd": "~1.1.2", "inherits": "2.0.3", - "setprototypeof": "1.1.0", - "statuses": ">= 1.4.0 < 2" - } - }, - "http-signature": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", - "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=", - "requires": { - "assert-plus": "^1.0.0", - "jsprim": "^1.2.2", - "sshpk": "^1.7.0" + "setprototypeof": "1.1.1", + "statuses": ">= 1.5.0 < 2", + "toidentifier": "1.0.0" } }, "humanize": { @@ -1794,11 +1790,15 @@ "version": "0.4.24", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", - "dev": true, "requires": { "safer-buffer": ">= 2.1.2 < 3" } }, + "ignore": { + "version": "5.1.4", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.1.4.tgz", + "integrity": "sha512-MzbUSahkTW1u7JpKKjY7LCARd1fU5W2rLdxlM4kdkayuCwZImjkpluF9CM1aLewYJguPDqewLam18Y6AU69A8A==" + }, "imurmurhash": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", @@ -1843,9 +1843,9 @@ "dev": true }, "ipaddr.js": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.8.0.tgz", - "integrity": "sha1-6qM9bd16zo9/b+DJygRA5wZzix4=" + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.0.tgz", + "integrity": "sha512-M4Sjn6N/+O6/IXSJseKqHoFc+5FdGJ22sXqnjTpdZweHK64MzEPAyQZyEU3R/KRv2GLoa7nNtg/C2Ev6m7z+eA==" }, "is-arrayish": { "version": "0.2.1", @@ -1869,11 +1869,24 @@ "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.1.tgz", "integrity": "sha1-mqIOtq7rv/d/vTPnTKAbM1gdOhY=" }, + "is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=" + }, "is-fullwidth-code-point": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=" }, + "is-glob": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz", + "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==", + "requires": { + "is-extglob": "^2.1.1" + } + }, "is-ip": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/is-ip/-/is-ip-2.0.0.tgz", @@ -1883,10 +1896,15 @@ "ip-regex": "^2.0.0" } }, + "is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==" + }, "is-path-cwd": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-path-cwd/-/is-path-cwd-2.1.0.tgz", - "integrity": "sha512-Sc5j3/YnM8tDeyCsVeKlm/0p95075DyLmDEIkSgQ7mXkrOX+uTCtmQFm0CYzVyJwcCCmO3k8qfJt17SxQwB5Zw==" + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/is-path-cwd/-/is-path-cwd-2.2.0.tgz", + "integrity": "sha512-w942bTcih8fdJPJmQHFzkS76NEP8Kzzvmw92cXsazb8intwLqPibPPdXf4ANdKV3rYMuuQYGIWtvz9JilB3NFQ==" }, "is-path-in-cwd": { "version": "2.1.0", @@ -1915,7 +1933,8 @@ "is-stream": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", - "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=" + "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=", + "dev": true }, "is-symbol": { "version": "1.0.2", @@ -1925,25 +1944,17 @@ "has-symbols": "^1.0.0" } }, - "is-typedarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", - "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=" - }, "isarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", + "dev": true }, "isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=" - }, - "isstream": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", - "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=" + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", + "dev": true }, "istanbul-lib-coverage": { "version": "2.0.5", @@ -1976,9 +1987,9 @@ }, "dependencies": { "semver": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.0.0.tgz", - "integrity": "sha512-0UewU+9rFapKFnlbirLi3byoOuhrSsli/z/ihNnvM24vgF+8sNBiI1LZPBSH9wJKUwaUbw+s3hToDLCXkrghrQ==", + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", "dev": true } } @@ -2028,22 +2039,27 @@ } }, "ms": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", - "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", "dev": true } } }, "istanbul-reports": { - "version": "2.2.4", - "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-2.2.4.tgz", - "integrity": "sha512-QCHGyZEK0bfi9GR215QSm+NJwFKEShbtc7tfbUdLAEzn3kKhLDDZqvljn8rPZM9v8CEOhzL1nlYoO4r1ryl67w==", + "version": "2.2.6", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-2.2.6.tgz", + "integrity": "sha512-SKi4rnMyLBKe0Jy2uUdx28h8oG7ph2PPuQPvIAh31d+Ci+lSiEu4C+h3oBPuJ9+mPKhOyW0M8gY4U5NM1WLeXA==", "dev": true, "requires": { "handlebars": "^4.1.2" } }, + "javascript-state-machine": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/javascript-state-machine/-/javascript-state-machine-3.1.0.tgz", + "integrity": "sha512-BwhYxQ1OPenBPXC735RgfB+ZUG8H3kjsx8hrYTgWnoy6TPipEy4fiicyhT2lxRKAXq9pG7CfFT8a2HLr6Hmwxg==" + }, "js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -2060,11 +2076,6 @@ "esprima": "^4.0.0" } }, - "jsbn": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", - "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=" - }, "jsesc": { "version": "2.5.2", "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", @@ -2076,21 +2087,6 @@ "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==" }, - "json-schema": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", - "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=" - }, - "json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" - }, - "json-stringify-safe": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", - "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=" - }, "jsonfile": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-3.0.1.tgz", @@ -2100,17 +2096,6 @@ "graceful-fs": "^4.1.6" } }, - "jsprim": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", - "integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=", - "requires": { - "assert-plus": "1.0.0", - "extsprintf": "1.3.0", - "json-schema": "0.2.3", - "verror": "1.10.0" - } - }, "jsrsasign": { "version": "8.0.12", "resolved": "https://registry.npmjs.org/jsrsasign/-/jsrsasign-8.0.12.tgz", @@ -2183,9 +2168,10 @@ } }, "lodash": { - "version": "4.17.11", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz", - "integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==" + "version": "4.17.14", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.14.tgz", + "integrity": "sha512-mmKYbW3GLuJeX+iGP+Y7Gp1AiGHGbXHCOh/jZmrawMmsE7MS4znI3RL2FsjbqOyMayHInjOeykW7PEajUk1/xw==", + "dev": true }, "lodash.camelcase": { "version": "4.3.0", @@ -2273,9 +2259,9 @@ }, "dependencies": { "semver": { - "version": "5.7.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.0.tgz", - "integrity": "sha512-Ya52jSX2u7QKghxeoFGpLwCtGlt7j0oY9DYb5apt9nPlJ42ID+ulTXESnt/qAQcoSERyZ5sl3LDIOw0nAn/5DA==", + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", "dev": true } } @@ -2290,6 +2276,7 @@ "version": "0.1.3", "resolved": "https://registry.npmjs.org/map-age-cleaner/-/map-age-cleaner-0.1.3.tgz", "integrity": "sha512-bJzx6nMoP6PDLPBFmg7+xRKeFZvFboMrGlxmNj9ClvX53KrmvM5bXFXEWjbz4cz1AFn+jWJ9z/DJSz7hrs0w3w==", + "dev": true, "requires": { "p-defer": "^1.0.0" } @@ -2309,7 +2296,7 @@ }, "marked": { "version": "0.4.0", - "resolved": "https://registry.plt.et.tu-dresden.de:4873/marked/-/marked-0.4.0.tgz", + "resolved": "https://registry.npmjs.org/marked/-/marked-0.4.0.tgz", "integrity": "sha512-tMsdNBgOsrUophCAFQl0XPe6Zqk/uy9gnue+jIIKhykO51hxyu6uNx7zBPy0+y/WKYVZZMspV9YeXLNdKk+iYw==", "dev": true }, @@ -2347,25 +2334,42 @@ "source-map": "^0.6.1" } }, + "merge2": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.2.4.tgz", + "integrity": "sha512-FYE8xI+6pjFOhokZu0We3S5NKCirLbCzSh2Usf3qEyr4X8U+0jNg9P8RZ4qz+V2UoECLVwSyzU3LxXBaLGtD3A==" + }, "methods": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=" }, + "micromatch": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.2.tgz", + "integrity": "sha512-y7FpHSbMUMoyPbYUSzO6PaZ6FyRnQOpHuKwbo1G+Knck95XVU4QAiKdGEnj5wwoS7PlOgthX/09u5iFJ+aYf5Q==", + "requires": { + "braces": "^3.0.1", + "picomatch": "^2.0.5" + } + }, "mime": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/mime/-/mime-1.4.1.tgz", - "integrity": "sha512-KI1+qOZu5DcW6wayYHSzR/tXKCDC5Om4s1z2QJjDULzLcmf3DvzS7oluY4HCTrc+9FiKmWUgeNLg7W3uIQvxtQ==" + "integrity": "sha512-KI1+qOZu5DcW6wayYHSzR/tXKCDC5Om4s1z2QJjDULzLcmf3DvzS7oluY4HCTrc+9FiKmWUgeNLg7W3uIQvxtQ==", + "dev": true }, "mime-db": { "version": "1.35.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.35.0.tgz", - "integrity": "sha512-JWT/IcCTsB0Io3AhWUMjRqucrHSPsSf2xKLaRldJVULioggvkJvggZ3VXNNSRkCddE6D+BUI4HEIZIA2OjwIvg==" + "integrity": "sha512-JWT/IcCTsB0Io3AhWUMjRqucrHSPsSf2xKLaRldJVULioggvkJvggZ3VXNNSRkCddE6D+BUI4HEIZIA2OjwIvg==", + "dev": true }, "mime-types": { "version": "2.1.19", "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.19.tgz", "integrity": "sha512-P1tKYHVSZ6uFo26mtnve4HQFE3koh1UWVkp8YUC+ESBHe945xWSoXuHHiGarDqcEZ+whpCDnlNw5LON0kLo+sw==", + "dev": true, "requires": { "mime-db": "~1.35.0" } @@ -2387,22 +2391,20 @@ "minimist": { "version": "0.0.8", "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", - "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", - "dev": true + "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=" }, "mkdirp": { "version": "0.5.1", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", - "dev": true, "requires": { "minimist": "0.0.8" } }, "mocha": { - "version": "6.1.4", - "resolved": "https://registry.npmjs.org/mocha/-/mocha-6.1.4.tgz", - "integrity": "sha512-PN8CIy4RXsIoxoFJzS4QNnCH4psUCPWc4/rPrst/ecSJJbLBkubMiyGCP2Kj/9YnWbotFqAoeXyXMucj7gwCFg==", + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-6.2.0.tgz", + "integrity": "sha512-qwfFgY+7EKAAUAdv7VYMZQknI7YJSGesxHyhn6qD52DV8UcSZs5XwCifcZGMVIE4a5fbmhvbotxC0DLQ0oKohQ==", "dev": true, "requires": { "ansi-colors": "3.2.3", @@ -2558,9 +2560,9 @@ } }, "p-limit": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.2.0.tgz", - "integrity": "sha512-pZbTJpoUsCzV48Mc9Nh51VbwO0X9cuPFE8gYwx9BTCt9SF8/b7Zljd2fVgOxhIF/HDTKgpVzs+GPhyKfjLLFRQ==", + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.2.1.tgz", + "integrity": "sha512-85Tk+90UCVWvbDavCLKPOLC9vvY8OwEX/RtKF+/1OADJMVlFfEHOiMTPVyxg7mk/dKa+ipdHm0OUkTvCpMTuwg==", "dev": true, "requires": { "p-try": "^2.0.0" @@ -2698,9 +2700,9 @@ "integrity": "sha1-iZ8R2WhuXgXLkbNdXw5jt3PPyQE=" }, "negotiator": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.1.tgz", - "integrity": "sha1-KzJxhOiZIQEXeyhWP7XnECrNDKk=" + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz", + "integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw==" }, "neo-async": { "version": "2.6.0", @@ -2717,12 +2719,8 @@ "nice-try": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.4.tgz", - "integrity": "sha512-2NpiFHqC87y/zFke0fC0spBXL3bBsoh/p5H1EFhshxjCR5+0g2d6BiXbUFz9v1sAcxsk2htp2eQnNIci2dIYcA==" - }, - "node-addon-api": { - "version": "1.6.3", - "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-1.6.3.tgz", - "integrity": "sha512-FXWH6mqjWgU8ewuahp4spec8LkroFZK2NicOv6bNwZC3kcwZUI8LeZdG80UzTSLLhK4T7MsgNwlYDVRlDdfTDg==" + "integrity": "sha512-2NpiFHqC87y/zFke0fC0spBXL3bBsoh/p5H1EFhshxjCR5+0g2d6BiXbUFz9v1sAcxsk2htp2eQnNIci2dIYcA==", + "dev": true }, "node-environment-flags": { "version": "1.0.5", @@ -2735,112 +2733,112 @@ }, "dependencies": { "semver": { - "version": "5.7.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.0.tgz", - "integrity": "sha512-Ya52jSX2u7QKghxeoFGpLwCtGlt7j0oY9DYb5apt9nPlJ42ID+ulTXESnt/qAQcoSERyZ5sl3LDIOw0nAn/5DA==", + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", "dev": true } } }, - "node-fqdn": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/node-fqdn/-/node-fqdn-1.1.1.tgz", - "integrity": "sha1-/0jWyXfEEtMOV//flbuRPUifbRY=", - "requires": { - "deasync": "^0.1.7" - } - }, "node-opcua": { - "version": "0.7.2", - "resolved": "https://registry.npmjs.org/node-opcua/-/node-opcua-0.7.2.tgz", - "integrity": "sha512-3q3ziufdE6cgz02L9r6chY4IlAiirFuAh9D7jwQOhYNsaoo+LKeYWqy+UC+ft7zkg/HlvvuyHy1fejcfKarJpw==", + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/node-opcua/-/node-opcua-2.1.5.tgz", + "integrity": "sha512-xOfZ9zg/DVvG8YEqiWUYO88S40vPZuTNAnJbC9OLOZiZQYKuvxKAdNTOPXcLhBL1NZ+PdXRI0ClBfxWZqy0QOw==", "requires": { - "colors": "^1.3.3", - "node-opcua-address-space": "^0.7.2", - "node-opcua-address-space-for-conformance-testing": "^0.7.2", - "node-opcua-assert": "^0.7.0", - "node-opcua-basic-types": "^0.7.2", - "node-opcua-client": "^0.7.2", - "node-opcua-client-crawler": "^0.7.2", - "node-opcua-client-proxy": "^0.7.2", - "node-opcua-common": "^0.7.2", - "node-opcua-constants": "^0.7.0", - "node-opcua-crypto": "^1.0.3", - "node-opcua-data-access": "^0.7.2", - "node-opcua-data-model": "^0.7.2", - "node-opcua-data-value": "^0.7.2", - "node-opcua-debug": "^0.7.2", - "node-opcua-enum": "^0.7.2", - "node-opcua-factory": "^0.7.2", - "node-opcua-hostname": "^0.7.0", - "node-opcua-nodeid": "^0.7.2", - "node-opcua-nodesets": "^0.7.0", - "node-opcua-numeric-range": "^0.7.2", - "node-opcua-packet-analyzer": "^0.7.2", - "node-opcua-secure-channel": "^0.7.2", - "node-opcua-server": "^0.7.2", - "node-opcua-server-discovery": "^0.7.2", - "node-opcua-service-browse": "^0.7.2", - "node-opcua-service-call": "^0.7.2", - "node-opcua-service-discovery": "^0.7.2", - "node-opcua-service-endpoints": "^0.7.2", - "node-opcua-service-filter": "^0.7.2", - "node-opcua-service-history": "^0.7.2", - "node-opcua-service-node-management": "^0.7.2", - "node-opcua-service-query": "^0.7.2", - "node-opcua-service-read": "^0.7.2", - "node-opcua-service-register-node": "^0.7.2", - "node-opcua-service-secure-channel": "^0.7.2", - "node-opcua-service-session": "^0.7.2", - "node-opcua-service-subscription": "^0.7.2", - "node-opcua-service-translate-browse-path": "^0.7.2", - "node-opcua-service-write": "^0.7.2", - "node-opcua-status-code": "^0.7.2", - "node-opcua-transport": "^0.7.2", - "node-opcua-utils": "^0.7.2", - "node-opcua-variant": "^0.7.2", - "node-opcua-vendor-diagnostic": "^0.7.2", - "semver": "^6.0.0" + "chalk": "^2.4.2", + "node-opcua-address-space": "^2.1.5", + "node-opcua-address-space-for-conformance-testing": "^2.1.5", + "node-opcua-assert": "^2.0.0", + "node-opcua-basic-types": "^2.1.5", + "node-opcua-certificate-manager": "^2.1.5", + "node-opcua-client": "^2.1.5", + "node-opcua-client-crawler": "^2.1.5", + "node-opcua-client-proxy": "^2.1.5", + "node-opcua-common": "^2.1.5", + "node-opcua-constants": "^2.1.5", + "node-opcua-crypto": "^1.1.2", + "node-opcua-data-access": "^2.1.5", + "node-opcua-data-model": "^2.1.5", + "node-opcua-data-value": "^2.1.5", + "node-opcua-debug": "^2.1.0", + "node-opcua-enum": "^2.1.0", + "node-opcua-factory": "^2.1.5", + "node-opcua-hostname": "^2.1.0", + "node-opcua-nodeid": "^2.1.5", + "node-opcua-nodesets": "^2.1.5", + "node-opcua-numeric-range": "^2.1.5", + "node-opcua-packet-analyzer": "^2.1.5", + "node-opcua-secure-channel": "^2.1.5", + "node-opcua-server": "^2.1.5", + "node-opcua-server-discovery": "^2.1.5", + "node-opcua-service-browse": "^2.1.5", + "node-opcua-service-call": "^2.1.5", + "node-opcua-service-discovery": "^2.1.5", + "node-opcua-service-endpoints": "^2.1.5", + "node-opcua-service-filter": "^2.1.5", + "node-opcua-service-history": "^2.1.5", + "node-opcua-service-node-management": "^2.1.5", + "node-opcua-service-query": "^2.1.5", + "node-opcua-service-read": "^2.1.5", + "node-opcua-service-register-node": "^2.1.5", + "node-opcua-service-secure-channel": "^2.1.5", + "node-opcua-service-session": "^2.1.5", + "node-opcua-service-subscription": "^2.1.5", + "node-opcua-service-translate-browse-path": "^2.1.5", + "node-opcua-service-write": "^2.1.5", + "node-opcua-status-code": "^2.1.5", + "node-opcua-transport": "^2.1.5", + "node-opcua-types": "^2.1.5", + "node-opcua-utils": "^2.1.5", + "node-opcua-variant": "^2.1.5", + "node-opcua-vendor-diagnostic": "^2.1.5", + "semver": "^6.3.0" }, "dependencies": { "semver": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.0.0.tgz", - "integrity": "sha512-0UewU+9rFapKFnlbirLi3byoOuhrSsli/z/ihNnvM24vgF+8sNBiI1LZPBSH9wJKUwaUbw+s3hToDLCXkrghrQ==" + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==" } } }, "node-opcua-address-space": { - "version": "0.7.2", - "resolved": "https://registry.npmjs.org/node-opcua-address-space/-/node-opcua-address-space-0.7.2.tgz", - "integrity": "sha512-oASdd4eorjPp9NhyfU+iA/tCcbMf1AZlWe+lAUjrP2MeWXiWhyDrwWFzDwiDvk7xDVZrzVNDm3+drUGCLBr9ng==", + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/node-opcua-address-space/-/node-opcua-address-space-2.1.5.tgz", + "integrity": "sha512-fcgm8PyMi/g0buJhhj9wQnekzoh0m+H6k7iOBT7BWCGP8pr13n2t5uSgFp5aKNVNuT8PdPh5gFGcv8JTOZqgtw==", "requires": { - "async": "^2.6.2", + "async": "^3.1.0", "dequeue": "^1.0.5", - "node-opcua-assert": "^0.7.0", - "node-opcua-basic-types": "^0.7.2", - "node-opcua-common": "^0.7.2", - "node-opcua-constants": "^0.7.0", - "node-opcua-data-access": "^0.7.2", - "node-opcua-data-model": "^0.7.2", - "node-opcua-data-value": "^0.7.2", - "node-opcua-date-time": "^0.7.2", - "node-opcua-debug": "^0.7.2", - "node-opcua-enum": "^0.7.2", - "node-opcua-factory": "^0.7.2", - "node-opcua-nodeid": "^0.7.2", - "node-opcua-numeric-range": "^0.7.2", - "node-opcua-object-registry": "^0.7.2", - "node-opcua-service-browse": "^0.7.2", - "node-opcua-service-call": "^0.7.2", - "node-opcua-service-filter": "^0.7.2", - "node-opcua-service-history": "^0.7.2", - "node-opcua-service-translate-browse-path": "^0.7.2", - "node-opcua-service-write": "^0.7.2", - "node-opcua-status-code": "^0.7.2", - "node-opcua-utils": "^0.7.2", - "node-opcua-variant": "^0.7.2", - "node-opcua-xml2json": "^0.7.0", + "node-opcua-assert": "^2.0.0", + "node-opcua-basic-types": "^2.1.5", + "node-opcua-client-dynamic-extension-object": "^2.1.5", + "node-opcua-common": "^2.1.5", + "node-opcua-constants": "^2.1.5", + "node-opcua-crypto": "^1.1.2", + "node-opcua-data-access": "^2.1.5", + "node-opcua-data-model": "^2.1.5", + "node-opcua-data-value": "^2.1.5", + "node-opcua-date-time": "^2.1.5", + "node-opcua-debug": "^2.1.0", + "node-opcua-enum": "^2.1.0", + "node-opcua-factory": "^2.1.5", + "node-opcua-nodeid": "^2.1.5", + "node-opcua-numeric-range": "^2.1.5", + "node-opcua-object-registry": "^2.1.0", + "node-opcua-pseudo-session": "^2.1.5", + "node-opcua-schemas": "^2.1.5", + "node-opcua-service-browse": "^2.1.5", + "node-opcua-service-call": "^2.1.5", + "node-opcua-service-filter": "^2.1.5", + "node-opcua-service-history": "^2.1.5", + "node-opcua-service-session": "^2.1.5", + "node-opcua-service-translate-browse-path": "^2.1.5", + "node-opcua-service-write": "^2.1.5", + "node-opcua-status-code": "^2.1.5", + "node-opcua-types": "^2.1.5", + "node-opcua-utils": "^2.1.5", + "node-opcua-variant": "^2.1.5", + "node-opcua-xml2json": "^2.1.5", "object.values": "^1.1.0", "set-prototype-of": "^1.0.0", "underscore": "^1.9.1", @@ -2848,221 +2846,290 @@ }, "dependencies": { "async": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/async/-/async-2.6.2.tgz", - "integrity": "sha512-H1qVYh1MYhEEFLsP97cVKqCGo7KfCyTt6uEWqsTBr9SO84oK9Uwbyd/yCW+6rKJLHksBNUVWZDAjfS+Ccx0Bbg==", - "requires": { - "lodash": "^4.17.11" - } + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/async/-/async-3.1.0.tgz", + "integrity": "sha512-4vx/aaY6j/j3Lw3fbCHNWP0pPaTCew3F6F3hYyl/tHs/ndmV1q7NW9T5yuJ2XAGwdQrP+6Wu20x06U4APo/iQQ==" } } }, "node-opcua-address-space-for-conformance-testing": { - "version": "0.7.2", - "resolved": "https://registry.npmjs.org/node-opcua-address-space-for-conformance-testing/-/node-opcua-address-space-for-conformance-testing-0.7.2.tgz", - "integrity": "sha512-HrL5bGnxJGfvYtqxKUJggojUJGIlolzBsKMhYtNwa9B+X2ajFISiFzd0ntE9ihjG8SIbkaor1a06fKaDjaKObg==", - "requires": { - "node-opcua-address-space": "^0.7.2", - "node-opcua-assert": "^0.7.0", - "node-opcua-basic-types": "^0.7.2", - "node-opcua-data-access": "^0.7.2", - "node-opcua-data-model": "^0.7.2", - "node-opcua-data-value": "^0.7.2", - "node-opcua-factory": "^0.7.2", - "node-opcua-nodeid": "^0.7.2", - "node-opcua-status-code": "^0.7.2", - "node-opcua-variant": "^0.7.2", + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/node-opcua-address-space-for-conformance-testing/-/node-opcua-address-space-for-conformance-testing-2.1.5.tgz", + "integrity": "sha512-sSJ93nagbKxIpSfIfHS63UJJzva23DS38P7micm6kOogHZnHOO92qa6P6FXXBBFqH9NctbeyUgsIKExn17Adig==", + "requires": { + "node-opcua-address-space": "^2.1.5", + "node-opcua-assert": "^2.0.0", + "node-opcua-basic-types": "^2.1.5", + "node-opcua-data-access": "^2.1.5", + "node-opcua-data-model": "^2.1.5", + "node-opcua-data-value": "^2.1.5", + "node-opcua-factory": "^2.1.5", + "node-opcua-nodeid": "^2.1.5", + "node-opcua-status-code": "^2.1.5", + "node-opcua-variant": "^2.1.5", "underscore": "^1.9.1" } }, "node-opcua-assert": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/node-opcua-assert/-/node-opcua-assert-0.7.0.tgz", - "integrity": "sha512-E19U63kOIWj2cw3ta0XbtoxYUkbAkUUaD6Duw1r5v6vkHiVrZin+e23Ra+m7stcXZ+Kx5u/Bj0BVHgOQKQ9ZbQ==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/node-opcua-assert/-/node-opcua-assert-2.0.0.tgz", + "integrity": "sha512-xY11k0/qZc4JieLbXm1NNjwiCwnTdhDW3dXFgJVvAZxNO+3B7w6/qQIZ3Elej3EBmMQ9MXIQrWzVppBAE4c6aw==", "requires": { - "better-assert": "^1.0.2", + "better-assert": "1.0.2", "chalk": "^2.4.2" } }, "node-opcua-basic-types": { - "version": "0.7.2", - "resolved": "https://registry.npmjs.org/node-opcua-basic-types/-/node-opcua-basic-types-0.7.2.tgz", - "integrity": "sha512-+ccg30xqriUS7Z4RMbXXLcaSWfYerDvRIku6Hmt5YvTu1MV0WbSd41xHiUw8Ly4CMiIvusMNawr8+LaujLTsTw==", - "requires": { - "node-opcua-assert": "^0.7.0", - "node-opcua-binary-stream": "^0.7.2", - "node-opcua-buffer-utils": "^0.7.2", - "node-opcua-date-time": "^0.7.2", - "node-opcua-enum": "^0.7.2", - "node-opcua-guid": "^0.7.0", - "node-opcua-nodeid": "^0.7.2", - "node-opcua-status-code": "^0.7.2", - "node-opcua-utils": "^0.7.2", + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/node-opcua-basic-types/-/node-opcua-basic-types-2.1.5.tgz", + "integrity": "sha512-CyPA3t741fOz4IEwqWdiU5arphd8MCtOhepCMDiFUrsHR4WFivRUVD73dVFvQ8GF2lNgdqhC5pjaNzK7W4NjvA==", + "requires": { + "node-opcua-assert": "^2.0.0", + "node-opcua-binary-stream": "^2.1.0", + "node-opcua-buffer-utils": "^2.1.0", + "node-opcua-date-time": "^2.1.5", + "node-opcua-enum": "^2.1.0", + "node-opcua-guid": "^2.1.0", + "node-opcua-nodeid": "^2.1.5", + "node-opcua-status-code": "^2.1.5", + "node-opcua-utils": "^2.1.5", "underscore": "^1.9.1" } }, "node-opcua-binary-stream": { - "version": "0.7.2", - "resolved": "https://registry.npmjs.org/node-opcua-binary-stream/-/node-opcua-binary-stream-0.7.2.tgz", - "integrity": "sha512-EHSe/Wcra8AmXX/MG2BLCO9Tnt2VRvTdDV1BG3tVlnHi2ScOKuhvLaYDhSIlb9zqzDmTv+QeQYty1TcoNuDk2A==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/node-opcua-binary-stream/-/node-opcua-binary-stream-2.1.0.tgz", + "integrity": "sha512-XoqXosF4iDx7vtbeAwlrB1a8DIQLyfaStIfb2XPSthBt/y+idF3hmSIksGxYwZbTy5SbqjVfsm3WbHqfS376Bg==", "requires": { "colors": "^1.3.3", - "node-opcua-assert": "^0.7.0", - "node-opcua-buffer-utils": "^0.7.2", + "node-opcua-assert": "^2.0.0", + "node-opcua-buffer-utils": "^2.1.0", "underscore": "^1.9.1" } }, "node-opcua-buffer-utils": { - "version": "0.7.2", - "resolved": "https://registry.npmjs.org/node-opcua-buffer-utils/-/node-opcua-buffer-utils-0.7.2.tgz", - "integrity": "sha512-+aoRn8y9n6LNDCJ4j6ISRgpwTabE9FemtmiNttOBWZG4iSHH6RmkRy1tJQVlDZ66nvg9SlrDzGQOH2Bwg7PNTA==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/node-opcua-buffer-utils/-/node-opcua-buffer-utils-2.1.0.tgz", + "integrity": "sha512-PHUhzjt6B93xk24dsQzudteGwcIbC1Ty09ZfEFEhgaEd0/jckulSm1UuoazYcLusvjPpCRcA91yx+kL3a8Gj9w==", "requires": { - "node-opcua-assert": "^0.7.0" + "node-opcua-assert": "^2.0.0" + } + }, + "node-opcua-certificate-manager": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/node-opcua-certificate-manager/-/node-opcua-certificate-manager-2.1.5.tgz", + "integrity": "sha512-120FlZtS8afrCzMUuFP6TFD6xF+yF7m4E6ft7lrRfYZvYoZUpLSNdVou1pPp3wd8DmKEkTvHvEFz7IE5KI+Mzg==", + "requires": { + "@types/mkdirp": "^0.5.2", + "async": "^3.1.0", + "chalk": "^2.4.2", + "delayed": "^2.0.0", + "env-paths": "^2.2.0", + "mkdirp": "^0.5.1", + "node-opcua-assert": "^2.0.0", + "node-opcua-basic-types": "^2.1.5", + "node-opcua-buffer-utils": "^2.1.0", + "node-opcua-common": "^2.1.5", + "node-opcua-constants": "^2.1.5", + "node-opcua-crypto": "^1.1.2", + "node-opcua-debug": "^2.1.0", + "node-opcua-pki": "^1.5.3", + "node-opcua-status-code": "^2.1.5", + "node-opcua-utils": "^2.1.5", + "once": "^1.4.0", + "thenify": "^3.3.0", + "underscore": "^1.9.1" + }, + "dependencies": { + "async": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/async/-/async-3.1.0.tgz", + "integrity": "sha512-4vx/aaY6j/j3Lw3fbCHNWP0pPaTCew3F6F3hYyl/tHs/ndmV1q7NW9T5yuJ2XAGwdQrP+6Wu20x06U4APo/iQQ==" + } } }, "node-opcua-chunkmanager": { - "version": "0.7.2", - "resolved": "https://registry.npmjs.org/node-opcua-chunkmanager/-/node-opcua-chunkmanager-0.7.2.tgz", - "integrity": "sha512-b4W/YJrpaskZjuh1Z6hc1uu42akPjpTEVjqmEjA4wnxzeSe4wR7xYxGxrs5roFT7ctOnOCfNHMnNUbP0caHelA==", + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/node-opcua-chunkmanager/-/node-opcua-chunkmanager-2.1.5.tgz", + "integrity": "sha512-oC+1RZCj4qoP+kwQcSCRAO+PWfCnuTsdJFsj2YUHSxjQFwajV18NrYGMM1xX5HRG+Jgok+8FQp6qli3AHriDVQ==", "requires": { - "node-opcua-assert": "^0.7.0", - "node-opcua-binary-stream": "^0.7.2", - "node-opcua-buffer-utils": "^0.7.2", + "chalk": "^2.4.2", + "node-opcua-assert": "^2.0.0", + "node-opcua-basic-types": "^2.1.5", + "node-opcua-binary-stream": "^2.1.0", + "node-opcua-buffer-utils": "^2.1.0", + "node-opcua-factory": "^2.1.5", + "node-opcua-packet-assembler": "^2.0.0", "underscore": "^1.9.1" } }, "node-opcua-client": { - "version": "0.7.2", - "resolved": "https://registry.npmjs.org/node-opcua-client/-/node-opcua-client-0.7.2.tgz", - "integrity": "sha512-xs9F2kGCNbACAJzRkdVkaqOMbzx0Hngc4rbddHP0aBvIJhy422rA8SCDUDr6t33ftM19kp2kGObCWAkfJgk1eA==", - "requires": { - "async": "^2.6.2", - "delayed": "^1.0.1", - "node-opcua-assert": "^0.7.0", - "node-opcua-buffer-utils": "^0.7.2", - "node-opcua-common": "^0.7.2", - "node-opcua-constants": "^0.7.0", - "node-opcua-crypto": "^1.0.3", - "node-opcua-data-model": "^0.7.2", - "node-opcua-data-value": "^0.7.2", - "node-opcua-debug": "^0.7.2", - "node-opcua-hostname": "^0.7.0", - "node-opcua-nodeid": "^0.7.2", - "node-opcua-object-registry": "^0.7.2", - "node-opcua-pki": "1.2.3", - "node-opcua-secure-channel": "^0.7.2", - "node-opcua-service-browse": "^0.7.2", - "node-opcua-service-call": "^0.7.2", - "node-opcua-service-discovery": "^0.7.2", - "node-opcua-service-endpoints": "^0.7.2", - "node-opcua-service-history": "^0.7.2", - "node-opcua-service-query": "^0.7.2", - "node-opcua-service-read": "^0.7.2", - "node-opcua-service-register-node": "^0.7.2", - "node-opcua-service-secure-channel": "^0.7.2", - "node-opcua-service-session": "^0.7.2", - "node-opcua-service-subscription": "^0.7.2", - "node-opcua-service-translate-browse-path": "^0.7.2", - "node-opcua-service-write": "^0.7.2", - "node-opcua-status-code": "^0.7.2", - "node-opcua-utils": "^0.7.2", - "node-opcua-variant": "^0.7.2", + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/node-opcua-client/-/node-opcua-client-2.1.5.tgz", + "integrity": "sha512-ai9B4Ys8rOIuMPzOclv6aim2IVU0z85Pui1ryAPvk8hsGFJq6EmAAQ37WpgMyRdW4qDSVm8JR4CiWL+ep4/Snw==", + "requires": { + "@types/async": "^3.0.1", + "@types/once": "^1.4.0", + "@types/underscore": "^1.9.2", + "async": "^3.1.0", + "callbackify": "^1.1.0", + "chalk": "^2.4.2", + "delayed": "^2.0.0", + "node-opcua-assert": "^2.0.0", + "node-opcua-basic-types": "^2.1.5", + "node-opcua-buffer-utils": "^2.1.0", + "node-opcua-certificate-manager": "^2.1.5", + "node-opcua-client-dynamic-extension-object": "^2.1.5", + "node-opcua-common": "^2.1.5", + "node-opcua-constants": "^2.1.5", + "node-opcua-crypto": "^1.1.2", + "node-opcua-data-model": "^2.1.5", + "node-opcua-data-value": "^2.1.5", + "node-opcua-date-time": "^2.1.5", + "node-opcua-debug": "^2.1.0", + "node-opcua-extension-object": "^2.1.5", + "node-opcua-hostname": "^2.1.0", + "node-opcua-nodeid": "^2.1.5", + "node-opcua-object-registry": "^2.1.0", + "node-opcua-pki": "^1.5.3", + "node-opcua-pseudo-session": "^2.1.5", + "node-opcua-schemas": "^2.1.5", + "node-opcua-secure-channel": "^2.1.5", + "node-opcua-service-browse": "^2.1.5", + "node-opcua-service-call": "^2.1.5", + "node-opcua-service-discovery": "^2.1.5", + "node-opcua-service-endpoints": "^2.1.5", + "node-opcua-service-filter": "^2.1.5", + "node-opcua-service-history": "^2.1.5", + "node-opcua-service-query": "^2.1.5", + "node-opcua-service-read": "^2.1.5", + "node-opcua-service-register-node": "^2.1.5", + "node-opcua-service-secure-channel": "^2.1.5", + "node-opcua-service-session": "^2.1.5", + "node-opcua-service-subscription": "^2.1.5", + "node-opcua-service-translate-browse-path": "^2.1.5", + "node-opcua-service-write": "^2.1.5", + "node-opcua-status-code": "^2.1.5", + "node-opcua-types": "^2.1.5", + "node-opcua-utils": "^2.1.5", + "node-opcua-variant": "^2.1.5", "once": "^1.4.0", "thenify": "^3.3.0", "underscore": "^1.9.1" }, "dependencies": { "async": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/async/-/async-2.6.2.tgz", - "integrity": "sha512-H1qVYh1MYhEEFLsP97cVKqCGo7KfCyTt6uEWqsTBr9SO84oK9Uwbyd/yCW+6rKJLHksBNUVWZDAjfS+Ccx0Bbg==", - "requires": { - "lodash": "^4.17.11" - } + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/async/-/async-3.1.0.tgz", + "integrity": "sha512-4vx/aaY6j/j3Lw3fbCHNWP0pPaTCew3F6F3hYyl/tHs/ndmV1q7NW9T5yuJ2XAGwdQrP+6Wu20x06U4APo/iQQ==" } } }, "node-opcua-client-crawler": { - "version": "0.7.2", - "resolved": "https://registry.npmjs.org/node-opcua-client-crawler/-/node-opcua-client-crawler-0.7.2.tgz", - "integrity": "sha512-Zy8JJc5ikIY9YOFJZrNMVPuHqvcJ9ig4MWBneAbYuIWsXBpjVrwWFMq/syrojG/i32gpTl6erd7wNGuvknmgtw==", - "requires": { - "async": "^2.6.2", - "node-opcua-assert": "^0.7.0", - "node-opcua-constants": "^0.7.0", - "node-opcua-data-model": "^0.7.2", - "node-opcua-debug": "^0.7.2", - "node-opcua-nodeid": "^0.7.2", - "node-opcua-service-browse": "^0.7.2", - "node-opcua-status-code": "^0.7.2", - "node-opcua-utils": "^0.7.2", + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/node-opcua-client-crawler/-/node-opcua-client-crawler-2.1.5.tgz", + "integrity": "sha512-kt1F8tiY/+rn294kce8XfuxcuGWSg3EnmrqwQHXr9ib3067IkMmNkw6dejz2SSc8qf7YH919uIH3D7ikgE2T/w==", + "requires": { + "async": "^3.1.0", + "node-opcua-assert": "^2.0.0", + "node-opcua-common": "^2.1.5", + "node-opcua-constants": "^2.1.5", + "node-opcua-data-model": "^2.1.5", + "node-opcua-data-value": "^2.1.5", + "node-opcua-debug": "^2.1.0", + "node-opcua-factory": "^2.1.5", + "node-opcua-nodeid": "^2.1.5", + "node-opcua-service-browse": "^2.1.5", + "node-opcua-status-code": "^2.1.5", + "node-opcua-utils": "^2.1.5", + "node-opcua-variant": "^2.1.5", "underscore": "^1.9.1" }, "dependencies": { "async": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/async/-/async-2.6.2.tgz", - "integrity": "sha512-H1qVYh1MYhEEFLsP97cVKqCGo7KfCyTt6uEWqsTBr9SO84oK9Uwbyd/yCW+6rKJLHksBNUVWZDAjfS+Ccx0Bbg==", - "requires": { - "lodash": "^4.17.11" - } + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/async/-/async-3.1.0.tgz", + "integrity": "sha512-4vx/aaY6j/j3Lw3fbCHNWP0pPaTCew3F6F3hYyl/tHs/ndmV1q7NW9T5yuJ2XAGwdQrP+6Wu20x06U4APo/iQQ==" } } }, + "node-opcua-client-dynamic-extension-object": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/node-opcua-client-dynamic-extension-object/-/node-opcua-client-dynamic-extension-object-2.1.5.tgz", + "integrity": "sha512-zUivV16BEDOZSTcHy+zRWFFCVec5co2zBJGUnQay4binE83HCuVgu6ogIrXCXb6T0TdlLxqWtsmI1iqsMVhVjQ==", + "requires": { + "chalk": "^2.4.2", + "node-opcua-assert": "^2.0.0", + "node-opcua-binary-stream": "^2.1.0", + "node-opcua-data-model": "^2.1.5", + "node-opcua-data-value": "^2.1.5", + "node-opcua-debug": "^2.1.0", + "node-opcua-extension-object": "^2.1.5", + "node-opcua-factory": "^2.1.5", + "node-opcua-nodeid": "^2.1.5", + "node-opcua-pseudo-session": "^2.1.5", + "node-opcua-schemas": "^2.1.5", + "node-opcua-service-browse": "^2.1.5", + "node-opcua-status-code": "^2.1.5", + "node-opcua-variant": "^2.1.5" + } + }, "node-opcua-client-proxy": { - "version": "0.7.2", - "resolved": "https://registry.npmjs.org/node-opcua-client-proxy/-/node-opcua-client-proxy-0.7.2.tgz", - "integrity": "sha512-dBr1QiTyMCB6iIH2UnA9MxO7zJn/TJfBT5M7xEhEYHEC1r4z5wNjBveHX/v9dvX/zlr/Y+olVDgcfO8FUYMEeA==", - "requires": { - "async": "^2.6.2", - "node-opcua-assert": "^0.7.0", - "node-opcua-client": "^0.7.2", - "node-opcua-constants": "^0.7.0", - "node-opcua-data-model": "^0.7.2", - "node-opcua-nodeid": "^0.7.2", - "node-opcua-service-call": "^0.7.2", - "node-opcua-service-read": "^0.7.2", - "node-opcua-status-code": "^0.7.2", - "node-opcua-utils": "^0.7.2", - "node-opcua-variant": "^0.7.2", + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/node-opcua-client-proxy/-/node-opcua-client-proxy-2.1.5.tgz", + "integrity": "sha512-nf/iJjqVybVV/0apdXKD7TscNEMGk7Y16bPGpe0qJoLoroFmrNwEanZuGozK8xU2i/WhZZ7wek8hV9ti+aGWsg==", + "requires": { + "async": "^3.1.0", + "node-opcua-assert": "^2.0.0", + "node-opcua-client": "^2.1.5", + "node-opcua-constants": "^2.1.5", + "node-opcua-data-model": "^2.1.5", + "node-opcua-data-value": "^2.1.5", + "node-opcua-nodeid": "^2.1.5", + "node-opcua-service-browse": "^2.1.5", + "node-opcua-service-call": "^2.1.5", + "node-opcua-service-read": "^2.1.5", + "node-opcua-service-subscription": "^2.1.5", + "node-opcua-service-write": "^2.1.5", + "node-opcua-status-code": "^2.1.5", + "node-opcua-utils": "^2.1.5", + "node-opcua-variant": "^2.1.5", "underscore": "^1.9.1" }, "dependencies": { "async": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/async/-/async-2.6.2.tgz", - "integrity": "sha512-H1qVYh1MYhEEFLsP97cVKqCGo7KfCyTt6uEWqsTBr9SO84oK9Uwbyd/yCW+6rKJLHksBNUVWZDAjfS+Ccx0Bbg==", - "requires": { - "lodash": "^4.17.11" - } + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/async/-/async-3.1.0.tgz", + "integrity": "sha512-4vx/aaY6j/j3Lw3fbCHNWP0pPaTCew3F6F3hYyl/tHs/ndmV1q7NW9T5yuJ2XAGwdQrP+6Wu20x06U4APo/iQQ==" } } }, "node-opcua-common": { - "version": "0.7.2", - "resolved": "https://registry.npmjs.org/node-opcua-common/-/node-opcua-common-0.7.2.tgz", - "integrity": "sha512-/N6ACR4+435wlQpKwoTb2YaiO8NiPrOoQ2lIs7ujiHt4rkhD0DbUYgsQea8nWwVc0Fiqo+Ph8dy2zi3XWzqs4w==", - "requires": { - "node-opcua-assert": "^0.7.0", - "node-opcua-basic-types": "^0.7.2", - "node-opcua-crypto": "^1.0.3", - "node-opcua-data-model": "^0.7.2", - "node-opcua-factory": "^0.7.2", - "node-opcua-nodeid": "^0.7.2", - "node-opcua-service-endpoints": "^0.7.2", - "node-opcua-service-secure-channel": "^0.7.2", + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/node-opcua-common/-/node-opcua-common-2.1.5.tgz", + "integrity": "sha512-6Cq01uwRQ07IKN3qeFYgdr1XBc4mkD8GmLu9hPspK94By+Zi6A+R8Pgm0V/5tCQ2PCDEh4a0tCG5LIrEJESOag==", + "requires": { + "node-opcua-assert": "^2.0.0", + "node-opcua-basic-types": "^2.1.5", + "node-opcua-crypto": "^1.1.2", + "node-opcua-data-model": "^2.1.5", + "node-opcua-factory": "^2.1.5", + "node-opcua-nodeid": "^2.1.5", + "node-opcua-service-endpoints": "^2.1.5", + "node-opcua-service-secure-channel": "^2.1.5", + "node-opcua-types": "^2.1.5", "underscore": "^1.9.1" } }, "node-opcua-constants": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/node-opcua-constants/-/node-opcua-constants-0.7.0.tgz", - "integrity": "sha512-MkiM1AsGN2QfXueNnqLWciYUO+SmytYLvJvd5/rqwnHZXXY2mrSExBFDgYfwI6VZS1YVKAG1Tyr5Rg37zq+vaw==" + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/node-opcua-constants/-/node-opcua-constants-2.1.5.tgz", + "integrity": "sha512-VPhx+oo3Lh0GN2EzkaF8HagG90tulu7ZgRZABBCxUvaxWLyjl4LW9rgUuM/1WKRRpsXz7xGBBTCME2QQA82EDg==" }, "node-opcua-crypto": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/node-opcua-crypto/-/node-opcua-crypto-1.1.1.tgz", - "integrity": "sha512-abxt9/zozwMBtig+/r41oJ+rUcd3F8SFSGt6w2GhG0ciWIEFXhFZFHM6kb/qWhoC718qKBLgFRQgwnhfe3wcsA==", + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/node-opcua-crypto/-/node-opcua-crypto-1.1.2.tgz", + "integrity": "sha512-blz7Ce3NQ0elmsZPWf7M4u13Kpkjvq9OzhPkm8VYaquCBfV1v6lRE0rmTrwu2jnEzKv5mQ/REf+o0Tiu4+6Yzg==", "requires": { "@types/lorem-ipsum": "^1.0.2", "better-assert": "^1.0.2", @@ -3074,238 +3141,271 @@ } }, "node-opcua-data-access": { - "version": "0.7.2", - "resolved": "https://registry.npmjs.org/node-opcua-data-access/-/node-opcua-data-access-0.7.2.tgz", - "integrity": "sha512-YNzUNpjO5636DjSeDc2A1QH04cvS41u/wIRZcOASpXIplr5cXKdC5tbsy7hQjKfU6NXWBQE4SmPC65NS6zi51Q==", - "requires": { - "node-opcua-assert": "^0.7.0", - "node-opcua-basic-types": "^0.7.2", - "node-opcua-data-model": "^0.7.2", - "node-opcua-factory": "^0.7.2", - "node-opcua-nodeid": "^0.7.2", + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/node-opcua-data-access/-/node-opcua-data-access-2.1.5.tgz", + "integrity": "sha512-WM+tiyCpv5O+qMCyNmVa7xuEw8caohcPFqkdsIgiiToZF5q4Qf0NEUVtb5dW3z4Jfmyiyt+TVRihCaTXXOR2hw==", + "requires": { + "node-opcua-assert": "^2.0.0", + "node-opcua-basic-types": "^2.1.5", + "node-opcua-binary-stream": "^2.1.0", + "node-opcua-data-model": "^2.1.5", + "node-opcua-factory": "^2.1.5", + "node-opcua-nodeid": "^2.1.5", + "node-opcua-types": "^2.1.5", "underscore": "^1.9.1" } }, "node-opcua-data-model": { - "version": "0.7.2", - "resolved": "https://registry.npmjs.org/node-opcua-data-model/-/node-opcua-data-model-0.7.2.tgz", - "integrity": "sha512-nLLBRL81B+6C0NZUVVDtDt/hO64ieUrZVEf3OsHPZoeh8GZAwgY4TWS1Pimkr10giTm9Q0L/QWlJEG4zTcCwEg==", - "requires": { - "node-opcua-assert": "^0.7.0", - "node-opcua-basic-types": "^0.7.2", - "node-opcua-binary-stream": "^0.7.2", - "node-opcua-enum": "^0.7.2", - "node-opcua-factory": "^0.7.2", - "node-opcua-nodeid": "^0.7.2", - "node-opcua-status-code": "^0.7.2", - "node-opcua-utils": "^0.7.2", + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/node-opcua-data-model/-/node-opcua-data-model-2.1.5.tgz", + "integrity": "sha512-2R+b6zq0Yz3E1GgS3FIezTrhYfEQTeAFaxftrl/CE2UKN14ZICockdvz7iuAF7vrNmCe4n8R9qyQ4gZfHjX0pQ==", + "requires": { + "node-opcua-assert": "^2.0.0", + "node-opcua-basic-types": "^2.1.5", + "node-opcua-binary-stream": "^2.1.0", + "node-opcua-enum": "^2.1.0", + "node-opcua-factory": "^2.1.5", + "node-opcua-nodeid": "^2.1.5", + "node-opcua-status-code": "^2.1.5", + "node-opcua-utils": "^2.1.5", "underscore": "^1.9.1" } }, "node-opcua-data-value": { - "version": "0.7.2", - "resolved": "https://registry.npmjs.org/node-opcua-data-value/-/node-opcua-data-value-0.7.2.tgz", - "integrity": "sha512-s5ijhJqnh1jF5i0zlSRJ/9hqLix36TU6uoHP9apQ7cUJgDvjcHHjmMcnV/2nkb2X5GZaOP/n07uH3v55wQGLqw==", - "requires": { - "node-opcua-assert": "^0.7.0", - "node-opcua-basic-types": "^0.7.2", - "node-opcua-binary-stream": "^0.7.2", - "node-opcua-date-time": "^0.7.2", - "node-opcua-factory": "^0.7.2", - "node-opcua-nodeid": "^0.7.2", - "node-opcua-status-code": "^0.7.2", - "node-opcua-utils": "^0.7.2", - "node-opcua-variant": "^0.7.2", + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/node-opcua-data-value/-/node-opcua-data-value-2.1.5.tgz", + "integrity": "sha512-ERbLA6mngDIaR3FuEbe05m0KUT1o7L/1cqqqOjwKu9xKn8mgj09Vv8NqQxi5yCVQK1OyDCy4SmmT8BL0PBq6XQ==", + "requires": { + "node-opcua-assert": "^2.0.0", + "node-opcua-basic-types": "^2.1.5", + "node-opcua-binary-stream": "^2.1.0", + "node-opcua-data-model": "^2.1.5", + "node-opcua-date-time": "^2.1.5", + "node-opcua-enum": "^2.1.0", + "node-opcua-extension-object": "^2.1.5", + "node-opcua-factory": "^2.1.5", + "node-opcua-nodeid": "^2.1.5", + "node-opcua-status-code": "^2.1.5", + "node-opcua-utils": "^2.1.5", + "node-opcua-variant": "^2.1.5", "underscore": "^1.9.1" } }, "node-opcua-date-time": { - "version": "0.7.2", - "resolved": "https://registry.npmjs.org/node-opcua-date-time/-/node-opcua-date-time-0.7.2.tgz", - "integrity": "sha512-khmAHvQylzUqtVwNfm0zmVy0MMlUPtdWjCugoTUiR+oqJFv1PewLWJ6yGQIYcrNkucRu5ruZrJV+bvsCOV1Jyg==", + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/node-opcua-date-time/-/node-opcua-date-time-2.1.5.tgz", + "integrity": "sha512-9nhB/4xPgOCLsGlfaNZsCf3U2cO16bvlBPiuNqE/4uLpWS9zU4QLbZTA/YL2dwZ1CiAYsJfZigkocXMWw2w17w==", "requires": { "long": "^4.0.0", - "node-opcua-assert": "^0.7.0", + "node-opcua-assert": "^2.0.0", "underscore": "^1.9.1" } }, "node-opcua-debug": { - "version": "0.7.2", - "resolved": "https://registry.npmjs.org/node-opcua-debug/-/node-opcua-debug-0.7.2.tgz", - "integrity": "sha512-ejP9HzK44oLDm3YbxD0mw/K4Uou8GWuM6WNy16BaC89mreSxFX483RSHBOapalVPYe+GeRZ8hz9kd4m1AZu4Iw==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/node-opcua-debug/-/node-opcua-debug-2.1.0.tgz", + "integrity": "sha512-7frKcPMUkKFNhbhv+AlfqT1QgirnFFfHCsm9En818Asbp73usWFA08/Fuat+cigWlbzkqEF2XQZ4Fk00QDRlyA==", "requires": { "chalk": "^2.4.2", "hexy": "^0.3.0", - "node-opcua-assert": "^0.7.0", - "node-opcua-buffer-utils": "^0.7.2", + "node-opcua-assert": "^2.0.0", + "node-opcua-buffer-utils": "^2.1.0", "underscore": "^1.9.1" } }, "node-opcua-enum": { - "version": "0.7.2", - "resolved": "https://registry.npmjs.org/node-opcua-enum/-/node-opcua-enum-0.7.2.tgz", - "integrity": "sha512-YGexTCJMQEjCRf8bDWqinvMVrJcEOVUgv15BGG48i97/EmDJFc+Y2aBHRCMDwnt+lrr30vfVimVoF2P7nJAE6g==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/node-opcua-enum/-/node-opcua-enum-2.1.0.tgz", + "integrity": "sha512-l158tBfwVfrErYewoCb0KH8N9pmIJnW7FCSHhXkz9lv/ClYrWqBChC6Zkb1OlGwahDOt9ys+QDpmxQkMxUNPlA==", "requires": { - "node-opcua-assert": "^0.7.0" + "node-opcua-assert": "^2.0.0" } }, "node-opcua-extension-object": { - "version": "0.7.2", - "resolved": "https://registry.npmjs.org/node-opcua-extension-object/-/node-opcua-extension-object-0.7.2.tgz", - "integrity": "sha512-hyjBXRefaf/URHRfjZabSo/kZjBoXXzlW4c7jbk5uxftlTjzPgdGVqDjIvpj5taw+Sron9XccGalQLpDFjvd+A==", + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/node-opcua-extension-object/-/node-opcua-extension-object-2.1.5.tgz", + "integrity": "sha512-NkEyckHWEusjBhRb+ygEAaVT4b5Ak8azFMcXjHhSr28ahiGglZmWzv5mZFG8gIDzRSc/wvswW79ZC0h8MTIJDw==", "requires": { - "node-opcua-assert": "^0.7.0", - "node-opcua-basic-types": "^0.7.2", - "node-opcua-factory": "^0.7.2", - "node-opcua-nodeid": "^0.7.2" + "chalk": "^2.4.2", + "node-opcua-assert": "^2.0.0", + "node-opcua-basic-types": "^2.1.5", + "node-opcua-binary-stream": "^2.1.0", + "node-opcua-debug": "^2.1.0", + "node-opcua-factory": "^2.1.5", + "node-opcua-nodeid": "^2.1.5" } }, "node-opcua-factory": { - "version": "0.7.2", - "resolved": "https://registry.npmjs.org/node-opcua-factory/-/node-opcua-factory-0.7.2.tgz", - "integrity": "sha512-i4I6vI//8IAHtg5UL1DJ3GUlGB5KE33NqmWvpoAUgfiKhTzdd/XA1SGvVD10L5VwXTORVVSgQzl6pYpu5ofnVA==", - "requires": { - "node-opcua-assert": "^0.7.0", - "node-opcua-basic-types": "^0.7.2", - "node-opcua-binary-stream": "^0.7.2", - "node-opcua-debug": "^0.7.2", - "node-opcua-enum": "^0.7.2", - "node-opcua-guid": "^0.7.0", - "node-opcua-nodeid": "^0.7.2", - "node-opcua-status-code": "^0.7.2", - "node-opcua-utils": "^0.7.2", + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/node-opcua-factory/-/node-opcua-factory-2.1.5.tgz", + "integrity": "sha512-rlb4zY3uw2B7P7lkpOBl0mQUBYRsjj/X+yL8yJTo/j5DF+j2FtWIupQPfkKlQWpZQfkMmM1V1hcN0tVEPi8G3Q==", + "requires": { + "chalk": "^2.4.2", + "node-opcua-assert": "^2.0.0", + "node-opcua-basic-types": "^2.1.5", + "node-opcua-binary-stream": "^2.1.0", + "node-opcua-enum": "^2.1.0", + "node-opcua-guid": "^2.1.0", + "node-opcua-nodeid": "^2.1.5", + "node-opcua-status-code": "^2.1.5", + "node-opcua-utils": "^2.1.5", "underscore": "^1.9.1" } }, + "node-opcua-generator": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/node-opcua-generator/-/node-opcua-generator-2.1.5.tgz", + "integrity": "sha512-h/TTZdE8txdTHV8YeIOpVxvK0IYFIE45mOYf5bHUJO7tfmK3NlciLQABALqUJRnFmiX6UYSCvhNUZ/bKQgmN2A==", + "requires": { + "chalk": "^2.4.2", + "node-opcua-assert": "^2.0.0", + "node-opcua-basic-types": "^2.1.5", + "node-opcua-buffer-utils": "^2.1.0", + "node-opcua-constants": "^2.1.5", + "node-opcua-date-time": "^2.1.5", + "node-opcua-debug": "^2.1.0", + "node-opcua-enum": "^2.1.0", + "node-opcua-factory": "^2.1.5", + "node-opcua-guid": "^2.1.0", + "node-opcua-nodeid": "^2.1.5", + "node-opcua-numeric-range": "^2.1.5", + "node-opcua-schemas": "^2.1.5", + "node-opcua-status-code": "^2.1.5", + "node-opcua-utils": "^2.1.5", + "node-opcua-xml2json": "^2.1.5", + "prettier": "^1.18.2", + "typescript": "^3.5.3", + "underscore": "^1.9.1" + }, + "dependencies": { + "typescript": { + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.5.3.tgz", + "integrity": "sha512-ACzBtm/PhXBDId6a6sDJfroT2pOWt/oOnk4/dElG5G33ZL776N3Y6/6bKZJBFpd+b05F3Ct9qDjMeJmRWtE2/g==" + } + } + }, "node-opcua-guid": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/node-opcua-guid/-/node-opcua-guid-0.7.0.tgz", - "integrity": "sha512-nQ+T26mxYFuml6CaZhZSCZU4LeKQYpZmG/o8oTZwKBPh3rmYW7u/OGTue5JF91IIKTmMHmoZq3ugVpHaJLa6fA==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/node-opcua-guid/-/node-opcua-guid-2.1.0.tgz", + "integrity": "sha512-bidaM9XXlaTb0wmDsqybjs7E/+XCtHdWHYJlaUK4PX8Lb0Fznn49PNrKFDllJFo1VfB3aX0meSwUrxjhp+GzuA==", "requires": { - "node-opcua-assert": "^0.7.0" + "node-opcua-assert": "^2.0.0" } }, "node-opcua-hostname": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/node-opcua-hostname/-/node-opcua-hostname-0.7.0.tgz", - "integrity": "sha512-GMYibPkVtpRw3pHle/jw4nggNzkaHR6TKygwIp03vsyHrVUb8i3yQaKkcRkrd1fBw2xAxp5Iz3ClzEIuzqP9vA==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/node-opcua-hostname/-/node-opcua-hostname-2.1.0.tgz", + "integrity": "sha512-NrJCTz+q/k7FACJ8AAtyC+UY+yMh84lDaD7NpljM7A9ivmWJwwoHzSti68fxTNLAZyamPBSNblLO/vpRgzq6Rg==", "requires": { - "node-fqdn": "^1.1.1", - "node-opcua-assert": "^0.7.0" + "node-opcua-assert": "^2.0.0" } }, "node-opcua-nodeid": { - "version": "0.7.2", - "resolved": "https://registry.npmjs.org/node-opcua-nodeid/-/node-opcua-nodeid-0.7.2.tgz", - "integrity": "sha512-YKAQvGrEHb9KK1VxPWq0jTGV3oVwt6BeZUf+yyBHC5rJF9jjLvjVRkfot4Tup1YSXJf99TXq9urZohm5g9NQbg==", - "requires": { - "node-opcua-assert": "^0.7.0", - "node-opcua-constants": "^0.7.0", - "node-opcua-enum": "^0.7.2", - "node-opcua-guid": "^0.7.0", + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/node-opcua-nodeid/-/node-opcua-nodeid-2.1.5.tgz", + "integrity": "sha512-zr9DiZKZA5kcAL28AUj5VT2CDU6ANUht6AS+l4iX6tr95nKqK7y8eCwbMruAz2ivVKznJ3nLD+wu21SlzJRuwg==", + "requires": { + "node-opcua-assert": "^2.0.0", + "node-opcua-constants": "^2.1.5", + "node-opcua-enum": "^2.1.0", + "node-opcua-guid": "^2.1.0", "underscore": "^1.9.1" } }, "node-opcua-nodesets": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/node-opcua-nodesets/-/node-opcua-nodesets-0.7.0.tgz", - "integrity": "sha512-EqEoCoA4GsSKnu90+Mphac/akn2PFSej/TguZN/cPe55y20C836LbzjW6XU2wEZn5vmAkhrUMval/o1OcSG/Yg==" + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/node-opcua-nodesets/-/node-opcua-nodesets-2.1.5.tgz", + "integrity": "sha512-kZ6YUIygjusPbUVtbnkdnzPgprzQOMbzo6u3nsIxsu4JaHdUmarkqQFdNLT+ao6Y1Laif7n1q8qGkK38NoLf2w==" }, "node-opcua-numeric-range": { - "version": "0.7.2", - "resolved": "https://registry.npmjs.org/node-opcua-numeric-range/-/node-opcua-numeric-range-0.7.2.tgz", - "integrity": "sha512-GeA80Ng0tBp+eJMcbb47Y/AeHga7RceGSzi1lIrJtpbuFrQ2XVcFSbNe6eteJia3ZeNrPib/3Di1HmC21EyTiw==", - "requires": { - "node-opcua-assert": "^0.7.0", - "node-opcua-basic-types": "^0.7.2", - "node-opcua-enum": "^0.7.2", - "node-opcua-factory": "^0.7.2", - "node-opcua-status-code": "^0.7.2", + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/node-opcua-numeric-range/-/node-opcua-numeric-range-2.1.5.tgz", + "integrity": "sha512-9aDuDecerSfCgt/B6bomXeCP9eoPekwbkpN5tFFf8rXpbJaukdvfgDmm/AUaGeqEsVDx9EmAPQA7szoPzDHVNw==", + "requires": { + "node-opcua-assert": "^2.0.0", + "node-opcua-basic-types": "^2.1.5", + "node-opcua-binary-stream": "^2.1.0", + "node-opcua-factory": "^2.1.5", + "node-opcua-status-code": "^2.1.5", "underscore": "^1.9.1" } }, "node-opcua-object-registry": { - "version": "0.7.2", - "resolved": "https://registry.npmjs.org/node-opcua-object-registry/-/node-opcua-object-registry-0.7.2.tgz", - "integrity": "sha512-ATOp/Vr1IoekcCtxIBqLJHKHXPFaDqOkAuN6f90GfaN4Z4pkUYMI+8W/ccgJOHcd9PYxO2yTUYyLaEA26B5kzQ==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/node-opcua-object-registry/-/node-opcua-object-registry-2.1.0.tgz", + "integrity": "sha512-/EMwVW7pu+S8L3YQKzXMEw3KiKQ5XIgN1YVA8sOozjeLT7F7RTufRnQEpi4Ia/NnGSRwGcu8MezwaZ4fTtG4ww==", "requires": { - "node-opcua-assert": "^0.7.0", - "node-opcua-debug": "^0.7.2", + "node-opcua-assert": "^2.0.0", + "node-opcua-debug": "^2.1.0", "underscore": "^1.9.1" } }, "node-opcua-packet-analyzer": { - "version": "0.7.2", - "resolved": "https://registry.npmjs.org/node-opcua-packet-analyzer/-/node-opcua-packet-analyzer-0.7.2.tgz", - "integrity": "sha512-vIEjzzLZ5nDaj8HNvYSrJJr6DDjU3Bj4n/SHqGmyJelpJ3XQSu7zL+g2gbOc/RPwbsgwCaunTZu1f1g4OA9jvA==", + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/node-opcua-packet-analyzer/-/node-opcua-packet-analyzer-2.1.5.tgz", + "integrity": "sha512-hASdd8oNCj2sQSMFqyCKGsGOeKwNORuhs7cYxhb/BVu22/5xfs3BF9Ar41SnDtNpoYAEc5wEZ9qvyZXxyM52rQ==", "requires": { - "colors": "^1.3.3", - "node-opcua-assert": "^0.7.0", - "node-opcua-basic-types": "^0.7.2", - "node-opcua-binary-stream": "^0.7.2", - "node-opcua-debug": "^0.7.2", - "node-opcua-enum": "^0.7.2", - "node-opcua-factory": "^0.7.2", - "node-opcua-utils": "^0.7.2", + "chalk": "^2.4.2", + "node-opcua-assert": "^2.0.0", + "node-opcua-basic-types": "^2.1.5", + "node-opcua-binary-stream": "^2.1.0", + "node-opcua-debug": "^2.1.0", + "node-opcua-factory": "^2.1.5", + "node-opcua-utils": "^2.1.5", "underscore": "^1.9.1" } }, "node-opcua-packet-assembler": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/node-opcua-packet-assembler/-/node-opcua-packet-assembler-0.7.0.tgz", - "integrity": "sha512-yqtYfLDRoC6TasiVAQidkPhS12pYyOPuDsPwjNf79Yxzu4kiKVH3DDN/3B9cbd8bsc6wtzX6NdNw3pcqL+bPJA==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/node-opcua-packet-assembler/-/node-opcua-packet-assembler-2.0.0.tgz", + "integrity": "sha512-oxXU1xBD6J0aYAU5r1CAHr+Sjl0qxGhGBn14BeWst97UzPx27TMi+Oy40eWQBLor9JIGVotc2nMvaQOmKWLoWA==", "requires": { - "node-opcua-assert": "^0.7.0", + "node-opcua-assert": "^2.0.0", "underscore": "^1.9.1" } }, "node-opcua-pki": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/node-opcua-pki/-/node-opcua-pki-1.2.3.tgz", - "integrity": "sha512-RUlxliGG+hy3QKxzgX8/THFjJYFMbcafXWHrq5bj1da+d7u96ipNE1Lo8eluB1so9MTBzAl9bJNiuCJe01HXOg==", + "version": "1.5.3", + "resolved": "https://registry.npmjs.org/node-opcua-pki/-/node-opcua-pki-1.5.3.tgz", + "integrity": "sha512-+dxGkQo+HLTPxKsKY0WtZya/FsmsQTOQAM7B7qq47F0HIYOhw05eh+5LiFuLEgvYiQE3qp+6XVarvFNhPgOpig==", "requires": { - "async": "^2.6.2", + "async": "^3.1.0", "better-assert": "^1.0.2", "byline": "^5.0.0", "chalk": "^2.4.2", "cli-spinner": "^0.2.10", "cli-table": "^0.3.1", - "del": "^4.1.0", - "node-opcua-crypto": "^1.0.3", + "del": "^5.0.0", + "node-opcua-crypto": "^1.1.2", "progress": "^2.0.3", + "source-map-support": "^0.5.12", "thenify": "^3.3.0", "underscore": "^1.9.1", "walk": "^2.3.14", "wget-improved": "^3.0.2", - "yargs": "^13.2.2", + "yargs": "^13.3.0", "yauzl": "^2.10.0" }, "dependencies": { "async": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/async/-/async-2.6.2.tgz", - "integrity": "sha512-H1qVYh1MYhEEFLsP97cVKqCGo7KfCyTt6uEWqsTBr9SO84oK9Uwbyd/yCW+6rKJLHksBNUVWZDAjfS+Ccx0Bbg==", - "requires": { - "lodash": "^4.17.11" - } + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/async/-/async-3.1.0.tgz", + "integrity": "sha512-4vx/aaY6j/j3Lw3fbCHNWP0pPaTCew3F6F3hYyl/tHs/ndmV1q7NW9T5yuJ2XAGwdQrP+6Wu20x06U4APo/iQQ==" }, "camelcase": { "version": "5.3.1", "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==" }, - "execa": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/execa/-/execa-1.0.0.tgz", - "integrity": "sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==", + "cliui": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-5.0.0.tgz", + "integrity": "sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA==", "requires": { - "cross-spawn": "^6.0.0", - "get-stream": "^4.0.0", - "is-stream": "^1.1.0", - "npm-run-path": "^2.0.0", - "p-finally": "^1.0.0", - "signal-exit": "^3.0.0", - "strip-eof": "^1.0.0" + "string-width": "^3.1.0", + "strip-ansi": "^5.2.0", + "wrap-ansi": "^5.1.0" } }, "find-up": { @@ -3321,59 +3421,13 @@ "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==" }, - "get-stream": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", - "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", - "requires": { - "pump": "^3.0.0" - } - }, - "invert-kv": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-2.0.0.tgz", - "integrity": "sha512-wPVv/y/QQ/Uiirj/vh3oP+1Ww+AWehmi1g5fFWGPF6IpCBCDVrhgHRMvrLfdYcwDh3QJbGXDW4JAuzxElLSqKA==" - }, - "lcid": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/lcid/-/lcid-2.0.0.tgz", - "integrity": "sha512-avPEb8P8EGnwXKClwsNUgryVjllcRqtMYa49NTsbQagYuT1DcXnl1915oxWjoyGrXR6zH/Y0Zc96xWsPcoDKeA==", - "requires": { - "invert-kv": "^2.0.0" - } - }, "locate-path": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", - "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", - "requires": { - "p-locate": "^3.0.0", - "path-exists": "^3.0.0" - } - }, - "mem": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/mem/-/mem-4.3.0.tgz", - "integrity": "sha512-qX2bG48pTqYRVmDB37rn/6PT7LcR8T7oAX3bf99u1Tt1nzxYfxkgqDwUwolPlXweM0XzBOBFzSx4kfp7KP1s/w==", - "requires": { - "map-age-cleaner": "^0.1.1", - "mimic-fn": "^2.0.0", - "p-is-promise": "^2.0.0" - } - }, - "mimic-fn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", - "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==" - }, - "os-locale": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-3.1.0.tgz", - "integrity": "sha512-Z8l3R4wYWM40/52Z+S265okfFj8Kt2cC2MKY+xNi3kFs+XGI7WXu/I309QQQYbRW4ijiZ+yxs9pqEhJh0DqW3Q==", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", "requires": { - "execa": "^1.0.0", - "lcid": "^2.0.0", - "mem": "^4.0.0" + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" } }, "p-limit": { @@ -3412,33 +3466,42 @@ "strip-ansi": "^5.1.0" } }, + "wrap-ansi": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-5.1.0.tgz", + "integrity": "sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q==", + "requires": { + "ansi-styles": "^3.2.0", + "string-width": "^3.0.0", + "strip-ansi": "^5.0.0" + } + }, "y18n": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.0.tgz", "integrity": "sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w==" }, "yargs": { - "version": "13.2.2", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-13.2.2.tgz", - "integrity": "sha512-WyEoxgyTD3w5XRpAQNYUB9ycVH/PQrToaTXdYXRdOXvEy1l19br+VJsc0vcO8PTGg5ro/l/GY7F/JMEBmI0BxA==", + "version": "13.3.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-13.3.0.tgz", + "integrity": "sha512-2eehun/8ALW8TLoIl7MVaRUrg+yCnenu8B4kBlRxj3GJGDKU1Og7sMXPNm1BYyM1DOJmTZ4YeN/Nwxv+8XJsUA==", "requires": { - "cliui": "^4.0.0", + "cliui": "^5.0.0", "find-up": "^3.0.0", "get-caller-file": "^2.0.1", - "os-locale": "^3.1.0", "require-directory": "^2.1.1", "require-main-filename": "^2.0.0", "set-blocking": "^2.0.0", "string-width": "^3.0.0", "which-module": "^2.0.0", "y18n": "^4.0.0", - "yargs-parser": "^13.0.0" + "yargs-parser": "^13.1.1" } }, "yargs-parser": { - "version": "13.1.0", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-13.1.0.tgz", - "integrity": "sha512-Yq+32PrijHRri0vVKQEm+ys8mbqWjLiwQkMFNXEENutzLPP0bE4Lcd4iA3OQY5HF+GD3xXxf0MEHb8E4/SA3AA==", + "version": "13.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-13.1.1.tgz", + "integrity": "sha512-oVAVsHz6uFrg3XQheFII8ESO2ssAf9luWuAd6Wexsu4F3OtIW0o8IribPXYrD4WC24LWtPrJlGy87y5udK+dxQ==", "requires": { "camelcase": "^5.0.0", "decamelize": "^1.2.0" @@ -3446,418 +3509,542 @@ } } }, + "node-opcua-pseudo-session": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/node-opcua-pseudo-session/-/node-opcua-pseudo-session-2.1.5.tgz", + "integrity": "sha512-q0xUQ72tNfbTUjcKZCdAEysYs7eKUTUXQGxHyD/IVwCfPM9xtL0eXsI+1RIZUr50/zDvf2JbHjq5QrhjiTUETQ==", + "requires": { + "async": "^3.1.0", + "node-opcua-data-model": "^2.1.5", + "node-opcua-data-value": "^2.1.5", + "node-opcua-nodeid": "^2.1.5", + "node-opcua-service-browse": "^2.1.5", + "node-opcua-service-call": "^2.1.5", + "node-opcua-service-read": "^2.1.5", + "node-opcua-service-translate-browse-path": "^2.1.5", + "node-opcua-status-code": "^2.1.5", + "node-opcua-utils": "^2.1.5", + "node-opcua-variant": "^2.1.5", + "underscore": "^1.9.1" + }, + "dependencies": { + "async": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/async/-/async-3.1.0.tgz", + "integrity": "sha512-4vx/aaY6j/j3Lw3fbCHNWP0pPaTCew3F6F3hYyl/tHs/ndmV1q7NW9T5yuJ2XAGwdQrP+6Wu20x06U4APo/iQQ==" + } + } + }, + "node-opcua-schemas": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/node-opcua-schemas/-/node-opcua-schemas-2.1.5.tgz", + "integrity": "sha512-GAVYFu39JCIFH3dUSEoxANRKC9HMS88aHOOTG5nXH9RemHaarW1D7daqiNCkBS9T21nnKzyaNLIJXosbHM46vw==", + "requires": { + "node-opcua-assert": "^2.0.0", + "node-opcua-binary-stream": "^2.1.0", + "node-opcua-data-model": "^2.1.5", + "node-opcua-debug": "^2.1.0", + "node-opcua-enum": "^2.1.0", + "node-opcua-extension-object": "^2.1.5", + "node-opcua-factory": "^2.1.5", + "node-opcua-nodeid": "^2.1.5", + "node-opcua-variant": "^2.1.5", + "node-opcua-xml2json": "^2.1.5" + } + }, "node-opcua-secure-channel": { - "version": "0.7.2", - "resolved": "https://registry.npmjs.org/node-opcua-secure-channel/-/node-opcua-secure-channel-0.7.2.tgz", - "integrity": "sha512-DSR//+D5bARVqKIaRT0c1C4ZdVZRFtkbttzPwE4xJXJhpmwc7CDYVpvzUmujfDAqOXQFkUoPyf6zpZDFFFIWMQ==", + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/node-opcua-secure-channel/-/node-opcua-secure-channel-2.1.5.tgz", + "integrity": "sha512-E2fGxUsYwP+emA+fX6Ilvcq5cm5NFbltAx4uh0N+q45yqxc1OpPGJ3iwzXmrAcQXV87U0impQ56X5jCNjWMekA==", "requires": { "backoff": "^2.5.0", - "colors": "^1.3.3", - "node-opcua-assert": "^0.7.0", - "node-opcua-basic-types": "^0.7.2", - "node-opcua-binary-stream": "^0.7.2", - "node-opcua-chunkmanager": "^0.7.2", - "node-opcua-crypto": "^1.0.3", - "node-opcua-debug": "^0.7.2", - "node-opcua-enum": "^0.7.2", - "node-opcua-factory": "^0.7.2", - "node-opcua-object-registry": "^0.7.2", - "node-opcua-packet-analyzer": "^0.7.2", - "node-opcua-service-secure-channel": "^0.7.2", - "node-opcua-status-code": "^0.7.2", - "node-opcua-transport": "^0.7.2", - "node-opcua-utils": "^0.7.2", + "chalk": "^2.4.2", + "node-opcua-assert": "^2.0.0", + "node-opcua-basic-types": "^2.1.5", + "node-opcua-binary-stream": "^2.1.0", + "node-opcua-certificate-manager": "^2.1.5", + "node-opcua-chunkmanager": "^2.1.5", + "node-opcua-common": "^2.1.5", + "node-opcua-crypto": "^1.1.2", + "node-opcua-debug": "^2.1.0", + "node-opcua-enum": "^2.1.0", + "node-opcua-factory": "^2.1.5", + "node-opcua-nodeid": "^2.1.5", + "node-opcua-object-registry": "^2.1.0", + "node-opcua-packet-analyzer": "^2.1.5", + "node-opcua-service-secure-channel": "^2.1.5", + "node-opcua-status-code": "^2.1.5", + "node-opcua-transport": "^2.1.5", + "node-opcua-types": "^2.1.5", + "node-opcua-utils": "^2.1.5", "underscore": "^1.9.1" } }, "node-opcua-server": { - "version": "0.7.2", - "resolved": "https://registry.npmjs.org/node-opcua-server/-/node-opcua-server-0.7.2.tgz", - "integrity": "sha512-wUVT2CFh+Y+V9mNPlYZb4NEDZAjpL7P2ORS6GAetdJITm6XIxVAVOagqs3UwXBP2bQyNHakm/KHVBzo9/anknA==", + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/node-opcua-server/-/node-opcua-server-2.1.5.tgz", + "integrity": "sha512-ajiE6+V2fUlOUS4qY2trjoH+kZeUUoXOqvjGeaScSbCFLz9qdY2yd9MouwB1IVaz/+kvuOFdI7UgNUcHcJUJxw==", "requires": { - "async": "^2.6.2", + "async": "^3.1.0", "bonjour": "^3.5.0", "colors": "^1.3.3", "dequeue": "^1.0.5", - "node-opcua-address-space": "^0.7.2", - "node-opcua-assert": "^0.7.0", - "node-opcua-basic-types": "^0.7.2", - "node-opcua-client": "^0.7.2", - "node-opcua-common": "^0.7.2", - "node-opcua-constants": "^0.7.0", - "node-opcua-crypto": "^1.0.3", - "node-opcua-data-model": "^0.7.2", - "node-opcua-data-value": "^0.7.2", - "node-opcua-date-time": "^0.7.2", - "node-opcua-debug": "^0.7.2", - "node-opcua-enum": "^0.7.2", - "node-opcua-factory": "^0.7.2", - "node-opcua-hostname": "^0.7.0", - "node-opcua-nodeid": "^0.7.2", - "node-opcua-nodesets": "^0.7.0", - "node-opcua-numeric-range": "^0.7.2", - "node-opcua-object-registry": "^0.7.2", - "node-opcua-pki": "1.2.3", - "node-opcua-secure-channel": "^0.7.2", - "node-opcua-service-browse": "^0.7.2", - "node-opcua-service-call": "^0.7.2", - "node-opcua-service-discovery": "^0.7.2", - "node-opcua-service-endpoints": "^0.7.2", - "node-opcua-service-filter": "^0.7.2", - "node-opcua-service-history": "^0.7.2", - "node-opcua-service-node-management": "^0.7.2", - "node-opcua-service-query": "^0.7.2", - "node-opcua-service-read": "^0.7.2", - "node-opcua-service-register-node": "^0.7.2", - "node-opcua-service-secure-channel": "^0.7.2", - "node-opcua-service-session": "^0.7.2", - "node-opcua-service-subscription": "^0.7.2", - "node-opcua-service-translate-browse-path": "^0.7.2", - "node-opcua-service-write": "^0.7.2", - "node-opcua-status-code": "^0.7.2", - "node-opcua-utils": "^0.7.2", - "node-opcua-variant": "^0.7.2", + "node-opcua-address-space": "^2.1.5", + "node-opcua-assert": "^2.0.0", + "node-opcua-basic-types": "^2.1.5", + "node-opcua-certificate-manager": "^2.1.5", + "node-opcua-client": "^2.1.5", + "node-opcua-client-dynamic-extension-object": "^2.1.5", + "node-opcua-common": "^2.1.5", + "node-opcua-constants": "^2.1.5", + "node-opcua-crypto": "^1.1.2", + "node-opcua-data-model": "^2.1.5", + "node-opcua-data-value": "^2.1.5", + "node-opcua-date-time": "^2.1.5", + "node-opcua-debug": "^2.1.0", + "node-opcua-enum": "^2.1.0", + "node-opcua-extension-object": "^2.1.5", + "node-opcua-factory": "^2.1.5", + "node-opcua-hostname": "^2.1.0", + "node-opcua-nodeid": "^2.1.5", + "node-opcua-nodesets": "^2.1.5", + "node-opcua-numeric-range": "^2.1.5", + "node-opcua-object-registry": "^2.1.0", + "node-opcua-pki": "^1.5.3", + "node-opcua-secure-channel": "^2.1.5", + "node-opcua-service-browse": "^2.1.5", + "node-opcua-service-call": "^2.1.5", + "node-opcua-service-discovery": "^2.1.5", + "node-opcua-service-endpoints": "^2.1.5", + "node-opcua-service-filter": "^2.1.5", + "node-opcua-service-history": "^2.1.5", + "node-opcua-service-node-management": "^2.1.5", + "node-opcua-service-query": "^2.1.5", + "node-opcua-service-read": "^2.1.5", + "node-opcua-service-register-node": "^2.1.5", + "node-opcua-service-secure-channel": "^2.1.5", + "node-opcua-service-session": "^2.1.5", + "node-opcua-service-subscription": "^2.1.5", + "node-opcua-service-translate-browse-path": "^2.1.5", + "node-opcua-service-write": "^2.1.5", + "node-opcua-status-code": "^2.1.5", + "node-opcua-types": "^2.1.5", + "node-opcua-utils": "^2.1.5", + "node-opcua-variant": "^2.1.5", "underscore": "^1.9.1" }, "dependencies": { "async": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/async/-/async-2.6.2.tgz", - "integrity": "sha512-H1qVYh1MYhEEFLsP97cVKqCGo7KfCyTt6uEWqsTBr9SO84oK9Uwbyd/yCW+6rKJLHksBNUVWZDAjfS+Ccx0Bbg==", - "requires": { - "lodash": "^4.17.11" - } + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/async/-/async-3.1.0.tgz", + "integrity": "sha512-4vx/aaY6j/j3Lw3fbCHNWP0pPaTCew3F6F3hYyl/tHs/ndmV1q7NW9T5yuJ2XAGwdQrP+6Wu20x06U4APo/iQQ==" } } }, "node-opcua-server-discovery": { - "version": "0.7.2", - "resolved": "https://registry.npmjs.org/node-opcua-server-discovery/-/node-opcua-server-discovery-0.7.2.tgz", - "integrity": "sha512-2pbefr2DL8CLSrCFPRh4jCjygRBXFAHY6absB6p5OHjsLNQjknUTqtEnWN6iuPtnLYVmjfvnQWefhqX6/M40dA==", + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/node-opcua-server-discovery/-/node-opcua-server-discovery-2.1.5.tgz", + "integrity": "sha512-cXMjsbYgzEukeBRJSH7DZQEOOpe6GOX5ljm3vPrHBcx6Z2GWb8A1G5AlPi4dvvV3aNQQu18O6KA8mSgRef/q4Q==", "requires": { + "@types/bonjour": "^3.5.5", "bonjour": "^3.5.0", - "node-opcua-assert": "^0.7.0", - "node-opcua-common": "^0.7.2", - "node-opcua-debug": "^0.7.2", - "node-opcua-hostname": "^0.7.0", - "node-opcua-pki": "1.2.3", - "node-opcua-server": "^0.7.2", - "node-opcua-service-discovery": "^0.7.2", - "node-opcua-service-endpoints": "^0.7.2", - "node-opcua-status-code": "^0.7.2", + "node-opcua-assert": "^2.0.0", + "node-opcua-basic-types": "^2.1.5", + "node-opcua-certificate-manager": "^2.1.5", + "node-opcua-common": "^2.1.5", + "node-opcua-debug": "^2.1.0", + "node-opcua-hostname": "^2.1.0", + "node-opcua-pki": "^1.5.3", + "node-opcua-secure-channel": "^2.1.5", + "node-opcua-server": "^2.1.5", + "node-opcua-service-discovery": "^2.1.5", + "node-opcua-service-endpoints": "^2.1.5", + "node-opcua-status-code": "^2.1.5", "underscore": "^1.9.1" } }, "node-opcua-service-browse": { - "version": "0.7.2", - "resolved": "https://registry.npmjs.org/node-opcua-service-browse/-/node-opcua-service-browse-0.7.2.tgz", - "integrity": "sha512-5CXEBPqATQ8OT+JJWzxTEXmMkUxu0GwFCRn4wIK9x3VzbzW0LyWdrOx0uCTPho2Dpofi7oWDNUvK4iByfa5Fog==", - "requires": { - "node-opcua-assert": "^0.7.0", - "node-opcua-basic-types": "^0.7.2", - "node-opcua-data-model": "^0.7.2", - "node-opcua-factory": "^0.7.2", - "node-opcua-nodeid": "^0.7.2", - "node-opcua-service-secure-channel": "^0.7.2", + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/node-opcua-service-browse/-/node-opcua-service-browse-2.1.5.tgz", + "integrity": "sha512-CajhwkWyfFb46JCISO1ffb/51XgDSKVcC/y12rK80cW+s+t7As/WC8++CxHbaxVEt7Jnys113vnjNg/ImI1paQ==", + "requires": { + "node-opcua-assert": "^2.0.0", + "node-opcua-basic-types": "^2.1.5", + "node-opcua-binary-stream": "^2.1.0", + "node-opcua-data-model": "^2.1.5", + "node-opcua-factory": "^2.1.5", + "node-opcua-nodeid": "^2.1.5", + "node-opcua-service-secure-channel": "^2.1.5", + "node-opcua-types": "^2.1.5", "underscore": "^1.9.1" } }, "node-opcua-service-call": { - "version": "0.7.2", - "resolved": "https://registry.npmjs.org/node-opcua-service-call/-/node-opcua-service-call-0.7.2.tgz", - "integrity": "sha512-Ji0K5NNCZhzhtGDryK2dVJCFSq0hGcw1XPlnRK5ZJgRtl1VH4ytqSDXb81Rpy4N6fUd4WFHzZTg54cN0MLgoIQ==", - "requires": { - "node-opcua-assert": "^0.7.0", - "node-opcua-basic-types": "^0.7.2", - "node-opcua-factory": "^0.7.2", - "node-opcua-nodeid": "^0.7.2", - "node-opcua-service-secure-channel": "^0.7.2", - "node-opcua-variant": "^0.7.2", + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/node-opcua-service-call/-/node-opcua-service-call-2.1.5.tgz", + "integrity": "sha512-9mP9890x8DdqSk+HSkyafud3k2Gfag1p2x2QPHygVjJjosUD6Jm9R1XQL3WxTrijO/8x1QQaZKDJZKdMcda9Gg==", + "requires": { + "node-opcua-assert": "^2.0.0", + "node-opcua-basic-types": "^2.1.5", + "node-opcua-binary-stream": "^2.1.0", + "node-opcua-factory": "^2.1.5", + "node-opcua-nodeid": "^2.1.5", + "node-opcua-service-secure-channel": "^2.1.5", + "node-opcua-types": "^2.1.5", + "node-opcua-variant": "^2.1.5", "underscore": "^1.9.1" } }, "node-opcua-service-discovery": { - "version": "0.7.2", - "resolved": "https://registry.npmjs.org/node-opcua-service-discovery/-/node-opcua-service-discovery-0.7.2.tgz", - "integrity": "sha512-MMLdwuW3Um1KWsSdQNcQyQHR7smV53PvgtNQIAz463cQpXq+fbIkyLgfhBn0lsIxFrxvuc8B4gbDo17ikG/ghg==", + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/node-opcua-service-discovery/-/node-opcua-service-discovery-2.1.5.tgz", + "integrity": "sha512-pnArCQ6WJ8MBKeP/EDb/NWEWlWQPVn27A0q7vk1ZgDVKiJsUQKf8CndbRx/f0eMBjxRdk6+oxLDoakTbDlSXNA==", "requires": { + "@types/bonjour": "^3.5.5", "bonjour": "^3.5.0", - "node-opcua-assert": "^0.7.0", - "node-opcua-basic-types": "^0.7.2", - "node-opcua-data-model": "^0.7.2", - "node-opcua-factory": "^0.7.2", - "node-opcua-nodeid": "^0.7.2", - "node-opcua-service-endpoints": "^0.7.2", - "node-opcua-service-secure-channel": "^0.7.2", + "node-opcua-assert": "^2.0.0", + "node-opcua-basic-types": "^2.1.5", + "node-opcua-binary-stream": "^2.1.0", + "node-opcua-data-model": "^2.1.5", + "node-opcua-debug": "^2.1.0", + "node-opcua-factory": "^2.1.5", + "node-opcua-nodeid": "^2.1.5", + "node-opcua-service-endpoints": "^2.1.5", + "node-opcua-service-secure-channel": "^2.1.5", + "node-opcua-types": "^2.1.5", "underscore": "^1.9.1" } }, "node-opcua-service-endpoints": { - "version": "0.7.2", - "resolved": "https://registry.npmjs.org/node-opcua-service-endpoints/-/node-opcua-service-endpoints-0.7.2.tgz", - "integrity": "sha512-0jZDoZCKJKTi2EzuQmIgzVTFtv6O7f5grrWtc0lbliuJaThwCcnPcHsfcRVqvwNP19PuZ22SKAm15mqgHwe71Q==", - "requires": { - "node-opcua-assert": "^0.7.0", - "node-opcua-basic-types": "^0.7.2", - "node-opcua-data-model": "^0.7.2", - "node-opcua-factory": "^0.7.2", - "node-opcua-nodeid": "^0.7.2", - "node-opcua-service-secure-channel": "^0.7.2", + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/node-opcua-service-endpoints/-/node-opcua-service-endpoints-2.1.5.tgz", + "integrity": "sha512-ag4YmhVn0juEqoZMlKBrsNj1NNLVQzV6p+EwXx2RQPAFtC7r0oq204h0PFa3M5fnTG4SToqUMvm03leo2MUSPg==", + "requires": { + "node-opcua-assert": "^2.0.0", + "node-opcua-basic-types": "^2.1.5", + "node-opcua-binary-stream": "^2.1.0", + "node-opcua-data-model": "^2.1.5", + "node-opcua-factory": "^2.1.5", + "node-opcua-nodeid": "^2.1.5", + "node-opcua-service-secure-channel": "^2.1.5", + "node-opcua-types": "^2.1.5", "underscore": "^1.9.1" } }, "node-opcua-service-filter": { - "version": "0.7.2", - "resolved": "https://registry.npmjs.org/node-opcua-service-filter/-/node-opcua-service-filter-0.7.2.tgz", - "integrity": "sha512-x3I/CBgR3UJQE1/F9KkEPxhiTKs155miEC21pHhK4vSTTYqP8poCEivUzQxFmZQ++hSaVyVWAMP1+7yynwQbLQ==", - "requires": { - "node-opcua-assert": "^0.7.0", - "node-opcua-basic-types": "^0.7.2", - "node-opcua-constants": "^0.7.0", - "node-opcua-data-model": "^0.7.2", - "node-opcua-debug": "^0.7.2", - "node-opcua-factory": "^0.7.2", - "node-opcua-nodeid": "^0.7.2", - "node-opcua-numeric-range": "^0.7.2", - "node-opcua-service-translate-browse-path": "^0.7.2", - "node-opcua-status-code": "^0.7.2", - "node-opcua-variant": "^0.7.2", + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/node-opcua-service-filter/-/node-opcua-service-filter-2.1.5.tgz", + "integrity": "sha512-+zn3KYdSCwt2ctWAij4AU/HpR/VViUhuSnht4wpHhcOGvEASZhYoIlvhMcOSOqB8rmYPqAid2q2WyuM8hBmZ8g==", + "requires": { + "node-opcua-assert": "^2.0.0", + "node-opcua-basic-types": "^2.1.5", + "node-opcua-binary-stream": "^2.1.0", + "node-opcua-constants": "^2.1.5", + "node-opcua-data-model": "^2.1.5", + "node-opcua-debug": "^2.1.0", + "node-opcua-factory": "^2.1.5", + "node-opcua-nodeid": "^2.1.5", + "node-opcua-numeric-range": "^2.1.5", + "node-opcua-service-translate-browse-path": "^2.1.5", + "node-opcua-status-code": "^2.1.5", + "node-opcua-types": "^2.1.5", + "node-opcua-variant": "^2.1.5", "underscore": "^1.9.1" } }, "node-opcua-service-history": { - "version": "0.7.2", - "resolved": "https://registry.npmjs.org/node-opcua-service-history/-/node-opcua-service-history-0.7.2.tgz", - "integrity": "sha512-AE/IbnrCN0t72u7kZjXLshwG3MAlh5DkZBlqxBogWA8d3H1Vyh1zG45qrI0HtE0A/0QI7GnXqUu1F5c/6l29qA==", - "requires": { - "node-opcua-assert": "^0.7.0", - "node-opcua-basic-types": "^0.7.2", - "node-opcua-data-model": "^0.7.2", - "node-opcua-data-value": "^0.7.2", - "node-opcua-extension-object": "^0.7.2", - "node-opcua-factory": "^0.7.2", - "node-opcua-nodeid": "^0.7.2", - "node-opcua-service-filter": "^0.7.2", - "node-opcua-service-secure-channel": "^0.7.2", + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/node-opcua-service-history/-/node-opcua-service-history-2.1.5.tgz", + "integrity": "sha512-J8HTYGl9eNgQYB8YLNEViSBtPT6twxSypyuqU0Q7SNEB2UT9MRajJC5esLHXiIWaYAKBWzT9PY/3ZVORu21jyw==", + "requires": { + "node-opcua-assert": "^2.0.0", + "node-opcua-basic-types": "^2.1.5", + "node-opcua-binary-stream": "^2.1.0", + "node-opcua-data-model": "^2.1.5", + "node-opcua-data-value": "^2.1.5", + "node-opcua-extension-object": "^2.1.5", + "node-opcua-factory": "^2.1.5", + "node-opcua-nodeid": "^2.1.5", + "node-opcua-service-filter": "^2.1.5", + "node-opcua-service-secure-channel": "^2.1.5", + "node-opcua-types": "^2.1.5", "underscore": "^1.9.1" } }, "node-opcua-service-node-management": { - "version": "0.7.2", - "resolved": "https://registry.npmjs.org/node-opcua-service-node-management/-/node-opcua-service-node-management-0.7.2.tgz", - "integrity": "sha512-xBS2d0j58EWEzhQdMsr/m+ul2Zf64tMsAFnjhSkiVNG3pa9FA0nLGofOfVYt+upns4/8GG5WpVSW8rV6AkWRuA==", - "requires": { - "node-opcua-assert": "^0.7.0", - "node-opcua-basic-types": "^0.7.2", - "node-opcua-data-model": "^0.7.2", - "node-opcua-extension-object": "^0.7.2", - "node-opcua-factory": "^0.7.2", - "node-opcua-nodeid": "^0.7.2", - "node-opcua-service-secure-channel": "^0.7.2", + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/node-opcua-service-node-management/-/node-opcua-service-node-management-2.1.5.tgz", + "integrity": "sha512-mskuGrkDz0EU2ya7DSJaF2AzS69R9+sk7slzMABr+sSKe3XuPgiZ7U72xC2dibzEnpZHpzwO9cbSiDrTh7JWsA==", + "requires": { + "node-opcua-assert": "^2.0.0", + "node-opcua-basic-types": "^2.1.5", + "node-opcua-binary-stream": "^2.1.0", + "node-opcua-data-model": "^2.1.5", + "node-opcua-extension-object": "^2.1.5", + "node-opcua-factory": "^2.1.5", + "node-opcua-nodeid": "^2.1.5", + "node-opcua-service-secure-channel": "^2.1.5", + "node-opcua-types": "^2.1.5", "underscore": "^1.9.1" } }, "node-opcua-service-query": { - "version": "0.7.2", - "resolved": "https://registry.npmjs.org/node-opcua-service-query/-/node-opcua-service-query-0.7.2.tgz", - "integrity": "sha512-Lz9NzkwGJ06snaJ88n6kwCSfbV6fWoHp8pkJNst0/dnY7MqhUjcI/wXEEwIyrQarcoroxCEiFAAFgQEB+rhjxQ==", - "requires": { - "node-opcua-assert": "^0.7.0", - "node-opcua-basic-types": "^0.7.2", - "node-opcua-data-model": "^0.7.2", - "node-opcua-factory": "^0.7.2", - "node-opcua-nodeid": "^0.7.2", - "node-opcua-numeric-range": "^0.7.2", - "node-opcua-service-browse": "^0.7.2", - "node-opcua-service-filter": "^0.7.2", - "node-opcua-service-secure-channel": "^0.7.2", - "node-opcua-service-subscription": "^0.7.2", - "node-opcua-service-translate-browse-path": "^0.7.2", + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/node-opcua-service-query/-/node-opcua-service-query-2.1.5.tgz", + "integrity": "sha512-QGAvBMhWyzqk49bZnF4HvOUAB3Uyeu3zvPZprgbKwauj0MSf9akc251YnIT95++A4BHsKOE01BgRIucJryWfaw==", + "requires": { + "node-opcua-assert": "^2.0.0", + "node-opcua-basic-types": "^2.1.5", + "node-opcua-binary-stream": "^2.1.0", + "node-opcua-data-model": "^2.1.5", + "node-opcua-factory": "^2.1.5", + "node-opcua-nodeid": "^2.1.5", + "node-opcua-numeric-range": "^2.1.5", + "node-opcua-service-browse": "^2.1.5", + "node-opcua-service-filter": "^2.1.5", + "node-opcua-service-secure-channel": "^2.1.5", + "node-opcua-service-subscription": "^2.1.5", + "node-opcua-service-translate-browse-path": "^2.1.5", + "node-opcua-types": "^2.1.5", "underscore": "^1.9.1" } }, "node-opcua-service-read": { - "version": "0.7.2", - "resolved": "https://registry.npmjs.org/node-opcua-service-read/-/node-opcua-service-read-0.7.2.tgz", - "integrity": "sha512-ueIkIjX0C75jI8hhB9OtfO1VjU7/OKW+J41PDI8Oz11SmA44Rh0d6g28KmTesFlmeyLpow6+KeKWP8VFRzXpOQ==", - "requires": { - "node-opcua-assert": "^0.7.0", - "node-opcua-basic-types": "^0.7.2", - "node-opcua-data-model": "^0.7.2", - "node-opcua-data-value": "^0.7.2", - "node-opcua-factory": "^0.7.2", - "node-opcua-nodeid": "^0.7.2", - "node-opcua-numeric-range": "^0.7.2", - "node-opcua-service-secure-channel": "^0.7.2", + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/node-opcua-service-read/-/node-opcua-service-read-2.1.5.tgz", + "integrity": "sha512-4s2Q8lm9wuyLC5CvurbIoBVLxfxHOVNMnd8ZmVJ+vEWH/rqioqlhjJeGXgXt12Ifknh0qKXWZh7Bx5U/2UAClw==", + "requires": { + "node-opcua-assert": "^2.0.0", + "node-opcua-basic-types": "^2.1.5", + "node-opcua-binary-stream": "^2.1.0", + "node-opcua-data-model": "^2.1.5", + "node-opcua-data-value": "^2.1.5", + "node-opcua-factory": "^2.1.5", + "node-opcua-nodeid": "^2.1.5", + "node-opcua-numeric-range": "^2.1.5", + "node-opcua-service-secure-channel": "^2.1.5", + "node-opcua-types": "^2.1.5", "underscore": "^1.9.1" } }, "node-opcua-service-register-node": { - "version": "0.7.2", - "resolved": "https://registry.npmjs.org/node-opcua-service-register-node/-/node-opcua-service-register-node-0.7.2.tgz", - "integrity": "sha512-J1BmZ2rj/5UKPH0Wci8E0882oE79mqRdTeOieoZDrV9VPgt2ehTIOt7oAujDwGk6EqvvjAZWMYiU0YsZ9yr0Qw==", - "requires": { - "node-opcua-assert": "^0.7.0", - "node-opcua-basic-types": "^0.7.2", - "node-opcua-factory": "^0.7.2", - "node-opcua-nodeid": "^0.7.2", - "node-opcua-service-secure-channel": "^0.7.2", + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/node-opcua-service-register-node/-/node-opcua-service-register-node-2.1.5.tgz", + "integrity": "sha512-Sl7gCUcufB6KVNV0LQaLCkaHe3kxHjMH8E1eyuIJFsCBQspZZGZzR2reqCZvO452oEQO83X3NGbLxh+5EHT+oQ==", + "requires": { + "node-opcua-assert": "^2.0.0", + "node-opcua-basic-types": "^2.1.5", + "node-opcua-binary-stream": "^2.1.0", + "node-opcua-factory": "^2.1.5", + "node-opcua-nodeid": "^2.1.5", + "node-opcua-service-secure-channel": "^2.1.5", + "node-opcua-types": "^2.1.5", "underscore": "^1.9.1" } }, "node-opcua-service-secure-channel": { - "version": "0.7.2", - "resolved": "https://registry.npmjs.org/node-opcua-service-secure-channel/-/node-opcua-service-secure-channel-0.7.2.tgz", - "integrity": "sha512-pg4zwtB3W/90wA9c54ffDaMBMtwzJA1QIhHH6AYWXGd1gbBSEpKh0Rghi0YR46MN7ncuy4NZjTK595QVo8RPPQ==", - "requires": { - "node-opcua-assert": "^0.7.0", - "node-opcua-basic-types": "^0.7.2", - "node-opcua-data-model": "^0.7.2", - "node-opcua-extension-object": "^0.7.2", - "node-opcua-factory": "^0.7.2", - "node-opcua-nodeid": "^0.7.2", + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/node-opcua-service-secure-channel/-/node-opcua-service-secure-channel-2.1.5.tgz", + "integrity": "sha512-z6TCp92/jHvVJCo7uogn8BFXqi5T5RbJjud6eHlVTqQKxLuSCtC7nPXxl8j2QUOOdNNAlVI3+UUc9gMroh84Eg==", + "requires": { + "node-opcua-assert": "^2.0.0", + "node-opcua-basic-types": "^2.1.5", + "node-opcua-binary-stream": "^2.1.0", + "node-opcua-data-model": "^2.1.5", + "node-opcua-extension-object": "^2.1.5", + "node-opcua-factory": "^2.1.5", + "node-opcua-nodeid": "^2.1.5", + "node-opcua-types": "^2.1.5", "underscore": "^1.9.1" } }, "node-opcua-service-session": { - "version": "0.7.2", - "resolved": "https://registry.npmjs.org/node-opcua-service-session/-/node-opcua-service-session-0.7.2.tgz", - "integrity": "sha512-TAmtP6nvqEykM8hR5g3Snh3y93UN3iLzyTWR1emAXxm0EDzzAPjZUGTfK7vz23lCPy2Ip4e7xAaeXZO+3pW9ZQ==", - "requires": { - "node-opcua-assert": "^0.7.0", - "node-opcua-basic-types": "^0.7.2", - "node-opcua-extension-object": "^0.7.2", - "node-opcua-factory": "^0.7.2", - "node-opcua-nodeid": "^0.7.2", - "node-opcua-service-endpoints": "^0.7.2", - "node-opcua-service-secure-channel": "^0.7.2", + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/node-opcua-service-session/-/node-opcua-service-session-2.1.5.tgz", + "integrity": "sha512-j2mRse0HthtOdU7b7Zu3r0kFMPVXrI5uxNmc7nHD85y00TKeBnO3JNNn0TfShEeaUD/GILAXW6iAYAC1+LaSOw==", + "requires": { + "node-opcua-assert": "^2.0.0", + "node-opcua-basic-types": "^2.1.5", + "node-opcua-binary-stream": "^2.1.0", + "node-opcua-extension-object": "^2.1.5", + "node-opcua-factory": "^2.1.5", + "node-opcua-nodeid": "^2.1.5", + "node-opcua-service-endpoints": "^2.1.5", + "node-opcua-service-secure-channel": "^2.1.5", + "node-opcua-types": "^2.1.5", "underscore": "^1.9.1" } }, "node-opcua-service-subscription": { - "version": "0.7.2", - "resolved": "https://registry.npmjs.org/node-opcua-service-subscription/-/node-opcua-service-subscription-0.7.2.tgz", - "integrity": "sha512-3C3Wh6H61yZdOmXo4sYxy66TwiF+Cedx8f4oToIFxHNRtWSl9KHM7Sol7A/D05q+cKn4FLUFzCFysHAOMG8CAA==", - "requires": { - "node-opcua-assert": "^0.7.0", - "node-opcua-basic-types": "^0.7.2", - "node-opcua-data-value": "^0.7.2", - "node-opcua-extension-object": "^0.7.2", - "node-opcua-factory": "^0.7.2", - "node-opcua-nodeid": "^0.7.2", - "node-opcua-service-filter": "^0.7.2", - "node-opcua-service-read": "^0.7.2", - "node-opcua-service-secure-channel": "^0.7.2", - "node-opcua-variant": "^0.7.2", + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/node-opcua-service-subscription/-/node-opcua-service-subscription-2.1.5.tgz", + "integrity": "sha512-NmML/nKU5mjnWs/La3lLImGi1rvaOPNsYfKYhM/RlX/zIICEB1fpB8JRzhfOvFOtGu5Fj8qW8EgCnpE4n3MzkA==", + "requires": { + "node-opcua-assert": "^2.0.0", + "node-opcua-basic-types": "^2.1.5", + "node-opcua-binary-stream": "^2.1.0", + "node-opcua-data-value": "^2.1.5", + "node-opcua-extension-object": "^2.1.5", + "node-opcua-factory": "^2.1.5", + "node-opcua-nodeid": "^2.1.5", + "node-opcua-service-filter": "^2.1.5", + "node-opcua-service-read": "^2.1.5", + "node-opcua-service-secure-channel": "^2.1.5", + "node-opcua-types": "^2.1.5", + "node-opcua-variant": "^2.1.5", "underscore": "^1.9.1" } }, "node-opcua-service-translate-browse-path": { - "version": "0.7.2", - "resolved": "https://registry.npmjs.org/node-opcua-service-translate-browse-path/-/node-opcua-service-translate-browse-path-0.7.2.tgz", - "integrity": "sha512-DIPaolab55RtGCXBuZa/KeZpnMZQsqvb+4a/ougOVHz6OnjwvpSwRBS3Xhr1zhGWw2UN66AT0AnDn1H20GvptQ==", - "requires": { - "node-opcua-assert": "^0.7.0", - "node-opcua-basic-types": "^0.7.2", - "node-opcua-constants": "^0.7.0", - "node-opcua-data-model": "^0.7.2", - "node-opcua-factory": "^0.7.2", - "node-opcua-nodeid": "^0.7.2", - "node-opcua-service-secure-channel": "^0.7.2", + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/node-opcua-service-translate-browse-path/-/node-opcua-service-translate-browse-path-2.1.5.tgz", + "integrity": "sha512-N8nZBgdAJrZS5Y+5EoKq5zrWFKYQ/j2QQSOalz5j+4XrS5+iqzXgMmawoRNJbYrUUKxdOkjnUP+GRgr+JPmfZg==", + "requires": { + "node-opcua-assert": "^2.0.0", + "node-opcua-basic-types": "^2.1.5", + "node-opcua-binary-stream": "^2.1.0", + "node-opcua-constants": "^2.1.5", + "node-opcua-data-model": "^2.1.5", + "node-opcua-factory": "^2.1.5", + "node-opcua-nodeid": "^2.1.5", + "node-opcua-service-secure-channel": "^2.1.5", + "node-opcua-types": "^2.1.5", "underscore": "^1.9.1" } }, "node-opcua-service-write": { - "version": "0.7.2", - "resolved": "https://registry.npmjs.org/node-opcua-service-write/-/node-opcua-service-write-0.7.2.tgz", - "integrity": "sha512-HNAst+acG/TtuRakvJzOOzTUZXZGyviJCdJQfriaRTyRsokn0GYO7dZntsehumuyn/MDqzBn4eHVfkrrv21sKw==", - "requires": { - "node-opcua-assert": "^0.7.0", - "node-opcua-basic-types": "^0.7.2", - "node-opcua-data-value": "^0.7.2", - "node-opcua-factory": "^0.7.2", - "node-opcua-nodeid": "^0.7.2", - "node-opcua-numeric-range": "^0.7.2", - "node-opcua-service-secure-channel": "^0.7.2", + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/node-opcua-service-write/-/node-opcua-service-write-2.1.5.tgz", + "integrity": "sha512-7farO9kMls8Ajzzq4gSQ+Fg39HZ6hp9YSaTb9iaBx5qbyg6sNqu2J15nxwM8lDxkut7ZPKPwl0nl2/GeWGzK2A==", + "requires": { + "node-opcua-assert": "^2.0.0", + "node-opcua-basic-types": "^2.1.5", + "node-opcua-binary-stream": "^2.1.0", + "node-opcua-data-value": "^2.1.5", + "node-opcua-factory": "^2.1.5", + "node-opcua-nodeid": "^2.1.5", + "node-opcua-numeric-range": "^2.1.5", + "node-opcua-service-secure-channel": "^2.1.5", + "node-opcua-types": "^2.1.5", "underscore": "^1.9.1" } }, "node-opcua-status-code": { - "version": "0.7.2", - "resolved": "https://registry.npmjs.org/node-opcua-status-code/-/node-opcua-status-code-0.7.2.tgz", - "integrity": "sha512-mJTrBcoZedUddQHbZNIvHPenv/gAS63FYTM6rs73lZJH/R8NikE426YGO6L56VzDq3Pgrv8H3HlIqQun/tfK4A==", + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/node-opcua-status-code/-/node-opcua-status-code-2.1.5.tgz", + "integrity": "sha512-Zz4y9OXIvNUXWrX3UPzp73risrMwas4tXUmtTweTH+XCKrDJfeZ2PbBLRIbTm+EQU7XpcAI2B9Bs8Qz821D4yA==", "requires": { - "node-opcua-assert": "^0.7.0", - "node-opcua-constants": "^0.7.0", + "node-opcua-assert": "^2.0.0", + "node-opcua-constants": "^2.1.5", "underscore": "^1.9.1" } }, "node-opcua-transport": { - "version": "0.7.2", - "resolved": "https://registry.npmjs.org/node-opcua-transport/-/node-opcua-transport-0.7.2.tgz", - "integrity": "sha512-F7yLldfcseEdPklUwhoK/57/q85qksca41eKp3Bbm4HMFIMqeNycoS1Z53D6VuiPttjAGUItOZuEbSjwq8e9KA==", - "requires": { - "node-opcua-assert": "^0.7.0", - "node-opcua-basic-types": "^0.7.2", - "node-opcua-binary-stream": "^0.7.2", - "node-opcua-buffer-utils": "^0.7.2", - "node-opcua-chunkmanager": "^0.7.2", - "node-opcua-debug": "^0.7.2", - "node-opcua-factory": "^0.7.2", - "node-opcua-nodeid": "^0.7.2", - "node-opcua-packet-assembler": "^0.7.0", - "node-opcua-status-code": "^0.7.2", - "node-opcua-utils": "^0.7.2", + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/node-opcua-transport/-/node-opcua-transport-2.1.5.tgz", + "integrity": "sha512-O4/179T0zJbvfuHnwL2Gj+2SGwlplYHYCYgcqKWnajS0Is8yfhGqc2MzOyO0S1anuYXEOI8hE/sR5TN8Q6ZZWg==", + "requires": { + "chalk": "^2.4.2", + "node-opcua-assert": "^2.0.0", + "node-opcua-basic-types": "^2.1.5", + "node-opcua-binary-stream": "^2.1.0", + "node-opcua-buffer-utils": "^2.1.0", + "node-opcua-chunkmanager": "^2.1.5", + "node-opcua-debug": "^2.1.0", + "node-opcua-factory": "^2.1.5", + "node-opcua-nodeid": "^2.1.5", + "node-opcua-packet-assembler": "^2.0.0", + "node-opcua-status-code": "^2.1.5", + "node-opcua-types": "^2.1.5", + "node-opcua-utils": "^2.1.5", + "underscore": "^1.9.1" + } + }, + "node-opcua-types": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/node-opcua-types/-/node-opcua-types-2.1.5.tgz", + "integrity": "sha512-m1uo1IHw6pCeRRu5OPzAalkvHqOetP0Pgkoj6GIUHKMQe768Uto2aR50DeRPfOauYqfg61oGxwL9UNEU0+PGKQ==", + "requires": { + "node-opcua-assert": "^2.0.0", + "node-opcua-basic-types": "^2.1.5", + "node-opcua-binary-stream": "^2.1.0", + "node-opcua-buffer-utils": "^2.1.0", + "node-opcua-data-model": "^2.1.5", + "node-opcua-data-value": "^2.1.5", + "node-opcua-date-time": "^2.1.5", + "node-opcua-debug": "^2.1.0", + "node-opcua-enum": "^2.1.0", + "node-opcua-extension-object": "^2.1.5", + "node-opcua-factory": "^2.1.5", + "node-opcua-generator": "^2.1.5", + "node-opcua-guid": "^2.1.0", + "node-opcua-nodeid": "^2.1.5", + "node-opcua-numeric-range": "^2.1.5", + "node-opcua-packet-analyzer": "^2.1.5", + "node-opcua-schemas": "^2.1.5", + "node-opcua-status-code": "^2.1.5", + "node-opcua-utils": "^2.1.5", + "node-opcua-variant": "^2.1.5", "underscore": "^1.9.1" } }, "node-opcua-utils": { - "version": "0.7.2", - "resolved": "https://registry.npmjs.org/node-opcua-utils/-/node-opcua-utils-0.7.2.tgz", - "integrity": "sha512-YXuAMsiVPWhSrL2J0rWIloudHCbj3BgE2t8YGDENpH83jwWk+1mB5bMEDiVQwM8vKEno7/jjc+CeQlkzFuJNuQ==", + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/node-opcua-utils/-/node-opcua-utils-2.1.5.tgz", + "integrity": "sha512-uVAv1Ry6uMDdoi+11u7ktDenZyeQCG44QO23MeurUuq1oeDRB1mXSaFUGIergG7YnT5v1IW83aLy53m6i3LEZw==", "requires": { - "node-opcua-assert": "^0.7.0", + "node-opcua-assert": "^2.0.0", "underscore": "^1.9.1" } }, "node-opcua-variant": { - "version": "0.7.2", - "resolved": "https://registry.npmjs.org/node-opcua-variant/-/node-opcua-variant-0.7.2.tgz", - "integrity": "sha512-sVjtIKbCyVPzANVLI8Hk4p+S9jnkGQZQMAwFIu1xeLGRu5LW0T3Z4pxHCt9dusg3uHcpjX8fndN403I6QT8i6g==", - "requires": { - "node-opcua-assert": "^0.7.0", - "node-opcua-basic-types": "^0.7.2", - "node-opcua-data-model": "^0.7.2", - "node-opcua-factory": "^0.7.2", - "node-opcua-nodeid": "^0.7.2", - "node-opcua-utils": "^0.7.2", + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/node-opcua-variant/-/node-opcua-variant-2.1.5.tgz", + "integrity": "sha512-HVv1GEIBlbrYzFRwb0yaS7DVwa8QqHuL+QpdAv6xHrL2q7N/jNMFik3RkZjNkCgT5mrIg/0qBZ9UPhwgRGAGtw==", + "requires": { + "node-opcua-assert": "^2.0.0", + "node-opcua-basic-types": "^2.1.5", + "node-opcua-data-model": "^2.1.5", + "node-opcua-enum": "^2.1.0", + "node-opcua-extension-object": "^2.1.5", + "node-opcua-factory": "^2.1.5", + "node-opcua-nodeid": "^2.1.5", + "node-opcua-utils": "^2.1.5", "underscore": "^1.9.1" } }, "node-opcua-vendor-diagnostic": { - "version": "0.7.2", - "resolved": "https://registry.npmjs.org/node-opcua-vendor-diagnostic/-/node-opcua-vendor-diagnostic-0.7.2.tgz", - "integrity": "sha512-3TX9giMPaEalKgEoub5KLaH5x7+JmB6+A9qdg3y7S//KYbB/GD6FyoIPKA0gYPO+hr2Fdm3UVNoOPdTmn+BpTA==", + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/node-opcua-vendor-diagnostic/-/node-opcua-vendor-diagnostic-2.1.5.tgz", + "integrity": "sha512-Zd+OgcuWzm9PFXVjs6brlclALXiGgWet+cOvJMC2l+ThuNOmvTlI7f9ocIdHnDvDAMlaI92mAevJiL9Wl0BJlg==", "requires": { "humanize": "0.0.9", - "node-opcua-assert": "^0.7.0", - "node-opcua-constants": "^0.7.0", - "node-opcua-server": "^0.7.2", - "node-opcua-status-code": "^0.7.2", - "node-opcua-variant": "^0.7.2" + "node-opcua-address-space": "^2.1.5", + "node-opcua-assert": "^2.0.0", + "node-opcua-constants": "^2.1.5", + "node-opcua-server": "^2.1.5", + "node-opcua-status-code": "^2.1.5", + "node-opcua-variant": "^2.1.5" } }, "node-opcua-xml2json": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/node-opcua-xml2json/-/node-opcua-xml2json-0.7.0.tgz", - "integrity": "sha512-uxPxWqQT3RC5lVxTzxEIZ1wcFZWUrAQe7mDROGgL5Kc2qESVvx12dvfjtP3VIBIt55IejdRORGS1HcehZxGpJQ==", + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/node-opcua-xml2json/-/node-opcua-xml2json-2.1.5.tgz", + "integrity": "sha512-6h2UaeSru3yhF0aAKVqRFnFjTdkRg91ZWttbHZz8k+Z+ynb70GxCfq7qR7foDjIdXBY1W41uIk4NPZ2n+TDs5g==", "requires": { - "bomstrip": "^0.1.4", + "bomstrip": "0.1.4", "ltx": "^2.8.1", - "node-opcua-assert": "^0.7.0", + "node-opcua-assert": "^2.0.0", + "node-opcua-debug": "^2.1.0", + "node-opcua-utils": "^2.1.5", "underscore": "^1.9.1" } }, + "node-pid-controller": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/node-pid-controller/-/node-pid-controller-1.0.1.tgz", + "integrity": "sha512-36gdeRz2emhIsznLpXksJSqmR13NU3vR+rkRlfHWCGGlZu9fK+dcTPRpud0FiH6b2Nhwm0kbcVk7vXFlg8Sw1w==" + }, "normalize-package-data": { "version": "2.5.0", "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", @@ -3877,9 +4064,9 @@ "dev": true }, "resolve": { - "version": "1.10.1", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.10.1.tgz", - "integrity": "sha512-KuIe4mf++td/eFb6wkaPbMDnP6kObCaEtIDuHOUED6MNUo4K670KZUHuuvYPZDxNF0WVLw49n06M2m2dXphEzA==", + "version": "1.12.0", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.12.0.tgz", + "integrity": "sha512-B/dOmuoAik5bKcD6s6nXDCjzUKnaDvdkRyAk6rsmsKLipWj4797iothd7jmmUhWTfinVMU+wc56rYKsit2Qy4w==", "dev": true, "requires": { "path-parse": "^1.0.6" @@ -3891,6 +4078,7 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz", "integrity": "sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8=", + "dev": true, "requires": { "path-key": "^2.0.0" } @@ -3898,12 +4086,13 @@ "number-is-nan": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", - "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=" + "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=", + "dev": true }, "nyc": { - "version": "14.1.0", - "resolved": "https://registry.npmjs.org/nyc/-/nyc-14.1.0.tgz", - "integrity": "sha512-iy9fEV8Emevz3z/AanIZsoGa8F4U2p0JKevZ/F0sk+/B2r9E6Qn+EPs0bpxEhnAt6UPlTL8mQZIaSJy8sK0ZFw==", + "version": "14.1.1", + "resolved": "https://registry.npmjs.org/nyc/-/nyc-14.1.1.tgz", + "integrity": "sha512-OI0vm6ZGUnoGZv/tLdZ2esSVzDwUC88SNs+6JoSOMVxA+gKMB8Tk7jBwgemLx4O40lhhvZCVw1C+OYLOBOPXWw==", "dev": true, "requires": { "archy": "^1.0.0", @@ -3939,19 +4128,15 @@ "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", "dev": true }, - "execa": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/execa/-/execa-1.0.0.tgz", - "integrity": "sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==", + "cliui": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-5.0.0.tgz", + "integrity": "sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA==", "dev": true, "requires": { - "cross-spawn": "^6.0.0", - "get-stream": "^4.0.0", - "is-stream": "^1.1.0", - "npm-run-path": "^2.0.0", - "p-finally": "^1.0.0", - "signal-exit": "^3.0.0", - "strip-eof": "^1.0.0" + "string-width": "^3.1.0", + "strip-ansi": "^5.2.0", + "wrap-ansi": "^5.1.0" } }, "find-up": { @@ -3969,19 +4154,10 @@ "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", "dev": true }, - "get-stream": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", - "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", - "dev": true, - "requires": { - "pump": "^3.0.0" - } - }, "glob": { - "version": "7.1.3", - "resolved": false, - "integrity": "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==", + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.4.tgz", + "integrity": "sha512-hkLPepehmnKk41pUGm3sYxoFs/umurYfYJCerbXEyFIWcAzvpipAgVkBqqT9RBKMGjnq6kMuyYwha6csxbiM1A==", "dev": true, "requires": { "fs.realpath": "^1.0.0", @@ -3992,21 +4168,6 @@ "path-is-absolute": "^1.0.0" } }, - "invert-kv": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-2.0.0.tgz", - "integrity": "sha512-wPVv/y/QQ/Uiirj/vh3oP+1Ww+AWehmi1g5fFWGPF6IpCBCDVrhgHRMvrLfdYcwDh3QJbGXDW4JAuzxElLSqKA==", - "dev": true - }, - "lcid": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/lcid/-/lcid-2.0.0.tgz", - "integrity": "sha512-avPEb8P8EGnwXKClwsNUgryVjllcRqtMYa49NTsbQagYuT1DcXnl1915oxWjoyGrXR6zH/Y0Zc96xWsPcoDKeA==", - "dev": true, - "requires": { - "invert-kv": "^2.0.0" - } - }, "locate-path": { "version": "3.0.0", "resolved": false, @@ -4017,38 +4178,10 @@ "path-exists": "^3.0.0" } }, - "mem": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/mem/-/mem-4.3.0.tgz", - "integrity": "sha512-qX2bG48pTqYRVmDB37rn/6PT7LcR8T7oAX3bf99u1Tt1nzxYfxkgqDwUwolPlXweM0XzBOBFzSx4kfp7KP1s/w==", - "dev": true, - "requires": { - "map-age-cleaner": "^0.1.1", - "mimic-fn": "^2.0.0", - "p-is-promise": "^2.0.0" - } - }, - "mimic-fn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", - "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", - "dev": true - }, - "os-locale": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-3.1.0.tgz", - "integrity": "sha512-Z8l3R4wYWM40/52Z+S265okfFj8Kt2cC2MKY+xNi3kFs+XGI7WXu/I309QQQYbRW4ijiZ+yxs9pqEhJh0DqW3Q==", - "dev": true, - "requires": { - "execa": "^1.0.0", - "lcid": "^2.0.0", - "mem": "^4.0.0" - } - }, "p-limit": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.2.0.tgz", - "integrity": "sha512-pZbTJpoUsCzV48Mc9Nh51VbwO0X9cuPFE8gYwx9BTCt9SF8/b7Zljd2fVgOxhIF/HDTKgpVzs+GPhyKfjLLFRQ==", + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.2.1.tgz", + "integrity": "sha512-85Tk+90UCVWvbDavCLKPOLC9vvY8OwEX/RtKF+/1OADJMVlFfEHOiMTPVyxg7mk/dKa+ipdHm0OUkTvCpMTuwg==", "dev": true, "requires": { "p-try": "^2.0.0" @@ -4086,6 +4219,17 @@ "strip-ansi": "^5.1.0" } }, + "wrap-ansi": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-5.1.0.tgz", + "integrity": "sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.0", + "string-width": "^3.0.0", + "strip-ansi": "^5.0.0" + } + }, "y18n": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.0.tgz", @@ -4093,28 +4237,27 @@ "dev": true }, "yargs": { - "version": "13.2.2", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-13.2.2.tgz", - "integrity": "sha512-WyEoxgyTD3w5XRpAQNYUB9ycVH/PQrToaTXdYXRdOXvEy1l19br+VJsc0vcO8PTGg5ro/l/GY7F/JMEBmI0BxA==", + "version": "13.3.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-13.3.0.tgz", + "integrity": "sha512-2eehun/8ALW8TLoIl7MVaRUrg+yCnenu8B4kBlRxj3GJGDKU1Og7sMXPNm1BYyM1DOJmTZ4YeN/Nwxv+8XJsUA==", "dev": true, "requires": { - "cliui": "^4.0.0", + "cliui": "^5.0.0", "find-up": "^3.0.0", "get-caller-file": "^2.0.1", - "os-locale": "^3.1.0", "require-directory": "^2.1.1", "require-main-filename": "^2.0.0", "set-blocking": "^2.0.0", "string-width": "^3.0.0", "which-module": "^2.0.0", "y18n": "^4.0.0", - "yargs-parser": "^13.0.0" + "yargs-parser": "^13.1.1" } }, "yargs-parser": { - "version": "13.1.0", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-13.1.0.tgz", - "integrity": "sha512-Yq+32PrijHRri0vVKQEm+ys8mbqWjLiwQkMFNXEENutzLPP0bE4Lcd4iA3OQY5HF+GD3xXxf0MEHb8E4/SA3AA==", + "version": "13.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-13.1.1.tgz", + "integrity": "sha512-oVAVsHz6uFrg3XQheFII8ESO2ssAf9luWuAd6Wexsu4F3OtIW0o8IribPXYrD4WC24LWtPrJlGy87y5udK+dxQ==", "dev": true, "requires": { "camelcase": "^5.0.0", @@ -4123,11 +4266,6 @@ } } }, - "oauth-sign": { - "version": "0.9.0", - "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", - "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==" - }, "object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", @@ -4195,7 +4333,7 @@ }, "optimist": { "version": "0.6.1", - "resolved": "https://registry.plt.et.tu-dresden.de:4873/optimist/-/optimist-0.6.1.tgz", + "resolved": "https://registry.npmjs.org/optimist/-/optimist-0.6.1.tgz", "integrity": "sha1-2j6nRob6IaGaERwybpDrFaAZZoY=", "dev": true, "requires": { @@ -4223,17 +4361,20 @@ "p-defer": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/p-defer/-/p-defer-1.0.0.tgz", - "integrity": "sha1-n26xgvbJqozXQwBKfU+WsZaw+ww=" + "integrity": "sha1-n26xgvbJqozXQwBKfU+WsZaw+ww=", + "dev": true }, "p-finally": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", - "integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=" + "integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=", + "dev": true }, "p-is-promise": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/p-is-promise/-/p-is-promise-2.1.0.tgz", - "integrity": "sha512-Y3W0wlRPK8ZMRbNq97l4M5otioeA5lm1z7bkNkxCka8HSPjR0xRWmpCmc9utiaLP9Jb1eD8BgeIxTW4AIF45Pg==" + "integrity": "sha512-Y3W0wlRPK8ZMRbNq97l4M5otioeA5lm1z7bkNkxCka8HSPjR0xRWmpCmc9utiaLP9Jb1eD8BgeIxTW4AIF45Pg==", + "dev": true }, "p-limit": { "version": "1.3.0", @@ -4277,9 +4418,9 @@ }, "dependencies": { "graceful-fs": { - "version": "4.1.15", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.15.tgz", - "integrity": "sha512-6uHUhOPEBgQ24HM+r6b/QwWfZq+yiFcipKFrOFiBEnWdy5sdzYoi+pJeQaPI5qOLRFqWmAXUPQNsielzdLoecA==", + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.2.tgz", + "integrity": "sha512-IItsdsea19BoLC7ELy13q1iJFNmd7ofZH5+X/pJr90/nRoPEX0DJo1dHDbgtYWOhJhcCgMDTOw84RZ72q6lB+Q==", "dev": true } } @@ -4295,9 +4436,9 @@ } }, "parseurl": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.2.tgz", - "integrity": "sha1-/CidTtiZMRlGDBViUyYs3I3mW/M=" + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==" }, "path-exists": { "version": "3.0.0", @@ -4317,7 +4458,8 @@ "path-key": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", - "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=" + "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=", + "dev": true }, "path-parse": { "version": "1.0.5", @@ -4358,28 +4500,16 @@ "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", "integrity": "sha1-elfrVQpng/kRUzH89GY9XI4AelA=" }, - "performance-now": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", - "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=" + "picomatch": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.0.7.tgz", + "integrity": "sha512-oLHIdio3tZ0qH76NybpeneBhYVj0QFTfXEFTc/B3zKQspYfYYkWYgFsmzo+4kvId/bQRcNkVeguI3y+CD22BtA==" }, "pify": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", - "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==" - }, - "pinkie": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz", - "integrity": "sha1-clVrgM+g1IqXToDnckjoDtT3+HA=" - }, - "pinkie-promise": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz", - "integrity": "sha1-ITXW36ejWMBprJsXh3YogihFD/o=", - "requires": { - "pinkie": "^2.0.0" - } + "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", + "dev": true }, "pkg-dir": { "version": "3.0.0", @@ -4410,9 +4540,9 @@ } }, "p-limit": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.2.0.tgz", - "integrity": "sha512-pZbTJpoUsCzV48Mc9Nh51VbwO0X9cuPFE8gYwx9BTCt9SF8/b7Zljd2fVgOxhIF/HDTKgpVzs+GPhyKfjLLFRQ==", + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.2.1.tgz", + "integrity": "sha512-85Tk+90UCVWvbDavCLKPOLC9vvY8OwEX/RtKF+/1OADJMVlFfEHOiMTPVyxg7mk/dKa+ipdHm0OUkTvCpMTuwg==", "dev": true, "requires": { "p-try": "^2.0.0" @@ -4440,10 +4570,16 @@ "resolved": "https://registry.npmjs.org/precond/-/precond-0.2.3.tgz", "integrity": "sha1-qpWRvKokkj8eD0hJ0kD0fvwQdaw=" }, + "prettier": { + "version": "1.18.2", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-1.18.2.tgz", + "integrity": "sha512-OeHeMc0JhFE9idD4ZdtNibzY0+TPHSpSSb9h8FqtP+YnoZZ1sl8Vc9b1sasjfymH3SonAF4QcA2+mzHPhMvIiw==" + }, "process-nextick-args": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz", - "integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw==" + "integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw==", + "dev": true }, "progress": { "version": "2.0.3", @@ -4456,12 +4592,12 @@ "integrity": "sha512-5yANTE0tmi5++POym6OgtFmwfDvOXABD9oj/jLQr5GPEyuNEb7jH4wbbANJceJid49jwhi1RddxnhnEAb/doqg==" }, "proxy-addr": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.4.tgz", - "integrity": "sha512-5erio2h9jp5CHGwcybmxmVqHmnCBZeewlfJ0pex+UW7Qny7OOZXTtH56TGNyBizkgiOwhJtMKrVzDTeKcySZwA==", + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.5.tgz", + "integrity": "sha512-t/7RxHXPH6cJtP0pRG6smSr9QJidhB+3kXu0KgXnbGYMgzEnUxRQ4/LDdfOwZEMyIh3/xHb8PX3t+lfL9z+YVQ==", "requires": { "forwarded": "~0.1.2", - "ipaddr.js": "1.8.0" + "ipaddr.js": "1.9.0" } }, "pseudomap": { @@ -4470,54 +4606,35 @@ "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM=", "dev": true }, - "psl": { - "version": "1.1.31", - "resolved": "https://registry.npmjs.org/psl/-/psl-1.1.31.tgz", - "integrity": "sha512-/6pt4+C+T+wZUieKR620OpzN/LlnNKuWjy1iFLQ/UG35JqHlR/89MP1d96dUfkf6Dne3TuLQzOYEYshJ+Hx8mw==" - }, "pump": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "dev": true, "requires": { "end-of-stream": "^1.1.0", "once": "^1.3.1" } }, - "punycode": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", - "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==" - }, "qs": { "version": "6.7.0", "resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz", "integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==" }, "range-parser": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.0.tgz", - "integrity": "sha1-9JvmtIeJTdxA3MlKMi9hEJLgDV4=" + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==" }, "raw-body": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.3.3.tgz", - "integrity": "sha512-9esiElv1BrZoI3rCDuOuKCBRbuApGGaDPQfjSflGxdy4oyzqghxu6klEkkVIvBje+FF0BX9coEv8KqW6X/7njw==", + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.0.tgz", + "integrity": "sha512-4Oz8DUIwdvoa5qMJelxipzi/iJIi40O5cGV1wNYp5hvZP8ZN0T+jiNkL0QepXs+EsQ9XJ8ipEDoiH70ySUJP3Q==", "requires": { - "bytes": "3.0.0", - "http-errors": "1.6.3", - "iconv-lite": "0.4.23", + "bytes": "3.1.0", + "http-errors": "1.7.2", + "iconv-lite": "0.4.24", "unpipe": "1.0.0" - }, - "dependencies": { - "iconv-lite": { - "version": "0.4.23", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.23.tgz", - "integrity": "sha512-neyTUVFtahjf0mB3dZT77u+8O0QB89jFdnBkd5P1JgYPbPaia3gXXOVL2fq8VyU2gMMD7SaN7QukTB/pmXYvDA==", - "requires": { - "safer-buffer": ">= 2.1.2 < 3" - } - } } }, "read-pkg": { @@ -4561,9 +4678,9 @@ } }, "p-limit": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.2.0.tgz", - "integrity": "sha512-pZbTJpoUsCzV48Mc9Nh51VbwO0X9cuPFE8gYwx9BTCt9SF8/b7Zljd2fVgOxhIF/HDTKgpVzs+GPhyKfjLLFRQ==", + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.2.1.tgz", + "integrity": "sha512-85Tk+90UCVWvbDavCLKPOLC9vvY8OwEX/RtKF+/1OADJMVlFfEHOiMTPVyxg7mk/dKa+ipdHm0OUkTvCpMTuwg==", "dev": true, "requires": { "p-try": "^2.0.0" @@ -4590,6 +4707,7 @@ "version": "2.3.6", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", + "dev": true, "requires": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", @@ -4602,7 +4720,7 @@ }, "rechoir": { "version": "0.6.2", - "resolved": "https://registry.plt.et.tu-dresden.de:4873/rechoir/-/rechoir-0.6.2.tgz", + "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.6.2.tgz", "integrity": "sha1-hSBLVNuoLVdC4oyWdW70OvUOM4Q=", "dev": true, "requires": { @@ -4623,45 +4741,6 @@ "es6-error": "^4.0.1" } }, - "request": { - "version": "2.88.0", - "resolved": "https://registry.npmjs.org/request/-/request-2.88.0.tgz", - "integrity": "sha512-NAqBSrijGLZdM0WZNsInLJpkJokL72XYjUpnB0iwsRgxh7dB6COrHnTBNwN0E+lHDAJzu7kLAkDeY08z2/A0hg==", - "requires": { - "aws-sign2": "~0.7.0", - "aws4": "^1.8.0", - "caseless": "~0.12.0", - "combined-stream": "~1.0.6", - "extend": "~3.0.2", - "forever-agent": "~0.6.1", - "form-data": "~2.3.2", - "har-validator": "~5.1.0", - "http-signature": "~1.2.0", - "is-typedarray": "~1.0.0", - "isstream": "~0.1.2", - "json-stringify-safe": "~5.0.1", - "mime-types": "~2.1.19", - "oauth-sign": "~0.9.0", - "performance-now": "^2.1.0", - "qs": "~6.5.2", - "safe-buffer": "^5.1.2", - "tough-cookie": "~2.4.3", - "tunnel-agent": "^0.6.0", - "uuid": "^3.3.2" - }, - "dependencies": { - "qs": { - "version": "6.5.2", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", - "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==" - }, - "safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" - } - } - }, "require-directory": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", @@ -4688,6 +4767,11 @@ "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", "dev": true }, + "reusify": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==" + }, "rimraf": { "version": "2.6.3", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz", @@ -4711,6 +4795,11 @@ } } }, + "run-parallel": { + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.1.9.tgz", + "integrity": "sha512-DEqnSRTDw/Tc3FXf49zedI638Z9onwUotBMiUFKmrO2sdFKIbXamXGQ3Axd4qgphxKB4kw/qP1w5kTxnfU1B9Q==" + }, "safe-buffer": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz", @@ -4728,9 +4817,9 @@ "dev": true }, "send": { - "version": "0.16.2", - "resolved": "https://registry.npmjs.org/send/-/send-0.16.2.tgz", - "integrity": "sha512-E64YFPUssFHEFBvpbbjr44NCLtI1AohxQ8ZSiJjQLskAdKuriYEP6VyGEsRDH8ScozGpkaX1BGvhanqCwkcEZw==", + "version": "0.17.1", + "resolved": "https://registry.npmjs.org/send/-/send-0.17.1.tgz", + "integrity": "sha512-BsVKsiGcQMFwT8UxypobUKyv7irCNRHk1T0G680vk88yf6LBByGcZJOTJCrTP2xVN6yI+XjPJcNuE3V4fT9sAg==", "requires": { "debug": "2.6.9", "depd": "~1.1.2", @@ -4739,30 +4828,35 @@ "escape-html": "~1.0.3", "etag": "~1.8.1", "fresh": "0.5.2", - "http-errors": "~1.6.2", - "mime": "1.4.1", - "ms": "2.0.0", + "http-errors": "~1.7.2", + "mime": "1.6.0", + "ms": "2.1.1", "on-finished": "~2.3.0", - "range-parser": "~1.2.0", - "statuses": "~1.4.0" + "range-parser": "~1.2.1", + "statuses": "~1.5.0" }, "dependencies": { - "statuses": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.4.0.tgz", - "integrity": "sha512-zhSCtt8v2NDrRlPQpCNtw/heZLtfUDqxBM1udqikb/Hbk52LK4nQSwr10u77iopCW5LsyHpuXS0GnEc48mLeew==" + "mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==" + }, + "ms": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", + "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==" } } }, "serve-static": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.13.2.tgz", - "integrity": "sha512-p/tdJrO4U387R9oMjb1oj7qSMaMfmOyd4j9hOFoxZe2baQszgHcSWjuya/CiT5kgZZKRudHNOA0pYXOl8rQ5nw==", + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.14.1.tgz", + "integrity": "sha512-JMrvUwE54emCYWlTI+hGrGv5I8dEwmco/00EvkzIIsR7MqrHonbD9pO2MOfFnpFntl7ecpZs+3mW+XbQZu9QCg==", "requires": { "encodeurl": "~1.0.2", "escape-html": "~1.0.3", - "parseurl": "~1.3.2", - "send": "0.16.2" + "parseurl": "~1.3.3", + "send": "0.17.1" } }, "set-blocking": { @@ -4776,14 +4870,15 @@ "integrity": "sha1-gCIdbaDsaFEd3HQ5CXV60UT8Hf0=" }, "setprototypeof": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz", - "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==" + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz", + "integrity": "sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw==" }, "shebang-command": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=", + "dev": true, "requires": { "shebang-regex": "^1.0.0" } @@ -4791,7 +4886,8 @@ "shebang-regex": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", - "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=" + "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=", + "dev": true }, "shelljs": { "version": "0.8.3", @@ -4807,7 +4903,8 @@ "signal-exit": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz", - "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=" + "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=", + "dev": true }, "simple-swizzle": { "version": "0.2.2", @@ -4826,17 +4923,20 @@ } } }, + "slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==" + }, "source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" }, "source-map-support": { "version": "0.5.12", "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.12.tgz", "integrity": "sha512-4h2Pbvyy15EE02G+JOZpUCmqWJuqrs+sEkzewTm++BPi7Hvn/HwcqLAcNxYAyI0x13CpPPn+kMjl+hplXMHITQ==", - "dev": true, "requires": { "buffer-from": "^1.0.0", "source-map": "^0.6.0" @@ -4883,9 +4983,9 @@ } }, "spdx-license-ids": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.4.tgz", - "integrity": "sha512-7j8LYJLeY/Yb6ACbQ7F76qy5jHkp0U6jgBfJsk97bwWlVUnUWsAgpyaCvo17h0/RQGnQ036tVDomiwoI4pDkQA==", + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.5.tgz", + "integrity": "sha512-J+FWzZoynJEXGphVIS+XEh3kFSjZX/1i9gFBaWQcB+/tmpe2qUsSBABpcxqxnAxFdiUFEgAX1bjYGQvIZmoz9Q==", "dev": true }, "sprintf-js": { @@ -4903,32 +5003,6 @@ "ctype": "0.5.2" } }, - "sshpk": { - "version": "1.16.1", - "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.16.1.tgz", - "integrity": "sha512-HXXqVUq7+pcKeLqqZj6mHFUMvXtOJt1uoUx09pFW6011inTMxqI8BA8PM95myrIyyKwdnzjdFjLiE6KBPVtJIg==", - "requires": { - "asn1": "~0.2.3", - "assert-plus": "^1.0.0", - "bcrypt-pbkdf": "^1.0.0", - "dashdash": "^1.12.0", - "ecc-jsbn": "~0.1.1", - "getpass": "^0.1.1", - "jsbn": "~0.1.0", - "safer-buffer": "^2.0.2", - "tweetnacl": "~0.14.0" - }, - "dependencies": { - "asn1": { - "version": "0.2.4", - "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz", - "integrity": "sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==", - "requires": { - "safer-buffer": "~2.1.0" - } - } - } - }, "stack-generator": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/stack-generator/-/stack-generator-1.1.0.tgz", @@ -4996,6 +5070,7 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", + "dev": true, "requires": { "is-fullwidth-code-point": "^2.0.0", "strip-ansi": "^4.0.0" @@ -5005,6 +5080,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "dev": true, "requires": { "ansi-regex": "^3.0.0" } @@ -5015,6 +5091,7 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, "requires": { "safe-buffer": "~5.1.0" } @@ -5043,7 +5120,8 @@ "strip-eof": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz", - "integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=" + "integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=", + "dev": true }, "strip-json-comments": { "version": "2.0.1", @@ -5055,6 +5133,7 @@ "version": "3.8.3", "resolved": "https://registry.npmjs.org/superagent/-/superagent-3.8.3.tgz", "integrity": "sha512-GLQtLMCoEIK4eDv6OGtkOoSMt3D+oq0y3dsxMuYuDvaNUvuT8eFBuLmfR0iYYzHC1e8hpzC6ZsxbuP6DIalMFA==", + "dev": true, "requires": { "component-emitter": "^1.2.0", "cookiejar": "^2.1.0", @@ -5072,6 +5151,7 @@ "version": "3.2.6", "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", + "dev": true, "requires": { "ms": "^2.1.1" } @@ -5079,7 +5159,8 @@ "ms": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", - "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==" + "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", + "dev": true } } }, @@ -5087,6 +5168,7 @@ "version": "4.0.2", "resolved": "https://registry.npmjs.org/supertest/-/supertest-4.0.2.tgz", "integrity": "sha512-1BAbvrOZsGA3YTCWqbmh14L0YEq0EGICX/nBnfkfVJn7SrxQV1I3pMYjSzG9y/7ZU2V9dWqyqk2POwxlb09duQ==", + "dev": true, "requires": { "methods": "^1.1.2", "superagent": "^3.8.3" @@ -5101,9 +5183,9 @@ } }, "table-layout": { - "version": "0.4.4", - "resolved": "https://registry.npmjs.org/table-layout/-/table-layout-0.4.4.tgz", - "integrity": "sha512-uNaR3SRMJwfdp9OUr36eyEi6LLsbcTqTO/hfTsNviKsNeyMBPICJCC7QXRF3+07bAP6FRwA8rczJPBqXDc0CkQ==", + "version": "0.4.5", + "resolved": "https://registry.npmjs.org/table-layout/-/table-layout-0.4.5.tgz", + "integrity": "sha512-zTvf0mcggrGeTe/2jJ6ECkJHAQPIYEwDoqsiqBjI24mvRmQbInK5jq33fyypaCBxX08hMkfmdOqj6haT33EqWw==", "requires": { "array-back": "^2.0.0", "deep-extend": "~0.6.0", @@ -5125,9 +5207,9 @@ }, "dependencies": { "glob": { - "version": "7.1.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.3.tgz", - "integrity": "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==", + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.4.tgz", + "integrity": "sha512-hkLPepehmnKk41pUGm3sYxoFs/umurYfYJCerbXEyFIWcAzvpipAgVkBqqT9RBKMGjnq6kMuyYwha6csxbiM1A==", "dev": true, "requires": { "fs.realpath": "^1.0.0", @@ -5176,22 +5258,19 @@ "integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=", "dev": true }, - "tough-cookie": { - "version": "2.4.3", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.4.3.tgz", - "integrity": "sha512-Q5srk/4vDM54WJsJio3XNn6K2sCG+CQ8G5Wz6bZhRZoAe/+TxjWB/GlFAnYEbkYVlON9FMk/fE3h2RLpPXo4lQ==", + "to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", "requires": { - "psl": "^1.1.24", - "punycode": "^1.4.1" - }, - "dependencies": { - "punycode": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", - "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=" - } + "is-number": "^7.0.0" } }, + "toidentifier": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz", + "integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==" + }, "trim-right": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/trim-right/-/trim-right-1.0.1.tgz", @@ -5205,9 +5284,9 @@ "dev": true }, "ts-node": { - "version": "8.2.0", - "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-8.2.0.tgz", - "integrity": "sha512-m8XQwUurkbYqXrKqr3WHCW310utRNvV5OnRVeISeea7LoCWVcdfeB/Ntl8JYWFh+WRoUAdBgESrzKochQt7sMw==", + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-8.3.0.tgz", + "integrity": "sha512-dyNS/RqyVTDcmNM4NIBAeDMpsAdaQ+ojdf0GOLqE6nwJOgzEkdRNzJywhDfwnuvB10oa6NLVG1rUJQCpRN7qoQ==", "dev": true, "requires": { "arg": "^4.1.0", @@ -5226,15 +5305,15 @@ } }, "tslib": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.9.3.tgz", - "integrity": "sha512-4krF8scpejhaOgqzBEcGM7yDIEfi0/8+8zDRZhNZZ2kjmHJ4hv3zCbQWxoJGz1iw5U0Jl0nma13xzHXcncMavQ==", + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.10.0.tgz", + "integrity": "sha512-qOebF53frne81cf0S9B41ByenJ3/IuH8yJKngAX35CmiZySA0khhkovshKK+jGCaMnVomla7gVlIcc3EvKPbTQ==", "dev": true }, "tslint": { - "version": "5.16.0", - "resolved": "https://registry.npmjs.org/tslint/-/tslint-5.16.0.tgz", - "integrity": "sha512-UxG2yNxJ5pgGwmMzPMYh/CCnCnh0HfPgtlVRDs1ykZklufFBL1ZoTlWFRz2NQjcoEiDoRp+JyT0lhBbbH/obyA==", + "version": "5.19.0", + "resolved": "https://registry.npmjs.org/tslint/-/tslint-5.19.0.tgz", + "integrity": "sha512-1LwwtBxfRJZnUvoS9c0uj8XQtAnyhWr9KlNvDIdB+oXyT+VpsOAaEhEgKi1HrZ8rq0ki/AAnbGSv4KM6/AfVZw==", "dev": true, "requires": { "@babel/code-frame": "^7.0.0", @@ -5243,7 +5322,7 @@ "commander": "^2.12.1", "diff": "^3.2.0", "glob": "^7.1.1", - "js-yaml": "^3.13.0", + "js-yaml": "^3.13.1", "minimatch": "^3.0.4", "mkdirp": "^0.5.1", "resolve": "^1.3.2", @@ -5266,19 +5345,6 @@ "resolved": "https://registry.npmjs.org/tunnel/-/tunnel-0.0.6.tgz", "integrity": "sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg==" }, - "tunnel-agent": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", - "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", - "requires": { - "safe-buffer": "^5.0.1" - } - }, - "tweetnacl": { - "version": "0.14.5", - "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", - "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=" - }, "type-detect": { "version": "4.0.8", "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", @@ -5286,12 +5352,27 @@ "dev": true }, "type-is": { - "version": "1.6.16", - "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.16.tgz", - "integrity": "sha512-HRkVv/5qY2G6I8iab9cI7v1bOIdhm94dVjQCPFElW9W+3GeDOSHmy2EBYe4VTApuzolPcmgFTN3ftVJRKR2J9Q==", + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", "requires": { "media-typer": "0.3.0", - "mime-types": "~2.1.18" + "mime-types": "~2.1.24" + }, + "dependencies": { + "mime-db": { + "version": "1.40.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.40.0.tgz", + "integrity": "sha512-jYdeOMPy9vnxEqFRRo6ZvTZ8d9oPb+k18PKoYNYUe2stVEBPPwsln/qWzdbmaIvnhZ9v2P+CuecK+fpUfsV2mA==" + }, + "mime-types": { + "version": "2.1.24", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.24.tgz", + "integrity": "sha512-WaFHS3MCl5fapm3oLxU4eYDw77IQM2ACcxQ9RIxfaC3ooc6PFuBMGZZsYpvoXS5D5QTWPieo1jjLdAm3TBP3cQ==", + "requires": { + "mime-db": "1.40.0" + } + } } }, "typedoc": { @@ -5329,14 +5410,14 @@ }, "typedoc-default-themes": { "version": "0.5.0", - "resolved": "https://registry.plt.et.tu-dresden.de:4873/typedoc-default-themes/-/typedoc-default-themes-0.5.0.tgz", + "resolved": "https://registry.npmjs.org/typedoc-default-themes/-/typedoc-default-themes-0.5.0.tgz", "integrity": "sha1-bcJDPnjti+qOiHo6zeLzF4W9Yic=", "dev": true }, "typescript": { - "version": "3.4.5", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.4.5.tgz", - "integrity": "sha512-YycBxUb49UUhdNMU5aJ7z5Ej2XGmaIBL0x34vZ82fn3hGvD+bgrMrVDpatgz2f7YxUMJxMkbWxJZeAvDxVe7Vw==", + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.6.2.tgz", + "integrity": "sha512-lmQ4L+J6mnu3xweP8+rOrUwzmN+MRAj7TgtJtDaXE5PMyX2kCrklhg3rvOsOIfNeAWMQWO2F1GPc1kMD2vLAfw==", "dev": true }, "typescript-logging": { @@ -5394,18 +5475,11 @@ "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=" }, - "uri-js": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz", - "integrity": "sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ==", - "requires": { - "punycode": "^2.1.0" - } - }, "util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", + "dev": true }, "utils-merge": { "version": "1.0.1", @@ -5413,9 +5487,9 @@ "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=" }, "uuid": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz", - "integrity": "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA==" + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.3.tgz", + "integrity": "sha512-pW0No1RGHgzlpHJO1nsVrHKpOEIxkGg1xB+v0ZmdNH5OAeAwzAVrCnI2/6Mtx+Uys6iaylxa+D3g4j63IKKjSQ==" }, "validate-npm-package-license": { "version": "3.0.4", @@ -5432,16 +5506,6 @@ "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=" }, - "verror": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", - "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=", - "requires": { - "assert-plus": "^1.0.0", - "core-util-is": "1.0.2", - "extsprintf": "^1.2.0" - } - }, "walk": { "version": "2.3.14", "resolved": "https://registry.npmjs.org/walk/-/walk-2.3.14.tgz", @@ -5470,6 +5534,7 @@ "version": "1.3.1", "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "dev": true, "requires": { "isexe": "^2.0.0" } @@ -5517,7 +5582,7 @@ }, "wordwrap": { "version": "0.0.3", - "resolved": "https://registry.plt.et.tu-dresden.de:4873/wordwrap/-/wordwrap-0.0.3.tgz", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.3.tgz", "integrity": "sha1-o9XabNXAvAAI03I0u68b7WMFkQc=", "dev": true }, @@ -5534,6 +5599,7 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz", "integrity": "sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU=", + "dev": true, "requires": { "string-width": "^1.0.1", "strip-ansi": "^3.0.1" @@ -5542,12 +5608,14 @@ "ansi-regex": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=" + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", + "dev": true }, "is-fullwidth-code-point": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", + "dev": true, "requires": { "number-is-nan": "^1.0.0" } @@ -5556,6 +5624,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", + "dev": true, "requires": { "code-point-at": "^1.0.0", "is-fullwidth-code-point": "^1.0.0", @@ -5566,6 +5635,7 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "dev": true, "requires": { "ansi-regex": "^2.0.0" } @@ -5578,9 +5648,9 @@ "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" }, "write-file-atomic": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-2.4.2.tgz", - "integrity": "sha512-s0b6vB3xIVRLWywa6X9TOMA7k9zio0TMOsl9ZnDkliA/cfJlpHXAscj0gbHVJiTdIuAYpIyqS5GW91fqm6gG5g==", + "version": "2.4.3", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-2.4.3.tgz", + "integrity": "sha512-GaETH5wwsX+GcnzhPgKcKjJ6M2Cq3/iZp1WyY/X1CSqrW+jVNM9Y7D8EC2sM4ZG/V8wZlSniJnCKWPmBYAucRQ==", "dev": true, "requires": { "graceful-fs": "^4.1.11", @@ -5589,9 +5659,9 @@ } }, "ws": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-7.0.0.tgz", - "integrity": "sha512-cknCal4k0EAOrh1SHHPPWWh4qm93g1IuGGGwBjWkXmCG7LsDtL8w9w+YVfaF+KSVwiHQKDIMsSLBVftKf9d1pg==", + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.1.2.tgz", + "integrity": "sha512-gftXq3XI81cJCgkUiAVixA0raD9IVmXqsylCrjRygw4+UOOGzPoxnQ6r/CnVL9i+mDncJo94tSkyrtuuQVBmrg==", "requires": { "async-limiter": "^1.0.0" } @@ -5746,9 +5816,9 @@ } }, "p-limit": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.2.0.tgz", - "integrity": "sha512-pZbTJpoUsCzV48Mc9Nh51VbwO0X9cuPFE8gYwx9BTCt9SF8/b7Zljd2fVgOxhIF/HDTKgpVzs+GPhyKfjLLFRQ==", + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.2.1.tgz", + "integrity": "sha512-85Tk+90UCVWvbDavCLKPOLC9vvY8OwEX/RtKF+/1OADJMVlFfEHOiMTPVyxg7mk/dKa+ipdHm0OUkTvCpMTuwg==", "dev": true, "requires": { "p-try": "^2.0.0" @@ -5811,9 +5881,9 @@ } }, "yn": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.0.tgz", - "integrity": "sha512-kKfnnYkbTfrAdd0xICNFw7Atm8nKpLcLv9AZGEt+kczL/WQVai4e2V6ZN8U/O+iI6WrNuJjNNOyu4zfhl9D3Hg==" + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", + "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==" } } } diff --git a/package.json b/package.json index eb027dfb..7cc608b7 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@p2olab/polaris-backend", - "version": "1.8.4", + "version": "1.9.0", "main": "./build/src/index.js", "types": "./build/src/index.d.ts", "description": "Server backend for POL recipe execution engine", @@ -9,16 +9,14 @@ "homepage": "https://github.com/p2o-lab/polaris-backend", "license": "MIT", "scripts": { - "dev": "nodemon", "start": "ts-node ./src/index.ts", - "start-dist": "node ./build/index.js", - "start-testserver": "ts-node ./src/testServer.ts", - "test": "nyc mocha -r ts-node/register test/**/*.ts test/**/**/*.ts", + "dist": "node ./build/index.js", + "testserver": "ts-node ./src/testServer.ts", + "test": "nyc mocha --retries 3 -r ts-node/register test/**/*.ts test/**/**/*.ts", "typedoc": "typedoc --mode file --module commonjs --out typedoc/ --target ES6 --excludePrivate --readme none src/", "apidoc": "apidoc -i src/server/ -o apidoc", "doc": "npm run apidoc && npm run typedoc", - "build": "rm -rf build/ && tsc", - "build-all": "npm run build && npm run doc" + "build": "tsc" }, "bin": { "polaris-backend": "./bin/polaris-backend" @@ -28,50 +26,51 @@ "assets" ], "dependencies": { - "@p2olab/polaris-interface": "^1.23.0", - "@types/chai-as-promised": "^7.1.0", - "assign-deep": "^1.0.0", - "chai-as-promised": "^7.1.1", + "@p2olab/polaris-interface": "^1.27.0", + "assign-deep": "^1.0.1", "command-line-args": "^5.1.1", "command-line-usage": "^5.0.5", "cors": "^2.8.5", - "expr-eval": "^1.2.2", - "express": "^4.16.4", + "expr-eval": "^2.0.1", + "express": "^4.17.1", "express-async-handler": "^1.1.4", + "javascript-state-machine": "^3.1.0", "json-parse-better-errors": "^1.0.2", - "node-opcua": "^0.7.2", + "node-opcua": "^2.1.5", + "node-pid-controller": "^1.0.1", "promise-timeout": "^1.3.0", - "request": "^2.88.0", - "supertest": "^4.0.2", "timeout-as-promise": "^1.0.0", "typescript-logging": "^0.6.3", - "uuid": "^3.3.2", - "ws": "^7.0.0", - "yn": "^3.1.0" + "uuid": "^3.3.3", + "ws": "^7.1.2", + "yn": "^3.1.1" }, "devDependencies": { - "@types/body-parser": "^1.17.0", - "@types/chai": "^4.1.7", + "@types/body-parser": "^1.17.1", + "@types/chai": "^4.2.0", + "@types/chai-as-promised": "^7.1.2", "@types/command-line-args": "^5.0.0", "@types/command-line-usage": "^5.0.1", - "@types/cors": "^2.8.5", - "@types/express": "^4.16.1", - "@types/mocha": "^5.2.6", - "@types/multer": "^1.3.7", - "@types/node": "^12.0.8", - "@types/uuid": "^3.4.4", - "@types/ws": "^6.0.1", + "@types/cors": "^2.8.6", + "@types/express": "^4.17.1", + "@types/mocha": "^5.2.7", + "@types/multer": "^1.3.9", + "@types/node": "^12.7.5", + "@types/uuid": "^3.4.5", + "@types/ws": "^6.0.3", "apidoc": "^0.17.7", "chai": "^4.2.0", + "chai-as-promised": "^7.1.1", "chai-http": "^4.3.0", - "mocha": "^6.1.4", + "mocha": "^6.2.0", "mocha-typescript": "^1.1.16", - "nyc": "^14.1.0", + "nyc": "^14.1.1", "strict-event-emitter-types": "^2.0.0", - "ts-node": "^8.2.0", - "tslint": "^5.16.0", + "supertest": "^4.0.2", + "ts-node": "^8.3.0", + "tslint": "^5.19.0", "typedoc": "^0.14.2", - "typescript": "^3.4.5" + "typescript": "^3.6.2" }, "nyc": { "include": [ diff --git a/src/achema/ReactorFix.ts b/src/achema/ReactorFix.ts new file mode 100644 index 00000000..bb7fd8e6 --- /dev/null +++ b/src/achema/ReactorFix.ts @@ -0,0 +1,95 @@ +/* + * MIT License + * + * Copyright (c) 2019 Markus Graube , + * Chair for Process Control Systems, Technische Universität Dresden + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +/** Fix reactor of ACHEMA demonstrator. + * Set all opModes from devices to automatic and set correct default values + */ +import { ClientSession } from 'node-opcua'; +import {DataType, Variant, VariantArrayType} from 'node-opcua-variant'; +import * as delay from 'timeout-as-promise'; +import {catOpc} from '../config/logging'; + +export async function fixReactor(session: ClientSession) { + const nodeIdsReactor = [ + 'ns=3;s="AEM01"."MTP_AnaDrv"."OpMode"', + 'ns=3;s="MFH01"."MTP_BinVlv"."OpMode"', + 'ns=3;s="MFH02"."MTP_BinVlv"."OpMode"', + 'ns=3;s="MFH03"."MTP_BinVlv"."OpMode"']; + + const valuesReactor = [ + ['ns=3;s="Fill_Level_Max"."MTP"."VExt"', 1.5], + ['ns=3;s="Stir_Level_Min"."MTP"."VExt"', 0.5], + ['ns=3;s="Stir_Period"."MTP"."VExt"', 0.5], + ['ns=3;s="Stir_Period"."MTP"."VOp"', 0.5], + ['ns=3;s="Empty_Level_Tank_Deadband"."MTP"."VExt"', 0.5], + ['ns=3;s="Empty_Level_Tank_Deadband"."MTP"."VExt"', 0.5], + ['ns=3;s="Empty_Level_Tank"."MTP"."VExt"', 0.5], + ['ns=3;s="Empty_Level_Tank"."MTP"."VOp"', 0.5], + ['ns=3;s="Empty_Vol_Flow"."MTP"."VOp"', 2.5], + ['ns=3;s="Empty_Vol_Flow"."MTP"."VExt"', 2.5], + ]; + + catOpc.info(`Fixing nodes in reactor PEA server`); + + // first set to manual + await Promise.all(nodeIdsReactor.map((nodeId) => { + return session.writeSingleNode( + nodeId, + Variant.coerce({ + dataType: DataType.UInt32, + value: 16, + arrayType: VariantArrayType.Scalar, + dimensions: null + }) + ); + })); + await delay(200); + + // then to automatic + await Promise.all(nodeIdsReactor.map((nodeId) => { + return session.writeSingleNode( + nodeId, + Variant.coerce({ + dataType: DataType.UInt32, + value: 64, + arrayType: VariantArrayType.Scalar, + }) + ); + })); + + // give all Parameters nice default values + await Promise.all(valuesReactor.map(async (item) => { + return session.writeSingleNode( + item[0], + Variant.coerce({ + dataType: DataType.Float, + value: item[1], + arrayType: VariantArrayType.Scalar, + }) + ); + })); + + catOpc.info(`Nodes in reactor PEA server fixed`); +} diff --git a/src/config/CustomLogger.ts b/src/config/CustomLogger.ts index 302c8ee5..099155aa 100644 --- a/src/config/CustomLogger.ts +++ b/src/config/CustomLogger.ts @@ -42,6 +42,7 @@ export class CustomLogger extends AbstractCategoryLogger { // Note: we use createDefaultLogMessage() to spit it out formatted and all // however you're free to print in any way you like, the data is all // present on the message. + /* tslint:disable:no-console */ const message = this.createDefaultLogMessage(msg); this.messages.push(message); console.log(message); diff --git a/src/config/logging.ts b/src/config/logging.ts index 9cc36af6..45560bdd 100644 --- a/src/config/logging.ts +++ b/src/config/logging.ts @@ -36,6 +36,7 @@ import {CustomLogger} from './CustomLogger'; // Create categories, they will autoregister themselves export const catRecipe = new Category('recipe'); +export const catDataAssembly = new Category('dataAssembly'); export const catParameter = new Category('parameter', catRecipe); export const catScopeItem = new Category('scopeItem', catRecipe); export const catCondition = new Category('condition', catRecipe); @@ -43,34 +44,27 @@ export const catOperation = new Category('operation', catRecipe); export const catPlayer = new Category('player'); export const catModule = new Category('module'); export const catService = new Category('service'); +export const catStrategy = new Category('strategy', catService); export const catManager = new Category('manager'); export const catOpc = new Category('opcua'); export const catServer = new Category('server'); export const catTestServer = new Category('testserver'); +export const catVirtualService = new Category('VirtualService'); +export const catTimer = new Category('Timer', catVirtualService); + // Custom logging export const messages: string[] = []; - -let logLevel = LogLevel.Info; -switch ((process.env.LOGLEVEL||'').toUpperCase()) { - case 'ERROR': - logLevel = LogLevel.Error; - break; - case 'WARN': - logLevel = LogLevel.Warn; - break; - case 'INFO': - logLevel = LogLevel.Info; - break; - case 'DEBUG': - logLevel = LogLevel.Debug; - break; - case 'TRACE': - logLevel = LogLevel.Trace; - break; -} +const logLevelMapping = { + 'ERROR': LogLevel.Error, + 'WARN': LogLevel.Warn, + 'INFO': LogLevel.Info, + 'DEBUG': LogLevel.Debug, + 'TRACE': LogLevel.Trace +}; +const logLevel = logLevelMapping[(process.env.LOGLEVEL || '').toUpperCase()] || LogLevel.Info; // Configure to use our custom logger, note the callback which returns our CustomLogger from above. const config = new CategoryConfiguration( diff --git a/src/index.ts b/src/index.ts index 36a9ab14..4efb93b0 100644 --- a/src/index.ts +++ b/src/index.ts @@ -23,14 +23,16 @@ * SOFTWARE. */ +/* tslint:disable:no-console */ + import * as commandLineArgs from 'command-line-args'; +import * as commandLineUsage from 'command-line-usage'; import * as fs from 'fs'; import {catModule} from './config/logging'; import {Manager} from './model/Manager'; import {ExternalTrigger} from './server/ExternalTrigger'; import {Server} from './server/server'; import * as serverHandlers from './server/serverHandlers'; -import commandLineUsage = require('command-line-usage'); const optionDefinitions = [ { @@ -49,6 +51,14 @@ const optionDefinitions = [ typeLabel: '{underline recipePath[]}', description: 'path to recipe.json which should be loaded at startup' }, + { + name: 'virtualService', + alias: 'v', + type: String, + multiple: true, + typeLabel: '{underline virtualServicePath[]}', + description: 'path to virtualService.json which should be loaded at startup' + }, { name: 'help', alias: 'h', @@ -61,7 +71,9 @@ const optionDefinitions = [ type: String, multiple: true, typeLabel: '{underline opcuaEndpoint} {underline opcuaNodeid}', - description: 'Monitors an OPC UA node (specified via {underline opcuaNodeId}) on the OPC UA server (specified by {underline opcuaEndpoint}. If the node changes to true or is true when the player completes, the player start from the first recipe.' + description: 'Monitors an OPC UA node (specified via {underline opcuaNodeId}) on the OPC UA server ' + + '(specified by {underline opcuaEndpoint}. If the node changes to true or is true when the player completes, ' + + 'the player start from the first recipe.' } ]; const sections = [ @@ -72,7 +84,10 @@ const sections = [ { header: 'Synopsis', content: [ - '$ node build/index.js [{bold --module} {underline modulePath}] [{bold --recipe} {underline recipePath}] [{bold --externalTrigger} {underline opcuaEndpoint} {underline opcuaNodeid}]' + '$ node build/src/index.js [{bold --module} {underline modulePath}] ' + + '[{bold --recipe} {underline recipePath}] ' + + '[{bold --virtualService} {underline virtualServicePath}] ' + + '[{bold --externalTrigger} {underline opcuaEndpoint} {underline opcuaNodeid}]' ] }, { @@ -84,7 +99,8 @@ const sections = [ content: [ { desc: 'Watching a OPC UA server', - example: '$ node src/index.js --externalTrigger opc.tcp://127.0.0.1:53530/OPCUA/SimulationServer "ns=3;s=BooleanDataItem"' + example: '$ node build/src/index.js ' + + '--externalTrigger opc.tcp://127.0.0.1:53530/OPCUA/SimulationServer "ns=3;s=BooleanDataItem"' }] } ]; @@ -93,7 +109,7 @@ let options; try { options = commandLineArgs(optionDefinitions); } catch (err) { - console.log('Error: Could not parse command line arguments', err.toString()); + console.log('Error: Could not parse commandNode line arguments', err.toString()); console.log(commandLineUsage(sections)); } if (options) { @@ -130,6 +146,14 @@ if (options) { }); } + if (options.virtualService && options.virtualService.length > 0) { + console.log(`Load virtual service from ${options.virtualService}`); + options.virtualService.forEach((vs) => { + const vsOptions = JSON.parse(fs.readFileSync(vs).toString()); + manager.instantiateVirtualService(vsOptions); + }); + } + /* Start OPC UA external trigger */ if (options.externalTrigger) { console.log('External Trigger:', options.externalTrigger); diff --git a/src/model/Manager.ts b/src/model/Manager.ts index ae416629..7477cecc 100644 --- a/src/model/Manager.ts +++ b/src/model/Manager.ts @@ -23,76 +23,95 @@ * SOFTWARE. */ -import {Recipe} from './recipe/Recipe'; -import {catManager} from '../config/logging'; +import {ModuleInterface, ModuleOptions, + RecipeOptions, + ServiceCommand, + VirtualServiceInterface} from '@p2olab/polaris-interface'; import {EventEmitter} from 'events'; -import {Module, ModuleOptions} from './core/Module'; +import StrictEventEmitter from 'strict-event-emitter-types'; +import {catManager} from '../config/logging'; +import {ServiceLogEntry, VariableLogEntry} from '../logging/archive'; +import {ServiceState} from './core/enum'; +import {Module} from './core/Module'; import {Service} from './core/Service'; -import {ManagerInterface, RecipeOptions, ServiceCommand} from '@p2olab/polaris-interface'; import {Player} from './recipe/Player'; -import {ServiceState} from './core/enum'; -import {ServiceLogEntry, VariableLogEntry} from '../logging/archive'; -import StrictEventEmitter from 'strict-event-emitter-types'; +import {Recipe} from './recipe/Recipe'; +import {VirtualService} from './virtualService/VirtualService'; +import {VirtualServiceFactory, VirtualServiceOptions} from './virtualService/VirtualServiceFactory'; interface ManagerEvents { /** * when one service goes to *completed* - * @event + * @event recipeFinished */ recipeFinished: void; - notify: (string, any) => void; + notify: (topic: string, data: any) => void; } type ManagerEmitter = StrictEventEmitter; -export class Manager extends (EventEmitter as { new(): ManagerEmitter }) { +interface LoadModuleOptions { + module?: ModuleOptions; + modules?: ModuleOptions[]; + subplants?: Array<{ modules: ModuleOptions[] }>; +} + +export class Manager extends (EventEmitter as new() => ManagerEmitter) { + + get autoreset(): boolean { + return this._autoreset; + } + + set autoreset(value: boolean) { + catManager.info(`Set AutoReset to ${value}`); + this._autoreset = value; + } // loaded recipes - readonly recipes: Recipe[] = []; + public readonly recipes: Recipe[] = []; // loaded modules - readonly modules: Module[] = []; + public readonly modules: Module[] = []; - readonly player: Player; + // instantiated virtual services + public readonly virtualServices: VirtualService[] = []; - variableArchive: VariableLogEntry[] = []; + public readonly player: Player; - serviceArchive: ServiceLogEntry[] = []; + public variableArchive: VariableLogEntry[] = []; + + public serviceArchive: ServiceLogEntry[] = []; // autoreset determines if a service is automatically reset when private _autoreset: boolean = true; // autoreset timeout in milliseconds - private _autoreset_timeout = 500; + private _autoresetTimeout: number = 500; constructor() { super(); this.player = new Player() .on('started', () => { - this.emit('notify', 'player', this.player.json()) - }) - .on('recipeStarted', () => { - this.emit('notify', 'player', this.player.json()) + this.emit('notify', 'player', this.player.json()); }) - .on('stepFinished', () => { - this.emit('notify', 'player', this.player.json()) + .on('recipeChanged', () => { + this.emit('notify', 'player', this.player.json()); }) .on('recipeFinished', () => { - this.emit('recipeFinished'); - this.emit('notify', 'player', this.player.json()) + this.emit('notify', 'player', this.player.json()); }) .on('completed', () => { - this.emit('notify', 'player', this.player.json()) + this.emit('notify', 'player', this.player.json()); }); } - get autoreset(): boolean { - return this._autoreset; - } - - set autoreset(value: boolean) { - catManager.info(`Set AutoReset to ${value}`); - this._autoreset = value; + public getModule(moduleId: string): Module { + const module = this.modules.find((mod) => mod.id === moduleId); + if (module) { + return module; + } else { + throw Error(`Module with id ${moduleId} not found`); + } } /** @@ -102,20 +121,17 @@ export class Manager extends (EventEmitter as { new(): ManagerEmitter }) { * @param {boolean} protectedModules should modules be protected from being deleted * @returns {Module[]} created modules */ - public loadModule(options: { - module?: ModuleOptions, - modules?: ModuleOptions[], - subplants?: { modules: ModuleOptions[] }[] - }, protectedModules: boolean = false): Module[] { - let newModules: Module[] = []; + public loadModule(options: LoadModuleOptions, protectedModules: boolean = false): Module[] { + const newModules: Module[] = []; if (!options) { throw new Error('No modules defined in supplied options'); } if (options.subplants) { options.subplants.forEach((subplantOptions) => { subplantOptions.modules.forEach((moduleOptions: ModuleOptions) => { - if (this.modules.find(module => module.id === moduleOptions.id)) { + if (this.modules.find((mod) => (mod).id === moduleOptions.id)) { catManager.warn(`Module ${moduleOptions.id} already in registered modules`); + throw new Error(`Module ${moduleOptions.id} already in registered modules`); } else { newModules.push(new Module(moduleOptions, protectedModules)); } @@ -123,16 +139,18 @@ export class Manager extends (EventEmitter as { new(): ManagerEmitter }) { }); } else if (options.modules) { options.modules.forEach((moduleOptions: ModuleOptions) => { - if (this.modules.find(module => module.id === moduleOptions.id)) { + if (this.modules.find((mod) => (mod).id === moduleOptions.id)) { catManager.warn(`Module ${moduleOptions.id} already in registered modules`); + throw new Error(`Module ${moduleOptions.id} already in registered modules`); } else { newModules.push(new Module(moduleOptions, protectedModules)); } }); } else if (options.module) { - let moduleOptions = options.module; - if (this.modules.find(module => module.id === moduleOptions.id)) { + const moduleOptions = options.module; + if (this.modules.find((mod) => (mod).id === moduleOptions.id)) { catManager.warn(`Module ${moduleOptions.id} already in registered modules`); + throw new Error(`Module ${moduleOptions.id} already in registered modules`); } else { newModules.push(new Module(moduleOptions, protectedModules)); } @@ -142,13 +160,18 @@ export class Manager extends (EventEmitter as { new(): ManagerEmitter }) { this.modules.push(...newModules); newModules.forEach(async (module: Module) => { module - .on('connected', () => this.emit('notify', 'module', null)) - .on('disconnected', () => this.emit('notify', 'module', null)) + .on('connected', () => { + this.emit('notify', 'module', null); + }) + .on('disconnected', () => { + catManager.info('Module disconnected'); + this.emit('notify', 'module', null); + }) .on('controlEnable', ({service, controlEnable}) => { this.emit('notify', 'module', { - module: service.parent.id, + module: module.id, service: service.name, - controlEnable: controlEnable + controlEnable }); }) .on('variableChanged', async (data) => { @@ -166,8 +189,8 @@ export class Manager extends (EventEmitter as { new(): ManagerEmitter }) { } this.emit('notify', 'variable', logEntry); }) - .on('parameterChanged', (data) => { - data['module'] = module.id; + .on('parameterChanged', (data: any) => { + data.module = module.id; this.emit('notify', 'module', data); }) .on('commandExecuted', (data) => { @@ -175,7 +198,7 @@ export class Manager extends (EventEmitter as { new(): ManagerEmitter }) { timestampPfe: data.timestampPfe, module: module.id, service: data.service.name, - strategy: data.strategy.name, + strategy: data.strategy ? data.strategy.name : undefined, command: ServiceCommand[data.command], parameter: data.parameter ? data.parameter.map((param) => { return {name: param.name, value: param.value}; @@ -186,7 +209,7 @@ export class Manager extends (EventEmitter as { new(): ManagerEmitter }) { this.player.currentRecipeRun.serviceLog.push(logEntry); } }) - .on('stateChanged', async ({service, state, timestampPfe}) => { + .on('stateChanged', async ({service, state}) => { const logEntry: ServiceLogEntry = { timestampPfe: new Date(), module: module.id, @@ -208,7 +231,7 @@ export class Manager extends (EventEmitter as { new(): ManagerEmitter }) { this.emit('notify', 'module', { module: module.id, service: service.name, - opMode: opMode + opMode }); }) .on('serviceCompleted', (service: Service) => { @@ -219,20 +242,30 @@ export class Manager extends (EventEmitter as { new(): ManagerEmitter }) { return newModules; } - public async removeModule(moduleId) { - const module = this.modules.find(module => module.id === moduleId); - if (!module) { - throw new Error(`No Module ${moduleId} found.`); - } + catManager.info(`Remove module ${moduleId}`); + const module = this.getModule(moduleId); if (module.protected) { throw new Error(`Module ${moduleId} is protected and can't be deleted`); } + + catManager.debug(`Disconnecting module ${moduleId} ...`); + await module.disconnect() + .catch((err) => catManager.warn('Something wrong while disconnecting from module: ' + err.toString())); + + catManager.debug(`Deleting module ${moduleId} ...`); const index = this.modules.indexOf(module, 0); if (index > -1) { this.modules.splice(index, 1); } - await module.disconnect(); + } + + public getModules(): ModuleInterface[] { + return this.modules.map((module) => module.json()); + } + + public getVirtualServices(): VirtualServiceInterface[] { + return this.virtualServices.map((vs) => vs.json()); } public loadRecipe(options: RecipeOptions, protectedRecipe: boolean = false): Recipe { @@ -245,7 +278,7 @@ export class Manager extends (EventEmitter as { new(): ManagerEmitter }) { /** * Abort all services from all loaded modules */ - abortAllServices() { + public abortAllServices() { const tasks = []; tasks.push(this.modules.map((module) => module.abort())); return Promise.all(tasks); @@ -254,7 +287,7 @@ export class Manager extends (EventEmitter as { new(): ManagerEmitter }) { /** * Stop all services from all loaded modules */ - stopAllServices() { + public stopAllServices() { const tasks = []; tasks.push(this.modules.map((module) => module.stop())); return Promise.all(tasks); @@ -263,48 +296,15 @@ export class Manager extends (EventEmitter as { new(): ManagerEmitter }) { /** * Reset all services from all loaded modules */ - resetAllServices() { + public resetAllServices() { const tasks = []; tasks.push(this.modules.map((module) => module.reset())); return Promise.all(tasks); } - /** - * get ManagerInterfacie as JSON - * - * @returns {ManagerInterface} - */ - json(): ManagerInterface { - return { - activeRecipe: this.player.getCurrentRecipe() ? this.player.getCurrentRecipe().json() : undefined, - modules: this.modules.map(module => module.id), - autoReset: this.autoreset - }; - } - - /** - * Perform autoreset for service (bring it automatically from completed to idle) - * @param {Service} service - */ - private performAutoReset(service: Service) { - if (this.autoreset) { - catManager.info(`Service ${service.parent.id}.${service.name} completed. Short waiting time (${this._autoreset_timeout}) to autoreset`); - setTimeout(async () => { - if (service.parent.isConnected() && await service.getServiceState() === ServiceState.COMPLETED) { - catManager.info(`Service ${service.parent.id}.${service.name} completed. Now perform autoreset`); - try { - service.execute(ServiceCommand.reset); - } catch (err) { - catManager.debug('Autoreset not possible') - } - } - }, this._autoreset_timeout); - } - } - public removeRecipe(recipeId: string) { catManager.debug(`Remove recipe ${recipeId}`); - const recipe = this.recipes.find(recipe => recipe.id === recipeId); + const recipe = this.recipes.find((rec) => rec.id === recipeId); if (!recipe) { throw new Error(`Recipe ${recipeId} not available.`); } @@ -319,20 +319,117 @@ export class Manager extends (EventEmitter as { new(): ManagerEmitter }) { } /** - * find [TestServerService] of a [Module] registered in manager + * find [Service] of a [Module] registered in manager * @param {string} moduleName * @param {string} serviceName * @returns {Service} */ - getService(moduleName: string, serviceName: string): Service { - const module: Module = this.modules.find(module => module.id === moduleName); + public getService(moduleName: string, serviceName: string): Service { + const module: Module = this.modules.find((mod) => mod.id === moduleName); if (!module) { throw new Error(`Module with id ${moduleName} not registered`); } - const service: Service = module.services.find(service => service.name === serviceName); + const service: Service = module.services.find((serv) => (serv).name === serviceName); if (!service) { throw new Error(`Service ${serviceName} does not exist on module ${moduleName}`); } return service; } + + public instantiateVirtualService(options: VirtualServiceOptions) { + const virtualService = VirtualServiceFactory.create(options); + catManager.info(`instantiated virtual Service ${virtualService.name}`); + virtualService.eventEmitter + .on('controlEnable', (controlEnable) => { + this.emit('notify', 'virtualService', { + service: virtualService.name, + controlEnable + }); + }) + /*.on('variableChanged', async (data) => { + const logEntry: VariableLogEntry = { + timestampPfe: new Date(), + module: 'virtualServices', + value: data.value, + variable: data.parameter, + unit: data.parameter.unit + }; + this.variableArchive.push(logEntry); + if (this.player.currentRecipeRun) { + this.player.currentRecipeRun.variableLog.push(logEntry); + } + this.emit('notify', 'variable', logEntry); + })*/ + .on('parameterChanged', (data: any) => { + this.emit('notify', 'virtualService', data); + }) + .on('commandExecuted', (data) => { + const logEntry: ServiceLogEntry = { + timestampPfe: new Date(), + module: 'virtualServices', + service: virtualService.name, + strategy: null, + command: ServiceCommand[data.command], + parameter: data.parameter ? data.parameter.map((param) => { + return {name: param.name, value: param.value}; + }) : undefined + }; + this.serviceArchive.push(logEntry); + if (this.player.currentRecipeRun) { + this.player.currentRecipeRun.serviceLog.push(logEntry); + } + }) + .on('state', (state) => { + const logEntry: ServiceLogEntry = { + timestampPfe: new Date(), + module: 'virtualServices', + service: virtualService.name, + state: ServiceState[state] + }; + this.serviceArchive.push(logEntry); + if (this.player.currentRecipeRun) { + this.player.currentRecipeRun.serviceLog.push(logEntry); + } + this.emit('notify', 'module', { + module: 'virtualServices', + service: virtualService.name, + status: ServiceState[state], + lastChange: 0 + }); + }); + this.virtualServices.push(virtualService); + } + + public removeVirtualService(virtualServiceId: string) { + catManager.debug(`Remove Virtual Service ${virtualServiceId}`); + const index = this.virtualServices.findIndex((virtualService) => virtualService.name === virtualServiceId); + if (index === -1) { + throw new Error(`Virtual Service ${virtualServiceId} not available.`); + } + if (index > -1) { + this.virtualServices.splice(index, 1); + } + } + + /** + * Perform autoreset for service (bring it automatically from completed to idle) + * @param {Service} service + */ + private performAutoReset(service: Service) { + if (this.autoreset) { + catManager.info(`Service ${service.connection.id}.${service.name} completed. ` + + `Short waiting time (${this._autoresetTimeout}) to autoreset`); + setTimeout(async () => { + if (service.connection.isConnected() && service.state === ServiceState.COMPLETED) { + catManager.info(`Service ${service.connection.id}.${service.name} completed. ` + + `Now perform autoreset`); + try { + service.execute(ServiceCommand.reset); + } catch (err) { + catManager.debug('Autoreset not possible'); + } + } + }, this._autoresetTimeout); + } + } } diff --git a/src/model/condition/AggregateCondition.ts b/src/model/condition/AggregateCondition.ts new file mode 100644 index 00000000..c30434e9 --- /dev/null +++ b/src/model/condition/AggregateCondition.ts @@ -0,0 +1,56 @@ +/* + * MIT License + * + * Copyright (c) 2019 Markus Graube , + * Chair for Process Control Systems, Technische Universität Dresden + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +import {AndConditionOptions, OrConditionOptions} from '@p2olab/polaris-interface'; +import {Module} from '../core/Module'; +import {Condition} from './Condition'; +import {ConditionFactory} from './ConditionFactory'; + +export abstract class AggregateCondition extends Condition { + public conditions: Condition[] = []; + + constructor(options: AndConditionOptions | OrConditionOptions, modules: Module[]) { + super(options); + this.conditions = options.conditions.map((option) => { + return ConditionFactory.create(option, modules); + }); + this._fulfilled = false; + } + + public clear() { + super.clear(); + this.conditions.forEach((cond) => cond.clear()); + } + + public getUsedModules(): Set { + const set = new Set(); + this.conditions.forEach((cond) => { + Array.from(cond.getUsedModules()).forEach((module) => { + set.add(module); + }); + }); + return set; + } +} diff --git a/src/model/condition/AndCondition.ts b/src/model/condition/AndCondition.ts new file mode 100644 index 00000000..87939b1e --- /dev/null +++ b/src/model/condition/AndCondition.ts @@ -0,0 +1,53 @@ +/* + * MIT License + * + * Copyright (c) 2019 Markus Graube , + * Chair for Process Control Systems, Technische Universität Dresden + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +import {AndConditionOptions} from '@p2olab/polaris-interface'; +import {catCondition} from '../../config/logging'; +import {Module} from '../core/Module'; +import {AggregateCondition} from './AggregateCondition'; +import {Condition} from './Condition'; + +export class AndCondition extends AggregateCondition { + + constructor(options: AndConditionOptions, modules: Module[]) { + super(options, modules); + catCondition.trace(`Add AndCondition: ${options}`); + } + + public listen(): Condition { + this.conditions.forEach((condition) => { + condition.listen().on('stateChanged', (state) => { + catCondition.debug(`AndCondition: ${state} = ` + + `${JSON.stringify(this.conditions.map((item) => item.fulfilled))}`); + const oldState = this._fulfilled; + this._fulfilled = this.conditions.every((cond) => cond.fulfilled); + if (oldState !== this._fulfilled) { + this.emit('stateChanged', this._fulfilled); + } + }); + }); + return this; + } +} diff --git a/src/model/condition/Condition.ts b/src/model/condition/Condition.ts new file mode 100644 index 00000000..4ba95280 --- /dev/null +++ b/src/model/condition/Condition.ts @@ -0,0 +1,76 @@ +/* + * MIT License + * + * Copyright (c) 2019 Markus Graube , + * Chair for Process Control Systems, Technische Universität Dresden + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +import {ConditionOptions} from '@p2olab/polaris-interface'; +import {EventEmitter} from 'events'; +import StrictEventEmitter from 'strict-event-emitter-types'; +import {Module} from '../core/Module'; + +/** + * Events emitted by [[Condition]] + */ +interface ConditionEvents { + /** + * Notify when the condition changes its state. Parameter is a boolean representing if condition is fulfilled. + * @event stateChanged + */ + stateChanged: boolean; +} + +type ConditionEmitter = StrictEventEmitter; + +export abstract class Condition extends (EventEmitter as new() => ConditionEmitter) { + + get fulfilled(): boolean { + return this._fulfilled; + } + + protected _fulfilled: boolean = false; + private options: ConditionOptions; + + constructor(options: ConditionOptions) { + super(); + this.options = options; + } + + /** + * Listen to any change in condition and inform via 'stateChanged' event + */ + public abstract listen(): Condition; + + /** + * Clear listening on condition + */ + public clear() { + this._fulfilled = undefined; + this.removeAllListeners('stateChanged'); + } + + public abstract getUsedModules(): Set; + + public json(): ConditionOptions { + return this.options; + } +} diff --git a/src/model/condition/ConditionFactory.ts b/src/model/condition/ConditionFactory.ts new file mode 100644 index 00000000..bdd0f128 --- /dev/null +++ b/src/model/condition/ConditionFactory.ts @@ -0,0 +1,72 @@ +/* + * MIT License + * + * Copyright (c) 2019 Markus Graube , + * Chair for Process Control Systems, Technische Universität Dresden + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +/** + * Create Condition + * @param {ConditionOptions} options options for creating Condition + * @param {Module[]} modules modules to be used for evaluating module name in expressions + * @returns Condition + */ +import { + AndConditionOptions, ConditionOptions, ConditionType, ExpressionConditionOptions, NotConditionOptions, + OrConditionOptions, + StateConditionOptions, + TimeConditionOptions, VariableConditionOptions +} from '@p2olab/polaris-interface'; +import {catCondition} from '../../config/logging'; +import {Module} from '../core/Module'; +import {AndCondition} from './AndCondition'; +import {Condition} from './Condition'; +import {ExpressionCondition} from './ExpressionCondition'; +import {NotCondition} from './NotCondition'; +import {OrCondition} from './OrCondition'; +import {StateCondition} from './StateCondition'; +import {TimeCondition} from './TimeCondition'; +import {TrueCondition} from './TrueCondition'; +import {VariableCondition} from './VariableCondition'; + +export class ConditionFactory { + public static create(options: ConditionOptions, modules: Module[]): Condition { + catCondition.trace(`Create Condition: ${JSON.stringify(options)}`); + const type: ConditionType = options ? options.type : null; + if (type === ConditionType.time) { + return new TimeCondition(options as TimeConditionOptions); + } else if (type === ConditionType.and) { + return new AndCondition(options as AndConditionOptions, modules); + } else if (type === ConditionType.state) { + return new StateCondition(options as StateConditionOptions, modules); + } else if (type === ConditionType.variable) { + return new VariableCondition(options as VariableConditionOptions, modules); + } else if (type === ConditionType.or) { + return new OrCondition(options as OrConditionOptions, modules); + } else if (type === ConditionType.not) { + return new NotCondition(options as NotConditionOptions, modules); + } else if (type === ConditionType.expression) { + return new ExpressionCondition(options as ExpressionConditionOptions, modules); + } else { + return new TrueCondition(options); + } + } +} diff --git a/src/model/condition/ExpressionCondition.ts b/src/model/condition/ExpressionCondition.ts new file mode 100644 index 00000000..1b8ec5c1 --- /dev/null +++ b/src/model/condition/ExpressionCondition.ts @@ -0,0 +1,100 @@ +/* + * MIT License + * + * Copyright (c) 2019 Markus Graube , + * Chair for Process Control Systems, Technische Universität Dresden + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +import {ExpressionConditionOptions, ScopeOptions} from '@p2olab/polaris-interface'; +import {Expression} from 'expr-eval'; +import {catCondition} from '../../config/logging'; +import {Module} from '../core/Module'; +import {ScopeItem} from '../recipe/ScopeItem'; +import {Condition} from './Condition'; + +export class ExpressionCondition extends Condition { + + private readonly expression: Expression; + private readonly scopeArray: ScopeItem[]; + + /** + * + * @param {ExpressionConditionOptions} options + * @param {Module[]} modules + */ + constructor(options: ExpressionConditionOptions, modules: Module[] = []) { + super(options); + catCondition.info(`Add ExpressionCondition: ${options.expression} ` + + `(${JSON.stringify(modules.map((m) => m.id))})`); + // evaluate scopeArray + this.scopeArray = (options.scope || []) + .map((item: ScopeOptions) => ScopeItem.extractFromScopeOptions(item, modules)); + + // evaluate additional variables from expression + const extraction = ScopeItem.extractFromExpressionString( + options.expression, + modules, + this.scopeArray.map((scope) => scope.name)); + this.expression = extraction.expression; + this.scopeArray.push (...extraction.scopeItems); + this._fulfilled = false; + } + + public getUsedModules(): Set { + return new Set([...this.scopeArray.map((sa) => sa.module)]); + } + + public listen(): Condition { + this.scopeArray.forEach((item) => { + item.dataAssembly.on(item.variableName, this.boundOnChanged); + }); + return this; + } + + public async onChanged() { + this._fulfilled = this.getValue() as boolean; + this.emit('stateChanged', this._fulfilled); + } + + /** + * calculate value from current scopeArray + */ + public getValue(): any { + // get current variables + const tasks = this.scopeArray.map((item) => { + return item.getScopeValue(); + }); + const assign = require('assign-deep'); + const scope = assign(...tasks); + catCondition.info(`Scope: ${JSON.stringify(scope)}`); + return this.expression.evaluate(scope); + } + + public clear() { + super.clear(); + this.scopeArray.forEach((item) => { + item.dataAssembly.removeListener(item.variableName, this.boundOnChanged); + }); + } + + private boundOnChanged = () => this.onChanged(); + +} diff --git a/src/model/condition/ModuleCondition.ts b/src/model/condition/ModuleCondition.ts new file mode 100644 index 00000000..6f16c019 --- /dev/null +++ b/src/model/condition/ModuleCondition.ts @@ -0,0 +1,49 @@ +/* + * MIT License + * + * Copyright (c) 2019 Markus Graube , + * Chair for Process Control Systems, Technische Universität Dresden + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +import {StateConditionOptions, VariableConditionOptions} from '@p2olab/polaris-interface'; +import {Module} from '../core/Module'; +import {Condition} from './Condition'; + +export abstract class ModuleCondition extends Condition { + protected readonly module: Module; + + constructor(options: StateConditionOptions | VariableConditionOptions, modules: Module[]) { + super(options); + if (options.module) { + this.module = modules.find((module) => module.id === options.module); + } else if (modules.length === 1) { + this.module = modules[0]; + } + if (!this.module) { + throw new Error(`Could not find module ${options.module} in ${JSON.stringify(modules.map((m) => m.id))}`); + } + } + + public getUsedModules() { + return new Set().add(this.module); + } + +} diff --git a/src/model/condition/NotCondition.ts b/src/model/condition/NotCondition.ts new file mode 100644 index 00000000..f9df986f --- /dev/null +++ b/src/model/condition/NotCondition.ts @@ -0,0 +1,58 @@ +/* + * MIT License + * + * Copyright (c) 2019 Markus Graube , + * Chair for Process Control Systems, Technische Universität Dresden + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +import {NotConditionOptions} from '@p2olab/polaris-interface'; +import {catCondition} from '../../config/logging'; +import {Module} from '../core/Module'; +import {Condition} from './Condition'; +import {ConditionFactory} from './ConditionFactory'; + +export class NotCondition extends Condition { + public condition: Condition; + + constructor(options: NotConditionOptions, modules: Module[]) { + super(options); + catCondition.trace(`Add NotCondition: ${options}`); + this.condition = ConditionFactory.create(options.condition, modules); + this._fulfilled = !this.condition.fulfilled; + } + + public clear() { + super.clear(); + this.condition.clear(); + } + + public listen(): Condition { + this.condition.listen().on('stateChanged', (state) => { + this._fulfilled = !state; + this.emit('stateChanged', this._fulfilled); + }); + return this; + } + + public getUsedModules(): Set { + return this.condition.getUsedModules(); + } +} diff --git a/src/model/condition/OrCondition.ts b/src/model/condition/OrCondition.ts new file mode 100644 index 00000000..a80d4f92 --- /dev/null +++ b/src/model/condition/OrCondition.ts @@ -0,0 +1,51 @@ +/* + * MIT License + * + * Copyright (c) 2019 Markus Graube , + * Chair for Process Control Systems, Technische Universität Dresden + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +import {OrConditionOptions} from '@p2olab/polaris-interface'; +import {catCondition} from '../../config/logging'; +import {Module} from '../core/Module'; +import {AggregateCondition} from './AggregateCondition'; +import {Condition} from './Condition'; + +export class OrCondition extends AggregateCondition { + + constructor(options: OrConditionOptions, modules: Module[]) { + super(options, modules); + catCondition.trace(`Add OrCondition: ${options}`); + } + + public listen(): Condition { + this.conditions.forEach((condition) => { + condition.listen().on('stateChanged', (status) => { + const oldState = this._fulfilled; + this._fulfilled = this.conditions.some((cond) => cond.fulfilled); + if (oldState !== this._fulfilled) { + this.emit('stateChanged', this._fulfilled); + } + }); + }); + return this; + } +} diff --git a/src/model/condition/StateCondition.ts b/src/model/condition/StateCondition.ts new file mode 100644 index 00000000..3758451c --- /dev/null +++ b/src/model/condition/StateCondition.ts @@ -0,0 +1,87 @@ +/* + * MIT License + * + * Copyright (c) 2019 Markus Graube , + * Chair for Process Control Systems, Technische Universität Dresden + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +import {StateConditionOptions} from '@p2olab/polaris-interface'; +import {catCondition} from '../../config/logging'; +import {ServiceState} from '../core/enum'; +import {Module} from '../core/Module'; +import {Service} from '../core/Service'; +import {Condition} from './Condition'; +import {ModuleCondition} from './ModuleCondition'; + +export class StateCondition extends ModuleCondition { + public readonly service: Service; + public readonly state: ServiceState; + + constructor(options: StateConditionOptions, modules: Module[]) { + super(options, modules); + if (this.module.services) { + this.service = this.module.services.find((service) => service.name === options.service); + } + if (!this.service) { + throw new Error(`Service "${options.service}" not found in provided module ${this.module.id}`); + } + const mapping = { + 'idle': ServiceState.IDLE, + 'starting': ServiceState.STARTING, + 'execute': ServiceState.EXECUTE, + 'completing': ServiceState.COMPLETING, + 'completed': ServiceState.COMPLETED, + 'resetting': ServiceState.RESETTING, + 'pausing': ServiceState.PAUSING, + 'paused': ServiceState.PAUSED, + 'resuming': ServiceState.RESUMING, + 'holding': ServiceState.HOLDING, + 'held': ServiceState.HELD, + 'unholding': ServiceState.UNHOLDING, + 'stopping': ServiceState.STOPPING, + 'stopped': ServiceState.STOPPED, + 'aborting': ServiceState.ABORTING, + 'aborted': ServiceState.ABORTED + }; + this.state = mapping[options.state.toLowerCase()]; + if (!this.state) { + throw new Error(`State ${options.state} is not a valid state for a condition (${JSON.stringify(options)}`); + } + } + + public listen(): Condition { + this.service.eventEmitter.on('state', this.check); + this.check(this.service.state); + return this; + } + + public clear() { + super.clear(); + this.service.eventEmitter.removeListener('state', this.check); + } + + private check = (expectedState: ServiceState) => { + this._fulfilled = (expectedState === this.state); + catCondition.info(`StateCondition ${this.service.qualifiedName}: actual=${ServiceState[expectedState]}` + + ` ; condition=${ServiceState[this.state]} -> ${this._fulfilled}`); + this.emit('stateChanged', this._fulfilled); + } +} diff --git a/src/model/condition/TimeCondition.ts b/src/model/condition/TimeCondition.ts new file mode 100644 index 00000000..88da22af --- /dev/null +++ b/src/model/condition/TimeCondition.ts @@ -0,0 +1,68 @@ +/* + * MIT License + * + * Copyright (c) 2019 Markus Graube , + * Chair for Process Control Systems, Technische Universität Dresden + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +import {TimeConditionOptions} from '@p2olab/polaris-interface'; +import {catCondition} from '../../config/logging'; +import {Module} from '../core/Module'; +import {Condition} from './Condition'; +import Timeout = NodeJS.Timeout; + +export class TimeCondition extends Condition { + + private timer: Timeout; + private duration: number; + + constructor(options: TimeConditionOptions) { + super(options); + if (options.duration <= 0) { + throw new Error('Duration is negative'); + } + this.duration = options.duration * 1000; + this._fulfilled = false; + catCondition.trace(`Add TimeCondition: ${JSON.stringify(options)}`); + } + + public listen(): Condition { + catCondition.debug(`Start Timer: ${this.duration}`); + this.timer = global.setTimeout(() => { + catCondition.debug(`TimeCondition finished: ${this.duration}`); + this._fulfilled = true; + this.emit('stateChanged', this._fulfilled); + }, + this.duration); + return this; + } + + public clear(): void { + super.clear(); + if (this.timer) { + global.clearTimeout(this.timer); + } + } + + public getUsedModules(): Set { + return new Set(); + } +} diff --git a/test/model/core/ProcessValue.spec.ts b/src/model/condition/TrueCondition.ts similarity index 63% rename from test/model/core/ProcessValue.spec.ts rename to src/model/condition/TrueCondition.ts index 7f2841d5..6529d4e3 100644 --- a/test/model/core/ProcessValue.spec.ts +++ b/src/model/condition/TrueCondition.ts @@ -23,23 +23,25 @@ * SOFTWARE. */ -import {expect} from 'chai'; -import {OpcUaNodeOptions} from '../../../src/model/core/Interfaces'; -import {DataAssembly} from '../../../src/model/dataAssembly/DataAssembly'; +import {Module} from '../core/Module'; +import {Condition} from './Condition'; -describe('ProcessValue', () => { +/** + * Condition which is always true + */ +export class TrueCondition extends Condition { + + constructor(options) { + super(options); + this._fulfilled = true; + } + + public listen(): Condition { + this.emit('stateChanged', true); + return this; + } - it('should fail with missing parameters', () => { - expect(() => { - const a = new DataAssembly(undefined, undefined); - }).to.throw(); - const opcUaNode: OpcUaNodeOptions = {namespace_index: 'CODESYSSPV3/3S/IecVarAccess', node_id: 'i=12'}; - expect(() => { - const a = new DataAssembly({ - name: 'name', - communication: [opcUaNode], - interface_class: 'analogitem' - }, undefined); - }).to.throw(); - }); -}); + public getUsedModules(): Set { + return new Set(); + } +} diff --git a/src/model/condition/VariableCondition.ts b/src/model/condition/VariableCondition.ts new file mode 100644 index 00000000..9bdd0b09 --- /dev/null +++ b/src/model/condition/VariableCondition.ts @@ -0,0 +1,86 @@ +/* + * MIT License + * + * Copyright (c) 2019 Markus Graube , + * Chair for Process Control Systems, Technische Universität Dresden + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +import {VariableConditionOptions} from '@p2olab/polaris-interface'; +import {catCondition} from '../../config/logging'; +import {Module} from '../core/Module'; +import {Condition} from './Condition'; +import {ModuleCondition} from './ModuleCondition'; + +export class VariableCondition extends ModuleCondition { + public readonly dataStructure: string; + public readonly variable: string; + public readonly value: string | number; + public readonly operator: '==' | '<' | '>' | '<=' | '>='; + + constructor(options: VariableConditionOptions, modules: Module[]) { + super(options, modules); + if (!options.dataAssembly) { + throw new Error(`Condition does not have 'dataAssembly' ${JSON.stringify(options)}`); + } + this.dataStructure = options.dataAssembly; + this.variable = options.variable || 'V'; + this.value = options.value; + this.operator = options.operator || '=='; + } + + public listen(): Condition { + catCondition.debug(`Listen to ${this.dataStructure}.${this.variable}`); + this.module.listenToDataAssembly(this.dataStructure, this.variable).on('changed', this.check); + return this; + } + + private check = (data: {value: number}) => { + catCondition.debug(`value changed to ${data.value} - (${this.operator}) compare against ${this.value}`); + const value: number = data.value; + let result = false; + if (this.operator === '==') { + if (value === this.value) { + result = true; + } + } else if (this.operator === '<=') { + if (value <= this.value) { + result = true; + } + } else if (this.operator === '>=') { + if (value >= this.value) { + result = true; + } + } else if (this.operator === '<') { + if (value < this.value) { + result = true; + } + } else if (this.operator === '>') { + if (value > this.value) { + result = true; + } + } + this._fulfilled = result; + this.emit('stateChanged', this._fulfilled); + catCondition.debug(`VariableCondition ${this.dataStructure}: ` + + `${data.value} ${this.operator} ${this.value} = ${this._fulfilled}`); + } + +} diff --git a/src/model/core/BaseService.ts b/src/model/core/BaseService.ts new file mode 100644 index 00000000..8e056bb3 --- /dev/null +++ b/src/model/core/BaseService.ts @@ -0,0 +1,163 @@ +/* + * MIT License + * + * Copyright (c) 2019 Markus Graube , + * Chair for Process Control Systems, Technische Universität Dresden + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +import {ControlEnableInterface, ParameterInterface, ParameterOptions, ServiceCommand} from '@p2olab/polaris-interface'; +import {EventEmitter} from 'events'; +import StrictEventEmitter from 'strict-event-emitter-types'; +import {catService} from '../../config/logging'; +import {Parameter} from '../recipe/Parameter'; +import {ServiceState} from './enum'; +import {Strategy} from './Strategy'; + +/** + * Events emitted by [[BaseService]] + */ +export interface BaseServiceEvents { + /** + * Notify when the [[Service] changes its state + * @event state + */ + state: ServiceState; + /** + * Notify when controlEnableNode changes + * @event controlEnable + */ + controlEnable: ControlEnableInterface; + /** + * whenever a commandNode is executed from the POL + * @event commandExecuted + */ + commandExecuted: { + timestamp: Date, + strategy: Strategy, + command: ServiceCommand, + parameter: ParameterInterface[], + scope?: any[] + }; + variableChanged: { strategy?: Strategy; parameter: string; value: number, unit: string }; + + parameterChanged: { strategy?: Strategy; parameter: string, value: number, unit: string }; +} + +type BaseServiceEmitter = StrictEventEmitter; + +export abstract class BaseService { + + protected set selfCompleting(value: boolean) { + this._selfCompleting = value; + } + + public get qualifiedName() { + return `${this.name}`; + } + + public get name(): string { + return this._name; + } + + public get lastStatusChange(): Date { + return this._lastStatusChange; + } + + public abstract get state(): ServiceState; + public abstract get controlEnable(): ControlEnableInterface + + public readonly eventEmitter: BaseServiceEmitter; + public parameters: ParameterInterface[] = []; + public processValuesIn: ParameterInterface[] = []; + public processValuesOut: ParameterInterface[] = []; + public reportValues: ParameterInterface[] = []; + + // name of the base service + protected _name: string; + + // is base service self completing + protected _selfCompleting: boolean = false; + + protected _lastStatusChange: Date = new Date(); + + protected constructor() { + this.eventEmitter = new EventEmitter(); + } + + public abstract setParameters(parameters: Array): Promise; + + /** + * allow controlEnable to execute specified command + * @param {ServiceCommand} command + * @returns {Promise} + */ + public isCommandExecutable(command: ServiceCommand): boolean { + const controlEnable: ControlEnableInterface = this.controlEnable; + catService.debug(`[${this.qualifiedName}] ControlEnable: ${JSON.stringify(controlEnable)}`); + return controlEnable[command]; + } + + /** + * Execute commandNode + * + * @param {ServiceCommand} command + * @returns {Promise} + */ + public async executeCommand(command: ServiceCommand): Promise { + if (!this.isCommandExecutable(command)) { + catService.info(`[${this.qualifiedName}] ControlOp does not allow command ${command}`); + throw new Error(`[${this.qualifiedName}] ControlOp does not allow command ${command}`); + } + let result; + if (command === ServiceCommand.start) { + result = this.start(); + } else if (command === ServiceCommand.stop) { + result = this.stop(); + } else if (command === ServiceCommand.reset) { + result = this.reset(); + } else if (command === ServiceCommand.complete) { + result = this.complete(); + } else if (command === ServiceCommand.abort) { + result = this.abort(); + } else if (command === ServiceCommand.unhold) { + result = this.unhold(); + } else if (command === ServiceCommand.pause) { + result = this.pause(); + } else if (command === ServiceCommand.resume) { + result = this.resume(); + } else if (command === ServiceCommand.restart) { + result = this.restart(); + } else { + throw new Error(`Command ${command} can not be interpreted`); + } + return result; + } + + protected abstract async start(); + protected abstract async stop(); + protected abstract async reset(); + protected abstract async complete(); + protected abstract async abort(); + protected abstract async unhold(); + protected abstract async pause(); + protected abstract async resume(); + protected abstract async restart(); +} diff --git a/src/model/core/Module.ts b/src/model/core/Module.ts index feb240db..8e9d3c6e 100644 --- a/src/model/core/Module.ts +++ b/src/model/core/Module.ts @@ -26,57 +26,24 @@ import { ControlEnableInterface, ModuleInterface, + ModuleOptions, OpModeInterface, ParameterInterface, ServiceCommand, ServiceInterface } from '@p2olab/polaris-interface'; import {EventEmitter} from 'events'; -import {DataType, VariantArrayType} from 'node-opcua'; -import { - AttributeIds, - ClientMonitoredItem, - ClientSession, - ClientSubscription, - coerceNodeId, - DataValue, - NodeId, - OPCUAClient, - Variant -} from 'node-opcua-client'; -import {TimestampsToReturn} from 'node-opcua-service-read'; -import {timeout} from 'promise-timeout'; import StrictEventEmitter from 'strict-event-emitter-types'; -import * as delay from 'timeout-as-promise'; import {Category} from 'typescript-logging'; import {catModule} from '../../config/logging'; import {VariableLogEntry} from '../../logging/archive'; -import {DataAssembly, DataAssemblyOptions} from '../dataAssembly/DataAssembly'; +import {DataAssembly} from '../dataAssembly/DataAssembly'; import {DataAssemblyFactory} from '../dataAssembly/DataAssemblyFactory'; +import {DataItemEmitter} from '../dataAssembly/DataItem'; import {ServiceState} from './enum'; -import {OpcUaNodeOptions} from './Interfaces'; -import {Service, ServiceOptions} from './Service'; +import {OpcUaConnection} from './OpcUaConnection'; +import {Service} from './Service'; import {Strategy} from './Strategy'; -import {UNIT} from './Unit'; - -export interface ModuleOptions { - id: string; - opcua_server_url: string; - hmi_url?: string; - services: ServiceOptions[]; - process_values: DataAssemblyOptions[]; -} - -/** - * Events emitted by [[OpcUaNodeOptions]] - */ -export interface OpcUaNodeEvents { - /** - * when OpcUaNodeOptions changes its value - * @event changed - */ - changed: {value: any, timestamp: Date}; -} /** * Events emitted by [[Module]] @@ -96,7 +63,7 @@ interface ModuleEvents { * when controlEnable of one service changes * @event controlEnable */ - controlEnable: {service: Service, controlEnable: ControlEnableInterface}; + controlEnable: { service: Service, controlEnable: ControlEnableInterface }; /** * Notify when a service changes its state * @event stateChanged @@ -105,14 +72,16 @@ interface ModuleEvents { timestampPfe: Date, timestampModule: Date, service: Service, - state: ServiceState}; + state: ServiceState + }; /** * Notify when a service changes its opMode * @event opModeChanged */ opModeChanged: { service: Service, - opMode: OpModeInterface}; + opMode: OpModeInterface + }; /** * Notify when a variable inside a module changes * @event variableChanged @@ -134,7 +103,7 @@ interface ModuleEvents { }; /** - * whenever a command is executed from the PFE + * whenever a command is executed from the POL * @event commandExecuted */ commandExecuted: { @@ -164,7 +133,6 @@ export class Module extends (EventEmitter as new() => ModuleEmitter) { public readonly options: ModuleOptions; public readonly id: string; - public readonly endpoint: string; public readonly hmiUrl: string; public readonly services: Service[]; public readonly variables: DataAssembly[]; @@ -173,237 +141,65 @@ export class Module extends (EventEmitter as new() => ModuleEmitter) { // module is protected and can't be deleted by the user public protected: boolean = false; - private session: ClientSession; - private client: OPCUAClient; - private subscription: ClientSubscription; - private monitoredItems: Map }>; - private namespaceArray: string[]; + public readonly connection: OpcUaConnection; constructor(options: ModuleOptions, protectedModule: boolean = false) { super(); this.options = options; this.id = options.id; - this.endpoint = options.opcua_server_url; this.protected = protectedModule; + this.hmiUrl = options.hmi_url; + this.connection = new OpcUaConnection(this.id, options.opcua_server_url, options.username, options.password) + .on('connected', () => this.emit('connected')) + .on('disconnected', () => this.emit('disconnected')); + this.logger = catModule; if (options.services) { - this.services = options.services.map((serviceOption) => new Service(serviceOption, this)); + this.services = options.services.map((serviceOpts) => new Service(serviceOpts, this.connection, this.id)); } if (options.process_values) { this.variables = options.process_values - .map((variableOptions) => DataAssemblyFactory.create(variableOptions, this)); - } - if (options.hmi_url) { - this.hmiUrl = options.hmi_url; + .map((variableOptions) => DataAssemblyFactory.create(variableOptions, this.connection)); } - - this.monitoredItems = new Map(); - - this.logger = catModule; - - this.client = new OPCUAClient({ - endpoint_must_exist: false, - connectionStrategy: { - maxRetry: 3 - } - }) - .on('close', async () => { - this.logger.info(`[${this.id}] Connection closed by OPC UA server`); - this.session = null; - this.emit('disconnected'); - }) - .on('connection_lost', async () => { - this.logger.info(`[${this.id}] Connection lost to OPC UA server`); - await this.subscription.terminate(); - await this.session.close(); - this.session = null; - await this.client.disconnect(); - this.emit('disconnected'); - }) - .on('timed_out_request', () => this.logger.warn(`[${this.id}] timed out request - retrying connection`)); } - /** - * Opens connection to server and establish session - * - * Subscribe to all important variables - * @returns {Promise} - */ - public async connect(): Promise { - if (this.session) { - this.logger.debug(`[${this.id}] Already connected`); - return Promise.resolve(); - } else { - this.logger.info(`[${this.id}] connect module via ${this.endpoint}`); - - await timeout(this.client.connect(this.endpoint), 2000); - this.logger.info(`[${this.id}] module connected via ${this.endpoint}`); - - const session = await this.client.createSession(); - this.logger.debug(`[${this.id}] session established`); - - const subscription = new ClientSubscription(session, { - requestedPublishingInterval: 100, - requestedLifetimeCount: 1000, - requestedMaxKeepAliveCount: 12, - maxNotificationsPerPublish: 10, - publishingEnabled: true, - priority: 10 - }); - - subscription - .on('started', () => { - this.logger.info(`[${this.id}] subscription started - ` + - `subscriptionId=${subscription.subscriptionId}`); - }) - .on('terminated', () => { - this.logger.debug(`[${this.id}] subscription terminated`); - }); - - // read namespace array - const result: DataValue = await session.readVariableValue('ns=0;i=2255'); - this.namespaceArray = result.value.value; - this.logger.debug(`[${this.id}] Got namespace array: ${JSON.stringify(this.namespaceArray)}`); - - // store everything - this.session = session; - this.subscription = subscription; - - if (this.endpoint === 'opc.tcp://10.6.51.22:4840') { - await this.fixReactor(); - } - - // set all services to correct operation mode - await Promise.all(this.services.map((service) => service.setOperationMode())); - // subscribe to all services - await this.subscribeToAllServices(); - - try { - this.subscribeToAllVariables(); - } catch (err) { - this.logger.warn('Could not connect to all variables:' + err); - } - this.emit('connected'); - return Promise.resolve(); - - } - } - - public async getServiceStates(): Promise { + public getServiceStates(): ServiceInterface[] { this.logger.trace(`[${this.id}] check service states`); - const tasks = this.services.map((service) => service.getOverview()); - return Promise.all(tasks); + return this.services.map((service) => service.getOverview()); } - /** - * Close session and disconnect from server of module - * - */ - public disconnect(): Promise { - this.services.forEach((s) => s.removeAllListeners()); - return new Promise(async (resolve, reject) => { - if (this.session) { - this.logger.info(`[${this.id}] Disconnect module`); - try { - await this.subscription.terminate(); - await timeout(this.session.close(), 1000); - this.session = undefined; - await timeout(this.client.disconnect(), 1000); - this.client = undefined; - this.logger.info(`[${this.id}] Module disconnected`); - this.emit('disconnected'); - resolve(`Module ${this.id} disconnected`); - } catch (err) { - reject(err); - } - } else { - resolve(`Module ${this.id} already disconnected`); - } - }); + public async connect() { + await this.connection.connect(); + await this.subscribeToAllVariables(); + await this.subscribeToAllServices(); + this.logger.info(`[${this.id}] Successfully subscribed to ${this.connection.monitoredItemSize()} assemblies`); } /** - * Listen to OPC UA node and return event listener which is triggered by any value change - * @param {OpcUaNodeOptions} node - * @param {number} samplingInterval OPC UA sampling interval for this subscription in milliseconds - * @returns {"events".internal.EventEmitter} "changed" event - */ - public listenToOpcUaNode(node: OpcUaNodeOptions, samplingInterval = 100): StrictEventEmitter { - const nodeId = this.resolveNodeId(node); - if (!this.monitoredItems.has(nodeId)) { - const monitoredItem: ClientMonitoredItem = this.subscription.monitor({ - nodeId, - attributeId: AttributeIds.Value - }, - { - samplingInterval, - discardOldest: true, - queueSize: 10 - }, TimestampsToReturn.Both); - - const emitter: StrictEventEmitter = new EventEmitter(); - monitoredItem.on('changed', (dataValue) => { - this.logger.debug(`[${this.id}] Variable Changed (${nodeId}) = ${dataValue.value.value.toString()}`); - node.value = dataValue.value.value; - node.timestamp = dataValue.serverTimestamp; - emitter.emit('changed', {value: dataValue.value.value, timestamp: dataValue.serverTimestamp}); - }); - this.monitoredItems.set(nodeId, { monitoredItem, emitter }); - } - return this.monitoredItems.get(nodeId).emitter; - } - - public listenToVariable(dataStructureName: string, variableName: string): StrictEventEmitter { - const dataStructure: DataAssembly = this.variables.find((variable) => variable.name === dataStructureName); - if (!dataStructure) { - throw new Error(`ProcessValue ${dataStructureName} is not specified for module ${this.id}`); - } else { - const variable: OpcUaNodeOptions = dataStructure.communication[variableName]; - return this.listenToOpcUaNode(variable); - } - } - - public async readVariableNode(node: OpcUaNodeOptions) { - if (!this.isConnected()) { - throw new Error(`Module ${this.id} not connected while trying to read variable ${JSON.stringify(node)}`); - } - const nodeId = this.resolveNodeId(node); - const result = await this.session.readVariableValue(nodeId); - this.logger.debug(`[${this.id}] Read Variable: ${JSON.stringify(node)} -> ${nodeId} = ${result}`); - if (result.statusCode.value !== 0) { - throw new Error(`Could not read ${nodeId.toString()}: ${result.statusCode.description}`); - } - return result; - } - - /** writes value to opc ua node + * Close session and disconnect from server of module * - * @param {OpcUaNodeOptions} node - * @param {} value - * @returns {Promise} */ - public async writeNode(node: OpcUaNodeOptions, value: Variant) { - if (!this.session) { - throw new Error(`Can not write node since OPC UA connection to module ${this.id} is not established`); - } else { - const result = await this.session.writeSingleNode(this.resolveNodeId(node), value); - this.logger.debug(`[${this.id}] Write result for ${node.node_id}=${value.value} -> ${result.name}`); - return result; - } + public async disconnect(): Promise { + this.logger.info(`[${this.id}] Disconnect module`); + await this.unsubscribeFromAllVariables(); + await this.unsubscribeFromAllServices(); + this.services.forEach((s) => s.unsubscribe()); + await this.connection.disconnect(); + this.emit('disconnected'); + this.logger.info(`[${this.id}] Module disconnected`); } /** * Get JSON serialisation of module - * - * @returns {Promise} */ - public async json(): Promise { + public json(): ModuleInterface { return { id: this.id, - endpoint: this.endpoint, + endpoint: this.connection.endpoint, hmiUrl: this.hmiUrl, connected: this.isConnected(), - services: this.isConnected() ? await this.getServiceStates() : undefined, + services: this.getServiceStates(), + process_values: [], protected: this.protected }; } @@ -413,7 +209,17 @@ export class Module extends (EventEmitter as new() => ModuleEmitter) { * @returns {boolean} */ public isConnected(): boolean { - return !!this.session; + return this.connection.isConnected(); + } + + public listenToDataAssembly(dataAssemblyName: string, variableName: string): DataItemEmitter { + const dataAssembly: DataAssembly = this.variables.find((variable) => variable.name === dataAssemblyName); + if (!dataAssembly) { + throw new Error(`ProcessValue ${dataAssemblyName} is not specified for module ${this.id}`); + } + const emitter: EventEmitter = new EventEmitter(); + dataAssembly.on(variableName, (data) => emitter.emit('changed', data)); + return emitter; } /** @@ -431,7 +237,7 @@ export class Module extends (EventEmitter as new() => ModuleEmitter) { public pause(): Promise { this.logger.info(`[${this.id}] Pause all running services`); const tasks = this.services.map(async (service) => { - if (await service.getServiceState() === ServiceState.EXECUTE) { + if (service.state === ServiceState.EXECUTE) { return service.execute(ServiceCommand.pause); } }); @@ -444,7 +250,7 @@ export class Module extends (EventEmitter as new() => ModuleEmitter) { public resume(): Promise { this.logger.info(`[${this.id}] Resume all paused services`); const tasks = this.services.map(async (service) => { - if (await service.getServiceState() === ServiceState.PAUSED) { + if (service.state === ServiceState.PAUSED) { return service.execute(ServiceCommand.resume); } }); @@ -457,7 +263,7 @@ export class Module extends (EventEmitter as new() => ModuleEmitter) { public stop(): Promise { this.logger.info(`[${this.id}] Stop all non-idle services`); const tasks = this.services.map((service) => { - if (service.status.value !== ServiceState.IDLE) { + if (service.state !== ServiceState.IDLE) { return service.execute(ServiceCommand.stop); } }); @@ -473,59 +279,61 @@ export class Module extends (EventEmitter as new() => ModuleEmitter) { return Promise.all(tasks); } - private subscribeToAllVariables() { - this.variables.forEach((variable: DataAssembly) => { - catModule.info(`[${this.id}] subscribe to process variable ${variable.name}`); - variable.subscribe(1000).on('V', (data) => { - let unit; - if (DataAssemblyFactory.isAnaView(variable)) { - const unitObject = UNIT.find((item) => item.value === parseInt(variable.VUnit.value, 10)); - unit = unitObject ? unitObject.unit : undefined; - } - this.logger.debug(`[${this.id}] variable changed: ${variable.name} = ` + - `${data.value} ${unit ? unit : ''}`); + private subscribeToAllVariables(): Promise { + return Promise.all( + this.variables.map((variable: DataAssembly) => { + catModule.debug(`[${this.id}] subscribe to process variable ${variable.name}`); + variable.on('V', (data) => { + this.logger.debug(`[${this.id}] variable changed: ${variable.name} = ` + + `${data.value} ${variable.getUnit()}`); const entry: VariableLogEntry = { timestampPfe: new Date(), timestampModule: data.timestamp, module: this.id, variable: variable.name, value: data.value, - unit + unit: variable.getUnit() }; this.emit('variableChanged', entry); }); - }); + return variable.subscribe(1000); + }) + ); + } + + private unsubscribeFromAllVariables() { + this.variables.forEach((variable: DataAssembly) => variable.unsubscribe()); } private subscribeToAllServices() { - return Promise.all(this.services.map(async (service) => { - return (await service.subscribeToService()) + return Promise.all(this.services.map((service) => { + service.eventEmitter .on('commandExecuted', (data) => { this.emit('commandExecuted', { service, - timestampPfe: data.timestampPfe, + timestampPfe: data.timestamp, strategy: data.strategy, command: data.command, parameter: data.parameter }); }) - .on('controlEnable', (controlEnable) => { - this.emit('controlEnable', {service, controlEnable} ); + .on('controlEnable', (controlEnable: ControlEnableInterface) => { + this.emit('controlEnable', {service, controlEnable}); }) - .on('state', ({state, timestamp}) => { + .on('state', (state) => { this.logger.debug(`[${this.id}] state changed: ${service.name} = ${ServiceState[state]}`); const entry = { timestampPfe: new Date(), - timestampModule: timestamp, - service, - state + timestampModule: service.lastStatusChange, + service: service, + state: state }; this.emit('stateChanged', entry); if (state === ServiceState.COMPLETED) { this.emit('serviceCompleted', service); } }) - .on('opMode', (opMode) => { + .on('opMode', (opMode: OpModeInterface) => { this.logger.debug(`[${this.id}] opMode changed: ${service.name} = ${JSON.stringify(opMode)}`); const entry = { service, @@ -535,128 +343,37 @@ export class Module extends (EventEmitter as new() => ModuleEmitter) { }) .on('variableChanged', (data) => { this.logger.debug(`[${this.id}] service variable changed: ` + - `${data.strategy.name}.${data.parameter.name} = ${data.value}`); - const variable: DataAssembly = data.parameter; - let unit; - if (DataAssemblyFactory.isAnaView(variable)) { - unit = UNIT.find((item) => item.value === variable.VUnit.value).unit; - } + `${data.strategy.name}.${data.parameter} = ${data.value}`); const entry = { timestampPfe: new Date(), timestampModule: new Date(), module: this.id, - variable: `${service.name}.${data.strategy.name}.${data.parameter.name}`, + variable: `${service.name}.${data.strategy.name}.${data.parameter}`, value: data.value, - unit + unit: data.unit }; this.emit('variableChanged', entry); }).on('parameterChanged', (data) => { this.logger.debug(`[${this.id}] parameter changed: ` + - `${data.strategy.name}.${data.parameter.name} = ${data.value}`); - const variable: DataAssembly = data.parameter; - let unit; - if (DataAssemblyFactory.isAnaView(variable)) { - unit = UNIT.find((item) => item.value === variable.VUnit.value).unit; - } + `${data.strategy.name}.${data.parameter} = ${data.value}`); const entry = { timestampPfe: new Date(), timestampModule: new Date(), module: this.id, service: service.name, strategy: data.strategy.id, - parameter: data.parameter.name, + parameter: data.parameter, value: data.value, - unit + unit: data.unit }; this.emit('parameterChanged', entry); }); + return service.subscribeToService(); })); } - /** - * Resolves nodeId of variable from module JSON using the namespace array - * @param {OpcUaNodeOptions} variable - * @returns {any} - */ - private resolveNodeId(variable: OpcUaNodeOptions) { - if (!variable) { - throw new Error('No variable specified to resolve nodeid'); - } else if (!this.namespaceArray) { - throw new Error(`No namespace array read for module ${this.id}`); - } else if (!variable.namespace_index) { - throw new Error(`namespace index is null in module ${this.id}`); - } else { - this.logger.debug(`[${this.id}] resolveNodeId ${JSON.stringify(variable)}`); - const nodeIdString = `ns=${this.namespaceArray.indexOf(variable.namespace_index)};s=${variable.node_id}`; - this.logger.debug(`[${this.id}] resolveNodeId ${JSON.stringify(variable)} -> ${nodeIdString}`); - return coerceNodeId(nodeIdString); - } - } - - /** Fix reactor of ACHEMA demonstrator. - * Set all opModes from devices to automatic and set senseful default values - */ - private async fixReactor() { - const nodeIdsReactor = [ - 'ns=3;s="AEM01"."MTP_AnaDrv"."OpMode"', - 'ns=3;s="MFH01"."MTP_BinVlv"."OpMode"', - 'ns=3;s="MFH02"."MTP_BinVlv"."OpMode"', - 'ns=3;s="MFH03"."MTP_BinVlv"."OpMode"']; - - const valuesReactor = [ - ['ns=3;s="Fill_Level_Max"."MTP"."VExt"', 1.5], - ['ns=3;s="Stir_Level_Min"."MTP"."VExt"', 0.5], - ['ns=3;s="Stir_Period"."MTP"."VExt"', 0.5], - ['ns=3;s="Stir_Period"."MTP"."VOp"', 0.5], - ['ns=3;s="Empty_Level_Tank_Deadband"."MTP"."VExt"', 0.5], - ['ns=3;s="Empty_Level_Tank_Deadband"."MTP"."VExt"', 0.5], - ['ns=3;s="Empty_Level_Tank"."MTP"."VExt"', 0.5], - ['ns=3;s="Empty_Level_Tank"."MTP"."VOp"', 0.5], - ['ns=3;s="Empty_Vol_Flow"."MTP"."VOp"', 2.5], - ['ns=3;s="Empty_Vol_Flow"."MTP"."VExt"', 2.5], - ]; - - this.logger.info(`[${this.id}] Fixing nodes in reactor PEA server`); - - // first set to manual - await Promise.all(nodeIdsReactor.map((nodeId) => { - return this.session.writeSingleNode( - nodeId, - { - dataType: DataType.UInt32, - value: 16, - arrayType: VariantArrayType.Scalar, - dimensions: null - } - ); - })); - await delay(200); - - // then to automatic - await Promise.all(nodeIdsReactor.map((nodeId) => { - return this.session.writeSingleNode( - nodeId, - { - dataType: DataType.UInt32, - value: 64, - arrayType: VariantArrayType.Scalar, - } - ); - })); - - // give all Parameters nice default values - await Promise.all(valuesReactor.map(async (item) => { - return this.session.writeSingleNode( - item[0], - { - dataType: DataType.Float, - value: item[1], - arrayType: VariantArrayType.Scalar, - } - ); - })); - - this.logger.info(`[${this.id}] Nodes in reactor PEA server fixed`); + private unsubscribeFromAllServices() { + this.services.forEach((service) => service.unsubscribe()); } } diff --git a/src/model/core/OpcUaConnection.ts b/src/model/core/OpcUaConnection.ts new file mode 100644 index 00000000..06f1562d --- /dev/null +++ b/src/model/core/OpcUaConnection.ts @@ -0,0 +1,299 @@ +/* + * MIT License + * + * Copyright (c) 2019 Markus Graube , + * Chair for Process Control Systems, Technische Universität Dresden + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +import {EventEmitter} from 'events'; +import { + AttributeIds, + ClientMonitoredItemBase, + ClientSession, + ClientSubscription, + coerceNodeId, + DataValue, + OPCUAClient, + TimestampsToReturn, + UserIdentityInfo, + UserIdentityInfoUserName, + UserTokenType, + Variant, + VariantArrayType +} from 'node-opcua'; +import {timeout} from 'promise-timeout'; +import StrictEventEmitter from 'strict-event-emitter-types'; +import {Category} from 'typescript-logging'; +import {fixReactor} from '../../achema/ReactorFix'; +import {catOpc} from '../../config/logging'; + +/** + * Events emitted by [[OpcUaConnection]] + */ +interface OpcUaConnectionEvents { + /** + * when module successfully connects to PEA + * @event connected + */ + connected: void; + /** + * when module is disconnected from PEA + * @event disconnected + */ + disconnected: void; +} + +type OpcUaConnectionEmitter = StrictEventEmitter; + +export class OpcUaConnection extends (EventEmitter as new() => OpcUaConnectionEmitter) { + + public readonly endpoint: string; + public readonly id: string; + private session: ClientSession; + private client: OPCUAClient; + private subscription: ClientSubscription; + private readonly monitoredItems: Map; + private namespaceArray: string[]; + private readonly logger: Category; + private readonly username: string; + private readonly password: string; + + constructor(moduleId: string, endpoint: string, username?: string, password?: string) { + super(); + this.id = moduleId; + this.endpoint = endpoint; + this.logger = catOpc; + this.monitoredItems = new Map(); + this.username = username; + this.password = password; + } + + /** + * Opens connection to server and establish session + */ + public async connect(): Promise { + if (this.isConnected()) { + this.logger.debug(`[${this.id}] Already connected`); + return Promise.resolve(); + } else { + this.client = await this.createAndConnectClient(); + this.session = await this.createSession(); + this.namespaceArray = await this.readNameSpaceArray(); + this.subscription = await this.createSubscription(); + + if (this.endpoint === 'opc.tcp://10.6.51.22:4840') { + await fixReactor(this.session) + .catch((err) => this.logger.warn(`Something wrong during fixing reactor ${JSON.stringify(err)}`)); + } + + this.logger.info(`[${this.id}] Successfully connected`); + this.emit('connected'); + } + } + + public async disconnect(): Promise { + if (this.client) { + this.client.removeAllListeners('close') + .removeAllListeners('connection_lost'); + } + if (this.subscription) { + await timeout(this.subscription.terminate(), 1000); + this.subscription = undefined; + } + if (this.session) { + await timeout(this.session.close(), 1000); + this.session = undefined; + } + if (this.client) { + await timeout(this.client.disconnect(), 1000); + this.client = undefined; + } + this.monitoredItems.clear(); + this.logger.info(`[${this.id}] OPC UA connection disconnected`); + } + + /** + * is module connected to physical PEA + * @returns {boolean} + */ + public isConnected(): boolean { + return !!this.client && !!this.session; + } + + public async readOpcUaNode(nodeId: string, namespaceUrl: string) { + const nodeIdResolved = this.resolveNodeId(nodeId, namespaceUrl); + return await this.session.readVariableValue(nodeIdResolved); + } + + public async listenToOpcUaNode(nodeId: string, + namespaceUrl: string, + samplingInterval = 100): Promise { + const nodeIdResolved = this.resolveNodeId(nodeId, namespaceUrl); + const monitoredItemKey = nodeIdResolved.toString(); + if (this.monitoredItems.has(monitoredItemKey)) { + return this.monitoredItems.get(monitoredItemKey); + } else { + const monitoredItem = await this.subscription.monitor({ + nodeId: nodeIdResolved, + attributeId: AttributeIds.Value + }, + { + samplingInterval, + discardOldest: true, + queueSize: 1 + }, TimestampsToReturn.Both); + + if (monitoredItem.statusCode.value !== 0) { + throw new Error(monitoredItem.statusCode.description); + } + this.logger.debug(`[${this.id}] subscribed to opc ua Variable ${monitoredItemKey} `); + this.monitoredItems.set(monitoredItemKey, monitoredItem); + return monitoredItem; + } + } + + public async writeOpcUaNode(nodeId: string, namespaceUrl: string, value: number | string, dataType) { + if (!this.isConnected()) { + throw new Error(`Can not write node since OPC UA connection to module ${this.id} is not established`); + } else { + const variant = Variant.coerce({ + value: value, + dataType: dataType, + arrayType: VariantArrayType.Scalar + }); + this.logger.info(`[${this.id}] Write ${nodeId} - ${JSON.stringify(variant)}`); + const statusCode = await this.session.writeSingleNode(this.resolveNodeId(nodeId, namespaceUrl), variant); + if (statusCode.value !== 0) { + this.logger.warn(`Error while writing to opcua: ${statusCode.description}`); + throw new Error(statusCode.description); + } + } + } + + public monitoredItemSize(): number { + return this.monitoredItems.size; + } + + /** + * Resolves a nodeId from nodeid and namesace url using the namespace array + */ + private resolveNodeId(nodeId: string, namespaceUrl: string) { + if (!this.namespaceArray) { + throw new Error(`No namespace array read for module ${this.id}`); + } else if (!namespaceUrl) { + throw new Error(`namespace index is null in module ${this.id}`); + } else if (!nodeId) { + throw new Error('nodeid is null'); + } + const nsIndex = this.namespaceArray.indexOf(namespaceUrl); + if (nsIndex === -1) { + throw new Error(`Could not resolve namespace ${namespaceUrl}`); + } + const nodeIdString = `ns=${nsIndex};s=${nodeId}`; + + this.logger.debug(`[${this.id}] resolveNodeId ${nodeId} -> ${nodeIdString}`); + return coerceNodeId(nodeIdString); + } + + private async readNameSpaceArray() { + const result: DataValue = await this.session.readVariableValue('ns=0;i=2255'); + const namespaceArray = result.value.value; + this.logger.debug(`[${this.id}] Got namespace array: ${JSON.stringify(namespaceArray)}`); + return namespaceArray; + } + + private async createAndConnectClient() { + const client = OPCUAClient.create({ + endpoint_must_exist: false, + connectionStrategy: { + maxRetry: 0 + } + }) + .on('close', async () => { + this.logger.info(`[${this.id}] Connection closed to OPC UA server`); + await this.disconnect(); + this.emit('disconnected'); + }) + .on('connection_lost', async () => { + this.logger.info(`[${this.id}] Connection lost to OPC UA server`); + await this.disconnect(); + this.emit('disconnected'); + }) + .on('timed_out_request', () => { + this.logger.warn(`[${this.id}] timed out request - retrying connection`); + }); + this.logger.info(`[${this.id}] connect module via ${this.endpoint}`); + + await timeout(client.connect(this.endpoint), 2000) + .catch((err) => { + client.disconnect(); + throw err; + }); + this.logger.info(`[${this.id}] opc ua server connected via ${this.endpoint}`); + return client; + } + + private async createSession() { + let userIdentityInfo: UserIdentityInfo = { type: UserTokenType.Anonymous}; + if (this.username && this.password) { + userIdentityInfo = + { + type: UserTokenType.UserName, + userName: this.username, + password: this.password + } as UserIdentityInfoUserName; + } + const session = await this.client.createSession(userIdentityInfo); + this.logger.info(`session created (#${session.sessionId})`); + return session; + } + + private async createSubscription() { + const subscription = ClientSubscription.create(this.session, { + requestedPublishingInterval: 100, + requestedLifetimeCount: 1000, + requestedMaxKeepAliveCount: 12, + maxNotificationsPerPublish: 10, + publishingEnabled: true, + priority: 10 + }); + + await new Promise((resolve) => + subscription + .on('started', () => { + this.logger.info(`[${this.id}] subscription started - ` + + `subscriptionId=${subscription.subscriptionId}`); + resolve(); + }) + .on('terminated', () => { + this.logger.info(`[${this.id}] subscription terminated`); + }) + .on('internal_error', (err: Error) => this.logger.debug(`[${this.id}] internal error: ${err}`)) + .on('error', (err: Error) => this.logger.warn(`[${this.id}] error: ${err}`)) + .on('status_changed', (data) => this.logger.debug(`[${this.id}] status changed: ${data}`)) + .on('item_added', (data) => this.logger.debug(`[${this.id}] item added: ${data}`)) + .on('raw_notification', (data) => this.logger.trace(`[${this.id}] raw_notification: ${data}`)) + + ); + return subscription; + } + +} diff --git a/src/model/core/Service.ts b/src/model/core/Service.ts index 232b791a..b618bb67 100644 --- a/src/model/core/Service.ts +++ b/src/model/core/Service.ts @@ -30,491 +30,377 @@ import { ParameterOptions, ServiceCommand, ServiceInterface, + ServiceOptions, StrategyInterface } from '@p2olab/polaris-interface'; import {EventEmitter} from 'events'; -import {DataType, DataValue, Variant, VariantArrayType} from 'node-opcua-client'; import StrictEventEmitter from 'strict-event-emitter-types'; import {Category} from 'typescript-logging'; import {catService} from '../../config/logging'; -import {DataAssembly, DataAssemblyOptions} from '../dataAssembly/DataAssembly'; +import {DataAssembly} from '../dataAssembly/DataAssembly'; import {DataAssemblyFactory} from '../dataAssembly/DataAssemblyFactory'; +import {OpcUaDataItem} from '../dataAssembly/DataItem'; +import {ServiceControl} from '../dataAssembly/ServiceControl'; import {Parameter} from '../recipe/Parameter'; -import { - controlEnableToJson, - isAutomaticState, - isExtSource, - isManualState, - isOffState, - OpMode, - opModetoJson, - ServiceControlEnable, - ServiceMtpCommand, - ServiceState -} from './enum'; -import {OpcUaNodeOptions} from './Interfaces'; -import {Module} from './Module'; -import {Strategy, StrategyOptions} from './Strategy'; -import {UNIT} from './Unit'; - -export interface ServiceOptions { - name: string; - communication: { - OpMode: OpcUaNodeOptions; - ControlOp?: OpcUaNodeOptions; - CommandMan?: OpcUaNodeOptions; - ControlExt?: OpcUaNodeOptions; - CommandExt?: OpcUaNodeOptions - ControlEnable?: OpcUaNodeOptions; - CommandEnable?: OpcUaNodeOptions; - State?: OpcUaNodeOptions; - CurrentState?: OpcUaNodeOptions; - StrategyOp?: OpcUaNodeOptions; - StrategyMan?: OpcUaNodeOptions; - StrategyExt: OpcUaNodeOptions; - CurrentStrategy: OpcUaNodeOptions; - }; - strategies: StrategyOptions[]; - parameters: DataAssemblyOptions[]; -} - -const interfaceClassToType = { - 'StrView': 'string', - 'AnaView': 'string', - 'ExtAnaOp': 'number', - 'ExtDigOp': 'number' -}; +import {BaseService, BaseServiceEvents} from './BaseService'; +import {controlEnableToJson, OpMode, opModetoJson, ServiceControlEnable, ServiceMtpCommand, ServiceState} from './enum'; +import {OpcUaConnection} from './OpcUaConnection'; +import {Strategy} from './Strategy'; /** - * Events emitted by [[TestServerService]] + * Events emitted by [[Service]] */ -interface ServiceEvents { - /** - * Notify when the [[TestServerService] changes its state - * @event state - */ - state: {state: ServiceState, timestamp: Date}; - - variableChanged: { strategy?: Strategy; parameter: DataAssembly; value: number }; - - parameterChanged: { strategy?: Strategy; parameter: DataAssembly, value: number }; - +interface ServiceEvents extends BaseServiceEvents { opMode: OpModeInterface; - /** - * Notify when controlEnable changes - * @event controlEnable - */ - controlEnable: ControlEnableInterface; - /** - * whenever a command is executed from the POL - * @event commandExecuted - */ - commandExecuted: { - timestampPfe: Date, - strategy: Strategy, - command: ServiceCommand, - parameter: ParameterInterface[], - scope?: any[] - }; } type ServiceEmitter = StrictEventEmitter; /** - * TestServerService of a [[Module]] + * Service of a [[Module]] * * after connection to a real PEA, commands can be triggered and states can be retrieved * */ -export class Service extends (EventEmitter as new() => ServiceEmitter) { +export class Service extends BaseService { public get qualifiedName() { - return `${this.parent.id}.${this.name}`; - } - - /** name of the service */ - public readonly name: string; - /** strategies of the service */ - public readonly strategies: Strategy[]; - /** service configuration configuration parameters */ - public readonly parameters: DataAssembly[]; - /** [Module] of the service */ - public readonly parent: Module; - /** OPC UA node of command/controlOp variable */ - public readonly command: OpcUaNodeOptions; - public readonly commandMan: OpcUaNodeOptions; - /** OPC UA node of status variable */ - public readonly status: OpcUaNodeOptions; - /** OPC UA node of controlEnable variable */ - public readonly controlEnable: OpcUaNodeOptions; - /** OPC UA node of opMode variable */ - public readonly opMode: OpcUaNodeOptions; - /** OPC UA node of strategy variable */ - public readonly strategy: OpcUaNodeOptions; - public readonly strategyMan: OpcUaNodeOptions; - /** OPC UA node of currentStrategy variable */ - public readonly currentStrategy: OpcUaNodeOptions; - public readonly logger: Category; - // use ControlExt (true) or ControlOp (false) - public readonly automaticMode: boolean; - private serviceParametersEventEmitters: EventEmitter[]; - private lastStatusChange: Date; + return `${this._parentId}.${this.name}`; + } - constructor(serviceOptions: ServiceOptions, parent: Module) { - super(); - this.name = serviceOptions.name; - this.automaticMode = true; + public get controlEnable(): ControlEnableInterface { + return controlEnableToJson(this.commandEnableNode.value as ServiceControlEnable); + } - const com = serviceOptions.communication; + public get state(): ServiceState { + return this.statusNode.value as ServiceState; + } - this.opMode = com.OpMode; - if (!this.opMode) { - throw new Error(`No opMode variable in service ${this.name} during parsing`); - } + public get lastStatusChange(): Date { + return this.statusNode.timestamp; + } - this.status = com.State || com.CurrentState; - if (!this.status) { - throw new Error(`No status variable in service ${this.name} during parsing`); - } + public get defaultStrategy(): Strategy { + return this.strategies.find((strategy) => strategy.defaultStrategy); + } - this.controlEnable = com.ControlEnable || com.CommandEnable; - if (!this.controlEnable) { - throw new Error(`No controlEnable variable in service ${this.name} during parsing`); - } + get opMode(): OpMode { + return this.opModeNode.value as OpMode; + } - this.command = com.ControlExt || com.CommandExt; - if (!this.command) { - throw new Error(`No command variable in service ${this.name} during parsing`); - } + get opModeNode(): OpcUaDataItem { + return this.serviceControl.communication.OpMode; + } - this.commandMan = com.ControlOp || com.CommandMan; - if (!this.commandMan) { - throw new Error(`No commandMan variable in service ${this.name} during parsing`); - } + get statusNode(): OpcUaDataItem { + return this.serviceControl.communication.State; + } - this.strategy = com.StrategyExt; - if (!this.strategy) { - throw new Error(`No strategy variable in service ${this.name} during parsing`); - } + get currentStrategyNode(): OpcUaDataItem { + return this.serviceControl.communication.CurrentStrategy; + } - this.strategyMan = com.StrategyOp || com.StrategyMan; - if (!this.strategyMan) { - throw new Error(`No strategyMan variable in service ${this.name} during parsing`); - } + get strategyExtNode(): OpcUaDataItem { + return this.serviceControl.communication.StrategyExt; + } - this.currentStrategy = com.CurrentStrategy; - if (!this.currentStrategy) { - throw new Error(`No currentStrategy variable in service ${this.name} during parsing`); - } + get strategyManNode(): OpcUaDataItem { + return this.serviceControl.communication.StrategyMan; + } - this.strategies = serviceOptions.strategies - .map((option) => new Strategy(option, parent)); - if (serviceOptions.parameters) { - this.parameters = serviceOptions.parameters - .map((options) => DataAssemblyFactory.create(options, parent)); + get commandEnableNode(): OpcUaDataItem { + return this.serviceControl.communication.CommandEnable; + } + + get commandExtNode(): OpcUaDataItem { + return this.serviceControl.communication.CommandExt; + } + + get commandManNode(): OpcUaDataItem { + return this.serviceControl.communication.CommandMan; + } + + public readonly eventEmitter: ServiceEmitter; + public readonly strategies: Strategy[] = []; + public readonly parameters: DataAssembly[] = []; + public readonly processValuesIn: DataAssembly[] = []; + public readonly processValuesOut: DataAssembly[] = []; + public readonly reportParameters: DataAssembly[] = []; + public readonly connection: OpcUaConnection; + // use ControlExt (true) or ControlOp (false) + public readonly automaticMode: boolean; + public readonly serviceControl: ServiceControl; + private readonly logger: Category; + private serviceParametersEventEmitters: EventEmitter[]; + private _parentId: string; + + constructor(serviceOptions: ServiceOptions, connection: OpcUaConnection, parentId: string) { + super(); + this._parentId = parentId; + this._name = serviceOptions.name; + if (!serviceOptions.name) { + throw new Error('No service name provided'); } - this.parent = parent; + + this.automaticMode = true; + this.connection = connection; this.serviceParametersEventEmitters = []; - this.lastStatusChange = new Date(); + this._lastStatusChange = new Date(); this.logger = catService; - } - /** - * Listen to state, controlEnable, command, currentStrategy and opMode of service and emits specific events for them - * - * @returns {TestServerService} - */ - public async subscribeToService(): Promise { - this.logger.info(`[${this.qualifiedName}] Subscribe to service`); - if (this.controlEnable) { - await this.getControlEnable(); - this.logger.debug(`[${this.qualifiedName}] initial controlEnable: ${this.controlEnable.value}`); - this.parent.listenToOpcUaNode(this.controlEnable) - .on('changed', () => { - this.logger.info(`[${this.qualifiedName}] ControlEnable changed: ` + - `${JSON.stringify(controlEnableToJson(this.controlEnable.value as ServiceControlEnable))}`); - this.emit('controlEnable', controlEnableToJson(this.controlEnable.value as ServiceControlEnable)); - }); - } - if (this.status) { - await this.getServiceState(); - this.logger.debug(`[${this.qualifiedName}] initial status: ${this.status.value}`); - this.parent.listenToOpcUaNode(this.status) - .on('changed', (data) => { - this.lastStatusChange = new Date(); - this.logger.info(`[${this.qualifiedName}] Status changed: ` + - `${ServiceState[this.status.value as ServiceState]}`); - this.emit('state', {state: this.status.value as ServiceState, timestamp: this.status.timestamp}); - if (data.value === ServiceState.COMPLETED || - data.value === ServiceState.ABORTED || - data.value === ServiceState.STOPPED) { - this.clearListeners(); - } - }); - } - if (this.command) { - const result = await this.parent.readVariableNode(this.command); - this.command.value = result.value.value; - this.logger.debug(`[${this.qualifiedName}] initial command: ${this.command.value}`); - this.parent.listenToOpcUaNode(this.command) - .on('changed', () => { - this.logger.debug(`[${this.qualifiedName}] Command changed: ` + - `${ServiceMtpCommand[this.command.value as ServiceMtpCommand]}`); - }); + this.serviceControl = new ServiceControl( + {name: this._name, interface_class: 'ServiceControl', communication: serviceOptions.communication}, + connection); + this.checkServiceControl(); + + this.strategies = serviceOptions.strategies + .map((option) => new Strategy(option, connection)); + + if (serviceOptions.parameters) { + this.parameters = serviceOptions.parameters + .map((options) => DataAssemblyFactory.create(options, connection)); } - if (this.commandMan) { - const result = await this.parent.readVariableNode(this.commandMan); - this.commandMan.value = result.value.value; - this.logger.debug(`[${this.qualifiedName}] initial commandMan: ${this.commandMan.value}`); - this.parent.listenToOpcUaNode(this.commandMan) - .on('changed', () => { - this.logger.debug(`[${this.qualifiedName}] CommandMan changed: ` + - `${ServiceMtpCommand[this.commandMan.value as ServiceMtpCommand]}`); - }); + if (serviceOptions.processValuesIn) { + this.processValuesIn = serviceOptions.processValuesIn + .map((pvOptions) => DataAssemblyFactory.create(pvOptions, connection)); } - if (this.currentStrategy) { - await this.getCurrentStrategy(); - this.logger.debug(`[${this.qualifiedName}] initial current strategy: ${this.currentStrategy.value}`); - this.parent.listenToOpcUaNode(this.currentStrategy) - .on('changed', () => { - this.logger.debug(`[${this.qualifiedName}] Current Strategy changed: ` + - `${this.currentStrategy.value}`); - }); + if (serviceOptions.processValuesOut) { + this.processValuesOut = serviceOptions.processValuesOut + .map((pvOptions) => DataAssemblyFactory.create(pvOptions, connection)); } - if (this.opMode) { - await this.getOpMode(); - this.logger.debug(`[${this.qualifiedName}] initial opMode: ${this.opMode.value}`); - this.parent.listenToOpcUaNode(this.opMode) - .on('changed', () => { - this.logger.debug(`[${this.qualifiedName}] Current OpMode changed: ${this.opMode.value}`); - this.emit('opMode', opModetoJson(this.opMode.value as OpMode)); - }); + if (serviceOptions.reportParameters) { + this.reportParameters = serviceOptions.reportParameters + .map((pvOptions) => DataAssemblyFactory.create(pvOptions, connection)); } - this.parameters.forEach((param) => param.subscribe() - .on('V', (data) => { - this.emit('variableChanged', {parameter: param, value: data}); - })); - this.strategies.forEach((strategy) => strategy.subscribe() - .on('processValueChanged', (data) => { - this.emit('variableChanged', {strategy, parameter: data.processValue, value: data.value}); - }) - .on('parameterChanged', (data) => { - this.emit('parameterChanged', {strategy, parameter: data.parameter, value: data.value}); - }) - ); - return this; } /** - * Get current service state from internal memory. - * If there is no state or the state is older than 1000ms, retrieve an updated version from PEA - * - * @returns {Promise} - */ - public async getServiceState(): Promise { - if (!this.status.value || - (new Date().getMilliseconds() - this.status.timestamp.getMilliseconds() < 1000)) { - const result = await this.parent.readVariableNode(this.status); - if (result.value.value !== this.status.value) { - this.lastStatusChange = new Date(); - } - this.status.value = result.value.value; - this.status.timestamp = new Date(); - this.logger.debug(`[${this.qualifiedName}] Update service state: ` + - `${ServiceState[this.status.value as ServiceState]}`); - } - return this.status.value as ServiceState; - } - - /** - * Get current control enable from internal memory. - * If there is no control enable or it is older than 1000ms, retrieve an updated version from PEA - * @param {boolean} force force to retrieve data from PEA and not from internal memory - * @returns {Promise} + * Get current strategy from internal memory. */ - public async getControlEnable(force = false): Promise { - if (!this.controlEnable.value || force || - (new Date().getMilliseconds() - this.controlEnable.timestamp.getMilliseconds() < 1000)) { - this.controlEnable.value = (await this.parent.readVariableNode(this.controlEnable)).value.value; - this.controlEnable.timestamp = new Date(); - this.logger.debug(`[${this.qualifiedName}] Update control enable: ` + - `${JSON.stringify(controlEnableToJson(this.controlEnable.value as ServiceControlEnable))}`); - } - return controlEnableToJson(this.controlEnable.value as ServiceControlEnable); + public getCurrentStrategy(): Strategy { + return this.strategies.find((strat) => parseInt(strat.id, 10) === this.currentStrategyNode.value); } /** - * Get current strategy from internal memory. - * If there is no current strategy or it is older than 1000ms, retrieve an updated version from PEA - * If PEA has no known strategy set in server, set it to default strategy + * Notify about changes in to serviceControl, strategies, configuration parameters and process values + * via events and log messages */ - public async getCurrentStrategy(): Promise { - if (!this.currentStrategy.value || - (new Date().getMilliseconds() - this.currentStrategy.timestamp.getMilliseconds() < 1000)) { - this.currentStrategy.value = (await this.parent.readVariableNode(this.currentStrategy)).value.value; - this.currentStrategy.timestamp = new Date(); - this.logger.debug(`[${this.qualifiedName}] Update currentStrategy: ${this.currentStrategy.value}`); - } - let strategy = this.strategies.find((strat) => strat.id === this.currentStrategy.value); - if (!strategy) { - strategy = this.strategies.find((strat) => strat.default); - this.setStrategyParameters(strategy); - } - return strategy; + public async subscribeToService(): Promise { + this.logger.info(`[${this.qualifiedName}] Subscribe to service`); + this.serviceControl + .on('CommandEnable', () => { + this.logger.debug(`[${this.qualifiedName}] ControlEnable changed: ` + + `${JSON.stringify(this.controlEnable)}`); + this.eventEmitter.emit('controlEnable', this.controlEnable); + }) + .on('CommandExt', () => { + this.logger.debug(`[${this.qualifiedName}] CommandExt changed: ` + + `${ServiceMtpCommand[this.commandExtNode.value as ServiceMtpCommand]}`); + }) + .on('CommandMan', () => { + this.logger.debug(`[${this.qualifiedName}] CommandMan changed: ` + + `${ServiceMtpCommand[this.commandManNode.value as ServiceMtpCommand]}`); + }) + .on('CurrentStrategy', () => { + this.logger.debug(`[${this.qualifiedName}] Current Strategy changed: ` + + `${this.currentStrategyNode.value}`); + }) + .on('OpMode', () => { + this.logger.info(`[${this.qualifiedName}] Current OpMode changed: ${this.opModeNode.value}`); + this.eventEmitter.emit('opMode', opModetoJson(this.opMode)); + }) + .on('State', () => { + this.logger.info(`[${this.qualifiedName}] State changed: ` + + `${ServiceState[this.statusNode.value as ServiceState]}`); + this.eventEmitter.emit('state', this.state); + if (this.state === ServiceState.COMPLETED || + this.state === ServiceState.ABORTED || + this.state === ServiceState.STOPPED) { + this.clearListeners(); + } + }); + const tasks = []; + tasks.push(this.serviceControl.subscribe(50)); + + tasks.concat( + this.parameters.map((param) => { + param.on('V', (data) => { + this.eventEmitter.emit('variableChanged', { + parameter: param.name, + value: data, + unit: param.getUnit() + }); + }); + return param.subscribe(); + }), + this.processValuesIn.map((pv) => { + pv.on('V', (data) => { + this.eventEmitter.emit('variableChanged', { + parameter: pv.name, + value: data, + unit: pv.getUnit() + }); + }); + return pv.subscribe(); + }), + this.processValuesOut.map((pv) => { + pv.on('V', (data) => { + this.eventEmitter.emit('variableChanged', { + parameter: pv.name, + value: data, + unit: pv.getUnit() + }); + }); + return pv.subscribe(); + }), + this.strategies.map((strategy) => { + strategy.on('parameterChanged', (data) => { + this.eventEmitter.emit('parameterChanged', { + strategy, + parameter: data.parameter.name, + value: data.value, + unit: data.parameter.getUnit() + }); + }); + return strategy.subscribe(); + })); + await Promise.all(tasks); + return this.eventEmitter; } - /** - * Get current opMode from internal memory. - * If there is no opMode or it is older than 1000ms, retrieve an updated version from PEA - */ - public async getOpMode(): Promise { - if (!this.opMode.value || - (new Date().getMilliseconds() - this.opMode.timestamp.getMilliseconds() < 1000)) { - const result = await this.parent.readVariableNode(this.opMode); - this.opMode.value = result.value.value; - this.opMode.timestamp = new Date(); - this.logger.debug(`[${this.qualifiedName}] Update opMode: ` + - `${JSON.stringify(opModetoJson(this.opMode.value as OpMode))}`); - } - return this.opMode.value as OpMode; + public unsubscribe() { + this.serviceControl.unsubscribe(); + this.parameters.forEach((param) => param.unsubscribe()); + this.processValuesIn.forEach((pv) => pv.unsubscribe()); + this.processValuesOut.forEach((pv) => pv.unsubscribe()); + this.strategies.forEach((strategy) => strategy.unsubscribe()); } /** - * get JSON overview about service and its state, opMode, strategies, strategyParameters and controlEnable - * @returns {Promise} + * get JSON overview about service and its state, opMode, strategies, parameters and controlEnable */ - public async getOverview(): Promise { - const opMode = await this.getOpMode(); - const state = await this.getServiceState(); - const strategies = await this.getStrategies(); - const params = await this.getCurrentParameters(); - const controlEnable = await this.getControlEnable(); - const currentStrategy = await this.getCurrentStrategy(); + public getOverview(): ServiceInterface { + const strategies = this.getStrategies(); + const params = this.getCurrentParameters(); + const currentStrategy = this.getCurrentStrategy(); return { name: this.name, - opMode: opModetoJson(opMode), - status: ServiceState[state], - strategies, - currentStrategy: currentStrategy.name, + opMode: opModetoJson(this.opMode), + status: ServiceState[this.state], + strategies: strategies, + currentStrategy: currentStrategy ? currentStrategy.name : null, parameters: params, - controlEnable, - lastChange: (new Date().getTime() - this.lastStatusChange.getTime()) / 1000 + processValuesIn: [], + processValuesOut: [], + reportParameters: [], + controlEnable: this.controlEnable, + lastChange: this.lastStatusChange ? + (new Date().getTime() - this.lastStatusChange.getTime()) / 1000 : + undefined }; } /** * Get all strategies for service with its current strategyParameters - * @returns {Promise} */ - public async getStrategies(): Promise { - return await Promise.all(this.strategies.map(async (strategy) => { + public getStrategies(): StrategyInterface[] { + return this.strategies.map((strategy) => { return { id: strategy.id, name: strategy.name, - default: strategy.default, - sc: strategy.sc, - parameters: await this.getCurrentParameters(strategy).catch(() => undefined) + default: strategy.defaultStrategy, + sc: strategy.selfCompleting, + parameters: this.getCurrentParameters(strategy) }; - })); + }); } - /** get current strategyParameters + /** get current parameters * from strategy or service (if strategy is undefined) - * @param {StrategyInterface} strategy - * @returns {Promise} + * @param {Strategy} strategy */ - public async getCurrentParameters(strategy?: Strategy): Promise { + public getCurrentParameters(strategy?: Strategy): ParameterInterface[] { let params: DataAssembly[] = []; if (strategy) { params = strategy.parameters; } else { params = this.parameters; } - let tasks = []; + let paramInterface = []; if (params) { - tasks = params.map(async (param) => { - const name = param.name; - let value; - let max; - let min; - let unit; - if (DataAssemblyFactory.isAnaView(param)) { - value = param.V.value; - max = param.VSclMax.value; - min = param.VSclMin.value; - unit = param.VUnit.value; - } else if (DataAssemblyFactory.isExtAnaOp(param)) { - value = param.VRbk.value; - max = param.VMax.value; - min = param.VMin.value; - unit = param.VUnit.value; - } - if (DataAssemblyFactory.isDigView(param)) { - value = param.V.value; - max = param.VSclMax.value; - min = param.VSclMin.value; - unit = param.VUnit.value; - } else if (DataAssemblyFactory.isExtDigOp(param)) { - value = param.VRbk.value; - max = param.VMax.value; - min = param.VMin.value; - unit = param.VUnit.value; - } else if (DataAssemblyFactory.isBinView(param)) { - value = param.V.value; - } else if (DataAssemblyFactory.isExtBinOp(param)) { - value = param.VRbk.value; - } else if (DataAssemblyFactory.isStrView(param)) { - value = param.Text.value; - } - if (unit) { - const unitItem = UNIT.find((item) => item.value === unit); - unit = unitItem.unit; - } - return { - name, - value, - max, - min, - unit, - readonly: param.interfaceClass === 'StrView', - type: interfaceClassToType[param.interfaceClass] - }; - }); + paramInterface = params.map((param) => param.toJson()); } - return await Promise.all(tasks); + return paramInterface; } /** * Set strategy and strategy parameters and execute a command for service on PEA * @param {ServiceCommand} command command to be executed on PEA - * @param {StrategyInterface} strategy to be set on PEA + * @param {Strategy} strategy strategy to be set on PEA * @param {Parameter[]|ParameterOptions[]} parameters strategyParameters to be set on PEA * @returns {Promise} */ - public async execute(command?: ServiceCommand, strategy?: Strategy, parameters?: Array): Promise { - if (!this.parent.isConnected()) { + public async execute(command?: ServiceCommand, + strategy?: Strategy, + parameters?: Array): Promise { + if (!this.connection.isConnected()) { throw new Error('Module is not connected'); } - this.logger.info(`[${this.qualifiedName}] Execute ${command} (${ strategy ? strategy.name : '' })`); - let result; - if (strategy || parameters) { - await this.setStrategyParameters(strategy, parameters); + this.logger.info(`[${this.qualifiedName}] Execute ${command} (${strategy ? strategy.name : ''})`); + if (strategy) { + await this.setStrategy(strategy); } if (!strategy) { strategy = await this.getCurrentStrategy(); } if (command) { - result = await this.executeCommand(command); + await this.executeCommand(command); } - this.emit('commandExecuted', { - timestampPfe: new Date(), - strategy, - command, - parameter: await this.getCurrentParameters(strategy) + this.eventEmitter.emit('commandExecuted', { + timestamp: new Date(), + strategy: strategy, + command: command, + parameter: this.getCurrentParameters(strategy) }); - return result; + } + + // overridden method from Base Service + public start() { + return this.sendCommand(ServiceMtpCommand.START); + } + + public restart() { + return this.sendCommand(ServiceMtpCommand.RESTART); + } + + public stop() { + return this.sendCommand(ServiceMtpCommand.STOP); + } + + public reset() { + return this.sendCommand(ServiceMtpCommand.RESET); + } + + public complete() { + return this.sendCommand(ServiceMtpCommand.COMPLETE); + } + + public abort() { + return this.sendCommand(ServiceMtpCommand.ABORT); + } + + public unhold() { + return this.sendCommand(ServiceMtpCommand.UNHOLD); + } + + public pause() { + return this.sendCommand(ServiceMtpCommand.PAUSE); + } + + public resume() { + return this.sendCommand(ServiceMtpCommand.RESUME); } /** @@ -531,154 +417,71 @@ export class Service extends (EventEmitter as new() => ServiceEmitter) { return Promise.all(tasks); } - /** Set strategy and strategy parameter + /** Set strategy * Use default strategy if strategy is omitted * * @param {StrategyInterface|string} strategy object or name of desired strategy * @param {(Parameter|ParameterOptions)[]} parameters * @returns {Promise} */ - public async setStrategyParameters(strategy?: Strategy | string, parameters?: Array): Promise { - // get strategy from input strategyParameters + + public async setStrategy(strategy?: Strategy | string, parameters?: Parameter[]): Promise { + // get strategy from input parameters let strat: Strategy; if (!strategy) { - strat = this.strategies.find((str) => str.default === true); + strat = this.defaultStrategy; } else if (typeof strategy === 'string') { - strat = this.strategies.find((str) => str.name === strategy); + strat = this.strategies.find((strati) => strati.name === strategy); } else { strat = strategy; } - // set strategy - this.logger.info(`[${this.qualifiedName}] Set strategy "${strat.name}" (${strat.id})`); - await this.parent.writeNode(this.automaticMode ? this.strategy : this.strategyMan, - { - dataType: DataType.UInt32, - value: strat.id, - arrayType: VariantArrayType.Scalar, - dimensions: null - }); - - // set strategy parameters + // first set opMode and then set strategy + await this.setOperationMode(); + const node = this.automaticMode ? + this.serviceControl.communication.StrategyExt : this.serviceControl.communication.StrategyMan; + await node.write(strat.id); if (parameters) { - const params: Parameter[] = parameters.map((param) => { - if (param instanceof Parameter) { - return param; - } else { - return new Parameter(param, this, strat); - } - }); - const tasks = params.map((param: Parameter) => param.updateValueOnModule()); - const paramResults = await Promise.all(tasks); - this.logger.trace(`[${this.qualifiedName}] Set Parameter Promises: ${JSON.stringify(paramResults)}`); - this.listenToServiceParameters(params); + this.setParameters(parameters); } } + /** Set both service parameter and strategy parameter + * + * @param parameters + */ + public async setParameters(parameters: Array = []): Promise { + const params: Parameter[] = await Promise.all(parameters.map(async (param) => { + if (param instanceof Parameter) { + return param; + } else { + const strat = await this.getCurrentStrategy(); + return new Parameter(param, this, strat); + } + })); + const tasks = params.map((param: Parameter) => param.updateValueOnModule()); + const paramResults = await Promise.all(tasks); + this.logger.trace(`[${this.qualifiedName}] Set Parameter Promises: ${JSON.stringify(paramResults)}`); + await this.listenToServiceParameters(params); + } + public setOperationMode(): Promise { if (this.automaticMode) { - return this.setToAutomaticOperationMode(); + return this.serviceControl.setToAutomaticOperationMode(); } else { - return this.setToManualOperationMode(); + return this.serviceControl.setToManualOperationMode(); } } public async waitForOpModeToPassSpecificTest(testFunction: (opMode: OpMode) => boolean) { - return new Promise((resolve) => { - const event = this.parent.listenToOpcUaNode(this.opMode); - event.on('changed', function test(data) { - if (testFunction(data.value)) { - event.removeListener('changed', test); - resolve(); - } - }); - }); - } - - public async isCommandExecutable(command: ServiceCommand): Promise { - const controlEnable: ControlEnableInterface = await this.getControlEnable(true); - this.logger.debug(`[${this.qualifiedName}] ControlEnable: ${JSON.stringify(controlEnable)}`); - return controlEnable[command]; - } - - /** - * Execute command by writing ControlOp/ControlExt - * - * @param {ServiceCommand} command - * @returns {Promise} - */ - private async executeCommand(command: ServiceCommand): Promise { - const commandExecutable = await this.isCommandExecutable(command); - if (!commandExecutable) { - this.logger.info(`[${this.qualifiedName}] ControlOp does not allow ${command}`); - throw new Error(`[${this.qualifiedName}] ControlOp does not allow command ${command}`); - } - let result; - if (command === ServiceCommand.start) { - result = this.start(); - } else if (command === ServiceCommand.stop) { - result = this.stop(); - } else if (command === ServiceCommand.reset) { - result = this.reset(); - } else if (command === ServiceCommand.complete) { - result = this.complete(); - } else if (command === ServiceCommand.abort) { - result = this.abort(); - } else if (command === ServiceCommand.unhold) { - result = this.unhold(); - } else if (command === ServiceCommand.pause) { - result = this.pause(); - } else if (command === ServiceCommand.resume) { - result = this.resume(); - } else if (command === ServiceCommand.restart) { - result = this.restart(); - } else { - throw new Error(`Command ${command} can not be interpreted`); - } - return result; - } - - private start(): Promise { - return this.sendCommand(ServiceMtpCommand.START); - } - - private restart(): Promise { - return this.sendCommand(ServiceMtpCommand.RESTART); - } - - private stop(): Promise { - return this.sendCommand(ServiceMtpCommand.STOP); - } - - private reset(): Promise { - return this.sendCommand(ServiceMtpCommand.RESET); - } - - private complete(): Promise { - return this.sendCommand(ServiceMtpCommand.COMPLETE); - } - - private abort(): Promise { - return this.sendCommand(ServiceMtpCommand.ABORT); - } - - private unhold(): Promise { - return this.sendCommand(ServiceMtpCommand.UNHOLD); - } - - private pause(): Promise { - return this.sendCommand(ServiceMtpCommand.PAUSE); - } - - private resume(): Promise { - return this.sendCommand(ServiceMtpCommand.RESUME); + return this.serviceControl.waitForOpModeToPassSpecificTest(testFunction); } private listenToServiceParameters(parameters: Parameter[]) { parameters.forEach((param) => { if (param.continuous) { - const listener: EventEmitter = param.listenToParameter() - .on('changed', () => param.updateValueOnModule()); + const listener: EventEmitter = param.listenToParameter(); + listener.on('changed', () => param.updateValueOnModule()); this.serviceParametersEventEmitters.push(listener); } }); @@ -692,86 +495,46 @@ export class Service extends (EventEmitter as new() => ServiceEmitter) { this.serviceParametersEventEmitters.forEach((listener) => listener.removeAllListeners()); } - /** - * Write OpMode to service - * @param {OpMode} opMode - * @returns {boolean} - */ - private async writeOpMode(opMode: OpMode): Promise { - this.logger.debug(`[${this.qualifiedName}] Write opMode ` + - `(${this.opMode.namespace_index} - ${this.opMode.node_id}): ${opMode as number}`); - const result = await this.parent.writeNode(this.opMode, - { - dataType: DataType.UInt32, - value: opMode, - arrayType: VariantArrayType.Scalar, - dimensions: null - }); - this.logger.debug(`[${this.qualifiedName}] Setting opMode ${JSON.stringify(result)}`); - if (result.value !== 0) { - this.logger.warn(`[${this.qualifiedName}] Error while setting opMode to ${opMode}: ` + - `${JSON.stringify(result)}`); - return Promise.reject(); - } else { - return Promise.resolve(); - } + private async sendCommand(command: ServiceMtpCommand) { + this.logger.info(`[${this.qualifiedName}] Send command ${ServiceMtpCommand[command]}`); + await this.setOperationMode(); + + const node = this.automaticMode ? this.commandExtNode : this.commandManNode; + await node.write(command); + this.logger.info(`[${this.qualifiedName}] Command ${ServiceMtpCommand[command]} written`); } - /** - * Set service to automatic operation mode and source to external source - * @returns {Promise} - */ - private async setToAutomaticOperationMode(): Promise { - const opMode: OpMode = await this.getOpMode(); - this.logger.debug(`[${this.qualifiedName}] Current opMode = ${JSON.stringify(opModetoJson(opMode))}`); - if (isOffState(this.opMode.value as OpMode)) { - this.logger.info(`[${this.qualifiedName}] Go to Manual state`); - this.writeOpMode(OpMode.stateManOp); - await this.waitForOpModeToPassSpecificTest(isManualState); - this.logger.info(`[${this.qualifiedName}] in ManualMode`); + private checkServiceControl() { + if (!this.opModeNode) { + throw new Error(`No OpMode variable in service ${this.qualifiedName} during parsing`); } - if (isManualState(this.opMode.value as OpMode)) { - this.logger.info(`[${this.qualifiedName}] Go to Automatic state`); - this.writeOpMode(OpMode.stateAutOp); - await this.waitForOpModeToPassSpecificTest(isAutomaticState); - this.logger.info(`[${this.qualifiedName}] in AutomaticMode`); + if (!this.statusNode) { + throw new Error(`No status variable in service ${this.qualifiedName} during parsing`); } - if (!isExtSource(this.opMode.value as OpMode)) { - this.logger.info(`[${this.qualifiedName}] Go to External source`); - this.writeOpMode(OpMode.srcExtOp); - await this.waitForOpModeToPassSpecificTest(isExtSource); - this.logger.info(`[${this.qualifiedName}] in ExtSource`); + if (!this.commandEnableNode) { + throw new Error(`No commandEnable variable in service ${this.qualifiedName} during parsing`); } - } - private async setToManualOperationMode(): Promise { - await this.getOpMode(); - if (!isManualState(this.opMode.value as OpMode)) { - this.logger.info(`[${this.qualifiedName}] Go to Manual state`); - await this.writeOpMode(OpMode.stateManOp); - await this.waitForOpModeToPassSpecificTest(isManualState); - this.logger.info(`[${this.qualifiedName}] in ManualMode`); + if (!this.commandExtNode) { + throw new Error(`No commandExt variable in service ${this.qualifiedName} during parsing`); } - } - private async sendCommand(command: ServiceMtpCommand): Promise { - if (!this.parent.isConnected()) { - throw new Error('Module is not connected'); + if (!this.commandManNode) { + throw new Error(`No commandMan variable in service ${this.qualifiedName} during parsing`); } - this.logger.info(`[${this.qualifiedName}] Send command ${ServiceMtpCommand[command]}`); - await this.setOperationMode(); - const result = await this.parent.writeNode(this.automaticMode ? this.command : this.commandMan, - { - dataType: DataType.UInt32, - value: command, - arrayType: VariantArrayType.Scalar - }); - this.logger.info(`[${this.qualifiedName}] Command ${ServiceMtpCommand[command]} written: ${result.name}`); + if (!this.strategyExtNode) { + throw new Error(`No strategyExt variable in service ${this.qualifiedName} during parsing`); + } - return result.value === 0; - } + if (!this.strategyManNode) { + throw new Error(`No strategyMan variable in service ${this.qualifiedName} during parsing`); + } + if (!this.currentStrategyNode) { + throw new Error(`No currentStrategy variable in service ${this.name} during parsing`); + } + } } diff --git a/src/model/core/Strategy.ts b/src/model/core/Strategy.ts index 4392f369..78eae608 100644 --- a/src/model/core/Strategy.ts +++ b/src/model/core/Strategy.ts @@ -23,75 +23,61 @@ * SOFTWARE. */ +import {StrategyOptions} from '@p2olab/polaris-interface'; import {EventEmitter} from 'events'; import StrictEventEmitter from 'strict-event-emitter-types'; -import {DataAssembly, DataAssemblyOptions} from '../dataAssembly/DataAssembly'; +import {Category} from 'typescript-logging'; +import {catStrategy} from '../../config/logging'; +import {DataAssembly} from '../dataAssembly/DataAssembly'; import {DataAssemblyFactory} from '../dataAssembly/DataAssemblyFactory'; -import {OpcUaNodeOptions} from './Interfaces'; -import {Module} from './Module'; - -export interface StrategyOptions { - id: string; - // name of strategy - name: string; - // default strategy - default: boolean; - // self-completing strategy - sc: boolean; - // strategyParameters of strategy - parameters: DataAssemblyOptions[]; - // process values of strategy - processValues: DataAssemblyOptions[]; -} +import {OpcUaDataItem} from '../dataAssembly/DataItem'; +import {OpcUaConnection} from './OpcUaConnection'; export interface StrategyEvents { - processValueChanged: { processValue: DataAssembly, value: any, timestamp: Date }; - parameterChanged: { parameter: DataAssembly, value: any, timestamp: Date }; } type StrategyEmitter = StrictEventEmitter; export class Strategy extends (EventEmitter as new() => StrategyEmitter) { - public id: string; - // name of strategy - public name: string; - // default strategy - public default: boolean; - // self-completing strategy - public sc: boolean; - // strategyParameters of strategy - public parameters: DataAssembly[] = []; - // process values of strategy - public processValues: DataAssembly[] = []; + public readonly id: string; + public readonly name: string; + public readonly defaultStrategy: boolean; + public readonly selfCompleting: boolean; + public readonly parameters: DataAssembly[] = []; + private readonly logger: Category; - constructor(options: StrategyOptions, module: Module) { + constructor(options: StrategyOptions, connection: OpcUaConnection) { super(); this.id = options.id; this.name = options.name; - this.default = options.default; - this.sc = options.sc; - this.parameters = options.parameters.map((paramOptions) => DataAssemblyFactory.create(paramOptions, module)); - if (options.processValues) { - this.processValues = options.processValues - .map((pvOptions) => DataAssemblyFactory.create(pvOptions, module)); - } + this.defaultStrategy = options.default; + this.selfCompleting = options.sc; + this.parameters = options.parameters.map((paramOpts) => DataAssemblyFactory.create(paramOpts, connection)); + this.logger = catStrategy; } - public subscribe() { - this.parameters.map((param) => param.subscribe() - .on('VRbk', (data: OpcUaNodeOptions) => { - this.emit('parameterChanged', {parameter: param, value: data.value, timestamp: data.timestamp}); - }) - .on('Text', (data: OpcUaNodeOptions) => { - this.emit('parameterChanged', {parameter: param, value: data.value, timestamp: data.timestamp}); - }) - ); - this.processValues.map((pv) => pv.subscribe() - .on('V', (data: OpcUaNodeOptions) => { - this.emit('processValueChanged', {processValue: pv, value: data.value, timestamp: data.timestamp}); + public async subscribe(): Promise { + this.logger.debug(`Subscribe to strategy ${this.name}: ${JSON.stringify(this.parameters.map((p) => p.name))}`); + await Promise.all( + this.parameters.map((param) => { + param + .on('VRbk', (data: OpcUaDataItem) => { + this.emit('parameterChanged', {parameter: param, value: data.value, timestamp: data.timestamp}); + }) + .on('Text', (data: OpcUaDataItem) => { + this.emit('parameterChanged', {parameter: param, value: data.value, timestamp: data.timestamp}); + }); + return param.subscribe(); }) ); + this.logger.debug(`Subscribed to strategy ${this.name}: ${JSON.stringify(this.parameters.map((p) => p.name))}`); return this; } + + public unsubscribe() { + this.parameters.forEach((param) => { + param.unsubscribe(); + }); + } } diff --git a/src/model/core/enum.ts b/src/model/core/enum.ts index 56e8fa72..28ea9a3d 100644 --- a/src/model/core/enum.ts +++ b/src/model/core/enum.ts @@ -23,8 +23,7 @@ * SOFTWARE. */ -import {ControlEnableInterface} from '@p2olab/polaris-interface'; -import {OpModeInterface} from '@p2olab/polaris-interface/dist/interfaces'; +import {ControlEnableInterface, OpModeInterface} from '@p2olab/polaris-interface'; export enum ServiceState { UNDEFINED = 1, @@ -105,12 +104,16 @@ export enum OpMode { } export function opModetoJson(opMode: OpMode): OpModeInterface { - let source: "external" | "internal" = isExtSource(opMode)? "external" : "internal"; + const source: 'external' | 'internal' = isExtSource(opMode) ? 'external' : 'internal'; let state; - if (isManualState(opMode)) {state = 'manual'} - else if (isAutomaticState(opMode)) {state = 'automatic'} - else if (isOffState(opMode)) {state = 'off'} - return {state, source} + if (isManualState(opMode)) { + state = 'manual'; + } else if (isAutomaticState(opMode)) { + state = 'automatic'; + } else if (isOffState(opMode)) { + state = 'off'; + } + return {state, source}; } export function isOffState(opMode: OpMode): boolean { diff --git a/src/model/dataAssembly/AnaOp.ts b/src/model/dataAssembly/AnaOp.ts index e81b6b40..7f868eb0 100644 --- a/src/model/dataAssembly/AnaOp.ts +++ b/src/model/dataAssembly/AnaOp.ts @@ -1,3 +1,4 @@ +/* tslint:disable:max-classes-per-file */ /* * MIT License * @@ -23,34 +24,55 @@ * SOFTWARE. */ -import {DataAssembly} from './DataAssembly'; +import {ParameterInterface} from '@p2olab/polaris-interface'; +import {OpcUaConnection} from '../core/OpcUaConnection'; +import {BaseDataAssemblyRuntime, DataAssembly} from './DataAssembly'; +import {OpcUaDataItem} from './DataItem'; +import {OpModeDA, OpModeRuntime} from './mixins/OpMode'; +import {ScaleSettingsDA, ScaleSettingsRuntime} from './mixins/ScaleSettings'; +import {UnitDA, UnitDataAssemblyRuntime} from './mixins/Unit'; +import {ValueLimitationDA, ValueLimitationRuntime} from './mixins/ValueLimitation'; -export class ExtAnaOp extends DataAssembly { +export type AnaOpRuntime = BaseDataAssemblyRuntime & + UnitDataAssemblyRuntime & ValueLimitationRuntime & + ScaleSettingsRuntime & { + VOut: OpcUaDataItem; + VRbk: OpcUaDataItem; + VExt: OpcUaDataItem; +}; - get VOut() {return this.communication['VOut']} - get VUnit() {return this.communication['VUnit']} - get VSclMin() {return this.communication['VSclMin']} - get VSclMax() {return this.communication['VSclMax']} - get VExt() {return this.communication['VExt']} - get VMin() {return this.communication['VMin']} - get VMax() {return this.communication['VMax']} - get VRbk() {return this.communication['VRbk']} +export class ExtAnaOp extends ValueLimitationDA(ScaleSettingsDA(UnitDA(DataAssembly))) { + public readonly communication: AnaOpRuntime; - constructor(options, module){ - super(options, module); - this.subscribedNodes.push('VOut', 'VUnit', 'VSclMin', 'VSclMax', 'VExt', 'VMin', 'VMax', 'VRbk'); + constructor(options, connection: OpcUaConnection) { + super(options, connection); + + this.createDataItem(options, 'VOut', 'read'); + this.createDataItem(options, 'VRbk', 'read'); + this.createDataItem(options, 'VExt', 'write'); } + public toJson(): ParameterInterface { + return { + ...super.toJson(), + value: this.communication.VOut.value, + type: 'number', + readonly: false + }; + } } -export class ExtIntAnaOp extends ExtAnaOp { +export type ExtIntAnaOpRuntime = AnaOpRuntime & OpModeRuntime & { + VInt: OpcUaDataItem; +}; + +export class ExtIntAnaOp extends OpModeDA(ExtAnaOp) { - get VInt() {return this.communication['VInt']} - get OpMode() {return this.communication['OpMode']} + public readonly communication: ExtIntAnaOpRuntime; - constructor(options, module){ - super(options, module); - this.subscribedNodes.push('VInt', 'OpMode'); + constructor(options, connection: OpcUaConnection) { + super(options, connection); + this.createDataItem(options, 'VInt', 'read'); } } @@ -59,4 +81,5 @@ export class AdvAnaOp extends ExtIntAnaOp { } export class AnaServParam extends ExtIntAnaOp { -} \ No newline at end of file + +} diff --git a/src/model/dataAssembly/AnaView.ts b/src/model/dataAssembly/AnaView.ts index 4aa5176c..a8de549a 100644 --- a/src/model/dataAssembly/AnaView.ts +++ b/src/model/dataAssembly/AnaView.ts @@ -1,3 +1,4 @@ +/* tslint:disable:max-classes-per-file */ /* * MIT License * @@ -23,34 +24,35 @@ * SOFTWARE. */ -import {DataAssembly} from './DataAssembly'; +import {ParameterInterface} from '@p2olab/polaris-interface'; +import {OpcUaConnection} from '../core/OpcUaConnection'; +import {BaseDataAssemblyRuntime, DataAssembly} from './DataAssembly'; +import {OpcUaDataItem} from './DataItem'; +import {MonitorSettings} from './mixins/MonitorSettings'; +import {ScaleSettingsDA, ScaleSettingsRuntime} from './mixins/ScaleSettings'; +import {UnitDA, UnitDataAssemblyRuntime} from './mixins/Unit'; -export class AnaView extends DataAssembly { +export type AnaViewRuntime = BaseDataAssemblyRuntime & UnitDataAssemblyRuntime & ScaleSettingsRuntime & { + V: OpcUaDataItem; +}; - get V() { return this.communication['V']} - get VUnit() {return this.communication['VUnit']} - get VSclMin() {return this.communication['VSclMin']} - get VSclMax() {return this.communication['VSclMax']} +export class AnaView extends ScaleSettingsDA(UnitDA(DataAssembly)) { + public readonly communication: AnaViewRuntime; - constructor(options, module){ - super(options, module); - this.subscribedNodes.push('V', 'VUnit', 'VSclMin', 'VSclMax'); + constructor(options, connection: OpcUaConnection) { + super(options, connection); + this.createDataItem(options, 'V', 'read'); } + public toJson(): ParameterInterface { + return { + ...super.toJson(), + value: this.communication.V.value, + type: 'number', + readonly: true + }; + } } -export class AnaMon extends AnaView { - - // TODO: add getters - - constructor(options, module){ - super(options, module); - this.subscribedNodes.push( - 'VAHEn', 'VAHLim', 'VAHAct', - 'VWHEn', 'VWHLim', 'VWHAct', - 'VTHEn', 'VTHLim', 'VTHAct', - 'VALEn', 'VALLim', 'VALAct', - 'VWLEn', 'VWLLim', 'VWLAct', - 'VTLEn', 'VTLLim', 'VTLAct'); - } +export class AnaMon extends MonitorSettings(AnaView) { } diff --git a/src/model/dataAssembly/BinOp.ts b/src/model/dataAssembly/BinOp.ts index 36be3f06..24a50475 100644 --- a/src/model/dataAssembly/BinOp.ts +++ b/src/model/dataAssembly/BinOp.ts @@ -1,3 +1,4 @@ +/* tslint:disable:max-classes-per-file */ /* * MIT License * @@ -23,32 +24,29 @@ * SOFTWARE. */ -import {DataAssembly} from './DataAssembly'; +import {BaseDataAssemblyRuntime, DataAssembly} from './DataAssembly'; +import {OpcUaDataItem} from './DataItem'; +import {OpModeDA} from './mixins/OpMode'; + +export type ExtBinOpRuntime = BaseDataAssemblyRuntime & { + VExt: OpcUaDataItem; + VRbk: OpcUaDataItem; + VOut: OpcUaDataItem; + VState0: OpcUaDataItem; + VState1: OpcUaDataItem; +}; export class ExtBinOp extends DataAssembly { - get VState0() {return this.communication['VState0']} - get VState1() {return this.communication['VState1']} - get VExt() {return this.communication['VExt']} - get VOut() {return this.communication['VOut']} - get VRbk() {return this.communication['VRbk']} + public readonly communication: ExtBinOpRuntime; - constructor(options, module){ + constructor(options, module) { super(options, module); - this.subscribedNodes.push('VOut', 'VState0', 'VState1', 'VExt', 'VRbk'); } } -export class ExtIntBinOp extends ExtBinOp { - - get VInt() {return this.communication['VInt']} - get OpMode() {return this.communication['OpMode']} - - constructor(options, module){ - super(options, module); - this.subscribedNodes.push('VInt', 'OpMode'); - } +export class ExtIntBinOp extends OpModeDA(ExtBinOp) { } export class AdvBinOp extends ExtIntBinOp { @@ -56,4 +54,5 @@ export class AdvBinOp extends ExtIntBinOp { } export class BinServParam extends ExtIntBinOp { -} \ No newline at end of file + +} diff --git a/src/model/dataAssembly/BinView.ts b/src/model/dataAssembly/BinView.ts index 02a4699f..258d5da4 100644 --- a/src/model/dataAssembly/BinView.ts +++ b/src/model/dataAssembly/BinView.ts @@ -1,3 +1,4 @@ +/* tslint:disable:max-classes-per-file */ /* * MIT License * @@ -23,29 +24,35 @@ * SOFTWARE. */ -import {DataAssembly} from './DataAssembly'; +import {BaseDataAssemblyRuntime, DataAssembly} from './DataAssembly'; +import {OpcUaDataItem} from './DataItem'; -export class BinView extends DataAssembly { +export type BinViewRuntime = BaseDataAssemblyRuntime & { + V: OpcUaDataItem; + VState0: OpcUaDataItem; + VState1: OpcUaDataItem; +}; - get V() { return this.communication['V']} - get VState0() {return this.communication['VState0']} - get VState1() {return this.communication['VState1']} +export class BinView extends DataAssembly { + public readonly communication: BinViewRuntime; - constructor(options, module){ + constructor(options, module) { super(options, module); - this.subscribedNodes.push('V', 'VState0', 'VState1'); } - } +export type BinMonRuntime = BinViewRuntime & { + VFlutTi: OpcUaDataItem; + VFlutEn: OpcUaDataItem; + VFlutCnt: OpcUaDataItem; + VFlutAct: OpcUaDataItem; +}; + export class BinMon extends BinView { - // TODO: add getters + public readonly communication: BinMonRuntime; - constructor(options, module){ + constructor(options, module) { super(options, module); - this.subscribedNodes.push( - 'VFlutTi', 'VFlutEn', 'VFlutCnt', - 'VFlutAct'); } } diff --git a/src/model/dataAssembly/DataAssembly.ts b/src/model/dataAssembly/DataAssembly.ts index 9bcd8083..7711834c 100644 --- a/src/model/dataAssembly/DataAssembly.ts +++ b/src/model/dataAssembly/DataAssembly.ts @@ -23,67 +23,91 @@ * SOFTWARE. */ +import { + DataAssemblyOptions, + ParameterInterface +} from '@p2olab/polaris-interface'; import {EventEmitter} from 'events'; -import {DataType, Variant, VariantArrayType} from 'node-opcua'; -import {catParameter, catService} from '../../config/logging'; -import {isAutomaticState, isExtSource, isManualState, isOffState, OpMode} from '../core/enum'; -import {OpcUaNodeOptions} from '../core/Interfaces'; -import {Module} from '../core/Module'; - -export interface DataAssemblyOptions { - name: string; - interface_class: string; - communication: OpcUaNodeOptions[]; +import {catDataAssembly} from '../../config/logging'; +import {OpcUaConnection} from '../core/OpcUaConnection'; +import {OpcUaDataItem} from './DataItem'; + +export interface BaseDataAssemblyRuntime { + TagName: OpcUaDataItem; + TagDescription: OpcUaDataItem; + OSLevel: OpcUaDataItem; + WQC: OpcUaDataItem; } export class DataAssembly extends EventEmitter { - public name: string; - public interfaceClass: string; - public communication: OpcUaNodeOptions[]; - protected module: Module; + get OSLevel() { + return this.communication.OSLevel; + } + + get WQC() { + return this.communication.WQC; + } - constructor(options: DataAssemblyOptions, module: Module) { + public readonly name: string; + public readonly interfaceClass: string; + public readonly communication: BaseDataAssemblyRuntime; + public subscriptionActive: boolean; + public readonly connection: OpcUaConnection; + + constructor(options: DataAssemblyOptions, connection: OpcUaConnection) { super(); - this.name = options.name; + this.name = options.name; this.interfaceClass = options.interface_class; - this.communication = options.communication; + this.communication = {} as BaseDataAssemblyRuntime; + this.subscriptionActive = false; - this.subscribedNodes.push('WQC', 'OSLevel'); - this.module = module; - if (!this.module) { + this.connection = connection; + if (!this.connection) { throw new Error(`No module for data assembly: ${JSON.stringify(options)}`); } - } - - protected subscribedNodes: string[] = []; - get OSLevel() { - return this.communication['OSLevel']; + if (!options.communication) { + throw new Error('Communication variables missing while creating DataAssembly'); + } + this.createDataItem(options, 'TagName', 'read', 'string'); + this.createDataItem(options, 'TagDescription', 'read'); + this.createDataItem(options, 'OSLevel', 'write'); + this.createDataItem(options, 'WQC', 'read'); } - get WQC() { - return this.communication['WQC']; + /** + * subscribe to changes in any of the variables of this data assembly (V, VUnit, etc.) + * + * The appropriate variables are detected via the type of the data assembly + * @param samplingInterval + */ + public async subscribe(samplingInterval = 1000): Promise { + if (!this.subscriptionActive) { + catDataAssembly.debug(`subscribe to ${this.name} ` + + `with variables ${Object.keys(this.communication)}`); + await Promise.all( + Object.entries(this.communication).map( + ([key, dataItem]: [string, OpcUaDataItem]) => { + dataItem.on('changed', () => { + catDataAssembly.debug(`Emit ${this.name}.${key} = ${dataItem.value}`); + this.emit(key, dataItem); + }); + return dataItem.subscribe(samplingInterval); + }) + ); + this.subscriptionActive = true; + catDataAssembly.debug(`successfully subscribed to all variables from ${this.name}`); + } + return this; } - public subscribe(samplingInterval = 1000) { - catParameter.debug(`DataAssembly ${this.name} subscribe to ${JSON.stringify(this.subscribedNodes)}`); - this.subscribedNodes - .filter((node) => this.communication[node] && - this.communication[node].node_id && - this.communication[node].namespace_index) - .forEach((node) => { - try { - this.module.listenToOpcUaNode(this.communication[node], samplingInterval) - .on('changed', () => { - catParameter.trace(`Emit ${this.name}.${node} = ${this.communication[node].value}`); - this.emit(node, this.communication[node]); - }); - } catch (err) { - catParameter.warn(`Could not subscribe to Data Assembly ${this.name}.${node}`); - } + public unsubscribe() { + this.subscriptionActive = false; + Object.values(this.communication).forEach((dataItem: OpcUaDataItem) => { + dataItem.unsubscribe(); + dataItem.removeAllListeners('changed'); }); - return this; } /** @@ -92,98 +116,31 @@ export class DataAssembly extends EventEmitter { * @param {string} variable * @returns {Promise} */ - public async setParameter(paramValue: any, variable: string = 'VExt'): Promise { - const opcUaNode = this.communication[variable]; - const value = await this.module.readVariableNode(opcUaNode); - const opcUaDataType = value.value ? value.value.dataType : undefined; - catParameter.debug(`Get data type for ${this.module.id}.${this.name} = ${opcUaDataType}`); - - const dataValue: Variant = { - dataType: opcUaDataType, - value: paramValue, - arrayType: VariantArrayType.Scalar, - dimensions: null - }; - catService.info(`Set Parameter: ${this.name} - ${JSON.stringify(opcUaNode)} -> ${JSON.stringify(dataValue)}`); - return await this.module.writeNode(opcUaNode, dataValue); + public async setParameter(paramValue: any, variable: string = 'VExt') { + const opcUaDataItem: OpcUaDataItem = this.communication[variable]; + catDataAssembly.info(`Set Parameter: ${this.name} - ${opcUaDataItem.nodeId} ` + + `-> ${JSON.stringify(paramValue)}`); + await opcUaDataItem.write(paramValue); } - /** - * Get current opMode of DataAssembly from PEA memory. - */ - public async getOpMode(): Promise { - if (this.communication['OpMode']) { - const result = await this.module.readVariableNode(this.communication['OpMode']); - return result.value.value as OpMode; - } else { - return null; - } - } - - public async waitForOpModeToPassSpecificTest(testFunction: (opMode: OpMode) => boolean) { - return new Promise((resolve) => { - const event = this.module.listenToOpcUaNode(this.communication['OpMode']); - event.on('changed', function test(data) { - if (testFunction(data.value)) { - event.removeListener('changed', test); - resolve(); - } - }); - }); - } - - /** - * Set service to automatic operation mode and source to external source - * @returns {Promise} - */ - public async setToAutomaticOperationMode(): Promise { - const opMode: OpMode = await this.getOpMode(); - catParameter.info(`[${this.name}] Current opMode = ${opMode}`); - if (opMode && isOffState(opMode)) { - catParameter.trace('First go to Manual state'); - this.writeOpMode(OpMode.stateManOp); - await this.waitForOpModeToPassSpecificTest(isManualState); - } - - if (opMode && isManualState(opMode)) { - this.writeOpMode(OpMode.stateAutOp); - await this.waitForOpModeToPassSpecificTest(isAutomaticState); - } - - if (opMode && !isExtSource(opMode)) { - this.writeOpMode(OpMode.srcExtOp); - await this.waitForOpModeToPassSpecificTest(isExtSource); - } + public getUnit(): string { + catDataAssembly.trace(`Try to access not existing unit in ${this.name}`); + return null; } - public async setToManualOperationMode(): Promise { - const opMode = await this.getOpMode(); - if (opMode && !isManualState(opMode)) { - this.writeOpMode(OpMode.stateManOp); - await this.waitForOpModeToPassSpecificTest(isManualState); - } + public toJson(): ParameterInterface { + return { + name: this.name + }; } - /** - * Write OpMode to service - * @param {OpMode} opMode - * @returns {boolean} - */ - private async writeOpMode(opMode: OpMode): Promise { - catParameter.debug(`[${this.name}] Write opMode: ${opMode as number}`); - const result = await this.module.writeNode(this.communication['OpMode'], - { - dataType: DataType.UInt32, - value: opMode, - arrayType: VariantArrayType.Scalar, - dimensions: null - }); - catParameter.debug(`[${this.name}] Setting opMode ${JSON.stringify(result)}`); - if (result.value !== 0) { - catParameter.warn(`[${this.name}] Error while setting opMode to ${opMode}: ${JSON.stringify(result)}`); - return Promise.reject(); + public createDataItem(options: DataAssemblyOptions, name: string, access: 'read'|'write', type?) { + if (!options.communication[name]) { + catDataAssembly.warn(`No variable "${name}" found during parsing of ` + + `DataAssembly "${this.name}" (type ${this.constructor.name})`); } else { - return Promise.resolve(); + this.communication[name] = + OpcUaDataItem.fromOptions(options.communication[name], this.connection, access, type); } } } diff --git a/src/model/dataAssembly/DataAssemblyFactory.ts b/src/model/dataAssembly/DataAssemblyFactory.ts index d82b3d00..7e9fb2ed 100644 --- a/src/model/dataAssembly/DataAssemblyFactory.ts +++ b/src/model/dataAssembly/DataAssemblyFactory.ts @@ -23,23 +23,24 @@ * SOFTWARE. */ -import {catModule} from '../../config/logging'; -import {Module} from '../core/Module'; +import {DataAssemblyOptions} from '@p2olab/polaris-interface'; +import {catDataAssembly} from '../../config/logging'; +import {OpcUaConnection} from '../core/OpcUaConnection'; import {AdvAnaOp, AnaServParam, ExtAnaOp, ExtIntAnaOp} from './AnaOp'; import {AnaMon, AnaView} from './AnaView'; import {AdvBinOp, BinServParam, ExtBinOp, ExtIntBinOp} from './BinOp'; import {BinMon, BinView} from './BinView'; -import {DataAssembly, DataAssemblyOptions} from './DataAssembly'; +import {DataAssembly} from './DataAssembly'; import {AdvDigOp, DigServParam, ExtDigOp, ExtIntDigOp} from './DigOp'; import {DigMon, DigView} from './DigView'; import {AnaDrv, MonAnaDrv} from './Drv'; +import {ServiceControl} from './ServiceControl'; import {StrView} from './Str'; import {AnaVlv, BinVlv, MonAnaVlv, MonBinVlv} from './Vlv'; export class DataAssemblyFactory { - public static create(variableOptions: DataAssemblyOptions, module: Module): DataAssembly { - catModule.debug(`Create DataAssembly ${variableOptions.name} (${variableOptions.interface_class})`); - + public static create(variableOptions: DataAssemblyOptions, connection: OpcUaConnection): DataAssembly { + catDataAssembly.debug(`Create DataAssembly ${variableOptions.name} (${variableOptions.interface_class})`); const types = { 'AnaView': AnaView, 'AnaMon': AnaMon, @@ -70,79 +71,22 @@ export class DataAssemblyFactory { 'AnaDrv': AnaDrv, 'MonAnaDrv': MonAnaDrv, - 'StrView': StrView + 'StrView': StrView, + + 'ServiceControl': ServiceControl }; let type = types[variableOptions.interface_class]; if (!type) { - catModule.warn(`No data assembly implemented for ${variableOptions.interface_class} ` + - `of ${variableOptions.name}. Use standard DataAssembly.`); + if (!variableOptions.interface_class) { + catDataAssembly.debug(`No interface class specified for DataAssembly ${variableOptions.name}. ` + + `Use standard DataAssembly.`); + } else { + catDataAssembly.warn(`No data assembly implemented for ${variableOptions.interface_class} ` + + `of ${variableOptions.name}. Use standard DataAssembly.`); + } type = DataAssembly; } - return new type(variableOptions, module); - } - - public static isAnaView(dataAssembly: DataAssembly): dataAssembly is AnaView { - return dataAssembly.interfaceClass === 'AnaView'; - } - - public static isExtAnaOp(dataAssembly: DataAssembly): dataAssembly is ExtAnaOp { - return this.isExtIntAnaOp(dataAssembly) || dataAssembly.interfaceClass === 'ExtAnaOp'; - } - - public static isExtIntAnaOp(dataAssembly: DataAssembly): dataAssembly is ExtIntAnaOp { - return this.isAdvAnaOp(dataAssembly) || dataAssembly.interfaceClass === 'ExtIntAnaOp'; - } - - public static isAdvAnaOp(dataAssembly: DataAssembly): dataAssembly is AdvAnaOp { - return this.isAnaServParam(dataAssembly) || dataAssembly.interfaceClass === 'AdvAnaOp'; - } - - public static isAnaServParam(dataAssembly: DataAssembly): dataAssembly is AnaServParam { - return dataAssembly.interfaceClass === 'AnaServParam'; - } - - public static isDigView(dataAssembly: DataAssembly): dataAssembly is DigView { - return dataAssembly.interfaceClass === 'DigView'; - } - - public static isExtDigOp(dataAssembly: DataAssembly): dataAssembly is ExtDigOp { - return this.isExtIntDigOp(dataAssembly) || dataAssembly.interfaceClass === 'ExtDigOp'; - } - - public static isExtIntDigOp(dataAssembly: DataAssembly): dataAssembly is ExtIntDigOp { - return this.isAdvDigOp(dataAssembly) || dataAssembly.interfaceClass === 'ExtIntDigOp'; - } - - public static isAdvDigOp(dataAssembly: DataAssembly): dataAssembly is AdvDigOp { - return this.isDigServParam(dataAssembly) || dataAssembly.interfaceClass === 'AdvDigOp'; - } - - public static isDigServParam(dataAssembly: DataAssembly): dataAssembly is DigServParam { - return dataAssembly.interfaceClass === 'DigServParam'; - } - - public static isBinView(dataAssembly: DataAssembly): dataAssembly is BinView { - return dataAssembly.interfaceClass === 'BinView'; - } - - public static isExtBinOp(dataAssembly: DataAssembly): dataAssembly is ExtBinOp { - return this.isExtIntBinOp(dataAssembly) || dataAssembly.interfaceClass === 'ExtBinOp'; - } - - public static isExtIntBinOp(dataAssembly: DataAssembly): dataAssembly is ExtIntBinOp { - return this.isAdvBinOp(dataAssembly) || dataAssembly.interfaceClass === 'ExtIntBinOp'; - } - - public static isAdvBinOp(dataAssembly: DataAssembly): dataAssembly is AdvBinOp { - return this.isBinServParam(dataAssembly) || dataAssembly.interfaceClass === 'AdvBinOp'; - } - - public static isBinServParam(dataAssembly: DataAssembly): dataAssembly is BinServParam { - return dataAssembly.interfaceClass === 'BinServParam'; - } - - public static isStrView(dataAssembly: DataAssembly): dataAssembly is StrView { - return dataAssembly.interfaceClass === 'StrView'; + return new type(variableOptions, connection); } } diff --git a/src/model/dataAssembly/DataItem.ts b/src/model/dataAssembly/DataItem.ts new file mode 100644 index 00000000..2ba9e465 --- /dev/null +++ b/src/model/dataAssembly/DataItem.ts @@ -0,0 +1,145 @@ +/* tslint:disable:max-classes-per-file */ +/* + * MIT License + * + * Copyright (c) 2019 Markus Graube , + * Chair for Process Control Systems, Technische Universität Dresden + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +import {OpcUaNodeOptions} from '@p2olab/polaris-interface'; +import {EventEmitter} from 'events'; +import {DataType} from 'node-opcua-client'; +import {timeout} from 'promise-timeout'; +import StrictEventEmitter from 'strict-event-emitter-types'; +import {Category} from 'typescript-logging'; +import {OpcUaConnection} from '../core/OpcUaConnection'; + +const catDataItem = new Category('DataItem'); + +/** + * Events emitted by [[DataItem]] + */ +export interface DataItemEvents { + /** + * when OpcUaNodeOptions changes its value + * @event changed + */ + changed: { value: any, timestamp: Date }; +} + +export type DataItemEmitter = StrictEventEmitter; + +export abstract class DataItem extends (EventEmitter as new() => DataItemEmitter) { + // data type of OPC UA node + public dataType: string; + // recent value + public value: T; + // timestamp of last update of value + public timestamp: Date; + // can DataItem be accessed + public access: 'read' | 'write'; + + protected logger: Category = catDataItem; + + public abstract async subscribe(samplingInterval); + + public abstract write(value: string|number); +} + +export class OpcUaDataItem extends DataItem { + + public static fromOptions( + options: OpcUaNodeOptions, connection: OpcUaConnection, + access: 'read' | 'write', type = 'number'): OpcUaDataItem { + const item = new OpcUaDataItem(); + + if (options) { + if (options.value !== undefined) { + if (type === 'number' && (typeof options.value === 'string')) { + item.value = parseFloat(options.value) as type; + } else if (type === 'string') { + item.value = options.value.toString() as type; + } else { + item.value = options.value as type; + } + } + item.dataType = options.data_type; + + item.namespaceIndex = options.namespace_index; + item.nodeId = options.node_id; + if ((item.nodeId == null || item.namespaceIndex == null) && item.value == null) { + catDataItem.warn('At least node id or value have to be specified during parsing of DataItem'); + } + } + item.access = access; + item.connection = connection; + return item; + } + + // this variable contains the *namespace url* of the node + public namespaceIndex: string; + // node id of the node as string (e.g. 's=myNode2' or 'i=12') + public nodeId: string; + + private connection: OpcUaConnection; + + public async subscribe(samplingInterval = 1000): Promise> { + if (this.value === undefined) { + await this.read(); + } + const monitoredItem = await timeout( + this.connection.listenToOpcUaNode(this.nodeId, this.namespaceIndex, samplingInterval), 1000); + monitoredItem.on('changed', (dataValue) => { + this.logger.debug(`[${this.connection.id}] Variable Changed (${this.nodeId}) ` + + `= ${dataValue.value.value.toString()}`); + this.value = dataValue.value.value; + this.timestamp = dataValue.serverTimestamp; + this.emit('changed', {value: this.value, timestamp: this.timestamp}); + }); + this.logger.debug(`subscribed to Data Item ${this.nodeId}`); + return this; + } + + public unsubscribe() { + this.logger.debug('unsubscribe from data item'); + } + + public write(value: number | string) { + return this.connection.writeOpcUaNode(this.nodeId, this.namespaceIndex, value, this.dataType); + } + + /** + * Reads the opc ua data item of the data item and use the results for initializing the data item + */ + public async read() { + const result = await this.connection.readOpcUaNode(this.nodeId, this.namespaceIndex); + this.logger.debug(`[${this.connection.id}] Read Variable: ${this.nodeId.toString()} = ${result}`); + if (result.statusCode.value !== 0) { + throw new Error(`Could not read ${this.nodeId.toString()}: ${result.statusCode.description}`); + } + this.value = result.value.value; + // readVariableValue does not provide serverTimestamp in node-opcua library + this.timestamp = new Date(); + this.dataType = DataType[result.value.dataType]; + this.logger.debug(`[${this.connection.id}] initialized Variable: ${this.nodeId.toString()} - ${this.value}`); + return this.value; + } +} diff --git a/src/model/dataAssembly/DigOp.ts b/src/model/dataAssembly/DigOp.ts index b4ea0ab4..a734299d 100644 --- a/src/model/dataAssembly/DigOp.ts +++ b/src/model/dataAssembly/DigOp.ts @@ -1,3 +1,4 @@ +/* tslint:disable:max-classes-per-file */ /* * MIT License * @@ -23,35 +24,18 @@ * SOFTWARE. */ +import {AnaOpRuntime} from './AnaOp'; import {DataAssembly} from './DataAssembly'; +import {OpModeDA} from './mixins/OpMode'; +import {ScaleSettingsDA} from './mixins/ScaleSettings'; +import {UnitDA} from './mixins/Unit'; -export class ExtDigOp extends DataAssembly { - - get VOut() {return this.communication['VOut']} - get VUnit() {return this.communication['VUnit']} - get VSclMin() {return this.communication['VSclMin']} - get VSclMax() {return this.communication['VSclMax']} - get VExt() {return this.communication['VExt']} - get VMin() {return this.communication['VMin']} - get VMax() {return this.communication['VMax']} - get VRbk() {return this.communication['VRbk']} - - constructor(options, module){ - super(options, module); - this.subscribedNodes.push('VOut', 'VUnit', 'VSclMin', 'VSclMax', 'VExt', 'VMin', 'VMax', 'VRbk'); - } - +export class ExtDigOp extends ScaleSettingsDA(UnitDA(DataAssembly)) { + public readonly communication: AnaOpRuntime; } -export class ExtIntDigOp extends ExtDigOp { +export class ExtIntDigOp extends OpModeDA(ExtDigOp) { - get VInt() {return this.communication['VInt']} - get OpMode() {return this.communication['OpMode']} - - constructor(options, module){ - super(options, module); - this.subscribedNodes.push('VInt', 'OpMode'); - } } export class AdvDigOp extends ExtIntDigOp { @@ -59,4 +43,4 @@ export class AdvDigOp extends ExtIntDigOp { } export class DigServParam extends ExtIntDigOp { -} \ No newline at end of file +} diff --git a/src/model/dataAssembly/DigView.ts b/src/model/dataAssembly/DigView.ts index 174bb8d7..1fbba98d 100644 --- a/src/model/dataAssembly/DigView.ts +++ b/src/model/dataAssembly/DigView.ts @@ -1,3 +1,4 @@ +/* tslint:disable:max-classes-per-file */ /* * MIT License * @@ -23,34 +24,30 @@ * SOFTWARE. */ +import {ParameterInterface} from '@p2olab/polaris-interface'; +import {AnaViewRuntime} from './AnaView'; import {DataAssembly} from './DataAssembly'; +import {MonitorSettings} from './mixins/MonitorSettings'; +import {ScaleSettingsDA} from './mixins/ScaleSettings'; +import {UnitDA} from './mixins/Unit'; -export class DigView extends DataAssembly { +export class DigView extends ScaleSettingsDA(UnitDA(DataAssembly)) { + public readonly communication: AnaViewRuntime; - get V() { return this.communication['V']} - get VUnit() {return this.communication['VUnit']} - get VSclMin() {return this.communication['VSclMin']} - get VSclMax() {return this.communication['VSclMax']} - - constructor(options, module){ - super(options, module); - this.subscribedNodes.push('V', 'VUnit', 'VSclMin', 'VSclMax'); + constructor(options, connection) { + super(options, connection); + this.createDataItem(options, 'V', 'read'); } + public toJson(): ParameterInterface { + return { + ...super.toJson(), + value: this.communication.V.value, + type: 'number', + readonly: true + }; + } } -export class DigMon extends DigView { - - // TODO: add getters - - constructor(options, module){ - super(options, module); - this.subscribedNodes.push( - 'VAHEn', 'VAHLim', 'VAHAct', - 'VWHEn', 'VWHLim', 'VWHAct', - 'VTHEn', 'VTHLim', 'VTHAct', - 'VALEn', 'VALLim', 'VALAct', - 'VWLEn', 'VWLLim', 'VWLAct', - 'VTLEn', 'VTLLim', 'VTLAct'); - } +export class DigMon extends MonitorSettings(DigView) { } diff --git a/src/model/dataAssembly/Drv.ts b/src/model/dataAssembly/Drv.ts index dc2db5d8..1661ce96 100644 --- a/src/model/dataAssembly/Drv.ts +++ b/src/model/dataAssembly/Drv.ts @@ -1,3 +1,4 @@ +/* tslint:disable:max-classes-per-file */ /* * MIT License * @@ -23,12 +24,52 @@ * SOFTWARE. */ -import {DataAssembly} from './DataAssembly'; +import {BaseDataAssemblyRuntime, DataAssembly} from './DataAssembly'; +import {OpcUaDataItem} from './DataItem'; +import {OpModeRuntime} from './mixins/OpMode'; + +export type AnaDrvRuntime = BaseDataAssemblyRuntime & OpModeRuntime & { + FwdEn: OpcUaDataItem; + RevEn: OpcUaDataItem; + StopOp: OpcUaDataItem; + FwdOp: OpcUaDataItem; + RevOp: OpcUaDataItem; + StopLi: OpcUaDataItem; + FwdLi: OpcUaDataItem; + RevLi: OpcUaDataItem; + FwdCtrl: OpcUaDataItem; + RevCtrl: OpcUaDataItem; + RevFbkEn: OpcUaDataItem; + FwdFbkEn: OpcUaDataItem; + RevFbk: OpcUaDataItem; + FwdFbk: OpcUaDataItem; + SafePos: OpcUaDataItem; + Trip: OpcUaDataItem; + RpmSclMax: OpcUaDataItem; + RpmSclMin: OpcUaDataItem; + RpmUnit: OpcUaDataItem; + RpmInt: OpcUaDataItem; + RpmExt: OpcUaDataItem; + RpmMin: OpcUaDataItem; + RpmMax: OpcUaDataItem; + Rpm: OpcUaDataItem; + RpmFbk: OpcUaDataItem; + PermEn: OpcUaDataItem; + Permit: OpcUaDataItem; + IntlEn: OpcUaDataItem; + Interlock: OpcUaDataItem; + ProtEn: OpcUaDataItem; + Protect: OpcUaDataItem; + ResetOp: OpcUaDataItem; + ResetLi: OpcUaDataItem; +}; export class AnaDrv extends DataAssembly { - constructor(options, module) { - super(options, module); - this.subscribedNodes.push('OpMode', 'FwdEn', 'RevEn', 'StopOp', 'FwdOp', 'RevOp', 'StopLi', 'FwdLi', 'RevLi', 'FwdCtrl', 'RevCtrl', 'RevFbkEn', 'FwdFbkEn', 'RevFbk', 'FwdFbk', 'SafePos', 'Trip', 'RpmSclMax', 'RpmSclMin', 'RpmUnit', 'RpmInt', 'RpmExt', 'RpmMin', 'RpmMax', 'Rpm', 'RpmFbk', 'PermEn', 'Permit', 'IntlEn', 'Interlock', 'ProtEn', 'Protect', 'ResetOp', 'ResetLi'); + + public readonly communication: AnaDrvRuntime; + + constructor(options, connection) { + super(options, connection); } } @@ -36,4 +77,4 @@ export class MonAnaDrv extends AnaDrv { constructor(options, module) { super(options, module); } -} \ No newline at end of file +} diff --git a/src/model/dataAssembly/ServiceControl.ts b/src/model/dataAssembly/ServiceControl.ts new file mode 100644 index 00000000..3ff1d2f3 --- /dev/null +++ b/src/model/dataAssembly/ServiceControl.ts @@ -0,0 +1,58 @@ +/* + * MIT License + * + * Copyright (c) 2019 Markus Graube , + * Chair for Process Control Systems, Technische Universität Dresden + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +/* tslint:disable:max-classes-per-file */ +import {BaseDataAssemblyRuntime, DataAssembly} from './DataAssembly'; +import {OpcUaDataItem} from './DataItem'; +import {OpModeDA} from './mixins/OpMode'; + +export interface ServiceControlRuntime extends BaseDataAssemblyRuntime { + OpMode: OpcUaDataItem; + CommandMan: OpcUaDataItem; + CommandExt: OpcUaDataItem; + CommandEnable: OpcUaDataItem; + State: OpcUaDataItem; + StrategyMan: OpcUaDataItem; + StrategyExt: OpcUaDataItem; + StrategyInt: OpcUaDataItem; + CurrentStrategy: OpcUaDataItem; +} + +export class ServiceControl extends OpModeDA(DataAssembly) { + public readonly communication: ServiceControlRuntime; + + constructor(options, connection) { + super(options, connection); + this.createDataItem(options, 'CommandMan', 'write'); + this.createDataItem(options, 'CommandExt', 'write'); + this.createDataItem(options, 'CommandEnable', 'read'); + this.createDataItem(options, 'State', 'read'); + this.createDataItem(options, 'StrategyMan', 'write'); + this.createDataItem(options, 'StrategyExt', 'write'); + this.createDataItem(options, 'StrategyInt', 'read'); + this.createDataItem(options, 'CurrentStrategy', 'read'); + } + +} diff --git a/src/model/dataAssembly/Str.ts b/src/model/dataAssembly/Str.ts index 19c34ae6..ea88e825 100644 --- a/src/model/dataAssembly/Str.ts +++ b/src/model/dataAssembly/Str.ts @@ -23,16 +23,33 @@ * SOFTWARE. */ -import {DataAssembly} from './DataAssembly'; +import {ParameterInterface} from '@p2olab/polaris-interface'; +import {BaseDataAssemblyRuntime, DataAssembly} from './DataAssembly'; +import {OpcUaDataItem} from './DataItem'; + +export type StrRuntime = BaseDataAssemblyRuntime & { + Text: OpcUaDataItem; +}; export class StrView extends DataAssembly { + public readonly communication: StrRuntime; + get Text() { - return this.communication['Text'] + return this.communication.Text; + } + + constructor(options, connection) { + super(options, connection); + this.createDataItem(options, 'Text', 'read', 'string'); } - constructor(options, module) { - super(options, module); - this.subscribedNodes.push('Text'); + public toJson(): ParameterInterface { + return { + ...super.toJson(), + value: this.Text.value, + type: 'string', + readonly: true + }; } -} \ No newline at end of file +} diff --git a/src/model/dataAssembly/Vlv.ts b/src/model/dataAssembly/Vlv.ts index 53405551..37ce7674 100644 --- a/src/model/dataAssembly/Vlv.ts +++ b/src/model/dataAssembly/Vlv.ts @@ -1,55 +1,123 @@ +/* tslint:disable:max-classes-per-file */ /* - * MIT License - * - * Copyright (c) 2019 Markus Graube , - * Chair for Process Control Systems, Technische Universität Dresden - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -import {DataAssembly} from './DataAssembly'; +* MIT License +* +* Copyright (c) 2019 Markus Graube , +* Chair for Process Control Systems, Technische Universität Dresden +* +* Permission is hereby granted, free of charge, to any person obtaining a copy +* of this software and associated documentation files (the "Software"), to deal +* in the Software without restriction, including without limitation the rights +* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +* copies of the Software, and to permit persons to whom the Software is +* furnished to do so, subject to the following conditions: +* +* The above copyright notice and this permission notice shall be included in all +* copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +* SOFTWARE. +*/ + +import {BaseDataAssemblyRuntime, DataAssembly} from './DataAssembly'; +import {OpcUaDataItem} from './DataItem'; +import {OpModeRuntime} from './mixins/OpMode'; + +export type BinVlvRuntime = BaseDataAssemblyRuntime & OpModeRuntime & { + SafePos: OpcUaDataItem; + OpenOp: OpcUaDataItem; + CloseOp: OpcUaDataItem; + OpenLi: OpcUaDataItem; + CloseLi: OpcUaDataItem; + Ctrl: OpcUaDataItem; + OpenFbkEn: OpcUaDataItem; + CloseFbkEn: OpcUaDataItem; + OpenFbk: OpcUaDataItem; + CloseFbk: OpcUaDataItem; + PermEn: OpcUaDataItem; + Permit: OpcUaDataItem; + IntlEn: OpcUaDataItem; + Interlock: OpcUaDataItem; + ProtEn: OpcUaDataItem; + Protect: OpcUaDataItem; + ResetOp: OpcUaDataItem; + ResetLi: OpcUaDataItem; +}; export class BinVlv extends DataAssembly { - constructor(options, module) { - super(options, module); - this.subscribedNodes.push('OpMode', 'SafePos', 'OpenOp', 'CloseOp', 'OpenLi', 'CloseLi', 'Ctrl', 'OpenFbkEn', 'CloseFbkEn', 'OpenFbk', 'CloseFbk', 'PermEn', 'Permit', 'IntlEn', 'Interlock', 'ProtEn', 'Protect', 'ResetOp', 'ResetLi'); - } + public readonly communication: BinVlvRuntime; } +export type MonBinVlvRuntime = BinVlvRuntime & { + MonEn: OpcUaDataItem; + MonSafePos: OpcUaDataItem; + MonStatErr: OpcUaDataItem; + MonDynErr: OpcUaDataItem; + MonStatTi: OpcUaDataItem; + MonDynTi: OpcUaDataItem; +}; + export class MonBinVlv extends BinVlv { - constructor(options, module) { - super(options, module); - this.subscribedNodes.push('MonEn', 'MonSafePos', 'MonStatErr', 'MonDynErr', 'MonStatTi', 'MonDynTi'); - } + public readonly communication: MonBinVlvRuntime; } +export type AnaVlvRuntime = BaseDataAssemblyRuntime & OpModeRuntime & { + OpenOp: OpcUaDataItem; + CloseOp: OpcUaDataItem; + OpenLi: OpcUaDataItem; + CloseLi: OpcUaDataItem; + Ctrl: OpcUaDataItem; + OpenFbkEn: OpcUaDataItem; + CloseFbkEn: OpcUaDataItem; + OpenFbk: OpcUaDataItem; + CloseFbk: OpcUaDataItem; + PosSclMin: OpcUaDataItem; + PosSclMax: OpcUaDataItem; + PosUnit: OpcUaDataItem; + PosInt: OpcUaDataItem; + PosExt: OpcUaDataItem; + PosMin: OpcUaDataItem; + PosMax: OpcUaDataItem; + SafePos: OpcUaDataItem; + PosCtrl: OpcUaDataItem; + PosFbkEn: OpcUaDataItem; + PosFbk: OpcUaDataItem; + PermEn: OpcUaDataItem; + Permit: OpcUaDataItem; + IntlEn: OpcUaDataItem; + Interlock: OpcUaDataItem; + ProtEn: OpcUaDataItem; + Protect: OpcUaDataItem; + ResetOp: OpcUaDataItem; + ResetLi: OpcUaDataItem; +}; + export class AnaVlv extends DataAssembly { - constructor(options, module) { - super(options, module); - this.subscribedNodes.push('OpMode', 'OpenOp', 'CloseOp', 'OpenLi', 'CloseLi', 'Ctrl', 'OpenFbkEn', 'CloseFbkEn', 'OpenFbk', 'CloseFbk', 'PosSclMin', 'PosSclMax', 'PosUnit', 'PosInt', 'PosExt', 'PosMin', 'PosMax', 'SafePos', 'PosCtrl', 'PosFbkEn', 'PosFbk', 'PermEn', 'Permit', 'IntlEn', 'Interlock', 'ProtEn', 'Protect', 'ResetOp', 'ResetLi'); - } + public readonly communication: AnaVlvRuntime; } +export type MonAnaVlvRuntime = AnaVlvRuntime & { + MonEn: OpcUaDataItem; + MonSafePos: OpcUaDataItem; + MonStatErr: OpcUaDataItem; + MonDynErr: OpcUaDataItem; + MonStatTi: OpcUaDataItem; + MonDynTi: OpcUaDataItem; + PosOpngFbk: OpcUaDataItem; + PosClsngFbk: OpcUaDataItem; + PosReachedFbk: OpcUaDataItem; + PosTolerance: OpcUaDataItem; + PosDefClose: OpcUaDataItem; + PosDefOpen: OpcUaDataItem; + MonPosTi: OpcUaDataItem; + MonPosErr: OpcUaDataItem; +}; + export class MonAnaVlv extends AnaVlv { - constructor(options, module) { - super(options, module); - this.subscribedNodes.push('MonEn', 'MonSafePos', 'MonStatErr', 'MonDynErr', 'MonStatTi', 'MonDynTi', 'PosOpngFbk', 'PosClsngFbk', 'PosReachedFbk', 'PosTolerance', 'PosDefClose', 'PosDefOpen', 'MonPosTi', 'MonPosErr'); - } + public readonly communication: MonAnaVlvRuntime; } - diff --git a/src/model/dataAssembly/mixins/MonitorSettings.ts b/src/model/dataAssembly/mixins/MonitorSettings.ts new file mode 100644 index 00000000..7c08ab9a --- /dev/null +++ b/src/model/dataAssembly/mixins/MonitorSettings.ts @@ -0,0 +1,78 @@ +/* + * MIT License + * + * Copyright (c) 2019 Markus Graube , + * Chair for Process Control Systems, Technische Universität Dresden + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +import {BaseDataAssemblyRuntime, DataAssembly} from '../DataAssembly'; +import {OpcUaDataItem} from '../DataItem'; +import {Constructor} from './mixins'; + +export type MonitorSettingsRuntime = BaseDataAssemblyRuntime & { + VAHEn: OpcUaDataItem; + VAHLim: OpcUaDataItem; + VAHAct: OpcUaDataItem; + VWHEn: OpcUaDataItem; + VWHLim: OpcUaDataItem; + VWHAct: OpcUaDataItem; + VTHEn: OpcUaDataItem; + VTHLim: OpcUaDataItem; + VTHAct: OpcUaDataItem; + VALEn: OpcUaDataItem; + VALLim: OpcUaDataItem; + VALAct: OpcUaDataItem; + VWLEn: OpcUaDataItem; + VWLLim: OpcUaDataItem; + VWLAct: OpcUaDataItem; + VTLEn: OpcUaDataItem; + VTLLim: OpcUaDataItem; + VTLAct: OpcUaDataItem; +}; + +// tslint:disable-next-line:variable-name +export function MonitorSettings>(Base: TBase) { + return class extends Base { + public communication: MonitorSettingsRuntime; + + constructor(...args: any[]) { + super(...args); + this.createDataItem(args[0], 'VAHEn', 'write'); + this.createDataItem(args[0], 'VAHLim', 'write'); + this.createDataItem(args[0], 'VAHAct', 'write'); + this.createDataItem(args[0], 'VWHEn', 'write'); + this.createDataItem(args[0], 'VWHLim', 'write'); + this.createDataItem(args[0], 'VWHAct', 'write'); + this.createDataItem(args[0], 'VTHEn', 'write'); + this.createDataItem(args[0], 'VTHLim', 'write'); + this.createDataItem(args[0], 'VTHAct', 'write'); + this.createDataItem(args[0], 'VALEn', 'write'); + this.createDataItem(args[0], 'VALLim', 'write'); + this.createDataItem(args[0], 'VALAct', 'write'); + this.createDataItem(args[0], 'VWLEn', 'write'); + this.createDataItem(args[0], 'VWLLim', 'write'); + this.createDataItem(args[0], 'VWLAct', 'write'); + this.createDataItem(args[0], 'VTLEn', 'write'); + this.createDataItem(args[0], 'VTLLim', 'write'); + this.createDataItem(args[0], 'VTLAct', 'write'); + } + }; +} diff --git a/src/model/dataAssembly/mixins/OpMode.ts b/src/model/dataAssembly/mixins/OpMode.ts new file mode 100644 index 00000000..e4bfc291 --- /dev/null +++ b/src/model/dataAssembly/mixins/OpMode.ts @@ -0,0 +1,106 @@ +/* + * MIT License + * + * Copyright (c) 2019 Markus Graube , + * Chair for Process Control Systems, Technische Universität Dresden + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +import {catDataAssembly} from '../../../config/logging'; +import {isAutomaticState, isExtSource, isManualState, isOffState, OpMode} from '../../core/enum'; +import {BaseDataAssemblyRuntime, DataAssembly} from '../DataAssembly'; +import {OpcUaDataItem} from '../DataItem'; +import {Constructor} from './mixins'; + +export type OpModeRuntime = BaseDataAssemblyRuntime & { + OpMode: OpcUaDataItem; +}; + +// tslint:disable-next-line:variable-name +export function OpModeDA>(Base: TBase) { + return class extends Base { + public communication: OpModeRuntime; + + constructor(...args: any[]) { + super(...args); + this.createDataItem(args[0], 'OpMode', 'write'); + } + + public getOpMode(): OpMode { + return this.communication.OpMode.value as OpMode; + } + + public async waitForOpModeToPassSpecificTest(testFunction: (opMode: OpMode) => boolean) { + if (!this.subscriptionActive) { + await this.subscribe(); + } + return new Promise((resolve) => { + if (testFunction(this.communication.OpMode.value)) { + resolve(); + } else { + this.on('OpMode', function test(data) { + if (testFunction(data.value)) { + this.removeListener('OpMode', test); + resolve(); + } + }); + } + }); + } + + /** + * Set data assembly to automatic operation mode and source to external source + */ + public async setToAutomaticOperationMode(): Promise { + catDataAssembly.debug(`[${this.name}] Current opMode = ${this.communication.OpMode.value}`); + if (isOffState(this.communication.OpMode.value)) { + catDataAssembly.trace('First go to Manual state'); + this.writeOpMode(OpMode.stateManOp); + await this.waitForOpModeToPassSpecificTest(isManualState); + } + + if (isManualState(this.communication.OpMode.value)) { + catDataAssembly.trace('Then to automatic'); + this.writeOpMode(OpMode.stateAutOp); + await this.waitForOpModeToPassSpecificTest(isAutomaticState); + } + + if (!isExtSource(this.communication.OpMode.value)) { + catDataAssembly.trace('Finally to Ext'); + this.writeOpMode(OpMode.srcExtOp); + await this.waitForOpModeToPassSpecificTest(isExtSource); + } + } + + public async setToManualOperationMode() { + const opMode = await this.getOpMode(); + if (opMode && !isManualState(opMode)) { + this.writeOpMode(OpMode.stateManOp); + await this.waitForOpModeToPassSpecificTest(isManualState); + } + } + + public async writeOpMode(opMode: OpMode) { + catDataAssembly.debug(`[${this.name}] Write opMode: ${opMode as number}`); + await this.communication.OpMode.write(opMode); + catDataAssembly.debug(`[${this.name}] Setting opMode successfully`); + } + }; +} diff --git a/src/model/dataAssembly/mixins/ScaleSettings.ts b/src/model/dataAssembly/mixins/ScaleSettings.ts new file mode 100644 index 00000000..5b501bf3 --- /dev/null +++ b/src/model/dataAssembly/mixins/ScaleSettings.ts @@ -0,0 +1,56 @@ +/* + * MIT License + * + * Copyright (c) 2019 Markus Graube , + * Chair for Process Control Systems, Technische Universität Dresden + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +import {ParameterInterface} from '@p2olab/polaris-interface'; +import {BaseDataAssemblyRuntime, DataAssembly} from '../DataAssembly'; +import {OpcUaDataItem} from '../DataItem'; +import {Constructor} from './mixins'; + +export interface ScaleSettingsRuntime extends BaseDataAssemblyRuntime { + VSclMin: OpcUaDataItem; + VSclMax: OpcUaDataItem; +} + +// tslint:disable-next-line:variable-name +export function ScaleSettingsDA>(Base: TBase) { + + return class extends Base { + public communication: ScaleSettingsRuntime; + + constructor(...args: any[]) { + super(...args); + this.createDataItem(args[0], 'VSclMax', 'read'); + this.createDataItem(args[0], 'VSclMin', 'read'); + } + + public toJson(): ParameterInterface { + return { + ...super.toJson(), + max: this.communication.VSclMax.value, + min: this.communication.VSclMin.value + }; + } + }; +} diff --git a/src/model/dataAssembly/mixins/Unit.ts b/src/model/dataAssembly/mixins/Unit.ts new file mode 100644 index 00000000..b92af40a --- /dev/null +++ b/src/model/dataAssembly/mixins/Unit.ts @@ -0,0 +1,59 @@ +/* + * MIT License + * + * Copyright (c) 2019 Markus Graube , + * Chair for Process Control Systems, Technische Universität Dresden + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +import {ParameterInterface} from '@p2olab/polaris-interface'; +import {UNIT} from '../../core/Unit'; +import {BaseDataAssemblyRuntime, DataAssembly} from '../DataAssembly'; +import {OpcUaDataItem} from '../DataItem'; +import {Constructor} from './mixins'; + +export interface UnitDataAssemblyRuntime extends BaseDataAssemblyRuntime { + VUnit: OpcUaDataItem; +} + +// tslint:disable-next-line:variable-name +export function UnitDA>(Base: TBase) { + + return class extends Base { + public communication: UnitDataAssemblyRuntime; + + constructor(...args: any[]) { + super(...args); + this.createDataItem(args[0], 'VUnit', 'read'); + } + + public getUnit(): string { + const unit = UNIT.find((item) => item.value === this.communication.VUnit.value); + return unit ? unit.unit : ''; + } + + public toJson(): ParameterInterface { + return { + ...super.toJson(), + unit: this.getUnit() + }; + } + }; +} diff --git a/src/model/dataAssembly/mixins/ValueLimitation.ts b/src/model/dataAssembly/mixins/ValueLimitation.ts new file mode 100644 index 00000000..a4661253 --- /dev/null +++ b/src/model/dataAssembly/mixins/ValueLimitation.ts @@ -0,0 +1,56 @@ +/* + * MIT License + * + * Copyright (c) 2019 Markus Graube , + * Chair for Process Control Systems, Technische Universität Dresden + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +import {ParameterInterface} from '@p2olab/polaris-interface'; +import {BaseDataAssemblyRuntime, DataAssembly} from '../DataAssembly'; +import {OpcUaDataItem} from '../DataItem'; +import {Constructor} from './mixins'; + +export type ValueLimitationRuntime = BaseDataAssemblyRuntime & { + VMin: OpcUaDataItem; + VMax: OpcUaDataItem; +}; + +// tslint:disable-next-line:variable-name +export function ValueLimitationDA>(Base: TBase) { + + return class extends Base { + public communication: ValueLimitationRuntime; + + constructor(...args: any[]) { + super(...args); + this.createDataItem(args[0], 'VMax', 'read'); + this.createDataItem(args[0], 'VMin', 'read'); + } + + public toJson(): ParameterInterface { + return { + ...super.toJson(), + max: this.communication.VMax.value, + min: this.communication.VMin.value + }; + } + }; +} diff --git a/src/model/dataAssembly/mixins/mixins.ts b/src/model/dataAssembly/mixins/mixins.ts new file mode 100644 index 00000000..25d5c1a7 --- /dev/null +++ b/src/model/dataAssembly/mixins/mixins.ts @@ -0,0 +1,26 @@ +/* + * MIT License + * + * Copyright (c) 2019 Markus Graube , + * Chair for Process Control Systems, Technische Universität Dresden + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +export type Constructor = new (...args: any[]) => T; diff --git a/src/model/recipe/Condition.ts b/src/model/recipe/Condition.ts deleted file mode 100644 index 40d98fc2..00000000 --- a/src/model/recipe/Condition.ts +++ /dev/null @@ -1,471 +0,0 @@ -/* - * MIT License - * - * Copyright (c) 2019 Markus Graube , - * Chair for Process Control Systems, Technische Universität Dresden - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -import { - AndConditionOptions, - ConditionOptions, - ConditionType, - ExpressionConditionOptions, - NotConditionOptions, - OrConditionOptions, - ScopeOptions, - StateConditionOptions, - TimeConditionOptions, - VariableConditionOptions -} from '@p2olab/polaris-interface'; -import {EventEmitter} from 'events'; -import {Expression} from 'expr-eval'; -import StrictEventEmitter from 'strict-event-emitter-types'; -import {catCondition} from '../../config/logging'; -import {ServiceState} from '../core/enum'; -import {Module} from '../core/Module'; -import {Service} from '../core/Service'; -import {ScopeItem} from './ScopeItem'; -import Timeout = NodeJS.Timeout; - -/** - * Events emitted by [[Condition]] - */ -interface ConditionEvents { - /** - * Notify when the condition changes its state. Parameter is a boolean representing if condition is fulfilled. - * @event - */ - stateChanged: boolean; -} - -type ConditionEmitter = StrictEventEmitter; - -export abstract class Condition extends (EventEmitter as new() => ConditionEmitter) { - - get fulfilled(): boolean { - return this._fulfilled; - } - - /** - * Create Condition - * @param {ConditionOptions} options options for creating Condition - * @param {Module[]} modules modules to be used for evaluating module name in expressions - * @returns Condition - */ - public static create(options: ConditionOptions, modules: Module[]): Condition { - catCondition.trace(`Create Condition: ${JSON.stringify(options)}`); - const type: ConditionType = options.type; - if (type === ConditionType.time) { - return new TimeCondition(options as TimeConditionOptions); - } else if (type === ConditionType.and) { - return new AndCondition(options as AndConditionOptions, modules); - } else if (type === ConditionType.state) { - return new StateCondition(options as StateConditionOptions, modules); - } else if (type === ConditionType.variable) { - return new VariableCondition(options as VariableConditionOptions, modules); - } else if (type === ConditionType.or) { - return new OrCondition(options as OrConditionOptions, modules); - } else if (type === ConditionType.not) { - return new NotCondition(options as NotConditionOptions, modules); - } else if (type === ConditionType.expression) { - return new ExpressionCondition(options as ExpressionConditionOptions, modules); - } else { - throw new Error(`No Condition found for ${options}`); - } - } - - protected _fulfilled: boolean = false; - private options: ConditionOptions; - - constructor(options: ConditionOptions) { - super(); - this.options = options; - } - - /** - * Listen to any change in condition and inform via 'stateChanged' event - */ - public abstract listen(): Condition; - - /** - * Clear listening on condition - */ - public clear() { - this._fulfilled = undefined; - this.removeAllListeners('stateChanged'); - } - - public abstract getUsedModules(): Set; - - public json(): ConditionOptions { - return this.options; - } -} - -export class ExpressionCondition extends Condition { - - private expression: Expression; - private scopeArray: ScopeItem[]; - private listenersExpression: EventEmitter[] = []; - - /** - * - * @param {ExpressionConditionOptions} options - * @param {Module[]} modules - */ - constructor(options: ExpressionConditionOptions, modules: Module[] = []) { - super(options); - catCondition.info(`Add ExpressionCondition: ${options.expression} (${JSON.stringify(modules.map((m) => m.id))})`); - // evaluate scopeArray - this.scopeArray = (options.scope || []).map((item: ScopeOptions) => ScopeItem.extractFromScopeOptions(item, modules)); - - // evaluate additional variables from expression - const extraction = ScopeItem.extractFromExpressionString(options.expression, modules, this.scopeArray.map((scope) => scope.name)); - this.expression = extraction.expression; - this.scopeArray.push (...extraction.scopeItems); - this._fulfilled = false; - } - - public getUsedModules(): Set { - return new Set([...this.scopeArray.map((sa) => sa.module)]); - } - - public listen(): Condition { - this.scopeArray.forEach(async (item) => { - const a = item.module.listenToOpcUaNode(item.variable); - a.on('changed', this.boundOnChanged); - this.listenersExpression.push(a); - }); - return this; - } - - public async onChanged() { - this._fulfilled = (await this.getValue()) as boolean; - this.emit('stateChanged', this._fulfilled); - } - - /** - * calculate value from current scopeArray - * @returns {Promise} - */ - public async getValue(): Promise { - // get current variables - const tasks = await Promise.all(this.scopeArray.map(async (item) => { - return item.module.readVariableNode(item.variable) - .then((value) => { - return item.name.split('.').reduceRight((previous, current) => { - const a = {}; - a[current] = previous; - return a; - }, value.value.value ); - }); - })); - const assign = require('assign-deep'); - const scope = assign(...tasks); - catCondition.info(`Scope: ${JSON.stringify(scope)}`); - return this.expression.evaluate(scope); - } - - public clear() { - super.clear(); - this.listenersExpression.forEach((item) => { - item.removeListener('changed', this.boundOnChanged); - }); - } - - private boundOnChanged = () => this.onChanged(); - -} - -export class NotCondition extends Condition { - public condition: Condition; - - constructor(options: NotConditionOptions, modules: Module[]) { - super(options); - catCondition.trace(`Add NotCondition: ${options}`); - this.condition = Condition.create(options.condition, modules); - this._fulfilled = !this.condition.fulfilled; - } - - public clear() { - super.clear(); - this.condition.clear(); - } - - public listen(): Condition { - this.condition.listen().on('stateChanged', (state) => { - this._fulfilled = !state; - this.emit('stateChanged', this._fulfilled); - }); - return this; - } - - public getUsedModules(): Set { - return this.condition.getUsedModules(); - } -} - -export abstract class AggregateCondition extends Condition { - public conditions: Condition[] = []; - - constructor(options: AndConditionOptions | OrConditionOptions, modules: Module[]) { - super(options); - this.conditions = options.conditions.map((option) => { - return Condition.create(option, modules); - }); - this._fulfilled = false; - } - - public clear() { - super.clear(); - this.conditions.forEach((cond) => cond.clear()); - } - - public getUsedModules(): Set { - const set = new Set(); - this.conditions.forEach((cond) => { - Array.from(cond.getUsedModules()).forEach((module) => { - set.add(module); - }); - }); - return set; - } -} - -export class AndCondition extends AggregateCondition { - - constructor(options: AndConditionOptions, modules: Module[]) { - super(options, modules); - catCondition.trace(`Add AndCondition: ${options}`); - } - - public listen(): Condition { - this.conditions.forEach((condition) => { - condition.listen().on('stateChanged', (state) => { - catCondition.debug(`AndCondition: ${state} = ${JSON.stringify(this.conditions.map((item) => item.fulfilled))}`); - const oldState = this._fulfilled; - this._fulfilled = this.conditions.every((condition) => condition.fulfilled); - if (oldState !== this._fulfilled) { - this.emit('stateChanged', this._fulfilled); - } - }); - }); - return this; - } -} - -export class OrCondition extends AggregateCondition { - - constructor(options: OrConditionOptions, modules: Module[]) { - super(options, modules); - catCondition.trace(`Add OrCondition: ${options}`); - } - - public listen(): Condition { - this.conditions.forEach((condition) => { - condition.listen().on('stateChanged', (status) => { - const oldState = this._fulfilled; - this._fulfilled = this.conditions.some((condition) => condition.fulfilled); - if (oldState !== this._fulfilled) { - this.emit('stateChanged', this._fulfilled); - } - }); - }); - return this; - } -} - -export class TimeCondition extends Condition { - - private timer: Timeout; - private duration: number; - - constructor(options: TimeConditionOptions) { - super(options); - if (options.duration <= 0) { - throw new Error('Duration is negative'); - } - this.duration = options.duration * 1000; - this._fulfilled = false; - catCondition.trace(`Add TimeCondition: ${JSON.stringify(options)}`); - } - - public listen(): Condition { - catCondition.debug(`Start Timer: ${this.duration}`); - this.timer = global.setTimeout(() => { - catCondition.debug(`TimeCondition finished: ${this.duration}`); - this._fulfilled = true; - this.emit('stateChanged', this._fulfilled); - }, - this.duration); - return this; - } - - public clear(): void { - super.clear(); - if (this.timer) { - global.clearTimeout(this.timer); - } - } - - public getUsedModules(): Set { - return new Set(); - } -} - -export abstract class ModuleCondition extends Condition { - protected readonly module: Module; - protected monitoredItem: EventEmitter; - - constructor(options: StateConditionOptions | VariableConditionOptions, modules: Module[]) { - super(options); - if (options.module) { - this.module = modules.find((module) => module.id === options.module); - } else if (modules.length === 1) { - this.module = modules[0]; - } - if (!this.module) { - throw new Error(`Could not find module ${options.module} in ${JSON.stringify(modules.map((m) => m.id))}`); - } - } - - public clear() { - super.clear(); - if (this.monitoredItem) { - this.monitoredItem.removeListener('changed', this.boundCheckHandler); - } - } - - public getUsedModules() { - return new Set().add(this.module); - } - - public abstract check(data); - - protected boundCheckHandler = (data) => this.check(data); -} - -export class StateCondition extends ModuleCondition { - public readonly service: Service; - public readonly state: ServiceState; - - constructor(options: StateConditionOptions, modules: Module[]) { - super(options, modules); - if (this.module.services) { - this.service = this.module.services.find((service) => service.name === options.service); - } - if (!this.service) { - throw new Error(`Service "${options.service}" not found in provided module ${this.module.id}`); - } - const mapping = { - 'idle': ServiceState.IDLE, - 'starting': ServiceState.STARTING, - 'execute': ServiceState.EXECUTE, - 'completing': ServiceState.COMPLETING, - 'completed': ServiceState.COMPLETED, - 'resetting': ServiceState.RESETTING, - 'pausing': ServiceState.PAUSING, - 'paused': ServiceState.PAUSED, - 'resuming': ServiceState.RESUMING, - 'holding': ServiceState.HOLDING, - 'held': ServiceState.HELD, - 'unholding': ServiceState.UNHOLDING, - 'stopping': ServiceState.STOPPING, - 'stopped': ServiceState.STOPPED, - 'aborting': ServiceState.ABORTING, - 'aborted': ServiceState.ABORTED - }; - this.state = mapping[options.state.toLowerCase()]; - if (!this.state) { - throw new Error(`State ${options.state} is not a valid state for a condition (${JSON.stringify(options)}`); - } - } - - public listen(): Condition { - this.monitoredItem = this.module.listenToOpcUaNode(this.service.status) - .on('changed', this.boundCheckHandler); - return this; - } - - public check(data) { - const state: ServiceState = data.value; - this._fulfilled = (state === this.state); - catCondition.info(`StateCondition ${this.service.qualifiedName}: actual=${ServiceState[state]}` + - ` ; condition=${ServiceState[this.state]} -> ${this._fulfilled}`); - this.emit('stateChanged', this._fulfilled); - } -} - -export class VariableCondition extends ModuleCondition { - public readonly dataStructure: string; - public readonly variable: string; - public readonly value: string | number; - public readonly operator: '==' | '<' | '>' | '<=' | '>='; - - constructor(options: VariableConditionOptions, modules: Module[]) { - super(options, modules); - if (!options.dataAssembly) { - throw new Error(`Condition does not have 'dataAssembly' ${JSON.stringify(options)}`); - } - this.dataStructure = options.dataAssembly; - this.variable = options.variable || 'V'; - this.value = options.value; - this.operator = options.operator || '=='; - } - - public listen(): Condition { - catCondition.debug(`Listen to ${this.dataStructure}.${this.variable}`); - this.monitoredItem = this.module.listenToVariable(this.dataStructure, this.variable) - .on('changed', this.boundCheckHandler); - return this; - } - - public check(data) { - catCondition.debug(`value changed to ${data.value} - (${this.operator}) compare against ${this.value}`); - const value: number = data.value; - let result = false; - if (this.operator === '==') { - if (value === this.value) { - result = true; - } - } else if (this.operator === '<=') { - if (value <= this.value) { - result = true; - } - } else if (this.operator === '>=') { - if (value >= this.value) { - result = true; - } - } else if (this.operator === '<') { - if (value < this.value) { - result = true; - } - } else if (this.operator === '>') { - if (value > this.value) { - result = true; - } - } - this._fulfilled = result; - this.emit('stateChanged', this._fulfilled); - catCondition.debug(`VariableCondition ${this.dataStructure}: ${data.value} ${this.operator} ${this.value} = ${this._fulfilled}`); - } - -} diff --git a/src/model/recipe/Operation.ts b/src/model/recipe/Operation.ts index 48b7351e..52d9aa09 100644 --- a/src/model/recipe/Operation.ts +++ b/src/model/recipe/Operation.ts @@ -24,6 +24,7 @@ */ import {OperationInterface, OperationOptions, ServiceCommand} from '@p2olab/polaris-interface'; +import {EventEmitter} from 'events'; import * as delay from 'timeout-as-promise'; import {catOperation} from '../../config/logging'; import {Module} from '../core/Module'; @@ -31,7 +32,7 @@ import {Service} from '../core/Service'; import {Strategy} from '../core/Strategy'; import {Parameter} from './Parameter'; -/** Operation used in a [[Step]] of a [[Recipe]] +/** Operation used in a [[Step]] of a [[Recipe]] or [[PetrinetState]] * */ export class Operation { @@ -40,13 +41,16 @@ export class Operation { public strategy: Strategy; public command: ServiceCommand; public parameters: Parameter[]; + public readonly emitter: EventEmitter; + private state: 'executing' | 'completed' | 'aborted'; constructor(options: OperationOptions, modules: Module[]) { if (modules) { if (options.module) { this.module = modules.find((module) => module.id === options.module); if (!this.module) { - throw new Error(`Could not find module ${options.module} in ${JSON.stringify(modules.map((m) => m.id))}`); + throw new Error(`Could not find module ${options.module} ` + + `in ${JSON.stringify(modules.map((m) => m.id))}`); } } else if (modules.length === 1) { this.module = modules[0]; @@ -60,12 +64,13 @@ export class Operation { this.service = this.module.services.find((service) => service.name === options.service); if (this.service === undefined) { - throw new Error(`Service ${ options.service }(${ this.module.id }) not found in modules`); + throw new Error(`Service ${ this.module.id }.${ options.service } not found in modules ` + + `(${JSON.stringify(modules.map((m) => m.id))})`); } if (options.strategy) { this.strategy = this.service.strategies.find((strategy) => strategy.name === options.strategy); } else { - this.strategy = this.service.strategies.find((strategy) => strategy.default === true); + this.strategy = this.service.defaultStrategy; } if (!this.strategy) { throw new Error(`Strategy '${options.strategy}' could not be found in ${ this.service.name }.`); @@ -78,6 +83,7 @@ export class Operation { (paramOptions) => new Parameter(paramOptions, this.service, this.strategy, modules) ); } + this.emitter = new EventEmitter(); } /** @@ -87,29 +93,49 @@ export class Operation { * @returns {Promise} */ public async execute(): Promise { - let operationExecuted: boolean = false; - while (!operationExecuted) { + let numberOfTries = 0; + const MAX_TRIES = 10; + this.state = 'executing'; + while (this.state === 'executing') { catOperation.info(`Perform operation ${ this.module.id }.${ this.service.name }.${ this.command }() ` + `(Strategy: ${ this.strategy ? this.strategy.name : '' })`); await this.service.execute(this.command, this.strategy, this.parameters) .then(() => { - operationExecuted = true; + this.state = 'completed'; + this.emitter.emit('changed', 'completed'); + this.emitter.removeAllListeners('changed'); }) .catch(async () => { - catOperation.warn('Could not execute operation. Another try in 500ms'); - await delay(500); + numberOfTries++; + if (numberOfTries === MAX_TRIES) { + this.state = 'aborted'; + this.emitter.emit('changed', 'aborted'); + this.emitter.removeAllListeners('changed'); + catOperation.warn('Could not execute operation. Stop restarting'); + } else { + catOperation.warn('Could not execute operation. Another try in 500ms'); + await delay(500); + } }); } + catOperation.info(`Operation ${ this.module.id }.${ this.service.name }.${ this.command }() executed`); } + public stop() { + if (this.state === 'executing') { + this.state = 'aborted'; + } + } + public json(): OperationInterface { return { module: this.module.id, service: this.service.name, strategy: this.strategy ? this.strategy.name : undefined, command: this.command, - parameter: this.parameters + parameter: this.parameters ? this.parameters.map((param) => param.options) : undefined, + state: this.state }; } } diff --git a/src/model/recipe/Parameter.ts b/src/model/recipe/Parameter.ts index 32c8c4ed..8d00be65 100644 --- a/src/model/recipe/Parameter.ts +++ b/src/model/recipe/Parameter.ts @@ -27,19 +27,22 @@ import {ParameterOptions, ScopeOptions} from '@p2olab/polaris-interface'; import * as assign from 'assign-deep'; import {EventEmitter} from 'events'; import {Expression} from 'expr-eval'; -import {DataType} from 'node-opcua-client'; import StrictEventEmitter from 'strict-event-emitter-types'; import {Category} from 'typescript-logging'; import {catParameter} from '../../config/logging'; -import {Module, OpcUaNodeEvents} from '../core/Module'; +import {Module} from '../core/Module'; import {Service} from '../core/Service'; import {Strategy} from '../core/Strategy'; +import {ExtIntAnaOp} from '../dataAssembly/AnaOp'; +import {ExtIntBinOp} from '../dataAssembly/BinOp'; import {DataAssembly} from '../dataAssembly/DataAssembly'; +import {DataItemEvents} from '../dataAssembly/DataItem'; +import {ExtIntDigOp} from '../dataAssembly/DigOp'; import {ScopeItem} from './ScopeItem'; /** - * Parameter of a [[TestServerService]]. Can be static or dynamic. Dynamic Parameters can depend on variables - * of the same or other modules. These can also be continuously updated (specified via continuous property) + * Parameter of an [[Operation]]. Can be static or dynamic. Dynamic Parameters can depend on variables of the same or + * other modules. These can also be continuously updated (specified via continuous property) */ export class Parameter { @@ -64,6 +67,7 @@ export class Parameter { * should parameter continuously be updated */ public continuous: boolean; + public readonly options: ParameterOptions; private expression: Expression; private service: Service; private _parameter: DataAssembly; @@ -73,12 +77,13 @@ export class Parameter { * * @param {ParameterOptions} parameterOptions * @param {Service} service service where the parameter belongs to - * @param {Strategy} strategy strategy to use + * @param {Strategy} strategy strategyNode to use * @param {Module[]} modules modules where expression can be matched */ constructor(parameterOptions: ParameterOptions, service: Service, strategy?: Strategy, modules?: Module[]) { catParameter.trace(`Create Parameter: ${JSON.stringify(parameterOptions)}`); + this.options = parameterOptions; this.name = parameterOptions.name; this.variable = parameterOptions.variable || service.automaticMode ? 'VExt' : 'VMan'; this.value = parameterOptions.value || 0; @@ -87,7 +92,7 @@ export class Parameter { this.logger = catParameter; this.service = service; - const strategyUsed: Strategy = strategy || service.strategies.find((strategy) => strategy.default); + const strategyUsed: Strategy = strategy || service.defaultStrategy; const parameterList: DataAssembly[] = [].concat(service.parameters, strategyUsed.parameters); try { this._parameter = parameterList.find((obj) => (obj && obj.name === this.name)); @@ -103,32 +108,32 @@ export class Parameter { .map((item: ScopeOptions) => ScopeItem.extractFromScopeOptions(item, modules)); // evaluate additional variables from expression - const extraction = ScopeItem.extractFromExpressionString( - this.value.toString(), modules, this.scopeArray.map((scope) => scope.name) - ); - this.expression = extraction.expression; - this.scopeArray.push (...extraction.scopeItems); + try { + const extraction = ScopeItem.extractFromExpressionString( + this.value.toString(), modules, this.scopeArray.map((scope) => scope.name) + ); + this.expression = extraction.expression; + this.scopeArray.push(...extraction.scopeItems); + } catch (err) { + throw new Error('Parsing error for Parameter'); + } } - public listenToParameter() { - const eventEmitter: StrictEventEmitter = new EventEmitter(); - this.scopeArray.forEach(async (item) => { - item.module.listenToOpcUaNode(item.variable) - .on('changed', (data) => eventEmitter.emit('changed', data)); + public listenToParameter(): EventEmitter { + const eventEmitter: StrictEventEmitter = new EventEmitter(); + this.scopeArray.forEach((item) => { + item.dataAssembly.on(item.variableName, (data) => eventEmitter.emit('changed', data)); }); return eventEmitter; } /** * calculate value from current scopeArray - * @returns {Promise} + * @returns number | boolean */ - public async getValue(): Promise { - if (!this.expression) { - return undefined; - } + public getValue(): number | boolean { // get current variables - const tasks = await Promise.all(this.scopeArray.map((item) => item.getScopeValue())); + const tasks = this.scopeArray.map((item) => item.getScopeValue()); const scope = assign(...tasks); const result = this.expression.evaluate(scope); catParameter.info(`Specific parameters: ${this.name} = ${this.value} (${JSON.stringify(scope)}) = ${result}`); @@ -151,14 +156,20 @@ export class Parameter { * @returns {Promise} */ public async setOperationMode(): Promise { - if (this.service.automaticMode) { - this.logger.info(`[${this.service.qualifiedName}.${this.name}] Bring to automatic mode`); - this._parameter.setToAutomaticOperationMode(); - this.logger.info(`[${this.service.qualifiedName}.${this.name}] Parameter now in automatic mode`); - } else { - this.logger.info(`[${this.service.qualifiedName}.${this.name}] Bring to manual mode`); - await this._parameter.setToManualOperationMode(); - this.logger.info(`[${this.service.qualifiedName}.${this.name}] Parameter now in manual mode`); + if ( + this._parameter instanceof ExtIntAnaOp || + this._parameter instanceof ExtIntBinOp || + this._parameter instanceof ExtIntDigOp + ) { + if (this.service.automaticMode) { + this.logger.info(`[${this.service.qualifiedName}.${this.name}] Bring to automatic mode`); + this._parameter.setToAutomaticOperationMode(); + this.logger.info(`[${this.service.qualifiedName}.${this.name}] Parameter now in automatic mode`); + } else { + this.logger.info(`[${this.service.qualifiedName}.${this.name}] Bring to manual mode`); + await this._parameter.setToManualOperationMode(); + this.logger.info(`[${this.service.qualifiedName}.${this.name}] Parameter now in manual mode`); + } } } diff --git a/src/model/recipe/Player.ts b/src/model/recipe/Player.ts index 73591b2c..ce97fc5f 100644 --- a/src/model/recipe/Player.ts +++ b/src/model/recipe/Player.ts @@ -29,7 +29,6 @@ import StrictEventEmitter from 'strict-event-emitter-types'; import {catManager, catPlayer} from '../../config/logging'; import {Recipe} from './Recipe'; import {RecipeRun} from './RecipeRun'; -import {Step} from './Step'; /** * Events emitted by [[Player]] @@ -37,27 +36,22 @@ import {Step} from './Step'; interface PlayerEvents { /** * when player has successfully started - * @event + * @event started */ started: void; /** - * when player has started a recipe - * @event + * when something inside a recipe changes + * @event recipeChanged */ - recipeStarted: Recipe; - /** - * when a step is finished in the player - * @event - */ - stepFinished: Step; + recipeChanged: Recipe; /** * Notify when a recipe is completed - * @event + * @event recipeFinished */ recipeFinished: Recipe; /** * when player completes - * @event + * @event completed */ completed: void; } @@ -140,7 +134,8 @@ export class Player extends (EventEmitter as new() => PlayerEmitter) { */ public json(): PlayerInterface { return { - playlist: this._playlist.map((recipe) => recipe.json()), + playlist: this._playlist.map((recipe) => ({id: recipe.id, name: recipe.name, options: recipe.options})), + currentRecipe: this.getCurrentRecipe() ? this.getCurrentRecipe().json() : undefined, currentItem: this._currentItem, repeat: this.repeat, status: this.status, @@ -149,7 +144,8 @@ export class Player extends (EventEmitter as new() => PlayerEmitter) { id: rr.id, name: rr.recipe.name, startTime: rr.startTime, - endTime: rr.endTime + endTime: rr.endTime, + status: rr.status }; }) }; @@ -253,10 +249,12 @@ export class Player extends (EventEmitter as new() => PlayerEmitter) { this.recipeRuns.push(this.currentRecipeRun); return new Promise((resolve) => { this.currentRecipeRun - .on('stepFinished', (finishedStep) => this.emit('stepFinished', finishedStep)) - .on('started', () => this.emit('recipeStarted', this.currentRecipeRun.recipe)) + .on('changed', () => { + this.emit('recipeChanged', this.currentRecipeRun.recipe); + }) .once('completed', () => { this.emit('recipeFinished', this.currentRecipeRun.recipe); + this.currentRecipeRun.removeAllListeners('changed'); catPlayer.info(`recipe finished ${this.currentItem + 1}/${this._playlist.length} (${this.status})`); resolve(); }); diff --git a/src/model/recipe/Recipe.ts b/src/model/recipe/Recipe.ts index 4ff3fc91..2aa717a5 100644 --- a/src/model/recipe/Recipe.ts +++ b/src/model/recipe/Recipe.ts @@ -23,7 +23,7 @@ * SOFTWARE. */ -import {RecipeInterface, RecipeOptions, RecipeState, StepInterface} from '@p2olab/polaris-interface'; +import {RecipeInterface, RecipeOptions, RecipeState} from '@p2olab/polaris-interface'; import {EventEmitter} from 'events'; import StrictEventEmitter from 'strict-event-emitter-types'; import {v4} from 'uuid'; @@ -38,22 +38,22 @@ import {Transition} from './Transition'; export interface RecipeEvents { /** * when recipe has successfully started - * @event + * @event started */ started: void; /** * when recipe has been stopped, returns recent step - * @event + * @event stopped */ stopped: Step; /** - * when a step is finished in the recipe - * @event + * when something internal changes in a recipe (e.g. a step is finished or operation is executed) + * @event changed */ - stepFinished: Step; + changed: void; /** * when recipe completes - * @event + * @event completed */ completed: void; } @@ -88,7 +88,7 @@ export class Recipe extends (EventEmitter as new() => RecipeEmitter) { if (options.name) { this.name = options.name; } else { - throw new Error('Version property of recipe is missing'); + throw new Error('Name property of recipe is missing'); } if (options.steps) { @@ -98,9 +98,11 @@ export class Recipe extends (EventEmitter as new() => RecipeEmitter) { this.steps.forEach((step: Step) => { this.modules = new Set([...this.modules, ...step.getUsedModules()]); step.transitions.forEach((transition) => { - transition.nextStep = this.steps.find((step) => step.name === transition.nextStepName); + transition.nextStep = this.steps.find((s) => s.name === transition.nextStepName); if (!transition.nextStep && !['completed', 'finished'].find((v) => v === transition.nextStepName)) { - throw new Error(`Recipe load error ${this.name}: Next step "${transition.nextStepName}" not found in "${step.name}" for condition "${JSON.stringify(transition.condition.json())}"`); + throw new Error(`Recipe load error ${this.name}: Next step "${transition.nextStepName}" ` + + `not found in step "${step.name}" ` + + `for condition "${JSON.stringify(transition.condition.json())}"`); } }); }); @@ -111,7 +113,7 @@ export class Recipe extends (EventEmitter as new() => RecipeEmitter) { this.initialStep = this.steps.find((step) => step.name === options.initial_step); } if (!this.initialStep) { - throw new Error(`"initial_step" property '${options.initial_step}' is missing in activeRecipe`); + throw new Error(`"initial_step" property '${options.initial_step}' is not found in provided steps`); } this.options = options; @@ -119,15 +121,7 @@ export class Recipe extends (EventEmitter as new() => RecipeEmitter) { this.status = RecipeState.idle; this.lastChange = new Date(); - catRecipe.info(`Recipe ${this.name} (${this.options.version}) successfully parsed`); - } - - public stepJson(): StepInterface { - let result; - if (this.currentStep) { - result = this.currentStep.json(); - } - return result; + catRecipe.info(`Recipe ${this.name} (version: ${this.options.version}) successfully parsed`); } /** Get JSON description of recipe @@ -139,7 +133,7 @@ export class Recipe extends (EventEmitter as new() => RecipeEmitter) { id: this.id, modules: Array.from(this.modules).map((module) => module.id), status: this.status, - currentStep: this.currentStep ? this.currentStep.name : undefined, + currentStep: this.currentStep ? this.currentStep.json() : undefined, options: this.options, protected: this.protected, lastChange: (new Date().getTime() - this.lastChange.getTime()) / 1000 @@ -162,8 +156,7 @@ export class Recipe extends (EventEmitter as new() => RecipeEmitter) { } /** - * Starts recipe - * Connect to modules and then start the recipe + * Connect to modules and then start the recipe; returns after connection to modules established */ public async start(): Promise { if (this.status === RecipeState.running) { @@ -209,6 +202,7 @@ export class Recipe extends (EventEmitter as new() => RecipeEmitter) { this.stepListener.removeAllListeners('completed'); } if (this.currentStep) { + this.currentStep.operations.forEach((operation) => operation.stop()); this.currentStep.transitions.forEach((trans) => trans.condition.clear()); } this.emit('stopped', this.currentStep); @@ -216,10 +210,14 @@ export class Recipe extends (EventEmitter as new() => RecipeEmitter) { } private executeStep() { - catRecipe.debug(`Execute step: ${this.currentStep.name}`); + catRecipe.info(`Execute step: ${this.currentStep.name}`); this.lastChange = new Date(); - this.stepListener = this.currentStep.execute() + this.stepListener = this.currentStep.eventEmitter + .on('operationChanged', () => { + this.emit('changed'); + }) .once('completed', (transition: Transition) => { + this.stepListener.removeAllListeners('operationChanged'); if (transition.nextStep) { catRecipe.info(`Step ${this.currentStep.name} finished. New step is ${transition.nextStepName}`); this.currentStep = transition.nextStep; @@ -230,8 +228,9 @@ export class Recipe extends (EventEmitter as new() => RecipeEmitter) { this.status = RecipeState.completed; this.emit('completed'); } - this.emit('stepFinished', this.currentStep); + this.emit('changed'); }); + this.currentStep.execute(); } } diff --git a/src/model/recipe/RecipeRun.ts b/src/model/recipe/RecipeRun.ts index d978ea4c..4287a092 100644 --- a/src/model/recipe/RecipeRun.ts +++ b/src/model/recipe/RecipeRun.ts @@ -23,12 +23,11 @@ * SOFTWARE. */ -import {RecipeRunInterface} from '@p2olab/polaris-interface'; +import {RecipeRunInterface, RecipeState} from '@p2olab/polaris-interface'; import {EventEmitter} from 'events'; import {v4} from 'uuid'; import {ServiceLogEntry, VariableLogEntry} from '../../logging/archive'; import {Recipe, RecipeEmitter} from './Recipe'; -import {Step} from './Step'; /** One specific recipe run with all logs * @@ -42,6 +41,10 @@ export class RecipeRun extends (EventEmitter as new() => RecipeEmitter) { return this._endTime; } + get status(): RecipeState { + return this._status; + } + public readonly id: string; public readonly recipe: Recipe; @@ -50,7 +53,14 @@ export class RecipeRun extends (EventEmitter as new() => RecipeEmitter) { private _startTime: Date; private _endTime: Date; - private boundOnCompleted = () => this.onCompleted(); + private _status: RecipeState; + + constructor(recipe: Recipe) { + super(); + this.id = v4(); + this.recipe = recipe; + this._status = RecipeState.idle; + } public json(): RecipeRunInterface { return { @@ -58,20 +68,12 @@ export class RecipeRun extends (EventEmitter as new() => RecipeEmitter) { startTime: this._startTime, endTime: this._endTime, recipe: this.recipe.options, + status: this._status, serviceLog: this.serviceLog, variableLog: this.variableLog }; } - private boundOnStarted = () => this.onStarted(); - private boundOnStepFinished = (finishedStep: Step) => this.onStepFinished(finishedStep); - - constructor(recipe: Recipe) { - super(); - this.id = v4(); - this.recipe = recipe; - } - /** Starts the linked recipe and resolves when recipe is started * */ @@ -79,7 +81,7 @@ export class RecipeRun extends (EventEmitter as new() => RecipeEmitter) { this._startTime = new Date(); this.recipe .on('completed', this.boundOnCompleted) - .on('stepFinished', this.boundOnStepFinished) + .on('changed', this.boundOnChanged) .on('started', this.boundOnStarted); return await this.recipe.start(); } @@ -88,28 +90,37 @@ export class RecipeRun extends (EventEmitter as new() => RecipeEmitter) { * */ public async stop(): Promise { + this._endTime = new Date(); + this._status = RecipeState.stopped; this.removeRecipeListeners(); this.emit('stopped', this.recipe.currentStep); await this.recipe.stop(); } + private boundOnCompleted = () => this.onCompleted(); + private boundOnStarted = () => this.onStarted(); + + private boundOnChanged = () => this.onChanged(); + private removeRecipeListeners() { this.recipe.removeListener('completed', this.boundOnCompleted); - this.recipe.removeListener('stepFinished', this.boundOnStepFinished); + this.recipe.removeListener('changed', this.boundOnChanged); this.recipe.removeListener('started', this.boundOnStarted); } private onCompleted() { this._endTime = new Date(); - this.removeRecipeListeners(); + this._status = RecipeState.completed; this.emit('completed'); + this.removeRecipeListeners(); } private onStarted() { - this.emit('started'); + this._status = RecipeState.running; + this.emit('changed'); } - private onStepFinished(finishedStep) { - this.emit('stepFinished', finishedStep); + private onChanged() { + this.emit('changed'); } } diff --git a/src/model/recipe/ScopeItem.ts b/src/model/recipe/ScopeItem.ts index 9749280e..816bb83c 100644 --- a/src/model/recipe/ScopeItem.ts +++ b/src/model/recipe/ScopeItem.ts @@ -26,20 +26,22 @@ import {ScopeOptions} from '@p2olab/polaris-interface'; import {Expression, Parser} from 'expr-eval'; import {catScopeItem} from '../../config/logging'; -import {OpcUaNodeOptions} from '../core/Interfaces'; +import {ServiceState} from '../core/enum'; import {Module} from '../core/Module'; import {Service} from '../core/Service'; import {Strategy} from '../core/Strategy'; +import {ExtAnaOp} from '../dataAssembly/AnaOp'; +import {AnaView} from '../dataAssembly/AnaView'; +import {ExtBinOp} from '../dataAssembly/BinOp'; +import {BinView} from '../dataAssembly/BinView'; import {DataAssembly} from '../dataAssembly/DataAssembly'; +import {OpcUaDataItem} from '../dataAssembly/DataItem'; +import {ExtDigOp} from '../dataAssembly/DigOp'; +import {DigView} from '../dataAssembly/DigView'; +import {ServiceControl} from '../dataAssembly/ServiceControl'; export class ScopeItem { - /** name of variable which should be replaced in value */ - public name: string; - public module: Module; - public service: Service; - public variable: OpcUaNodeOptions; - /** * * @param {string} expression @@ -60,16 +62,26 @@ export class ScopeItem { } /** - * Extract scope item from expression variable - * - * * + * @param {ScopeOptions} item + * @param {Module[]} modules modules to be searched in for variable names (default: all modules in manager) + * @returns {ScopeItem} + */ + public static extractFromScopeOptions(item: ScopeOptions, modules: Module[]): ScopeItem { + const module = modules.find((m) => m.id === item.module); + const dataAssembly = module.variables.find((v) => v.name === item.dataAssembly); + return new ScopeItem(item.name, module, dataAssembly, item.variable); + } + + /** + * Extract scope item from expression variable * * @param {string} variable - * @param {Module[]} modules modules to be searched in for variable names (default: all modules in manager) + * @param {Module[]} modules modules to be searched in for variable names * @returns {ScopeItem} */ public static extractFromExpressionVariable(variable: string, modules: Module[]): ScopeItem { + let dataAssembly: DataAssembly; const components = variable.split('.').map((tokenT: string) => tokenT.replace(new RegExp('__', 'g'), '.')); let token = components.shift(); @@ -81,7 +93,7 @@ export class ScopeItem { } else { catScopeItem.warn(`Could not evaluate variable "${variable}": module "${token}" not found in ` + `${JSON.stringify(modules.map((m) => m.id))}`); - return undefined; + return null; } } else { token = components.shift(); @@ -91,61 +103,95 @@ export class ScopeItem { const service: Service = module.services.find((s) => s.name === token); let strategy: Strategy; if (service) { - strategy = service.strategies.find((strat) => strat.id === service.currentStrategy.value); + strategy = service.getCurrentStrategy(); if (!strategy) { - strategy = service.strategies.find((strat) => strat.default); + strategy = service.defaultStrategy; } token = components.shift(); - } - // find data assembly - let dataAssembly: DataAssembly; - if (strategy && strategy.parameters.find((p) => p.name === token)) { - dataAssembly = strategy.parameters.find((p) => p.name === token); - } else if (service && service.parameters.find((p) => p.name === token)) { - dataAssembly = strategy.parameters.find((p) => p.name === token); - } else if (module.variables.find((v) => v.name === token)) { - dataAssembly = module.variables.find((v) => v.name === token); + if (strategy.parameters.find((p) => p.name === token)) { + dataAssembly = strategy.parameters.find((p) => p.name === token); + } else if (service.parameters.find((p) => p.name === token)) { + dataAssembly = strategy.parameters.find((p) => p.name === token); + } else if (token === 'state') { + return new ScopeItem(variable, module, service.serviceControl, 'State'); + } else { + catScopeItem.warn(`Could not evaluate variable "${variable}": ` + + `Token "${token}" not found as service parameter ` + + `in service ${service.qualifiedName}.${strategy.name}`); + return null; + } } else { - catScopeItem.warn(`Could not evaluate variable "${variable}": Token "${token}" not found as dataAssembly ` + - `in module ${module.id}: ${module.variables.map((v) => v.name)}`); - return undefined; + // find data assembly in process values + if (module.variables.find((v) => v.name === token)) { + dataAssembly = module.variables.find((v) => v.name === token); + } else { + catScopeItem.warn(`Could not evaluate variable "${variable}": ` + + `Token "${token}" not found as dataAssembly ` + + `in module ${module.id}: ${module.variables.map((v) => v.name)}`); + return null; + } } // find data assembly variable token = components.shift(); - const opcUaNode = dataAssembly.communication[token] || - dataAssembly.communication['V'] || - dataAssembly.communication['VExt']; - return Object.assign(new ScopeItem(), {name: variable, module, variable: opcUaNode}); + return new ScopeItem(variable, module, dataAssembly, token); } - /** - * - * @param {ScopeOptions} item - * @param {Module[]} modules modules to be searched in for variable names (default: all modules in manager) - * @returns {ScopeItem} - */ - public static extractFromScopeOptions(item: ScopeOptions, modules: Module[]): ScopeItem { - const module = modules.find((m) => m.id === item.module); - const dataAssembly = module.variables.find((v) => v.name === item.dataAssembly); - const opcUaNode = dataAssembly.communication[item.variable] || - dataAssembly.communication['V'] || - dataAssembly.communication['VExt']; - return Object.assign(new ScopeItem(), {name: item.name, module, variable: opcUaNode}); + /** name of variable which should be replaced in value */ + public readonly name: string; + public readonly dataAssembly: DataAssembly; + public readonly dataItem: OpcUaDataItem; + public readonly module: Module; + public readonly variableName: string; + + constructor(name: string, module, dataAssembly: DataAssembly, variableName?: string) { + this.name = name; + this.module = module; + this.dataAssembly = dataAssembly; + this.variableName = variableName; + this.dataItem = this.getDataItem(variableName); } /** * Returning an nested object following the name construction. The leaf contains the current value * as object suitable for expr-eval.evaluate() */ - public async getScopeValue(): Promise { - const value = await this.module.readVariableNode(this.variable); + public getScopeValue(): object { + let value = this.dataItem.value; + if (this.dataAssembly instanceof ServiceControl && this.variableName === 'State') { + value = ServiceState[this.dataItem.value as ServiceState]; + } + if (value === undefined) { + throw new Error(`Could not evaluate scope item ${this.name} since it seems not connected`); + } return this.name.split('.').reduceRight((previous, current) => { const a = {}; a[current] = previous; return a; - }, value.value.value); + }, value); } + + private getDataItem(token: string): OpcUaDataItem { + let dataItem = this.dataAssembly.communication[token]; + if (!dataItem) { + // set default values + if (this.dataAssembly instanceof AnaView) { + dataItem = this.dataAssembly.communication.V; + } else if (this.dataAssembly instanceof DigView) { + dataItem = this.dataAssembly.communication.V; + } else if (this.dataAssembly instanceof BinView) { + dataItem = this.dataAssembly.communication.V; + } else if (this.dataAssembly instanceof ExtAnaOp) { + dataItem = this.dataAssembly.communication.VOut; + } else if (this.dataAssembly instanceof ExtDigOp) { + dataItem = this.dataAssembly.communication.VOut; + } else if (this.dataAssembly instanceof ExtBinOp) { + dataItem = this.dataAssembly.communication.VOut; + } + } + return dataItem; + } + } diff --git a/src/model/recipe/Step.ts b/src/model/recipe/Step.ts index 9a66b110..a9e6b30d 100644 --- a/src/model/recipe/Step.ts +++ b/src/model/recipe/Step.ts @@ -23,13 +23,13 @@ * SOFTWARE. */ -import {Operation} from './Operation'; -import {Transition, TransitionOptions} from './Transition'; -import {catRecipe} from '../../config/logging'; +import {OperationOptions, StepInterface, TransitionOptions} from '@p2olab/polaris-interface'; import {EventEmitter} from 'events'; -import {Module} from '../core/Module'; -import {OperationOptions, StepInterface} from '@p2olab/polaris-interface'; import StrictEventEmitter from 'strict-event-emitter-types'; +import {catRecipe} from '../../config/logging'; +import {Module} from '../core/Module'; +import {Operation} from './Operation'; +import {Transition} from './Transition'; export interface StepOptions { name: string; @@ -43,19 +43,25 @@ export interface StepOptions { interface StepEvents { /** * when step is completed - * @event + * @event completed */ completed: Transition; + + /** + * when operation inside step has changed (completed or aborted) + * @event operationChanged + */ + operationChanged: Operation; } /** * Executable [[Step]] from [[Recipe]] */ export class Step { - name: string; - operations: Operation[]; - transitions: Transition[]; - private eventEmitter: StrictEventEmitter = new EventEmitter(); + public name: string; + public operations: Operation[]; + public transitions: Transition[]; + public readonly eventEmitter: StrictEventEmitter; constructor(options: StepOptions, modules: Module[]) { if (options.name) { @@ -65,34 +71,41 @@ export class Step { } if (options.operations) { this.operations = options.operations.map( - operationOptions => new Operation(operationOptions, modules) + (operationOptions) => new Operation(operationOptions, modules) ); } else { throw new Error(`"operations" array is missing in ${JSON.stringify(options)}`); } if (options.transitions) { this.transitions = options.transitions.map( - transitionOptions => new Transition(transitionOptions, modules) + (transitionOptions) => new Transition(transitionOptions, modules) ); } else { throw new Error(`"transitions" array is missing in ${JSON.stringify(options)}`); } + this.eventEmitter = new EventEmitter(); } - getUsedModules(): Set { - let set = new Set(this.operations.map(op => op.module)); - this.transitions.forEach(tr => { + public getUsedModules(): Set { + let set = new Set(this.operations.map((op) => op.module)); + this.transitions.forEach((tr) => { set = new Set([...set, ...tr.getUsedModules()]); }); return set; } - execute() { + public execute() { // execute operations for step this.operations.forEach((operation) => { catRecipe.info(`Start operation ${operation.module.id} ${operation.service.name} ` + `${JSON.stringify(operation.command)}`); operation.execute(); + operation.emitter.on('changed', (state) => { + if (state === 'completed' || state === 'aborted') { + operation.emitter.removeAllListeners('changed'); + } + this.eventEmitter.emit('operationChanged', operation); + }); }); // TODO: check if operation all have successfully executed @@ -100,35 +113,35 @@ export class Step { // start listening to transitions of step this.transitions.forEach((transition) => { catRecipe.info(`Start listening for transition ${JSON.stringify(transition.json())}`); - const events = transition.condition.listen() + transition.condition .on('stateChanged', (status) => { - catRecipe.info(`Status of step ${this.name} for transition to ${transition.nextStepName}: ${status}`); + catRecipe.info(`Status of step ${this.name} ` + + `for transition to ${transition.nextStepName}: ${status}`); if (status) { this.enterTransition(transition); } }); + transition.condition.listen(); }); - - return this.eventEmitter; } /** * enter transition (clear conditions and emit 'completed') - * @param {Transition} transition + * @param {Transition} nextTransition */ - public enterTransition(transition: Transition) { + public enterTransition(nextTransition: Transition) { // clear up all conditions this.transitions.forEach((transition) => { transition.condition.clear(); }); - this.eventEmitter.emit('completed', transition); + this.eventEmitter.emit('completed', nextTransition); } public json(): StepInterface { return { name: this.name, - transitions: this.transitions.map(transition => transition.json()), - operations: this.operations.map(operation => operation.json()) + transitions: this.transitions.map((transition) => transition.json()), + operations: this.operations.map((operation) => operation.json()) }; } } diff --git a/src/model/recipe/Transition.ts b/src/model/recipe/Transition.ts index 40e5043b..c5d5b845 100644 --- a/src/model/recipe/Transition.ts +++ b/src/model/recipe/Transition.ts @@ -23,16 +23,12 @@ * SOFTWARE. */ -import {ConditionOptions, TransitionInterface} from '@p2olab/polaris-interface'; +import {TransitionInterface, TransitionOptions} from '@p2olab/polaris-interface'; +import {Condition} from '../condition/Condition'; +import {ConditionFactory} from '../condition/ConditionFactory'; import {Module} from '../core/Module'; -import {Condition} from './Condition'; import {Step} from './Step'; -export interface TransitionOptions { - next_step: string; - condition: ConditionOptions; -} - export class Transition { public nextStep: Step; public readonly nextStepName: string; @@ -45,7 +41,7 @@ export class Transition { throw new Error(`"next_step" property is missing in ${JSON.stringify(options)}`); } if (options.condition) { - this.condition = Condition.create(options.condition, modules); + this.condition = ConditionFactory.create(options.condition, modules); } else { throw new Error(`"condition" property is missing in ${JSON.stringify(options)}`); } diff --git a/src/model/virtualService/AggregatedService.ts b/src/model/virtualService/AggregatedService.ts new file mode 100644 index 00000000..3bc682ff --- /dev/null +++ b/src/model/virtualService/AggregatedService.ts @@ -0,0 +1,176 @@ +/* + * MIT License + * + * Copyright (c) 2019 Markus Graube , + * Chair for Process Control Systems, Technische Universität Dresden + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +import {ParameterInterface} from '@p2olab/polaris-interface'; +import {ServiceState} from '../core/enum'; +import {Module} from '../core/Module'; +import {Petrinet, PetrinetOptions} from './aggregatedService/Petrinet'; +import {VirtualService} from './VirtualService'; +import {VirtualServiceOptions} from './VirtualServiceFactory'; + +export interface AggregatedServiceOptions extends VirtualServiceOptions { + type: 'aggregatedService'; + description: string; + version: string; + parameters: ParameterInterface[]; + stateMachine: StateMachineOptions; + commandEnable: CommandEnableOptions; +} + +// CommandEnableOptions defines the conditions as expressions for the commands to be triggered externally +export interface CommandEnableOptions { + // only in IDLE + start: string; + // only in EXECUTE; if omitted, use same as *start* condition + restart: string; + // only in STARTING, EXECUTE, COMPLETING, COMPLETED, PAUSED, PAUSING, RESUMING, HOLDING, HELD, UNHOLDING + stop: string; + // only in EXECUTE + pause: string; + // only in PAUSED + resume: string; + // only in EXECUTE + complete: string; + // only in HELD + unhold: string; + // defines the condition when virtual service automatically goes into HOLDING (without any user interaction) + hold: string; + // for the following commands there should not exist further condition except the current state + // abort (all the time except in ABORTED) + // reset (in ABORTED, STOPPED and COMPLETED) + + // all state-change transitions are automatically triggered when the underlying petri net + // of the appropriate step is finished + +} + +/** + * default is empty Petrinet which just jumps to the next state in the state machine + */ +export interface StateMachineOptions { + starting: PetrinetOptions; + execute: PetrinetOptions; + pausing: PetrinetOptions; + resuming: PetrinetOptions; + completing: PetrinetOptions; + aborting: PetrinetOptions; + stopping: PetrinetOptions; + holding: PetrinetOptions; + unholding: PetrinetOptions; + // following states should not perform any actions which can be defined by the user + // idle + // paused + // completed + // resetting +} + +/** Virtual Service which can be started. + */ +export class AggregatedService extends VirtualService { + + public static type: string = 'aggregatedService'; + + // necessary modules + public modules: Set = new Set(); + + // dynamic properties + public currentState: ServiceState; + public _lastStatusChange: Date; + private commandEnableExpression: CommandEnableOptions; + private options: AggregatedServiceOptions; + + private starting: Petrinet; + private execute: Petrinet; + private completing: Petrinet; + private stopping: Petrinet; + private pausing: Petrinet; + private resuming: Petrinet; + private aborting: Petrinet; + private holding: Petrinet; + private unholding: Petrinet; + + constructor(options: AggregatedServiceOptions, modules: Module[]) { + super(options.name); + this.options = options; + this._lastStatusChange = new Date(); + + if (options.commandEnable) { + this.commandEnableExpression = options.commandEnable; + } + + this.starting = new Petrinet(options.stateMachine.starting, modules); + this.execute = new Petrinet(options.stateMachine.execute, modules); + this.completing = new Petrinet(options.stateMachine.completing, modules); + this.stopping = new Petrinet(options.stateMachine.stopping, modules); + this.pausing = new Petrinet(options.stateMachine.pausing, modules); + this.resuming = new Petrinet(options.stateMachine.resuming, modules); + this.aborting = new Petrinet(options.stateMachine.aborting, modules); + this.holding = new Petrinet(options.stateMachine.holding, modules); + this.unholding = new Petrinet(options.stateMachine.unholding, modules); + + this.initParameter(); + } + + protected initParameter() { + this.parameters = this.options.parameters; + this.selfCompleting = true; + } + + protected async onStarting(): Promise { + await this.starting.run(); + } + + protected async onExecute(): Promise { + await this.execute.run(); + } + + protected async onPausing(): Promise { + await this.pausing.run(); + } + + protected async onCompleting(): Promise { + await this.pausing.run(); + } + + protected async onResuming(): Promise { + await this.resuming.run(); + } + + protected async onAborting(): Promise { + await this.aborting.run(); + } + + protected async onStopping(): Promise { + await this.stopping.run(); + } + + protected async onUnholding(): Promise { + await this.unholding.run(); + } + + protected async onHolding(): Promise { + await this.holding.run(); + } +} diff --git a/src/model/virtualService/FunctionGenerator.ts b/src/model/virtualService/FunctionGenerator.ts new file mode 100644 index 00000000..bbf7980d --- /dev/null +++ b/src/model/virtualService/FunctionGenerator.ts @@ -0,0 +1,99 @@ +/* + * MIT License + * + * Copyright (c) 2019 Markus Graube , + * Chair for Process Control Systems, Technische Universität Dresden + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +import Timeout = NodeJS.Timeout; +import {Expression, Parser} from 'expr-eval'; +import {VirtualService} from './VirtualService'; + +/** + * Function Generator + * + * Parameters: + * - function: expr-eval Expression used to generate output (Variable *t* can be used inside function to link to + * elapsed time since start of function in seconds + * - updateRate: update rate of evaluating function + * - output: output value of function + */ +export class FunctionGenerator extends VirtualService { + + public static type: string = 'functionGenerator'; + + public startTime: Date; + private timerUpdateId: Timeout; + private _output: number; + private expression: Expression; + + set output(value: number) { + this._output = value; + this.parameters.find((p) => p.name === 'output').value = this._output; + this.eventEmitter.emit('parameterChanged', {value: this._output, parameter: 'output', unit: null}); + } + + constructor(name: string) { + super(name); + this.initParameter(); + } + + public async onStarting(): Promise { + this.startTime = new Date(); + this.expression = new Parser().parse(this.parameters.find((p) => p.name === 'function').value.toString()); + + const updateRate = this.parameters.find((p) => p.name === 'updateRate').value as number; + this.timerUpdateId = global.setInterval(() => { + const elapsedTime = (new Date().getTime() - this.startTime.getTime()) / 1000; + const value = this.expression.evaluate({t: elapsedTime }); + this.output = value; + }, updateRate); + } + + public async onPausing() { + this.timerUpdateId.unref(); + } + + public async onResuming() { + const updateRate = this.parameters.find((p) => p.name === 'updateRate').value as number; + this.timerUpdateId = global.setInterval(() => { + const elapsedTime = (new Date().getTime() - this.startTime.getTime()) / 1000; + this.output = this.expression.evaluate({t: elapsedTime }); + }, updateRate); + } + + public async onCompleting() { + this.onStopping(); + } + public async onAborting() { + this.onStopping(); + } + public async onStopping() { + this.timerUpdateId.unref(); + } + + protected initParameter() { + this.parameters = [ + {name: 'function', value: 'sin(t)'}, + {name: 'updateRate', value: 1000, unit: 'ms', min: 1}, + {name: 'output', value: undefined, readonly: true}]; + } +} diff --git a/src/model/virtualService/PidController.ts b/src/model/virtualService/PidController.ts new file mode 100644 index 00000000..c0066177 --- /dev/null +++ b/src/model/virtualService/PidController.ts @@ -0,0 +1,78 @@ +/* + * MIT License + * + * Copyright (c) 2019 Markus Graube , + * Chair for Process Control Systems, Technische Universität Dresden + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +import {ParameterOptions} from '@p2olab/polaris-interface'; +import * as Controller from 'node-pid-controller'; +import {ServiceState} from '../core/enum'; +import {Parameter} from '../recipe/Parameter'; +import {VirtualService} from './VirtualService'; + +export class PidController extends VirtualService { + set output(value: number) { + this._output = value; + this.parameters.find((p) => p.name === 'output').value = this._output; + } + + public static type: string = 'pidController'; + private ctr: Controller; + private _output: number; + + constructor(name: string) { + super(name); + this.initParameter(); + } + + public async setParameters(parameters: Array): Promise { + super.setParameters(parameters); + const setpoint = parameters.find((param) => param.name === 'setpoint'); + if (setpoint) { + this.ctr.setTarget(setpoint.value as number); + } + const input = parameters.find((param) => param.name === 'input'); + if (input && this.state === ServiceState.EXECUTE) { + this._output = this.ctr.update(input.value as number); + } + } + + public async onStarting() { + this.ctr = new Controller({ + k_p: this.parameters.find((param) => param.name === 'p').value as number, + k_i: this.parameters.find((param) => param.name === 'i').value as number, + k_d: this.parameters.find((param) => param.name === 'd').value as number + }); + } + + protected initParameter() { + this.parameters = [ + {name: 'setpoint', value: undefined}, + {name: 'input', value: undefined}, + {name: 'output', value: undefined, readonly: true}, + {name: 'p', value: 0.25}, + {name: 'i', value: 0.01}, + {name: 'd', value: 0.01} + ]; + } + +} diff --git a/src/model/core/Interfaces.ts b/src/model/virtualService/Storage.ts similarity index 70% rename from src/model/core/Interfaces.ts rename to src/model/virtualService/Storage.ts index d865b2c5..91b3eba4 100644 --- a/src/model/core/Interfaces.ts +++ b/src/model/virtualService/Storage.ts @@ -1,7 +1,7 @@ /* * MIT License * - * Copyright (c) 2018 Markus Graube , + * Copyright (c) 2019 Markus Graube , * Chair for Process Control Systems, Technische Universität Dresden * * Permission is hereby granted, free of charge, to any person obtaining a copy @@ -23,15 +23,19 @@ * SOFTWARE. */ -export interface OpcUaNodeOptions { - /* despite its current name this variable contains the *namespace url* of the node*/ - namespace_index: string; - /** node id of the node as string (e.g. 's=sdfdsf' or 'i=12') */ - node_id: string; - /** data type of OPC UA node */ - data_type?: string; - /** recent value */ - value?: number| string| boolean; - /** timestamp of last update of value */ - timestamp?: Date; +import {VirtualService} from './VirtualService'; + +export class Storage extends VirtualService { + + public static type: string = 'storage'; + + constructor(name: string) { + super(name); + this.initParameter(); + } + + protected initParameter() { + this.parameters = [{name: 'storage', value: undefined}]; + } + } diff --git a/src/model/virtualService/Timer.ts b/src/model/virtualService/Timer.ts new file mode 100644 index 00000000..d34f4fa1 --- /dev/null +++ b/src/model/virtualService/Timer.ts @@ -0,0 +1,112 @@ +/* + * MIT License + * + * Copyright (c) 2019 Markus Graube , + * Chair for Process Control Systems, Technische Universität Dresden + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +import Timeout = NodeJS.Timeout; +import {catTimer} from '../../config/logging'; +import {VirtualService} from './VirtualService'; + +export class Timer extends VirtualService { + + public static type: string = 'timer'; + + private durationMs: number; + private timestampStart: Date; + private _remainingTime: number; + private elapsedTime: number; + private timerId: Timeout; + private timerUpdateId: Timeout; + + private get remainingTime(): number { + return this._remainingTime; + } + private set remainingTime(value: number) { + this._remainingTime = value; + this.processValuesOut.find((p) => p.name === 'remainingTime').value = this._remainingTime; + this.eventEmitter.emit('variableChanged', {parameter: 'remainingTime', value: this._remainingTime, unit: 'ms'}); + } + + constructor(options) { + super(options); + this.initParameter(); + } + + protected initParameter() { + this.parameters = [ + {name: 'duration', value: 10000, min: 1, unit: 'ms'}, + {name: 'updateRate', value: 1000, min: 100, unit: 'ms'} + ]; + this.processValuesOut = [ + {name: 'remainingTime', value: 10000, unit: 'ms', readonly: true}, + ]; + this.selfCompleting = true; + } + + protected async onStarting(): Promise { + this.durationMs = this.parameters.find((p) => p.name === 'duration').value as number; + this.timestampStart = new Date(); + this.elapsedTime = 0; + this.remainingTime = this.durationMs; + + await catTimer.info(`timer on starting: ${this.remainingTime}`); + } + + protected async onExecute() { + this.timerId = global.setTimeout(() => { + super.complete(); + this.timerUpdateId.unref(); + }, this.remainingTime); + + const updateRate = this.parameters.find((p) => p.name === 'updateRate').value as number; + this.timerUpdateId = global.setInterval(() => { + this.remainingTime = this.remainingTime - updateRate; + }, updateRate); + + } + + protected async onPausing(): Promise { + this.elapsedTime = this.elapsedTime + new Date().getTime() - this.timestampStart.getTime(); + this.remainingTime = this.durationMs - this.elapsedTime; + global.clearTimeout(this.timerId); + global.clearInterval(this.timerUpdateId); + await catTimer.info(`timer on pausing (${this.remainingTime})`); + } + + protected async onResuming(): Promise { + this.timestampStart = new Date(); + await catTimer.info(`timer on resuming (${this.remainingTime})`); + } + + protected async onCompleting() { + this.onStopping(); + } + protected async onAborting() { + this.onStopping(); + } + protected async onStopping() { + global.clearTimeout(this.timerId); + global.clearInterval(this.timerUpdateId); + } + +} diff --git a/src/model/virtualService/VirtualService.ts b/src/model/virtualService/VirtualService.ts new file mode 100644 index 00000000..ccd46a09 --- /dev/null +++ b/src/model/virtualService/VirtualService.ts @@ -0,0 +1,452 @@ +/* + * MIT License + * + * Copyright (c) 2019 Markus Graube , + * Chair for Process Control Systems, Technische Universität Dresden + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +import {ControlEnableInterface, ParameterOptions, VirtualServiceInterface} from '@p2olab/polaris-interface'; +import {catVirtualService} from '../../config/logging'; +import {BaseService} from '../core/BaseService'; +import {ServiceState} from '../core/enum'; +import {Parameter} from '../recipe/Parameter'; + +/** + * A generic function block following the service state machine + */ +export abstract class VirtualService extends BaseService { + + public get controlEnable(): ControlEnableInterface { + return this._controlEnable; + } + + public get state(): ServiceState { + return this._state; + } + + public static type: string; + + protected _controlEnable: ControlEnableInterface; + protected _state: ServiceState = ServiceState.IDLE; + + constructor(name: string) { + super(); + this._name = name; + this._controlEnable = { + start: true, + abort: true, + complete: false, + pause: false, + reset: false, + restart: false, + resume: false, + stop: true, + unhold: false + }; + } + + // Public methods + + public json(): VirtualServiceInterface { + return { + name: this.name, + type: this.constructor.name, + parameters: this.parameters, + processValuesIn: this.processValuesIn, + processValuesOut: this.processValuesOut, + reportParameters: this.reportValues, + status: ServiceState[this.state], + controlEnable: this.controlEnable, + lastChange: (new Date().getTime() - this.lastStatusChange.getTime()) / 1000, + sc: this.selfCompleting + }; + } + + public async setParameters(parameters: Array): Promise { + catVirtualService.info(`Set parameter: ${JSON.stringify(parameters)}`); + parameters.forEach((pNew) => { + const pOld = this.parameters.find((param) => param.name === pNew.name); + if (!pOld) { + throw new Error('try to write not existent variable'); + } + if (pOld.readonly) { + throw new Error('try to write to readonly variable'); + } + Object.assign(pOld, pNew); + }); + } + + public async start() { + if (this._controlEnable.start) { + await this.gotoStarting(); + } + } + public async restart() { + if (this._controlEnable.restart) { + await this.gotoStarting(); + } + } + public async pause() { + if (this._controlEnable.pause) { + await this.gotoPausing(); + } + } + public async resume() { + if (this._controlEnable.resume) { + await this.gotoResuming(); + } + } + public async complete() { + if (this._controlEnable.complete) { + await this.gotoCompleting(); + } else { + catVirtualService.warn(`Can not complete, ${JSON.stringify(this._controlEnable)}`); + } + } + public async stop() { + if (this._controlEnable.stop) { + await this.gotoStopping(); + } + } + public async abort() { + if (this._controlEnable.abort) { + await this.gotoAborting(); + } + } + public async reset() { + if (this._controlEnable.reset) { + await this.gotoResetting(); + } + } + public async unhold() { + if (this._controlEnable.unhold) { + await this.gotoUnholding(); + } + } + + // Allow user to inject own functionality after reaching each state + + /** + * initialize parameters during construction and when resetting + */ + protected abstract initParameter(); + protected async onStarting(): Promise { + catVirtualService.debug(`[${this.name}] onStarting`); + } + protected async onExecute(): Promise { + catVirtualService.debug(`[${this.name}] onExecute`); + } + protected async onPausing(): Promise { + catVirtualService.debug(`[${this.name}] onPausing`); + } + protected async onPaused(): Promise { + catVirtualService.debug(`[${this.name}] onPaused`); + } + protected async onResuming(): Promise { + catVirtualService.debug(`[${this.name}] onResuming`); + } + protected async onCompleting(): Promise { + catVirtualService.debug(`[${this.name}] onCompleting`); + } + protected async onCompleted(): Promise { + catVirtualService.debug(`[${this.name}] onCompleted`); + } + protected async onResetting(): Promise { + catVirtualService.debug(`[${this.name}] onResetting`); + } + protected async onAborting(): Promise { + catVirtualService.debug(`[${this.name}] onAborting`); + } + protected async onAborted(): Promise { + catVirtualService.debug(`[${this.name}] onAborted`); + } + protected async onStopping(): Promise { + catVirtualService.debug(`[${this.name}] onStopping`); + } + protected async onStopped(): Promise { + catVirtualService.debug(`[${this.name}] onStopped`); + } + protected async onIdle(): Promise { + catVirtualService.debug(`[${this.name}] onIdle`); + } + protected async onUnholding(): Promise { + catVirtualService.debug(`[${this.name}] onUnholding`); + } + + // Internal + private setState(newState: ServiceState) { + this.eventEmitter.emit('state', newState); + this._state = newState; + } + + private setControlEnable(controlEnable: ControlEnableInterface) { + this.eventEmitter.emit('controlEnable', controlEnable); + this._controlEnable = controlEnable; + } + + private async gotoStarting(): Promise { + this.setState(ServiceState.STARTING); + this.setControlEnable({ + start: false, + abort: true, + complete: false, + pause: false, + reset: false, + restart: false, + resume: false, + stop: true, + unhold: false + }); + catVirtualService.info('starting'); + await this.onStarting(); + this.gotoExecute(); + } + + private async gotoExecute(): Promise { + this.setState(ServiceState.EXECUTE); + this.setControlEnable({ + start: false, + abort: true, + complete: this.selfCompleting || true, + pause: true, + reset: false, + restart: true, + resume: false, + stop: true, + unhold: false + }); + catVirtualService.info('running'); + await this.onExecute(); + } + + private async gotoPausing(): Promise { + this.setState(ServiceState.PAUSING); + this.setControlEnable({ + start: false, + abort: true, + complete: false, + pause: false, + reset: false, + restart: false, + resume: false, + stop: true, + unhold: false + }); + catVirtualService.info('pausing'); + await this.onPausing(); + this.gotoPaused(); + } + + private async gotoPaused(): Promise { + this.setState(ServiceState.PAUSED); + this.setControlEnable({ + start: false, + abort: true, + complete: false, + pause: false, + reset: false, + restart: false, + resume: true, + stop: true, + unhold: false + }); + catVirtualService.info('paused'); + await this.onPaused(); + } + + private async gotoResuming(): Promise { + this.setState(ServiceState.RESUMING); + this.setControlEnable({ + start: false, + abort: true, + complete: false, + pause: false, + reset: false, + restart: false, + resume: false, + stop: true, + unhold: false + }); + catVirtualService.info('resuming'); + await this.onResuming(); + this.gotoExecute(); + } + + private async gotoCompleting(): Promise { + this.setState(ServiceState.COMPLETING); + this.setControlEnable({ + start: false, + abort: true, + complete: false, + pause: false, + reset: false, + restart: false, + resume: false, + stop: true, + unhold: false + }); + catVirtualService.info('completing'); + await this.onCompleting(); + this.gotoCompleted(); + } + + private async gotoCompleted(): Promise { + this.setState(ServiceState.COMPLETED); + this.setControlEnable({ + start: false, + abort: true, + complete: false, + pause: false, + reset: true, + restart: false, + resume: false, + stop: true, + unhold: false + }); + catVirtualService.info('completed'); + await this.onCompleted(); + } + + private async gotoStopping(): Promise { + this.setState(ServiceState.STOPPING); + this.setControlEnable({ + start: false, + abort: true, + complete: false, + pause: false, + reset: false, + restart: false, + resume: false, + stop: false, + unhold: false + }); + catVirtualService.info('stopping'); + await this.onStopping(); + this.gotoStopped(); + } + + private async gotoStopped(): Promise { + this.setState(ServiceState.STOPPED); + this.setControlEnable({ + start: false, + abort: true, + complete: false, + pause: false, + reset: true, + restart: false, + resume: false, + stop: false, + unhold: false + }); + catVirtualService.info('stopped'); + await this.onStopped(); + } + + private async gotoAborting(): Promise { + this.setState(ServiceState.ABORTING); + this.setControlEnable({ + start: false, + abort: false, + complete: false, + pause: false, + reset: false, + restart: false, + resume: false, + stop: false, + unhold: false + }); + catVirtualService.info('completed'); + await this.onAborting(); + this.gotoAborted(); + } + + private async gotoAborted(): Promise { + this.setState(ServiceState.COMPLETED); + this.setControlEnable({ + start: false, + abort: true, + complete: false, + pause: false, + reset: true, + restart: false, + resume: false, + stop: true, + unhold: false + }); + catVirtualService.info('completed'); + await this.onAborted(); + this.gotoIdle(); + } + + private async gotoResetting(): Promise { + this.setControlEnable({ + start: false, + abort: true, + complete: false, + pause: false, + reset: false, + restart: false, + resume: false, + stop: true, + unhold: false + }); + this.setState(ServiceState.RESETTING); + catVirtualService.info('resetting'); + await this.onResetting(); + this.initParameter(); + this.gotoIdle(); + } + + private async gotoIdle(): Promise { + this.setControlEnable({ + start: true, + abort: true, + complete: false, + pause: false, + reset: false, + restart: false, + resume: false, + stop: true, + unhold: false + }); + this.setState(ServiceState.IDLE); + catVirtualService.info('idle'); + await this.onIdle(); + } + + private async gotoUnholding(): Promise { + this.setControlEnable({ + start: false, + abort: true, + complete: false, + pause: false, + reset: false, + restart: false, + resume: false, + stop: true, + unhold: false + }); + this.setState(ServiceState.UNHOLDING); + catVirtualService.info('unholding'); + await this.onUnholding(); + this.gotoExecute(); + } +} diff --git a/src/model/virtualService/VirtualServiceFactory.ts b/src/model/virtualService/VirtualServiceFactory.ts new file mode 100644 index 00000000..a25f27bc --- /dev/null +++ b/src/model/virtualService/VirtualServiceFactory.ts @@ -0,0 +1,57 @@ +/* + * MIT License + * + * Copyright (c) 2019 Markus Graube , + * Chair for Process Control Systems, Technische Universität Dresden + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +import {Module} from '../core/Module'; +import { + AggregatedService, AggregatedServiceOptions +} from './AggregatedService'; +import {FunctionGenerator} from './FunctionGenerator'; +import {PidController} from './PidController'; +import {Storage} from './Storage'; +import {Timer} from './Timer'; +import {VirtualService} from './VirtualService'; + +export interface VirtualServiceOptions { + name: string; + type: string; +} + +export class VirtualServiceFactory { + public static create(options: VirtualServiceOptions, modules?: Module[]): VirtualService { + if (options.type === Timer.type) { + return new Timer(options.name); + } else if (options.type === Storage.type) { + return new Storage(options.name); + } else if (options.type === FunctionGenerator.type) { + return new FunctionGenerator(options.name); + } else if (options.type === PidController.type) { + return new PidController(options.name); + } else if (options.type === AggregatedService.type) { + return new AggregatedService(options as AggregatedServiceOptions, modules); + } else { + throw new Error(`Unknown virtual service type ${options.type}`); + } + } +} diff --git a/src/model/virtualService/aggregatedService/Petrinet.ts b/src/model/virtualService/aggregatedService/Petrinet.ts new file mode 100644 index 00000000..26bd0d3c --- /dev/null +++ b/src/model/virtualService/aggregatedService/Petrinet.ts @@ -0,0 +1,142 @@ +/* + * MIT License + * + * Copyright (c) 2019 Markus Graube , + * Chair for Process Control Systems, Technische Universität Dresden + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +import {EventEmitter} from 'events'; +import {catRecipe} from '../../../config/logging'; +import {Module} from '../../core/Module'; +import {PetrinetState, PetrinetStateOptions} from './PetrinetState'; +import {PetrinetTransition, PetrinetTransitionOptions} from './PetrinetTransition'; + +export interface PetrinetOptions { + states: PetrinetStateOptions[]; + transitions: PetrinetTransitionOptions[]; + // name of the id of the initial transition + initialTransition: string; +} + +export class Petrinet { + + public readonly eventEmitter: EventEmitter; + + public readonly options: PetrinetOptions; + public readonly states: PetrinetState[]; + public readonly transitions: PetrinetTransition[]; + public readonly initialTransition: PetrinetTransition; + public readonly activeStates: PetrinetState[]; + + constructor(options: PetrinetOptions, modules: Module[]) { + this.options = options; + this.activeStates = []; + this.eventEmitter = new EventEmitter(); + + if (options) { + this.states = options.states.map((opt) => new PetrinetState(opt, modules)); + this.transitions = options.transitions.map((opt) => new PetrinetTransition(opt, modules)); + + // Resolve transitions and state strings to appropriate objects + this.initialTransition = + this.transitions.find((tr: PetrinetTransition) => tr.id === options.initialTransition); + this.transitions.forEach((tr: PetrinetTransition) => { + tr.nextStates = + this.states.filter((state) => tr.options.nextStates.includes(state.id)); + }); + this.states.forEach((state: PetrinetState) => { + state.nextTransitions = + this.transitions.filter((transition) => state.options.nextTransitions.includes(transition.id)); + }); + this.transitions.forEach((tr: PetrinetTransition) => { + tr.priorStates = + this.states.filter((state) => state.nextTransitions.find((tr1) => tr1.id === tr.id)); + }); + } + } + + public async run() { + if (this.initialTransition) { + this.listenToTransition(this.initialTransition); + await new Promise((resolve) => this.eventEmitter.once('completed', () => resolve())); + } + } + + private listenToTransition(transition: PetrinetTransition) { + this.eventEmitter.emit('transition', transition); + transition.condition + .on('stateChanged', (status) => { + if (status) { + this.useTransition(transition); + } + }); + transition.condition.listen(); + } + + /** + * activate state + * + * after executing the state the listening of successor transitions will be started as long + * as all of the previous states of this transition are active + * @param {PetrinetState} state + */ + private activateState(state: PetrinetState) { + this.activeStates.push(state); + this.eventEmitter.emit('state', state); + state.execute() + .then(() => { + state.nextTransitions.forEach((tr) => { + if (tr.priorStates.every((priorState) => + this.activeStates.includes(priorState) && priorState.operationCompleted)) { + this.listenToTransition(tr); + } + }); + }) + .catch(() => { + catRecipe.warn(`Petrinet has some aborted operations`); + }); + } + + /** + * enter transition (clear conditions and emit 'completed') + * @param {PetrinetTransition} currentTransition + */ + private useTransition(currentTransition: PetrinetTransition) { + // stop listening + currentTransition.condition.clear(); + + // remove marks from all previous states + currentTransition.priorStates.forEach((state) => { + const indexState = this.activeStates.indexOf(state); + this.activeStates.splice(indexState, 1); + }); + + // put marks on all next states or complete petrinet + if (currentTransition.nextStates.length > 0) { + currentTransition.nextStates.forEach((state) => this.activateState(state)); + } else { + // clear all transition conditions + this.transitions.forEach((transition) => transition.condition.clear()); + this.eventEmitter.emit('completed'); + } + } + +} diff --git a/src/model/virtualService/aggregatedService/PetrinetState.ts b/src/model/virtualService/aggregatedService/PetrinetState.ts new file mode 100644 index 00000000..cc605eb2 --- /dev/null +++ b/src/model/virtualService/aggregatedService/PetrinetState.ts @@ -0,0 +1,76 @@ +/* + * MIT License + * + * Copyright (c) 2019 Markus Graube , + * Chair for Process Control Systems, Technische Universität Dresden + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +import {OperationOptions} from '@p2olab/polaris-interface'; +import {catRecipe} from '../../../config/logging'; +import {Module} from '../../core/Module'; +import {Operation} from '../../recipe/Operation'; +import {PetrinetTransition} from './PetrinetTransition'; + +export interface PetrinetStateOptions { + id: string; + operations: OperationOptions[]; + nextTransitions: string[]; +} + +export class PetrinetState { + + public readonly id: string; + public readonly options: PetrinetStateOptions; + public nextTransitions: PetrinetTransition[]; + public operationCompleted: boolean; + private operations: Operation[]; + + constructor(options, modules: Module[]) { + this.id = options.id; + this.options = options; + this.operations = options.operations.map((op) => new Operation(op, modules)); + this.operationCompleted = false; + } + + /** + * Execute all operations for PetrinetState + * + * Resolves when all operations are completed; rejects if one operation is aborted + * @returns {Promise} + */ + public async execute() { + const tasks = this.operations.map((operation) => new Promise((resolve, reject) => { + catRecipe.info(`Start operation ${operation.module.id} ${operation.service.name} ` + + `${JSON.stringify(operation.command)}`); + operation.execute(); + operation.emitter.on('changed', (state) => { + if (state === 'completed') { + resolve(); + } else if (state === 'aborted') { + reject(); + } + }); + }) + ); + await Promise.all(tasks); + this.operationCompleted = true; + } +} diff --git a/src/model/virtualService/aggregatedService/PetrinetTransition.ts b/src/model/virtualService/aggregatedService/PetrinetTransition.ts new file mode 100644 index 00000000..cda34169 --- /dev/null +++ b/src/model/virtualService/aggregatedService/PetrinetTransition.ts @@ -0,0 +1,59 @@ +/* + * MIT License + * + * Copyright (c) 2019 Markus Graube , + * Chair for Process Control Systems, Technische Universität Dresden + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +import {ConditionOptions} from '@p2olab/polaris-interface'; +import {Condition} from '../../condition/Condition'; +import {ConditionFactory} from '../../condition/ConditionFactory'; +import {Module} from '../../core/Module'; +import {PetrinetState} from './PetrinetState'; + +export interface PetrinetTransitionOptions { + id: string; + condition?: ConditionOptions; + // name of the succeeding states or 'finished' or 'hold' + nextStates: string[]; +} + +export class PetrinetTransition { + + public readonly id: string; + public readonly options: PetrinetTransitionOptions; + public nextStates: PetrinetState[]; + public priorStates: PetrinetState[]; + public readonly condition: Condition; + + constructor(options: PetrinetTransitionOptions, modules: Module[]) { + this.options = options; + this.id = options.id; + this.condition = ConditionFactory.create(options.condition, modules); + } + + public json() { + return { + nextStates: this.options.nextStates, + condition: this.condition.json() + }; + } +} diff --git a/src/moduleTestServer/ModuleTestNumericVariable.ts b/src/moduleTestServer/ModuleTestNumericVariable.ts new file mode 100644 index 00000000..e49b3566 --- /dev/null +++ b/src/moduleTestServer/ModuleTestNumericVariable.ts @@ -0,0 +1,130 @@ +/* + * MIT License + * + * Copyright (c) 2019 Markus Graube , + * Chair for Process Control Systems, Technische Universität Dresden + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +import {DataType, StatusCodes, Variant} from 'node-opcua'; +import {TestServerVariable} from './ModuleTestVariable'; +import Timeout = NodeJS.Timeout; + +export class TestServerNumericVariable extends TestServerVariable { + + public v: number = 20; + public vext: number = 20; + public sclMin: number = Math.random() * 100; + public sclMax: number = this.sclMin + Math.random() * 100; + public unit: number = Math.floor((Math.random() * 100) + 1000); + protected interval: Timeout; + + constructor(namespace, rootNode, variableName: string, simulation = false) { + super(namespace, rootNode, variableName, simulation); + + namespace.addVariable({ + componentOf: this.variableNode, + nodeId: `ns=1;s=${variableName}.V`, + browseName: `${variableName}.V`, + dataType: 'Double', + value: { + get: () => { + return new Variant({dataType: DataType.Double, value: this.v}); + } + } + }); + + namespace.addVariable({ + componentOf: this.variableNode, + nodeId: `ns=1;s=${variableName}.VUnit`, + browseName: `${variableName}.VUnit`, + dataType: 'Double', + value: { + get: () => { + return new Variant({dataType: DataType.Double, value: this.unit}); + } + } + }); + + namespace.addVariable({ + componentOf: this.variableNode, + nodeId: `ns=1;s=${variableName}.VSclMin`, + browseName: `${variableName}.VSclMin`, + dataType: 'Double', + value: { + get: () => { + return new Variant({dataType: DataType.Double, value: this.sclMin}); + } + } + }); + + namespace.addVariable({ + componentOf: this.variableNode, + nodeId: `ns=1;s=${variableName}.VSclMax`, + browseName: `${variableName}.VSclMax`, + dataType: 'Double', + value: { + get: () => { + return new Variant({dataType: DataType.Double, value: this.sclMax}); + } + } + }); + if (!this.simulation) { + namespace.addVariable({ + componentOf: this.variableNode, + nodeId: `ns=1;s=${variableName}.VExt`, + browseName: `${variableName}.VExt`, + dataType: 'Double', + value: { + get: () => { + return new Variant({dataType: DataType.Double, value: this.vext}); + }, + set: (variant) => { + this.vext = parseInt(variant.value, 10); + setTimeout(() => { + this.v = this.vext; + }, 500); + return StatusCodes.Good; + } + + } + }); + } + + } + + public startSimulation() { + if (this.simulation) { + let time = 0; + const f1 = Math.random(); + const f2 = Math.random(); + const amplitude = this.sclMax - this.sclMin; + const average = (this.sclMax + this.sclMin) / 2; + this.interval = global.setInterval(() => { + time = time + 0.05; + this.v = average + 0.5 * amplitude * Math.sin(2 * f1 * time + 3 * f2); + }, 100); + } + } + + public stopSimulation() { + global.clearInterval(this.interval); + } +} diff --git a/src/moduleTestServer/ModuleTestServer.ts b/src/moduleTestServer/ModuleTestServer.ts index cc244313..89843c54 100644 --- a/src/moduleTestServer/ModuleTestServer.ts +++ b/src/moduleTestServer/ModuleTestServer.ts @@ -24,373 +24,16 @@ */ import * as net from 'net'; -import {DataType, Variant} from 'node-opcua'; +import {AddressSpace, DataType, Namespace, Variant} from 'node-opcua'; import {OPCUAServer} from 'node-opcua-server'; import {catTestServer} from '../config/logging'; -import {OpMode, ServiceControlEnable, ServiceMtpCommand, ServiceState} from '../model/core/enum'; -import Timeout = NodeJS.Timeout; +import {TestServerNumericVariable} from './ModuleTestNumericVariable'; +import {TestServerService} from './ModuleTestService'; +import {TestServerVariable} from './ModuleTestVariable'; -export class TestServerVariable { - - public v: number = 20; - public vext: number = 20; - public sclMin: number = Math.random() * 100; - public sclMax: number = this.sclMin + Math.random() * 100; - public unit: number = Math.floor((Math.random() * 100) + 1000); - public opMode: number = 0; - private interval: Timeout; - private simulation: boolean; - - constructor(namespace, rootNode, variableName, simulation = false, opMode = true) { - catTestServer.info(`Add variable ${variableName}`); - - this.simulation = simulation; - const variableNode = namespace.addObject({ - organizedBy: rootNode, - browseName: variableName - }); - - namespace.addVariable({ - componentOf: variableNode, - nodeId: `ns=1;s=${variableName}.V`, - browseName: `${variableName}.V`, - dataType: 'Double', - value: { - get: () => { - return new Variant({dataType: DataType.Double, value: this.v}); - } - } - }); - - namespace.addVariable({ - componentOf: variableNode, - nodeId: `ns=1;s=${variableName}.VUnit`, - browseName: `${variableName}.VUnit`, - dataType: 'Double', - value: { - get: () => { - return new Variant({dataType: DataType.Double, value: this.unit}); - } - } - }); - - namespace.addVariable({ - componentOf: variableNode, - nodeId: `ns=1;s=${variableName}.VSclMin`, - browseName: `${variableName}.VSclMin`, - dataType: 'Double', - value: { - get: () => { - return new Variant({dataType: DataType.Double, value: this.sclMin}); - } - } - }); - - namespace.addVariable({ - componentOf: variableNode, - nodeId: `ns=1;s=${variableName}.VSclMax`, - browseName: `${variableName}.VSclMax`, - dataType: 'Double', - value: { - get: () => { - return new Variant({dataType: DataType.Double, value: this.sclMax}); - } - } - }); - if (!this.simulation) { - namespace.addVariable({ - componentOf: variableNode, - nodeId: `ns=1;s=${variableName}.VExt`, - browseName: `${variableName}.VExt`, - dataType: 'Double', - value: { - get: () => { - return new Variant({dataType: DataType.Double, value: this.vext}); - }, - set: (variant) => { - this.vext = parseInt(variant.value, 10); - setTimeout(() => { - this.v = this.vext; - }, 500); - } - - } - }); - } - - namespace.addVariable({ - componentOf: variableNode, - nodeId: `ns=1;s=${variableName}.OpMode`, - browseName: `${variableName}.OpMode`, - dataType: 'UInt32', - value: { - get: () => { - catTestServer.trace(`[${variableName}] Get Opmode in testserver ${this.opMode}`); - return new Variant({dataType: DataType.UInt32, value: this.opMode}); - }, - set: (variant) => { - if (parseInt(variant.value, 10) === OpMode.stateManOp) { - this.opMode = this.opMode & ~OpMode.stateAutAct; - this.opMode = this.opMode | OpMode.stateManAct; - } - if (parseInt(variant.value, 10) === OpMode.stateAutOp) { - this.opMode = this.opMode & ~OpMode.stateManAct; - this.opMode = this.opMode | OpMode.stateAutAct; - } - if (parseInt(variant.value, 10) === OpMode.srcExtOp) { - this.opMode = this.opMode | OpMode.srcExtAct; - } - catTestServer.trace(`[${variableName}] Set Opmode in testserver ${variant} ` + - `${parseInt(variant.value, 10)} -> ${this.opMode}`); - } - } - }); - } - - public startSimulation() { - if (this.simulation) { - let time = 0; - const f1 = Math.random(); - const f2 = Math.random(); - const amplitude = this.sclMax - this.sclMin; - const average = (this.sclMax + this.sclMin) / 2; - this.interval = global.setInterval(() => { - time = time + 0.05; - this.v = average + 0.5 * amplitude * Math.sin(2 * f1 * time + 3 * f2); - }, 100); - } - } - - public stopSimulation() { - clearTimeout(this.interval); - - } -} - -export class TestServerStringVariable { - - public v: string = 'initial value'; - public vext: string = ''; - private interval: Timeout; - - constructor(namespace, rootNode, variableName) { - const variableNode = namespace.addObject({ - organizedBy: rootNode, - browseName: variableName - }); - - namespace.addVariable({ - componentOf: variableNode, - nodeId: `ns=1;s=${variableName}.Text`, - browseName: `${variableName}.Text`, - dataType: 'String', - value: { - get: () => { - return new Variant({dataType: DataType.String, value: this.v}); - }, - set: (variant) => { - this.v = variant.value; - } - } - }); - - } - - public startSimulation() { - this.interval = global.setInterval(() => { - this.v = new Date().toTimeString(); - }, 3000); - } - - public stopSimulation() { - clearTimeout(this.interval); - } -} - -export class TestServerService { - public varStatus: number = 0; - public varStrategy: number = 1; - public varCommand: number = 0; - public varCommandEnable: number = 0; - public varOpmode: number = 0; - public serviceName: string; - public readonly parameter: Array = []; - - constructor(namespace, rootNode, serviceName) { - catTestServer.info(`Add service ${serviceName}`); - this.serviceName = serviceName; - this.state(ServiceState.IDLE); - - const serviceNode = namespace.addObject({ - organizedBy: rootNode, - browseName: serviceName - }); - - this.parameter.push(new TestServerVariable(namespace, serviceNode, serviceName + '.Parameter1', false)); - this.parameter.push(new TestServerVariable(namespace, serviceNode, serviceName + '.Parameter2', false)); - - this.parameter.push(new TestServerStringVariable(namespace, serviceNode, serviceName + '.ErrorMsg')); - - namespace.addVariable({ - componentOf: serviceNode, - nodeId: `ns=1;s=${serviceName}.Strategy`, - browseName: `${serviceName}.Strategy`, - dataType: 'UInt32', - value: { - get: () => { - return new Variant({dataType: DataType.UInt32, value: this.varStrategy}); - }, - set: (variant) => { - this.varStrategy = parseInt(variant.value, 10); - } - } - }); - - namespace.addVariable({ - componentOf: serviceNode, - nodeId: `ns=1;s=${serviceName}.CurrentStrategy`, - browseName: `${serviceName}.CurrentStrategy`, - dataType: 'UInt32', - value: { - get: () => { - return new Variant({dataType: DataType.UInt32, value: this.varStrategy}); - } - } - }); - - namespace.addVariable({ - componentOf: serviceNode, - nodeId: `ns=1;s=${serviceName}.CommandEnable`, - browseName: `${serviceName}.CommandEnable`, - dataType: 'UInt32', - value: { - get: () => { - return new Variant({dataType: DataType.UInt32, value: this.varCommandEnable}); - } - } - }); - - namespace.addVariable({ - componentOf: serviceNode, - nodeId: `ns=1;s=${serviceName}.OpMode`, - browseName: `${serviceName}.OpMode`, - dataType: 'UInt32', - value: { - get: () => { - catTestServer.trace(`[${this.serviceName}] Get Opmode in testserver ${this.varOpmode}`); - return new Variant({dataType: DataType.UInt32, value: this.varOpmode}); - }, - set: (variant) => { - if (parseInt(variant.value, 10) === OpMode.stateManOp) { - this.varOpmode = this.varOpmode & ~OpMode.stateAutAct; - this.varOpmode = this.varOpmode | OpMode.stateManAct; - } - if (parseInt(variant.value, 10) === OpMode.stateAutOp) { - this.varOpmode = this.varOpmode & ~OpMode.stateManAct; - this.varOpmode = this.varOpmode | OpMode.stateAutAct; - } - if (parseInt(variant.value, 10) === OpMode.srcExtOp) { - this.varOpmode = this.varOpmode | OpMode.srcExtAct; - } - catTestServer.debug(`[${this.serviceName}] Set Opmode in testserver ${variant} ` + - `${parseInt(variant.value, 10)} -> ${this.varOpmode}`); - } - } - }); - - namespace.addVariable({ - componentOf: serviceNode, - nodeId: `ns=1;s=${serviceName}.State`, - browseName: `${serviceName}.State`, - dataType: 'UInt32', - value: { - get: () => { - return new Variant({dataType: DataType.UInt32, value: this.varStatus}); - } - } - }); - - namespace.addVariable({ - componentOf: serviceNode, - nodeId: `ns=1;s=${serviceName}.Command`, - browseName: `${serviceName}.Command`, - dataType: 'UInt32', - value: { - get: () => { - return new Variant({dataType: DataType.UInt32, value: this.varCommand}); - }, - set: (variant) => { - this.varCommand = parseInt(variant.value, 10); - if (this.varCommand === ServiceMtpCommand.COMPLETE && this.varStatus === ServiceState.EXECUTE) { - this.state(ServiceState.COMPLETING); - } else if (this.varCommand === ServiceMtpCommand.RESTART && - this.varStatus === ServiceState.EXECUTE) { - this.state(ServiceState.STARTING); - } else if (this.varCommand === ServiceMtpCommand.RESET) { - this.state(ServiceState.IDLE); - } else if (this.varCommand === ServiceMtpCommand.START && this.varStatus === ServiceState.IDLE) { - this.state(ServiceState.STARTING); - } else if (this.varCommand === ServiceMtpCommand.RESUME && this.varStatus === ServiceState.PAUSED) { - this.state(ServiceState.RESUMING); - } else if (this.varCommand === ServiceMtpCommand.PAUSE && this.varStatus === ServiceState.EXECUTE) { - this.state(ServiceState.PAUSING); - } else if (this.varCommand === ServiceMtpCommand.STOP) { - this.state(ServiceState.STOPPING); - } else if (this.varCommand === ServiceMtpCommand.ABORT) { - this.state(ServiceState.ABORTING); - } - } - } - }); - } - - public startSimulation() { - this.parameter.forEach((variable) => variable.startSimulation()); - } - - public stopSimulation() { - this.parameter.forEach((variable) => variable.stopSimulation()); - } - - private state(state: ServiceState) { - catTestServer.info(`Set ServiceState: ${ServiceState[state]}`); - this.varStatus = state; - this.varCommand = ServiceMtpCommand.UNDEFINED; - this.varCommandEnable = ServiceControlEnable.ABORT + ServiceControlEnable.STOP; - switch (state) { - case ServiceState.IDLE: - this.varCommandEnable += ServiceControlEnable.START; - break; - case ServiceState.EXECUTE: - this.varCommandEnable += ServiceControlEnable.PAUSE + - ServiceControlEnable.COMPLETE + ServiceControlEnable.RESTART; - break; - case ServiceState.PAUSED: - this.varCommandEnable += ServiceControlEnable.RESUME; - break; - case ServiceState.COMPLETED: - case ServiceState.STOPPED: - case ServiceState.ABORTED: - this.varCommandEnable += ServiceControlEnable.RESET; - break; - } - this.automaticStateChange(ServiceState.STARTING, ServiceState.EXECUTE); - this.automaticStateChange(ServiceState.STOPPING, ServiceState.STOPPED); - this.automaticStateChange(ServiceState.ABORTING, ServiceState.ABORTED); - this.automaticStateChange(ServiceState.PAUSING, ServiceState.PAUSED); - this.automaticStateChange(ServiceState.RESUMING, ServiceState.EXECUTE); - this.automaticStateChange(ServiceState.COMPLETING, ServiceState.COMPLETED); - } - - private automaticStateChange(currentState: ServiceState, nextState: ServiceState, delay = 100) { - if (this.varStatus === currentState) { - setTimeout(() => { - if (this.varStatus === currentState) { - this.state(nextState); - } - }, delay); - } - } +function validUserFunc(username: string, password: string): boolean { + catTestServer.info(`Try to login with ${username}:${password}`); + return username === 'admin' && password === '1234'; } export class ModuleTestServer { @@ -399,23 +42,23 @@ export class ModuleTestServer { public variables: TestServerVariable[] = []; public services: TestServerService[] = []; private server: OPCUAServer; + private port: number; - constructor() { - this.server = new OPCUAServer({ - port: 4334 - }); + constructor(port = 4334) { + this.port = port; + this.server = new OPCUAServer({port: this.port, userManager: {isValidUser: validUserFunc}}); this.externalTrigger = false; } - public async portInUse(port): Promise { + public async portInUse(): Promise { const server = net.createServer((socket) => { socket.write('Echo server\r\n'); socket.pipe(socket); }); return new Promise((resolve) => { - server.listen(port, '127.0.0.1'); + server.listen(this.port, '127.0.0.1'); server.on('error', () => { resolve(true); }); @@ -427,17 +70,18 @@ export class ModuleTestServer { } public async start() { - if (await this.portInUse(4334)) { + if (await this.portInUse()) { throw new Error('Port is in use'); } await new Promise((resolve) => this.server.initialize(resolve)); this.createAddressSpace(); await new Promise((resolve) => this.server.start(resolve)); - catTestServer.info('server started'); + catTestServer.info('server started on port ' + this.port); } public async shutdown() { catTestServer.info('Shutdown test server'); + this.stopSimulation(); await new Promise((resolve) => this.server.shutdown(100, resolve)); } @@ -452,8 +96,8 @@ export class ModuleTestServer { } private createAddressSpace() { - const addressSpace = this.server.engine.addressSpace; - const namespace = addressSpace.getOwnNamespace(); + const addressSpace: AddressSpace = this.server.engine.addressSpace; + const namespace: Namespace = addressSpace.getOwnNamespace(); // declare a new object const myModule = namespace.addObject({ @@ -461,9 +105,9 @@ export class ModuleTestServer { browseName: 'TestModule' }); - this.variables.push(new TestServerVariable(namespace, myModule, 'Variable1', true)); - this.variables.push(new TestServerVariable(namespace, myModule, 'Variable2', true)); - this.variables.push(new TestServerVariable(namespace, myModule, 'TestServerVariable.3', true)); + this.variables.push(new TestServerNumericVariable(namespace, myModule, 'Variable1', true)); + this.variables.push(new TestServerNumericVariable(namespace, myModule, 'Variable2', true)); + this.variables.push(new TestServerNumericVariable(namespace, myModule, 'TestServerVariable.3', true)); this.services.push(new TestServerService(namespace, myModule, 'Service1')); this.services.push(new TestServerService(namespace, myModule, 'Service2')); diff --git a/src/moduleTestServer/ModuleTestService.ts b/src/moduleTestServer/ModuleTestService.ts new file mode 100644 index 00000000..314484d0 --- /dev/null +++ b/src/moduleTestServer/ModuleTestService.ts @@ -0,0 +1,241 @@ +/* + * MIT License + * + * Copyright (c) 2019 Markus Graube , + * Chair for Process Control Systems, Technische Universität Dresden + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +import {DataType, Namespace, StatusCodes, Variant} from 'node-opcua'; +import {catTestServer} from '../config/logging'; +import {OpMode, ServiceControlEnable, ServiceMtpCommand, ServiceState} from '../model/core/enum'; +import {TestServerNumericVariable} from './ModuleTestNumericVariable'; +import {TestServerStringVariable} from './ModuleTestStringVariable'; +import {TestServerVariable} from './ModuleTestVariable'; +import Timeout = NodeJS.Timeout; + +export class TestServerService { + public varStatus: number = 0; + public varStrategy: number = 1; + public varCommand: number = 0; + public varCommandEnable: number = 0; + public varOpmode: number = 0; + public serviceName: string; + public readonly parameter: TestServerVariable[] = []; + private interval: Timeout; + + constructor(namespace: Namespace, rootNode, serviceName: string) { + catTestServer.info(`Add service ${serviceName}`); + this.serviceName = serviceName; + this.state(ServiceState.IDLE); + + const serviceNode = namespace.addObject({ + organizedBy: rootNode, + browseName: serviceName + }); + + this.parameter.push( + new TestServerNumericVariable(namespace, serviceNode, serviceName + '.Parameter1', false), + new TestServerNumericVariable(namespace, serviceNode, serviceName + '.Parameter2', false), + new TestServerNumericVariable(namespace, serviceNode, serviceName + '.ProcessValueIn', false), + new TestServerNumericVariable(namespace, serviceNode, serviceName + '.ProcessValueOut', false), + new TestServerNumericVariable(namespace, serviceNode, serviceName + '.ProcessValueIntegral', false), + new TestServerStringVariable(namespace, serviceNode, serviceName + '.ErrorMsg')); + + namespace.addVariable({ + componentOf: serviceNode, + nodeId: `ns=1;s=${serviceName}.Strategy`, + browseName: `${serviceName}.Strategy`, + dataType: 'UInt32', + value: { + get: () => { + return new Variant({dataType: DataType.UInt32, value: this.varStrategy}); + }, + set: (variant) => { + this.varStrategy = parseInt(variant.value, 10); + return StatusCodes.Good; + } + } + }); + + namespace.addVariable({ + componentOf: serviceNode, + nodeId: `ns=1;s=${serviceName}.CurrentStrategy`, + browseName: `${serviceName}.CurrentStrategy`, + dataType: 'UInt32', + value: { + get: () => { + return new Variant({dataType: DataType.UInt32, value: this.varStrategy}); + } + } + }); + + namespace.addVariable({ + componentOf: serviceNode, + nodeId: `ns=1;s=${serviceName}.CommandEnable`, + browseName: `${serviceName}.CommandEnable`, + dataType: 'UInt32', + value: { + get: () => { + return new Variant({dataType: DataType.UInt32, value: this.varCommandEnable}); + } + } + }); + + namespace.addVariable({ + componentOf: serviceNode, + nodeId: `ns=1;s=${serviceName}.OpMode`, + browseName: `${serviceName}.OpMode`, + dataType: DataType.UInt32, + value: { + get: () => { + catTestServer.trace(`[${this.serviceName}] Get Opmode in testserver ${this.varOpmode}`); + return new Variant({dataType: DataType.UInt32, value: this.varOpmode}); + }, + set: (variant) => { + const opModeInt = parseInt(variant.value, 10); + if (opModeInt === OpMode.stateManOp) { + this.varOpmode = this.varOpmode & ~OpMode.stateAutAct; + this.varOpmode = this.varOpmode | OpMode.stateManAct; + } else if (opModeInt === OpMode.stateAutOp) { + this.varOpmode = this.varOpmode & ~OpMode.stateManAct; + this.varOpmode = this.varOpmode | OpMode.stateAutAct; + } else if (opModeInt === OpMode.srcExtOp) { + this.varOpmode = this.varOpmode | OpMode.srcExtAct; + } else { + return StatusCodes.Bad; + } + catTestServer.debug(`[${this.serviceName}] Set Opmode in testserver ${variant} ` + + `${parseInt(variant.value, 10)} -> ${this.varOpmode}`); + return StatusCodes.Good; + } + } + }); + + namespace.addVariable({ + componentOf: serviceNode, + nodeId: `ns=1;s=${serviceName}.State`, + browseName: `${serviceName}.State`, + dataType: 'UInt32', + value: { + get: () => { + return new Variant({dataType: DataType.UInt32, value: this.varStatus}); + } + } + }); + + namespace.addVariable({ + componentOf: serviceNode, + nodeId: `ns=1;s=${serviceName}.Command`, + browseName: `${serviceName}.Command`, + dataType: 'UInt32', + value: { + get: () => { + catTestServer.debug(`Get service command (${this.serviceName}): ${this.varCommand}`); + return new Variant({dataType: DataType.UInt32, value: this.varCommand}); + }, + set: (variant) => { + this.varCommand = parseInt(variant.value, 10); + catTestServer.info('Set service command: ' + this.varCommand); + if (this.varCommand === ServiceMtpCommand.COMPLETE && this.varStatus === ServiceState.EXECUTE) { + this.state(ServiceState.COMPLETING); + } else if (this.varCommand === ServiceMtpCommand.RESTART && + this.varStatus === ServiceState.EXECUTE) { + this.state(ServiceState.STARTING); + } else if (this.varCommand === ServiceMtpCommand.RESET) { + this.state(ServiceState.IDLE); + } else if (this.varCommand === ServiceMtpCommand.START && this.varStatus === ServiceState.IDLE) { + this.state(ServiceState.STARTING); + } else if (this.varCommand === ServiceMtpCommand.RESUME && this.varStatus === ServiceState.PAUSED) { + this.state(ServiceState.RESUMING); + } else if (this.varCommand === ServiceMtpCommand.PAUSE && this.varStatus === ServiceState.EXECUTE) { + this.state(ServiceState.PAUSING); + } else if (this.varCommand === ServiceMtpCommand.STOP) { + this.state(ServiceState.STOPPING); + } else if (this.varCommand === ServiceMtpCommand.ABORT) { + this.state(ServiceState.ABORTING); + } else { + return StatusCodes.Bad; + } + return StatusCodes.Good; + } + } + }); + } + + public startSimulation() { + this.parameter.forEach((variable) => variable.startSimulation()); + const processValueIn = + this.parameter.find((p) => p.name === this.serviceName + '.ProcessValueIn') as TestServerNumericVariable; + const processValueOut = + this.parameter.find((p) => p.name === this.serviceName + '.ProcessValueOut') as TestServerNumericVariable; + const processValueIntegral = this.parameter.find( + (p) => p.name === this.serviceName + '.ProcessValueIntegral') as TestServerNumericVariable; + this.interval = global.setInterval(() => { + processValueOut.v = 2 * (processValueIn.v as number); + processValueIntegral.v = (processValueIntegral.v as number) + (processValueIn.v as number); + }, 1000); + } + + public stopSimulation() { + this.parameter.forEach((variable) => variable.stopSimulation()); + global.clearInterval(this.interval); + } + + private state(state: ServiceState) { + catTestServer.debug(`Set ServiceState: ${ServiceState[state]}`); + this.varStatus = state; + this.varCommand = ServiceMtpCommand.UNDEFINED; + this.varCommandEnable = ServiceControlEnable.ABORT + ServiceControlEnable.STOP; + switch (state) { + case ServiceState.IDLE: + this.varCommandEnable += ServiceControlEnable.START; + break; + case ServiceState.EXECUTE: + this.varCommandEnable += ServiceControlEnable.PAUSE + + ServiceControlEnable.COMPLETE + ServiceControlEnable.RESTART; + break; + case ServiceState.PAUSED: + this.varCommandEnable += ServiceControlEnable.RESUME; + break; + case ServiceState.COMPLETED: + case ServiceState.STOPPED: + case ServiceState.ABORTED: + this.varCommandEnable += ServiceControlEnable.RESET; + break; + } + this.automaticStateChange(ServiceState.STARTING, ServiceState.EXECUTE); + this.automaticStateChange(ServiceState.STOPPING, ServiceState.STOPPED); + this.automaticStateChange(ServiceState.ABORTING, ServiceState.ABORTED); + this.automaticStateChange(ServiceState.PAUSING, ServiceState.PAUSED); + this.automaticStateChange(ServiceState.RESUMING, ServiceState.EXECUTE); + this.automaticStateChange(ServiceState.COMPLETING, ServiceState.COMPLETED); + } + + private automaticStateChange(currentState: ServiceState, nextState: ServiceState, delay = 100) { + if (this.varStatus === currentState) { + setTimeout(() => { + if (this.varStatus === currentState) { + this.state(nextState); + } + }, delay); + } + } +} diff --git a/src/moduleTestServer/ModuleTestStringVariable.ts b/src/moduleTestServer/ModuleTestStringVariable.ts new file mode 100644 index 00000000..e3e83e7f --- /dev/null +++ b/src/moduleTestServer/ModuleTestStringVariable.ts @@ -0,0 +1,68 @@ +/* + * MIT License + * + * Copyright (c) 2019 Markus Graube , + * Chair for Process Control Systems, Technische Universität Dresden + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +import {DataType, StatusCodes, Variant} from 'node-opcua'; +import Timeout = NodeJS.Timeout; +import {catTestServer} from '../config/logging'; +import {TestServerVariable} from './ModuleTestVariable'; + +export class TestServerStringVariable extends TestServerVariable { + + public v: string = 'initial value'; + public vext: string = ''; + protected interval: Timeout; + + constructor(namespace, rootNode, variableName: string) { + super(namespace, rootNode, variableName); + + namespace.addVariable({ + componentOf: this.variableNode, + nodeId: `ns=1;s=${variableName}.Text`, + browseName: `${variableName}.Text`, + dataType: 'String', + value: { + get: () => { + catTestServer.debug(`[${this.name}] Get string variable in testserver ${this.v}`); + return new Variant({dataType: DataType.String, value: this.v}); + }, + set: (variant) => { + this.v = variant.value; + return StatusCodes.Good; + } + } + }); + + } + + public startSimulation() { + this.interval = global.setInterval(() => { + this.v = new Date().toTimeString(); + }, 1000); + } + + public stopSimulation() { + global.clearTimeout(this.interval); + } +} diff --git a/src/moduleTestServer/ModuleTestVariable.ts b/src/moduleTestServer/ModuleTestVariable.ts new file mode 100644 index 00000000..3fe77eca --- /dev/null +++ b/src/moduleTestServer/ModuleTestVariable.ts @@ -0,0 +1,108 @@ +/* + * MIT License + * + * Copyright (c) 2019 Markus Graube , + * Chair for Process Control Systems, Technische Universität Dresden + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +import {DataType, Namespace, StatusCodes, UAObject, Variant} from 'node-opcua'; +import {catTestServer} from '../config/logging'; +import {OpMode} from '../model/core/enum'; + +export abstract class TestServerVariable { + + public readonly name: string; + public opMode: number = 0; + public wqc: number = 0; + public osLevel: number = 0; + protected simulation: boolean; + protected variableNode: UAObject; + + constructor(namespace: Namespace, rootNode: UAObject, variableName: string, simulation = false) { + catTestServer.info(`Add variable ${variableName}`); + + this.name = variableName; + this.simulation = simulation; + + this.variableNode = namespace.addObject({ + organizedBy: rootNode, + browseName: variableName + }); + + namespace.addVariable({ + componentOf: this.variableNode, + nodeId: `ns=1;s=${variableName}.WQC`, + browseName: `${variableName}.WQC`, + dataType: DataType.UInt32, + value: { + get: () => { + return new Variant({dataType: DataType.UInt32, value: this.wqc}); + } + } + }); + + namespace.addVariable({ + componentOf: this.variableNode, + nodeId: `ns=1;s=${variableName}.OSLevel`, + browseName: `${variableName}.OSLevel`, + dataType: DataType.UInt32, + value: { + get: () => { + return new Variant({dataType: DataType.UInt32, value: this.osLevel}); + } + } + }); + + namespace.addVariable({ + componentOf: this.variableNode, + nodeId: `ns=1;s=${variableName}.OpMode`, + browseName: `${variableName}.OpMode`, + dataType: DataType.UInt32, + value: { + get: () => { + catTestServer.debug(`[${variableName}] Get Opmode in testserver ${this.opMode}`); + return new Variant({dataType: DataType.UInt32, value: this.opMode}); + }, + set: (variant) => { + const opModeInt = parseInt(variant.value, 10); + if (opModeInt === OpMode.stateManOp) { + this.opMode = this.opMode & ~OpMode.stateAutAct; + this.opMode = this.opMode | OpMode.stateManAct; + } else if (opModeInt === OpMode.stateAutOp) { + this.opMode = this.opMode & ~OpMode.stateManAct; + this.opMode = this.opMode | OpMode.stateAutAct; + } else if (opModeInt === OpMode.srcExtOp) { + this.opMode = this.opMode | OpMode.srcExtAct; + } else { + return StatusCodes.Bad; + } + catTestServer.debug(`[${variableName}] Set Opmode in testserver ${variant} ` + + `${opModeInt} -> ${this.opMode}`); + return StatusCodes.Good; + } + } + }); + } + + public abstract startSimulation(); + + public abstract stopSimulation(); +} diff --git a/src/server/ExternalTrigger.ts b/src/server/ExternalTrigger.ts index ef224968..14d11cb1 100644 --- a/src/server/ExternalTrigger.ts +++ b/src/server/ExternalTrigger.ts @@ -23,10 +23,9 @@ * SOFTWARE. */ -import {series} from 'async'; import {AttributeIds, NodeId, resolveNodeId} from 'node-opcua'; -import {ClientSession, ClientSubscription, OPCUAClient} from 'node-opcua-client'; -import {post} from 'request'; +import {ClientSession, ClientSubscription, OPCUAClient, + TimestampsToReturn} from 'node-opcua-client'; import {catOpc} from '../config/logging'; export class ExternalTrigger { @@ -53,7 +52,7 @@ export class ExternalTrigger { this.endpoint = endpoint; this.nodeId = resolveNodeId(nodeId); this.callback = callback; - this.client = new OPCUAClient({ + this.client = OPCUAClient.create({ endpoint_must_exist: false, connectionStrategy: { maxRetry: 5 @@ -71,7 +70,7 @@ export class ExternalTrigger { await this.client.connect(this.endpoint); this.session = await this.client.createSession(); - const subscription = new ClientSubscription(this.session, { + const subscription = ClientSubscription.create(this.session, { requestedPublishingInterval: 1000, requestedLifetimeCount: 10, requestedMaxKeepAliveCount: 2, @@ -89,7 +88,7 @@ export class ExternalTrigger { samplingInterval: 100, discardOldest: true, queueSize: 10 - }); + }, TimestampsToReturn.Both); monitoredItem.on('changed', (dataValue) => { catOpc.info(`External trigger is ${dataValue.value.value}`); diff --git a/src/server/router/coreRouter.ts b/src/server/router/coreRouter.ts index bcc3697f..ab11fec8 100644 --- a/src/server/router/coreRouter.ts +++ b/src/server/router/coreRouter.ts @@ -32,17 +32,6 @@ import {Manager} from '../../model/Manager'; export const coreRouter: Router = Router(); -/** - * @api {get} / Get Manager - * @apiName GetManager - * @apiGroup Manager - */ -coreRouter.get('/', (req: Request, res: Response) => { - const manager: Manager = req.app.get('manager'); - const result = manager.json(); - res.json(result); -}); - /** * @api {post} /shutdown Shutdown * @apiName Shutdown @@ -65,7 +54,7 @@ coreRouter.get('/version', (req: Request, res: Response) => { /** * @api {get} /autoReset Get autoReset * @apiName GetAutoReset - * @apiDescription Get status of autoReset + * @apiDescription Get statusNode of autoReset * @apiGroup Manager */ coreRouter.get('/autoReset', asyncHandler(async (req: Request, res: Response) => { @@ -76,7 +65,7 @@ coreRouter.get('/autoReset', asyncHandler(async (req: Request, res: Response) => /** * @api {post} /autoReset Set autoReset * @apiName PostAutoReset - * @apiDescription Set status of autoReset and returns updated value + * @apiDescription Set statusNode of autoReset and returns updated value * @apiGroup Manager * @apiParam {Boolean} autoReset new value of autoReset */ diff --git a/src/server/router/moduleRouter.ts b/src/server/router/moduleRouter.ts index 4e2cabb9..33ec5f28 100644 --- a/src/server/router/moduleRouter.ts +++ b/src/server/router/moduleRouter.ts @@ -27,6 +27,7 @@ import {Request, Response, Router} from 'express'; import {Manager} from '../../model/Manager'; import * as asyncHandler from 'express-async-handler'; +import {constants} from 'http2'; import {catModule, catServer} from '../../config/logging'; export const moduleRouter: Router = Router(); @@ -38,8 +39,7 @@ export const moduleRouter: Router = Router(); */ moduleRouter.get('', asyncHandler(async (req: Request, res: Response) => { const manager: Manager = req.app.get('manager'); - const tasks = manager.modules.map(async (module) => await module.json()); - res.json(await Promise.all(tasks)); + res.json(await manager.getModules()); })); /** @@ -48,10 +48,14 @@ moduleRouter.get('', asyncHandler(async (req: Request, res: Response) => { * @apiGroup Module * @apiParam {string} id Module id */ -moduleRouter.get('/:id', asyncHandler(async (req: Request, res: Response) => { +moduleRouter.get('/:id', (req: Request, res: Response) => { const manager: Manager = req.app.get('manager'); - res.json(await manager.modules.find((module) => module.id === req.params.id).json()); -})); + try { + res.json(manager.getModule(req.params.id).json()); + } catch (err) { + res.status(constants.HTTP_STATUS_NOT_FOUND).send(err.toString()); + } +}); /** * @api {get} /module/:id/download Download module options @@ -59,10 +63,10 @@ moduleRouter.get('/:id', asyncHandler(async (req: Request, res: Response) => { * @apiGroup Module * @apiParam {string} id Module id */ -moduleRouter.get('/:id/download', asyncHandler(async (req: Request, res: Response) => { +moduleRouter.get('/:id/download', (req: Request, res: Response) => { const manager: Manager = req.app.get('manager'); - res.json(await manager.modules.find((module) => module.id === req.params.id).options); -})); + res.json(manager.getModule(req.params.id).options); +}); /** * @api {put} /module Add module @@ -70,18 +74,16 @@ moduleRouter.get('/:id/download', asyncHandler(async (req: Request, res: Respons * @apiGroup Module * @apiParam {ModuleOptions} module Module to be added */ -moduleRouter.put('', asyncHandler(async (req, res) => { - const moduleOptions = req.body.modules; +moduleRouter.put('', (req, res) => { catServer.info(`Load module`); const manager: Manager = req.app.get('manager'); - const newModules = manager.loadModule({module: req.body.module}); + const newModules = manager.loadModule(req.body); newModules.forEach((module) => module.connect() - .catch(() => catModule.warn(`Could not connect to module ${module.id}`) - ) + .catch(() => catModule.warn(`Could not connect to module ${module.id}`)) ); - res.json(await Promise.all(newModules.map((module) => module.json()))); -})); + res.json(newModules.map((module) => module.json())); +}); /** * @api {delete} /module/:id Delete module @@ -94,8 +96,8 @@ moduleRouter.delete('/:id', asyncHandler(async (req: Request, res: Response) => try { await manager.removeModule(req.params.id); res.send({ status: 'Successful deleted', id: req.params.id }); - } catch (e) { - res.status(404).send(`Module {$req.params.id} is protected and can't be deleted`); + } catch (err) { + res.status(404).send(err.toString()); } })); @@ -107,7 +109,7 @@ moduleRouter.delete('/:id', asyncHandler(async (req: Request, res: Response) => */ moduleRouter.post('/:id/connect', asyncHandler(async (req: Request, res: Response) => { const manager: Manager = req.app.get('manager'); - const module = manager.modules.find((module) => module.id === req.params.id); + const module = manager.getModule(req.params.id); await module.connect(); res.json({ module: module.id, status: 'Succesfully connected' }); })); @@ -120,7 +122,7 @@ moduleRouter.post('/:id/connect', asyncHandler(async (req: Request, res: Respons */ moduleRouter.post('/:id/disconnect', asyncHandler(async (req: Request, res: Response) => { const manager: Manager = req.app.get('manager'); - const module = manager.modules.find((module) => module.id === req.params.id); + const module = manager.getModule(req.params.id); await module.disconnect(); res.json({ module: module.id, status: 'Succesfully disconnected' }); })); diff --git a/src/server/router/playerRouter.ts b/src/server/router/playerRouter.ts index 10fe9b51..b10267f9 100644 --- a/src/server/router/playerRouter.ts +++ b/src/server/router/playerRouter.ts @@ -94,7 +94,7 @@ playerRouter.post('/reset', asyncHandler(async (req: Request, res: Response) => playerRouter.post('/enqueue', async (req: Request, res: Response) => { catServer.info(`Enqueue recipe ${req.body}`); const manager: Manager = req.app.get('manager'); - const recipe = manager.recipes.find((recipe) => recipe.id === req.body.recipeId); + const recipe = manager.recipes.find((rec) => rec.id === req.body.recipeId); catServer.debug(`Enqueue recipe ${recipe.name}`); if (recipe) { manager.player.enqueue(recipe); diff --git a/src/server/router/recipeRouter.ts b/src/server/router/recipeRouter.ts index 2f869583..3e500f4c 100644 --- a/src/server/router/recipeRouter.ts +++ b/src/server/router/recipeRouter.ts @@ -24,7 +24,6 @@ */ import {Request, Response, Router} from 'express'; -import * as asyncHandler from 'express-async-handler'; import {catServer} from '../../config/logging'; import {Manager} from '../../model/Manager'; @@ -35,13 +34,13 @@ export const recipeRouter: Router = Router(); * @apiName GetRecipeList * @apiGroup Recipe */ -recipeRouter.get('/', asyncHandler(async (req: Request, res: Response) => { +recipeRouter.get('/', (req: Request, res: Response) => { const manager: Manager = req.app.get('manager'); const result = manager.recipes.map((recipe) => { return { id: recipe.id, options: recipe.options, protected: recipe.protected }; }); res.json(result); -})); +}); /** * @api {get} /recipe/:recipeId Get recipe @@ -49,10 +48,9 @@ recipeRouter.get('/', asyncHandler(async (req: Request, res: Response) => { * @apiGroup Recipe * @apiParam recipeId */ -recipeRouter.get('/:recipeId', async (req: Request, res: Response) => { +recipeRouter.get('/:recipeId', (req: Request, res: Response) => { const manager: Manager = req.app.get('manager'); - const result = manager.recipes.find((recipe) => recipe.id === req.params.recipeId).json(); - res.json(result); + res.json(manager.recipes.find((recipe) => recipe.id === req.params.recipeId).json()); }); /** @@ -61,7 +59,7 @@ recipeRouter.get('/:recipeId', async (req: Request, res: Response) => { * @apiGroup Recipe * @apiParam recipeId */ -recipeRouter.get('/:recipeId/download', async (req: Request, res: Response) => { +recipeRouter.get('/:recipeId/download', (req: Request, res: Response) => { const manager: Manager = req.app.get('manager'); const result = manager.recipes.find((recipe) => recipe.id === req.params.recipeId).options; res.json(result); @@ -73,7 +71,7 @@ recipeRouter.get('/:recipeId/download', async (req: Request, res: Response) => { * @apiGroup Recipe * @apiParam recipeId id of recipe to be deleted */ -recipeRouter.delete('/:recipeId', asyncHandler(async (req: Request, res: Response) => { +recipeRouter.delete('/:recipeId', (req: Request, res: Response) => { const manager: Manager = req.app.get('manager'); try { manager.removeRecipe(req.params.recipeId); @@ -81,7 +79,7 @@ recipeRouter.delete('/:recipeId', asyncHandler(async (req: Request, res: Respons } catch (err) { res.status(400).send(err.toString()); } -})); +}); /** * @api {put} /recipe Load recipe @@ -89,9 +87,9 @@ recipeRouter.delete('/:recipeId', asyncHandler(async (req: Request, res: Respons * @apiGroup Recipe * @apiParam {Object} recipe new recipe */ -recipeRouter.put('', asyncHandler(async (req: Request, res: Response) => { +recipeRouter.put('', (req: Request, res: Response) => { catServer.debug(`PUT /recipe: ${JSON.stringify(req.body)}`); const manager: Manager = req.app.get('manager'); manager.loadRecipe(req.body); res.json({ status: 'recipe successful loaded' }); -})); +}); diff --git a/src/server/router/serviceRouter.ts b/src/server/router/serviceRouter.ts index 07b76890..8ecdc679 100644 --- a/src/server/router/serviceRouter.ts +++ b/src/server/router/serviceRouter.ts @@ -23,6 +23,7 @@ * SOFTWARE. */ +import {ServiceCommand} from '@p2olab/polaris-interface'; import {Request, Response, Router} from 'express'; import * as asyncHandler from 'express-async-handler'; import {catServer} from '../../config/logging'; @@ -31,26 +32,26 @@ import {Manager} from '../../model/Manager'; export const serviceRouter: Router = Router(); /** - * @api {post} /module/:moduleId/service/:serviceName/parameter Configure TestServerService + * @api {post} /module/:moduleId/service/:serviceName/parameter Configure Service * @apiName ConfigureService * @apiDescription Configure service parameter - * @apiGroup TestServerService + * @apiGroup Service * @apiParam {string} moduleId Module id * @apiParam {string} serviceName Name of service - * @apiParam {ParameterOptions[]} strategyParameters Module TestServerService Parameter + * @apiParam {ParameterOptions[]} strategyParameters Module Service Parameter */ serviceRouter.post('/:moduleId/service/:serviceName/parameter', asyncHandler(async (req: Request, res: Response) => { const manager: Manager = req.app.get('manager'); const service = manager.getService(req.params.moduleId, req.params.serviceName); await service.setServiceParameters(req.body.parameters); - res.json(await service.getOverview()); + res.json(service.getOverview()); })); /** * @api {post} /module/:moduleId/service/:serviceName/strategy Configure Strategy * @apiName ConfigureStrategy * @apiDescription Configure strategy and set strategyParameters of service - * @apiGroup TestServerService + * @apiGroup Service * @apiParam {string} moduleId Module id * @apiParam {string} serviceName Name of service * @apiParam {string} strategy Name of strategy @@ -60,15 +61,17 @@ serviceRouter.post('/:moduleId/service/:serviceName/strategy', asyncHandler(asyn catServer.info(`Set Strategy: ${req.body.strategy}; Parameters: ${JSON.stringify(req.body.parameters)}`); const manager: Manager = req.app.get('manager'); const service = manager.getService(req.params.moduleId, req.params.serviceName); - await service.setStrategyParameters(req.body.strategy, req.body.parameters); - - res.json(await service.getOverview()); + await service.setStrategy(req.body.strategy); + if (req.body.parameters) { + await service.setParameters(req.body.parameters); + } + res.json(service.getOverview()); })); /** - * @api {post} /module/:moduleId/service/:serviceName/:command Call service + * @api {post} /module/:moduleId/service/:serviceName/:command Call service * @apiName CallService - * @apiGroup TestServerService + * @apiGroup Service * @apiParam {string} moduleId Module id * @apiParam {string} serviceName Name of service * @apiParam {string="start","stop","abort","complete","pause","unhold","reset"} command Command name @@ -79,7 +82,7 @@ serviceRouter.post('/:moduleId/service/:serviceName/:command', asyncHandler(asyn catServer.info(`Call service: ${JSON.stringify(req.params)}`); const manager: Manager = req.app.get('manager'); const service = manager.getService(req.params.moduleId, req.params.serviceName); - const result = await service.execute(req.params.command, req.body.strategy, req.body.parameters); + await service.execute(req.params.command as ServiceCommand, req.body.strategy, req.body.parameters); res.json({ module: module.id, service: service.name, @@ -89,14 +92,14 @@ serviceRouter.post('/:moduleId/service/:serviceName/:command', asyncHandler(asyn })); /** - * @api {get} /module/:moduleId/service/:serviceName/ Get service status + * @api {get} /module/:moduleId/service/:serviceName Get service statusNode * @apiName GetService - * @apiGroup TestServerService + * @apiGroup Service * @apiParam {string} moduleId Module id * @apiParam {string} serviceName Name of service */ serviceRouter.get('/:moduleId/service/:serviceName', asyncHandler(async (req: Request, res: Response) => { const manager: Manager = req.app.get('manager'); const service = manager.getService(req.params.moduleId, req.params.serviceName); - res.json(await service.getOverview()); + res.json(service.getOverview()); })); diff --git a/src/server/router/virtualServiceRouter.ts b/src/server/router/virtualServiceRouter.ts new file mode 100644 index 00000000..676f80fa --- /dev/null +++ b/src/server/router/virtualServiceRouter.ts @@ -0,0 +1,127 @@ +/* + * MIT License + * + * Copyright (c) 2019 Markus Graube , + * Chair for Process Control Systems, Technische Universität Dresden + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +import {ServiceCommand} from '@p2olab/polaris-interface'; +import {Request, Response, Router} from 'express'; +import * as asyncHandler from 'express-async-handler'; +import {catServer} from '../../config/logging'; +import {Manager} from '../../model/Manager'; + +export const virtualServiceRouter: Router = Router(); + +/** + * @api {get} /virtualService Get virtual service list + * @apiName GetVirtualServiceList + * @apiGroup VirtualService + */ +virtualServiceRouter.get('/', asyncHandler(async (req: Request, res: Response) => { + const manager: Manager = req.app.get('manager'); + res.json(await manager.getVirtualServices()); +})); + +/** + * @api {get} /virtualService/:virtualServiceId Get virtual service + * @apiName GetVirtualService + * @apiGroup VirtualService + * @apiParam virtualServiceId id of virtual service + */ +virtualServiceRouter.get('/:virtualServiceId', asyncHandler(async (req: Request, res: Response) => { + const manager: Manager = req.app.get('manager'); + const virtualService = manager.virtualServices.find((vs) => vs.name === req.params.virtualServiceId); + if (virtualService) { + res.json(virtualService.json()); + } else { + throw new Error('No such virtual service'); + } +})); + +/** + * @api {delete} /virtualService/:virtualServiceId Delete virtual service + * @apiName DeleteVirtualService + * @apiGroup VirtualService + * @apiParam virtualServiceId id of virtual service to be deleted + */ +virtualServiceRouter.delete('/:virtualServiceId', asyncHandler(async (req: Request, res: Response) => { + try { + const manager: Manager = req.app.get('manager'); + manager.removeVirtualService(req.params.virtualServiceId); + res.send({ status: 'Successful deleted', id: req.params.virtualServiceId }); + } catch (err) { + res.status(400).send(err.toString()); + } +})); + +/** + * @api {put} /virtualService Instantiate virtual service + * @apiName PutVirtualService + * @apiGroup VirtualService + * @apiParam {string} name new virtual service name + * @apiParam {string} type new virtual service type + */ +virtualServiceRouter.put('', asyncHandler(async (req: Request, res: Response) => { + catServer.debug(`PUT /virtualService: ${JSON.stringify(req.body)}`); + const manager: Manager = req.app.get('manager'); + manager.instantiateVirtualService(req.body); + res.json({ status: 'virtual service successful instantiated' }); +})); + +/** + * @api {post} /virtualService/:virtualServiceName/parameter Configure VirtualService + * @apiName ConfigureVirtualService + * @apiDescription Configure virtual service parameter + * @apiGroup VirtualService + * @apiParam {string} virtualServiceId Id of virtual service + * @apiParam {ParameterOptions[]} parameters virtual service parameter + */ +virtualServiceRouter.post('/:virtualServiceId/parameter', asyncHandler(async (req: Request, res: Response) => { + const manager: Manager = req.app.get('manager'); + const virtualService = manager.virtualServices.find((vs) => vs.name === req.params.virtualServiceId); + await virtualService.setParameters(JSON.parse(req.body.parameters)); + res.json(virtualService.json()); +})); + +/** + * @api {post} /virtualService/:virtualServiceId/:command Call virtual service + * @apiName CallVirtualService + * @apiGroup VirtualService + * @apiParam {string} virtualServiceId Name of virtual service + * @apiParam {string="start","stop","abort","complete","pause","unhold","reset"} commandNode Command name + * @apiParam {ParameterOptions[]} [parameters] Parameters for *start* or *restart* + */ +virtualServiceRouter.post('/:virtualServiceId/:command', asyncHandler(async (req: Request, res: Response) => { + catServer.info(`Call virtual service: ${JSON.stringify(req.params)} - ${JSON.stringify(req.body)}`); + const manager: Manager = req.app.get('manager'); + const virtualService = manager.virtualServices.find((vs) => vs.name === req.params.virtualServiceId); + + if (req.body.parameters) { + await virtualService.setParameters(req.body.parameters); + } + await virtualService.executeCommand(req.params.command as ServiceCommand); + res.json({ + virtualService: virtualService.name, + command: req.params.command, + status: 'Command succesfully send' + }); +})); diff --git a/src/server/routes.ts b/src/server/routes.ts index 03459c7c..4e4afbde 100644 --- a/src/server/routes.ts +++ b/src/server/routes.ts @@ -33,6 +33,7 @@ import {playerRouter} from './router/playerRouter'; import {recipeRouter} from './router/recipeRouter'; import {recipeRunRouter} from './router/recipeRunRouter'; import {serviceRouter} from './router/serviceRouter'; +import {virtualServiceRouter} from './router/virtualServiceRouter'; export default class Routes { public static init(app: express.Application, manager: Manager): void { @@ -51,11 +52,12 @@ export default class Routes { app.use('/api/recipeRun', recipeRunRouter); app.use('/api/recipe', recipeRouter); app.use('/api/player', playerRouter); + app.use('/api/virtualService', virtualServiceRouter); app.use('/api', coreRouter); // Error handling app.use((err: Error, req: Request, res: Response, next: NextFunction) => { - catServer.error(`An Error occured: ${err.toString()}`, err); + catServer.warn(`Internal server error (HTTP 500): ${err.toString()}`); res.status(500).send({ status: 'error', error: err.toString(), stack: err.stack }); }); diff --git a/src/server/server.ts b/src/server/server.ts index 2e91a476..d10d4021 100755 --- a/src/server/server.ts +++ b/src/server/server.ts @@ -88,7 +88,7 @@ export class Server { * @param data */ private notifyClients(message: string, data: any) { - catServer.trace(`WS refresh published ${message} ${data}`); + catServer.trace(`WS refresh published ${message}: ${JSON.stringify(data)}`); if (this.wss) { this.wss.clients.forEach((client) => { if (client.readyState === WebSocket.OPEN) { diff --git a/src/server/serverHandlers.ts b/src/server/serverHandlers.ts index f7ce09f1..e73064e9 100644 --- a/src/server/serverHandlers.ts +++ b/src/server/serverHandlers.ts @@ -23,6 +23,8 @@ * SOFTWARE. */ +import {catServer} from '../config/logging'; + export function normalizePort(val: number | string): number | string | boolean { const port: number = (typeof val === 'string') ? parseInt(val, 10) : val; @@ -45,11 +47,11 @@ export function onError(error: NodeJS.ErrnoException, port: number | string | bo switch (error.code) { case 'EACCES': - console.error(`${bind} requires elevated privileges`); + catServer.warn(`${bind} requires elevated privileges`); process.exit(1); break; case 'EADDRINUSE': - console.error(`${bind} is already in use`); + catServer.warn(`${bind} is already in use`); process.exit(1); break; default: diff --git a/test/helper.ts b/test/helper.ts index 5e3854ef..9c00e6dd 100644 --- a/test/helper.ts +++ b/test/helper.ts @@ -24,24 +24,47 @@ */ import {timeout} from 'promise-timeout'; +import {BaseService} from '../src/model/core/BaseService'; import {ServiceState} from '../src/model/core/enum'; -import {Service} from '../src/model/core/Service'; +import {Module} from '../src/model/core/Module'; /** * resolve when service changes to expectedState * rejects after ms milliseconds - * @param {Service} service service to be waited for + * @param {BaseService} service service to be waited for * @param {string} expectedState * @param {number} ms max time before promise is rejected * @returns {Promise} */ -export function waitForStateChange(service: Service, expectedState: string, ms = 1000): Promise { +export function waitForStateChange(service: BaseService, expectedState: string, ms = 1000): Promise { return timeout(new Promise((resolve) => { - service.on('state', function test(data) { - if (ServiceState[data.state] === expectedState) { - service.removeListener('state', test); + service.eventEmitter.on('state', function test(state) { + if (ServiceState[state] === expectedState) { + service.eventEmitter.removeListener('state', test); resolve(); } }); }), ms); } + +export function waitForParameterChange(module: Module, parameterName: string, expected = null) { + return new Promise((resolve) => + module.on('parameterChanged', (data) => { + if (data.parameter === parameterName && (expected === null || data.value === expected)) { + resolve(); + module.removeListener('parameterChanged', test); + } + }) + ); +} + +export function waitForVariableChange(module: Module, variableName: string, expected = null) { + return new Promise((resolve) => + module.on('variableChanged', function test(data) { + if (data.variable === variableName && (expected === null || data.value === expected)) { + resolve(); + module.removeListener('variableChanged', test); + } + }) + ); +} diff --git a/test/integration/cif.integration.ts b/test/integration/cif.integration.ts new file mode 100644 index 00000000..e69de29b diff --git a/test/model/DataAssembly.spec.ts b/test/model/DataAssembly.spec.ts deleted file mode 100644 index 0d2288ac..00000000 --- a/test/model/DataAssembly.spec.ts +++ /dev/null @@ -1,87 +0,0 @@ -/* - * MIT License - * - * Copyright (c) 2019 Markus Graube , - * Chair for Process Control Systems, Technische Universität Dresden - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -import * as chai from 'chai'; -import * as chaiAsPromised from 'chai-as-promised'; -import * as fs from 'fs'; -import {Module} from '../../src/model/core/Module'; -import {DataAssemblyFactory} from '../../src/model/dataAssembly/DataAssemblyFactory'; -import {ModuleTestServer, TestServerVariable} from '../../src/moduleTestServer/ModuleTestServer'; -import {isAutomaticState, isManualState, OpMode, opModetoJson} from '../../src/model/core/enum'; - -chai.use(chaiAsPromised); -const expect = chai.expect; - -describe('DataAssembly', () => { - - let moduleServer: ModuleTestServer; - let moduleJson; - let module: Module; - - before(async () => { - moduleServer = new ModuleTestServer(); - await moduleServer.start(); - - moduleJson = JSON.parse(fs.readFileSync('assets/modules/module_testserver_1.0.0.json').toString()) - .modules[0]; - module = new Module(moduleJson); - - await module.connect(); - - }); - - after(async () => { - await module.disconnect(); - await moduleServer.shutdown(); - }); - - it('should create', async () => { - expect(() => DataAssemblyFactory.create( - {name: 'test', interface_class: 'none', communication: null}, null) - ).to.throw(/No module for data assembly/); - - const daJson = moduleJson.services[0].strategies[0].parameters[0]; - const da = DataAssemblyFactory.create(daJson, module); - - expect(DataAssemblyFactory.isExtAnaOp(da)).to.equal(true); - expect(DataAssemblyFactory.isExtIntAnaOp(da)).to.equal(true); - expect(DataAssemblyFactory.isAdvAnaOp(da)).to.equal(false); - - let opMode = await da.getOpMode(); - expect(opModetoJson(opMode)).to.deep.equal({state: 'off', source: 'internal'}); - - (moduleServer.services[0].parameter[0] as TestServerVariable).opMode = OpMode.stateManAct; - - await da.waitForOpModeToPassSpecificTest(isManualState); - opMode = await da.getOpMode(); - expect(opModetoJson(opMode)).to.deep.equal({state: 'manual', source: 'internal'}); - - da.waitForOpModeToPassSpecificTest(isAutomaticState); - (moduleServer.services[0].parameter[0] as TestServerVariable).opMode = OpMode.stateAutAct; - opMode = await da.getOpMode(); - expect(opModetoJson(opMode)).to.deep.equal({state: 'automatic', source: 'internal'}); - }); - -}); diff --git a/test/model/Manager.spec.ts b/test/model/Manager.spec.ts index 832f0b32..972d5db8 100644 --- a/test/model/Manager.spec.ts +++ b/test/model/Manager.spec.ts @@ -39,57 +39,98 @@ const expect = chai.expect; describe('Manager', () => { - it('should reject loading modules with empty options', () => { - const manager = new Manager(); - expect(() => manager.loadModule({})).to.throw(); - expect(() => manager.loadModule({someattribute: 'abc'} as any)).to.throw(); - }); + context('loading modules', () => { - it('should load with single module', () => { - const modulesJson = JSON.parse(fs.readFileSync('assets/modules/module_cif.json').toString()); - const moduleJson = modulesJson.modules[0]; - const manager = new Manager(); - manager.loadModule({module: moduleJson}); - }); + it('should reject loading modules with empty options', () => { + const manager = new Manager(); + expect(() => manager.loadModule(null)).to.throw(); + expect(() => manager.loadModule({})).to.throw(); + expect(() => manager.loadModule({someattribute: 'abc'} as any)).to.throw(); + }); - it('should load with subplants options', () => { - const modulesJson = JSON.parse(fs.readFileSync('assets/modules/module_cif.json').toString()); - const manager = new Manager(); - manager.loadModule({subplants: [modulesJson]}); - }); + it('should load modules', () => { + const modulesJson = JSON.parse(fs.readFileSync('assets/modules/module_cif.json').toString()); + const manager = new Manager(); + manager.loadModule(modulesJson); + expect(() => manager.loadModule(modulesJson)).to.throw('already in registered modules'); + }); - it('should load the achema modules', () => { - const manager = new Manager(); - const modules = manager.loadModule( - JSON.parse(fs.readFileSync('assets/modules/modules_achema.json').toString()), - true); - expect(modules).to.have.lengthOf(3); + it('should load with single module', () => { + const modulesJson = JSON.parse(fs.readFileSync('assets/modules/module_cif.json').toString()); + const moduleJson = modulesJson.modules[0]; + const manager = new Manager(); + manager.loadModule({module: moduleJson}); + expect(() => manager.loadModule({module: moduleJson})).to.throw('already in registered modules'); + }); + + it('should load with subplants options', () => { + const modulesJson = JSON.parse(fs.readFileSync('assets/modules/module_cif.json').toString()); + const manager = new Manager(); + manager.loadModule({subplants: [modulesJson]}); + expect(() => manager.loadModule({subplants: [modulesJson]})).to.throw('already in registered modules'); + }); + + it('should load the achema modules', async () => { + const manager = new Manager(); + const modules = manager.loadModule( + JSON.parse(fs.readFileSync('assets/modules/achema_demonstrator/modules_achema.json').toString()), + true); + expect(modules).to.have.lengthOf(3); - expect(manager.modules).to.have.lengthOf(3); + expect(manager.modules).to.have.lengthOf(3); - const service = manager.getService('Dose', 'Fill'); - expect(service).to.be.instanceOf(Service); - expect(service.name).to.equal('Fill'); - expect(() => manager.getService('Dose', 'NoService')).to.throw(); - expect(() => manager.getService('NoModule', 'NoService')).to.throw(); + const service = manager.getService('Dose', 'Fill'); + expect(service).to.be.instanceOf(Service); + expect(service.name).to.equal('Fill'); + expect(() => manager.getService('Dose', 'NoService')).to.throw(); + expect(() => manager.getService('NoModule', 'NoService')).to.throw(); + + await expect(manager.removeModule('something')).to.be.rejectedWith('Module with id something not found'); + }); - expect(manager.removeModule('something')).to.be.rejectedWith(/No Module/); - expect(manager.removeModule(manager.modules[1].id)).to.be.rejectedWith(/is protected/); + it('should prevent removing a protected module', async () => { + const manager = new Manager(); + manager.loadModule( + JSON.parse(fs.readFileSync('assets/modules/achema_demonstrator/modules_achema.json').toString()), + true); + await expect(manager.removeModule(manager.modules[0].id)).to.be.rejectedWith(/is protected/); + }); }); - it('should prevent removing a protected module', () => { + it('should load and remove recipe', () => { + const modulesRecipe = + JSON.parse(fs.readFileSync('assets/recipes/test/recipe_time_local.json').toString()); const manager = new Manager(); - const modules = manager.loadModule( - JSON.parse(fs.readFileSync('assets/modules/modules_achema.json').toString()), - true); + manager.loadRecipe(modulesRecipe); + manager.loadRecipe(modulesRecipe, true); + + expect(manager.recipes).to.have.lengthOf(2); + + expect(() => manager.removeRecipe('whatever')).to.throw('not available'); + + manager.removeRecipe(manager.recipes[0].id); + expect(manager.recipes).to.have.lengthOf(1); + + expect(() => manager.removeRecipe(manager.recipes[0].id)).to.throw('protected'); }); - it('should provide JSON output', () => { + it('should load and provide virtual services', () => { const manager = new Manager(); - expect(manager.json().autoReset).to.equal(true); + expect(manager.getVirtualServices()).to.have.length(0); + + manager.instantiateVirtualService({name: 'timer1', type: 'timer'}); + expect(manager.getVirtualServices()).to.have.length(1); + expect(manager.getVirtualServices()[0]).to.have.property('name', 'timer1'); + + expect(() => manager.removeVirtualService('timer234')).to.throw('not available'); + manager.removeVirtualService('timer1'); + expect(manager.getVirtualServices()).to.have.length(0); + + expect(() => manager.removeVirtualService('timer1')).to.throw('not available'); }); - describe('test with test module', () => { + describe('test with test module', function() { + this.timeout(5000); let moduleServer: ModuleTestServer; before(async () => { @@ -102,39 +143,45 @@ describe('Manager', () => { }); it('should load from options, stop, abort and reset manager and remove module', async () => { - - const moduleJson = parseJson(fs.readFileSync('assets/modules/module_testserver_1.0.0.json', 'utf8'), null, 60); + const moduleJson = parseJson( + fs.readFileSync('assets/modules/module_testserver_1.0.0.json', 'utf8'), null, 60); const manager = new Manager(); manager.loadModule(moduleJson); expect(manager.modules).to.have.lengthOf(1); const module = manager.modules[0]; - const service = module.services[1]; + const service1 = module.services[0]; + const service2 = module.services[1]; module.connect(); - await waitForStateChange(service, 'IDLE', 2000); - service.execute(ServiceCommand.start); - await waitForStateChange(service, 'EXECUTE'); + await waitForStateChange(service2, 'IDLE', 2000); + service2.execute(ServiceCommand.start); + await waitForStateChange(service2, 'EXECUTE'); await manager.stopAllServices(); - await waitForStateChange(service, 'STOPPED'); - expect(service.status.value).to.equal(ServiceState.STOPPED); + await waitForStateChange(service2, 'STOPPED'); + expect(service2.state).to.equal(ServiceState.STOPPED); await manager.abortAllServices(); - await waitForStateChange(service, 'ABORTED'); - expect(service.status.value).to.equal(ServiceState.ABORTED); + await Promise.all([ + waitForStateChange(service1, 'ABORTED'), + waitForStateChange(service2, 'ABORTED')] + ); + expect(service1.state).to.equal(ServiceState.ABORTED); + expect(service2.state).to.equal(ServiceState.ABORTED); await manager.resetAllServices(); - await waitForStateChange(service, 'IDLE'); - expect(service.status.value).to.equal(ServiceState.IDLE); + await waitForStateChange(service2, 'IDLE'); + expect(service2.state).to.equal(ServiceState.IDLE); await manager.removeModule(module.id); expect(manager.modules).to.have.lengthOf(0); - }).slow(2000).timeout(10000).retries(3); + }).timeout(5000); it('should autoreset service', async () => { - const moduleJson = parseJson(fs.readFileSync('assets/modules/module_testserver_1.0.0.json', 'utf8'), null, 60); + const moduleJson = parseJson( + fs.readFileSync('assets/modules/module_testserver_1.0.0.json', 'utf8'), null, 60); const manager = new Manager(); manager.autoreset = true; @@ -143,16 +190,16 @@ describe('Manager', () => { const module = manager.modules[0]; const service = module.services[1]; - await module.connect(); - await waitForStateChange(service, 'IDLE'); + module.connect(); + await waitForStateChange(service, 'IDLE', 2000); service.execute(ServiceCommand.start); await waitForStateChange(service, 'EXECUTE'); service.execute(ServiceCommand.complete); await waitForStateChange(service, 'COMPLETED'); await waitForStateChange(service, 'IDLE'); - }).slow(2000).timeout(5000); + }); - }).retries(3); + }); }); diff --git a/test/model/core/Module.spec.ts b/test/model/core/Module.spec.ts index 5dab6c6a..dc99929b 100644 --- a/test/model/core/Module.spec.ts +++ b/test/model/core/Module.spec.ts @@ -23,54 +23,101 @@ * SOFTWARE. */ -import * as assert from 'assert'; -import {expect} from 'chai'; +import * as chai from 'chai'; +import * as chaiAsPromised from 'chai-as-promised'; import * as fs from 'fs'; -import {Module, ModuleOptions} from '../../../src/model/core/Module'; +import {Module} from '../../../src/model/core/Module'; +import {StrView} from '../../../src/model/dataAssembly/Str'; import {ModuleTestServer} from '../../../src/moduleTestServer/ModuleTestServer'; +chai.use(chaiAsPromised); +const expect = chai.expect; + describe('Module', () => { - it('should not connect to a module with wrong endpoint', async () => { - const options: ModuleOptions = JSON.parse(fs.readFileSync('assets/modules/module_cif.json').toString()).modules[0]; - options.opcua_server_url = 'opc.tcp://10.6.51.99:484144'; - const module = new Module(options); - try { - await module.connect(); - } catch (e) { - expect(e).to.exist; - } - expect(module.isConnected()).to.equal(false); + it('should load the cif module json', () => { + const f = fs.readFileSync('assets/modules/module_cif.json'); + const module = new Module(JSON.parse(f.toString()).modules[0]); + expect(module).to.have.property('id', 'CIF'); + expect(module.services).to.have.length(6); }); - it('should load the cif module json', (done) => { - fs.readFile('assets/modules/module_cif.json', (err, file) => { - const module = new Module(JSON.parse(file.toString()).modules[0]); - assert.equal(module.id, 'CIF'); - assert.equal(module.services.length, 6); - done(); + context('with module server', () => { + let moduleServer: ModuleTestServer; + + before(async () => { + moduleServer = new ModuleTestServer(); + await moduleServer.start(); + moduleServer.startSimulation(); }); - }); - it('should recognize a opc ua server shutdown', async () => { - const moduleJson = JSON.parse(fs.readFileSync('assets/modules/module_testserver_1.0.0.json', 'utf8')).modules[0]; + after(async () => { + moduleServer.stopSimulation(); + await moduleServer.shutdown(); + }); + + it('should connect to module, provide correct json output and disconnect', async () => { + const moduleJson = + JSON.parse(fs.readFileSync('assets/modules/module_testserver_1.0.0.json', 'utf8')).modules[0]; + const module = new Module(moduleJson); + await module.connect(); + + const json = module.json(); + expect(json).to.have.property('id', 'CIF'); + expect(json).to.have.property('endpoint', 'opc.tcp://127.0.0.1:4334/ModuleTestServer'); + expect(json).to.have.property('protected', false); + expect(json).to.have.property('services') + .to.have.lengthOf(2); - const module = new Module(moduleJson); + expect(module.services[0].eventEmitter.listenerCount('state')).to.equal(1); + expect(module.services[0].serviceControl.listenerCount('State')).to.equal(1); - const moduleServer = new ModuleTestServer(); - await moduleServer.start(); - expect(module.isConnected()).to.equal(false); + expect(module.variables[0].listenerCount('V')).to.equal(1); + expect(module.variables[0].communication.WQC.listenerCount('changed')).to.equal(1); + expect(module.variables[0].communication.WQC.listenerCount('changed')).to.equal(1); + expect(module.services[0].eventEmitter.listenerCount('parameterChanged')).to.equal(1); - await module.connect(); - expect(module.isConnected()).to.equal(true); + const errorMsg = module.services[0].strategies[0].parameters[2] as StrView; + expect(errorMsg.communication.WQC.listenerCount('changed')).to.equal(1); + expect(errorMsg.communication.Text.listenerCount('changed')).to.equal(1); + expect(errorMsg.listenerCount('Text')).to.equal(1); - await new Promise((resolve) => { - module.once('disconnected', () => { - expect(module.isConnected()).to.equal(false); - resolve(); - }); - moduleServer.shutdown(); + await Promise.all([ + new Promise((resolve) => module.on('parameterChanged', resolve)), + new Promise((resolve) => module.on('variableChanged', resolve)), + new Promise((resolve) => module.on('stateChanged', resolve)) + ]); + await module.disconnect(); }); - }).retries(3); + + it('should work after reconnect', async () => { + const moduleJson = + JSON.parse(fs.readFileSync('assets/modules/module_testserver_1.0.0.json', 'utf8')).modules[0]; + const module = new Module(moduleJson); + const param = module.services[0].strategies[0].parameters[2]; + expect(param.listenerCount('Text')).to.equal(0); + + await module.connect(); + expect(module.connection.monitoredItemSize()).to.equal(48); + expect(param.listenerCount('Text')).to.equal(1); + + await Promise.all([ + new Promise((resolve) => module.on('parameterChanged', resolve)), + new Promise((resolve) => module.on('variableChanged', resolve)), + new Promise((resolve) => module.on('stateChanged', resolve)) + ]); + await module.disconnect(); + expect(module.connection.monitoredItemSize()).to.equal(0); + + await module.connect(); + expect(module.connection.monitoredItemSize()).to.equal(48); + await Promise.all([ + new Promise((resolve) => module.on('parameterChanged', resolve)), + new Promise((resolve) => module.on('variableChanged', resolve)), + new Promise((resolve) => module.on('stateChanged', resolve)) + ]); + }).timeout(5000); + + }); }); diff --git a/test/model/core/OpcUaConnection.spec.ts b/test/model/core/OpcUaConnection.spec.ts new file mode 100644 index 00000000..f2ce88e7 --- /dev/null +++ b/test/model/core/OpcUaConnection.spec.ts @@ -0,0 +1,178 @@ +/* + * MIT License + * + * Copyright (c) 2019 Markus Graube , + * Chair for Process Control Systems, Technische Universität Dresden + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +import * as chai from 'chai'; +import * as chaiAsPromised from 'chai-as-promised'; +import {OpcUaConnection} from '../../../src/model/core/OpcUaConnection'; +import {ModuleTestServer} from '../../../src/moduleTestServer/ModuleTestServer'; + +chai.use(chaiAsPromised); +const expect = chai.expect; + +describe('OpcUaConnection', () => { + + it('should reject connecting to a server with too high port', async () => { + const connection = new OpcUaConnection('test', 'opc.tcp://127.0.0.1:44447777'); + expect(connection.isConnected()).to.equal(false); + await expect(connection.connect()).to.be.rejectedWith('The connection has been rejected by server'); + }); + + it('should reject connecting to a server with not existing endpoint', async () => { + const connection = new OpcUaConnection('test', 'opc.tcp://127.0.0.1:4444'); + expect(connection.isConnected()).to.equal(false); + await expect(connection.connect()).to.be.rejectedWith('cannot be established'); + expect(connection.isConnected()).to.equal(false); + }).timeout(5000); + + describe('with test server', () => { + let moduleServer: ModuleTestServer; + + before(async () => { + moduleServer = new ModuleTestServer(); + await moduleServer.start(); + }); + + after(async () => { + await moduleServer.shutdown(); + }); + + it('should connect to a opc ua test server, read an opc item and disconnect', async () => { + const connection = new OpcUaConnection('testserver', 'opc.tcp://localhost:4334'); + expect(connection.isConnected()).to.equal(false); + + await connection.connect(); + expect(connection.isConnected()).to.equal(true); + + const result = await connection.readOpcUaNode('Service1.Parameter1.VExt', 'urn:NodeOPCUA-Server-default'); + expect(result.statusCode.value).to.equal(0); + expect(result.statusCode.description).to.equal('No Error'); + expect(result.value.value).to.equal(20); + + await connection.disconnect(); + }); + + it('should connect to a opc ua test server, subscribes to one opc item and disconnect', async () => { + const connection = new OpcUaConnection('testserver', 'opc.tcp://localhost:4334'); + expect(connection.isConnected()).to.equal(false); + + await connection.connect(); + expect(connection.isConnected()).to.equal(true); + + const result = await connection.listenToOpcUaNode('Service1.ErrorMsg.Text', 'urn:NodeOPCUA-Server-default'); + expect(result.statusCode.value).to.equal(0); + expect(result.statusCode.description).to.equal('No Error'); + + await new Promise((resolve) => result.on('changed', resolve)); + + await connection.disconnect(); + }); + + it('should work after reconnection', async () => { + const connection = new OpcUaConnection('testserver', 'opc.tcp://localhost:4334'); + expect(connection.isConnected()).to.equal(false); + + await connection.connect(); + expect(connection.isConnected()).to.equal(true); + + const result = await connection.listenToOpcUaNode('Service1.ErrorMsg.Text', 'urn:NodeOPCUA-Server-default'); + expect(result.statusCode.value).to.equal(0); + expect(result.statusCode.description).to.equal('No Error'); + expect(connection.monitoredItemSize()).to.equal(1); + + await new Promise((resolve) => result.on('changed', resolve)); + + await connection.disconnect(); + expect(connection.monitoredItemSize()).to.equal(0); + await connection.connect(); + + const res2 = await connection.listenToOpcUaNode('Service1.ErrorMsg.Text', 'urn:NodeOPCUA-Server-default'); + await new Promise((resolve, reject) => { + result.on('changed', reject); + res2.on('changed', resolve); + }); + expect(connection.monitoredItemSize()).to.equal(1); + }); + + it('should connect to a opc ua test server, listen to some opc items and disconnect', async () => { + const connection = new OpcUaConnection('testserver', 'opc.tcp://localhost:4334'); + expect(connection.isConnected()).to.equal(false); + + await connection.connect(); + expect(connection.isConnected()).to.equal(true); + + await connection.listenToOpcUaNode('Service1.Parameter1.VExt', 'urn:NodeOPCUA-Server-default'); + expect(connection.monitoredItemSize()).equals(1); + + await connection.listenToOpcUaNode('Service1.Parameter1.VExt', 'urn:NodeOPCUA-Server-default'); + expect(connection.monitoredItemSize()).equals(1); + + await expect(connection.listenToOpcUaNode('notexistant', 'urn:NodeOPCUA-Server-default')) + .to.be.rejectedWith('does not exist'); + expect(connection.monitoredItemSize()).equals(1); + + await expect(connection.listenToOpcUaNode('notexistant', 'urn:nan')) + .to.be.rejectedWith('Could not resolve namespace'); + expect(connection.monitoredItemSize()).equals(1); + + await connection.listenToOpcUaNode('Service1.OpMode', 'urn:NodeOPCUA-Server-default'); + expect(connection.monitoredItemSize()).equals(2); + + await connection.disconnect(); + + }).timeout(50000); + + it('should connect with username and password', async () => { + const connection = new OpcUaConnection('testserver', 'opc.tcp://localhost:4334', 'admin', '1234'); + await connection.connect(); + await connection.disconnect(); + }); + + it('should fail connecting with wrong username and password', async () => { + const connection = new OpcUaConnection('testserver', 'opc.tcp://localhost:4334', 'admin', 'empty'); + await expect(connection.connect()).to.be.rejectedWith('BadUserAccessDenied'); + }); + + }); + + it('should connect to a opc ua test server and recognize a shutdown of this server', async () => { + const connection = new OpcUaConnection('testserver', 'opc.tcp://localhost:4334'); + const moduleServer = new ModuleTestServer(); + await moduleServer.start(); + + expect(connection.isConnected()).to.equal(false); + + await connection.connect(); + expect(connection.isConnected()).to.equal(true); + + await new Promise((resolve) => { + connection.once('disconnected', () => { + expect(connection.isConnected()).to.equal(false); + resolve(); + }); + moduleServer.shutdown(); + }); + }).timeout(5000); + +}); diff --git a/test/model/core/Service.spec.ts b/test/model/core/Service.spec.ts index 4d1e3db7..639c7288 100644 --- a/test/model/core/Service.spec.ts +++ b/test/model/core/Service.spec.ts @@ -28,11 +28,14 @@ import * as chai from 'chai'; import * as chaiAsPromised from 'chai-as-promised'; import * as fs from 'fs'; import * as parseJson from 'json-parse-better-errors'; -import * as delay from 'timeout-as-promise'; -import {OpMode} from '../../../src/model/core/enum'; +import { + isAutomaticState, isExtSource, isOffState, OpMode, + ServiceState +} from '../../../src/model/core/enum'; import {Module} from '../../../src/model/core/Module'; import {Service} from '../../../src/model/core/Service'; -import {ModuleTestServer, TestServerService} from '../../../src/moduleTestServer/ModuleTestServer'; +import {ModuleTestServer} from '../../../src/moduleTestServer/ModuleTestServer'; +import {TestServerService} from '../../../src/moduleTestServer/ModuleTestService'; import {waitForStateChange} from '../../helper'; chai.use(chaiAsPromised); @@ -40,178 +43,217 @@ const expect = chai.expect; describe('Service', () => { - let moduleServer: ModuleTestServer; - let service: Service; - let testService: TestServerService; - let module: Module; - - beforeEach(async () => { - moduleServer = new ModuleTestServer(); - await moduleServer.start(); - moduleServer.startSimulation(); - testService = moduleServer.services[0]; - - const moduleJson = parseJson(fs.readFileSync('assets/modules/module_testserver_1.0.0.json', 'utf8'), null, 60) - .modules[0]; - const serviceJson = moduleJson.services[0]; - - // copy object - const moduleWithoutService = JSON.parse(JSON.stringify(moduleJson)); - moduleWithoutService.services = []; - - module = new Module(moduleWithoutService); - service = new Service(serviceJson, module); - - await module.connect(); - }); - - afterEach(async () => { - await module.disconnect(); - moduleServer.stopSimulation(); - await moduleServer.shutdown(); - }); + context('constructor', () => { + it('should fail with missing options', () => { + expect(() => new Service(null, null, null)).to.throw(); + }); - it('should reject command if not command enabled', async () => { - expect(service.name).to.equal('Service1'); - let commandEnable = await service.getControlEnable(); - expect(commandEnable).to.deep.equal({ - abort: true, - complete: false, - pause: false, - reset: false, - restart: false, - resume: false, - start: true, - stop: true, - unhold: false + it('should fail with missing name', () => { + expect(() => new Service( + {name: null, parameters: null, communication: null, strategies: null}, null, null) + ).to.throw('No service name'); }); - await service.execute(ServiceCommand.start); + it('should fail with missing module', () => { + expect(() => new Service( + {name: 'test', parameters: null, communication: null, strategies: null}, null, null) + ).to.throw('No module'); + }); - expect(service.execute(ServiceCommand.resume)).to.be.rejectedWith(/ControlOp/); - commandEnable = await service.getControlEnable(); }); it('should reject command if not connected', async () => { - await module.disconnect(); - - expect(service.execute(ServiceCommand.start)).to.be.rejectedWith('Module is not connected'); - }); - - it('waitForOpModeSpecificTest', async () => { - expect(service.name).to.equal('Service1'); - testService.varOpmode = OpMode.stateAutAct; - let opMode = await service.getOpMode(); - expect(opMode).to.equal(OpMode.stateAutAct); - - testService.varOpmode = 0; - await service.execute(ServiceCommand.start); - - opMode = await service.getOpMode(); - expect(opMode).to.equal(OpMode.stateAutAct + OpMode.srcExtAct); + const moduleJson = + parseJson(fs.readFileSync('assets/modules/module_testserver_1.0.0.json', 'utf8'), null, 60) + .modules[0]; + const module = new Module(moduleJson); + const service = module.services[0]; + await expect(service.execute(ServiceCommand.start)).to.be.rejectedWith('Module is not connected'); }); - it('full service state cycle', async () => { - - let result = await service.getOverview(); - expect(result).to.have.property('status', 'IDLE'); - expect(result).to.have.property('controlEnable') - .to.deep.equal({ - abort: true, - complete: false, - pause: false, - reset: false, - restart: false, - resume: false, - start: true, - stop: true, - unhold: false + context('dynamic test', () => { + let moduleServer: ModuleTestServer; + let service: Service; + let testService: TestServerService; + let module: Module; + + beforeEach(async function() { + this.timeout(5000); + moduleServer = new ModuleTestServer(); + await moduleServer.start(); + moduleServer.startSimulation(); + testService = moduleServer.services[0]; + + const moduleJson = + parseJson(fs.readFileSync('assets/modules/module_testserver_1.0.0.json', 'utf8'), null, 60) + .modules[0]; + module = new Module(moduleJson); + service = module.services[0]; + await module.connect(); }); - expect(result).to.have.property('currentStrategy', 'Strategy 1'); - expect(result).to.have.property('name', 'Service1'); - expect(result).to.have.property('opMode').to.deep.equal({ - state: 'off', - source: 'internal' + afterEach(async () => { + await module.disconnect(); + moduleServer.stopSimulation(); + await moduleServer.shutdown(); }); - service.subscribeToService(); - await service.setOperationMode(); - - result = await service.getOverview(); - expect(result).to.have.property('status', 'IDLE'); - expect(result).to.have.property('controlEnable') - .to.deep.equal({ - abort: true, - complete: false, - pause: false, - reset: false, - restart: false, - resume: false, - start: true, - stop: true, - unhold: false + it('should reject command if not command enabled', async () => { + expect(service.name).to.equal('Service1'); + expect(ServiceState[service.state]).to.equal('IDLE'); + expect(service.controlEnable).to.deep.equal({ + abort: true, + complete: false, + pause: false, + reset: false, + restart: false, + resume: false, + start: true, + stop: true, + unhold: false + }); + + await service.execute(ServiceCommand.start); + await waitForStateChange(service, 'STARTING'); + expect(service.controlEnable).to.deep.equal({ + abort: true, + complete: false, + pause: false, + reset: false, + restart: false, + resume: false, + start: false, + stop: true, + unhold: false + }); + + await expect(service.execute(ServiceCommand.resume)).to.be.rejectedWith(/ControlOp/); + expect(service.controlEnable).to.deep.equal({ + abort: true, + complete: false, + pause: false, + reset: false, + restart: false, + resume: false, + start: false, + stop: true, + unhold: false + }); }); - expect(result).to.have.property('currentStrategy', 'Strategy 1'); - expect(result).to.have.property('name', 'Service1'); - expect(result).to.have.property('opMode').to.deep.equal({ - state: 'automatic', - source: 'external' - }); - - await delay(50); - let stateChangeCount = 0; - service.on('state', () => { - stateChangeCount++; - }); - - service.execute(ServiceCommand.start); - await waitForStateChange(service, 'STARTING'); - await waitForStateChange(service, 'EXECUTE'); - - service.execute(ServiceCommand.restart); - await waitForStateChange(service, 'STARTING'); - await waitForStateChange(service, 'EXECUTE'); - - service.execute(ServiceCommand.stop); - await waitForStateChange(service, 'STOPPING'); - await waitForStateChange(service, 'STOPPED'); - - service.execute(ServiceCommand.reset); - await waitForStateChange(service, 'IDLE'); + it('waitForOpModeSpecificTest', async () => { + testService.varOpmode = 0; + await service.waitForOpModeToPassSpecificTest(isOffState); + expect(service.opMode).to.equal(0); - service.execute(ServiceCommand.start); - await waitForStateChange(service, 'STARTING'); - await waitForStateChange(service, 'EXECUTE'); + service.setOperationMode(); - service.execute(ServiceCommand.pause); - await waitForStateChange(service, 'PAUSING'); - await waitForStateChange(service, 'PAUSED'); + await service.waitForOpModeToPassSpecificTest(isAutomaticState); + expect(service.opMode).to.equal(OpMode.stateAutAct); - service.execute(ServiceCommand.resume); - await waitForStateChange(service, 'RESUMING'); - await waitForStateChange(service, 'EXECUTE'); - - service.execute(ServiceCommand.complete); - await waitForStateChange(service, 'COMPLETING'); - await waitForStateChange(service, 'COMPLETED'); - - service.execute(ServiceCommand.abort); - await waitForStateChange(service, 'ABORTING'); - await waitForStateChange(service, 'ABORTED'); - - service.execute(ServiceCommand.reset); - await waitForStateChange(service, 'IDLE'); - - service.execute(ServiceCommand.start); - await waitForStateChange(service, 'STARTING'); - await waitForStateChange(service, 'EXECUTE'); - - service.execute(ServiceCommand.complete); - await waitForStateChange(service, 'COMPLETING'); - await waitForStateChange(service, 'COMPLETED'); + await service.waitForOpModeToPassSpecificTest(isExtSource); + expect(service.opMode).to.equal(OpMode.stateAutAct + OpMode.srcExtAct); + }); - expect(stateChangeCount).to.equal(22); - }).timeout(10000).retries(3); + it('full service state cycle', async () => { + let result = service.getOverview(); + expect(result).to.have.property('status', 'IDLE'); + expect(result).to.have.property('controlEnable') + .to.deep.equal({ + abort: true, + complete: false, + pause: false, + reset: false, + restart: false, + resume: false, + start: true, + stop: true, + unhold: false + }); + + expect(result).to.have.property('currentStrategy', 'Strategy 1'); + expect(result).to.have.property('name', 'Service1'); + expect(result).to.have.property('opMode').to.deep.equal({ + state: 'off', + source: 'internal' + }); + + await service.setOperationMode(); + + result = service.getOverview(); + expect(result).to.have.property('status', 'IDLE'); + expect(result).to.have.property('controlEnable') + .to.deep.equal({ + abort: true, + complete: false, + pause: false, + reset: false, + restart: false, + resume: false, + start: true, + stop: true, + unhold: false + }); + + expect(result).to.have.property('currentStrategy', 'Strategy 1'); + expect(result).to.have.property('name', 'Service1'); + expect(result).to.have.property('opMode').to.deep.equal({ + state: 'automatic', + source: 'external' + }); + + let stateChangeCount = 0; + service.eventEmitter.on('state', () => { + stateChangeCount++; + }); + + service.execute(ServiceCommand.start); + await waitForStateChange(service, 'STARTING'); + await waitForStateChange(service, 'EXECUTE'); + + service.execute(ServiceCommand.restart); + await waitForStateChange(service, 'STARTING'); + await waitForStateChange(service, 'EXECUTE'); + + service.execute(ServiceCommand.stop); + await waitForStateChange(service, 'STOPPING'); + await waitForStateChange(service, 'STOPPED'); + + service.execute(ServiceCommand.reset); + await waitForStateChange(service, 'IDLE'); + + service.execute(ServiceCommand.start); + await waitForStateChange(service, 'STARTING'); + await waitForStateChange(service, 'EXECUTE'); + + service.execute(ServiceCommand.pause); + await waitForStateChange(service, 'PAUSING'); + await waitForStateChange(service, 'PAUSED'); + + service.execute(ServiceCommand.resume); + await waitForStateChange(service, 'RESUMING'); + await waitForStateChange(service, 'EXECUTE'); + + service.execute(ServiceCommand.complete); + await waitForStateChange(service, 'COMPLETING'); + await waitForStateChange(service, 'COMPLETED'); + + service.execute(ServiceCommand.abort); + await waitForStateChange(service, 'ABORTING'); + await waitForStateChange(service, 'ABORTED'); + + service.execute(ServiceCommand.reset); + await waitForStateChange(service, 'IDLE'); + + service.execute(ServiceCommand.start); + await waitForStateChange(service, 'STARTING'); + await waitForStateChange(service, 'EXECUTE'); + + service.execute(ServiceCommand.complete); + await waitForStateChange(service, 'COMPLETING'); + await waitForStateChange(service, 'COMPLETED'); + + expect(stateChangeCount).to.equal(22); + }).timeout(6000).slow(4000); + }); }); diff --git a/test/model/dataAssembly/DataAssembly.spec.ts b/test/model/dataAssembly/DataAssembly.spec.ts new file mode 100644 index 00000000..0ba413fb --- /dev/null +++ b/test/model/dataAssembly/DataAssembly.spec.ts @@ -0,0 +1,296 @@ +/* + * MIT License + * + * Copyright (c) 2019 Markus Graube , + * Chair for Process Control Systems, Technische Universität Dresden + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +import {ModuleOptions, OpcUaNodeOptions} from '@p2olab/polaris-interface'; +import * as chai from 'chai'; +import * as chaiAsPromised from 'chai-as-promised'; +import * as fs from 'fs'; +import {isAutomaticState, isManualState, isOffState, OpMode, opModetoJson} from '../../../src/model/core/enum'; +import {OpcUaConnection} from '../../../src/model/core/OpcUaConnection'; +import {AdvAnaOp, AnaServParam, ExtAnaOp, ExtIntAnaOp} from '../../../src/model/dataAssembly/AnaOp'; +import {AnaView} from '../../../src/model/dataAssembly/AnaView'; +import {DataAssembly} from '../../../src/model/dataAssembly/DataAssembly'; +import {DataAssemblyFactory} from '../../../src/model/dataAssembly/DataAssemblyFactory'; +import {ServiceControl} from '../../../src/model/dataAssembly/ServiceControl'; +import {StrView} from '../../../src/model/dataAssembly/Str'; +import {ModuleTestServer} from '../../../src/moduleTestServer/ModuleTestServer'; +import {TestServerVariable} from '../../../src/moduleTestServer/ModuleTestVariable'; + +chai.use(chaiAsPromised); +const expect = chai.expect; + +describe('DataAssembly', () => { + + describe('static', () => { + + it('should fail with missing parameters', () => { + expect(() => new DataAssembly(undefined, undefined)).to.throw(); + const opcUaNode: OpcUaNodeOptions = { + namespace_index: 'CODESYSSPV3/3S/IecVarAccess', + node_id: 'i=12', + data_type: 'Float' + }; + expect(() => new DataAssembly({ + name: 'name', + communication: { + TagName: opcUaNode as OpcUaNodeOptions, + TagDescription: opcUaNode as OpcUaNodeOptions, + OSLevel: opcUaNode as OpcUaNodeOptions, + WQC: null, + access: 'read' + } as any, + interface_class: 'analogitem' + }, undefined) + ).to.throw(); + }); + + it('should fail without provided module', async () => { + expect(() => DataAssemblyFactory.create( + {name: 'test', interface_class: 'none', communication: null}, null) + ).to.throw(/No module for data assembly/); + + }); + + it('should create ServiceControl', async () => { + const da1 = DataAssemblyFactory.create({ + name: 'serviceControl1', + interface_class: 'ServiceControl', + communication: { + OSLevel: null, + TagDescription: null, + TagName: {}, + WQC: null, + CommandMan: { + nodeId: 'sdf' + }, + CommandExt: {} + } as any + }, new OpcUaConnection(null, null)); + expect(da1 instanceof ServiceControl).to.equal(true); + }); + + it('should create AnaView', async () => { + const moduleCifJson = JSON.parse(fs.readFileSync('assets/modules/module_cif.json').toString()).modules[0]; + + const daJson1 = moduleCifJson.process_values.find((d) => d.name === 'Test_AnaView.L004'); + const daJson2 = moduleCifJson.process_values.find((d) => d.name === 'Sensoren.L001'); + + const da1 = DataAssemblyFactory.create(daJson1, new OpcUaConnection(null, null)); + const da2 = DataAssemblyFactory.create(daJson2, new OpcUaConnection(null, null)); + + expect(da1 instanceof AnaView).to.equal(true); + expect(da1 instanceof ExtIntAnaOp).to.equal(false); + expect(da1 instanceof AdvAnaOp).to.equal(false); + + expect(da1 instanceof AnaView).to.equals(true); + if (da1 instanceof AnaView) { + expect(da1.communication.OSLevel).to.have.property('access', 'write'); + expect(da1.communication.V).to.have.property('access', 'read'); + expect(da1.communication.VSclMin).to.have.property('value', 0); + expect(da1.communication.VSclMax).to.have.property('value', 35.5); + expect(da1.communication.V).to.have.property('nodeId', + '|var|WAGO 750-8202 PFC200 2ETH RS.Application.Test_AnaView.L001_PV.rPV'); + expect(da1.communication.VUnit).to.have.property('value', 1038); + expect(da1.getUnit()).to.equal('L'); + + expect(da1.toJson()).to.deep.equal({ + name: 'Test_AnaView.L004', + min: 0, + max: 35.5, + value: undefined, + unit: 'L', + type: 'number', + readonly: true + }); + + da1.communication.V.value = 12.3; + + expect(da1.toJson()).to.deep.equal({ + name: 'Test_AnaView.L004', + min: 0, + max: 35.5, + value: 12.3, + unit: 'L', + type: 'number', + readonly: true + }); + } + + expect(da2 instanceof AnaView).to.equals(true); + if (da2 instanceof AnaView) { + expect(da2.communication.VSclMin).to.have.property('value', 0); + expect(da2.communication.VSclMax).to.have.property('value', 30); + expect(da2.communication.V).to.have.property('nodeId', + '|var|WAGO 750-8202 PFC200 2ETH RS.Application.Sensoren.L001.V'); + expect(da2.communication.VUnit).to.have.property('value', 1038); + expect(da2.getUnit()).to.equal('L'); + } + }); + + it('should create AnaServParam', async () => { + const moduleJsonDosierer = + JSON.parse(fs.readFileSync('assets/modules/module_dosierer_1.1.0.json').toString()).modules[0]; + const daJson = moduleJsonDosierer.services[0].strategies[1].parameters[0]; + const da = DataAssemblyFactory.create(daJson as any, new OpcUaConnection(null, null)); + + expect(da instanceof ExtAnaOp).to.equal(true); + expect(da instanceof ExtIntAnaOp).to.equal(true); + expect(da instanceof AdvAnaOp).to.equal(false); + expect(da instanceof AnaServParam).to.equal(true); + + if (da instanceof AnaServParam) { + expect(da.communication.VOut).to.have.property('nodeId', + '|var|WAGO 750-8202 PFC200 2ETH RS.App_Dosing.Services.Fill.SetVolume.VOut'); + expect(da.communication.VInt).to.have.property('nodeId', + '|var|WAGO 750-8202 PFC200 2ETH RS.App_Dosing.Services.Fill.SetVolume.VInt'); + expect(da.communication.VMin).to.have.property('nodeId', + '|var|WAGO 750-8202 PFC200 2ETH RS.App_Dosing.Services.Fill.SetVolume.VMin'); + expect(da.communication.VMax).to.have.property('nodeId', + '|var|WAGO 750-8202 PFC200 2ETH RS.App_Dosing.Services.Fill.SetVolume.VMax'); + expect(da.WQC).to.have.property('nodeId', + '|var|WAGO 750-8202 PFC200 2ETH RS.App_Dosing.Services.Fill.SetVolume.WQC'); + expect(da.communication.OpMode).to.have.property('nodeId', + '|var|WAGO 750-8202 PFC200 2ETH RS.App_Dosing.Services.Fill.SetVolume.OpMode.binary'); + expect(da.communication.VExt).to.have.property('nodeId', + '|var|WAGO 750-8202 PFC200 2ETH RS.App_Dosing.Services.Fill.SetVolume.VExt'); + expect(da.communication.VSclMax).to.have.property('nodeId', + '|var|WAGO 750-8202 PFC200 2ETH RS.App_Dosing.Services.Fill.SetVolume.VSclMax'); + expect(da.communication.VSclMin).to.have.property('nodeId', + '|var|WAGO 750-8202 PFC200 2ETH RS.App_Dosing.Services.Fill.SetVolume.VSclMin'); + } + }); + }); + + describe('with testserver', () => { + + let moduleServer: ModuleTestServer; + let connection: OpcUaConnection; + + before(async () => { + moduleServer = new ModuleTestServer(); + await moduleServer.start(); + + connection = new OpcUaConnection('CIF', 'opc.tcp://127.0.0.1:4334/ModuleTestServer'); + await connection.connect(); + }); + + after(async () => { + await connection.disconnect(); + await moduleServer.shutdown(); + }); + + it('should create ExtIntAnaOp', async () => { + const daJson = JSON.parse(fs.readFileSync('assets/modules/module_testserver_1.0.0.json').toString()) + .modules[0].services[0].strategies[0].parameters[0]; + const da = DataAssemblyFactory.create(daJson as any, connection) as ExtIntAnaOp; + + await da.subscribe(); + expect(da.name).to.equal('Parameter001'); + expect(da instanceof ExtAnaOp).to.equal(true); + expect(da instanceof ExtIntAnaOp).to.equal(true); + expect(da instanceof AdvAnaOp).to.equal(false); + expect(da.communication.OpMode.value).to.equal(0); + + await da.waitForOpModeToPassSpecificTest(isOffState); + let opMode = da.getOpMode(); + expect(opModetoJson(opMode)).to.deep.equal({state: 'off', source: 'internal'}); + + (moduleServer.services[0].parameter[0] as TestServerVariable).opMode = OpMode.stateManAct; + await da.waitForOpModeToPassSpecificTest(isManualState); + opMode = da.getOpMode(); + expect(opModetoJson(opMode)).to.deep.equal({state: 'manual', source: 'internal'}); + + (moduleServer.services[0].parameter[0] as TestServerVariable).opMode = OpMode.stateAutAct; + await da.waitForOpModeToPassSpecificTest(isAutomaticState); + opMode = da.getOpMode(); + expect(opModetoJson(opMode)).to.deep.equal({state: 'automatic', source: 'internal'}); + + if (da instanceof ExtIntAnaOp) { + expect(da.communication.VOut).to.have.property('nodeId', 'Service1.Parameter1.V'); + expect(da.communication.VOut).to.have.property('value', 20); + const json = da.toJson(); + expect(json).to.have.property('name', 'Parameter001'); + expect(json).to.have.property('readonly', false); + expect(json).to.have.property('type', 'number'); + expect(json).to.have.property('value', 20); + expect(json).to.have.property('min'); + expect(json).to.have.property('max'); + expect(json).to.have.property('unit'); + } + }).timeout(5000); + + it('should create StrView', async () => { + const daJson = { + name: 'ErrorMsg', + interface_class: 'StrView', + communication: + { + WQC: + { + namespace_index: 'urn:NodeOPCUA-Server-default', + node_id: 'Service1.ErrorMsg.WQC', + data_type: 'Byte' + }, + OSLevel: + { + namespace_index: 'urn:NodeOPCUA-Server-default', + node_id: 'Service1.ErrorMsg.OSLevel', + data_type: 'Byte' + }, + Text: + { + namespace_index: 'urn:NodeOPCUA-Server-default', + node_id: 'Service1.ErrorMsg.Text', + data_type: 'String' + } + } + }; + const da = DataAssemblyFactory.create(daJson as any, connection); + + expect(da instanceof ExtAnaOp).to.equal(false); + expect(da instanceof ExtIntAnaOp).to.equal(false); + expect(da instanceof AdvAnaOp).to.equal(false); + + expect(da instanceof StrView).to.equal(true); + + if (da instanceof StrView) { + await da.subscribe(); + expect(da.OSLevel).to.have.property('dataType', 'UInt32'); + expect(da.OSLevel).to.have.property('namespaceIndex', 'urn:NodeOPCUA-Server-default'); + expect(da.OSLevel).to.have.property('nodeId', 'Service1.ErrorMsg.OSLevel'); + + expect(da.Text).to.have.property('nodeId', 'Service1.ErrorMsg.Text'); + expect(da.Text).to.have.property('value', 'initial value'); + + const json = da.toJson(); + expect(json).to.have.property('name', 'ErrorMsg'); + expect(json).to.have.property('readonly', true); + expect(json).to.have.property('type', 'string'); + expect(json).to.have.property('value', 'initial value'); + } + }); + }); + +}); diff --git a/test/model/dataAssembly/DataItem.spec.ts b/test/model/dataAssembly/DataItem.spec.ts new file mode 100644 index 00000000..4306c301 --- /dev/null +++ b/test/model/dataAssembly/DataItem.spec.ts @@ -0,0 +1,205 @@ +/* + * MIT License + * + * Copyright (c) 2019 Markus Graube , + * Chair for Process Control Systems, Technische Universität Dresden + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +import * as chai from 'chai'; +import * as chaiAsPromised from 'chai-as-promised'; +import {OpcUaConnection} from '../../../src/model/core/OpcUaConnection'; +import {OpcUaDataItem} from '../../../src/model/dataAssembly/DataItem'; +import {ModuleTestServer} from '../../../src/moduleTestServer/ModuleTestServer'; + +chai.use(chaiAsPromised); +const expect = chai.expect; + +describe('DataItem', () => { + + describe('static', () => { + + const connection = new OpcUaConnection(null, null); + + it('should reject construction with missing options', () => { + const di = OpcUaDataItem.fromOptions(null, null, 'read', 'string'); + expect(di.access).to.equal('read'); + }); + + it('should work with float', () => { + const di = OpcUaDataItem.fromOptions( + {value: 1.2, data_type: 'Float', node_id: 'test', namespace_index: 'test2'}, null, 'read', 'number'); + expect(di.value).to.equal(1.2); + }); + + it('should work with float conversion', () => { + const di = OpcUaDataItem.fromOptions( + {value: '1.2', data_type: 'Float', node_id: 'test', namespace_index: 'test2'}, null, 'read', 'number'); + expect(di.value).to.equal(1.2); + }); + + it('should work with value = 0', () => { + const di = OpcUaDataItem.fromOptions( + {value: 0.0, data_type: 'Float', node_id: 'test', namespace_index: 'test2'}, null, 'read', 'number'); + expect(di.value).to.equal(0); + }); + + it('should work with negative value', () => { + const di = OpcUaDataItem.fromOptions( + {value: -2, data_type: 'Float', node_id: 'test', namespace_index: 'test2'}, null, 'read', 'number'); + expect(di.value).to.equal(-2.0); + }); + + it('should work with null value', () => { + const di = OpcUaDataItem.fromOptions( + {value: null, data_type: 'Float', node_id: 'test', namespace_index: 'test2'}, null, 'read', 'number'); + expect(di.value).to.equal(null); + expect(di.access).to.equal('read'); + }); + + it('should work with undefined value', () => { + const di = OpcUaDataItem.fromOptions( + { + value: undefined, + data_type: 'Float', + node_id: 'test', + namespace_index: 'test2' + }, null, 'write', 'number'); + expect(di.value).to.equal(undefined); + expect(di.access).to.equal('write'); + }); + + it('should work with string conversion', () => { + const di = OpcUaDataItem.fromOptions( + {value: 1.2, data_type: 'Float', node_id: 'test', namespace_index: 'test2'}, null, 'read', 'string'); + expect(di.value).to.equal('1.2'); + }); + + it('should reject working when not connected', async () => { + const di = OpcUaDataItem.fromOptions( + { + namespace_index: 'urn:NodeOPCUA-Server-default', + node_id: 'Service1.Parameter1.VExt', + data_type: 'Double' + }, connection, 'write', 'number'); + await expect(di.read()).to.be.rejectedWith('namespace'); + }); + }); + + describe('with testserver', () => { + + let moduleServer: ModuleTestServer; + let connection: OpcUaConnection; + + before(async function() { + this.timeout(5000); + moduleServer = new ModuleTestServer(); + await moduleServer.start(); + moduleServer.startSimulation(); + + connection = new OpcUaConnection('CIF', 'opc.tcp://127.0.0.1:4334/ModuleTestServer'); + await connection.connect(); + }); + + after(async () => { + await connection.disconnect(); + moduleServer.stopSimulation(); + await moduleServer.shutdown(); + }); + + it('should subscribe', async () => { + const di = OpcUaDataItem.fromOptions( + { + namespace_index: 'urn:NodeOPCUA-Server-default', + node_id: 'Service1.ErrorMsg.Text', + data_type: 'String' + }, connection, 'write', 'string'); + + const a = await di.subscribe(); + expect(di.value).to.equal('initial value'); + + await new Promise((resolve) => a.on('changed', resolve)); + }); + + it('should subscribe, disconnect and resubscribe', async () => { + const di = OpcUaDataItem.fromOptions( + { + namespace_index: 'urn:NodeOPCUA-Server-default', + node_id: 'Service1.ErrorMsg.Text', + data_type: 'String' + }, connection, 'write', 'string'); + + const a = await di.subscribe(); + await new Promise((resolve) => a.on('changed', resolve)); + + await connection.disconnect(); + await connection.connect(); + + const a1 = await di.subscribe(); + await new Promise((resolve) => a1.on('changed', resolve)); + }); + + it('should write', async () => { + const di = OpcUaDataItem.fromOptions( + { + namespace_index: 'urn:NodeOPCUA-Server-default', + node_id: 'Service1.Parameter1.VExt', + data_type: 'Double' + }, connection, 'write', 'number'); + await di.write(22.0); + + const value = await di.read(); + expect(value).to.be.equal(22.0); + }); + + it('should fail while writing with wrong datatype', async () => { + const di = OpcUaDataItem.fromOptions( + { + namespace_index: 'urn:NodeOPCUA-Server-default', + node_id: 'Service1.Parameter1.VExt', + data_type: 'Float' + }, connection, 'write', 'number'); + + await expect(di.write(22)).to.be.rejectedWith('value supplied for the attribute is not of the same type'); + }); + + it('should fail while writing with wrong datatype 2', async () => { + const di = OpcUaDataItem.fromOptions( + { + namespace_index: 'urn:NodeOPCUA-Server-default', + node_id: 'Service1.Parameter1.VExt', + data_type: 'abc' + }, connection, 'write', 'number'); + + await expect(di.write(22)).to.be.rejectedWith('datatype abc must be registered'); + }); + + it('should fail while writing with wrong datatype 3', async () => { + const di = OpcUaDataItem.fromOptions( + { + namespace_index: 'urn:NodeOPCUA-Server-default', + node_id: 'Service1.Parameter1.VExt', + data_type: 'Byte' + }, connection, 'write', 'number'); + + await expect(di.write(22)).to.be.rejectedWith('value supplied for the attribute is not of the same type'); + }); + }); +}); diff --git a/test/model/recipe/Condition.spec.ts b/test/model/recipe/Condition.spec.ts index 742a10cc..3123cd2c 100644 --- a/test/model/recipe/Condition.spec.ts +++ b/test/model/recipe/Condition.spec.ts @@ -24,15 +24,23 @@ */ import {ConditionType} from '@p2olab/polaris-interface'; -import {expect} from 'chai'; +import * as chai from 'chai'; +import * as chaiAsPromised from 'chai-as-promised'; import * as fs from 'fs'; -import {OPCUAServer} from 'node-opcua-server'; -import {timeout} from 'promise-timeout'; import * as delay from 'timeout-as-promise'; +import {catCondition} from '../../../src/config/logging'; +import {ConditionFactory} from '../../../src/model/condition/ConditionFactory'; +import {ExpressionCondition} from '../../../src/model/condition/ExpressionCondition'; +import {TimeCondition} from '../../../src/model/condition/TimeCondition'; +import {TrueCondition} from '../../../src/model/condition/TrueCondition'; import {ServiceState} from '../../../src/model/core/enum'; import {Module} from '../../../src/model/core/Module'; -import {Condition, ExpressionCondition, TimeCondition} from '../../../src/model/recipe/Condition'; +import {TestServerNumericVariable} from '../../../src/moduleTestServer/ModuleTestNumericVariable'; import {ModuleTestServer} from '../../../src/moduleTestServer/ModuleTestServer'; +import {waitForVariableChange} from '../../helper'; + +chai.use(chaiAsPromised); +const expect = chai.expect; /** * Test for [[Condition]] @@ -40,6 +48,16 @@ import {ModuleTestServer} from '../../../src/moduleTestServer/ModuleTestServer'; describe('Condition', () => { describe('without test server', () => { + + it('should provide TrueCondition with no type', () => { + expect(ConditionFactory.create(null, undefined)).to.instanceOf(TrueCondition); + }); + + it('should provide TrueCondition with wrong type', () => { + const cond = ConditionFactory.create({type: 'test'} as any, []); + expect(cond).to.instanceOf(TrueCondition); + }); + it('should listen to a time condition of 0.1s', (done) => { const condition = new TimeCondition({type: ConditionType.time, duration: 0.1}); @@ -56,7 +74,7 @@ describe('Condition', () => { }); it('should listen to an AND condition of two time conditions', async () => { - const condition = Condition.create({ + const condition = ConditionFactory.create({ type: ConditionType.and, conditions: [ {type: ConditionType.time, duration: 0.2}, @@ -80,7 +98,7 @@ describe('Condition', () => { }); it('should listen to a OR condition of two time conditions', async () => { - const condition = Condition.create({ + const condition = ConditionFactory.create({ type: ConditionType.or, conditions: [ {type: ConditionType.time, duration: 0.5}, @@ -106,7 +124,7 @@ describe('Condition', () => { }); it('should listen to a NOT condition', async () => { - const condition = Condition.create({ + const condition = ConditionFactory.create({ type: ConditionType.not, condition: {type: ConditionType.time, duration: 0.1} }, undefined); @@ -117,10 +135,14 @@ describe('Condition', () => { expect(condition).to.have.property('fulfilled', true); await delay(100); expect(condition).to.have.property('fulfilled', false); + + expect(condition.getUsedModules().size).to.equal(0); + + condition.clear(); }); it('should fail with wrong parameter', () => { - expect(() => Condition.create({type: ConditionType.time, duration: -10}, undefined)) + expect(() => ConditionFactory.create({type: ConditionType.time, duration: -10}, undefined)) .to.throw(); }); @@ -128,7 +150,8 @@ describe('Condition', () => { it('should work with simple expression', async () => { const expr = new ExpressionCondition({type: ConditionType.expression, expression: '4>3'}); - const value = await expr.getValue(); + expr.listen(); + const value = expr.getValue(); expect(value).to.equal(true); }); @@ -137,7 +160,7 @@ describe('Condition', () => { .modules[0]; const module = new Module(moduleJson); - const expr = Condition.create({ + const expr = ConditionFactory.create({ type: ConditionType.expression, expression: 'sin(a)^2 + cos(CIF.Variable001)^2 < 0.5', scope: [ @@ -149,8 +172,7 @@ describe('Condition', () => { } ] }, [module]) as ExpressionCondition; - - expect(() => expr.getValue()).to.throw; + expect(() => expr.getValue()).to.throw('not connected'); }); }); @@ -160,11 +182,13 @@ describe('Condition', () => { describe('with ModuleTestServer', () => { let moduleServer: ModuleTestServer; let module: Module; + let var0: TestServerNumericVariable; - before(async function () { - this.timeout(4000); + beforeEach(async function() { + this.timeout(10000); moduleServer = new ModuleTestServer(); await moduleServer.start(); + var0 = moduleServer.variables[0] as TestServerNumericVariable; const moduleJson = JSON.parse(fs.readFileSync('assets/modules/module_testserver_1.0.0.json', 'utf8')) .modules[0]; @@ -173,13 +197,13 @@ describe('Condition', () => { await module.connect(); }); - after(async () => { + afterEach(async () => { await module.disconnect(); await moduleServer.shutdown(); }); it('specialized as VariableCondition should work', async () => { - const condition = Condition.create({ + const condition = ConditionFactory.create({ type: ConditionType.variable, module: 'CIF', dataAssembly: 'Variable001', @@ -189,32 +213,31 @@ describe('Condition', () => { }, [module]); condition.listen(); - - expect(module.services[0]).to.have.property('name', 'Service1'); expect(condition).to.have.property('fulfilled', false); - moduleServer.variables[0].v = 22; + var0.v = 22; + await waitForVariableChange(module, 'Variable001', 22); expect(condition).to.have.property('fulfilled', false); - moduleServer.variables[0].v = 26; + var0.v = 26; await new Promise((resolve) => { - condition.once('stateChanged', (state) => { + condition.once('stateChanged', () => { resolve(); } ); }); expect(condition).to.have.property('fulfilled', true); condition.clear(); - moduleServer.variables[0].v = 24.4; + var0.v = 24.4; expect(condition).to.have.property('fulfilled', undefined); - moduleServer.variables[0].v = 37; + var0.v = 37; expect(condition).to.have.property('fulfilled', undefined); - }); + }).timeout(5000); it('specialized as StateCondition should work', async function() { this.timeout(5000); - const condition = Condition.create({ + const condition = ConditionFactory.create({ type: ConditionType.state, module: 'CIF', service: 'Service1', @@ -260,7 +283,7 @@ describe('Condition', () => { }); it('should not react on a closed condition', async () => { - const condition = Condition.create({ + const condition = ConditionFactory.create({ type: ConditionType.state, module: 'CIF', service: 'Service1', @@ -269,19 +292,17 @@ describe('Condition', () => { condition.listen(); moduleServer.services[0].varStatus = ServiceState.IDLE; - await delay(150); + await delay(50); expect(condition).to.have.property('fulfilled', false); - condition.on('stateChanged', () => console.log('state changed')); + condition.on('stateChanged', () => catCondition.debug('state changed')); expect(condition.listenerCount('stateChanged')).to.equal(1); condition.clear(); expect(condition).to.have.property('fulfilled', undefined); expect(condition.listenerCount('stateChanged')).to.equal(0); - await delay(50); condition.listen(); - await delay(200); expect(condition).to.have.property('fulfilled', false); moduleServer.services[0].varStatus = ServiceState.COMPLETED; @@ -295,64 +316,73 @@ describe('Condition', () => { } ); }); condition.clear(); - }); describe('ExpressionCondition', () => { it('should work with simple server expression', async () => { - const expr = new ExpressionCondition({type: ConditionType.expression, expression: 'CIF.Variable001.V>10'}, [module]); + const expr = new ExpressionCondition( + {type: ConditionType.expression, expression: 'CIF.Variable001.V>10'}, [module]); expr.listen(); - moduleServer.variables[0].v = 0; + expect(expr.getUsedModules().size).to.equal(1); + + var0.v = 0; + await waitForVariableChange(module, 'Variable001', 0); expect(expr).to.have.property('fulfilled', false); - let value = await expr.getValue(); + let value = expr.getValue(); expect(value).to.equal(false); - moduleServer.variables[0].v = 11; - await new Promise((resolve) => { - expr.once('stateChanged', (state) => { - resolve(); - } ); - }); - expect(expr).to.have.property('fulfilled', true); - value = await expr.getValue(); + var0.v = 11; + await waitForVariableChange(module, 'Variable001', 11); + value = expr.getValue(); expect(value).to.equal(true); expect(expr).to.have.property('fulfilled', true); - moduleServer.variables[0].v = 8; - value = await expr.getValue(); + var0.v = 8; + await Promise.all([ + new Promise((resolve) => expr.once('stateChanged', resolve)), + waitForVariableChange(module, 'Variable001', 8) + ]); + value = expr.getValue(); expect(value).to.equal(false); - await new Promise((resolve) => { - expr.once('stateChanged', (state) => { - resolve(); - } ); - }); expect(expr).to.have.property('fulfilled', false); expr.clear(); - moduleServer.variables[0].v = 12; - value = await expr.getValue(); + var0.v = 12; + expr.once('stateChanged', () => { throw new Error('State has changed after it was cleared'); }); + await waitForVariableChange(module, 'Variable001', 12); + value = expr.getValue(); expect(value).to.equal(true); expect(expr).to.have.property('fulfilled', undefined); - }); + }).timeout(5000); it('should work with semi-complex expression', async () => { - moduleServer.variables[0].v = 3.1; - const expr: ExpressionCondition = Condition.create({ + const expr: ExpressionCondition = ConditionFactory.create({ type: ConditionType.expression, expression: 'cos(CIF.Variable001.V)^2 > 0.9' }, [module]) as ExpressionCondition; - let value = await expr.getValue(); + expr.listen(); + + var0.v = 3.1; + await Promise.all([ + new Promise((resolve) => expr.once('stateChanged', resolve)), + waitForVariableChange(module, 'Variable001', 3.1) + ]); + let value = expr.getValue(); expect(value).to.equal(true); - moduleServer.variables[0].v = 0.7; - value = await expr.getValue(); + var0.v = 0.7; + await Promise.all([ + new Promise((resolve) => expr.once('stateChanged', resolve)), + waitForVariableChange(module, 'Variable001', 0.7) + ]); + value = expr.getValue(); expect(value).to.equal(false); - }); + }).timeout(5000); it('should work with complex expression', async () => { - const expr = Condition.create({ + const expr = ConditionFactory.create({ type: ConditionType.expression, expression: 'sin(a)^2 + cos(CIF.Variable001)^2 < 0.5', scope: [ @@ -364,9 +394,11 @@ describe('Condition', () => { } ] }, [module]) as ExpressionCondition; - moduleServer.variables[0].v = 0.7; - const value = await expr.getValue(); + var0.v = 0.7; + await new Promise((resolve) => module.once('variableChanged', resolve)); + + const value = expr.getValue(); expect(value).to.equal(false); }); diff --git a/test/model/recipe/Operation.spec.ts b/test/model/recipe/Operation.spec.ts index 2cea5063..e9c16972 100644 --- a/test/model/recipe/Operation.spec.ts +++ b/test/model/recipe/Operation.spec.ts @@ -31,6 +31,7 @@ import {ClientSession, OPCUAClient} from 'node-opcua-client'; import {OPCUAServer} from 'node-opcua-server'; import * as delay from 'timeout-as-promise'; import {Module} from '../../../src/model/core/Module'; +import {Service} from '../../../src/model/core/Service'; import {Operation} from '../../../src/model/recipe/Operation'; import {ModuleTestServer} from '../../../src/moduleTestServer/ModuleTestServer'; import {waitForStateChange} from '../../helper'; @@ -40,28 +41,117 @@ const expect = chai.expect; describe('Operation', () => { + context('constructor', () => { + + let module; + + before(() => { + const moduleJson = JSON.parse(fs.readFileSync('assets/modules/module_testserver_1.0.0.json').toString()) + .modules[0]; + module = new Module(moduleJson); + }); + + it('should fail with missing options', () => { + expect(() => new Operation(null, [])).to.throw(); + }); + + it('should fail with missing modules', () => { + expect(() => new Operation({service: null, command: null}, null)).to.throw('No modules specified'); + }); + + it('should fail with missing 2', () => { + expect(() => new Operation({service: null, command: null}, [])).to.throw(); + }); + + it('should fail with missing 3', () => { + expect(() => new Operation({ + module: 'test', + service: null, + command: null + }, [module])).to.throw('Could not find'); + }); + + it('should fail with wrong service name', () => { + expect(() => new Operation({ + module: 'CIF', + service: 'test', + command: null + }, [module])).to.throw('not found'); + }); + + it('should fail with wrong strategy name', () => { + expect(() => new Operation({ + module: 'CIF', + service: 'Service1', + strategy: 'dd', + command: null + }, [module])).to.throw('not be found'); + }); + + it('should work', () => { + const op = new Operation({ + module: 'CIF', service: 'Service1', command: 'start' as any, + parameter: [{name: 'Parameter001', value: 3}] + }, [module]); + expect(op).to.have.property('module'); + expect(op.json()).to.deep.equal({ + command: 'start', + module: 'CIF', + parameter: [ + { + name: 'Parameter001', + value: 3 + } + ], + service: 'Service1', + state: undefined, + strategy: 'Strategy 1' + }); + }); + }); + describe('OPC UA server mockup', () => { let moduleServer: ModuleTestServer; + let module: Module; + let service: Service; - before(async () => { + beforeEach(async function () { + this.timeout(5000); moduleServer = new ModuleTestServer(); await moduleServer.start(); + + const moduleJson = JSON.parse(fs.readFileSync('assets/modules/module_testserver_1.0.0.json').toString()) + .modules[0]; + module = new Module(moduleJson); + service = module.services[0]; + + await module.connect(); }); - after(async () => { + afterEach(async () => { + await module.disconnect(); await moduleServer.shutdown(); }); it('should try execute operation until it works', async () => { + const operation = new Operation({ + service: 'Service1', + command: 'complete' as ServiceCommand + }, [module]); - const moduleJson = JSON.parse(fs.readFileSync('assets/modules/module_testserver_1.0.0.json').toString()) - .modules[0]; - const module = new Module(moduleJson); - const service = module.services[0]; + operation.execute(); + await delay(300); + expect(operation.json()).to.have.property('state', 'executing'); + // set precondition for operation + service.execute(ServiceCommand.start); - await module.connect(); + await waitForStateChange(service, 'COMPLETED', 3000); + expect(operation.json()).to.have.property('state', 'completed'); + }).timeout(10000); + + it('should try execute operation until it is stopped', async () => { const operation = new Operation({ service: 'Service1', command: 'complete' as ServiceCommand @@ -69,9 +159,23 @@ describe('Operation', () => { operation.execute(); await delay(600); - service.execute(ServiceCommand.start); + expect(operation.json()).to.have.property('state', 'executing'); + operation.stop(); + expect(operation.json()).to.have.property('state', 'aborted'); - await waitForStateChange(service, 'COMPLETED', 3000); + }).timeout(10000); + + it('should try execute operation until timeout', async () => { + const operation = new Operation({ + service: 'Service1', + command: 'complete' as ServiceCommand + }, [module]); + + operation.execute(); + await delay(3000); + expect(operation.json()).to.have.property('state', 'executing'); + await delay(2050); + expect(operation.json()).to.have.property('state', 'aborted'); }).timeout(10000); diff --git a/test/model/recipe/Parameter.spec.ts b/test/model/recipe/Parameter.spec.ts index 443559e6..2289482d 100644 --- a/test/model/recipe/Parameter.spec.ts +++ b/test/model/recipe/Parameter.spec.ts @@ -28,6 +28,7 @@ import * as fs from 'fs'; import {Module} from '../../../src/model/core/Module'; import {Service} from '../../../src/model/core/Service'; import {Parameter} from '../../../src/model/recipe/Parameter'; +import {TestServerNumericVariable} from '../../../src/moduleTestServer/ModuleTestNumericVariable'; import {ModuleTestServer} from '../../../src/moduleTestServer/ModuleTestServer'; describe('Parameter', () => { @@ -43,12 +44,11 @@ describe('Parameter', () => { service = module.services[0]; }); - it('should load', () => { - const param = new Parameter({ + it('should load', () => new Parameter({ name: 'var1', value: 3 - }, service); - }); + }, service) + ); it('should load with expression', async () => { const param = new Parameter({ @@ -67,23 +67,46 @@ describe('Parameter', () => { }); it('should fail with wrong parameter name', () => { - expect(() => { - const param = new Parameter({ + expect(() => new Parameter({ name: 'non-existing-parameter', value: 3 - }, service); - }).to.throw(); + }, service) + ).to.throw(); + }); + + it('should provide 0 with empty expression', async () => { + const param = new Parameter({ + name: 'var1', + value: '' + }, service); + expect(await param.getValue()).to.equal(0); + }); + + it('should provide 0 with no expression', async () => { + const param = new Parameter({ + name: 'var1', + value: null + }, service); + expect(await param.getValue()).to.equal(0); + }); + + it('should provide 0 with non valid expression', async () => { + expect(() => new Parameter({ + name: 'var1', + value: 'ssd+4335.,dfgölkp94' + }, service)).to.throw('Parsing error'); }); }); context('with ModuleTestServer', () => { let service: Service; let module: Module; - let moduleServer: ModuleTestServer; + let moduleTestServer: ModuleTestServer; - before(async () => { - moduleServer = new ModuleTestServer(); - await moduleServer.start(); + before(async function before() { + this.timeout(5000); + moduleTestServer = new ModuleTestServer(); + await moduleTestServer.start(); const moduleJson = JSON.parse(fs.readFileSync('assets/modules/module_testserver_1.0.0.json', 'utf8')) .modules[0]; module = new Module(moduleJson); @@ -93,7 +116,7 @@ describe('Parameter', () => { after(async () => { await module.disconnect(); - await moduleServer.shutdown(); + await moduleTestServer.shutdown(); }); it('should load with complex expression and given scopeArray', async () => { @@ -110,15 +133,22 @@ describe('Parameter', () => { ] }, service, undefined, [module]); - expect(await param.getValue()).to.be.closeTo(1, 0.01); + expect(param.scopeArray).to.have.lengthOf(2); + expect(param.scopeArray[1].getScopeValue()).to.deep.equal({ + CIF: { + Variable001: 20 + } + }); + expect(param.scopeArray[0].getScopeValue()).to.deep.equal({a: 20}); + expect(param.getValue()).to.be.closeTo(1, 0.01); }); - it('should load with complex expression with dataAssembly variables', async () => { + it('should load with complex expression with dataAssembly variables', () => { const param = new Parameter({ name: 'Parameter001', value: '2 * CIF.Variable001.V + CIF.Variable002 + Variable\\.003' }, service, undefined, [module]); - expect(await param.getValue()).to.be.greaterThan(0.01); + expect(param.getValue()).to.be.greaterThan(0.01); }); it('should update value on module', async () => { @@ -127,7 +157,8 @@ describe('Parameter', () => { value: '2 * 3' }, service, undefined, [module]); await param.updateValueOnModule(); - expect(moduleServer.services[0].parameter[0].vext).to.equal(6); + const param1Ext = moduleTestServer.services[0].parameter[0] as TestServerNumericVariable; + expect(param1Ext.vext).to.equal(6); const param2 = new Parameter({ name: 'Parameter002', @@ -135,7 +166,24 @@ describe('Parameter', () => { }, service, undefined, [module]); const value = await param2.getValue(); await param2.updateValueOnModule(); - expect(moduleServer.services[0].parameter[1].vext).to.equal(value); + const param2Ext = moduleTestServer.services[0].parameter[1] as TestServerNumericVariable; + expect(param2Ext.vext).to.equal(value); + }); + + it('should listen to dynamic parameter', async () => { + const param = new Parameter({ + name: 'Parameter001', + value: '2 * CIF.Variable001.V' + }, service, undefined, [module]); + expect(param.scopeArray[0].dataAssembly.subscriptionActive).to.equal(true); + expect(param.scopeArray[0].dataAssembly.name).to.equal('Variable001'); + expect(param.scopeArray[0].dataItem.value).to.equal(20); + + (moduleTestServer.variables[0] as TestServerNumericVariable).v = 10; + await new Promise((resolve) => { + param.listenToParameter().once('changed', () => resolve()); + }); + expect(param.getValue()).to.equal(20); }); }); diff --git a/test/model/recipe/Player.spec.ts b/test/model/recipe/Player.spec.ts index 0baa1b29..9f4ca583 100644 --- a/test/model/recipe/Player.spec.ts +++ b/test/model/recipe/Player.spec.ts @@ -27,11 +27,9 @@ import {RecipeState} from '@p2olab/polaris-interface'; import * as chai from 'chai'; import * as chaiAsPromised from 'chai-as-promised'; import * as fs from 'fs'; -import {ClientSession, OPCUAClient} from 'node-opcua-client'; -import {OPCUAServer} from 'node-opcua-server'; import {timeout} from 'promise-timeout'; import * as delay from 'timeout-as-promise'; -import {controlEnableToJson, ServiceState} from '../../../src/model/core/enum'; +import {ServiceState} from '../../../src/model/core/enum'; import {Module} from '../../../src/model/core/Module'; import {Player} from '../../../src/model/recipe/Player'; import {Recipe} from '../../../src/model/recipe/Recipe'; @@ -56,44 +54,24 @@ describe('Player', () => { await moduleServer.shutdown(); }); - it('should OPC UA server has been started', async () => { - const client = new OPCUAClient({ - endpoint_must_exist: false, - connectionStrategy: { - maxRetry: 10 - } - }); - - await client.connect('opc.tcp://localhost:4334/ModuleTestServer'); - const session: ClientSession = await client.createSession(); - - let result = await session.readVariableValue('ns=1;s=Service1.State'); - expect(result.value.value).to.equal(ServiceState.IDLE); - - moduleServer.services[0].varStatus = 8; - result = await session.readVariableValue('ns=1;s=Service1.State'); - expect(result.value.value).to.equal(ServiceState.STARTING); - - const result2 = await session.readVariableValue('ns=1;s=Service1.CommandEnable'); - const ce = controlEnableToJson(result2.value.value); - expect(ce).to.deep.equal({ - abort: true, - complete: false, - pause: false, - reset: false, - restart: false, - resume: false, - start: true, - stop: true, - unhold: false - }); - - const result3 = await await session.readVariableValue('ns=0;i=2255'); - expect(result3.value.value).to.deep.equal(['http://opcfoundation.org/UA/', - 'urn:NodeOPCUA-Server-default']); - - await client.disconnect(); - }); + it('should run a simple test recipe', async () => { + const moduleJson = JSON.parse(fs.readFileSync('assets/modules/module_testserver_1.0.0.json').toString()) + .modules[0]; + const module = new Module(moduleJson); + await module.connect(); + // now test recipe + const recipeJson = JSON.parse( + fs.readFileSync('assets/recipes/test/recipe_testserver_1.0.0.json').toString() + ); + const recipe = new Recipe(recipeJson, [module]); + const player = new Player(); + player.enqueue(recipe); + + player.start(); + await new Promise((resolve) => player.once('completed', resolve)); + + await module.disconnect(); + }).timeout(10000); it('should run a test recipe two times', async () => { @@ -103,8 +81,6 @@ describe('Player', () => { const service = module.services[0]; await module.connect(); - moduleServer.services[0].varStatus = ServiceState.IDLE; - await waitForStateChange(service, 'IDLE'); // now test recipe const recipeJson = JSON.parse( @@ -117,21 +93,21 @@ describe('Player', () => { player.enqueue(recipe); player.enqueue(recipe); - expect(service.status.value).to.equal(ServiceState.IDLE); + expect(service.state).to.equal(ServiceState.IDLE); player.start(); expect(player.status).to.equal(RecipeState.running); - waitForStateChange(service, 'STARTING'); + await waitForStateChange(service, 'STARTING', 2000); await waitForStateChange(service, 'EXECUTE'); - waitForStateChange(service, 'COMPLETING', 2000); + await waitForStateChange(service, 'COMPLETING', 2000); await waitForStateChange(service, 'COMPLETED', 2000); await waitForStateChange(service, 'IDLE'); // here the second run of the recipe should automatically start, since first recipe is finished - waitForStateChange(service, 'STARTING', 2000); + await waitForStateChange(service, 'STARTING', 2000); await waitForStateChange(service, 'EXECUTE', 2000); - waitForStateChange(service, 'COMPLETING', 2000); + await waitForStateChange(service, 'COMPLETING', 2000); await waitForStateChange(service, 'COMPLETED', 2000); await waitForStateChange(service, 'IDLE'); @@ -141,7 +117,7 @@ describe('Player', () => { player.reset(); await module.disconnect(); - }).timeout(10000).retries(3); + }).timeout(10000); it('should run a playlist while modifying it', async () => { @@ -150,8 +126,7 @@ describe('Player', () => { const module = new Module(moduleJson); const service = module.services[0]; - module.connect(); - await waitForStateChange(service, 'IDLE', 2000); + await module.connect(); // now test recipe const recipeJson = JSON.parse( @@ -166,17 +141,17 @@ describe('Player', () => { player.start(); expect(player.status).to.equal(RecipeState.running); - waitForStateChange(service, 'STARTING'); + await waitForStateChange(service, 'STARTING'); await waitForStateChange(service, 'EXECUTE'); - waitForStateChange(service, 'COMPLETING', 2000); + await waitForStateChange(service, 'COMPLETING', 2000); await waitForStateChange(service, 'COMPLETED', 2000); await waitForStateChange(service, 'IDLE'); // here the second run of the recipe should automatically start, since first recipe is finished - waitForStateChange(service, 'STARTING', 2000); + await waitForStateChange(service, 'STARTING', 2000); await waitForStateChange(service, 'EXECUTE', 2000); - waitForStateChange(service, 'COMPLETING', 2000); + await waitForStateChange(service, 'COMPLETING', 2000); await waitForStateChange(service, 'COMPLETED', 2000); await waitForStateChange(service, 'IDLE'); @@ -186,7 +161,7 @@ describe('Player', () => { player.reset(); await module.disconnect(); - }).timeout(10000).retries(3); + }).timeout(10000); it('should run the test recipe two times with several player interactions (pause, resume, stop)', async () => { @@ -195,8 +170,7 @@ describe('Player', () => { const module = new Module(moduleJson); const service = module.services[0]; - module.connect(); - await waitForStateChange(service, 'IDLE', 2000); + await module.connect(); // now test recipe const recipeJson = JSON.parse( @@ -209,33 +183,33 @@ describe('Player', () => { player.enqueue(recipe); player.enqueue(recipe); - expect(service.status.value).to.equal(ServiceState.IDLE); + expect(service.state).to.equal(ServiceState.IDLE); player.start(); expect(player.status).to.equal(RecipeState.running); - waitForStateChange(service, 'STARTING', 2000); - await waitForStateChange(service, 'EXECUTE', 2000); + await waitForStateChange(service, 'STARTING', 2000); + await waitForStateChange(service, 'EXECUTE'); player.pause(); - waitForStateChange(service, 'PAUSING'); + await waitForStateChange(service, 'PAUSING'); await waitForStateChange(service, 'PAUSED'); expect(player.status).to.equal(RecipeState.paused); player.start(); await waitForStateChange(service, 'RESUMING'); await waitForStateChange(service, 'EXECUTE'); - expect(service.status.value).to.equal(ServiceState.EXECUTE); + expect(service.state).to.equal(ServiceState.EXECUTE); expect(player.status).to.equal(RecipeState.running); - waitForStateChange(service, 'COMPLETING', 2000); - await waitForStateChange(service, 'COMPLETED', 2000); + await waitForStateChange(service, 'COMPLETING', 2000); + await waitForStateChange(service, 'COMPLETED'); await waitForStateChange(service, 'IDLE'); // here the second run of the recipe should automatically start, since first recipe is finished - waitForStateChange(service, 'STARTING', 1000); - await waitForStateChange(service, 'EXECUTE', 1000); + await waitForStateChange(service, 'STARTING', 2000); + await waitForStateChange(service, 'EXECUTE'); await player.stop(); @@ -246,7 +220,7 @@ describe('Player', () => { player.reset(); await module.disconnect(); - }).timeout(10000).retries(3); + }).timeout(10000); }); @@ -281,16 +255,6 @@ describe('Player', () => { expect(recipe).to.have.property('status', 'completed'); completedRecipes.push(recipe.id); }) - .on('recipeStarted', (recipe) => { - if (completedRecipes.length === 2) { - expect(player.getCurrentRecipe().id).to.equal(recipe.id); - player.pause(); - expect(player.status).to.equal(RecipeState.paused); - player.start(); - expect(player.status).to.equal(RecipeState.running); - expect(player.getCurrentRecipe().id).to.equal(recipe.id); - } - }) .once('completed', async () => { expect(completedRecipes).to.have.length(3); expect(player.status).to.equal(RecipeState.completed); @@ -375,7 +339,7 @@ describe('Player', () => { player.once('started', () => resolve()); }), 1000); timeout(new Promise((resolve) => { - player.once('recipeStarted', () => resolve()); + player.once('recipeChanged', () => resolve()); }), 1000); player.start(); expect(player.getCurrentRecipe().currentStep.name).to.equal('S1'); @@ -384,30 +348,10 @@ describe('Player', () => { expect(() => player.forceTransition('S1', 'non-existant')).to.throw(); expect(() => player.forceTransition('S1', 'S3')).to.throw(); - // do not change in next 100ms - await new Promise((resolve, reject) => { - player.once('stepFinished', () => { - reject(); - }); - setTimeout(() => { - resolve(); - }, 100); - }); - + await delay(10); player.forceTransition('S1', 'S2'); - expect(player.getCurrentRecipe().currentStep.name).to.equal('S2'); - // do not change in next 100ms - await new Promise((resolve, reject) => { - player.once('stepFinished', () => { - reject(); - }); - setTimeout(() => { - resolve(); - }, 100); - }); - player.forceTransition('S2', 'S3'); expect(player.getCurrentRecipe().currentStep.name).to.equal('S3'); diff --git a/test/model/recipe/Recipe.spec.ts b/test/model/recipe/Recipe.spec.ts index b38f6344..d2f647d7 100644 --- a/test/model/recipe/Recipe.spec.ts +++ b/test/model/recipe/Recipe.spec.ts @@ -23,7 +23,7 @@ * SOFTWARE. */ -import {RecipeInterface} from '@p2olab/polaris-interface'; +import {ConditionType, RecipeInterface, RecipeOptions} from '@p2olab/polaris-interface'; import * as assert from 'assert'; import * as chai from 'chai'; import * as chaiAsPromised from 'chai-as-promised'; @@ -38,6 +38,56 @@ const expect = chai.expect; describe('Recipe', () => { + it('should fail with missing name', () => { + expect(() => new Recipe({ + name: null, author: null, description: null, + initial_step: null, version: null, steps: null + }, [])).to.throw('missing'); + }); + + it('should fail with missing steps', () => { + expect(() => new Recipe({ + name: 'test', author: null, description: null, + initial_step: null, version: null, steps: null + }, [])).to.throw('missing'); + }); + + it('should fail with missing initial step', () => { + expect(() => new Recipe({ + name: 'test', author: null, description: null, + initial_step: null, version: null, steps: [] + }, [])).to.throw('not found'); + }); + + it('should fail with wrong initial step', () => { + expect(() => new Recipe({ + name: 'test', author: null, description: null, + initial_step: 'initial', version: null, steps: [] + }, [])).to.throw('not found'); + }); + + it('should fail with wrong next step', () => { + expect(() => new Recipe({ + name: 'test', author: null, description: null, + initial_step: 'initial', version: null, + steps: [{ + name: 'initial', operations: [], transitions: [{ + next_step: 'notexisting', + condition: {type: ConditionType.time, duration: 1} + }] + }] + } + , [])).to.throw('not found'); + }); + + it('should work', () => { + expect(new Recipe({ + name: 'test', author: null, description: null, + initial_step: 'initial', version: null, steps: [{name: 'initial', operations: [], transitions: []}] + }, [])) + .to.have.property('id'); + }); + describe('with module test server', () => { let moduleServer: ModuleTestServer; @@ -50,9 +100,7 @@ describe('Recipe', () => { const moduleJson = JSON.parse(fs.readFileSync('assets/modules/module_testserver_1.0.0.json').toString()) .modules[0]; module = new Module(moduleJson); - await module.connect(); - }); afterEach(async () => { @@ -61,7 +109,6 @@ describe('Recipe', () => { }); it('runs test recipe successfully', async () => { - // now test recipe const recipeJson = JSON.parse( fs.readFileSync('assets/recipes/test/recipe_testserver_2services_1.0.0.json').toString()); const recipe = new Recipe(recipeJson, [module]); @@ -69,11 +116,8 @@ describe('Recipe', () => { await recipe.start(); await new Promise((resolve) => { - recipe.on('completed', () => { - resolve(); - }); + recipe.on('completed', resolve); }); - }).timeout(5000); it('should only run one recipe at a time', async () => { @@ -83,11 +127,9 @@ describe('Recipe', () => { const recipe = new Recipe(recipeJson, [module]); await recipe.start(); - expect(recipe.start()).to.be.rejectedWith(/already running/); + await expect(recipe.start()).to.be.rejectedWith(/already running/); await new Promise((resolve) => { - recipe.on('completed', () => { - resolve(); - }); + recipe.on('completed', resolve); }); }).timeout(5000); @@ -96,11 +138,11 @@ describe('Recipe', () => { fs.readFileSync('assets/recipes/test/recipe_testserver_2services_1.0.0.json').toString()); const recipe = new Recipe(recipeJson, [module]); - expect(recipe.stop()).to.be.rejectedWith('Can only stop running recipe'); + await expect(recipe.stop()).to.be.rejectedWith('Can only stop running recipe'); await recipe.start(); await delay(100); await recipe.stop(); - expect(recipe.stop()).to.be.rejectedWith('Can only stop running recipe'); + await expect(recipe.stop()).to.be.rejectedWith('Can only stop running recipe'); await delay(100); }).timeout(5000); @@ -111,7 +153,7 @@ describe('Recipe', () => { const modules = []; before(() => { - let file = fs.readFileSync('assets/modules/modules_achema.json'); + let file = fs.readFileSync('assets/modules/achema_demonstrator/modules_achema.json'); let options = JSON.parse(file.toString()); modules.push(new Module(options.modules[0])); modules.push(new Module(options.modules[1])); @@ -149,11 +191,11 @@ describe('Recipe', () => { fs.readdirSync(path).forEach((filename) => { const completePath = path + filename; if (fs.statSync(completePath).isFile()) { - it(`should load recipe ${completePath}`, (done) => { + it(`should load recipe ${completePath}`, () => { const file = fs.readFileSync(completePath); - const options = JSON.parse(file.toString()); + const options: RecipeOptions = JSON.parse(file.toString()); const recipe = new Recipe(options, modules); - done(); + expect(recipe.name).to.equal(options.name); }); } }); diff --git a/test/model/recipe/ScopeItem.spec.ts b/test/model/recipe/ScopeItem.spec.ts index cfcdbd7e..3586f649 100644 --- a/test/model/recipe/ScopeItem.spec.ts +++ b/test/model/recipe/ScopeItem.spec.ts @@ -24,50 +24,205 @@ */ import {expect} from 'chai'; +import {Expression} from 'expr-eval'; import * as fs from 'fs'; -import {OPCUAServer} from 'node-opcua-server'; -import {timeout} from 'promise-timeout'; import {Module} from '../../../src/model/core/Module'; import {ScopeItem} from '../../../src/model/recipe/ScopeItem'; +import {TestServerNumericVariable} from '../../../src/moduleTestServer/ModuleTestNumericVariable'; +import {ModuleTestServer} from '../../../src/moduleTestServer/ModuleTestServer'; +import {waitForParameterChange, waitForVariableChange} from '../../helper'; /** - * Test for [[Condition]] + * Tests for [[ScopeItem]] */ -describe('Scope Item', () => { +describe('ScopeItem', () => { - let moduleJson; - let module: Module; + let moduleTestServer: Module; + let moduleDosierer: Module; before(() => { - moduleJson = JSON.parse(fs.readFileSync('assets/modules/module_testserver_1.0.0.json', 'utf8')) - .modules[0]; - module = new Module(moduleJson); + moduleDosierer = new Module( + JSON.parse(fs.readFileSync('assets/modules/module_dosierer_1.1.0.json').toString()).modules[0]); + moduleTestServer = new Module( + JSON.parse(fs.readFileSync('assets/modules/module_testserver_1.0.0.json').toString()).modules[0]); }); it('should work for normal expression', () => { - const extraction = ScopeItem.extractFromExpressionString('CIF.Variable001 + 3', [module]); + const extraction = ScopeItem.extractFromExpressionString('CIF.Variable001 + 3', [moduleTestServer]); expect(extraction.scopeItems).to.have.lengthOf(1); expect(extraction.scopeItems[0].name).to.equal('CIF.Variable001'); }); - it('should work for mutliple variables', () => { - const extraction = ScopeItem.extractFromExpressionString('CIF.Variable001 + CIF.Variable002', [module]); + it('should work for multiple variables', () => { + const extraction = ScopeItem.extractFromExpressionString('CIF.Variable001 + CIF.Variable002', + [moduleTestServer]); expect(extraction.scopeItems).to.have.lengthOf(2); expect(extraction.scopeItems[0].name).to.equal('CIF.Variable001'); expect(extraction.scopeItems[1].name).to.equal('CIF.Variable002'); }); - it('should work for mutliple times of same variables', () => { - const extraction = ScopeItem.extractFromExpressionString('CIF.Variable001 + CIF.Variable001', [module]); + it('should work for multiple times of same variables', () => { + const extraction = ScopeItem.extractFromExpressionString('CIF.Variable001 + CIF.Variable001', + [moduleTestServer]); expect(extraction.scopeItems).to.have.lengthOf(1); expect(extraction.scopeItems[0].name).to.equal('CIF.Variable001'); }); it('should work for expression with special characters', () => { - const extraction = ScopeItem.extractFromExpressionString('CIF.Variable\\.003 + 3', [module]); + const extraction = ScopeItem.extractFromExpressionString('CIF.Variable\\.003 + 3', [moduleTestServer]); expect(extraction.scopeItems).to.have.lengthOf(1); - expect(extraction.scopeItems[0].variable.node_id).to.equal('TestServerVariable.3.V'); + expect(extraction.scopeItems[0].dataItem.nodeId).to.equal('TestServerVariable.3.V'); expect(extraction.scopeItems[0].name).to.equal('CIF.Variable__003'); }); + it('should return null without modules', () => { + expect(ScopeItem.extractFromExpressionVariable('Variable001', [])) + .to.equal(null); + }); + + it('should return ScopeItem 1', () => { + const item = ScopeItem.extractFromExpressionVariable('Variable001', [moduleTestServer]); + expect(item).to.have.property('module').to.have.property('id', 'CIF'); + expect(item).to.have.property('name', 'Variable001'); + expect(item).to.have.property('dataItem').to.have.property('nodeId', 'Variable1.V'); + }); + + it('should return ScopeItem 2', () => { + const item = ScopeItem.extractFromExpressionVariable('Variable001.VUnit', [moduleTestServer]); + expect(item).to.have.property('module').to.have.property('id', 'CIF'); + expect(item).to.have.property('name', 'Variable001.VUnit'); + expect(item).to.have.property('dataItem').to.have.property('nodeId', 'Variable1.VUnit'); + }); + + it('should return ScopeItem 3', () => { + const item = ScopeItem.extractFromExpressionVariable('Variable001.V', [moduleTestServer]); + expect(item).to.have.property('module').to.have.property('id', 'CIF'); + expect(item).to.have.property('name', 'Variable001.V'); + expect(item).to.have.property('dataItem').to.have.property('nodeId', 'Variable1.V'); + }); + + it('should return ScopeItem 4', async () => { + const item = ScopeItem.extractFromExpressionVariable('CIF.Variable001.VUnit', + [moduleTestServer, moduleDosierer]); + expect(item).to.have.property('module').to.have.property('id', 'CIF'); + expect(item).to.have.property('name', 'CIF.Variable001.VUnit'); + expect(item).to.have.property('dataItem').to.have.property('nodeId', 'Variable1.VUnit'); + }); + + it('should return ScopeItem 5', () => { + const item = ScopeItem.extractFromExpressionVariable('CIF.Service1.Parameter001', + [moduleTestServer, moduleDosierer]); + expect(item).to.have.property('module').to.have.property('id', 'CIF'); + expect(item).to.have.property('name', 'CIF.Service1.Parameter001'); + expect(item).to.have.property('dataItem').to.have.property('nodeId', 'Service1.Parameter1.V'); + }); + + it('should return null when parameter name is not existant', () => { + const item = ScopeItem.extractFromExpressionVariable('CIF.Service1.Parameter00x', + [moduleTestServer, moduleDosierer]); + expect(item).to.equal(null); + }); + + it('should return null if service name is not existant', () => { + const item = ScopeItem.extractFromExpressionVariable('CIF.Service5.Parameter00x', + [moduleTestServer, moduleDosierer]); + expect(item).to.equal(null); + }); + + it('should return null when no module is given and more than one module available', () => { + expect(ScopeItem.extractFromExpressionVariable('Variable001', [moduleTestServer, moduleDosierer])) + .to.equal(null); + }); + + context('with module testserver', () => { + + let moduleServer: ModuleTestServer; + + before(async function() { + this.timeout(5000); + moduleServer = new ModuleTestServer(); + await moduleServer.start(); + + await moduleTestServer.connect(); + }); + + after(async () => { + await moduleTestServer.disconnect(); + await moduleServer.shutdown(); + }); + + it('get scope value for variable', async () => { + const item = ScopeItem.extractFromExpressionVariable('CIF.Variable001', [moduleTestServer, moduleDosierer]); + expect(item.getScopeValue()).to.deep.equal({ + 'CIF': { + 'Variable001': 20 + } + }); + + (moduleServer.variables[0] as TestServerNumericVariable).v = 3; + await waitForVariableChange(moduleTestServer, 'Variable001', 3); + expect(item.getScopeValue()).to.deep.equal({ + 'CIF': { + 'Variable001': 3 + } + }); + + (moduleServer.variables[0] as TestServerNumericVariable).v = 4; + await waitForVariableChange(moduleTestServer, 'Variable001', 4); + expect(item.getScopeValue()).to.deep.equal({ + 'CIF': { + 'Variable001': 4 + } + }); + }).timeout(5000); + + it('get scope value for parameter', async () => { + const item = ScopeItem.extractFromExpressionVariable('CIF.Service1.Parameter001', + [moduleTestServer, moduleDosierer]); + + expect(item.name).to.equal('CIF.Service1.Parameter001'); + expect(item.dataItem.nodeId).to.equal('Service1.Parameter1.V'); + expect(item.getScopeValue()).to.deep.equal({ + 'CIF': { + 'Service1': { + 'Parameter001': 20 + } + } + }); + + (moduleServer.services[0].parameter[0] as TestServerNumericVariable).v = 30; + await waitForParameterChange(moduleTestServer, 'Parameter001', 30); + expect(item.getScopeValue()).to.deep.equal({ + 'CIF': { + 'Service1': { + 'Parameter001': 30 + } + } + }); + }).timeout(5000); + + it('should work with state', () => { + const data: {expression: Expression, scopeItems: ScopeItem[]} = + ScopeItem.extractFromExpressionString('CIF.Service1.state==\'IDLE\'', [moduleTestServer]); + expect(data.scopeItems).to.have.lengthOf(1); + expect(data.scopeItems[0]).to.have.property('module').to.have.property('id', 'CIF'); + expect(data.scopeItems[0]).to.have.property('name', 'CIF.Service1.state'); + expect(data.scopeItems[0]).to.have.property('dataItem').to.have.property('nodeId', 'Service1.State'); + + const tasks = data.scopeItems.map((item) => { + return item.getScopeValue(); + }); + const assign = require('assign-deep'); + const scope = assign(...tasks); + expect(scope).to.deep.equal({ + CIF: { + Service1: { + state: 'IDLE' + } + } + }); + expect(data.expression.evaluate(scope)).to.equal(true); + }); + + }); }); diff --git a/test/model/recipe/Step.spec.ts b/test/model/recipe/Step.spec.ts new file mode 100644 index 00000000..d68c1426 --- /dev/null +++ b/test/model/recipe/Step.spec.ts @@ -0,0 +1,98 @@ +/* + * MIT License + * + * Copyright (c) 2019 Markus Graube , + * Chair for Process Control Systems, Technische Universität Dresden + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +import {ServiceCommand, StepOptions} from '@p2olab/polaris-interface'; +import {expect} from 'chai'; +import * as fs from 'fs'; +import {Module} from '../../../src/model/core/Module'; +import {Step} from '../../../src/model/recipe/Step'; +import {ModuleTestServer} from '../../../src/moduleTestServer/ModuleTestServer'; + +describe('Step', () => { + + it('should fail to create with missing name', () => { + expect(() => new Step({name: null, operations: null, transitions: null}, null)).to.throw('missing'); + }); + + it('should fail to create with missing operations', () => { + expect(() => new Step({name: 'test', operations: null, transitions: null}, null)).to.throw('missing'); + }); + + it('should fail to create with missing name', () => { + expect(() => new Step({name: 'test', operations: [], transitions: null}, null)).to.throw('missing'); + }); + + it('should create', () => { + expect(new Step({name: 'test', operations: [], transitions: []}, null)).to.have.property('name', 'test'); + }); + + describe('with module test server', () => { + + let moduleServer: ModuleTestServer; + let module: Module; + + beforeEach(async function() { + this.timeout(5000); + moduleServer = new ModuleTestServer(); + await moduleServer.start(); + + const moduleJson = JSON.parse(fs.readFileSync('assets/modules/module_testserver_1.0.0.json').toString()) + .modules[0]; + module = new Module(moduleJson); + await module.connect(); + + }); + + afterEach(async () => { + await moduleServer.shutdown(); + }); + + it('should execute step', async () => { + const step = new Step({ + name: 'S0.CheckInitialConditions', + operations: [ { + service: 'Service1', + command: ServiceCommand.start + }], + transitions: [ + { + next_step: 'completed', + condition: { + type: 'state', + module: 'CIF', + service: 'Service1', + state: 'starting' + } + } + ] + } as StepOptions, [module]); + + step.execute(); + + await new Promise((resolve) => step.eventEmitter.on('completed', resolve)); + }); + + }); +}); diff --git a/test/model/virtualService/AggregatedService.spec.ts b/test/model/virtualService/AggregatedService.spec.ts new file mode 100644 index 00000000..1faab9e7 --- /dev/null +++ b/test/model/virtualService/AggregatedService.spec.ts @@ -0,0 +1,76 @@ +/* + * MIT License + * + * Copyright (c) 2019 Markus Graube , + * Chair for Process Control Systems, Technische Universität Dresden + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +import {ModuleOptions} from '@p2olab/polaris-interface'; +import * as fs from 'fs'; +import {Module} from '../../../src/model/core/Module'; +import {AggregatedService, AggregatedServiceOptions} from '../../../src/model/virtualService/AggregatedService'; +import {ModuleTestServer} from '../../../src/moduleTestServer/ModuleTestServer'; +import {waitForStateChange} from '../../helper'; + +describe('AggregatedService', () => { + + let moduleServer1: ModuleTestServer; + let moduleServer2: ModuleTestServer; + + beforeEach(async () => { + moduleServer1 = new ModuleTestServer(4334); + moduleServer2 = new ModuleTestServer(4335); + await moduleServer1.start(); + await moduleServer2.start(); + moduleServer1.startSimulation(); + moduleServer2.startSimulation(); + }); + + afterEach(async () => { + await moduleServer1.shutdown(); + await moduleServer2.shutdown(); + }); + + it('should work', async () => { + const moduleJson: ModuleOptions = + JSON.parse(fs.readFileSync('assets/modules/module_testserver_1.0.0.json', 'utf8')).modules[0]; + + moduleJson.id = 'PEA1'; + moduleJson.opcua_server_url = 'opc.tcp://127.0.0.1:4334/ModuleTestServer'; + const module1 = new Module(moduleJson); + await module1.connect(); + + moduleJson.id = 'PEA2'; + moduleJson.opcua_server_url = 'opc.tcp://127.0.0.1:4335/ModuleTestServer'; + const module2 = new Module(moduleJson); + await module2.connect(); + + const aggregatedServiceJson: AggregatedServiceOptions = + JSON.parse(fs.readFileSync('assets/virtualService/aggregatedService_moduletestserver.json', 'utf8')); + + const as = new AggregatedService(aggregatedServiceJson, [module1, module2]); + + as.start(); + + await waitForStateChange(as, 'EXECUTE'); + }).timeout(5000); + +}); diff --git a/test/model/virtualService/Petrinet.spec.ts b/test/model/virtualService/Petrinet.spec.ts new file mode 100644 index 00000000..eb9a9de3 --- /dev/null +++ b/test/model/virtualService/Petrinet.spec.ts @@ -0,0 +1,181 @@ +/* + * MIT License + * + * Copyright (c) 2019 Markus Graube , + * Chair for Process Control Systems, Technische Universität Dresden + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +import {expect} from 'chai'; +import {Petrinet} from '../../../src/model/virtualService/aggregatedService/Petrinet'; + +describe('Petrinet', () => { + + it('should handle simple petrinet', (done) => { + const pn = new Petrinet({ + initialTransition: 't1', + states: [ + { + id: 'Init', + operations: [ ], + nextTransitions: ['t2'] + } + ], + transitions: [ + { + id: 't1', + condition: null, + nextStates: ['Init'] + }, + { + id: 't2', + condition: null, + nextStates: ['finished'] + } + ] + }, []); + + expect(pn.transitions[1].nextStates).to.have.lengthOf(0); + expect(pn.transitions[1].priorStates[0].id).to.equal('Init'); + expect(pn.states[0].nextTransitions).to.have.lengthOf(1); + expect(pn.states[0].nextTransitions[0].id).to.equal('t2'); + + expect(pn.activeStates).to.have.lengthOf(0); + const stateChanges = []; + const transitionChanges = []; + pn.eventEmitter.on('state', (state) => { + expect(pn.activeStates.length).to.be.greaterThan(0); + stateChanges.push(state.id); + }); + pn.eventEmitter.on('transition', (tr) => { + transitionChanges.push(tr.id); + }); + + pn.run(); + + pn.eventEmitter.once('completed', () => { + expect(pn.activeStates).to.have.lengthOf(0); + expect(stateChanges).to.deep.equal([ 'Init' ]); + expect(transitionChanges).to.deep.equal([ 't1', 't2' ]); + done(); + }); + }); + + it('should handle complex petrinet', (done) => { + const pn = new Petrinet({ + initialTransition: 't1', + states: [ + { + id: 's1a', + operations: [ ], + nextTransitions: ['t2'] + }, + { + id: 's1b', + operations: [ ], + nextTransitions: ['t2'] + }, + { + id: 's2', + operations: [ ], + nextTransitions: ['t3a', 't3b'] + }, + { + id: 's3a', + operations: [ ], + nextTransitions: ['t4a'] + }, + { + id: 's3b', + operations: [ ], + nextTransitions: ['t4b'] + }, + { + id: 's4', + operations: [ ], + nextTransitions: ['t5'] + } + ], + transitions: [ + { + id: 't1', + condition: null, + nextStates: ['s1a', 's1b'] + }, + { + id: 't2', + condition: null, + nextStates: ['s2'] + }, + { + id: 't3a', + condition: null, + nextStates: ['s3a'] + }, + { + id: 't3b', + condition: null, + nextStates: ['s3b'] + }, + { + id: 't4a', + condition: null, + nextStates: ['s4'] + }, + { + id: 't4b', + condition: null, + nextStates: ['s4'] + }, + { + id: 't5', + condition: null, + nextStates: ['finished'] + } + ] + }, []); + + expect(pn.transitions[1].nextStates).to.have.lengthOf(1); + expect(pn.transitions[1].priorStates[0].id).to.equal('s1a'); + expect(pn.states[0].nextTransitions).to.have.lengthOf(1); + expect(pn.states[0].nextTransitions[0].id).to.equal('t2'); + + expect(pn.activeStates).to.have.lengthOf(0); + const stateChanges = []; + const transitionChanges = []; + pn.eventEmitter.on('state', (state) => { + expect(pn.activeStates.length).to.be.greaterThan(0); + stateChanges.push(state.id); + }); + pn.eventEmitter.on('transition', (tr) => { + transitionChanges.push(tr.id); + }); + + pn.run(); + + pn.eventEmitter.once('completed', () => { + expect(pn.activeStates).to.have.lengthOf(0); + expect(stateChanges).to.deep.equal([ 's1a', 's1b', 's2', 's3a', 's4' ]); + expect(transitionChanges).to.deep.equal([ 't1', 't2', 't3a', 't4a', 't5' ]); + done(); + }); + }); + +}); diff --git a/test/model/virtualService/VirtualService.spec.ts b/test/model/virtualService/VirtualService.spec.ts new file mode 100644 index 00000000..502f64f8 --- /dev/null +++ b/test/model/virtualService/VirtualService.spec.ts @@ -0,0 +1,226 @@ +/* + * MIT License + * + * Copyright (c) 2019 Markus Graube , + * Chair for Process Control Systems, Technische Universität Dresden + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +import {expect} from 'chai'; +import * as fs from 'fs'; +import * as parseJson from 'json-parse-better-errors'; +import * as delay from 'timeout-as-promise'; +import {ServiceState} from '../../../src/model/core/enum'; +import {Manager} from '../../../src/model/Manager'; +import {FunctionGenerator} from '../../../src/model/virtualService/FunctionGenerator'; +import {Storage} from '../../../src/model/virtualService/Storage'; +import {Timer} from '../../../src/model/virtualService/Timer'; +import {VirtualServiceFactory} from '../../../src/model/virtualService/VirtualServiceFactory'; + +describe('VirtualService', () => { + + describe('via Manager', () => { + it('should instantiate timer', async () => { + const manager = new Manager(); + manager.instantiateVirtualService({name: 'timer1', type: 'timer'}); + manager.instantiateVirtualService({name: 'timer2', type: 'timer'}); + + expect(manager.virtualServices).to.have.lengthOf(2); + }); + }); + + describe('Factory', () => { + it('should instantiate timer', () => { + const timerJson = parseJson(fs.readFileSync('assets/virtualService/timer.json', 'utf8'), null, 60); + + const timer = VirtualServiceFactory.create(timerJson); + + const json = timer.json(); + expect(json.type).equal('Timer'); + expect(json.name).equal('timer1'); + expect(json.parameters).deep.equal([ + {min: 1, + name: 'duration', + unit: 'ms', + value: 10000 + }, + { + min: 100, + name: 'updateRate', + unit: 'ms', + value: 1000 + }]); + expect(json.processValuesOut).to.deep.equal([ + { + name: 'remainingTime', + readonly: true, + unit: 'ms', + value: 10000 + } + ]); + }); + + it('should instantiate aggregated service', () => { + const manager = new Manager(); + const modules = manager.loadModule( + JSON.parse(fs.readFileSync('assets/modules/achema_demonstrator/modules_achema.json').toString()), + true); + expect(modules).to.have.lengthOf(3); + + const asJson = parseJson( + fs.readFileSync('assets/virtualService/virtualService_achema_dose_fill.json', 'utf8'), null, 60); + + const aggregatedService = VirtualServiceFactory.create(asJson, modules); + + const json = aggregatedService.json(); + expect(json.type).equal('AggregatedService'); + expect(json.name).equal('DoseFill'); + expect(json.parameters).deep.equal([ + { + default: '0', + name: 'SetVolume', + type: 'ExtAnaOp', + unit: '1038' + } + ]); + }); + + }); + + describe('Timer', () => { + it('should run', async () => { + const timer = new Timer('t1'); + expect(timer.state).to.equal(ServiceState.IDLE); + + const params = timer.parameters; + expect(params).to.have.lengthOf(2); + expect(params[0]).to.deep.equal({ + 'min': 1, + 'name': 'duration', + 'unit': 'ms', + 'value': 10000 + }); + + await timer.setParameters([{name: 'duron', value: 1000}]).then(expect.fail, (err) => err); + + await timer.setParameters([{name: 'remainingTime', value: 1000}]).then(expect.fail, (err) => err); + + await timer.setParameters([{name: 'duration', value: 100}, {name: 'updateRate', value: 10}]); + + let hit = 0; + timer.eventEmitter.on('variableChanged', (data) => { + if (data.parameter === 'remainingTime') { + hit = hit + 1; + } + }); + await timer.start(); + expect(hit).to.equal(1); + expect(timer.state).to.equal(ServiceState.EXECUTE); + await delay(45); + expect(hit).to.equal(5); + expect(timer.processValuesOut.find((p) => p.name === 'remainingTime')) + .to.have.property('value') + .to.closeTo(55, 5); + + await timer.pause(); + expect(hit).to.equal(6); + await delay(25); + expect(hit).to.equal(6); + expect(timer.processValuesOut.find((p) => p.name === 'remainingTime')) + .to.have.property('value') + .to.closeTo(55, 5); + + await timer.resume(); + expect(hit).to.equal(6); + await delay(12); + expect(hit).to.equal(7); + expect(timer.processValuesOut.find((p) => p.name === 'remainingTime')) + .to.have.property('value') + .to.closeTo(43, 5); + + await delay(20); + expect(hit).to.equal(9); + expect(timer.processValuesOut.find((p) => p.name === 'remainingTime')) + .to.have.property('value') + .to.closeTo(23, 5); + + await timer.reset(); + }); + }); + + describe('FunctionGenerator', () => { + it('should run', async () => { + const f1 = new FunctionGenerator('f1'); + expect(f1.state).to.equal(ServiceState.IDLE); + + let params = f1.parameters; + expect(params).to.have.lengthOf(3); + + await f1.setParameters([{name: 'function', value: 'sin(5*t)'}, {name: 'updateRate', value: 100}]); + await f1.start(); + expect(f1.state).to.equal(ServiceState.EXECUTE); + + await delay(110); + params = f1.parameters; + let value = params.find((p) => p.name === 'output'); + expect(value).to.have.property('value').to.be.closeTo(0.5, 0.03); + await f1.pause(); + await delay(100); + + params = f1.parameters; + value = params.find((p) => p.name === 'output'); + expect(value).to.have.property('value').to.be.closeTo(0.841, 0.03); + await f1.resume(); + await delay(100); + + await f1.stop(); + + await f1.reset(); + }); + }); + + describe('Storage', () => { + it('should work', async () => { + const s1 = new Storage('s1'); + let params = s1.parameters; + expect(params).to.have.lengthOf(1); + expect(params[0]).to.have.property('name', 'storage'); + expect(params[0]).to.have.property('value', undefined); + + s1.setParameters([{name: 'storage', value: 2}]); + params = s1.parameters; + expect(params).to.have.lengthOf(1); + expect(params[0]).to.have.property('name', 'storage'); + expect(params[0]).to.have.property('value', 2); + + s1.setParameters([{name: 'storage', value: 'teststring'}]); + params = s1.parameters; + expect(params).to.have.lengthOf(1); + expect(params[0]).to.have.property('name', 'storage'); + expect(params[0]).to.have.property('value', 'teststring'); + + await s1.start(); + await s1.complete(); + await s1.reset(); + params = s1.parameters; + expect(params).to.have.lengthOf(1); + }); + }); +}); diff --git a/test/model/virtualService/complex_petrinet.puml b/test/model/virtualService/complex_petrinet.puml new file mode 100644 index 00000000..722ae592 --- /dev/null +++ b/test/model/virtualService/complex_petrinet.puml @@ -0,0 +1,55 @@ +@startuml + +skinparam monochrome true + +==t1== --> s1a +==t1== --> s1b + +==t2== --> s2 + +==t3a== --> s3a +==t3b== --> s3b + + +==t4a== --> s4 +==t4b== --> s4 + +==t5== -->(*) + +(*) --> ==t1== +note right +t1 +end note + +s1a --> ==t2== +s1b --> ==t2== +note right +t2 +end note + +s2 --> ==t3a== +note right +t3a +end note +s2 --> ==t3b== +note right +t3b +end note + +s3a --> ==t4a== +note right +t4a +end note + +s3b --> ==t4b== +note right +t4b +end note + +s4 --> ==t5== +note right +t5 +end note + + +@enduml \ No newline at end of file diff --git a/test/moduleTestServer/ModuleTestServer.spec.ts b/test/moduleTestServer/ModuleTestServer.spec.ts new file mode 100644 index 00000000..0071a388 --- /dev/null +++ b/test/moduleTestServer/ModuleTestServer.spec.ts @@ -0,0 +1,87 @@ +/* + * MIT License + * + * Copyright (c) 2019 Markus Graube , + * Chair for Process Control Systems, Technische Universität Dresden + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +import * as chai from 'chai'; +import * as chaiAsPromised from 'chai-as-promised'; +import {ClientSession, OPCUAClient} from 'node-opcua-client'; +import {controlEnableToJson, ServiceState} from '../../src/model/core/enum'; +import {ModuleTestServer} from '../../src/moduleTestServer/ModuleTestServer'; + +chai.use(chaiAsPromised); +const expect = chai.expect; + +describe('ModuleTestServer', () => { + + let moduleServer: ModuleTestServer; + + beforeEach(async () => { + moduleServer = new ModuleTestServer(); + await moduleServer.start(); + }); + + afterEach(async () => { + await moduleServer.shutdown(); + }); + + it('should connect to OPC UA server', async () => { + const client = OPCUAClient.create({ + endpoint_must_exist: false, + connectionStrategy: { + maxRetry: 10 + } + }); + + await client.connect('opc.tcp://localhost:4334/ModuleTestServer'); + const session: ClientSession = await client.createSession(); + + let result = await session.readVariableValue('ns=1;s=Service1.State'); + expect(result.value.value).to.equal(ServiceState.IDLE); + + moduleServer.services[0].varStatus = 8; + result = await session.readVariableValue('ns=1;s=Service1.State'); + expect(result.value.value).to.equal(ServiceState.STARTING); + + const result2 = await session.readVariableValue('ns=1;s=Service1.CommandEnable'); + const ce = controlEnableToJson(result2.value.value); + expect(ce).to.deep.equal({ + abort: true, + complete: false, + pause: false, + reset: false, + restart: false, + resume: false, + start: true, + stop: true, + unhold: false + }); + + const result3 = await session.readVariableValue('ns=0;i=2255'); + expect(result3.value.value).to.deep.equal(['http://opcfoundation.org/UA/', + 'urn:NodeOPCUA-Server-default']); + + await client.disconnect(); + }); + +}); diff --git a/test/server/ExternalTrigger.spec.ts b/test/server/ExternalTrigger.spec.ts index 935649ee..7725fc36 100644 --- a/test/server/ExternalTrigger.spec.ts +++ b/test/server/ExternalTrigger.spec.ts @@ -24,7 +24,6 @@ */ import {expect} from 'chai'; -import {OPCUAServer} from 'node-opcua-server'; import {ModuleTestServer} from '../../src/moduleTestServer/ModuleTestServer'; import {ExternalTrigger} from '../../src/server/ExternalTrigger'; @@ -42,15 +41,9 @@ describe('ExternalTrigger', () => { }); it('should fail with missing endpoint', () => { - expect(() => { - const et = new ExternalTrigger(undefined, undefined, undefined); - }).to.throw(); - expect(() => { - const et = new ExternalTrigger('sdfsd', undefined, undefined); - }).to.throw(); - expect(() => { - const et = new ExternalTrigger('opc.tcp://localhost:4334/Ua/MyLittleServer', undefined, undefined); - }).to.throw(); + expect(() => new ExternalTrigger(undefined, undefined, undefined)).to.throw(); + expect(() => new ExternalTrigger('sdfsd', undefined, undefined)).to.throw(); + expect(() => new ExternalTrigger('opc.tcp://localhost:4334/Ua/MyServer', undefined, undefined)).to.throw(); }); it('should work with the sample server', async () => { diff --git a/test/server/routes.spec.ts b/test/server/routes.spec.ts index c47374e2..242d87a1 100644 --- a/test/server/routes.spec.ts +++ b/test/server/routes.spec.ts @@ -23,25 +23,34 @@ * SOFTWARE. */ +import * as fs from 'fs'; import * as request from 'supertest'; +import * as WebSocket from 'ws'; import {Manager} from '../../src/model/Manager'; +import {ModuleTestServer} from '../../src/moduleTestServer/ModuleTestServer'; import Routes from '../../src/server/routes'; import {Server} from '../../src/server/server'; describe('Routes', () => { let app; + let appServer: Server; before(() => { - const appServer = new Server(new Manager()); + appServer = new Server(new Manager()); + appServer.startHttpServer(3000); + appServer.initSocketServer(); app = appServer.app; }); + after(async () => { + await appServer.stop(); + }); + it('should give code 404 for not existing routes', (done) => { request(app).get('/api/notExisting') .expect(404, done); }); - context('#coreRoutes', () => { context('autoReset', () => { @@ -118,12 +127,6 @@ describe('Routes', () => { .expect(200, done); }); - it('should provide meta information', (done) => { - request(app).get('/api/') - .expect('Content-Type', /json/) - .expect(200, done); - }); - context('logs', () => { it('should provide logs', (done) => { @@ -166,10 +169,116 @@ describe('Routes', () => { }); context('#moduleRoutes', () => { - it('should provide modules', (done) => { - request(app).get('/api/module') + it('should provide modules', async () => { + await request(app).get('/api/module') .expect('Content-Type', /json/) - .expect(200, done); + .expect(200) + .expect([]); + }); + + it('should throw 404 when get not existing module', async () => { + await request(app).get('/api/module/abc1234') + .expect(404) + .expect('Error: Module with id abc1234 not found'); + }); + + it('should provide download for not existing modules', async () => { + await request(app).get('/api/module/abc1234/download') + .expect(500); + }); + + it('should allow interacting with all modules', async () => { + await request(app).post('/api/module/abort') + .expect('Content-Type', /json/) + .expect(200) + .expect({status: 'aborted all services from all modules'}); + await request(app).post('/api/module/stop') + .expect('Content-Type', /json/) + .expect(200) + .expect({status: 'stopped all services from all modules'}); + await request(app).post('/api/module/reset') + .expect('Content-Type', /json/) + .expect(200) + .expect({status: 'reset all services from all modules'}); + }); + + it('should fail wile loading module with empty content', async () => { + await request(app).put('/api/module') + .send({}) + .expect('Content-Type', /json/) + .expect(500); + }); + + it('should fail while loading module without content', async () => { + await request(app).put('/api/module') + .send(null) + .expect('Content-Type', /json/) + .expect(500); + }); + + it('should fail while connecting a not existing module', async () => { + await request(app).post('/api/module/test/connect') + .send(null) + .expect(500) + .expect('Content-Type', /json/) + .expect(/Error: Module with id test not found/); + }); + + it('should fail while disconnecting from a not existing module', async () => { + await request(app).post('/api/module/test/disconnect') + .send(null) + .expect(500) + .expect('Content-Type', /json/) + .expect(/Error: Module with id test not found/); + }); + + describe('with test module', () => { + let moduleServer: ModuleTestServer; + + before(async () => { + moduleServer = new ModuleTestServer(); + await moduleServer.start(); + }); + + after(async () => { + await moduleServer.shutdown(); + }); + + it('should load, get, disconnect and delete module', async () => { + const options = + JSON.parse(fs.readFileSync('assets/modules/module_testserver_1.0.0.json').toString()).modules[0]; + await request(app).put('/api/module') + .send({module: options}) + .expect(200) + .expect('Content-Type', /json/) + .expect(/"connected":false/) + .expect(/"protected":false/); + + // wait until first update of state via websocket + const ws = new WebSocket('ws:/localhost:3000'); + await new Promise((resolve) => ws.on('message', function incoming(msg) { + const data = JSON.parse(msg.toString()); + if (data.data && data.data.status) { + ws.removeListener('message', incoming); + resolve(); + } + })); + + await request(app).get('/api/module/CIF') + .expect(200) + .expect('Content-Type', /json/) + .expect(/"status":"IDLE"/); + + await request(app).post('/api/module/CIF/disconnect') + .expect(200) + .expect('Content-Type', /json/) + .expect(/"status":"Succesfully disconnected"/); + + await request(app).delete('/api/module/CIF') + .expect(200) + .expect('Content-Type', /json/) + .expect({status: 'Successful deleted', id: 'CIF'}); + }); }); }); @@ -182,10 +291,20 @@ describe('Routes', () => { }); context('#recipeRoutes', () => { - it('should provide recipes', (done) => { - request(app).get('/api/recipe') + it('should provide recipes', async () => { + await request(app).get('/api/recipe') .expect('Content-Type', /json/) - .expect(200, done); + .expect(200); + }); + + it('should provide specific recipe', async () => { + await request(app).get('/api/recipe/abc123') + .expect(500); + }); + + it('should delete not existing recipe', async () => { + await request(app).delete('/api/recipe/abc123') + .expect(400); }); }); -}); \ No newline at end of file +}); diff --git a/tslint.json b/tslint.json index bb9b085c..9c19f7cc 100755 --- a/tslint.json +++ b/tslint.json @@ -5,6 +5,7 @@ true, "single" ], + "object-literal-shorthand": false, "object-literal-key-quotes": false, "object-literal-sort-keys": false, "ordered-imports": true, @@ -22,9 +23,14 @@ "trailing-comma": [ true ], - "variable-name": [ - true - ], + "variable-name": { + "options": [ + "ban-keywords", + "check-format", + "allow-leading-underscore" + ] + }, + "no-bitwise": false, "no-unused-variable": true, "typedef": [ true,