@tier0/node-red-contrib-opcda-client 1.0.0 → 1.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/OPCDA_Bridge_Setup_Guide.md +132 -0
- package/README.md +9 -16
- package/docker-compose.yml +460 -0
- package/opcda/opcda-read.js +34 -7
- package/opcda/opcda-server.html +63 -15
- package/opcda/opcda-server.js +94 -22
- package/opctest.py +214 -0
- package/package.json +9 -6
- package/patch-debug.js +13 -0
- package/patch-ntlm.js +46 -0
- package/patch-pwdcheck.js +17 -0
- package/test-connect.js +69 -0
- package/test-connect2.js +30 -0
- package/test-direct.js +53 -0
- package/test-ntlm-vectors.js +100 -0
- package/test-ntlm-verify.js +77 -0
- package/uns_import.json +204 -0
package/test-connect2.js
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
const opcda = require('node-opc-da');
|
|
2
|
+
const { OPCServer } = opcda;
|
|
3
|
+
const { ComServer, Session, Clsid } = opcda.dcom;
|
|
4
|
+
|
|
5
|
+
async function test(domain) {
|
|
6
|
+
console.log('\n--- Testing with domain="' + domain + '" ---');
|
|
7
|
+
try {
|
|
8
|
+
var session = new Session();
|
|
9
|
+
session = session.createSession(domain, 'Administrator', 'Supos@1304');
|
|
10
|
+
session.setGlobalSocketTimeout(10000);
|
|
11
|
+
|
|
12
|
+
var comServer = new ComServer(
|
|
13
|
+
new Clsid('7BC0CC8E-482C-47CA-ABDC-0FE7F9C6E729'),
|
|
14
|
+
'192.168.31.75', session);
|
|
15
|
+
|
|
16
|
+
await comServer.init();
|
|
17
|
+
console.log('SUCCESS with domain="' + domain + '"');
|
|
18
|
+
await comServer.closeStub();
|
|
19
|
+
} catch (e) {
|
|
20
|
+
var code = typeof e === 'number' ? '0x' + (e >>> 0).toString(16) : (e.message || String(e));
|
|
21
|
+
console.log('FAILED:', code);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
(async () => {
|
|
26
|
+
await test('DESKTOP-BBD7VBL');
|
|
27
|
+
await test('.');
|
|
28
|
+
await test('');
|
|
29
|
+
await test('WORKGROUP');
|
|
30
|
+
})();
|
package/test-direct.js
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
const opcda = require('/usr/src/node-red/node_modules/node-opc-da/src/index.js');
|
|
2
|
+
const dcom = require('node-dcom');
|
|
3
|
+
const { ComServer, Session, Clsid } = dcom;
|
|
4
|
+
|
|
5
|
+
const address = '192.168.31.75';
|
|
6
|
+
const domain = 'DESKTOP-BBD7VBL';
|
|
7
|
+
const username = 'Administrator';
|
|
8
|
+
const password = 'Supos@1304';
|
|
9
|
+
const clsidStr = '7BC0CC8E-482C-47CA-ABDC-0FE7F9C6E729';
|
|
10
|
+
|
|
11
|
+
async function main() {
|
|
12
|
+
console.log('=== OPC DA Direct Connection Test ===');
|
|
13
|
+
|
|
14
|
+
let comSession, comServer;
|
|
15
|
+
try {
|
|
16
|
+
comSession = new Session();
|
|
17
|
+
comSession = comSession.createSession(domain, username, password);
|
|
18
|
+
comSession.setGlobalSocketTimeout(15000);
|
|
19
|
+
console.log('Session: domain=' + comSession.getDomain() + ' user=' + comSession.getUserName());
|
|
20
|
+
|
|
21
|
+
comServer = new ComServer(new Clsid(clsidStr), address, comSession);
|
|
22
|
+
console.log('Connecting...');
|
|
23
|
+
await comServer.init();
|
|
24
|
+
console.log('Connected!');
|
|
25
|
+
|
|
26
|
+
const comObject = await comServer.createInstance();
|
|
27
|
+
console.log('Instance created');
|
|
28
|
+
|
|
29
|
+
const server = new opcda.OPCServer();
|
|
30
|
+
await server.init(comObject);
|
|
31
|
+
console.log('OPCServer initialized');
|
|
32
|
+
|
|
33
|
+
const status = await server.getStatus();
|
|
34
|
+
console.log('Server Status:', JSON.stringify(status, null, 2));
|
|
35
|
+
|
|
36
|
+
const browser = await server.getBrowser();
|
|
37
|
+
console.log('Got browser');
|
|
38
|
+
|
|
39
|
+
const items = await browser.browse();
|
|
40
|
+
console.log('Items found:', items.length);
|
|
41
|
+
if (items.length > 0) {
|
|
42
|
+
console.log('First 5 items:', items.slice(0, 5));
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
console.log('\n=== SUCCESS ===');
|
|
46
|
+
} catch (err) {
|
|
47
|
+
console.error('ERROR:', err.message || err);
|
|
48
|
+
console.error('Stack:', err.stack);
|
|
49
|
+
}
|
|
50
|
+
process.exit(0);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
main();
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
// MS-NLMP Appendix A test vectors for NTLMv2
|
|
2
|
+
// Reference: https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-nlmp/
|
|
3
|
+
const Crypto = require('crypto');
|
|
4
|
+
|
|
5
|
+
// Force legacy provider for MD4
|
|
6
|
+
process.env.NODE_OPTIONS = '--openssl-legacy-provider';
|
|
7
|
+
|
|
8
|
+
const Responses = require('/usr/src/node-red/node_modules/node-dcom/dcom/rpc/security/responses.js');
|
|
9
|
+
|
|
10
|
+
const r = new Responses();
|
|
11
|
+
|
|
12
|
+
// MS-NLMP test vectors
|
|
13
|
+
const User = 'User';
|
|
14
|
+
const UserDom = 'Domain';
|
|
15
|
+
const Passwd = 'Password';
|
|
16
|
+
const ServerChallenge = [0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef];
|
|
17
|
+
const ClientChallenge = [0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa];
|
|
18
|
+
const Time = [0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00];
|
|
19
|
+
|
|
20
|
+
// Expected values from MS-NLMP spec
|
|
21
|
+
const EXPECTED_NT_HASH = 'a4f49c406510bdcab6824ee7c30fd852';
|
|
22
|
+
const EXPECTED_NTLMv2_HASH = '0c868a403bfd7a93a3001ef22ef02e3f';
|
|
23
|
+
|
|
24
|
+
// Test 1: NT Hash (MD4 of UTF-16LE password)
|
|
25
|
+
console.log('=== Test 1: NT Hash ===');
|
|
26
|
+
const ntHash = r.ntlmHash(Passwd);
|
|
27
|
+
const ntHashHex = Buffer.from(ntHash).toString('hex');
|
|
28
|
+
console.log('Computed:', ntHashHex);
|
|
29
|
+
console.log('Expected:', EXPECTED_NT_HASH);
|
|
30
|
+
console.log('Match:', ntHashHex === EXPECTED_NT_HASH ? 'PASS' : 'FAIL');
|
|
31
|
+
|
|
32
|
+
// Test 2: NTLMv2 Hash
|
|
33
|
+
console.log('\n=== Test 2: NTLMv2 Hash ===');
|
|
34
|
+
const ntlmv2Hash = r.ntlmv2Hash(UserDom, User, Passwd);
|
|
35
|
+
const ntlmv2HashHex = Buffer.from(ntlmv2Hash).toString('hex');
|
|
36
|
+
console.log('Computed:', ntlmv2HashHex);
|
|
37
|
+
console.log('Expected:', EXPECTED_NTLMv2_HASH);
|
|
38
|
+
console.log('Match:', ntlmv2HashHex === EXPECTED_NTLMv2_HASH ? 'PASS' : 'FAIL');
|
|
39
|
+
|
|
40
|
+
// Test 3: LMv2 Response
|
|
41
|
+
console.log('\n=== Test 3: LMv2 Response ===');
|
|
42
|
+
const lmv2 = r.getLMv2Response(UserDom, User, Passwd, ServerChallenge, ClientChallenge);
|
|
43
|
+
const lmv2Hex = Buffer.from(lmv2).toString('hex');
|
|
44
|
+
console.log('Computed:', lmv2Hex);
|
|
45
|
+
// Expected from MS-NLMP: d6e6152ea25d03b7c6ba6629c2d6aaf0aaaaaaaaaaaaaaaa
|
|
46
|
+
const EXPECTED_LMv2 = 'd6e6152ea25d03b7c6ba6629c2d6aaf0aaaaaaaaaaaaaaaa';
|
|
47
|
+
console.log('Expected:', EXPECTED_LMv2);
|
|
48
|
+
console.log('Match:', lmv2Hex === EXPECTED_LMv2 ? 'PASS' : 'FAIL');
|
|
49
|
+
|
|
50
|
+
// Test 4: hmacMD5 argument order verification
|
|
51
|
+
console.log('\n=== Test 4: HMAC-MD5 argument order ===');
|
|
52
|
+
const testHmac = r.hmacMD5([0x01, 0x02, 0x03], [0x04, 0x05, 0x06]);
|
|
53
|
+
console.log('hmacMD5([1,2,3], [4,5,6]) =', Buffer.from(testHmac).toString('hex'));
|
|
54
|
+
// Verify: HMAC-MD5(key=040506, data=010203)
|
|
55
|
+
const verifyHmac = Crypto.createHmac('md5', Buffer.from([0x04, 0x05, 0x06]));
|
|
56
|
+
verifyHmac.update(Buffer.from([0x01, 0x02, 0x03]));
|
|
57
|
+
console.log('Crypto.HMAC(key=040506, data=010203) =', verifyHmac.digest('hex'));
|
|
58
|
+
|
|
59
|
+
// Test 5: NTLMv2 Response with known blob
|
|
60
|
+
console.log('\n=== Test 5: NTLMv2 Response ===');
|
|
61
|
+
// Build a minimal target info for testing
|
|
62
|
+
const targetInfo = [
|
|
63
|
+
0x02, 0x00, 0x0c, 0x00, // MsvAvNbDomainName, len=12
|
|
64
|
+
0x44, 0x00, 0x6f, 0x00, 0x6d, 0x00, 0x61, 0x00, 0x69, 0x00, 0x6e, 0x00, // "Domain" in UTF-16LE
|
|
65
|
+
0x01, 0x00, 0x0c, 0x00, // MsvAvNbComputerName, len=12
|
|
66
|
+
0x53, 0x00, 0x65, 0x00, 0x72, 0x00, 0x76, 0x00, 0x65, 0x00, 0x72, 0x00, // "Server" in UTF-16LE
|
|
67
|
+
0x00, 0x00, 0x00, 0x00 // MsvAvEOL
|
|
68
|
+
];
|
|
69
|
+
|
|
70
|
+
// Test blob creation
|
|
71
|
+
const blob = r.createBlob(targetInfo, ClientChallenge);
|
|
72
|
+
console.log('Blob length:', blob.length);
|
|
73
|
+
console.log('Blob header (first 8):', Buffer.from(blob.slice(0, 8)).toString('hex'));
|
|
74
|
+
console.log('Blob timestamp (next 8):', Buffer.from(blob.slice(8, 16)).toString('hex'));
|
|
75
|
+
console.log('Blob clientNonce (next 8):', Buffer.from(blob.slice(16, 24)).toString('hex'));
|
|
76
|
+
|
|
77
|
+
// Verify blob structure
|
|
78
|
+
console.log('\nBlob structure check:');
|
|
79
|
+
console.log('Byte 0-1 (signature):', blob[0] === 1 && blob[1] === 1 ? 'PASS (0x0101)' : 'FAIL');
|
|
80
|
+
console.log('Byte 2-7 (zeros):', blob.slice(2, 8).every(b => b === 0) ? 'PASS' : 'FAIL');
|
|
81
|
+
console.log('Byte 16-23 (client nonce):', Buffer.from(blob.slice(16, 24)).toString('hex') === 'aaaaaaaaaaaaaaaa' ? 'PASS' : 'FAIL');
|
|
82
|
+
console.log('Byte 24-27 (Z4):', blob.slice(24, 28).every(b => b === 0) ? 'PASS' : 'FAIL');
|
|
83
|
+
|
|
84
|
+
// Verify the full NTLMv2 response computation
|
|
85
|
+
const retval = r.getNTLMv2Response(UserDom, User, Passwd, targetInfo, ServerChallenge, ClientChallenge);
|
|
86
|
+
console.log('\nNTLMv2Response length:', retval[0].length);
|
|
87
|
+
console.log('NTProofStr:', Buffer.from(retval[0].slice(0, 16)).toString('hex'));
|
|
88
|
+
|
|
89
|
+
// Test session base key
|
|
90
|
+
const ntProofStr = retval[0].slice(0, 16);
|
|
91
|
+
const hashForSession = r.ntlmv2Hash(UserDom, User, Passwd);
|
|
92
|
+
const sessionBaseKey = r.hmacMD5(ntProofStr, hashForSession);
|
|
93
|
+
console.log('SessionBaseKey:', Buffer.from(sessionBaseKey).toString('hex'));
|
|
94
|
+
|
|
95
|
+
// Now test with actual credentials
|
|
96
|
+
console.log('\n\n=== Test with actual credentials ===');
|
|
97
|
+
const actualNtHash = r.ntlmHash('Supos@1304');
|
|
98
|
+
console.log('NT Hash (Supos@1304):', Buffer.from(actualNtHash).toString('hex'));
|
|
99
|
+
const actualV2Hash = r.ntlmv2Hash('DESKTOP-BBD7VBL', 'Administrator', 'Supos@1304');
|
|
100
|
+
console.log('NTLMv2 Hash:', Buffer.from(actualV2Hash).toString('hex'));
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
const Crypto = require('crypto');
|
|
2
|
+
|
|
3
|
+
// Verify DES update() vs final() behavior in this Node.js version
|
|
4
|
+
console.log('=== Node.js version:', process.version, '===');
|
|
5
|
+
console.log('=== OpenSSL version:', process.versions.openssl, '===\n');
|
|
6
|
+
|
|
7
|
+
// Test 1: DES-ECB update() behavior
|
|
8
|
+
console.log('--- Test 1: DES-ECB update vs final ---');
|
|
9
|
+
const key1 = Buffer.from('0123456789ABCDEF', 'hex');
|
|
10
|
+
const data1 = Buffer.from('4E6F772069732074', 'hex');
|
|
11
|
+
const des1 = Crypto.createCipheriv('des-ecb', key1, '');
|
|
12
|
+
const update1 = des1.update(data1);
|
|
13
|
+
const final1 = des1.final();
|
|
14
|
+
console.log('update() returned', update1.length, 'bytes:', update1.toString('hex'));
|
|
15
|
+
console.log('final() returned', final1.length, 'bytes:', final1.toString('hex'));
|
|
16
|
+
console.log('Expected update=3fa40e8a984d4815 (8 bytes)\n');
|
|
17
|
+
|
|
18
|
+
// Test 2: Verify NT hash
|
|
19
|
+
console.log('--- Test 2: NT Hash of Supos@1304 ---');
|
|
20
|
+
const md4 = Crypto.createHash('md4');
|
|
21
|
+
md4.update(Buffer.from('Supos@1304', 'utf16le'));
|
|
22
|
+
const ntHash = md4.digest();
|
|
23
|
+
console.log('NT Hash:', ntHash.toString('hex'));
|
|
24
|
+
console.log('Length:', ntHash.length, '\n');
|
|
25
|
+
|
|
26
|
+
// Test 3: NTLM2 Session Response with known values
|
|
27
|
+
console.log('--- Test 3: Full NTLM2 Session Response ---');
|
|
28
|
+
const Responses = require('node-opc-da/node_modules/node-dcom/dcom/rpc/security/responses.js');
|
|
29
|
+
const r = new Responses();
|
|
30
|
+
|
|
31
|
+
const fakeChallenge = Buffer.from('0123456789abcdef', 'hex');
|
|
32
|
+
const fakeClientNonce = Buffer.from('ffffff0011223344', 'hex');
|
|
33
|
+
const testResponse = r.getNTLM2SessionResponse('Password', fakeChallenge, fakeClientNonce);
|
|
34
|
+
console.log('Response type:', typeof testResponse, testResponse.constructor.name);
|
|
35
|
+
console.log('Response length:', testResponse.length);
|
|
36
|
+
console.log('Response hex:', testResponse.toString('hex'));
|
|
37
|
+
|
|
38
|
+
// Independent verification of the same computation
|
|
39
|
+
const ntHash2 = Crypto.createHash('md4').update(Buffer.from('Password', 'utf16le')).digest();
|
|
40
|
+
console.log('\nIndependent NT hash:', ntHash2.toString('hex'));
|
|
41
|
+
const sessionNonce = Buffer.concat([fakeChallenge, fakeClientNonce]);
|
|
42
|
+
const sessionHash = Crypto.createHash('md5').update(sessionNonce).digest().slice(0, 8);
|
|
43
|
+
console.log('Session hash:', sessionHash.toString('hex'));
|
|
44
|
+
|
|
45
|
+
// Manual DES with NT hash
|
|
46
|
+
function createDESKey(bytes, offset) {
|
|
47
|
+
let keyBytes = bytes.slice(offset, 7 + offset);
|
|
48
|
+
let material = Buffer.alloc(8);
|
|
49
|
+
material[0] = keyBytes[0];
|
|
50
|
+
material[1] = ((keyBytes[0] << 7) & 0xff | ((keyBytes[1] & 0xff) >>> 1));
|
|
51
|
+
material[2] = ((keyBytes[1] << 6) & 0xff | ((keyBytes[2] & 0xff) >>> 2));
|
|
52
|
+
material[3] = ((keyBytes[2] << 5) & 0xff | ((keyBytes[3] & 0xff) >>> 3));
|
|
53
|
+
material[4] = ((keyBytes[3] << 4) & 0xff | ((keyBytes[4] & 0xff) >>> 4));
|
|
54
|
+
material[5] = ((keyBytes[4] << 3) & 0xff | ((keyBytes[5] & 0xff) >>> 5));
|
|
55
|
+
material[6] = ((keyBytes[5] << 2) & 0xff | ((keyBytes[6] & 0xff) >>> 6));
|
|
56
|
+
material[7] = ((keyBytes[6] << 1));
|
|
57
|
+
// odd parity
|
|
58
|
+
for (let i = 0; i < 8; i++) {
|
|
59
|
+
let b = material[i];
|
|
60
|
+
let needsParity = (((b >>> 7) ^ (b >>> 6) ^ (b >>> 5) ^ (b >>> 4) ^ (b >>> 3) ^ (b >>> 2) ^ (b >>> 1)) & 0x01) == 0;
|
|
61
|
+
if (needsParity) material[i] |= 0x01;
|
|
62
|
+
else material[i] &= 0xfe;
|
|
63
|
+
}
|
|
64
|
+
return material;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const padded = Buffer.concat([ntHash2, Buffer.alloc(5)]);
|
|
68
|
+
let manualResult = Buffer.alloc(0);
|
|
69
|
+
for (let off of [0, 7, 14]) {
|
|
70
|
+
const k = createDESKey(padded, off);
|
|
71
|
+
const d = Crypto.createCipheriv('des-ecb', k, '');
|
|
72
|
+
const enc = d.update(sessionHash);
|
|
73
|
+
d.final();
|
|
74
|
+
manualResult = Buffer.concat([manualResult, enc]);
|
|
75
|
+
}
|
|
76
|
+
console.log('Manual response:', manualResult.toString('hex'));
|
|
77
|
+
console.log('Match:', testResponse.toString('hex') === manualResult.toString('hex') ? 'YES' : 'NO');
|
package/uns_import.json
ADDED
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
{
|
|
2
|
+
"UNS": [
|
|
3
|
+
{
|
|
4
|
+
"type": "path",
|
|
5
|
+
"name": "opcda",
|
|
6
|
+
"children": [
|
|
7
|
+
{
|
|
8
|
+
"type": "path",
|
|
9
|
+
"name": "通道_1",
|
|
10
|
+
"children": [
|
|
11
|
+
{
|
|
12
|
+
"type": "path",
|
|
13
|
+
"name": "设备_1",
|
|
14
|
+
"children": [
|
|
15
|
+
{
|
|
16
|
+
"dataType": "METRIC",
|
|
17
|
+
"displayName": "Metric",
|
|
18
|
+
"type": "path",
|
|
19
|
+
"name": "Metric",
|
|
20
|
+
"children": [
|
|
21
|
+
{
|
|
22
|
+
"topicType": "METRIC",
|
|
23
|
+
"generateDashboard": "TRUE",
|
|
24
|
+
"writeData": "FALSE",
|
|
25
|
+
"name": "标记_1",
|
|
26
|
+
"enableHistory": "TRUE",
|
|
27
|
+
"dataType": "TIME_SEQUENCE_TYPE",
|
|
28
|
+
"fields": [
|
|
29
|
+
{
|
|
30
|
+
"systemField": true,
|
|
31
|
+
"unique": true,
|
|
32
|
+
"type": "DATETIME",
|
|
33
|
+
"name": "timeStamp"
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
"tbValueName": "tag",
|
|
37
|
+
"systemField": true,
|
|
38
|
+
"unique": true,
|
|
39
|
+
"type": "LONG",
|
|
40
|
+
"name": "tag"
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
"index": "double_1",
|
|
44
|
+
"type": "FLOAT",
|
|
45
|
+
"name": "value"
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
"systemField": true,
|
|
49
|
+
"type": "LONG",
|
|
50
|
+
"name": "quality"
|
|
51
|
+
}
|
|
52
|
+
],
|
|
53
|
+
"type": "topic",
|
|
54
|
+
"mockData": "FALSE"
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
"topicType": "METRIC",
|
|
58
|
+
"generateDashboard": "TRUE",
|
|
59
|
+
"writeData": "FALSE",
|
|
60
|
+
"name": "标记_2",
|
|
61
|
+
"enableHistory": "TRUE",
|
|
62
|
+
"dataType": "TIME_SEQUENCE_TYPE",
|
|
63
|
+
"fields": [
|
|
64
|
+
{
|
|
65
|
+
"systemField": true,
|
|
66
|
+
"unique": true,
|
|
67
|
+
"type": "DATETIME",
|
|
68
|
+
"name": "timeStamp"
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
"tbValueName": "tag",
|
|
72
|
+
"systemField": true,
|
|
73
|
+
"unique": true,
|
|
74
|
+
"type": "LONG",
|
|
75
|
+
"name": "tag"
|
|
76
|
+
},
|
|
77
|
+
{
|
|
78
|
+
"index": "double_1",
|
|
79
|
+
"type": "FLOAT",
|
|
80
|
+
"name": "value"
|
|
81
|
+
},
|
|
82
|
+
{
|
|
83
|
+
"systemField": true,
|
|
84
|
+
"type": "LONG",
|
|
85
|
+
"name": "quality"
|
|
86
|
+
}
|
|
87
|
+
],
|
|
88
|
+
"type": "topic",
|
|
89
|
+
"mockData": "FALSE"
|
|
90
|
+
}
|
|
91
|
+
]
|
|
92
|
+
}
|
|
93
|
+
]
|
|
94
|
+
}
|
|
95
|
+
]
|
|
96
|
+
},
|
|
97
|
+
{
|
|
98
|
+
"type": "path",
|
|
99
|
+
"name": "模拟器示例",
|
|
100
|
+
"children": [
|
|
101
|
+
{
|
|
102
|
+
"type": "path",
|
|
103
|
+
"name": "功能",
|
|
104
|
+
"children": [
|
|
105
|
+
{
|
|
106
|
+
"dataType": "METRIC",
|
|
107
|
+
"displayName": "Metric",
|
|
108
|
+
"type": "path",
|
|
109
|
+
"name": "Metric",
|
|
110
|
+
"children": [
|
|
111
|
+
{
|
|
112
|
+
"topicType": "METRIC",
|
|
113
|
+
"generateDashboard": "TRUE",
|
|
114
|
+
"writeData": "FALSE",
|
|
115
|
+
"name": "Sine1",
|
|
116
|
+
"enableHistory": "TRUE",
|
|
117
|
+
"dataType": "TIME_SEQUENCE_TYPE",
|
|
118
|
+
"fields": [
|
|
119
|
+
{
|
|
120
|
+
"systemField": true,
|
|
121
|
+
"unique": true,
|
|
122
|
+
"type": "DATETIME",
|
|
123
|
+
"name": "timeStamp"
|
|
124
|
+
},
|
|
125
|
+
{
|
|
126
|
+
"tbValueName": "tag",
|
|
127
|
+
"systemField": true,
|
|
128
|
+
"unique": true,
|
|
129
|
+
"type": "LONG",
|
|
130
|
+
"name": "tag"
|
|
131
|
+
},
|
|
132
|
+
{
|
|
133
|
+
"index": "double_1",
|
|
134
|
+
"type": "FLOAT",
|
|
135
|
+
"name": "value"
|
|
136
|
+
},
|
|
137
|
+
{
|
|
138
|
+
"systemField": true,
|
|
139
|
+
"type": "LONG",
|
|
140
|
+
"name": "quality"
|
|
141
|
+
}
|
|
142
|
+
],
|
|
143
|
+
"type": "topic",
|
|
144
|
+
"mockData": "FALSE"
|
|
145
|
+
}
|
|
146
|
+
]
|
|
147
|
+
}
|
|
148
|
+
]
|
|
149
|
+
}
|
|
150
|
+
]
|
|
151
|
+
},
|
|
152
|
+
{
|
|
153
|
+
"type": "path",
|
|
154
|
+
"name": "_System",
|
|
155
|
+
"children": [
|
|
156
|
+
{
|
|
157
|
+
"dataType": "METRIC",
|
|
158
|
+
"displayName": "Metric",
|
|
159
|
+
"type": "path",
|
|
160
|
+
"name": "Metric",
|
|
161
|
+
"children": [
|
|
162
|
+
{
|
|
163
|
+
"topicType": "METRIC",
|
|
164
|
+
"generateDashboard": "TRUE",
|
|
165
|
+
"writeData": "FALSE",
|
|
166
|
+
"name": "_Time",
|
|
167
|
+
"enableHistory": "TRUE",
|
|
168
|
+
"dataType": "TIME_SEQUENCE_TYPE",
|
|
169
|
+
"fields": [
|
|
170
|
+
{
|
|
171
|
+
"systemField": true,
|
|
172
|
+
"unique": true,
|
|
173
|
+
"type": "DATETIME",
|
|
174
|
+
"name": "timeStamp"
|
|
175
|
+
},
|
|
176
|
+
{
|
|
177
|
+
"tbValueName": "tag",
|
|
178
|
+
"systemField": true,
|
|
179
|
+
"unique": true,
|
|
180
|
+
"type": "LONG",
|
|
181
|
+
"name": "tag"
|
|
182
|
+
},
|
|
183
|
+
{
|
|
184
|
+
"index": "double_1",
|
|
185
|
+
"type": "FLOAT",
|
|
186
|
+
"name": "value"
|
|
187
|
+
},
|
|
188
|
+
{
|
|
189
|
+
"systemField": true,
|
|
190
|
+
"type": "LONG",
|
|
191
|
+
"name": "quality"
|
|
192
|
+
}
|
|
193
|
+
],
|
|
194
|
+
"type": "topic",
|
|
195
|
+
"mockData": "FALSE"
|
|
196
|
+
}
|
|
197
|
+
]
|
|
198
|
+
}
|
|
199
|
+
]
|
|
200
|
+
}
|
|
201
|
+
]
|
|
202
|
+
}
|
|
203
|
+
]
|
|
204
|
+
}
|