node-red-contrib-symi-mesh 1.3.1 → 1.6.1

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.
@@ -0,0 +1,125 @@
1
+ <script type="text/javascript">
2
+ RED.nodes.registerType('symi-485-config', {
3
+ category: 'config',
4
+ defaults: {
5
+ name: { value: '' },
6
+ connectionType: { value: 'serial' },
7
+ host: { value: '' },
8
+ port: { value: 502 },
9
+ serialPort: { value: '' },
10
+ baudRate: { value: 9600 },
11
+ parity: { value: 'none' }
12
+ },
13
+ label: function() {
14
+ return this.name || (this.connectionType === 'tcp'
15
+ ? 'RS485 (' + this.host + ':' + this.port + ')'
16
+ : 'RS485 (' + this.serialPort + ')');
17
+ },
18
+ oneditprepare: function() {
19
+ var node = this;
20
+
21
+ // 连接类型切换
22
+ $('#node-config-input-connectionType').on('change', function() {
23
+ if ($(this).val() === 'tcp') {
24
+ $('.tcp-config').show();
25
+ $('.serial-config').hide();
26
+ } else {
27
+ $('.tcp-config').hide();
28
+ $('.serial-config').show();
29
+ }
30
+ }).trigger('change');
31
+
32
+ // 加载串口列表
33
+ function loadSerialPorts() {
34
+ $.getJSON('/symi-rs485-bridge/serial-ports', function(ports) {
35
+ var select = $('#node-config-input-serialPort');
36
+ var currentVal = select.val() || node.serialPort;
37
+ select.empty();
38
+ select.append('<option value="">-- 选择串口 --</option>');
39
+ ports.forEach(function(p) {
40
+ var label = p.path;
41
+ if (p.manufacturer) label += ' (' + p.manufacturer + ')';
42
+ var sel = (p.path === currentVal) ? ' selected' : '';
43
+ select.append('<option value="' + p.path + '"' + sel + '>' + label + '</option>');
44
+ });
45
+ });
46
+ }
47
+
48
+ $('#btn-refresh-ports').on('click', loadSerialPorts);
49
+ loadSerialPorts();
50
+ }
51
+ });
52
+ </script>
53
+
54
+ <script type="text/html" data-template-name="symi-485-config">
55
+ <div class="form-row">
56
+ <label for="node-config-input-name"><i class="fa fa-tag"></i> 名称</label>
57
+ <input type="text" id="node-config-input-name" placeholder="如:客厅RS485">
58
+ </div>
59
+
60
+ <div class="form-row">
61
+ <label for="node-config-input-connectionType"><i class="fa fa-plug"></i> 连接方式</label>
62
+ <select id="node-config-input-connectionType">
63
+ <option value="serial">串口 (RS485)</option>
64
+ <option value="tcp">TCP/IP (Modbus TCP)</option>
65
+ </select>
66
+ </div>
67
+
68
+ <div class="serial-config">
69
+ <div class="form-row">
70
+ <label for="node-config-input-serialPort"><i class="fa fa-usb"></i> 串口</label>
71
+ <select id="node-config-input-serialPort" style="width:60%"></select>
72
+ <button type="button" id="btn-refresh-ports" class="red-ui-button" style="margin-left:5px">
73
+ <i class="fa fa-refresh"></i>
74
+ </button>
75
+ </div>
76
+
77
+ <div class="form-row">
78
+ <label for="node-config-input-baudRate"><i class="fa fa-tachometer"></i> 波特率</label>
79
+ <select id="node-config-input-baudRate">
80
+ <option value="9600">9600</option>
81
+ <option value="19200">19200</option>
82
+ <option value="38400">38400</option>
83
+ <option value="57600">57600</option>
84
+ <option value="115200">115200</option>
85
+ </select>
86
+ </div>
87
+
88
+ <div class="form-row">
89
+ <label for="node-config-input-parity"><i class="fa fa-check-square"></i> 校验位</label>
90
+ <select id="node-config-input-parity">
91
+ <option value="none">无 (None)</option>
92
+ <option value="even">偶校验 (Even)</option>
93
+ <option value="odd">奇校验 (Odd)</option>
94
+ </select>
95
+ </div>
96
+ </div>
97
+
98
+ <div class="tcp-config" style="display:none">
99
+ <div class="form-row">
100
+ <label for="node-config-input-host"><i class="fa fa-server"></i> 主机地址</label>
101
+ <input type="text" id="node-config-input-host" placeholder="192.168.1.100">
102
+ </div>
103
+
104
+ <div class="form-row">
105
+ <label for="node-config-input-port"><i class="fa fa-random"></i> 端口</label>
106
+ <input type="number" id="node-config-input-port" placeholder="502">
107
+ </div>
108
+ </div>
109
+ </script>
110
+
111
+ <script type="text/html" data-help-name="symi-485-config">
112
+ <p>RS485/Modbus连接配置节点</p>
113
+ <h3>连接方式</h3>
114
+ <dl>
115
+ <dt>串口 (RS485)</dt>
116
+ <dd>通过USB转RS485模块连接,选择对应的串口</dd>
117
+ <dt>TCP/IP (Modbus TCP)</dt>
118
+ <dd>通过RS485转TCP网关连接,输入IP和端口</dd>
119
+ </dl>
120
+ <h3>串口参数</h3>
121
+ <ul>
122
+ <li>波特率:常用9600、19200、115200</li>
123
+ <li>校验位:根据设备要求选择</li>
124
+ </ul>
125
+ </script>
@@ -0,0 +1,275 @@
1
+ /**
2
+ * Symi RS485 Config Node - RS485/Modbus连接配置节点
3
+ * 使用与Mesh网关相同的技术栈(SerialPort/net)
4
+ */
5
+
6
+ const { SerialPort } = require('serialport');
7
+ const net = require('net');
8
+
9
+ module.exports = function(RED) {
10
+
11
+ function SymiRS485ConfigNode(config) {
12
+ RED.nodes.createNode(this, config);
13
+ const node = this;
14
+
15
+ // 配置
16
+ node.name = config.name || '';
17
+ node.connectionType = config.connectionType || 'serial';
18
+ node.host = config.host || '';
19
+ node.port = parseInt(config.port) || 502;
20
+ node.serialPort = config.serialPort || '';
21
+ node.baudRate = parseInt(config.baudRate) || 9600;
22
+ node.parity = config.parity || 'none';
23
+
24
+ // 连接状态
25
+ node.client = null;
26
+ node.connected = false;
27
+ node.receiveBuffer = Buffer.alloc(0);
28
+ node.users = [];
29
+
30
+ // 注册使用者
31
+ node.register = function(userNode) {
32
+ if (!node.users.includes(userNode)) {
33
+ node.users.push(userNode);
34
+ }
35
+ if (node.users.length === 1) {
36
+ node.connect();
37
+ }
38
+ };
39
+
40
+ // 注销使用者
41
+ node.deregister = function(userNode) {
42
+ const idx = node.users.indexOf(userNode);
43
+ if (idx >= 0) {
44
+ node.users.splice(idx, 1);
45
+ }
46
+ if (node.users.length === 0) {
47
+ node.disconnect();
48
+ }
49
+ };
50
+
51
+ // 连接
52
+ node.connect = function() {
53
+ if (node.connected) return;
54
+
55
+ try {
56
+ if (node.connectionType === 'serial') {
57
+ if (!node.serialPort) {
58
+ node.error('未配置串口');
59
+ return;
60
+ }
61
+
62
+ node.client = new SerialPort({
63
+ path: node.serialPort,
64
+ baudRate: node.baudRate,
65
+ dataBits: 8,
66
+ stopBits: 1,
67
+ parity: node.parity,
68
+ autoOpen: false
69
+ });
70
+
71
+ node.client.on('open', () => {
72
+ node.connected = true;
73
+ node.log(`RS485串口已连接: ${node.serialPort}`);
74
+ node.emit('connected');
75
+ });
76
+
77
+ node.client.on('data', (data) => {
78
+ // 触发原始数据事件(用于调试节点)
79
+ node.emit('data', data);
80
+ // 帧解析
81
+ node.handleData(data);
82
+ });
83
+
84
+ node.client.on('error', (err) => {
85
+ node.error(`RS485串口错误: ${err.message}`);
86
+ node.emit('error', err);
87
+ });
88
+
89
+ node.client.on('close', () => {
90
+ if (node.connected) {
91
+ node.connected = false;
92
+ node.log('RS485串口已断开');
93
+ node.emit('disconnected');
94
+ }
95
+ // 自动重连(静默)
96
+ if (node.users.length > 0 && !node._reconnecting) {
97
+ node._reconnecting = true;
98
+ setTimeout(() => {
99
+ node._reconnecting = false;
100
+ node.connect();
101
+ }, 5000);
102
+ }
103
+ });
104
+
105
+ node.client.open();
106
+
107
+ } else if (node.connectionType === 'tcp') {
108
+ if (!node.host) {
109
+ node.error('未配置TCP主机');
110
+ return;
111
+ }
112
+
113
+ node.client = new net.Socket();
114
+
115
+ node.client.on('connect', () => {
116
+ node.connected = true;
117
+ node.warn(`[RS485] TCP已连接: ${node.host}:${node.port}`);
118
+ node.emit('connected');
119
+ });
120
+
121
+ node.client.on('data', (data) => {
122
+ // 触发原始数据事件(用于调试节点)
123
+ node.emit('data', data);
124
+ // 帧解析
125
+ node.handleData(data);
126
+ });
127
+
128
+ node.client.on('error', (err) => {
129
+ node.error(`RS485 TCP错误: ${err.message}`);
130
+ node.emit('error', err);
131
+ });
132
+
133
+ node.client.on('close', () => {
134
+ if (node.connected) {
135
+ node.connected = false;
136
+ node.log('RS485 TCP已断开');
137
+ node.emit('disconnected');
138
+ }
139
+ // 自动重连(静默)
140
+ if (node.users.length > 0 && !node._reconnecting) {
141
+ node._reconnecting = true;
142
+ setTimeout(() => {
143
+ node._reconnecting = false;
144
+ node.connect();
145
+ }, 5000);
146
+ }
147
+ });
148
+
149
+ node.client.connect(node.port, node.host);
150
+ }
151
+ } catch (err) {
152
+ node.error(`RS485连接失败: ${err.message}`);
153
+ }
154
+ };
155
+
156
+ // 断开连接
157
+ node.disconnect = function() {
158
+ if (node.client) {
159
+ try {
160
+ if (node.connectionType === 'serial') {
161
+ node.client.close();
162
+ } else {
163
+ node.client.destroy();
164
+ }
165
+ } catch (e) {}
166
+ node.client = null;
167
+ }
168
+ node.connected = false;
169
+ };
170
+
171
+ // 处理接收数据(仅做帧解析,原始数据已在上层emit)
172
+ node.handleData = function(data) {
173
+ node.receiveBuffer = Buffer.concat([node.receiveBuffer, data]);
174
+
175
+ // 防止缓冲区过大(最大8KB)
176
+ if (node.receiveBuffer.length > 8192) {
177
+ node.receiveBuffer = node.receiveBuffer.subarray(-2048);
178
+ node.warn('接收缓冲区溢出,已截断');
179
+ }
180
+
181
+ // Modbus RTU最小帧长度为4字节
182
+ while (node.receiveBuffer.length >= 4) {
183
+ const frameLen = node.getFrameLength(node.receiveBuffer);
184
+ if (frameLen === 0 || node.receiveBuffer.length < frameLen) break;
185
+
186
+ const frame = node.receiveBuffer.subarray(0, frameLen);
187
+ node.receiveBuffer = node.receiveBuffer.subarray(frameLen);
188
+
189
+ if (node.validateCRC(frame)) {
190
+ node.emit('frame', frame);
191
+ }
192
+ }
193
+ };
194
+
195
+ // 获取帧长度
196
+ node.getFrameLength = function(buffer) {
197
+ if (buffer.length < 2) return 0;
198
+ const fc = buffer[1];
199
+ if (fc === 0x03 || fc === 0x04) {
200
+ if (buffer.length < 3) return 0;
201
+ return 3 + buffer[2] + 2;
202
+ } else if (fc === 0x05 || fc === 0x06) {
203
+ return 8;
204
+ } else if (fc >= 0x80) {
205
+ return 5;
206
+ }
207
+ return 8;
208
+ };
209
+
210
+ // 验证CRC
211
+ node.validateCRC = function(frame) {
212
+ if (frame.length < 4) return false;
213
+ const data = frame.subarray(0, frame.length - 2);
214
+ const receivedCRC = frame.readUInt16LE(frame.length - 2);
215
+ const calculatedCRC = node.calculateCRC16(data);
216
+ return receivedCRC === calculatedCRC;
217
+ };
218
+
219
+ // CRC16计算
220
+ node.calculateCRC16 = function(buffer) {
221
+ let crc = 0xFFFF;
222
+ for (let i = 0; i < buffer.length; i++) {
223
+ crc ^= buffer[i];
224
+ for (let j = 0; j < 8; j++) {
225
+ if (crc & 0x0001) {
226
+ crc = (crc >> 1) ^ 0xA001;
227
+ } else {
228
+ crc >>= 1;
229
+ }
230
+ }
231
+ }
232
+ return crc;
233
+ };
234
+
235
+ // 发送数据
236
+ node.send = function(data) {
237
+ return new Promise((resolve, reject) => {
238
+ if (!node.connected || !node.client) {
239
+ reject(new Error('RS485未连接'));
240
+ return;
241
+ }
242
+ const buffer = Buffer.isBuffer(data) ? data : Buffer.from(data);
243
+ node.client.write(buffer, (err) => {
244
+ if (err) {
245
+ reject(err);
246
+ } else {
247
+ // 发送tx事件用于调试
248
+ node.emit('tx', buffer);
249
+ resolve();
250
+ }
251
+ });
252
+ });
253
+ };
254
+
255
+ // 构建并发送Modbus帧
256
+ node.writeRegister = function(slaveAddr, registerAddr, value) {
257
+ const buffer = Buffer.alloc(8);
258
+ buffer.writeUInt8(slaveAddr, 0);
259
+ buffer.writeUInt8(0x06, 1); // 写单个寄存器
260
+ buffer.writeUInt16BE(registerAddr, 2);
261
+ buffer.writeUInt16BE(value, 4);
262
+ const crc = node.calculateCRC16(buffer.subarray(0, 6));
263
+ buffer.writeUInt16LE(crc, 6);
264
+ return node.send(buffer);
265
+ };
266
+
267
+ // 清理
268
+ node.on('close', (done) => {
269
+ node.disconnect();
270
+ done();
271
+ });
272
+ }
273
+
274
+ RED.nodes.registerType('symi-485-config', SymiRS485ConfigNode);
275
+ };
@@ -6,8 +6,8 @@
6
6
  name: { value: '' },
7
7
  gateway: { value: '', type: 'symi-gateway', required: true },
8
8
  mqttConfig: { value: '', type: 'symi-mqtt' },
9
- appId: { value: 'cee70459', required: true },
10
- appSecret: { value: 'a719a4b58e2e57e50d758ee3762507e7', required: true },
9
+ appId: { value: '', required: false },
10
+ appSecret: { value: '', required: false },
11
11
  hotelId: { value: '' },
12
12
  roomNo: { value: '' },
13
13
  roomUuid: { value: '' },
@@ -314,12 +314,12 @@
314
314
 
315
315
  <div class="form-row">
316
316
  <label for="node-input-appId"><i class="fa fa-key"></i> App ID</label>
317
- <input type="text" id="node-input-appId" placeholder="cee70459">
317
+ <input type="text" id="node-input-appId" placeholder="请输入App ID">
318
318
  </div>
319
319
 
320
320
  <div class="form-row">
321
321
  <label for="node-input-appSecret"><i class="fa fa-lock"></i> App Secret</label>
322
- <input type="password" id="node-input-appSecret" placeholder="a719a4b58e2e57e50d758ee3762507e7">
322
+ <input type="password" id="node-input-appSecret" placeholder="请输入App Secret">
323
323
  </div>
324
324
 
325
325
  <div class="form-row">
@@ -154,12 +154,12 @@ module.exports = function(RED) {
154
154
  node.log(`设备名称已更新: ${oldName} -> ${newName} (MAC: ${cloudDevice.mac})`);
155
155
  }
156
156
 
157
- // 同步按键名称
157
+ // 同步按键完整配置(名称、类型、场景ID等)
158
158
  if (cloudDevice.sub_device && Array.isArray(cloudDevice.sub_device) && cloudDevice.sub_device.length > 0) {
159
159
  const newSubNames = cloudDevice.sub_device.map(sub => sub.sub_name);
160
160
  const oldSubNames = localDevice.subDeviceNames || [];
161
161
 
162
- node.log(`[按键名称] 设备: ${localDevice.name}, 云端按键数: ${cloudDevice.sub_device.length}, 按键名称: [${newSubNames.join(', ')}]`);
162
+ node.log(`[按键配置] 设备: ${localDevice.name}, 云端按键数: ${cloudDevice.sub_device.length}, 按键名称: [${newSubNames.join(', ')}]`);
163
163
 
164
164
  // 检查是否有变化
165
165
  const hasChanged = newSubNames.length !== oldSubNames.length ||
@@ -168,12 +168,40 @@ module.exports = function(RED) {
168
168
  if (hasChanged) {
169
169
  localDevice.subDeviceNames = newSubNames;
170
170
  deviceUpdated = true;
171
- node.log(`[按键名称] 设备按键名称已更新: ${localDevice.name} -> [${newSubNames.join(', ')}]`);
171
+ node.log(`[按键配置] 设备按键名称已更新: ${localDevice.name} -> [${newSubNames.join(', ')}]`);
172
172
  } else {
173
- node.log(`[按键名称] 设备按键名称无变化: ${localDevice.name}`);
173
+ node.log(`[按键配置] 设备按键名称无变化: ${localDevice.name}`);
174
174
  }
175
+
176
+ // 存储完整的按键配置(包含场景ID等信息)
177
+ localDevice.subDeviceConfigs = cloudDevice.sub_device.map(sub => ({
178
+ sub_name: sub.sub_name,
179
+ sub_type: sub.sub_type, // "普通", "场景", "双控", "总控"
180
+ switch_type: sub.switch_type, // "normal", "light"
181
+ scene_id: sub.scene_id, // 场景按键的场景ID
182
+ on_scene_id: sub.on_scene_id, // 双控/总控的开场景ID
183
+ off_scene_id: sub.off_scene_id // 双控/总控的关场景ID
184
+ }));
185
+
186
+ // 记录场景绑定信息
187
+ const sceneBindings = cloudDevice.sub_device
188
+ .map((sub, idx) => {
189
+ if (sub.sub_type === '场景' && sub.scene_id) {
190
+ return `按键${idx + 1}(${sub.sub_name})→场景${sub.scene_id}`;
191
+ } else if ((sub.sub_type === '双控' || sub.sub_type === '总控') && (sub.on_scene_id || sub.off_scene_id)) {
192
+ return `按键${idx + 1}(${sub.sub_name})→开:场景${sub.on_scene_id || '无'}/关:场景${sub.off_scene_id || '无'}`;
193
+ }
194
+ return null;
195
+ })
196
+ .filter(Boolean);
197
+
198
+ if (sceneBindings.length > 0) {
199
+ node.log(`[场景绑定] ${localDevice.name}: ${sceneBindings.join(', ')}`);
200
+ }
201
+
202
+ deviceUpdated = true;
175
203
  } else {
176
- node.log(`[按键名称] 设备无sub_device数据: ${localDevice.name}`);
204
+ node.log(`[按键配置] 设备无sub_device数据: ${localDevice.name}`);
177
205
  }
178
206
 
179
207
  if (deviceUpdated) {
@@ -201,8 +229,8 @@ module.exports = function(RED) {
201
229
 
202
230
  if (updatedCount > 0 && node.mqttConfig) {
203
231
  setTimeout(() => {
204
- node.log('重新发布MQTT Discovery配置(名称已更新)');
205
- node.mqttConfig.publishAllDiscovery(devices);
232
+ node.log('重新发布MQTT Discovery配置(名称已更新,强制更新)');
233
+ node.mqttConfig.publishAllDiscovery(devices, true); // forceUpdate=true
206
234
  }, 1000);
207
235
  }
208
236
  };
@@ -223,11 +251,14 @@ module.exports = function(RED) {
223
251
  const config = generateSceneButtonConfig(scene, roomNo, mqttPrefix);
224
252
 
225
253
  setTimeout(() => {
254
+ node.log(`[场景按钮] 发布: ${scene.scene_name}, topic=${config.topic}`);
255
+ node.debug(`[场景按钮] payload=${config.payload}`);
256
+
226
257
  mqttClient.publish(config.topic, config.payload, { retain: true }, (err) => {
227
258
  if (err) {
228
259
  node.error(`发布场景按钮失败: ${scene.scene_name}, ${err.message}`);
229
260
  } else {
230
- node.debug(`场景按钮已发布: ${scene.scene_name}`);
261
+ node.log(`场景按钮已发布: ${scene.scene_name}`);
231
262
  }
232
263
  });
233
264
  }, index * 100);
@@ -256,6 +287,8 @@ module.exports = function(RED) {
256
287
  if (node.mqttConfig) {
257
288
  node.mqttConfig.on('scene-trigger', (topic, message) => {
258
289
  const roomNo = node.roomNo || 'unknown';
290
+
291
+ // 直接使用原始roomNo匹配topic
259
292
  if (topic.startsWith(`symi_mesh/room_${roomNo}/scene/`) && topic.endsWith('/trigger')) {
260
293
  const sceneIdMatch = topic.match(/scene\/(\d+)\/trigger/);
261
294
  if (sceneIdMatch) {
@@ -272,9 +305,9 @@ module.exports = function(RED) {
272
305
  node.log(`[场景控制] 场景控制帧: ${frame.toString('hex').toUpperCase()}`);
273
306
 
274
307
  node.gateway.sendScene(sceneId).then(() => {
275
- node.log(`[场景控制] 场景控制命令已发送: ${scene.scene_name} (ID: ${sceneId})`);
308
+ node.log(`[场景控制] 场景控制命令已发送: ${scene.scene_name} (ID: ${sceneId})`);
276
309
  }).catch(err => {
277
- node.error(`[场景控制] 场景控制命令发送失败: ${err.message}`);
310
+ node.error(`[场景控制] 场景控制命令发送失败: ${err.message}`);
278
311
  });
279
312
  } else {
280
313
  node.error('[场景控制] 网关未连接,无法执行场景');
@@ -212,6 +212,7 @@
212
212
  <p><b>输入:</b>接收控制命令,支持多种格式</p>
213
213
  <p><b>输出:</b>设备状态变化时自动输出</p>
214
214
  <p><b>KNX同步:</b>直接与KNX节点连线即可实现双向同步</p>
215
+ <p><b>485同步:</b>使用"485桥接"节点配置双向同步</p>
215
216
  </div>
216
217
  </script>
217
218
 
@@ -37,21 +37,30 @@ module.exports = function(RED) {
37
37
  }
38
38
 
39
39
  node.device = null;
40
- node.status({ fill: 'yellow', shape: 'ring', text: '等待连接' });
40
+
41
+ // 检查设备是否已配置
42
+ if (!node.deviceMac) {
43
+ node.status({ fill: 'grey', shape: 'ring', text: '请选择设备' });
44
+ } else {
45
+ node.status({ fill: 'yellow', shape: 'ring', text: '等待连接' });
46
+ }
41
47
 
42
48
  const updateDevice = () => {
43
- if (node.deviceMac) {
44
- node.device = node.gateway.getDevice(node.deviceMac);
45
- if (node.device) {
46
- const channelInfo = node.device.channels > 1 ? ` 第${node.channel}路` : '';
47
- node.status({
48
- fill: 'green',
49
- shape: 'dot',
50
- text: `已连接${channelInfo}`
51
- });
52
- } else {
53
- node.status({ fill: 'yellow', shape: 'ring', text: '等待设备' });
54
- }
49
+ if (!node.deviceMac) {
50
+ node.status({ fill: 'grey', shape: 'ring', text: '请选择设备' });
51
+ return;
52
+ }
53
+
54
+ node.device = node.gateway.getDevice(node.deviceMac);
55
+ if (node.device) {
56
+ const channelInfo = node.device.channels > 1 ? ` 第${node.channel}路` : '';
57
+ node.status({
58
+ fill: 'green',
59
+ shape: 'dot',
60
+ text: `已连接${channelInfo}`
61
+ });
62
+ } else {
63
+ node.status({ fill: 'yellow', shape: 'ring', text: '等待设备' });
55
64
  }
56
65
  };
57
66
 
@@ -134,7 +143,7 @@ module.exports = function(RED) {
134
143
  let command = null;
135
144
 
136
145
  if (entityType === 'switch') {
137
- const channel = parseInt(msg.channel) || node.channel || 1;
146
+ const channel = parseInt(msg.channel) || this.channel || 1;
138
147
  let value;
139
148
 
140
149
  if (typeof payload === 'boolean') {