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,355 @@
|
|
|
1
|
+
module.exports = function (RED) {
|
|
2
|
+
|
|
3
|
+
// === 提供給前端介面抓取 Database Schema 的 API ===
|
|
4
|
+
RED.httpAdmin.get("/mysql-extended/schema/:id", RED.auth.needsPermission('nodes.read'), function(req, res) {
|
|
5
|
+
const configNode = RED.nodes.getNode(req.params.id);
|
|
6
|
+
if (configNode && configNode.pool) {
|
|
7
|
+
const sql = "SELECT TABLE_NAME, COLUMN_NAME FROM information_schema.columns WHERE table_schema = DATABASE()";
|
|
8
|
+
configNode.pool.query(sql, function(err, rows) {
|
|
9
|
+
if (err) {
|
|
10
|
+
return res.status(500).json({ error: err.message });
|
|
11
|
+
}
|
|
12
|
+
const tables = new Set();
|
|
13
|
+
const cols = new Set();
|
|
14
|
+
rows.forEach(r => {
|
|
15
|
+
tables.add(r.TABLE_NAME);
|
|
16
|
+
cols.add(r.COLUMN_NAME);
|
|
17
|
+
});
|
|
18
|
+
res.json({
|
|
19
|
+
tables: Array.from(tables),
|
|
20
|
+
allColumns: Array.from(cols)
|
|
21
|
+
});
|
|
22
|
+
});
|
|
23
|
+
} else {
|
|
24
|
+
res.status(404).json({ error: "Database not connected or node not deployed yet." });
|
|
25
|
+
}
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
function MysqlExtendedNode(config) {
|
|
29
|
+
RED.nodes.createNode(this, config);
|
|
30
|
+
const node = this;
|
|
31
|
+
config.autoGenerate = (config.autoGenerate !== false && config.autoGenerate !== "false");
|
|
32
|
+
config.action = (config.action || "SELECT").toUpperCase();
|
|
33
|
+
|
|
34
|
+
// 綁定資料庫設定
|
|
35
|
+
this.mydb = config.mydb;
|
|
36
|
+
this.mydbConfig = RED.nodes.getNode(this.mydb);
|
|
37
|
+
this.status({});
|
|
38
|
+
|
|
39
|
+
if (this.mydbConfig) {
|
|
40
|
+
this.mydbConfig.connect();
|
|
41
|
+
// 監聽連線狀態,同步顯示在節點下方
|
|
42
|
+
this.mydbConfig.on("state", function(info) {
|
|
43
|
+
if (info === "connecting") { node.status({fill:"grey", shape:"ring", text:info}); }
|
|
44
|
+
else if (info === "connected") { node.status({fill:"green", shape:"dot", text:info}); }
|
|
45
|
+
else { node.status({fill:"red", shape:"ring", text:info}); }
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
node.on('input', function (msg, send, done) {
|
|
50
|
+
send = send || function() { node.send.apply(node, arguments) };
|
|
51
|
+
try {
|
|
52
|
+
let sqlTemplate = "";
|
|
53
|
+
if (!config.autoGenerate) {
|
|
54
|
+
sqlTemplate = config.sqlCustom || "";
|
|
55
|
+
} else {
|
|
56
|
+
switch (config.action) {
|
|
57
|
+
case "SELECT": sqlTemplate = buildSelectSQL(config, msg, node); break;
|
|
58
|
+
case "INSERT": sqlTemplate = buildInsertSQL(config, msg, node); break;
|
|
59
|
+
case "UPDATE": sqlTemplate = buildUpdateSQL(config, msg, node); break;
|
|
60
|
+
case "DELETE": sqlTemplate = buildDeleteSQL(config, msg, node); break;
|
|
61
|
+
default: sqlTemplate = "";
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
if (msg.sql && typeof msg.sql === "string" && msg.useRawSql === true) {
|
|
65
|
+
sqlTemplate = msg.sql;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
sqlTemplate = applyAllTemplates(sqlTemplate, msg, node);
|
|
69
|
+
msg.topic = sqlTemplate;
|
|
70
|
+
msg.sql = sqlTemplate;
|
|
71
|
+
|
|
72
|
+
// === 執行查詢的整合邏輯 ===
|
|
73
|
+
if (node.mydbConfig && node.mydbConfig.connected) {
|
|
74
|
+
node.mydbConfig.pool.getConnection(function (err, conn) {
|
|
75
|
+
if (err) {
|
|
76
|
+
if (conn) conn.release();
|
|
77
|
+
node.status({ fill: "red", shape: "ring", text: "Error: " + err.code });
|
|
78
|
+
node.error(err, msg);
|
|
79
|
+
if (done) done();
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
conn.query(sqlTemplate, [], function (err, rows) {
|
|
84
|
+
conn.release();
|
|
85
|
+
if (err) {
|
|
86
|
+
node.status({ fill: "red", shape: "ring", text: "Error: " + err.code });
|
|
87
|
+
node.error(err, msg);
|
|
88
|
+
} else {
|
|
89
|
+
msg.payload = rows;
|
|
90
|
+
send(msg);
|
|
91
|
+
node.status({ fill: "green", shape: "dot", text: "OK" });
|
|
92
|
+
}
|
|
93
|
+
if (done) done();
|
|
94
|
+
});
|
|
95
|
+
});
|
|
96
|
+
} else {
|
|
97
|
+
node.error("MySQL Database not connected or not configured", msg);
|
|
98
|
+
node.status({fill:"red", shape:"ring", text:"Not connected"});
|
|
99
|
+
if (done) done();
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
} catch (e) {
|
|
103
|
+
node.error(e && e.message || e, msg);
|
|
104
|
+
msg.payload = { error: (e && e.message) || String(e) };
|
|
105
|
+
send(msg); done && done();
|
|
106
|
+
}
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
node.on('close', function() {
|
|
110
|
+
if (node.mydbConfig) {
|
|
111
|
+
node.mydbConfig.removeAllListeners();
|
|
112
|
+
}
|
|
113
|
+
node.status({});
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// ==== 輔助函數區 ====
|
|
118
|
+
function normOp(op) { return op === "==" ? "=" : op; }
|
|
119
|
+
|
|
120
|
+
function parseJoins(jsonStr) {
|
|
121
|
+
try {
|
|
122
|
+
const arr = JSON.parse(jsonStr || "[]");
|
|
123
|
+
return Array.isArray(arr) ? arr : [];
|
|
124
|
+
} catch (e) { return []; }
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function autoAliasFor(table) {
|
|
128
|
+
const t = (table || "").trim(); if (!t) return "";
|
|
129
|
+
if (t === "oc_order") return "oo";
|
|
130
|
+
if (t === "oc_order_product") return "ooc";
|
|
131
|
+
const bare = t.replace(/^oc_/, "");
|
|
132
|
+
const parts = bare.split("_").filter(Boolean);
|
|
133
|
+
if (parts.length === 0) return t[0];
|
|
134
|
+
return parts.map(p => p[0]).join("");
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function qField(aliasOrTable, field) {
|
|
138
|
+
field = (field || "").trim();
|
|
139
|
+
if (!field) return "";
|
|
140
|
+
if (field.includes(".") || /\bas\b/i.test(field) || field.includes("(")) return field;
|
|
141
|
+
if (field === "*") return (aliasOrTable) + ".*";
|
|
142
|
+
return (aliasOrTable) + "." + field;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function getMsgProp(msg, path) {
|
|
146
|
+
if (!msg || !path) return undefined;
|
|
147
|
+
const parts = String(path).split(".");
|
|
148
|
+
let obj = msg;
|
|
149
|
+
for (let i = 0; i < parts.length; i++) {
|
|
150
|
+
if (obj == null) return undefined;
|
|
151
|
+
obj = obj[parts[i]];
|
|
152
|
+
}
|
|
153
|
+
return obj;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
function applyAllTemplates(str, msg, node) {
|
|
157
|
+
if (!str || typeof str !== "string") return str;
|
|
158
|
+
return str.replace(/\{\{\s*(.*?)\s*\}\}/g, function (_all, path) {
|
|
159
|
+
path = path.trim();
|
|
160
|
+
let type = "msg";
|
|
161
|
+
|
|
162
|
+
if (path.startsWith("msg.")) { type = "msg"; path = path.substring(4); }
|
|
163
|
+
else if (path.startsWith("flow.")) { type = "flow"; path = path.substring(5); }
|
|
164
|
+
else if (path.startsWith("global.")) { type = "global"; path = path.substring(7); }
|
|
165
|
+
else if (path === "payload") { type = "msg"; path = "payload"; }
|
|
166
|
+
|
|
167
|
+
let val;
|
|
168
|
+
if (type === "msg") { val = getMsgProp(msg, path); }
|
|
169
|
+
else if (type === "flow") { try { val = node.context().flow.get(path); } catch(e){} }
|
|
170
|
+
else if (type === "global") { try { val = node.context().global.get(path); } catch(e){} }
|
|
171
|
+
|
|
172
|
+
if (val === null || val === undefined) return "";
|
|
173
|
+
return String(val);
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
function resolveCtxValue(src, nameOrLiteral, msg, node) {
|
|
178
|
+
if (src === "flow") { try { return node.context().flow.get(nameOrLiteral); } catch (e) { return undefined; } }
|
|
179
|
+
if (src === "global") { try { return node.context().global.get(nameOrLiteral); } catch (e) { return undefined; } }
|
|
180
|
+
return nameOrLiteral;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
function sqlValueLiteral(v) {
|
|
184
|
+
if (v === null || v === undefined) return "NULL";
|
|
185
|
+
if (typeof v === "number") return String(v);
|
|
186
|
+
if (typeof v === "boolean") return v ? "1" : "0";
|
|
187
|
+
return "'" + String(v).replace(/'/g, "\\'") + "'";
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
function buildWhereFromCfg(cfg, aliasMain, aliasForLastJoin, msg, node) {
|
|
191
|
+
let whereSQL = (cfg.whereManual || "").trim();
|
|
192
|
+
if (whereSQL) return whereSQL;
|
|
193
|
+
|
|
194
|
+
const wField = (cfg.whereField || "").trim();
|
|
195
|
+
const wOp = normOp((cfg.whereOp || "").trim());
|
|
196
|
+
const wVal = (cfg.whereValue || "").trim();
|
|
197
|
+
if (wField && wOp && wVal) {
|
|
198
|
+
const qualified = (wField.indexOf(".") !== -1)
|
|
199
|
+
? wField
|
|
200
|
+
: qField(aliasMain || aliasForLastJoin || "", wField);
|
|
201
|
+
|
|
202
|
+
const vsrc = (cfg.whereVSrc || "value");
|
|
203
|
+
let raw = (vsrc && vsrc !== "value") ? resolveCtxValue(vsrc, wVal, msg, node) : wVal;
|
|
204
|
+
let lit;
|
|
205
|
+
if (raw === null || raw === undefined) lit = "NULL";
|
|
206
|
+
else if (typeof raw === "number" || /^-?\d+(?:\.\d+)?$/.test(String(raw))) lit = String(raw);
|
|
207
|
+
else if (typeof raw === "boolean") lit = raw ? "1" : "0";
|
|
208
|
+
else lit = "'" + String(raw).replace(/'/g, "\\'") + "'";
|
|
209
|
+
return qualified + " " + wOp + " " + lit;
|
|
210
|
+
}
|
|
211
|
+
return "";
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
function buildOrderByFromCfg(cfg, aliasMain) {
|
|
215
|
+
const raw = (cfg.orderByFields || "").trim();
|
|
216
|
+
if (!raw) return "";
|
|
217
|
+
const dir = ((cfg.orderDir || "ASC").toUpperCase() === "DESC") ? "DESC" : "ASC";
|
|
218
|
+
const list = raw.split(",").map(s => s.trim()).filter(Boolean);
|
|
219
|
+
if (!list.length) return "";
|
|
220
|
+
const parts = list.map(f => (f.indexOf(".") !== -1 || /\bas\b/i.test(f)) ? f : qField(aliasMain, f));
|
|
221
|
+
return " ORDER BY " + parts.join(", ") + " " + dir;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
function buildSelectSQL(cfg, msg, node) {
|
|
225
|
+
const mainTable = (cfg.mainTable || "").trim();
|
|
226
|
+
if (!mainTable) return "";
|
|
227
|
+
const mainAlias = (cfg.mainAlias || autoAliasFor(mainTable));
|
|
228
|
+
const mainFields = (cfg.mainFields || "").split(",").map(s => s.trim()).filter(Boolean);
|
|
229
|
+
const joins = parseJoins(cfg.joinsJSON);
|
|
230
|
+
|
|
231
|
+
let selectParts = [];
|
|
232
|
+
if (mainFields.length === 0 && (!joins.length || joins.every(j => !j.fields))) {
|
|
233
|
+
selectParts.push(mainAlias + ".*");
|
|
234
|
+
} else {
|
|
235
|
+
selectParts = selectParts.concat(mainFields.map(f => qField(mainAlias, f)));
|
|
236
|
+
joins.forEach(j => {
|
|
237
|
+
const alias = (j.alias && j.alias.trim()) || autoAliasFor(j.table);
|
|
238
|
+
const fields = (j.fields || "").split(",").map(s => s.trim()).filter(Boolean);
|
|
239
|
+
fields.forEach(f => selectParts.push(qField(alias, f)));
|
|
240
|
+
});
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
let fromSQL = `${mainTable} AS ${mainAlias}`;
|
|
244
|
+
let lastJoinAlias = null;
|
|
245
|
+
joins.forEach(j => {
|
|
246
|
+
const jt = (j.table || "").trim();
|
|
247
|
+
if (!jt) return;
|
|
248
|
+
const jtype = (j.type || "JOIN").toUpperCase();
|
|
249
|
+
const key = (j.key || "id").trim();
|
|
250
|
+
const alias = (j.alias && j.alias.trim()) || autoAliasFor(jt);
|
|
251
|
+
fromSQL += ` ${jtype} ${jt} AS ${alias} ON ${mainAlias}.${key} = ${alias}.${key}`;
|
|
252
|
+
lastJoinAlias = alias;
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
const whereSQL = buildWhereFromCfg(cfg, mainAlias, lastJoinAlias, msg, node);
|
|
256
|
+
const orderSQL = buildOrderByFromCfg(cfg, mainAlias);
|
|
257
|
+
let sql = "SELECT " + selectParts.join(", ") + " FROM " + fromSQL;
|
|
258
|
+
if (whereSQL) sql += " WHERE " + whereSQL;
|
|
259
|
+
if (orderSQL) sql += orderSQL;
|
|
260
|
+
return sql;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
function buildInsertSQL(cfg, msg, node) {
|
|
264
|
+
const table = (cfg.iTable || "").trim();
|
|
265
|
+
const cols = (cfg.iColumns || "").split(",").map(s => s.trim()).filter(Boolean);
|
|
266
|
+
|
|
267
|
+
const vals = (cfg.iValues || "").split(",").map(s => {
|
|
268
|
+
let v = s.trim();
|
|
269
|
+
let m = v.match(/^\{\{\s*(.*?)\s*\}\}$/);
|
|
270
|
+
if (m) {
|
|
271
|
+
let path = m[1].trim();
|
|
272
|
+
let type = "msg";
|
|
273
|
+
if (path.startsWith("msg.")) { type = "msg"; path = path.substring(4); }
|
|
274
|
+
else if (path.startsWith("flow.")) { type = "flow"; path = path.substring(5); }
|
|
275
|
+
else if (path.startsWith("global.")) { type = "global"; path = path.substring(7); }
|
|
276
|
+
else if (path === "payload") { type = "msg"; path = "payload"; }
|
|
277
|
+
|
|
278
|
+
let val;
|
|
279
|
+
if (type === "msg") { val = getMsgProp(msg, path); }
|
|
280
|
+
else if (type === "flow") { try { val = node.context().flow.get(path); } catch(e){} }
|
|
281
|
+
else if (type === "global") { try { val = node.context().global.get(path); } catch(e){} }
|
|
282
|
+
return sqlValueLiteral(val);
|
|
283
|
+
}
|
|
284
|
+
return v;
|
|
285
|
+
}).filter(Boolean);
|
|
286
|
+
|
|
287
|
+
if (!table || cols.length === 0 || vals.length === 0) return "";
|
|
288
|
+
return "INSERT INTO " + table + " (" + cols.join(", ") + ") VALUES (" + vals.join(", ") + ")";
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
function buildUpdateSQL(cfg, msg, node) {
|
|
292
|
+
const table = (cfg.uTable || "").trim();
|
|
293
|
+
const setClause = (cfg.uSet || "").trim();
|
|
294
|
+
if (!table || !setClause) return "";
|
|
295
|
+
|
|
296
|
+
const uWhereManual = (cfg.uWhereManual || "").trim();
|
|
297
|
+
let whereSQL = "";
|
|
298
|
+
|
|
299
|
+
if (uWhereManual) {
|
|
300
|
+
whereSQL = uWhereManual;
|
|
301
|
+
} else {
|
|
302
|
+
const wf = (cfg.uWhereField || "").trim();
|
|
303
|
+
const wo = normOp((cfg.uWhereOp || "").trim());
|
|
304
|
+
const wv = (cfg.uWhereValue || "").trim();
|
|
305
|
+
const vs = (cfg.uWhereVSrc || "value");
|
|
306
|
+
|
|
307
|
+
if (wf && wo && wv) {
|
|
308
|
+
const qualified = (wf.indexOf(".") !== -1) ? wf : (table + "." + wf);
|
|
309
|
+
const __raw = resolveCtxValue(vs, wv, msg, node);
|
|
310
|
+
let __lit;
|
|
311
|
+
if (__raw === null || __raw === undefined) __lit = "NULL";
|
|
312
|
+
else if (typeof __raw === "number" || /^-?\d+(?:\.\d+)?$/.test(String(__raw))) __lit = String(__raw);
|
|
313
|
+
else __lit = sqlValueLiteral(__raw);
|
|
314
|
+
whereSQL = qualified + " " + wo + " " + __lit;
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
let sql = "UPDATE " + table + " SET " + setClause;
|
|
319
|
+
if (whereSQL) sql += " WHERE " + whereSQL;
|
|
320
|
+
return sql;
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
function buildDeleteSQL(cfg, msg, node) {
|
|
324
|
+
const table = (cfg.dTable || "").trim();
|
|
325
|
+
if (!table) return "";
|
|
326
|
+
|
|
327
|
+
const dWhereManual = (cfg.dWhereManual || "").trim();
|
|
328
|
+
let whereSQL = "";
|
|
329
|
+
|
|
330
|
+
if (dWhereManual) {
|
|
331
|
+
whereSQL = dWhereManual;
|
|
332
|
+
} else {
|
|
333
|
+
const wf = (cfg.dWhereField || "").trim();
|
|
334
|
+
const wo = normOp((cfg.dWhereOp || "").trim());
|
|
335
|
+
const wv = (cfg.dWhereValue || "").trim();
|
|
336
|
+
const vs = (cfg.dWhereVSrc || "value");
|
|
337
|
+
|
|
338
|
+
if (wf && wo && wv) {
|
|
339
|
+
const qualified = (wf.indexOf(".") !== -1) ? wf : (table + "." + wf);
|
|
340
|
+
const __raw = resolveCtxValue(vs, wv, msg, node);
|
|
341
|
+
let __lit;
|
|
342
|
+
if (__raw === null || __raw === undefined) __lit = "NULL";
|
|
343
|
+
else if (typeof __raw === "number" || /^-?\d+(?:\.\d+)?$/.test(String(__raw))) __lit = String(__raw);
|
|
344
|
+
else __lit = sqlValueLiteral(__raw);
|
|
345
|
+
whereSQL = qualified + " " + wo + " " + __lit;
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
let sql = "DELETE FROM " + table;
|
|
350
|
+
if (whereSQL) sql += " WHERE " + whereSQL;
|
|
351
|
+
return sql;
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
RED.nodes.registerType("mysql-extended", MysqlExtendedNode);
|
|
355
|
+
};
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
<script type="text/javascript">
|
|
2
|
+
RED.nodes.registerType('rfid-lazy',{
|
|
3
|
+
category: 'mytools',
|
|
4
|
+
color: '#87A980',
|
|
5
|
+
defaults: {
|
|
6
|
+
name: {value:""},
|
|
7
|
+
mode: {value:"READ"},
|
|
8
|
+
readTriggerMode: {value:"manual"},
|
|
9
|
+
autoReadInterval: {value:"1000", validate:RED.validators.number()},
|
|
10
|
+
writeStrategy: {value:"manual"},
|
|
11
|
+
offset: {value:"0", validate:RED.validators.number()},
|
|
12
|
+
length: {value:"4", validate:RED.validators.number()},
|
|
13
|
+
writeData: {value:""},
|
|
14
|
+
writeDataType: {value:"str"},
|
|
15
|
+
enableDeparture: {value:"false"},
|
|
16
|
+
departureTimeout: {value:"3", validate:RED.validators.number()}
|
|
17
|
+
},
|
|
18
|
+
inputs: 1,
|
|
19
|
+
outputs: 3,
|
|
20
|
+
outputLabels: ["連至 Flex Getter", "連至 Flex Write", "最終輸出結果"],
|
|
21
|
+
icon: "font-awesome/fa-microchip",
|
|
22
|
+
label: function() {
|
|
23
|
+
return this.name || "RFID 智慧模組";
|
|
24
|
+
},
|
|
25
|
+
oneditprepare: function() {
|
|
26
|
+
$("#node-input-writeData").typedInput({
|
|
27
|
+
default: 'str',
|
|
28
|
+
typeField: $("#node-input-writeDataType"),
|
|
29
|
+
types: ['str', 'msg', 'flow', 'global']
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
function toggleOptions() {
|
|
33
|
+
var mode = $("#node-input-mode").val();
|
|
34
|
+
var triggerMode = $("#node-input-readTriggerMode").val();
|
|
35
|
+
var enableDep = $("#node-input-enableDeparture").val();
|
|
36
|
+
var writeStrat = $("#node-input-writeStrategy").val();
|
|
37
|
+
|
|
38
|
+
if (mode === "WRITE") {
|
|
39
|
+
$(".write-strategy-options").show();
|
|
40
|
+
$(".write-options").show();
|
|
41
|
+
$(".read-options").hide();
|
|
42
|
+
$(".auto-read-options").hide();
|
|
43
|
+
if (writeStrat === "auto") {
|
|
44
|
+
$(".length-row").hide();
|
|
45
|
+
} else {
|
|
46
|
+
$(".length-row").show();
|
|
47
|
+
}
|
|
48
|
+
} else if (mode === "READ") {
|
|
49
|
+
$(".write-strategy-options").hide();
|
|
50
|
+
$(".write-options").hide();
|
|
51
|
+
$(".read-options").show();
|
|
52
|
+
$(".length-row").show();
|
|
53
|
+
if (triggerMode === "auto") {
|
|
54
|
+
$(".auto-read-options").show();
|
|
55
|
+
} else {
|
|
56
|
+
$(".auto-read-options").hide();
|
|
57
|
+
}
|
|
58
|
+
} else {
|
|
59
|
+
$(".write-strategy-options").hide();
|
|
60
|
+
$(".write-options").hide();
|
|
61
|
+
$(".read-options").hide();
|
|
62
|
+
$(".auto-read-options").hide();
|
|
63
|
+
$(".length-row").show();
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if (enableDep === "true") {
|
|
67
|
+
$(".departure-timeout-row").show();
|
|
68
|
+
} else {
|
|
69
|
+
$(".departure-timeout-row").hide();
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
$("#node-input-mode").on("change", toggleOptions);
|
|
74
|
+
$("#node-input-readTriggerMode").on("change", toggleOptions);
|
|
75
|
+
$("#node-input-enableDeparture").on("change", toggleOptions);
|
|
76
|
+
$("#node-input-writeStrategy").on("change", toggleOptions);
|
|
77
|
+
toggleOptions();
|
|
78
|
+
}
|
|
79
|
+
});
|
|
80
|
+
</script>
|
|
81
|
+
|
|
82
|
+
<script type="text/html" data-template-name="rfid-lazy">
|
|
83
|
+
<div class="form-row">
|
|
84
|
+
<label for="node-input-name"><i class="fa fa-tag"></i> 節點名稱</label>
|
|
85
|
+
<input type="text" id="node-input-name" placeholder="自訂名稱">
|
|
86
|
+
</div>
|
|
87
|
+
<div class="form-row">
|
|
88
|
+
<label for="node-input-mode"><i class="fa fa-sliders"></i> 執行模式</label>
|
|
89
|
+
<select id="node-input-mode">
|
|
90
|
+
<option value="READ">Read (讀取 USER 區)</option>
|
|
91
|
+
<option value="WRITE">Write (寫入 USER 區)</option>
|
|
92
|
+
<option value="CLEAR">Clear (批次清空 USER 區)</option>
|
|
93
|
+
</select>
|
|
94
|
+
</div>
|
|
95
|
+
|
|
96
|
+
<div class="form-row read-options">
|
|
97
|
+
<label for="node-input-readTriggerMode"><i class="fa fa-refresh"></i> 讀取觸發方式</label>
|
|
98
|
+
<select id="node-input-readTriggerMode">
|
|
99
|
+
<option value="manual">手動 (需外部 Inject 觸發)</option>
|
|
100
|
+
<option value="auto">自動 (持續偵測 Tag)</option>
|
|
101
|
+
</select>
|
|
102
|
+
</div>
|
|
103
|
+
<div class="form-row auto-read-options">
|
|
104
|
+
<label for="node-input-autoReadInterval"><i class="fa fa-clock-o"></i> 自動偵測頻率</label>
|
|
105
|
+
<input type="number" id="node-input-autoReadInterval" placeholder="毫秒 (預設: 1000)">
|
|
106
|
+
</div>
|
|
107
|
+
|
|
108
|
+
<hr>
|
|
109
|
+
<div class="form-row">
|
|
110
|
+
<label for="node-input-enableDeparture"><i class="fa fa-bell-o"></i> 移開提醒</label>
|
|
111
|
+
<select id="node-input-enableDeparture">
|
|
112
|
+
<option value="false">停用</option>
|
|
113
|
+
<option value="true">啟用 (Tag 移開後發送通知)</option>
|
|
114
|
+
</select>
|
|
115
|
+
</div>
|
|
116
|
+
<div class="form-row departure-timeout-row">
|
|
117
|
+
<label for="node-input-departureTimeout"><i class="fa fa-hourglass-half"></i> 延遲時間 (秒)</label>
|
|
118
|
+
<input type="number" id="node-input-departureTimeout" placeholder="輸入秒數 (例如: 3)">
|
|
119
|
+
</div>
|
|
120
|
+
<hr>
|
|
121
|
+
|
|
122
|
+
<div class="form-row write-strategy-options">
|
|
123
|
+
<label for="node-input-writeStrategy"><i class="fa fa-cogs"></i> 寫入模式</label>
|
|
124
|
+
<select id="node-input-writeStrategy">
|
|
125
|
+
<option value="manual">手動設定長度 (超出長度自動截斷)</option>
|
|
126
|
+
<option value="auto">全自動計算 (依字串長度無限分批)</option>
|
|
127
|
+
</select>
|
|
128
|
+
</div>
|
|
129
|
+
|
|
130
|
+
<div class="form-row">
|
|
131
|
+
<label for="node-input-offset"><i class="fa fa-step-forward"></i> 起始位址</label>
|
|
132
|
+
<input type="number" id="node-input-offset" placeholder="Word 偏移量">
|
|
133
|
+
</div>
|
|
134
|
+
<div class="form-row length-row">
|
|
135
|
+
<label for="node-input-length"><i class="fa fa-arrows-h"></i> 執行長度</label>
|
|
136
|
+
<input type="number" id="node-input-length" placeholder="讀取最大 16, 寫/清最大 64">
|
|
137
|
+
</div>
|
|
138
|
+
|
|
139
|
+
<div class="form-row write-options">
|
|
140
|
+
<label for="node-input-writeData"><i class="fa fa-pencil"></i> 寫入數值</label>
|
|
141
|
+
<input type="text" id="node-input-writeData">
|
|
142
|
+
<input type="hidden" id="node-input-writeDataType">
|
|
143
|
+
</div>
|
|
144
|
+
</script>
|
|
145
|
+
|
|
146
|
+
<script type="text/html" data-help-name="rfid-lazy">
|
|
147
|
+
<p>這是一個全自動的 RFID 讀寫控制模組 V9 (絕對穩定版)。</p>
|
|
148
|
+
<h3>模式說明:</h3>
|
|
149
|
+
<ul>
|
|
150
|
+
<li><strong>讀取 (Read):</strong> 為確保通訊絕對穩定,單次讀取極限強制鎖定為 16 個 Word。</li>
|
|
151
|
+
<li><strong>寫入 (Write) 自動分批:</strong> 你只需給定字串內容,大腦將自動計算長度,並在背景以「高延遲防撞機制」接力分批寫入,直到超長資料完美寫入完畢!</li>
|
|
152
|
+
</ul>
|
|
153
|
+
</script>
|