node-red-contrib-my-tools 1.0.0
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/README.txt +69 -0
- package/node-red-contrib-mysql-extended-test/mysql-extended.html +443 -0
- package/node-red-contrib-mysql-extended-test/mysql-extended.js +355 -0
- package/node-red-contrib-rfid-helper/rfid-lazy.html +153 -0
- package/node-red-contrib-rfid-helper/rfid-lazy.js +361 -0
- package/node-red-contrib-s7-plus/LICENSE +235 -0
- package/node-red-contrib-s7-plus/README.md +141 -0
- package/node-red-contrib-s7-plus/examples/read-multiple-values-flow.json +346 -0
- package/node-red-contrib-s7-plus/examples/read-write-test-flow.json +172 -0
- package/node-red-contrib-s7-plus/examples/write-single-values-flow.json +5583 -0
- package/node-red-contrib-s7-plus/lib/s7plus/browse/README.md +72 -0
- package/node-red-contrib-s7-plus/lib/s7plus/browse/areas.js +36 -0
- package/node-red-contrib-s7-plus/lib/s7plus/browse/datatypes.js +91 -0
- package/node-red-contrib-s7-plus/lib/s7plus/browse/flat-browser.js +433 -0
- package/node-red-contrib-s7-plus/lib/s7plus/browse/index.js +22 -0
- package/node-red-contrib-s7-plus/lib/s7plus/browse/lazy.js +397 -0
- package/node-red-contrib-s7-plus/lib/s7plus/browse/node-id.js +22 -0
- package/node-red-contrib-s7-plus/lib/s7plus/browse/resolve-symbolic.js +181 -0
- package/node-red-contrib-s7-plus/lib/s7plus/browse/vte-helpers.js +49 -0
- package/node-red-contrib-s7-plus/lib/s7plus/buffer-stream.js +96 -0
- package/node-red-contrib-s7-plus/lib/s7plus/client.js +1238 -0
- package/node-red-contrib-s7-plus/lib/s7plus/constants.js +155 -0
- package/node-red-contrib-s7-plus/lib/s7plus/crc/index.js +3 -0
- package/node-red-contrib-s7-plus/lib/s7plus/crc/s7-crc32.js +70 -0
- package/node-red-contrib-s7-plus/lib/s7plus/crc/symbol-crc.js +271 -0
- package/node-red-contrib-s7-plus/lib/s7plus/debug.js +88 -0
- package/node-red-contrib-s7-plus/lib/s7plus/explore-result.js +41 -0
- package/node-red-contrib-s7-plus/lib/s7plus/item-address.js +55 -0
- package/node-red-contrib-s7-plus/lib/s7plus/pdu-explore.js +77 -0
- package/node-red-contrib-s7-plus/lib/s7plus/pdu-messages.js +359 -0
- package/node-red-contrib-s7-plus/lib/s7plus/pobject.js +197 -0
- package/node-red-contrib-s7-plus/lib/s7plus/pvalue-codec.js +435 -0
- package/node-red-contrib-s7-plus/lib/s7plus/pvalue.js +653 -0
- package/node-red-contrib-s7-plus/lib/s7plus/pvar-lists.js +336 -0
- package/node-red-contrib-s7-plus/lib/s7plus/read-result.js +91 -0
- package/node-red-contrib-s7-plus/lib/s7plus/s7p.js +266 -0
- package/node-red-contrib-s7-plus/lib/s7plus/tag-routing.js +30 -0
- package/node-red-contrib-s7-plus/lib/s7plus/transport/cotp-stream.js +335 -0
- package/node-red-contrib-s7-plus/lib/s7plus/transport/s7-transport.js +335 -0
- package/node-red-contrib-s7-plus/nodes/icons/s7complus.png +0 -0
- package/node-red-contrib-s7-plus/nodes/icons/s7complus.svg +17 -0
- package/node-red-contrib-s7-plus/nodes/s7complus-endpoint.js +825 -0
- package/node-red-contrib-s7-plus/nodes/s7complus-explore.js +91 -0
- package/node-red-contrib-s7-plus/nodes/s7complus-in.js +255 -0
- package/node-red-contrib-s7-plus/nodes/s7complus-out.js +182 -0
- package/node-red-contrib-s7-plus/nodes/s7complus.html +1541 -0
- package/node-red-contrib-s7-plus/nodes/s7complus.js +10 -0
- package/node-red-contrib-s7-plus/package.json +50 -0
- package/node-red-contrib-s7-plus/plc/s7-1500/DB_Arrays.db +17 -0
- package/node-red-contrib-s7-plus/plc/s7-1500/DB_Binary.db +16 -0
- package/node-red-contrib-s7-plus/plc/s7-1500/DB_BitStrings.db +36 -0
- package/node-red-contrib-s7-plus/plc/s7-1500/DB_CharacterStrings.db +42 -0
- package/node-red-contrib-s7-plus/plc/s7-1500/DB_DateAndTime.db +70 -0
- package/node-red-contrib-s7-plus/plc/s7-1500/DB_FloatingPoint.db +42 -0
- package/node-red-contrib-s7-plus/plc/s7-1500/DB_Integers.db +72 -0
- package/node-red-contrib-s7-plus/plc/s7-1500/DB_Timers.db +40 -0
- package/node-red-contrib-s7-plus/scripts/example-flow-shared.js +39 -0
- package/node-red-contrib-s7-plus/scripts/generate-read-multiple-flow.js +331 -0
- package/node-red-contrib-s7-plus/scripts/generate-rw-test-flow.js +690 -0
- package/node-red-contrib-s7-plus/scripts/generate-write-flow.js +476 -0
- package/node-red-contrib-s7-plus/scripts/layout-write-flow.js +156 -0
- package/node-red-contrib-s7-plus/scripts/run-tests.js +16 -0
- package/node-red-flowgobal-change/variable-change.html +425 -0
- package/node-red-flowgobal-change/variable-change.js +92 -0
- package/node-red-ui_media_viewer/ui_media_viewer.html +87 -0
- package/node-red-ui_media_viewer/ui_media_viewer.js +136 -0
- package/package.json +12 -0
|
@@ -0,0 +1,435 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const pvalue = require('./pvalue');
|
|
4
|
+
const { softdatatypeName } = require('./browse/datatypes');
|
|
5
|
+
|
|
6
|
+
// ── S7 String (softdatatype 19) ──────────────────────────────────────
|
|
7
|
+
// Wire format: USInt array [maxLen, currentLen, ...bytes]
|
|
8
|
+
// Byte 0 = max capacity
|
|
9
|
+
// Byte 1 = actual character count
|
|
10
|
+
// Bytes 2..n = ISO-8859-1 / CP1252 encoded characters
|
|
11
|
+
function decodeS7String(arr) {
|
|
12
|
+
if (!Array.isArray(arr) || arr.length < 2) return '';
|
|
13
|
+
const len = arr[1];
|
|
14
|
+
const bytes = arr.slice(2, 2 + len);
|
|
15
|
+
return Buffer.from(bytes).toString('latin1');
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
// ── S7 WString (softdatatype 62) ─────────────────────────────────────
|
|
19
|
+
// Wire format: UInt16 array [maxLen, currentLen, ...UCS-2 codepoints]
|
|
20
|
+
// Word 0 = max capacity (chars)
|
|
21
|
+
// Word 1 = actual character count
|
|
22
|
+
// Words 2..n = UCS-2 code points
|
|
23
|
+
function decodeS7WString(arr) {
|
|
24
|
+
if (!Array.isArray(arr) || arr.length < 2) return '';
|
|
25
|
+
const len = arr[1];
|
|
26
|
+
const codes = arr.slice(2, 2 + len);
|
|
27
|
+
return String.fromCharCode(...codes);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// ── S5Time (BCD) → milliseconds ──────────────────────────────────────
|
|
31
|
+
// Wire: UInt16 (ValueWord). Bits 13-12 = time base, 11-0 = BCD value
|
|
32
|
+
function decodeS5Time(raw) {
|
|
33
|
+
const n = typeof raw === 'number' ? raw : Number(raw);
|
|
34
|
+
const base = (n >> 12) & 0x03;
|
|
35
|
+
const bcd = n & 0x0fff;
|
|
36
|
+
const hundreds = (bcd >> 8) & 0x0f;
|
|
37
|
+
const tens = (bcd >> 4) & 0x0f;
|
|
38
|
+
const ones = bcd & 0x0f;
|
|
39
|
+
const val = hundreds * 100 + tens * 10 + ones;
|
|
40
|
+
const multipliers = [10, 100, 1000, 10000];
|
|
41
|
+
return val * multipliers[base];
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function encodeS5Time(ms) {
|
|
45
|
+
const abs = Math.max(0, Math.min(ms, 9990000));
|
|
46
|
+
let base, divisor;
|
|
47
|
+
if (abs <= 9990) { base = 0; divisor = 10; }
|
|
48
|
+
else if (abs <= 99900) { base = 1; divisor = 100; }
|
|
49
|
+
else if (abs <= 999000) { base = 2; divisor = 1000; }
|
|
50
|
+
else { base = 3; divisor = 10000; }
|
|
51
|
+
const val = Math.round(abs / divisor);
|
|
52
|
+
const hundreds = Math.floor(val / 100) % 10;
|
|
53
|
+
const tens = Math.floor(val / 10) % 10;
|
|
54
|
+
const ones = val % 10;
|
|
55
|
+
return (base << 12) | (hundreds << 8) | (tens << 4) | ones;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// ── DateAndTime (BCD, 8 bytes) → Date ────────────────────────────────
|
|
59
|
+
// Wire: USInt array [YY, MM, DD, HH, MM, SS, ms_hi, ms_lo_dow]
|
|
60
|
+
function decodeDateAndTime(arr) {
|
|
61
|
+
if (!Array.isArray(arr) || arr.length < 8) return new Date(0);
|
|
62
|
+
const bcd = b => ((b >> 4) & 0x0f) * 10 + (b & 0x0f);
|
|
63
|
+
let year = bcd(arr[0]);
|
|
64
|
+
year += year >= 90 ? 1900 : 2000;
|
|
65
|
+
const month = bcd(arr[1]);
|
|
66
|
+
const day = bcd(arr[2]);
|
|
67
|
+
const hour = bcd(arr[3]);
|
|
68
|
+
const minute = bcd(arr[4]);
|
|
69
|
+
const second = bcd(arr[5]);
|
|
70
|
+
const msHi = bcd(arr[6]);
|
|
71
|
+
const msLo = (arr[7] >> 4) & 0x0f;
|
|
72
|
+
const ms = msHi * 10 + msLo;
|
|
73
|
+
return new Date(Date.UTC(year, month - 1, day, hour, minute, second, ms));
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function encodeDateAndTimeBcd(d) {
|
|
77
|
+
const toBcd = n => ((Math.floor(n / 10) % 10) << 4) | (n % 10);
|
|
78
|
+
const year = d.getUTCFullYear();
|
|
79
|
+
const ms = d.getUTCMilliseconds();
|
|
80
|
+
const dow = d.getUTCDay() === 0 ? 7 : d.getUTCDay();
|
|
81
|
+
const arr = [
|
|
82
|
+
toBcd(year % 100),
|
|
83
|
+
toBcd(d.getUTCMonth() + 1),
|
|
84
|
+
toBcd(d.getUTCDate()),
|
|
85
|
+
toBcd(d.getUTCHours()),
|
|
86
|
+
toBcd(d.getUTCMinutes()),
|
|
87
|
+
toBcd(d.getUTCSeconds()),
|
|
88
|
+
toBcd(Math.floor(ms / 10)),
|
|
89
|
+
((ms % 10) << 4) | (dow & 0x0f),
|
|
90
|
+
];
|
|
91
|
+
return new pvalue.ValueUSIntArray(arr);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// ── LDT / Ldt → Date ──────────────────────────────────────────────
|
|
95
|
+
// Wire: Timestamp (UInt64), nanoseconds since 1970-01-01
|
|
96
|
+
function decodeLdt(bigVal) {
|
|
97
|
+
const ns = typeof bigVal === 'bigint' ? bigVal : BigInt(bigVal);
|
|
98
|
+
const ms = Number(ns / 1000000n);
|
|
99
|
+
return new Date(ms);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// ── DTL (softdatatype 67) ↔ Date ─────────────────────────────────────
|
|
103
|
+
// DTL is transmitted as a packed Struct (id 0x02000043 = TI_LIB.SimpleType.67)
|
|
104
|
+
// carrying a 12-byte big-endian array:
|
|
105
|
+
// [0..1] YEAR (UInt) [2] MONTH [3] DAY [4] WEEKDAY
|
|
106
|
+
// [5] HOUR [6] MINUTE [7] SECOND [8..11] NANOSECOND (UDInt)
|
|
107
|
+
// Valid range: 1970-01-01 00:00:00 .. 2262-04-11 23:47:16.
|
|
108
|
+
const DTL_STRUCT_ID = 0x02000043;
|
|
109
|
+
// Type-version timestamp the PLC expects when writing a packed DTL. Used
|
|
110
|
+
// only as a fallback when the live value has not been captured from the
|
|
111
|
+
// PLC yet (see endpoint DTL timestamp cache).
|
|
112
|
+
const DTL_DEFAULT_INTERFACE_TIMESTAMP = 0x10ff4ad6dfd5774cn;
|
|
113
|
+
const DTL_MIN_MS = Date.UTC(1970, 0, 1, 0, 0, 0);
|
|
114
|
+
const DTL_MAX_MS = Date.UTC(2262, 3, 11, 23, 47, 16, 999);
|
|
115
|
+
|
|
116
|
+
// Extract the raw 12-byte payload from a decoded DTL value. The packed
|
|
117
|
+
// struct decodes to { <structId>: Buffer }; accept a Buffer/array directly
|
|
118
|
+
// for robustness.
|
|
119
|
+
function dtlBytes(j) {
|
|
120
|
+
if (Buffer.isBuffer(j)) return j;
|
|
121
|
+
if (Array.isArray(j)) return Buffer.from(j);
|
|
122
|
+
if (j && typeof j === 'object') {
|
|
123
|
+
const buf = Object.values(j).find(v => Buffer.isBuffer(v));
|
|
124
|
+
if (buf) return buf;
|
|
125
|
+
const arr = Object.values(j).find(v => Array.isArray(v));
|
|
126
|
+
if (arr) return Buffer.from(arr);
|
|
127
|
+
}
|
|
128
|
+
return null;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function decodeDtl(j) {
|
|
132
|
+
const b = dtlBytes(j);
|
|
133
|
+
if (!b || b.length < 12) return new Date(0);
|
|
134
|
+
const year = (b[0] << 8) | b[1];
|
|
135
|
+
const month = b[2];
|
|
136
|
+
const day = b[3];
|
|
137
|
+
// b[4] = WEEKDAY (derived from the date; ignored here)
|
|
138
|
+
const hour = b[5];
|
|
139
|
+
const minute = b[6];
|
|
140
|
+
const second = b[7];
|
|
141
|
+
const nanosecond = ((b[8] << 24) | (b[9] << 16) | (b[10] << 8) | b[11]) >>> 0;
|
|
142
|
+
const ms = Math.round(nanosecond / 1e6);
|
|
143
|
+
return new Date(Date.UTC(year, month - 1, day, hour, minute, second, ms));
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function encodeDtlBytes(d) {
|
|
147
|
+
const year = d.getUTCFullYear();
|
|
148
|
+
const month = d.getUTCMonth() + 1;
|
|
149
|
+
const day = d.getUTCDate();
|
|
150
|
+
const hour = d.getUTCHours();
|
|
151
|
+
const minute = d.getUTCMinutes();
|
|
152
|
+
const second = d.getUTCSeconds();
|
|
153
|
+
const nanosecond = (d.getUTCMilliseconds() * 1e6) >>> 0;
|
|
154
|
+
const b = Buffer.alloc(12);
|
|
155
|
+
b[0] = (year >> 8) & 0xff;
|
|
156
|
+
b[1] = year & 0xff;
|
|
157
|
+
b[2] = month;
|
|
158
|
+
b[3] = day;
|
|
159
|
+
b[4] = 0; // WEEKDAY: the PLC recomputes it on write
|
|
160
|
+
b[5] = hour;
|
|
161
|
+
b[6] = minute;
|
|
162
|
+
b[7] = second;
|
|
163
|
+
b[8] = (nanosecond >>> 24) & 0xff;
|
|
164
|
+
b[9] = (nanosecond >>> 16) & 0xff;
|
|
165
|
+
b[10] = (nanosecond >>> 8) & 0xff;
|
|
166
|
+
b[11] = nanosecond & 0xff;
|
|
167
|
+
return b;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// ── Date (softdatatype 9) → Date ─────────────────────────────────────
|
|
171
|
+
// Wire: UInt16, days since 1990-01-01
|
|
172
|
+
const DATE_EPOCH = Date.UTC(1990, 0, 1);
|
|
173
|
+
function decodeS7Date(daysSinceEpoch) {
|
|
174
|
+
const n = typeof daysSinceEpoch === 'number' ? daysSinceEpoch : Number(daysSinceEpoch);
|
|
175
|
+
// Return local midnight of the calendar day so encode/decode are symmetric
|
|
176
|
+
// and read->write round-trips stay stable in any timezone.
|
|
177
|
+
const utc = new Date(DATE_EPOCH + n * 86400000);
|
|
178
|
+
return new Date(utc.getUTCFullYear(), utc.getUTCMonth(), utc.getUTCDate());
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// ═════════════════════════════════════════════════════════════════════
|
|
182
|
+
// decodeReadValue – PValue → JS value, per README type contract
|
|
183
|
+
// ═════════════════════════════════════════════════════════════════════
|
|
184
|
+
|
|
185
|
+
function decodeReadValue(pval, softdatatype) {
|
|
186
|
+
if (!pval || typeof pval.toJs !== 'function') return null;
|
|
187
|
+
const j = pval.toJs();
|
|
188
|
+
|
|
189
|
+
// Accept both the canonical type name ('String') and the raw numeric
|
|
190
|
+
// softdatatype id (19). The latter may leak in from the CRC warmup
|
|
191
|
+
// cache; normalizing here keeps the switch a single source of truth.
|
|
192
|
+
if (typeof softdatatype === 'number') softdatatype = softdatatypeName(softdatatype);
|
|
193
|
+
|
|
194
|
+
switch (softdatatype) {
|
|
195
|
+
// ── Char / WChar → string ────────────────────────────────
|
|
196
|
+
case 'Char':
|
|
197
|
+
return String.fromCharCode(typeof j === 'number' ? j : Number(j));
|
|
198
|
+
case 'WChar':
|
|
199
|
+
return String.fromCharCode(typeof j === 'number' ? j : Number(j));
|
|
200
|
+
|
|
201
|
+
// ── String → decode header+bytes to string ───────────────
|
|
202
|
+
case 'String':
|
|
203
|
+
if (Array.isArray(j)) return decodeS7String(j);
|
|
204
|
+
if (typeof j === 'string') return j;
|
|
205
|
+
if (Buffer.isBuffer(j)) return j.toString('latin1');
|
|
206
|
+
return String(j);
|
|
207
|
+
|
|
208
|
+
// ── WString → decode header+UCS2 to string ──────────────
|
|
209
|
+
case 'WString':
|
|
210
|
+
if (Array.isArray(j)) return decodeS7WString(j);
|
|
211
|
+
if (typeof j === 'string') return j;
|
|
212
|
+
return String(j);
|
|
213
|
+
|
|
214
|
+
// ── 64-bit integers → BigInt ─────────────────────────────
|
|
215
|
+
case 'LInt':
|
|
216
|
+
case 'ULInt':
|
|
217
|
+
return typeof j === 'bigint' ? j : BigInt(j);
|
|
218
|
+
|
|
219
|
+
// ── LTime → BigInt (nanoseconds) ─────────────────────────
|
|
220
|
+
case 'LTime':
|
|
221
|
+
return typeof j === 'bigint' ? j : BigInt(j);
|
|
222
|
+
|
|
223
|
+
// ── LTOD → BigInt (nanoseconds since midnight) ───────────
|
|
224
|
+
case 'LTod':
|
|
225
|
+
return typeof j === 'bigint' ? j : BigInt(j);
|
|
226
|
+
|
|
227
|
+
// ── Date → Date object ───────────────────────────────────
|
|
228
|
+
case 'Date':
|
|
229
|
+
return decodeS7Date(j);
|
|
230
|
+
|
|
231
|
+
// ── LDT / Ldt → Date object ─────────────────────────────
|
|
232
|
+
case 'Ldt':
|
|
233
|
+
return decodeLdt(j);
|
|
234
|
+
|
|
235
|
+
// ── DTL → Date object (packed struct, 12-byte payload) ───
|
|
236
|
+
case 'Dtl':
|
|
237
|
+
return decodeDtl(j);
|
|
238
|
+
|
|
239
|
+
// ── DateAndTime → Date object ────────────────────────────
|
|
240
|
+
case 'DateAndTime':
|
|
241
|
+
if (Array.isArray(j)) return decodeDateAndTime(j);
|
|
242
|
+
if (typeof j === 'bigint') return decodeLdt(j);
|
|
243
|
+
return new Date(typeof j === 'number' ? j : 0);
|
|
244
|
+
|
|
245
|
+
// ── S5Time → number (milliseconds) ───────────────────────
|
|
246
|
+
case 'S5Time':
|
|
247
|
+
return decodeS5Time(typeof j === 'number' ? j : Number(j));
|
|
248
|
+
|
|
249
|
+
// ── Time → number (milliseconds, already DInt) ───────────
|
|
250
|
+
case 'Time':
|
|
251
|
+
return typeof j === 'number' ? j : Number(j);
|
|
252
|
+
|
|
253
|
+
// ── TOD → number (ms since midnight, already UDInt) ──────
|
|
254
|
+
case 'TimeOfDay':
|
|
255
|
+
return typeof j === 'number' ? j : Number(j);
|
|
256
|
+
|
|
257
|
+
// ── LWord → BigInt (64-bit, cannot fit a JS number) ──────
|
|
258
|
+
case 'LWord':
|
|
259
|
+
return typeof j === 'bigint' ? j : BigInt(j);
|
|
260
|
+
|
|
261
|
+
// ── default: best-effort ─────────────────────────────────
|
|
262
|
+
default:
|
|
263
|
+
if (typeof j === 'bigint') return Number(j);
|
|
264
|
+
if (Buffer.isBuffer(j)) return j;
|
|
265
|
+
return j;
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
// ═════════════════════════════════════════════════════════════════════
|
|
270
|
+
// encodeWriteValue – JS value → PValue, per softdatatype
|
|
271
|
+
// ═════════════════════════════════════════════════════════════════════
|
|
272
|
+
|
|
273
|
+
// Coerce a write value to a 64-bit BigInt without silent precision loss.
|
|
274
|
+
// A JS number only holds 53 significant bits, so any 64-bit literal beyond
|
|
275
|
+
// Number.MAX_SAFE_INTEGER is already corrupted by the time it arrives here
|
|
276
|
+
// (e.g. a Node-RED "num" inject of 0xFFFFFFFFFFFFFFFF). We therefore accept
|
|
277
|
+
// BigInt and strings (hex "0x.." or decimal, losslessly parsed) and reject
|
|
278
|
+
// unsafe numbers loudly instead of writing a wrong value.
|
|
279
|
+
function toBigInt64(val, typeName) {
|
|
280
|
+
if (typeof val === 'bigint') return val;
|
|
281
|
+
if (typeof val === 'string') {
|
|
282
|
+
const s = val.trim();
|
|
283
|
+
try {
|
|
284
|
+
return BigInt(s);
|
|
285
|
+
} catch (e) {
|
|
286
|
+
throw new Error(`Cannot encode ${typeName} value "${val}": not a valid integer`);
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
if (typeof val === 'number') {
|
|
290
|
+
if (!Number.isInteger(val) || !Number.isSafeInteger(val)) {
|
|
291
|
+
throw new Error(
|
|
292
|
+
`Cannot encode ${typeName} value ${val}: 64-bit values beyond ` +
|
|
293
|
+
`Number.MAX_SAFE_INTEGER (2^53) lose precision as a JS number — ` +
|
|
294
|
+
`pass a BigInt or a string instead`
|
|
295
|
+
);
|
|
296
|
+
}
|
|
297
|
+
return BigInt(val);
|
|
298
|
+
}
|
|
299
|
+
return BigInt(val);
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
function encodeWriteValue(val, softdatatype, opts = {}) {
|
|
303
|
+
if (typeof softdatatype === 'number') softdatatype = softdatatypeName(softdatatype);
|
|
304
|
+
|
|
305
|
+
switch (softdatatype) {
|
|
306
|
+
case 'Bool':
|
|
307
|
+
case 'BBool':
|
|
308
|
+
return new pvalue.ValueBool(!!val);
|
|
309
|
+
|
|
310
|
+
case 'Byte':
|
|
311
|
+
return new pvalue.ValueByte(Number(val));
|
|
312
|
+
case 'Word':
|
|
313
|
+
return new pvalue.ValueWord(Number(val));
|
|
314
|
+
case 'DWord':
|
|
315
|
+
return new pvalue.ValueDWord(Number(val));
|
|
316
|
+
case 'LWord':
|
|
317
|
+
return new pvalue.ValueLWord(toBigInt64(val, 'LWord'));
|
|
318
|
+
|
|
319
|
+
case 'SInt':
|
|
320
|
+
return new pvalue.ValueSInt(Number(val));
|
|
321
|
+
case 'Int':
|
|
322
|
+
return new pvalue.ValueInt(Number(val));
|
|
323
|
+
case 'DInt':
|
|
324
|
+
return new pvalue.ValueDInt(Number(val));
|
|
325
|
+
case 'USInt':
|
|
326
|
+
return new pvalue.ValueUSInt(Number(val));
|
|
327
|
+
case 'UInt':
|
|
328
|
+
return new pvalue.ValueUInt(Number(val));
|
|
329
|
+
case 'UDInt':
|
|
330
|
+
return new pvalue.ValueUDInt(Number(val));
|
|
331
|
+
case 'LInt':
|
|
332
|
+
return new pvalue.ValueLInt(toBigInt64(val, 'LInt'));
|
|
333
|
+
case 'ULInt':
|
|
334
|
+
return new pvalue.ValueULInt(toBigInt64(val, 'ULInt'));
|
|
335
|
+
|
|
336
|
+
case 'Real':
|
|
337
|
+
return new pvalue.ValueReal(Number(val));
|
|
338
|
+
case 'LReal':
|
|
339
|
+
return new pvalue.ValueLReal(Number(val));
|
|
340
|
+
|
|
341
|
+
case 'Char':
|
|
342
|
+
return new pvalue.ValueUSInt(typeof val === 'string' ? val.charCodeAt(0) : Number(val));
|
|
343
|
+
case 'WChar':
|
|
344
|
+
return new pvalue.ValueUInt(typeof val === 'string' ? val.charCodeAt(0) : Number(val));
|
|
345
|
+
case 'String': {
|
|
346
|
+
const s = String(val);
|
|
347
|
+
const bytes = Buffer.from(s, 'latin1');
|
|
348
|
+
const maxLen = 254;
|
|
349
|
+
const actualLen = Math.min(bytes.length, maxLen);
|
|
350
|
+
const arr = new Array(maxLen + 2).fill(0);
|
|
351
|
+
arr[0] = maxLen;
|
|
352
|
+
arr[1] = actualLen;
|
|
353
|
+
for (let i = 0; i < actualLen; i++) arr[i + 2] = bytes[i];
|
|
354
|
+
return new pvalue.ValueUSIntArray(arr);
|
|
355
|
+
}
|
|
356
|
+
case 'WString': {
|
|
357
|
+
const s = String(val);
|
|
358
|
+
const maxLen = 254;
|
|
359
|
+
const actualLen = Math.min(s.length, maxLen);
|
|
360
|
+
const arr = new Array(maxLen + 2).fill(0);
|
|
361
|
+
arr[0] = maxLen;
|
|
362
|
+
arr[1] = actualLen;
|
|
363
|
+
for (let i = 0; i < actualLen; i++) arr[i + 2] = s.charCodeAt(i);
|
|
364
|
+
return new pvalue.ValueUIntArray(arr);
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
case 'Time':
|
|
368
|
+
return new pvalue.ValueDInt(Number(val));
|
|
369
|
+
case 'S5Time': {
|
|
370
|
+
const bcd = encodeS5Time(Number(val));
|
|
371
|
+
return new pvalue.ValueWord(bcd);
|
|
372
|
+
}
|
|
373
|
+
case 'LTime':
|
|
374
|
+
return new pvalue.ValueTimespan(toBigInt64(val, 'LTime'));
|
|
375
|
+
|
|
376
|
+
case 'TimeOfDay':
|
|
377
|
+
return new pvalue.ValueUDInt(Number(val));
|
|
378
|
+
case 'LTod':
|
|
379
|
+
return new pvalue.ValueULInt(toBigInt64(val, 'LTod'));
|
|
380
|
+
|
|
381
|
+
case 'Date': {
|
|
382
|
+
const d = val instanceof Date ? val : new Date(val);
|
|
383
|
+
// Treat the JS Date as a local calendar date: derive the day count
|
|
384
|
+
// from its local Y/M/D so a local-midnight Date does not truncate to
|
|
385
|
+
// the previous day in timezones east of UTC.
|
|
386
|
+
const localMidnightUtc = Date.UTC(d.getFullYear(), d.getMonth(), d.getDate());
|
|
387
|
+
const days = Math.floor((localMidnightUtc - DATE_EPOCH) / 86400000);
|
|
388
|
+
return new pvalue.ValueUInt(Math.max(0, days));
|
|
389
|
+
}
|
|
390
|
+
case 'Ldt': {
|
|
391
|
+
const d = val instanceof Date ? val : (typeof val === 'bigint' ? val : new Date(val));
|
|
392
|
+
const ns = typeof d === 'bigint'
|
|
393
|
+
? toBigInt64(d, 'Ldt')
|
|
394
|
+
: BigInt(d.getTime()) * 1000000n;
|
|
395
|
+
// LDT is an unsigned nanosecond count since 1970-01-01. A value
|
|
396
|
+
// before the epoch (e.g. a Date parsed in a positive UTC offset)
|
|
397
|
+
// would otherwise wrap to ~2554 via two's complement.
|
|
398
|
+
if (ns < 0n || ns > 0xffffffffffffffffn) {
|
|
399
|
+
throw new Error(
|
|
400
|
+
`Cannot encode Ldt value ${ns} ns: out of LDT range ` +
|
|
401
|
+
`(1970-01-01 00:00:00.000000000 .. 2554-07-21 23:34:33.709551615)`
|
|
402
|
+
);
|
|
403
|
+
}
|
|
404
|
+
return new pvalue.ValueTimestamp(ns);
|
|
405
|
+
}
|
|
406
|
+
case 'DateAndTime': {
|
|
407
|
+
const d = val instanceof Date ? val : new Date(val);
|
|
408
|
+
return encodeDateAndTimeBcd(d);
|
|
409
|
+
}
|
|
410
|
+
case 'Dtl': {
|
|
411
|
+
const d = val instanceof Date ? val : new Date(val);
|
|
412
|
+
const ms = d.getTime();
|
|
413
|
+
if (Number.isNaN(ms) || ms < DTL_MIN_MS || ms > DTL_MAX_MS) {
|
|
414
|
+
throw new Error(
|
|
415
|
+
`Cannot encode Dtl value ${val}: out of DTL range ` +
|
|
416
|
+
`(1970-01-01 00:00:00 .. 2262-04-11 23:47:16)`
|
|
417
|
+
);
|
|
418
|
+
}
|
|
419
|
+
const struct = new pvalue.ValueStruct(DTL_STRUCT_ID);
|
|
420
|
+
struct.packedInterfaceTimestamp = opts.dtlInterfaceTimestamp != null
|
|
421
|
+
? (typeof opts.dtlInterfaceTimestamp === 'bigint'
|
|
422
|
+
? opts.dtlInterfaceTimestamp
|
|
423
|
+
: BigInt(opts.dtlInterfaceTimestamp))
|
|
424
|
+
: DTL_DEFAULT_INTERFACE_TIMESTAMP;
|
|
425
|
+
struct.packedTransportFlags = 2;
|
|
426
|
+
struct.addStructElement(DTL_STRUCT_ID, new pvalue.ValueBlob(0, encodeDtlBytes(d)));
|
|
427
|
+
return struct;
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
default:
|
|
431
|
+
return pvalue.valueFromJs(val, softdatatype ? softdatatype.toLowerCase() : undefined);
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
module.exports = { encodeWriteValue, decodeReadValue, decodeS5Time, encodeS5Time };
|