node-red-contrib-symi-modbus 2.10.17 → 2.10.19

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.md CHANGED
@@ -487,6 +487,18 @@ msg.payload = 1; // 或 0
487
487
 
488
488
  ### 更新日志
489
489
 
490
+ #### v2.10.19 (2026-06-30)
491
+
492
+ - **[工程]** 提取 `lib/lru-map.js`、`lib/slave-switch-cache.js`,精简 `modbus-slave-switch` 全局缓存逻辑并限制 Mesh LED 定时器上限(5000)。
493
+ - **[工程]** 新增 `lib/http-admin-utils.js`,14 个 HTTP Admin 路由统一权限校验(read/write)。
494
+ - **[工程]** `modbus-master` 串口列表 API 复用 `lib/serial-utils`,消除重复实现。
495
+ - **[测试]** 新增 `mesh-protocol`、`lightweight-protocol` 协议帧解析单元测试(`npm test`)。
496
+
497
+ #### v2.10.18 (2026-06-30)
498
+
499
+ - **[打包]** 修复 npm 发布包误含 `.codebase-memory/` 开发索引文件的问题,包体积从约 493KB 降至约 158KB,避免将本地知识图谱索引发布到 npm。
500
+ - **[工程]** 在 `.npmignore` 与 `.gitignore` 中排除 `.codebase-memory/`,确保开发工具产物不会进入发布包或版本库。
501
+
490
502
  #### v2.10.17 (2026-02-07)
491
503
 
492
504
  - **Mesh 窗帘协议兼容性**:按照《蓝牙MESH网关(初级版)串口协议V1.0》修正 0x53 0x80 0x06 节点状态帧解析逻辑,明确区分事件类型 RESULT_EVENT_NODE_STATUS(0x06) 与 `VD_MSG_TYPE_CURT_RUN_STATUS(0x05)` / `VD_MSG_TYPE_CURT_RUN_PER_POS(0x06)`,不再误把 `subOp` 当成窗帘 msg_type 使用,严格从数据区解析 `[msg_type][param]`。
@@ -0,0 +1,34 @@
1
+ "use strict";
2
+
3
+ /**
4
+ * Node-RED HTTP Admin 路由注册(统一权限校验)
5
+ */
6
+ function needsPermission(RED, permission) {
7
+ if (RED.auth && typeof RED.auth.needsPermission === "function") {
8
+ return RED.auth.needsPermission(permission);
9
+ }
10
+ return function passThrough(req, res, next) {
11
+ if (typeof next === "function") {
12
+ next();
13
+ }
14
+ };
15
+ }
16
+
17
+ function registerGet(RED, path, permission, handler) {
18
+ RED.httpAdmin.get(path, needsPermission(RED, permission), handler);
19
+ }
20
+
21
+ function registerPost(RED, path, permission, handler) {
22
+ RED.httpAdmin.post(path, needsPermission(RED, permission), handler);
23
+ }
24
+
25
+ function registerDelete(RED, path, permission, handler) {
26
+ RED.httpAdmin.delete(path, needsPermission(RED, permission), handler);
27
+ }
28
+
29
+ module.exports = {
30
+ needsPermission,
31
+ registerGet,
32
+ registerPost,
33
+ registerDelete
34
+ };
package/lib/lru-map.js ADDED
@@ -0,0 +1,36 @@
1
+ "use strict";
2
+
3
+ /**
4
+ * 带大小限制的 LRU Map,用于全局缓存防内存泄漏
5
+ */
6
+ class LRUMap extends Map {
7
+ constructor(maxSize, onEvict) {
8
+ super();
9
+ this.maxSize = maxSize;
10
+ this.onEvict = typeof onEvict === "function" ? onEvict : null;
11
+ }
12
+
13
+ set(key, value) {
14
+ if (this.has(key)) {
15
+ this.delete(key);
16
+ } else if (this.size >= this.maxSize) {
17
+ const firstKey = this.keys().next().value;
18
+ this.delete(firstKey);
19
+ if (this.onEvict) {
20
+ this.onEvict(firstKey);
21
+ }
22
+ }
23
+ return super.set(key, value);
24
+ }
25
+
26
+ get(key) {
27
+ const value = super.get(key);
28
+ if (value !== undefined) {
29
+ this.delete(key);
30
+ super.set(key, value);
31
+ }
32
+ return value;
33
+ }
34
+ }
35
+
36
+ module.exports = { LRUMap };
@@ -0,0 +1,88 @@
1
+ "use strict";
2
+
3
+ const { LRUMap } = require("./lru-map");
4
+
5
+ const GLOBAL_CACHE_CONFIG = {
6
+ maxSize: 10000,
7
+ cleanupInterval: 600000,
8
+ expireTime: 3600000,
9
+ meshLedTimerMaxSize: 5000
10
+ };
11
+
12
+ /**
13
+ * 从站开关节点全局缓存(LRU + 定期清理)
14
+ */
15
+ function createSlaveSwitchCaches(RED) {
16
+ const onEvict = (key) => {
17
+ if (RED && RED.log && RED.log.debug) {
18
+ RED.log.debug(`LRU缓存淘汰: 删除键 ${key}`);
19
+ }
20
+ };
21
+
22
+ const globalDebounceCache = new LRUMap(GLOBAL_CACHE_CONFIG.maxSize, onEvict);
23
+ const meshDeviceStates = new LRUMap(GLOBAL_CACHE_CONFIG.maxSize, onEvict);
24
+ const meshWirelessButtons = new LRUMap(GLOBAL_CACHE_CONFIG.maxSize, onEvict);
25
+ const globalButtonRelayStates = new LRUMap(GLOBAL_CACHE_CONFIG.maxSize, onEvict);
26
+ const meshLedDebounceTimers = new Map();
27
+
28
+ function getScopedKey(node, key) {
29
+ const configId = (node.serialPortConfig && node.serialPortConfig.id) || node.id || "global";
30
+ return `${configId}:${key}`;
31
+ }
32
+
33
+ let cleanupTimer = null;
34
+
35
+ function startGlobalDebounceCacheCleanup() {
36
+ if (cleanupTimer) {
37
+ return;
38
+ }
39
+ cleanupTimer = setInterval(() => {
40
+ const now = Date.now();
41
+ const expireThreshold = 60 * 1000;
42
+ let cleanedCount = 0;
43
+
44
+ for (const [key, timestamp] of globalDebounceCache.entries()) {
45
+ if (now - timestamp > expireThreshold) {
46
+ globalDebounceCache.delete(key);
47
+ cleanedCount++;
48
+ }
49
+ }
50
+
51
+ // Mesh LED 防抖定时器:清理已失效条目并限制最大数量
52
+ for (const [key, timerObj] of meshLedDebounceTimers.entries()) {
53
+ if (!timerObj || !timerObj.timer) {
54
+ meshLedDebounceTimers.delete(key);
55
+ cleanedCount++;
56
+ }
57
+ }
58
+ while (meshLedDebounceTimers.size > GLOBAL_CACHE_CONFIG.meshLedTimerMaxSize) {
59
+ const firstKey = meshLedDebounceTimers.keys().next().value;
60
+ const timerObj = meshLedDebounceTimers.get(firstKey);
61
+ if (timerObj && timerObj.timer) {
62
+ clearTimeout(timerObj.timer);
63
+ }
64
+ meshLedDebounceTimers.delete(firstKey);
65
+ cleanedCount++;
66
+ }
67
+
68
+ if (cleanedCount > 0 && RED && RED.log && RED.log.debug) {
69
+ RED.log.debug(
70
+ `全局缓存清理: 删除${cleanedCount}条记录,防抖${globalDebounceCache.size}条,LED定时器${meshLedDebounceTimers.size}条`
71
+ );
72
+ }
73
+ }, 10 * 60 * 1000);
74
+ }
75
+
76
+ return {
77
+ GLOBAL_CACHE_CONFIG,
78
+ globalDebounceCache,
79
+ meshDeviceStates,
80
+ meshWirelessButtons,
81
+ meshLedDebounceTimers,
82
+ globalButtonRelayStates,
83
+ getScopedKey,
84
+ startGlobalDebounceCacheCleanup
85
+ };
86
+ }
87
+
88
+ module.exports = { createSlaveSwitchCaches, GLOBAL_CACHE_CONFIG };
@@ -1,5 +1,6 @@
1
1
  module.exports = function(RED) {
2
2
  "use strict";
3
+ const httpAdmin = require("../lib/http-admin-utils");
3
4
 
4
5
  function CustomProtocolNode(config) {
5
6
  RED.nodes.createNode(this, config);
@@ -173,7 +174,7 @@ module.exports = function(RED) {
173
174
  RED.nodes.registerType("custom-protocol", CustomProtocolNode);
174
175
 
175
176
  // HTTP API:测试发送指令
176
- RED.httpAdmin.post("/custom-protocol/test", function(req, res) {
177
+ httpAdmin.registerPost(RED, "/custom-protocol/test", "custom-protocol.write", function(req, res) {
177
178
  var serialConfigId = req.body.serialConfig;
178
179
  var hexString = req.body.hexString;
179
180
  var cmdName = req.body.cmdName;
@@ -1,6 +1,7 @@
1
1
  module.exports = function(RED) {
2
2
  "use strict";
3
3
  const { ensureRedEventsMaxListeners } = require("../lib/red-events-utils");
4
+ const httpAdmin = require("../lib/http-admin-utils");
4
5
  ensureRedEventsMaxListeners(RED);
5
6
  const hap = require("hap-nodejs");
6
7
  const storage = require("node-persist");
@@ -335,7 +336,7 @@ module.exports = function(RED) {
335
336
  RED.nodes.registerType("homekit-bridge", HomekitBridgeNode);
336
337
 
337
338
  // HTTP API:获取主站节点配置
338
- RED.httpAdmin.get("/homekit-bridge/master-config/:id", function(req, res) {
339
+ httpAdmin.registerGet(RED, "/homekit-bridge/master-config/:id", "homekit-bridge.read", function(req, res) {
339
340
  var masterNodeId = req.params.id;
340
341
  var masterNode = RED.nodes.getNode(masterNodeId);
341
342
 
@@ -1,5 +1,6 @@
1
1
  module.exports = function(RED) {
2
2
  "use strict";
3
+ const httpAdmin = require("../lib/http-admin-utils");
3
4
 
4
5
  // 全局状态缓存(所有dashboard节点共享)
5
6
  var globalStateCache = {};
@@ -87,7 +88,7 @@ module.exports = function(RED) {
87
88
  RED.nodes.registerType("modbus-dashboard", ModbusDashboardNode);
88
89
 
89
90
  // HTTP API:获取主站节点配置
90
- RED.httpAdmin.get("/modbus-dashboard/master-config/:id", function(req, res) {
91
+ httpAdmin.registerGet(RED, "/modbus-dashboard/master-config/:id", "modbus-dashboard.read", function(req, res) {
91
92
  var masterNodeId = req.params.id;
92
93
  var masterNode = RED.nodes.getNode(masterNodeId);
93
94
 
@@ -105,7 +106,7 @@ module.exports = function(RED) {
105
106
  });
106
107
 
107
108
  // HTTP API:获取状态
108
- RED.httpAdmin.get("/modbus-dashboard/state/:masterNodeId", function(req, res) {
109
+ httpAdmin.registerGet(RED, "/modbus-dashboard/state/:masterNodeId", "modbus-dashboard.read", function(req, res) {
109
110
  var masterNodeId = req.params.masterNodeId;
110
111
  var states = {};
111
112
 
@@ -123,7 +124,7 @@ module.exports = function(RED) {
123
124
  });
124
125
 
125
126
  // HTTP API:刷新状态(从主站重新获取真实状态)
126
- RED.httpAdmin.post("/modbus-dashboard/refresh/:masterNodeId", function(req, res) {
127
+ httpAdmin.registerPost(RED, "/modbus-dashboard/refresh/:masterNodeId", "modbus-dashboard.write", function(req, res) {
127
128
  var masterNodeId = req.params.masterNodeId;
128
129
  var masterNode = RED.nodes.getNode(masterNodeId);
129
130
 
@@ -170,7 +171,7 @@ module.exports = function(RED) {
170
171
  });
171
172
 
172
173
  // HTTP API:发送控制命令
173
- RED.httpAdmin.post("/modbus-dashboard/control", function(req, res) {
174
+ httpAdmin.registerPost(RED, "/modbus-dashboard/control", "modbus-dashboard.write", function(req, res) {
174
175
  var slave = parseInt(req.body.slave);
175
176
  var coil = parseInt(req.body.coil);
176
177
  var value = Boolean(req.body.value);
@@ -4,85 +4,19 @@ module.exports = function(RED) {
4
4
  const mqtt = require("mqtt");
5
5
  const protocol = require("./lightweight-protocol");
6
6
  const mqttHelper = require("./mqtt-helper");
7
+ const serialUtils = require("../lib/serial-utils");
8
+ const httpAdmin = require("../lib/http-admin-utils");
7
9
 
8
10
  // 全局主站实例注册表(用于多主站独立运行,避免互相干扰)
9
11
  // 每个主站节点有独立的Modbus客户端实例,互不影响
10
12
  const masterInstances = new Map();
11
13
 
12
- // 串口列表API - 支持Windows、Linux、macOS所有串口设备
13
- RED.httpAdmin.get("/modbus-master/serialports", async function(req, res) {
14
+ // 串口列表API - 使用公共串口工具模块
15
+ httpAdmin.registerGet(RED, "/modbus-master/serialports", "modbus-master.read", async function(req, res) {
14
16
  try {
15
- // 尝试从多个可能的位置获取serialport模块
16
- let SerialPort;
17
- try {
18
- // 优先尝试使用node_modules中的serialport
19
- SerialPort = require("serialport");
20
- } catch (e) {
21
- try {
22
- // 如果失败,尝试从modbus-serial的依赖中获取
23
- const ModbusRTU = require("modbus-serial");
24
- SerialPort = ModbusRTU.SerialPort || require("serialport");
25
- } catch (e2) {
26
- // 两种方式都失败,返回特殊错误标记
27
- RED.log.warn("串口模块未找到,可能需要额外安装 serialport 模块");
28
- return res.json([{
29
- comName: "未检测到串口",
30
- manufacturer: "Docker环境需要映射设备:--device=/dev/ttyUSB0 或 --privileged",
31
- isError: true
32
- }]);
33
- }
34
- }
35
-
36
- // serialport v10+ (使用SerialPort.SerialPort.list)
37
- if (SerialPort && SerialPort.SerialPort && SerialPort.SerialPort.list) {
38
- const ports = await SerialPort.SerialPort.list();
39
- if (ports.length === 0) {
40
- // 没有发现串口,返回提示信息
41
- return res.json([{
42
- comName: "未检测到串口",
43
- manufacturer: "Docker环境需要映射设备:--device=/dev/ttyUSB0 或 --privileged",
44
- isError: true
45
- }]);
46
- }
47
- const portList = ports.map(port => ({
48
- comName: port.path || port.comName,
49
- manufacturer: port.manufacturer || "未知设备",
50
- vendorId: port.vendorId || "",
51
- productId: port.productId || "",
52
- isError: false
53
- }));
54
- return res.json(portList);
55
- }
56
-
57
- // serialport v9 (使用SerialPort.list)
58
- if (SerialPort && SerialPort.list) {
59
- const ports = await SerialPort.list();
60
- if (ports.length === 0) {
61
- // 没有发现串口,返回提示信息
62
- return res.json([{
63
- comName: "未检测到串口",
64
- manufacturer: "Docker环境需要映射设备:--device=/dev/ttyUSB0 或 --privileged",
65
- isError: true
66
- }]);
67
- }
68
- const portList = ports.map(port => ({
69
- comName: port.path || port.comName,
70
- manufacturer: port.manufacturer || "未知设备",
71
- vendorId: port.vendorId || "",
72
- productId: port.productId || "",
73
- isError: false
74
- }));
75
- return res.json(portList);
76
- }
77
-
78
- // 如果以上方法都不可用,返回提示信息
79
- res.json([{
80
- comName: "串口功能不可用",
81
- manufacturer: "请手动输入串口路径,如:/dev/ttyUSB0",
82
- isError: true
83
- }]);
17
+ const ports = await serialUtils.getSerialPortListWithTips(RED);
18
+ res.json(ports);
84
19
  } catch (err) {
85
- // 发生错误时记录日志并返回错误信息
86
20
  RED.log.warn(`串口列表获取失败: ${err.message}`);
87
21
  res.json([{
88
22
  comName: "串口搜索失败",
@@ -9,95 +9,20 @@ module.exports = function(RED) {
9
9
  const path = require("path");
10
10
  const os = require("os");
11
11
  const serialUtils = require("../lib/serial-utils");
12
+ const httpAdmin = require("../lib/http-admin-utils");
13
+ const { createSlaveSwitchCaches } = require("../lib/slave-switch-cache");
14
+
15
+ const {
16
+ globalDebounceCache,
17
+ meshDeviceStates,
18
+ meshWirelessButtons,
19
+ meshLedDebounceTimers,
20
+ globalButtonRelayStates,
21
+ getScopedKey,
22
+ startGlobalDebounceCacheCleanup
23
+ } = createSlaveSwitchCaches(RED);
12
24
 
13
- // 全局缓存配置
14
- const GLOBAL_CACHE_CONFIG = {
15
- maxSize: 10000, // 最大缓存条目数
16
- cleanupInterval: 600000, // 清理间隔:10分钟
17
- expireTime: 3600000 // 过期时间:1小时
18
- };
19
-
20
- // LRU Map 实现(带大小限制)
21
- class LRUMap extends Map {
22
- constructor(maxSize) {
23
- super();
24
- this.maxSize = maxSize;
25
- }
26
-
27
- set(key, value) {
28
- // 如果 key 已存在,先删除再添加(保持最新访问顺序)
29
- if (this.has(key)) {
30
- this.delete(key);
31
- }
32
- // 如果超过最大大小,删除最旧的条目
33
- else if (this.size >= this.maxSize) {
34
- const firstKey = this.keys().next().value;
35
- this.delete(firstKey);
36
- RED.log.debug(`LRU缓存淘汰: 删除键 ${firstKey}`);
37
- }
38
- return super.set(key, value);
39
- }
40
-
41
- get(key) {
42
- const value = super.get(key);
43
- if (value !== undefined) {
44
- // 移动到最新位置(LRU策略)
45
- this.delete(key);
46
- super.set(key, value);
47
- }
48
- return value;
49
- }
50
- }
51
-
52
- // 全局防抖缓存:防止多个节点重复处理同一个按键事件
53
- // key: "serialPortConfigId:switchId-buttonNumber", value: timestamp
54
- const globalDebounceCache = new LRUMap(GLOBAL_CACHE_CONFIG.maxSize);
55
-
56
- // 全局Mesh设备状态缓存(按短地址索引)
57
- // key: "serialPortConfigId:meshShortAddress", value: [state1, state2, ...]
58
- const meshDeviceStates = new LRUMap(GLOBAL_CACHE_CONFIG.maxSize);
59
-
60
- // 全局Mesh无线模式按键标记(按短地址索引)
61
- // key: "serialPortConfigId:meshShortAddress", value: Set([buttonNumber1, buttonNumber2, ...])
62
- const meshWirelessButtons = new LRUMap(GLOBAL_CACHE_CONFIG.maxSize);
63
-
64
- // 全局Mesh设备LED反馈防抖定时器(按短地址索引)
65
- // key: "serialPortConfigId:meshShortAddress", value: {timer, nodeId, serialPortConfig}
66
- const meshLedDebounceTimers = new Map(); // 定时器需要精确控制,不使用LRU
67
-
68
- // 全局按钮关联继电器状态表(用于多对一控制时的LED状态聚合)
69
- // key: "serialPortConfigId:switchId:buttonNumber", value: Map<nodeId, boolean>
70
- const globalButtonRelayStates = new LRUMap(GLOBAL_CACHE_CONFIG.maxSize);
71
-
72
- // 辅助函数:获取带作用域的键名
73
- // 确保configId始终有效,避免返回"default"前缀导致不同配置下的节点共用防抖键
74
- function getScopedKey(node, key) {
75
- // 优先使用serialPortConfig.id,如果不存在则使用node.id作为fallback,最后使用 "global"
76
- const configId = (node.serialPortConfig && node.serialPortConfig.id) || node.id || "global";
77
- return `${configId}:${key}`;
78
- }
79
-
80
- // 全局防抖缓存定期清理(每10分钟清理超过1分钟未使用的条目,防止内存泄漏)
81
- let globalDebounceCacheCleanupTimer = null;
82
- function startGlobalDebounceCacheCleanup() {
83
- if (globalDebounceCacheCleanupTimer) {return;}
84
- globalDebounceCacheCleanupTimer = setInterval(() => {
85
- const now = Date.now();
86
- const expireThreshold = 60 * 1000; // 1分钟未使用的条目将被清理
87
- let cleanedCount = 0;
88
- for (const [key, timestamp] of globalDebounceCache.entries()) {
89
- if (now - timestamp > expireThreshold) {
90
- globalDebounceCache.delete(key);
91
- cleanedCount++;
92
- }
93
- }
94
- if (cleanedCount > 0) {
95
- RED.log.debug(`全局防抖缓存清理: 删除${cleanedCount}条过期记录,剩余${globalDebounceCache.size}条`);
96
- }
97
- }, 10 * 60 * 1000); // 每10分钟执行一次
98
- }
99
25
  startGlobalDebounceCacheCleanup();
100
-
101
26
  // 初始化Mesh设备持久化存储
102
27
  const meshPersistDir = path.join(RED.settings.userDir || os.homedir() + "/.node-red", "mesh-devices-persist");
103
28
 
@@ -127,7 +52,7 @@ module.exports = function(RED) {
127
52
  }
128
53
 
129
54
  // Mesh设备发现API(使用共享连接)
130
- RED.httpAdmin.post("/symi-mesh/discover", async function(req, res) {
55
+ httpAdmin.registerPost(RED, "/symi-mesh/discover", "modbus-slave-switch.write", async function(req, res) {
131
56
  try {
132
57
  const serialPortConfigId = req.body.serialPortConfig;
133
58
  RED.log.info(`[Mesh设备发现] 收到请求,连接配置ID: ${serialPortConfigId}`);
@@ -320,7 +245,7 @@ module.exports = function(RED) {
320
245
  });
321
246
 
322
247
  // 获取已保存的Mesh设备列表API
323
- RED.httpAdmin.get("/symi-mesh/devices", async function(req, res) {
248
+ httpAdmin.registerGet(RED, "/symi-mesh/devices", "modbus-slave-switch.read", async function(req, res) {
324
249
  try {
325
250
  await initMeshStorage();
326
251
  const deviceMap = await storage.getItem("meshDeviceMap") || {};
@@ -343,7 +268,7 @@ module.exports = function(RED) {
343
268
  });
344
269
 
345
270
  // 串口列表API - 使用公共串口工具模块
346
- RED.httpAdmin.get("/modbus-slave-switch/serialports", async function(req, res) {
271
+ httpAdmin.registerGet(RED, "/modbus-slave-switch/serialports", "modbus-slave-switch.read", async function(req, res) {
347
272
  try {
348
273
  const ports = await serialUtils.getSerialPortList(RED);
349
274
  // 转换为旧版格式以保持兼容性
@@ -10,6 +10,7 @@ module.exports = function(RED) {
10
10
  const storage = require("node-persist");
11
11
  const meshProtocol = require("./mesh-protocol")(RED);
12
12
  const serialUtils = require("../lib/serial-utils");
13
+ const httpAdmin = require("../lib/http-admin-utils");
13
14
 
14
15
  const { SerialPort } = require("serialport");
15
16
  const net = require("net");
@@ -1134,7 +1135,7 @@ module.exports = function(RED) {
1134
1135
  RED.nodes.registerType("serial-port-config", SerialPortConfigNode);
1135
1136
 
1136
1137
  // 提供串口搜索API - 使用公共串口工具模块
1137
- RED.httpAdmin.get("/serial-ports", async (_req, res) => {
1138
+ httpAdmin.registerGet(RED, "/serial-ports", "serial-port-config.read", async (_req, res) => {
1138
1139
  try {
1139
1140
  const ports = await serialUtils.getSerialPortList(RED);
1140
1141
  res.json(ports);
@@ -1144,7 +1145,7 @@ module.exports = function(RED) {
1144
1145
  });
1145
1146
 
1146
1147
  // 提供Mesh实体列表API (用于透明节点选择)
1147
- RED.httpAdmin.get("/symi-mesh/entities", (req, res) => {
1148
+ httpAdmin.registerGet(RED, "/symi-mesh/entities", "serial-port-config.read", (req, res) => {
1148
1149
  try {
1149
1150
  const configId = req.query.configId;
1150
1151
  if (!configId) {
@@ -1199,7 +1200,7 @@ module.exports = function(RED) {
1199
1200
  });
1200
1201
 
1201
1202
  // 删除Mesh实体API
1202
- RED.httpAdmin.delete("/symi-mesh/entity/:configId/:shortAddr", RED.auth.needsPermission("serial-port-config.write"), async (req, res) => {
1203
+ httpAdmin.registerDelete(RED, "/symi-mesh/entity/:configId/:shortAddr", "serial-port-config.write", async (req, res) => {
1203
1204
  try {
1204
1205
  const configId = req.params.configId;
1205
1206
  const shortAddr = req.params.shortAddr;
package/package.json CHANGED
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "node-red-contrib-symi-modbus",
3
- "version": "2.10.17",
3
+ "version": "2.10.19",
4
4
  "description": "Node-RED Modbus节点,支持TCP/串口通信、多主站独立运行、串口自动搜索、多设备轮询、可选MQTT集成(支持纯本地模式和MQTT模式)、Home Assistant自动发现、HomeKit网桥、可视化控制看板、自定义协议转换和物理开关面板双向同步(支持Symi/Clowire品牌),工控机长期稳定运行",
5
5
  "main": "nodes/modbus-master.js",
6
6
  "scripts": {
7
7
  "lint": "eslint nodes/*.js lib/*.js --ext .js",
8
8
  "lint:fix": "eslint nodes/*.js lib/*.js --ext .js --fix",
9
- "test": "echo \"Error: no test specified\" && exit 0"
9
+ "test": "node --test test/*.test.js"
10
10
  },
11
11
  "keywords": [
12
12
  "node-red",