├── env.d.ts
├── docs
└── example.png
├── public
├── favicon.ico
└── profile.xml
├── .prettierrc.json
├── .editorconfig
├── .vscode
└── extensions.json
├── src
├── assets
│ ├── main.css
│ └── base.css
├── main.ts
├── ProfileEditor
│ ├── node
│ │ ├── index.ts
│ │ ├── operationNode.ts
│ │ ├── instrumentNode.ts
│ │ ├── modelNode.ts
│ │ ├── configNode.ts
│ │ ├── OperationNode.vue
│ │ ├── InstrumentNode.vue
│ │ ├── ModelNode.vue
│ │ └── ConfigNode.vue
│ ├── assets
│ │ ├── rotate-ccw.svg
│ │ ├── database.svg
│ │ ├── zoom-out.svg
│ │ ├── download.svg
│ │ ├── import.svg
│ │ ├── computer.svg
│ │ ├── maximize.svg
│ │ ├── minimize.svg
│ │ ├── zoom-in.svg
│ │ ├── bolt.svg
│ │ └── zap.svg
│ ├── icons
│ │ ├── ArrowLeft.vue
│ │ └── CirclePlus.vue
│ ├── components
│ │ ├── drawer
│ │ │ ├── InstrumentNodeForm.vue
│ │ │ ├── CustomOperationNodeForm.vue
│ │ │ ├── ModelNodeForm.vue
│ │ │ ├── FunctionOperationNodeForm.vue
│ │ │ ├── NiVisaOperationNodeForm.vue
│ │ │ ├── MethodEditor
│ │ │ │ ├── ParameterForm.vue
│ │ │ │ └── MethodEditor.vue
│ │ │ ├── MeasureModeEditor
│ │ │ │ ├── ByteStreamForm.vue
│ │ │ │ ├── WorkPlaceList.vue
│ │ │ │ └── MeasureModeEditor.vue
│ │ │ ├── ConfigNodeForm.vue
│ │ │ └── NodeEditDrawer.vue
│ │ └── ControlPanel.vue
│ ├── utils
│ │ ├── inital.ts
│ │ └── adaptor.ts
│ ├── common.ts
│ ├── types.ts
│ └── ProfileEditor.vue
└── App.vue
├── tsconfig.json
├── index.html
├── tsconfig.app.json
├── README.md
├── .gitignore
├── vite.config.ts
├── tsconfig.node.json
├── eslint.config.js
└── package.json
/env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
--------------------------------------------------------------------------------
/docs/example.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ChangeSuger/instrument-profile-editor/HEAD/docs/example.png
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ChangeSuger/instrument-profile-editor/HEAD/public/favicon.ico
--------------------------------------------------------------------------------
/.prettierrc.json:
--------------------------------------------------------------------------------
1 |
2 | {
3 | "$schema": "https://json.schemastore.org/prettierrc",
4 | "semi": false,
5 | "singleQuote": true,
6 | "printWidth": 100
7 | }
8 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | [*.{js,jsx,mjs,cjs,ts,tsx,mts,cts,vue}]
2 | charset = utf-8
3 | indent_size = 2
4 | indent_style = space
5 | insert_final_newline = true
6 | trim_trailing_whitespace = true
7 |
--------------------------------------------------------------------------------
/.vscode/extensions.json:
--------------------------------------------------------------------------------
1 | {
2 | "recommendations": [
3 | "Vue.volar",
4 | "dbaeumer.vscode-eslint",
5 | "EditorConfig.EditorConfig",
6 | "esbenp.prettier-vscode"
7 | ]
8 | }
9 |
--------------------------------------------------------------------------------
/src/assets/main.css:
--------------------------------------------------------------------------------
1 | @import './base.css';
2 |
3 | #app {
4 | width: 100vw;
5 | height: 100vh;
6 | margin: 0;
7 | padding: 0;
8 | overflow: hidden;
9 | font-weight: normal;
10 | }
11 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "files": [],
3 | "references": [
4 | {
5 | "path": "./tsconfig.node.json"
6 | },
7 | {
8 | "path": "./tsconfig.app.json"
9 | }
10 | ]
11 | }
12 |
--------------------------------------------------------------------------------
/src/main.ts:
--------------------------------------------------------------------------------
1 | import './assets/main.css';
2 |
3 | import { createApp } from 'vue';
4 |
5 | import ArcoVue from '@arco-design/web-vue';
6 | import App from './App.vue';
7 |
8 | import '@arco-design/web-vue/dist/arco.css';
9 |
10 | createApp(App).use(ArcoVue).mount('#app');
11 |
--------------------------------------------------------------------------------
/src/ProfileEditor/node/index.ts:
--------------------------------------------------------------------------------
1 | import InstrumentNode from "./instrumentNode";
2 | import ModelNode from "./modelNode";
3 | import ConfigNode from "./configNode";
4 | import OperationNode from "./operationNode";
5 |
6 | export {
7 | InstrumentNode,
8 | ModelNode,
9 | ConfigNode,
10 | OperationNode
11 | };
--------------------------------------------------------------------------------
/src/ProfileEditor/assets/rotate-ccw.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/ProfileEditor/assets/database.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/ProfileEditor/assets/zoom-out.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/ProfileEditor/assets/download.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/ProfileEditor/assets/import.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/ProfileEditor/assets/computer.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/ProfileEditor/assets/maximize.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/ProfileEditor/assets/minimize.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/ProfileEditor/assets/zoom-in.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/ProfileEditor/assets/bolt.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/ProfileEditor/assets/zap.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Instrument Profile Editor
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/tsconfig.app.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "@vue/tsconfig/tsconfig.dom.json",
3 | "include": ["env.d.ts", "src/**/*", "src/**/*.vue"],
4 | "exclude": ["src/**/__tests__/*"],
5 | "compilerOptions": {
6 | "composite": true,
7 | "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
8 |
9 | "baseUrl": ".",
10 | "paths": {
11 | "@/*": ["./src/*"]
12 | }
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # INSTRUMENT-PROFILE-EDITOR
2 |
3 | 基于 Vue3 + [LogicFlow](https://github.com/didi/LogicFlow) 实现的仪器配置文件编辑器。
4 |
5 | 
6 |
7 | ## 本地开发
8 |
9 | ```bash
10 | # 安装依赖
11 | npm install
12 |
13 | # 启动本地服务
14 | npm run dev
15 | ```
16 |
17 | ## 物料来源
18 |
19 | | 物料 | 来源 |
20 | | --- | --- |
21 | | Icon | [Lucide](https://lucide.dev/) |
22 | | 组件库 | [Arco Vue](https://arco.design/vue/docs/start) |
23 |
--------------------------------------------------------------------------------
/src/ProfileEditor/icons/ArrowLeft.vue:
--------------------------------------------------------------------------------
1 |
2 |
16 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 | pnpm-debug.log*
8 | lerna-debug.log*
9 |
10 | node_modules
11 | .DS_Store
12 | dist
13 | dist-ssr
14 | coverage
15 | *.local
16 |
17 | /cypress/videos/
18 | /cypress/screenshots/
19 |
20 | # Editor directories and files
21 | .vscode/*
22 | !.vscode/extensions.json
23 | .idea
24 | *.suo
25 | *.ntvs*
26 | *.njsproj
27 | *.sln
28 | *.sw?
29 |
30 | *.tsbuildinfo
31 |
--------------------------------------------------------------------------------
/src/ProfileEditor/icons/CirclePlus.vue:
--------------------------------------------------------------------------------
1 |
2 |
17 |
--------------------------------------------------------------------------------
/vite.config.ts:
--------------------------------------------------------------------------------
1 | import { fileURLToPath, URL } from 'node:url'
2 |
3 | import { defineConfig } from 'vite'
4 | import vue from '@vitejs/plugin-vue'
5 | import vueDevTools from 'vite-plugin-vue-devtools'
6 |
7 | // https://vite.dev/config/
8 | export default defineConfig({
9 | plugins: [
10 | vue(),
11 | vueDevTools(),
12 | ],
13 | resolve: {
14 | alias: {
15 | '@': fileURLToPath(new URL('./src', import.meta.url))
16 | },
17 | },
18 | })
19 |
--------------------------------------------------------------------------------
/tsconfig.node.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "@tsconfig/node22/tsconfig.json",
3 | "include": [
4 | "vite.config.*",
5 | "vitest.config.*",
6 | "cypress.config.*",
7 | "nightwatch.conf.*",
8 | "playwright.config.*"
9 | ],
10 | "compilerOptions": {
11 | "composite": true,
12 | "noEmit": true,
13 | "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
14 |
15 | "module": "ESNext",
16 | "moduleResolution": "Bundler",
17 | "types": ["node"]
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/eslint.config.js:
--------------------------------------------------------------------------------
1 | import pluginVue from 'eslint-plugin-vue';
2 | import vueTsEslintConfig from '@vue/eslint-config-typescript';
3 | import skipFormatting from '@vue/eslint-config-prettier/skip-formatting';
4 |
5 | export default [
6 | {
7 | name: 'app/files-to-lint',
8 | files: ['**/*.{ts,mts,tsx,vue}'],
9 | },
10 |
11 | {
12 | name: 'app/files-to-ignore',
13 | ignores: ['**/dist/**', '**/dist-ssr/**', '**/coverage/**'],
14 | },
15 |
16 | ...pluginVue.configs['flat/essential'],
17 | ...vueTsEslintConfig(),
18 | skipFormatting,
19 | {
20 | rules: {
21 | 'semi': 2,
22 | 'vue/no-mutating-props': 0,
23 | }
24 | }
25 | ];
26 |
--------------------------------------------------------------------------------
/src/ProfileEditor/components/drawer/InstrumentNodeForm.vue:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
43 |
44 |
45 |
--------------------------------------------------------------------------------
/src/ProfileEditor/components/drawer/CustomOperationNodeForm.vue:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
46 |
47 |
48 |
--------------------------------------------------------------------------------
/src/App.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | 导入示例数据
6 |
7 |
8 | 校验
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
40 |
41 |
48 |
--------------------------------------------------------------------------------
/src/ProfileEditor/utils/inital.ts:
--------------------------------------------------------------------------------
1 | import type { InstrumentNodeData, ModelNodeData, ConfigNodeData, OperationNodeData } from '../types';
2 |
3 | type FlodedData = { isFloded: boolean };
4 |
5 | export function initInstrumentNodeData(): InstrumentNodeData {
6 | return {
7 | id: '',
8 | };
9 | }
10 |
11 | export function initModelNodeData(): ModelNodeData & FlodedData {
12 | return {
13 | isFloded: false,
14 | id: '',
15 | configType: 'NI-VISA',
16 | };
17 | }
18 |
19 | export function initConfigNodeData(): ConfigNodeData & FlodedData {
20 | return {
21 | isFloded: false,
22 | id: 'NI-VISA',
23 | spaceName: '',
24 | className: '',
25 | dllTemplate: '',
26 | isVisa: 'false',
27 | communicationType: 'RS232',
28 | communicationConfig: {
29 | baudRate: '9600',
30 | dataBits: '8',
31 | stopBits: '1',
32 | parity: '0',
33 | bufferBytes: '0',
34 | handShake: '0',
35 | timeout: '2000',
36 | ip: '',
37 | port: '',
38 | },
39 | };
40 | }
41 |
42 | export function initOperationNodeData(): OperationNodeData {
43 | return {
44 | id: '',
45 | parameter: '',
46 | hasReturn: 'false',
47 | command: '',
48 | methods: [],
49 | measureModes: [],
50 | };
51 | }
52 |
--------------------------------------------------------------------------------
/src/ProfileEditor/components/drawer/ModelNodeForm.vue:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
11 |
12 |
13 |
18 |
19 |
20 |
21 |
22 |
51 |
52 |
53 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "profile-editor",
3 | "version": "0.0.0",
4 | "private": true,
5 | "type": "module",
6 | "scripts": {
7 | "dev": "vite",
8 | "build": "run-p type-check \"build-only {@}\" --",
9 | "preview": "vite preview",
10 | "build-only": "vite build",
11 | "type-check": "vue-tsc --build --force",
12 | "lint": "eslint . --fix",
13 | "format": "prettier --write src/"
14 | },
15 | "dependencies": {
16 | "@logicflow/core": "^2.0.7",
17 | "@logicflow/extension": "^2.0.11",
18 | "events": "^3.3.0",
19 | "lodash-es": "^4.17.21",
20 | "sass": "^1.69.7",
21 | "uuid": "^11.0.3",
22 | "vue": "^3.5.12",
23 | "xml2js": "^0.6.2"
24 | },
25 | "devDependencies": {
26 | "@arco-design/web-vue": "^2.54.5",
27 | "@tsconfig/node22": "^22.0.0",
28 | "@types/lodash-es": "^4.17.12",
29 | "@types/node": "^22.9.0",
30 | "@types/xml2js": "^0.4.14",
31 | "@vitejs/plugin-vue": "^5.1.4",
32 | "@vue/eslint-config-prettier": "^10.1.0",
33 | "@vue/eslint-config-typescript": "^14.1.3",
34 | "@vue/tsconfig": "^0.5.1",
35 | "eslint": "^9.14.0",
36 | "eslint-plugin-vue": "^9.30.0",
37 | "npm-run-all2": "^7.0.1",
38 | "prettier": "^3.3.3",
39 | "typescript": "~5.6.3",
40 | "vite": "^5.4.10",
41 | "vite-plugin-vue-devtools": "^7.5.4",
42 | "vue-tsc": "^2.1.10"
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/src/ProfileEditor/components/drawer/FunctionOperationNodeForm.vue:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
53 |
54 |
55 |
--------------------------------------------------------------------------------
/src/ProfileEditor/components/drawer/NiVisaOperationNodeForm.vue:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
56 |
57 |
58 |
--------------------------------------------------------------------------------
/src/ProfileEditor/components/drawer/MethodEditor/ParameterForm.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 | 新增参数
33 |
34 |
35 |
36 |
37 |
69 |
70 |
--------------------------------------------------------------------------------
/src/ProfileEditor/components/drawer/MeasureModeEditor/ByteStreamForm.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 | 发送
23 |
24 |
25 |
26 |
27 | 接收
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 | 新增字节流
38 |
39 |
40 |
41 |
42 |
73 |
74 |
--------------------------------------------------------------------------------
/src/assets/base.css:
--------------------------------------------------------------------------------
1 | /* color palette from */
2 | :root {
3 | --vt-c-white: #ffffff;
4 | --vt-c-white-soft: #f8f8f8;
5 | --vt-c-white-mute: #f2f2f2;
6 |
7 | --vt-c-black: #181818;
8 | --vt-c-black-soft: #222222;
9 | --vt-c-black-mute: #282828;
10 |
11 | --vt-c-indigo: #2c3e50;
12 |
13 | --vt-c-divider-light-1: rgba(60, 60, 60, 0.29);
14 | --vt-c-divider-light-2: rgba(60, 60, 60, 0.12);
15 | --vt-c-divider-dark-1: rgba(84, 84, 84, 0.65);
16 | --vt-c-divider-dark-2: rgba(84, 84, 84, 0.48);
17 |
18 | --vt-c-text-light-1: var(--vt-c-indigo);
19 | --vt-c-text-light-2: rgba(60, 60, 60, 0.66);
20 | --vt-c-text-dark-1: var(--vt-c-white);
21 | --vt-c-text-dark-2: rgba(235, 235, 235, 0.64);
22 | }
23 |
24 | /* semantic color variables for this project */
25 | :root {
26 | --color-background: var(--vt-c-white);
27 | --color-background-soft: var(--vt-c-white-soft);
28 | --color-background-mute: var(--vt-c-white-mute);
29 |
30 | --color-border: var(--vt-c-divider-light-2);
31 | --color-border-hover: var(--vt-c-divider-light-1);
32 |
33 | --color-heading: var(--vt-c-text-light-1);
34 | --color-text: var(--vt-c-text-light-1);
35 |
36 | --section-gap: 160px;
37 | }
38 |
39 | @media (prefers-color-scheme: dark) {
40 | :root {
41 | --color-background: var(--vt-c-black);
42 | --color-background-soft: var(--vt-c-black-soft);
43 | --color-background-mute: var(--vt-c-black-mute);
44 |
45 | --color-border: var(--vt-c-divider-dark-2);
46 | --color-border-hover: var(--vt-c-divider-dark-1);
47 |
48 | --color-heading: var(--vt-c-text-dark-1);
49 | --color-text: var(--vt-c-text-dark-2);
50 | }
51 | }
52 |
53 | *,
54 | *::before,
55 | *::after {
56 | box-sizing: border-box;
57 | margin: 0;
58 | font-weight: normal;
59 | }
60 |
61 | body {
62 | min-height: 100vh;
63 | color: var(--color-text);
64 | background: var(--color-background);
65 | transition:
66 | color 0.5s,
67 | background-color 0.5s;
68 | line-height: 1.6;
69 | font-family:
70 | Inter,
71 | -apple-system,
72 | BlinkMacSystemFont,
73 | 'Segoe UI',
74 | Roboto,
75 | Oxygen,
76 | Ubuntu,
77 | Cantarell,
78 | 'Fira Sans',
79 | 'Droid Sans',
80 | 'Helvetica Neue',
81 | sans-serif;
82 | font-size: 15px;
83 | text-rendering: optimizeLegibility;
84 | -webkit-font-smoothing: antialiased;
85 | -moz-osx-font-smoothing: grayscale;
86 | }
87 |
--------------------------------------------------------------------------------
/src/ProfileEditor/common.ts:
--------------------------------------------------------------------------------
1 | export enum NodeType {
2 | Instrument = 'Instrument',
3 | Model = 'Model',
4 | Config = 'Config',
5 | NI_VISA_OPERATION = 'NI-VISA_OPERATION',
6 | FUNCTION_OPERATION = 'FUNCTION_OPERATION',
7 | CUSTOM_OPERATION = 'CUSTOM_OPERATION',
8 | }
9 |
10 | export const OPERATION_NODE_TYPE_MAP = {
11 | 'NI-VISA': NodeType.NI_VISA_OPERATION,
12 | 'FUNCTION': NodeType.FUNCTION_OPERATION,
13 | 'CUSTOM': NodeType.CUSTOM_OPERATION,
14 | } as const;
15 |
16 | export const POSITION_X = {
17 | 'instrument-node': 100,
18 | 'model-node': 350,
19 | 'config-node': 600,
20 | 'operation-node': 850,
21 | } as const;
22 |
23 | export const NODE_WIDTH = 150;
24 | export const NODE_HEIGHT = 30;
25 | export const NODE_WIDTH_HALF = NODE_WIDTH / 2;
26 |
27 | /**
28 | * @description 配置类型
29 | */
30 | export const CONFIG_TYPE_OPTIONS = [
31 | { value: 'NI-VISA', label: 'NI-VISA' },
32 | { value: 'FUNCTION', label: 'FUNCTION' },
33 | { value: 'CUSTOM', label: 'CUSTOM' },
34 | ];
35 |
36 | export const BOOLEAN_STRING_OPTIONS = [
37 | { value: 'true', label: '是' },
38 | { value: 'false', label: '否' },
39 | ];
40 |
41 | /**
42 | * @description 通讯配置-通讯类型
43 | */
44 | export const COMMUNICATION_TYPE_OPTIONS = [
45 | { value: 'RS232', label: 'RS232' },
46 | { value: 'RS485', label: 'RS485' },
47 | { value: 'TCP', label: 'TCP' },
48 | ];
49 |
50 | /**
51 | * @description 方法参数类型
52 | */
53 | export const PARAMETER_TYPE_OPTIONS = [
54 | { value: 'System.Boolean', label: 'Boolean' },
55 | { value: 'System.Char', label: 'Char' },
56 | { value: 'System.Double', label: 'Double' },
57 | { value: 'System.Int32', label: 'Int32' },
58 | { value: 'System.String', label: 'String' },
59 | { value: 'System.Single', label: 'Single' },
60 | ];
61 |
62 | /**
63 | * @description 通讯配置-停止位
64 | */
65 | export const STOP_BITS_OPTIONS = [
66 | { value: '0', label: 'None' },
67 | { value: '1', label: 'One' },
68 | { value: '2', label: 'Two' },
69 | { value: '3', label: 'OnePointFive' },
70 | ];
71 |
72 | /**
73 | * @description 通讯配置-奇偶校验
74 | */
75 | export const PARITY_OPTIONS = [
76 | { value: '0', label: 'None' },
77 | { value: '1', label: 'Odd' },
78 | { value: '2', label: 'Even' },
79 | { value: '3', label: 'Mark' },
80 | { value: '4', label: 'Space' },
81 | ];
82 |
83 | /**
84 | * @description 通讯配置-握手协议
85 | */
86 | export const HAND_SHAKE_OPTIONS = [
87 | { value: '0', label: 'None' },
88 | { value: '1', label: 'Xon/Xoff' },
89 | { value: '2', label: 'RequestToSend' },
90 | { value: '3', label: 'RequestToSendXonXoff' },
91 | ];
92 |
--------------------------------------------------------------------------------
/src/ProfileEditor/node/operationNode.ts:
--------------------------------------------------------------------------------
1 | import { HtmlNode, HtmlNodeModel, type IHtmlNodeProps, type Model, BaseNodeModel } from '@logicflow/core';
2 | import OperationNode from './OperationNode.vue';
3 | import { createApp, h, ref } from 'vue';
4 | import { NODE_WIDTH, NODE_HEIGHT } from '../common';
5 |
6 | class OperationNodeView extends HtmlNode {
7 | root: HTMLDivElement;
8 | vueComponent: typeof OperationNode;
9 | vm: ReturnType | null = null;
10 | vmProps: ReturnType> | null = null;
16 |
17 | constructor(props: IHtmlNodeProps) {
18 | super(props);
19 | this.root = document.createElement('div');
20 | this.root.style.width = '100%';
21 | this.root.style.height = '100%';
22 | this.vueComponent = OperationNode;
23 | }
24 |
25 | shouldUpdate() {
26 | const data = {
27 | id: this.props.model.properties.id,
28 | isSelected: this.props.model.isSelected,
29 | isHovered: this.props.model.isHovered
30 | };
31 | if (this.preProperties && this.preProperties === JSON.stringify(data)) {
32 | return false;
33 | }
34 | this.preProperties = JSON.stringify(data);
35 | return true;
36 | }
37 |
38 | setHtml(rootEl: SVGForeignObjectElement): void {
39 | rootEl.appendChild(this.root);
40 | if (this.vm && this.vmProps) {
41 | this.vmProps.value = {
42 | properties: this.props.model.properties,
43 | isSelected: this.props.model.isSelected,
44 | isHovered: this.props.model.isHovered,
45 | model: this.props.model
46 | };
47 | } else {
48 | // @ts-expect-error 暂时不解决
49 | this.vmProps = ref({
50 | properties: this.props.model.properties,
51 | isSelected: this.props.model.isSelected,
52 | isHovered: this.props.model.isHovered,
53 | model: this.props.model,
54 | });
55 | this.vm = createApp({
56 | render: () => h(this.vueComponent, this.vmProps!.value)
57 | });
58 | this.vm.mount(this.root);
59 | }
60 | }
61 | }
62 |
63 | class OperationNodeModel extends HtmlNodeModel {
64 | setAttributes() {
65 | this.width = NODE_WIDTH;
66 | this.height = NODE_HEIGHT;
67 | }
68 |
69 | getDefaultAnchor(): Model.AnchorConfig[] {
70 | const { width, x, y, id } = this;
71 | return [
72 | {
73 | x: x - width / 2,
74 | y,
75 | name: 'left',
76 | id: `${id}_0`
77 | },
78 | {
79 | x: x + width / 2,
80 | y,
81 | name: 'right',
82 | id: `${id}_1`,
83 | },
84 | ];
85 | }
86 | }
87 |
88 | export default {
89 | type: 'operation-node',
90 | model: OperationNodeModel,
91 | view: OperationNodeView
92 | };
--------------------------------------------------------------------------------
/src/ProfileEditor/node/instrumentNode.ts:
--------------------------------------------------------------------------------
1 | import { HtmlNode, HtmlNodeModel, type IHtmlNodeProps, type Model } from '@logicflow/core';
2 | import InstrumentNode from './InstrumentNode.vue';
3 | import { createApp, h, ref } from 'vue';
4 | import { NODE_WIDTH, NODE_HEIGHT } from '../common';
5 |
6 | class InstrumentNodeView extends HtmlNode {
7 | root: HTMLDivElement;
8 | vueComponent: typeof InstrumentNode;
9 | vm: ReturnType | null = null;
10 | vmProps: ReturnType> | null = null;
16 |
17 | constructor(props: IHtmlNodeProps) {
18 | super(props);
19 | this.root = document.createElement('div');
20 | this.root.style.width = '100%';
21 | this.root.style.height = '100%';
22 | this.vueComponent = InstrumentNode;
23 | }
24 |
25 | shouldUpdate() {
26 | const data = {
27 | id: this.props.model.properties.id,
28 | isSelected: this.props.model.isSelected,
29 | isHovered: this.props.model.isHovered
30 | };
31 | if (this.preProperties && this.preProperties === JSON.stringify(data)) {
32 | return false;
33 | }
34 | this.preProperties = JSON.stringify(data);
35 | return true;
36 | }
37 |
38 | setHtml(rootEl: SVGForeignObjectElement): void {
39 | rootEl.appendChild(this.root);
40 | if (this.vm && this.vmProps) {
41 | this.vmProps.value = {
42 | properties: this.props.model.properties,
43 | isSelected: this.props.model.isSelected,
44 | isHovered: this.props.model.isHovered,
45 | model: this.props.model
46 | };
47 | } else {
48 | // @ts-expect-error 暂时不解决
49 | this.vmProps = ref({
50 | properties: this.props.model.properties,
51 | isSelected: this.props.model.isSelected,
52 | isHovered: this.props.model.isHovered,
53 | model: this.props.model,
54 | });
55 | this.vm = createApp({
56 | render: () => h(this.vueComponent, this.vmProps!.value)
57 | });
58 | this.vm.mount(this.root);
59 | }
60 | }
61 | }
62 |
63 | class InstrumentNodeModel extends HtmlNodeModel {
64 | setAttributes() {
65 | this.width = NODE_WIDTH;
66 | this.height = NODE_HEIGHT;
67 | }
68 |
69 | getDefaultAnchor(): Model.AnchorConfig[] {
70 | const { width, x, y, id } = this;
71 | return [
72 | {
73 | x: x - width / 2,
74 | y,
75 | name: 'left',
76 | id: `${id}_0`
77 | },
78 | {
79 | x: x + width / 2,
80 | y,
81 | name: 'right',
82 | id: `${id}_1`,
83 | },
84 | ];
85 | }
86 | }
87 |
88 | export default {
89 | type: 'instrument-node',
90 | model: InstrumentNodeModel,
91 | view: InstrumentNodeView
92 | };
93 |
--------------------------------------------------------------------------------
/src/ProfileEditor/node/modelNode.ts:
--------------------------------------------------------------------------------
1 | import { HtmlNode, HtmlNodeModel, type IHtmlNodeProps, type Model } from '@logicflow/core';
2 | import ModelNode from './ModelNode.vue';
3 | import { createApp, h, ref } from 'vue';
4 | import { NODE_WIDTH, NODE_HEIGHT } from '../common';
5 |
6 | class ModelNodeView extends HtmlNode {
7 | root: HTMLDivElement;
8 | vueComponent: typeof ModelNode;
9 | vm: ReturnType | null = null;
10 | vmProps: ReturnType> | null = null;
17 |
18 | constructor(props: IHtmlNodeProps) {
19 | super(props);
20 | this.root = document.createElement('div');
21 | this.root.style.width = '100%';
22 | this.root.style.height = '100%';
23 | this.vueComponent = ModelNode;
24 | }
25 |
26 | shouldUpdate() {
27 | const data = {
28 | id: this.props.model.properties.id,
29 | isFloded: this.props.model.properties.isFloded,
30 | isSelected: this.props.model.isSelected,
31 | isHovered: this.props.model.isHovered
32 | };
33 | if (this.preProperties && this.preProperties === JSON.stringify(data)) {
34 | return false;
35 | }
36 | this.preProperties = JSON.stringify(data);
37 | return true;
38 | }
39 |
40 | setHtml(rootEl: SVGForeignObjectElement): void {
41 | rootEl.appendChild(this.root);
42 | if (this.vm && this.vmProps) {
43 | this.vmProps.value = {
44 | properties: this.props.model.properties,
45 | isSelected: this.props.model.isSelected,
46 | isHovered: this.props.model.isHovered,
47 | isFloded: this.props.model.properties.isFloded as boolean,
48 | model: this.props.model
49 | };
50 | } else {
51 | // @ts-expect-error 暂时不解决
52 | this.vmProps = ref({
53 | properties: this.props.model.properties,
54 | isSelected: this.props.model.isSelected,
55 | isHovered: this.props.model.isHovered,
56 | isFloded: this.props.model.properties.isFloded,
57 | model: this.props.model,
58 | });
59 | this.vm = createApp({
60 | render: () => h(this.vueComponent, this.vmProps!.value)
61 | });
62 | this.vm.mount(this.root);
63 | }
64 | }
65 | }
66 |
67 | class ModelNodeModel extends HtmlNodeModel {
68 | setAttributes() {
69 | this.width = NODE_WIDTH;
70 | this.height = NODE_HEIGHT;
71 | }
72 |
73 | getDefaultAnchor(): Model.AnchorConfig[] {
74 | const { width, x, y, id } = this;
75 | return [
76 | {
77 | x: x - width / 2,
78 | y,
79 | name: 'left',
80 | id: `${id}_0`
81 | },
82 | {
83 | x: x + width / 2,
84 | y,
85 | name: 'right',
86 | id: `${id}_1`,
87 | },
88 | ];
89 | }
90 | }
91 |
92 | export default {
93 | type: 'model-node',
94 | model: ModelNodeModel,
95 | view: ModelNodeView
96 | };
97 |
--------------------------------------------------------------------------------
/src/ProfileEditor/node/configNode.ts:
--------------------------------------------------------------------------------
1 | import { HtmlNode, HtmlNodeModel, type IHtmlNodeProps, type Model } from '@logicflow/core';
2 | import ConfigNode from './ConfigNode.vue';
3 | import { createApp, h, ref } from 'vue';
4 | import { NODE_WIDTH, NODE_HEIGHT } from '../common';
5 |
6 | class ConfigNodeView extends HtmlNode {
7 | root: HTMLDivElement;
8 | vueComponent: typeof ConfigNode;
9 | vm: ReturnType | null = null;
10 | vmProps: ReturnType> | null = null;
17 |
18 | constructor(props: IHtmlNodeProps) {
19 | super(props);
20 | this.root = document.createElement('div');
21 | this.root.style.width = '100%';
22 | this.root.style.height = '100%';
23 | this.vueComponent = ConfigNode;
24 | }
25 |
26 | shouldUpdate() {
27 | const data = {
28 | id: this.props.model.properties.id,
29 | isFloded: this.props.model.properties.isFloded,
30 | isSelected: this.props.model.isSelected,
31 | isHovered: this.props.model.isHovered,
32 | };
33 | if (this.preProperties && this.preProperties === JSON.stringify(data)) {
34 | return false;
35 | }
36 | this.preProperties = JSON.stringify(data);
37 | return true;
38 | }
39 |
40 | setHtml(rootEl: SVGForeignObjectElement): void {
41 | rootEl.appendChild(this.root);
42 | if (this.vm && this.vmProps) {
43 | this.vmProps.value = {
44 | properties: this.props.model.properties,
45 | isSelected: this.props.model.isSelected,
46 | isHovered: this.props.model.isHovered,
47 | isFloded: this.props.model.properties.isFloded as boolean,
48 | model: this.props.model
49 | };
50 | } else {
51 | // @ts-expect-error 暂时不解决
52 | this.vmProps = ref({
53 | properties: this.props.model.properties,
54 | isSelected: this.props.model.isSelected,
55 | isHovered: this.props.model.isHovered,
56 | isFloded: this.props.model.properties.isFloded,
57 | model: this.props.model,
58 | });
59 | this.vm = createApp({
60 | render: () => h(this.vueComponent, this.vmProps!.value)
61 | });
62 | this.vm.mount(this.root);
63 | }
64 | }
65 | }
66 |
67 | class ConfigNodeModel extends HtmlNodeModel {
68 | setAttributes() {
69 | this.width = NODE_WIDTH;
70 | this.height = NODE_HEIGHT;
71 | }
72 |
73 | getDefaultAnchor(): Model.AnchorConfig[] {
74 | const { width, x, y, id } = this;
75 | return [
76 | {
77 | x: x - width / 2,
78 | y,
79 | name: 'left',
80 | id: `${id}_0`
81 | },
82 | {
83 | x: x + width / 2,
84 | y,
85 | name: 'right',
86 | id: `${id}_1`,
87 | },
88 | ];
89 | }
90 | }
91 |
92 | export default {
93 | type: 'config-node',
94 | model: ConfigNodeModel,
95 | view: ConfigNodeView
96 | };
97 |
--------------------------------------------------------------------------------
/src/ProfileEditor/node/OperationNode.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | {{ properties.id || '操作' }}
9 |
10 |
11 |
12 |
13 | {{ errorInfo }}
14 |
15 |
16 |
17 |
18 |
57 |
58 |
117 |
--------------------------------------------------------------------------------
/src/ProfileEditor/components/drawer/MeasureModeEditor/WorkPlaceList.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | 工位
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 | 新增工位
45 |
46 |
47 |
48 |
49 |
--------------------------------------------------------------------------------
/src/ProfileEditor/components/drawer/MethodEditor/MethodEditor.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 | 方法
4 |
5 |
6 |
7 |
8 |
9 | 方法名
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 | 新增方法
46 |
47 |
48 |
49 |
50 |
100 |
101 |
--------------------------------------------------------------------------------
/public/profile.xml:
--------------------------------------------------------------------------------
1 |
2 | <设备类别 id="万用表">
3 | <型号 id="DMM6500-0">
4 | <配置方式>NI-VISA配置方式>
5 | <配置 id="NI-VISA">
6 | <操作 id="采集直流电压" Parameter="" HasReturn="true">
7 | <指令>abort;MEASure:VOLTage:DC?指令>
8 | 操作>
9 | <操作 id="采集交流电压" Parameter="" HasReturn="true">
10 | <指令>abort;MEASure:CURRent:AC?指令>
11 | 操作>
12 | <操作 id="采集直流电流" Parameter="" HasReturn="true">
13 | <指令>abort;MEASure:CURRent:DC?指令>
14 | 操作>
15 | <操作 id="采集电阻" Parameter="" HasReturn="true">
16 | <指令>abort;MEAS:RES?指令>
17 | 操作>
18 | 配置>
19 | 型号>
20 | <型号 id="DMM6500-1">
21 | <配置方式>FUNCTION配置方式>
22 | <配置 id="FUNCTION">
23 | <类 Type="命名空间.类名, DLL模板" IsVISA="true"/>
24 | <操作 id="连接检测" Parameter="" HasReturn="true">
25 | <方法 Name="CheckConnect"/>
26 | 操作>
27 | <操作 id="PICO设置" Parameter="" HasReturn="true">
28 | <方法 Name="setAmplitude">
29 | <参数 Type="System.Int32" Value="5"/>
30 | <参数 Type="System.String" Value="V"/>
31 | <参数 Type="System.String" Value="AC"/>
32 | <参数 Type="System.Int32" Value="1"/>
33 | 方法>
34 | <方法 Name="setAmplitude">
35 | <参数 Type="System.Int32" Value="5"/>
36 | <参数 Type="System.String" Value="V"/>
37 | <参数 Type="System.String" Value="AC"/>
38 | <参数 Type="System.Int32" Value="2"/>
39 | 方法>
40 | <方法 Name="setTime">
41 | <参数 Type="System.Int32" Value="40000"/>
42 | <参数 Type="System.Int32" Value="500"/>
43 | <参数 Type="System.String" Value="us"/>
44 | 方法>
45 | <方法 Name="setBuffer">
46 | <参数 Type="System.Int32" Value="1"/>
47 | 方法>
48 | <方法 Name="setBuffer">
49 | <参数 Type="System.Int32" Value="2"/>
50 | 方法>
51 | 操作>
52 | 配置>
53 | 型号>
54 | <型号 id="DMM6500-2">
55 | <配置方式>CUSTOM配置方式>
56 | <配置 id="CUSTOM">
57 | <通信方法>TCP通信方法>
58 |
59 | 192.172.0.1
60 | <端口>80端口>
61 |
62 | <操作 id="切换">
63 | <测量模式 id="S01">
64 | <工位 id="1">
65 | <字节流 send="0x00" receive="0x00"/>
66 | <字节流 send="0x00" receive="0x00"/>
67 | 工位>
68 | <工位 id="2">
69 | <字节流 send="0x00" receive="0x00"/>
70 | <字节流 send="0x00" receive="0x00"/>
71 | 工位>
72 | <工位 id="3">
73 | <字节流 send="0x00" receive="0x00"/>
74 | <字节流 send="0x00" receive="0x00"/>
75 | 工位>
76 | <工位 id="4">
77 | <字节流 send="0x00" receive="0x00"/>
78 | <字节流 send="0x00" receive="0x00"/>
79 | 工位>
80 | 测量模式>
81 | <测量模式 id="S02">
82 | <工位 id="1">
83 | <字节流 send="0x00" receive="0x00"/>
84 | <字节流 send="0x00" receive="0x00"/>
85 | 工位>
86 | <工位 id="2">
87 | <字节流 send="0x00" receive="0x00"/>
88 | <字节流 send="0x00" receive="0x00"/>
89 | 工位>
90 | <工位 id="3">
91 | <字节流 send="0x00" receive="0x00"/>
92 | <字节流 send="0x00" receive="0x00"/>
93 | 工位>
94 | <工位 id="4">
95 | <字节流 send="0x00" receive="0x00"/>
96 | <字节流 send="0x00" receive="0x00"/>
97 | 工位>
98 | 测量模式>
99 | 操作>
100 | 配置>
101 | 型号>
102 | 设备类别>
--------------------------------------------------------------------------------
/src/ProfileEditor/components/drawer/MeasureModeEditor/MeasureModeEditor.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 | 测量模式
4 |
5 |
6 |
7 |
8 |
9 | 测量模式
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 | 新增测量模式
46 |
47 |
48 |
49 |
50 |
100 |
101 |
--------------------------------------------------------------------------------
/src/ProfileEditor/node/InstrumentNode.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | {{ properties.id || '设备类别' }}
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 | {{ errorInfo }}
18 |
19 |
20 |
21 |
22 |
77 |
78 |
162 |
--------------------------------------------------------------------------------
/src/ProfileEditor/components/drawer/ConfigNodeForm.vue:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
29 |
30 |
31 |
32 |
33 |
34 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
139 |
140 |
--------------------------------------------------------------------------------
/src/ProfileEditor/components/ControlPanel.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | 导入
6 |
7 |
8 |
9 | 导出
10 |
11 |
12 |
13 | 放大
14 |
15 |
16 |
17 | 缩小
18 |
19 |
20 |
21 | 重置
22 |
23 |
24 |
25 | 折叠
26 |
27 |
28 |
29 | 展开
30 |
31 |
32 |
33 |
34 |
137 |
138 |
165 |
--------------------------------------------------------------------------------
/src/ProfileEditor/types.ts:
--------------------------------------------------------------------------------
1 | // 用于数据转换的配置文件定义
2 | export type ProfileData = InstrumentNodeData & {
3 | models: ModelData[];
4 | }
5 |
6 | export type ModelData = ModelNodeData & {
7 | 'NI-VISA'?: ConfigNodeDataMap['NI-VISA'] & { operations: OperationNodeDataMap['NI-VISA'][] };
8 | 'FUNCTION'?: ConfigNodeDataMap['FUNCTION'] & { operations: OperationNodeDataMap['FUNCTION'][] };
9 | 'CUSTOM'?: ConfigNodeDataMap['CUSTOM'] & { operations: OperationNodeDataMap['CUSTOM'][] };
10 | };
11 |
12 | // 流程图节点数据梳理
13 |
14 | export type ConfigType = 'NI-VISA' | 'FUNCTION' | 'CUSTOM';
15 | export type CommunicationType = 'RS232' | 'RS485' | 'TCP';
16 |
17 | export type InstrumentNodeData = {
18 | /**
19 | * 设备类别
20 | */
21 | id: string;
22 | }
23 |
24 | export type ModelNodeData = {
25 | /**
26 | * 设备型号
27 | */
28 | id: string;
29 | /**
30 | * 配置方式
31 | */
32 | configType: ConfigType;
33 | }
34 |
35 | export type ConfigNodeData =
36 | Omit &
37 | Omit &
38 | Omit & {
39 | id: ConfigType;
40 | };
41 |
42 | export type ConfigNodeDataMap = {
43 | 'NI-VISA': {
44 | id: 'NI-VISA';
45 | };
46 | 'FUNCTION': {
47 | id: 'FUNCTION';
48 | spaceName: string;
49 | className: string;
50 | dllTemplate: string;
51 | isVisa: BooleanString;
52 | }
53 | 'CUSTOM': {
54 | id: 'CUSTOM';
55 | } & {
56 | communicationType: 'RS232' | 'RS485' | 'TCP';
57 | communicationConfig: {
58 | /**
59 | * 波特率
60 | */
61 | baudRate: string;
62 | /**
63 | * 数据位
64 | */
65 | dataBits: string;
66 | /**
67 | * 停止位
68 | * - `0`: None
69 | * - `1`: One
70 | * - `2`: Two
71 | * - `3`: OnePointFive
72 | */
73 | stopBits: string;
74 | /**
75 | * 奇偶校验
76 | * - `0`: None
77 | * - `1`: Odd
78 | * - `2`: Even
79 | * - `3`: Mark
80 | * - `4`: Space
81 | */
82 | parity: string;
83 | /**
84 | * 缓冲区字节数
85 | */
86 | bufferBytes: string;
87 | /**
88 | * 握手协议
89 | * - `0`: None
90 | * - `1`: Xon/Xoff
91 | * - `2`: RequestToSend
92 | * - `3`: RequestToSendXonXoff
93 | */
94 | handShake: string;
95 | /**
96 | * 超时时间
97 | */
98 | timeout: string;
99 | /**
100 | * IP 地址
101 | */
102 | ip: string;
103 | /**
104 | * 端口号
105 | */
106 | port: string;
107 | }
108 | }
109 | }
110 |
111 | export type OperationNodeData = OperationNodeDataMap['NI-VISA'] & OperationNodeDataMap['FUNCTION'] & OperationNodeDataMap['CUSTOM'];
112 |
113 | export type OperationNodeDataMap = {
114 | 'NI-VISA': {
115 | id: string;
116 | parameter: string;
117 | hasReturn: BooleanString;
118 | command: string;
119 | }
120 | 'FUNCTION': {
121 | id: string;
122 | parameter: string;
123 | hasReturn: BooleanString;
124 | methods: MethodData[];
125 | }
126 | 'CUSTOM': {
127 | id: string;
128 | measureModes: MeasureMode[];
129 | }
130 | }
131 |
132 | export type MethodData = {
133 | name: string;
134 | parameters: {
135 | type: string;
136 | value: string;
137 | }[];
138 | }
139 |
140 | export type MeasureMode = {
141 | id: string;
142 | workplaces: Workplace[];
143 | }
144 |
145 | export type Workplace = {
146 | id: string;
147 | byteStreams: ByteStream[];
148 | }
149 |
150 | export type ByteStream = {
151 | send: string;
152 | receive: string;
153 | }
154 |
155 | // xml2js 解析 xml 得到的配置文件数据定义
156 |
157 | export type XMLProfileData = {
158 | '设备类别': {
159 | $: { id: string };
160 | '型号': XMLModelData[];
161 | }
162 | }
163 |
164 | export type XMLModelData = {
165 | $: { id: string };
166 | '配置方式': [ConfigType];
167 | '配置': XMLConfigData[];
168 | }
169 |
170 | export type XMLConfigData = XMLConfigDataMap[keyof XMLConfigDataMap];
171 |
172 | export type XMLConfigDataMap = {
173 | 'NI-VISA': {
174 | $: { id: 'NI-VISA' };
175 | '操作': XMLOperationDataMap['NI-VISA'][];
176 | }
177 | 'FUNCTION': {
178 | $: { id: 'FUNCTION' };
179 | '操作': XMLOperationDataMap['FUNCTION'][];
180 | '类': [{ $: {
181 | Type: string;
182 | IsVISA: BooleanString;
183 | }}]
184 | }
185 | 'CUSTOM': {
186 | $: { id: 'CUSTOM' };
187 | '操作': XMLOperationDataMap['CUSTOM'][];
188 | } & (XMLCommunicationConfigDataMap[keyof XMLCommunicationConfigDataMap])
189 | }
190 |
191 | export type XMLCommunicationConfigDataMap = {
192 | 'RS232': {
193 | '通信方法': ['RS232'];
194 | RS232: [{
195 | '波特率': [string];
196 | '数据位': [string];
197 | '停止位': [string];
198 | '奇偶校验': [string];
199 | '缓冲区字节数': [string];
200 | '握手协议': [string];
201 | '超时时间': [string];
202 | }]
203 | }
204 | 'RS485': {
205 | '通信方法': ['RS485'];
206 | RS485: [{
207 | '波特率': [string];
208 | '数据位': [string];
209 | '停止位': [string];
210 | '奇偶校验': [string];
211 | '缓冲区字节数': [string];
212 | '握手协议': [string];
213 | '超时时间': [string];
214 | }]
215 | }
216 | 'TCP': {
217 | '通信方法': ['TCP'];
218 | TCP: [{
219 | IP: [string];
220 | '端口': [string];
221 | }]
222 | }
223 | }
224 |
225 | export type XMLOperationDataMap = {
226 | 'NI-VISA': {
227 | $: { id: string; Parameter: string; HasReturn: BooleanString };
228 | '指令': [string];
229 | }
230 | 'FUNCTION': {
231 | $: { id: string; Parameter: string; HasReturn: BooleanString };
232 | '方法': XMLMethodData[];
233 | }
234 | 'CUSTOM': {
235 | $: { id: string };
236 | '测量模式': XMLMeasureModeData[];
237 | }
238 | }
239 |
240 | export type XMLMethodData = {
241 | $: { Name: string };
242 | '参数': XMLParameterData[];
243 | }
244 |
245 | export type XMLParameterData = {
246 | $: { Type: string; Value: string };
247 | }
248 |
249 | export type XMLMeasureModeData = {
250 | $: { id: string };
251 | '工位': XMLWorkplaceData[];
252 | }
253 |
254 | export type XMLWorkplaceData = {
255 | $: { id: string };
256 | '字节流': XMLByteStreamData[];
257 | }
258 |
259 | export type XMLByteStreamData = {
260 | $: { send: string; receive: string };
261 | }
262 |
263 | export type BooleanString = 'true' | 'false';
264 |
--------------------------------------------------------------------------------
/src/ProfileEditor/components/drawer/NodeEditDrawer.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
35 |
36 |
37 |
42 |
43 |
48 |
49 |
54 |
55 |
60 |
61 |
66 |
67 |
72 |
73 |
74 |
78 |
79 |
80 |
81 |
207 |
208 |
253 |
--------------------------------------------------------------------------------
/src/ProfileEditor/node/ModelNode.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | {{ properties.id || '型号' }}
9 |
10 |
11 |
12 |
13 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 | {{ errorInfo }}
32 |
33 |
34 |
35 |
36 |
166 |
167 |
289 |
--------------------------------------------------------------------------------
/src/ProfileEditor/node/ConfigNode.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | {{ properties.id }}
9 |
10 |
11 |
12 |
13 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 | {{ errorInfo }}
31 |
32 |
33 |
34 |
35 |
179 |
180 |
302 |
--------------------------------------------------------------------------------
/src/ProfileEditor/ProfileEditor.vue:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
11 |
251 |
252 |
275 |
276 |
314 |
--------------------------------------------------------------------------------
/src/ProfileEditor/utils/adaptor.ts:
--------------------------------------------------------------------------------
1 | import type {
2 | ProfileData,
3 | ModelData,
4 | XMLProfileData,
5 | XMLModelData,
6 | XMLConfigDataMap,
7 | XMLCommunicationConfigDataMap,
8 | } from '../types';
9 | import { v4 as uuidv4 } from 'uuid';
10 | import type LogicFlow from '@logicflow/core';
11 | import { NodeType, NODE_WIDTH_HALF } from '../common';
12 | import { initConfigNodeData, initOperationNodeData } from './inital';
13 |
14 | /**
15 | * 将 xml2js 解析 xml 得到的数据适配为配置文件数据
16 | */
17 | export function xmlData2ProfileData(xmlProfileData: XMLProfileData): ProfileData {
18 | const profileData: ProfileData = {
19 | id: xmlProfileData['设备类别'].$.id,
20 | models: [],
21 | };
22 |
23 | for (const xmlModelData of xmlProfileData['设备类别']['型号'] ?? []) {
24 | const modelData: ModelData = {
25 | id: xmlModelData.$.id,
26 | configType: xmlModelData['配置方式'][0],
27 | };
28 | for (const xmlConfigData of xmlModelData['配置'] ?? []) {
29 | if (xmlConfigData.$.id === 'NI-VISA') {
30 | const configData: ModelData['NI-VISA'] = {
31 | id: xmlConfigData.$.id,
32 | operations: (xmlConfigData as XMLConfigDataMap['NI-VISA'])['操作']?.map((xmlOperationData) => {
33 | return {
34 | id: xmlOperationData.$.id,
35 | parameter: xmlOperationData.$.Parameter,
36 | hasReturn: xmlOperationData.$.HasReturn,
37 | command: xmlOperationData['指令'][0],
38 | };
39 | }) || [],
40 | };
41 | if (!modelData['NI-VISA']) {
42 | modelData['NI-VISA'] = configData;
43 | }
44 | } else if (xmlConfigData.$.id === 'FUNCTION') {
45 | const [spaceName = '', nextString = ''] = (xmlConfigData as XMLConfigDataMap['FUNCTION'])['类'][0].$.Type.split('.');
46 | const [className = '', dllTemplate = ''] = nextString.split(', ');
47 | const configData: ModelData['FUNCTION'] = {
48 | id: xmlConfigData.$.id,
49 | spaceName,
50 | className,
51 | dllTemplate,
52 | isVisa: (xmlConfigData as XMLConfigDataMap['FUNCTION'])['类'][0].$.IsVISA,
53 | operations: (xmlConfigData as XMLConfigDataMap['FUNCTION'])['操作']?.map((xmlOperationData) => {
54 | return {
55 | id: xmlOperationData.$.id,
56 | parameter: xmlOperationData.$.Parameter,
57 | hasReturn: xmlOperationData.$.HasReturn,
58 | methods: xmlOperationData['方法']?.map(xmlMethodData => {
59 | return {
60 | name: xmlMethodData.$.Name,
61 | parameters: xmlMethodData['参数']?.map(xmlParameterData => {
62 | return {
63 | type: xmlParameterData.$.Type,
64 | value: xmlParameterData.$.Value,
65 | };
66 | }) || [],
67 | };
68 | }) || [],
69 | };
70 | }) || [],
71 | };
72 | if (!modelData.FUNCTION) {
73 | modelData.FUNCTION = configData;
74 | }
75 | } else if (xmlConfigData.$.id === 'CUSTOM') {
76 | const communicationType = (xmlConfigData as XMLConfigDataMap['CUSTOM'])['通信方法'][0];
77 | if (communicationType === 'RS232' || communicationType === 'RS485') {
78 | // @ts-expect-error 这个类型定义太麻烦了
79 | const communicationConfig: XMLCommunicationConfigDataMap['RS232']['RS232'][0] = xmlConfigData[communicationType][0];
80 | const configData: ModelData['CUSTOM'] = {
81 | id: xmlConfigData.$.id,
82 | communicationType,
83 | communicationConfig: {
84 | baudRate: communicationConfig['波特率'][0],
85 | dataBits: communicationConfig['数据位'][0],
86 | stopBits: communicationConfig['停止位'][0],
87 | parity: communicationConfig['奇偶校验'][0],
88 | bufferBytes: communicationConfig['缓冲区字节数'][0],
89 | handShake: communicationConfig['握手协议'][0],
90 | timeout: communicationConfig['超时时间'][0],
91 | ip: '',
92 | port: '',
93 | },
94 | operations: (xmlConfigData as XMLConfigDataMap['CUSTOM'])['操作']?.map((xmlOperationData) => {
95 | return {
96 | id: xmlOperationData.$.id,
97 | measureModes: xmlOperationData['测量模式']?.map(xmlMeasureModeData => {
98 | return {
99 | id: xmlMeasureModeData.$.id,
100 | workplaces: xmlMeasureModeData['工位']?.map(xmlWorkplaceData => {
101 | return {
102 | id: xmlWorkplaceData.$.id,
103 | byteStreams: xmlWorkplaceData['字节流']?.map(xmlByteStreamData => {
104 | return {
105 | send: xmlByteStreamData.$.send,
106 | receive: xmlByteStreamData.$.receive,
107 | };
108 | }) || [],
109 | };
110 | }) || [],
111 | };
112 | }) || [],
113 | };
114 | }) || [],
115 | };
116 | if (!modelData.CUSTOM) {
117 | modelData.CUSTOM = configData;
118 | }
119 | } else if (communicationType === 'TCP') {
120 | // @ts-expect-error 这个类型定义太麻烦了
121 | const communicationConfig: XMLCommunicationConfigDataMap['TCP']['TCP'][0] = xmlConfigData[communicationType][0];
122 | const configData: ModelData['CUSTOM'] = {
123 | id: xmlConfigData.$.id,
124 | communicationType,
125 | communicationConfig: {
126 | baudRate: '',
127 | dataBits: '',
128 | stopBits: '',
129 | parity: '',
130 | bufferBytes: '',
131 | handShake: '',
132 | timeout: '',
133 | ip: communicationConfig['IP'][0],
134 | port: communicationConfig['端口'][0],
135 | },
136 | operations: (xmlConfigData as XMLConfigDataMap['CUSTOM'])['操作']?.map((xmlOperationData) => {
137 | return {
138 | id: xmlOperationData.$.id,
139 | measureModes: xmlOperationData['测量模式']?.map(xmlMeasureModeData => {
140 | return {
141 | id: xmlMeasureModeData.$.id,
142 | workplaces: xmlMeasureModeData['工位']?.map(xmlWorkplaceData => {
143 | return {
144 | id: xmlWorkplaceData.$.id,
145 | byteStreams: xmlWorkplaceData['字节流']?.map(xmlByteStreamData => {
146 | return {
147 | send: xmlByteStreamData.$.send,
148 | receive: xmlByteStreamData.$.receive,
149 | };
150 | }) || [],
151 | };
152 | }) || [],
153 | };
154 | }) || [],
155 | };
156 | }) || [],
157 | };
158 | if (!modelData.CUSTOM) {
159 | modelData.CUSTOM = configData;
160 | }
161 | }
162 | }
163 | }
164 | profileData.models.push(modelData);
165 | }
166 |
167 | return profileData;
168 | }
169 |
170 |
171 | /**
172 | * 将配置文件数据适配为 xml 数据,使得 xml2js 可以将其转换为 xml 文件
173 | */
174 | export function profileData2XmlData(profileData: ProfileData): XMLProfileData {
175 | const xmlProfileData: XMLProfileData = {
176 | '设备类别' : {
177 | $: { id: profileData.id },
178 | '型号': [],
179 | }
180 | };
181 |
182 | for (const modelData of profileData.models ?? []) {
183 | const xmlModelData: XMLModelData = {
184 | $: { id: modelData.id },
185 | '配置方式': [modelData.configType],
186 | '配置': [],
187 | };
188 | if (modelData['NI-VISA']) {
189 | const xmlConfigData: XMLConfigDataMap['NI-VISA'] = {
190 | $: { id: 'NI-VISA' },
191 | '操作': modelData['NI-VISA'].operations?.map(operationData => {
192 | return {
193 | $: {
194 | id: operationData.id,
195 | Parameter: operationData.parameter,
196 | HasReturn: operationData.hasReturn,
197 | },
198 | '指令': [operationData.command],
199 | };
200 | }),
201 | };
202 | xmlModelData['配置'].push(xmlConfigData);
203 | }
204 | if (modelData.FUNCTION) {
205 | const xmlConfigData: XMLConfigDataMap['FUNCTION'] = {
206 | $: { id: 'FUNCTION' },
207 | '类': [{
208 | $: {
209 | Type: `${modelData.FUNCTION.spaceName}.${modelData.FUNCTION.className}, ${modelData.FUNCTION.dllTemplate}`,
210 | IsVISA: modelData.FUNCTION.isVisa,
211 | }
212 | }],
213 | '操作': modelData.FUNCTION.operations?.map(operationData => {
214 | return {
215 | $: {
216 | id: operationData.id,
217 | Parameter: operationData.parameter,
218 | HasReturn: operationData.hasReturn,
219 | },
220 | '方法': operationData.methods?.map(methodData => {
221 | return {
222 | $: { Name: methodData.name },
223 | '参数': methodData.parameters?.map(parameterData => {
224 | return {
225 | $: {
226 | Type: parameterData.type,
227 | Value: parameterData.value,
228 | }
229 | };
230 | }),
231 | };
232 | }),
233 | };
234 | }),
235 | };
236 | xmlModelData['配置'].push(xmlConfigData);
237 | }
238 | if (modelData.CUSTOM) {
239 | // @ts-expect-error 这个类型定义太麻烦了
240 | const xmlConfigData: XMLConfigDataMap['CUSTOM'] = {
241 | $: { id: 'CUSTOM' },
242 | '通信方法': [modelData.CUSTOM.communicationType],
243 | [modelData.CUSTOM.communicationType]: [{
244 | ...(
245 | modelData.CUSTOM.communicationType === 'RS232' || modelData.CUSTOM.communicationType === 'RS485'
246 | ? {
247 | '波特率': [modelData.CUSTOM.communicationConfig.baudRate],
248 | '数据位': [modelData.CUSTOM.communicationConfig.dataBits],
249 | '停止位': [modelData.CUSTOM.communicationConfig.stopBits],
250 | '奇偶校验': [modelData.CUSTOM.communicationConfig.parity],
251 | '缓冲区字节数': [modelData.CUSTOM.communicationConfig.bufferBytes],
252 | '握手协议': [modelData.CUSTOM.communicationConfig.handShake],
253 | '超时时间': [modelData.CUSTOM.communicationConfig.timeout],
254 | }
255 | : {
256 | 'IP': [modelData.CUSTOM.communicationConfig.ip],
257 | '端口': [modelData.CUSTOM.communicationConfig.port],
258 | }
259 | ),
260 | }],
261 | '操作': modelData.CUSTOM.operations?.map(operationData => {
262 | return {
263 | $: { id: operationData.id },
264 | '测量模式': operationData.measureModes?.map(measureModeData => {
265 | return {
266 | $: { id: measureModeData.id },
267 | '工位': measureModeData.workplaces?.map(workplaceData => {
268 | return {
269 | $: { id: workplaceData.id },
270 | '字节流': workplaceData.byteStreams?.map(byteStreamData => {
271 | return {
272 | $: {
273 | send: byteStreamData.send,
274 | receive: byteStreamData.receive,
275 | }
276 | };
277 | }),
278 | };
279 | }),
280 | };
281 | }),
282 | };
283 | }),
284 | };
285 | xmlModelData['配置'].push(xmlConfigData);
286 | }
287 | xmlProfileData['设备类别']['型号'].push(xmlModelData);
288 | }
289 |
290 | return xmlProfileData;
291 | }
292 |
293 | /**
294 | * 将配置文件数据适配为流程图数据
295 | */
296 | export function adaptorIn(profileData: ProfileData): LogicFlow.GraphData {
297 | const graphData: LogicFlow.GraphData = {
298 | nodes: [],
299 | edges: [],
300 | };
301 |
302 | const instrumentNode = {
303 | id: uuidv4(),
304 | type: 'instrument-node',
305 | x: 0,
306 | y: 0,
307 | properties: {
308 | type: NodeType.Instrument,
309 | id: profileData.id,
310 | }
311 | };
312 |
313 | graphData.nodes.push(instrumentNode);
314 |
315 | for (const modelData of profileData.models ?? []) {
316 | const modelNode = {
317 | id: uuidv4(),
318 | type: 'model-node',
319 | x: 0,
320 | y: 0,
321 | properties: {
322 | type: NodeType.Model,
323 | parentId: instrumentNode.id,
324 | id: modelData.id,
325 | configType: modelData.configType,
326 | }
327 | };
328 |
329 | graphData.nodes.push(modelNode);
330 | graphData.edges.push(initEdgeData(instrumentNode.id, modelNode.id));
331 |
332 | if (modelData['NI-VISA']) {
333 | const configNode = {
334 | id: uuidv4(),
335 | type: 'config-node',
336 | x: 0,
337 | y: 0,
338 | properties: {
339 | type: NodeType.Config,
340 | parentId: modelNode.id,
341 | ...initConfigNodeData(),
342 | id: modelData['NI-VISA'].id,
343 | }
344 | };
345 |
346 | graphData.nodes.push(configNode);
347 | graphData.edges.push(initEdgeData(modelNode.id, configNode.id));
348 |
349 | for (const operationData of modelData['NI-VISA'].operations ?? []) {
350 | const operationNode = {
351 | id: uuidv4(),
352 | type: 'operation-node',
353 | x: 0,
354 | y: 0,
355 | properties: {
356 | type: NodeType.NI_VISA_OPERATION,
357 | parentId: configNode.id,
358 | ...initOperationNodeData(),
359 | id: operationData.id,
360 | parameter: operationData.parameter,
361 | hasReturn: operationData.hasReturn,
362 | command: operationData.command,
363 | }
364 | };
365 |
366 | graphData.nodes.push(operationNode);
367 | graphData.edges.push(initEdgeData(configNode.id, operationNode.id));
368 | }
369 | }
370 |
371 | if (modelData['FUNCTION']) {
372 | const configNode = {
373 | id: uuidv4(),
374 | type: 'config-node',
375 | x: 0,
376 | y: 0,
377 | properties: {
378 | type: NodeType.Config,
379 | parentId: modelNode.id,
380 | ...initConfigNodeData(),
381 | id: modelData.FUNCTION.id,
382 | spaceName: modelData.FUNCTION.spaceName,
383 | className: modelData.FUNCTION.className,
384 | dllTemplate: modelData.FUNCTION.dllTemplate,
385 | isVisa: modelData.FUNCTION.isVisa,
386 | }
387 | };
388 |
389 | graphData.nodes.push(configNode);
390 | graphData.edges.push(initEdgeData(modelNode.id, configNode.id));
391 |
392 | for (const operationData of modelData.FUNCTION.operations ?? []) {
393 | const operationNode = {
394 | id: uuidv4(),
395 | type: 'operation-node',
396 | x: 0,
397 | y: 0,
398 | properties: {
399 | type: NodeType.FUNCTION_OPERATION,
400 | parentId: configNode.id,
401 | ...initOperationNodeData(),
402 | id: operationData.id,
403 | parameter: operationData.parameter,
404 | hasReturn: operationData.hasReturn,
405 | methods: operationData.methods,
406 | }
407 | };
408 |
409 | graphData.nodes.push(operationNode);
410 | graphData.edges.push(initEdgeData(configNode.id, operationNode.id));
411 | }
412 | }
413 |
414 | if (modelData['CUSTOM']) {
415 | const configNode = {
416 | id: uuidv4(),
417 | type: 'config-node',
418 | x: 0,
419 | y: 0,
420 | properties: {
421 | type: NodeType.Config,
422 | parentId: modelNode.id,
423 | ...initConfigNodeData(),
424 | id: modelData.CUSTOM.id,
425 | communicationType: modelData.CUSTOM.communicationType,
426 | communicationConfig: modelData.CUSTOM.communicationConfig,
427 | }
428 | };
429 |
430 | graphData.nodes.push(configNode);
431 | graphData.edges.push(initEdgeData(modelNode.id, configNode.id));
432 |
433 | for (const operationData of modelData.CUSTOM.operations ?? []) {
434 | const operationNode = {
435 | id: uuidv4(),
436 | type: 'operation-node',
437 | x: 0,
438 | y: 0,
439 | properties: {
440 | type: NodeType.CUSTOM_OPERATION,
441 | parentId: configNode.id,
442 | ...initOperationNodeData(),
443 | id: operationData.id,
444 | measureModes: operationData.measureModes,
445 | }
446 | };
447 |
448 | graphData.nodes.push(operationNode);
449 | graphData.edges.push(initEdgeData(configNode.id, operationNode.id));
450 | }
451 | }
452 | }
453 |
454 | return graphData;
455 | }
456 |
457 | function initEdgeData(sourceNodeId: string, targetNodeId: string): LogicFlow.EdgeData {
458 | return {
459 | id: uuidv4(),
460 | type: 'polyline',
461 | sourceNodeId,
462 | targetNodeId,
463 | startPoint: { x: NODE_WIDTH_HALF, y: 0 },
464 | endPoint: { x: -NODE_WIDTH_HALF, y: 0 },
465 | };
466 | }
467 |
468 | /**
469 | * 将流程图数据适配为配置文件数据
470 | */
471 | export function adaptorOut(graphData: LogicFlow.GraphData): ProfileData {
472 | const nodeMap = new Map();
473 | let startNode = graphData.nodes[0];
474 | graphData.nodes.forEach((node) => {
475 | const parentId = node.properties!.parentId;
476 | if (parentId) {
477 | if (nodeMap.has(parentId)) {
478 | nodeMap.get(parentId)!.push(node);
479 | } else {
480 | nodeMap.set(parentId, [node]);
481 | }
482 | }
483 | if (node.type === 'instrument-node') {
484 | startNode = node;
485 | }
486 | });
487 |
488 | const profileData: ProfileData = {
489 | id: startNode.properties!.id,
490 | models: [],
491 | };
492 |
493 | nodeMap.get(startNode.id)?.forEach(modelNode => {
494 | const modelData: ModelData = {
495 | id: modelNode.properties!.id,
496 | configType: modelNode.properties!.configType,
497 | };
498 |
499 | nodeMap.get(modelNode.id)?.forEach(configNode => {
500 | if (configNode.properties!.id === 'NI-VISA' && !modelData['NI-VISA']) {
501 | const configData: ModelData['NI-VISA'] = {
502 | id: 'NI-VISA',
503 | operations: [],
504 | };
505 |
506 | nodeMap.get(configNode.id)?.forEach(operationNode => {
507 | configData.operations.push({
508 | id: operationNode.properties!.id,
509 | parameter: operationNode.properties!.parameter,
510 | hasReturn: operationNode.properties!.hasReturn,
511 | command: operationNode.properties!.command,
512 | });
513 | });
514 |
515 | modelData['NI-VISA'] = configData;
516 | } else if (configNode.properties!.id === 'FUNCTION' && !modelData.FUNCTION) {
517 | const configData: ModelData['FUNCTION'] = {
518 | id: 'FUNCTION',
519 | spaceName: configNode.properties!.spaceName,
520 | className: configNode.properties!.className,
521 | dllTemplate: configNode.properties!.dllTemplate,
522 | isVisa: configNode.properties!.isVisa,
523 | operations: [],
524 | };
525 |
526 | nodeMap.get(configNode.id)?.forEach(operationNode => {
527 | configData.operations.push({
528 | id: operationNode.properties!.id,
529 | parameter: operationNode.properties!.parameter,
530 | hasReturn: operationNode.properties!.hasReturn,
531 | methods: operationNode.properties!.methods,
532 | });
533 | });
534 |
535 | modelData.FUNCTION = configData;
536 | } else if (configNode.properties!.id === 'CUSTOM' && !modelData.CUSTOM) {
537 | const configData: ModelData['CUSTOM'] = {
538 | id: 'CUSTOM',
539 | communicationType: configNode.properties!.communicationType,
540 | communicationConfig: configNode.properties!.communicationConfig,
541 | operations: [],
542 | };
543 |
544 | nodeMap.get(configNode.id)?.forEach(operationNode => {
545 | const operationData = {
546 | id: operationNode.properties!.id,
547 | measureModes: operationNode.properties!.measureModes,
548 | };
549 | configData.operations.push(operationData);
550 | });
551 |
552 | modelData.CUSTOM = configData;
553 | }
554 | });
555 |
556 | profileData.models.push(modelData);
557 | });
558 |
559 | return profileData;
560 | }
561 |
--------------------------------------------------------------------------------