node-red-contrib-symi-mesh 1.7.1 → 1.7.3
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 +274 -20
- package/examples/knx-sync-example.json +122 -58
- package/examples/rs485-sync-example.json +76 -0
- package/lib/device-manager.js +96 -51
- package/lib/serial-client.js +23 -4
- package/nodes/rs485-debug.html +2 -1
- package/nodes/symi-485-bridge.html +233 -32
- package/nodes/symi-485-bridge.js +874 -98
- package/nodes/symi-485-config.html +44 -21
- package/nodes/symi-485-config.js +49 -11
- package/nodes/symi-cloud-sync.html +2 -0
- package/nodes/symi-device.html +5 -3
- package/nodes/symi-gateway.html +49 -1
- package/nodes/symi-gateway.js +43 -3
- package/nodes/symi-knx-bridge.html +3 -2
- package/nodes/symi-knx-bridge.js +3 -3
- package/nodes/symi-knx-ha-bridge.html +4 -3
- package/nodes/symi-knx-ha-bridge.js +2 -2
- package/nodes/symi-mqtt-brand.html +75 -0
- package/nodes/symi-mqtt-brand.js +238 -0
- package/nodes/symi-mqtt-sync.html +381 -0
- package/nodes/symi-mqtt-sync.js +473 -0
- package/nodes/symi-rs485-sync.html +361 -0
- package/nodes/symi-rs485-sync.js +765 -0
- package/package.json +5 -2
|
@@ -10,13 +10,14 @@
|
|
|
10
10
|
},
|
|
11
11
|
inputs: 1,
|
|
12
12
|
outputs: 1,
|
|
13
|
-
icon: '
|
|
13
|
+
icon: 'font-awesome/fa-exchange',
|
|
14
|
+
align: 'left',
|
|
14
15
|
paletteLabel: 'RS485桥接',
|
|
15
16
|
label: function() {
|
|
16
17
|
if (this.name) return this.name;
|
|
17
18
|
try {
|
|
18
19
|
var m = JSON.parse(this.mappings || '[]');
|
|
19
|
-
if (m.length > 0) return 'RS485桥接
|
|
20
|
+
if (m.length > 0) return 'RS485桥接(' + m.length + '组)';
|
|
20
21
|
} catch(e) {}
|
|
21
22
|
return 'RS485桥接';
|
|
22
23
|
},
|
|
@@ -83,8 +84,10 @@
|
|
|
83
84
|
// 构建Mesh设备选项(不包含按键,按键单独选择)
|
|
84
85
|
function getMeshOptions(selectedMac) {
|
|
85
86
|
var html = '<option value="">-- 选择 --</option>';
|
|
87
|
+
var selMacNorm = (selectedMac || '').toLowerCase().replace(/:/g, '');
|
|
86
88
|
meshDevices.forEach(function(d) {
|
|
87
|
-
var
|
|
89
|
+
var devMacNorm = (d.mac || '').toLowerCase().replace(/:/g, '');
|
|
90
|
+
var selected = (devMacNorm === selMacNorm && selMacNorm !== '') ? ' selected' : '';
|
|
88
91
|
html += '<option value="' + d.mac + '" data-channels="' + (d.channels || 1) + '"' + selected + '>' + d.name + '</option>';
|
|
89
92
|
});
|
|
90
93
|
return html;
|
|
@@ -92,7 +95,10 @@
|
|
|
92
95
|
|
|
93
96
|
// 构建Mesh按键选项(仅当开关设备时显示)
|
|
94
97
|
function getMeshChannelOptions(mac, selectedChannel) {
|
|
95
|
-
var
|
|
98
|
+
var macNorm = (mac || '').toLowerCase().replace(/:/g, '');
|
|
99
|
+
var device = meshDevices.find(function(d) {
|
|
100
|
+
return (d.mac || '').toLowerCase().replace(/:/g, '') === macNorm;
|
|
101
|
+
});
|
|
96
102
|
var channels = device ? (device.channels || 1) : 0;
|
|
97
103
|
if (channels <= 1) return '';
|
|
98
104
|
var html = '<select class="mesh-channel">';
|
|
@@ -106,7 +112,10 @@
|
|
|
106
112
|
|
|
107
113
|
// 获取Mesh设备的按键数
|
|
108
114
|
function getMeshDeviceChannels(mac) {
|
|
109
|
-
var
|
|
115
|
+
var macNorm = (mac || '').toLowerCase().replace(/:/g, '');
|
|
116
|
+
var device = meshDevices.find(function(d) {
|
|
117
|
+
return (d.mac || '').toLowerCase().replace(/:/g, '') === macNorm;
|
|
118
|
+
});
|
|
110
119
|
return device ? (device.channels || 1) : 1;
|
|
111
120
|
}
|
|
112
121
|
|
|
@@ -159,7 +168,7 @@
|
|
|
159
168
|
}
|
|
160
169
|
|
|
161
170
|
mappings.forEach(function(m, idx) {
|
|
162
|
-
var row = $('<div class="mapping-row" data-idx="' + idx + '"
|
|
171
|
+
var row = $('<div class="mapping-row" data-idx="' + idx + '"></div>');
|
|
163
172
|
// 地址输入框:杜亚2字节地址,自定义模式隐藏,其他显示
|
|
164
173
|
var addrHtml = '';
|
|
165
174
|
if (m.brand === 'custom') {
|
|
@@ -174,7 +183,29 @@
|
|
|
174
183
|
'<input type="number" class="addr-input" value="' + (m.address !== undefined ? m.address : 1) + '" min="0" max="255" title="Modbus从机地址(0-255)">' +
|
|
175
184
|
'</div>';
|
|
176
185
|
}
|
|
186
|
+
// 生成自定义码摘要信息
|
|
187
|
+
var codeSummary = '';
|
|
188
|
+
if (m.brand === 'custom' && m.customCodes) {
|
|
189
|
+
var codes = m.customCodes;
|
|
190
|
+
if (m.device === 'custom_switch') {
|
|
191
|
+
codeSummary = (codes.sendOn || codes.recvOn || codes.sendOff || codes.recvOff) ? '已配置' : '未配置';
|
|
192
|
+
} else if (m.device === 'custom_curtain') {
|
|
193
|
+
codeSummary = (codes.sendOpen || codes.sendClose || codes.recvOpen || codes.recvClose) ? '已配置' : '未配置';
|
|
194
|
+
} else if (m.device === 'custom_climate') {
|
|
195
|
+
codeSummary = (codes.acSendOn || codes.fanSendHigh || codes.modeSendCool) ? '已配置' : '未配置';
|
|
196
|
+
} else {
|
|
197
|
+
codeSummary = codes.trigger ? '已配置' : '未配置';
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
// 自定义码折叠按钮和反馈选项(放在同一行)
|
|
201
|
+
var toggleBtn = '';
|
|
202
|
+
if (m.brand === 'custom' && m.device) {
|
|
203
|
+
var feedbackChecked = (m.feedback !== false) ? ' checked' : '';
|
|
204
|
+
toggleBtn = '<label class="feedback-label" title="勾选:RS485收码后Mesh执行并反馈发码;不勾选:RS485收码后Mesh执行但不反馈" style="font-size:10px;cursor:pointer;white-space:nowrap;"><input type="checkbox" class="feedback-checkbox"' + feedbackChecked + ' style="margin-right:1px;vertical-align:middle;">反馈</label>';
|
|
205
|
+
toggleBtn += '<button type="button" class="red-ui-button red-ui-button-small btn-toggle-codes" title="展开/收起自定义码" style="margin-left:2px;background:#fff8e1;border-color:#ffa726;color:#f57c00;padding:2px 4px;"><i class="fa fa-code"></i>' + codeSummary + '</button>';
|
|
206
|
+
}
|
|
177
207
|
row.html(
|
|
208
|
+
'<div class="mapping-main">' +
|
|
178
209
|
'<div class="mesh-col">' +
|
|
179
210
|
' <select class="mesh-select">' + getMeshOptions(m.meshMac) + '</select>' +
|
|
180
211
|
' <span class="mesh-ch-wrap">' + getMeshChannelOptions(m.meshMac, m.meshChannel || 1) + '</span>' +
|
|
@@ -188,16 +219,22 @@
|
|
|
188
219
|
' <span class="rs485-ch-wrap">' + getRS485ChannelOptions(m.brand, m.device, m.rs485Channel || 1) + '</span>' +
|
|
189
220
|
'</div>' +
|
|
190
221
|
addrHtml +
|
|
191
|
-
'<div class="del-col"
|
|
222
|
+
'<div class="del-col">' + toggleBtn + '<button type="button" class="red-ui-button red-ui-button-small btn-remove" title="删除" style="padding:2px 6px;"><i class="fa fa-times"></i></button></div>' +
|
|
223
|
+
'</div>'
|
|
192
224
|
);
|
|
193
225
|
container.append(row);
|
|
194
226
|
// 如果是自定义协议,显示自定义码输入框
|
|
195
227
|
if (m.brand === 'custom' && m.device) {
|
|
196
228
|
updateCustomCodesVisibility(row, m.brand, m.device, idx);
|
|
197
229
|
}
|
|
230
|
+
// 如果是SYMI或中弘协议,显示桥接配置
|
|
231
|
+
if ((m.brand === 'symi' || m.brand === 'zhonghong') && m.device) {
|
|
232
|
+
updateCustomCodesVisibility(row, m.brand, m.device, idx);
|
|
233
|
+
}
|
|
198
234
|
});
|
|
199
235
|
|
|
200
236
|
bindEvents();
|
|
237
|
+
bindToggleEvents();
|
|
201
238
|
}
|
|
202
239
|
|
|
203
240
|
// 绑定事件
|
|
@@ -224,17 +261,13 @@
|
|
|
224
261
|
});
|
|
225
262
|
|
|
226
263
|
container.find('.device-select').off('change').on('change', function() {
|
|
227
|
-
var
|
|
228
|
-
var idx = row.data('idx');
|
|
264
|
+
var idx = $(this).closest('.mapping-row').data('idx');
|
|
229
265
|
var deviceId = $(this).val();
|
|
230
|
-
var brandId = row.find('.brand-select').val();
|
|
266
|
+
var brandId = $(this).closest('.mapping-row').find('.brand-select').val();
|
|
231
267
|
mappings[idx].device = deviceId;
|
|
232
|
-
// 更新RS485按键选择
|
|
233
|
-
row.find('.rs485-ch-wrap').html(getRS485ChannelOptions(brandId, deviceId, 1));
|
|
234
268
|
mappings[idx].rs485Channel = 1;
|
|
235
|
-
//
|
|
236
|
-
|
|
237
|
-
bindEvents(); // 重新绑定新添加的元素事件
|
|
269
|
+
// 重新渲染以更新反馈/折叠按钮和录码区域
|
|
270
|
+
renderMappings();
|
|
238
271
|
});
|
|
239
272
|
|
|
240
273
|
container.find('.rs485-channel').off('change').on('change', function() {
|
|
@@ -281,37 +314,43 @@
|
|
|
281
314
|
renderMappings();
|
|
282
315
|
});
|
|
283
316
|
|
|
317
|
+
// 反馈选项
|
|
318
|
+
container.find('.feedback-checkbox').off('change').on('change', function() {
|
|
319
|
+
var idx = $(this).closest('.mapping-row').data('idx');
|
|
320
|
+
mappings[idx].feedback = $(this).is(':checked');
|
|
321
|
+
});
|
|
322
|
+
|
|
284
323
|
// 自定义码输入事件 - 开关
|
|
285
|
-
container.find('.custom-send-on').off('change').on('change', function() {
|
|
324
|
+
container.find('.custom-code-send-on').off('change').on('change', function() {
|
|
286
325
|
var idx = $(this).closest('.mapping-row').data('idx');
|
|
287
326
|
mappings[idx].customCodes = mappings[idx].customCodes || {};
|
|
288
327
|
mappings[idx].customCodes.sendOn = $(this).val();
|
|
289
328
|
});
|
|
290
|
-
container.find('.custom-send-off').off('change').on('change', function() {
|
|
329
|
+
container.find('.custom-code-send-off').off('change').on('change', function() {
|
|
291
330
|
var idx = $(this).closest('.mapping-row').data('idx');
|
|
292
331
|
mappings[idx].customCodes = mappings[idx].customCodes || {};
|
|
293
332
|
mappings[idx].customCodes.sendOff = $(this).val();
|
|
294
333
|
});
|
|
295
|
-
container.find('.custom-recv-on').off('change').on('change', function() {
|
|
334
|
+
container.find('.custom-code-recv-on').off('change').on('change', function() {
|
|
296
335
|
var idx = $(this).closest('.mapping-row').data('idx');
|
|
297
336
|
mappings[idx].customCodes = mappings[idx].customCodes || {};
|
|
298
337
|
mappings[idx].customCodes.recvOn = $(this).val();
|
|
299
338
|
});
|
|
300
|
-
container.find('.custom-recv-off').off('change').on('change', function() {
|
|
339
|
+
container.find('.custom-code-recv-off').off('change').on('change', function() {
|
|
301
340
|
var idx = $(this).closest('.mapping-row').data('idx');
|
|
302
341
|
mappings[idx].customCodes = mappings[idx].customCodes || {};
|
|
303
342
|
mappings[idx].customCodes.recvOff = $(this).val();
|
|
304
343
|
});
|
|
305
344
|
// 窗帘码
|
|
306
|
-
container.find('.custom-send-open, .custom-send-close, .custom-send-stop, .custom-recv-open, .custom-recv-close, .custom-recv-stop').off('change').on('change', function() {
|
|
345
|
+
container.find('.custom-code-send-open, .custom-code-send-close, .custom-code-send-stop, .custom-code-recv-open, .custom-code-recv-close, .custom-code-recv-stop').off('change').on('change', function() {
|
|
307
346
|
var idx = $(this).closest('.mapping-row').data('idx');
|
|
308
347
|
mappings[idx].customCodes = mappings[idx].customCodes || {};
|
|
309
|
-
if ($(this).hasClass('custom-send-open')) mappings[idx].customCodes.sendOpen = $(this).val();
|
|
310
|
-
if ($(this).hasClass('custom-send-close')) mappings[idx].customCodes.sendClose = $(this).val();
|
|
311
|
-
if ($(this).hasClass('custom-send-stop')) mappings[idx].customCodes.sendStop = $(this).val();
|
|
312
|
-
if ($(this).hasClass('custom-recv-open')) mappings[idx].customCodes.recvOpen = $(this).val();
|
|
313
|
-
if ($(this).hasClass('custom-recv-close')) mappings[idx].customCodes.recvClose = $(this).val();
|
|
314
|
-
if ($(this).hasClass('custom-recv-stop')) mappings[idx].customCodes.recvStop = $(this).val();
|
|
348
|
+
if ($(this).hasClass('custom-code-send-open')) mappings[idx].customCodes.sendOpen = $(this).val();
|
|
349
|
+
if ($(this).hasClass('custom-code-send-close')) mappings[idx].customCodes.sendClose = $(this).val();
|
|
350
|
+
if ($(this).hasClass('custom-code-send-stop')) mappings[idx].customCodes.sendStop = $(this).val();
|
|
351
|
+
if ($(this).hasClass('custom-code-recv-open')) mappings[idx].customCodes.recvOpen = $(this).val();
|
|
352
|
+
if ($(this).hasClass('custom-code-recv-close')) mappings[idx].customCodes.recvClose = $(this).val();
|
|
353
|
+
if ($(this).hasClass('custom-code-recv-stop')) mappings[idx].customCodes.recvStop = $(this).val();
|
|
315
354
|
});
|
|
316
355
|
// 空调码
|
|
317
356
|
container.find('[class^="custom-ac-"], [class^="custom-fan-"], [class^="custom-mode-"], [class^="custom-temp-"]').off('change').on('change', function() {
|
|
@@ -327,9 +366,11 @@
|
|
|
327
366
|
else if (cls.includes('fan-send-high')) mappings[idx].customCodes.fanSendHigh = $(this).val();
|
|
328
367
|
else if (cls.includes('fan-send-mid')) mappings[idx].customCodes.fanSendMid = $(this).val();
|
|
329
368
|
else if (cls.includes('fan-send-low')) mappings[idx].customCodes.fanSendLow = $(this).val();
|
|
369
|
+
else if (cls.includes('fan-send-auto')) mappings[idx].customCodes.fanSendAuto = $(this).val();
|
|
330
370
|
else if (cls.includes('fan-recv-high')) mappings[idx].customCodes.fanRecvHigh = $(this).val();
|
|
331
371
|
else if (cls.includes('fan-recv-mid')) mappings[idx].customCodes.fanRecvMid = $(this).val();
|
|
332
372
|
else if (cls.includes('fan-recv-low')) mappings[idx].customCodes.fanRecvLow = $(this).val();
|
|
373
|
+
else if (cls.includes('fan-recv-auto')) mappings[idx].customCodes.fanRecvAuto = $(this).val();
|
|
333
374
|
else if (cls.includes('mode-send-cool')) mappings[idx].customCodes.modeSendCool = $(this).val();
|
|
334
375
|
else if (cls.includes('mode-send-heat')) mappings[idx].customCodes.modeSendHeat = $(this).val();
|
|
335
376
|
else if (cls.includes('mode-send-dry')) mappings[idx].customCodes.modeSendDry = $(this).val();
|
|
@@ -369,14 +410,112 @@
|
|
|
369
410
|
});
|
|
370
411
|
}
|
|
371
412
|
|
|
413
|
+
// 绑定SYMI/Zhonghong桥接配置事件
|
|
414
|
+
function bindBridgeEvents() {
|
|
415
|
+
var container = $('#mapping-list');
|
|
416
|
+
|
|
417
|
+
// SYMI配置
|
|
418
|
+
container.find('.symi-local-addr').off('change').on('change', function() {
|
|
419
|
+
var idx = $(this).closest('.mapping-row').data('idx');
|
|
420
|
+
mappings[idx].symiLocalAddr = parseInt($(this).val()) || 1;
|
|
421
|
+
});
|
|
422
|
+
container.find('.symi-device-addr').off('change').on('change', function() {
|
|
423
|
+
var idx = $(this).closest('.mapping-row').data('idx');
|
|
424
|
+
mappings[idx].symiDeviceAddr = parseInt($(this).val()) || 1;
|
|
425
|
+
});
|
|
426
|
+
container.find('.symi-device-channel').off('change').on('change', function() {
|
|
427
|
+
var idx = $(this).closest('.mapping-row').data('idx');
|
|
428
|
+
mappings[idx].symiDeviceChannel = parseInt($(this).val()) || 0;
|
|
429
|
+
});
|
|
430
|
+
container.find('.symi-brand-id').off('change').on('change', function() {
|
|
431
|
+
var idx = $(this).closest('.mapping-row').data('idx');
|
|
432
|
+
mappings[idx].symiBrandId = parseInt($(this).val()) || 0;
|
|
433
|
+
});
|
|
434
|
+
|
|
435
|
+
// Zhonghong配置
|
|
436
|
+
container.find('.zh-slave-addr').off('change').on('change', function() {
|
|
437
|
+
var idx = $(this).closest('.mapping-row').data('idx');
|
|
438
|
+
mappings[idx].zhSlaveAddr = parseInt($(this).val()) || 1;
|
|
439
|
+
});
|
|
440
|
+
container.find('.zh-outdoor-addr').off('change').on('change', function() {
|
|
441
|
+
var idx = $(this).closest('.mapping-row').data('idx');
|
|
442
|
+
mappings[idx].zhOutdoorAddr = parseInt($(this).val()) || 1;
|
|
443
|
+
});
|
|
444
|
+
container.find('.zh-indoor-addr').off('change').on('change', function() {
|
|
445
|
+
var idx = $(this).closest('.mapping-row').data('idx');
|
|
446
|
+
mappings[idx].zhIndoorAddr = parseInt($(this).val()) || 1;
|
|
447
|
+
});
|
|
448
|
+
|
|
449
|
+
// 桥接启用
|
|
450
|
+
container.find('.zh-bridge-target').off('change').on('change', function() {
|
|
451
|
+
var idx = $(this).closest('.mapping-row').data('idx');
|
|
452
|
+
mappings[idx].zhBridgeTarget = $(this).is(':checked');
|
|
453
|
+
});
|
|
454
|
+
container.find('.symi-bridge-target').off('change').on('change', function() {
|
|
455
|
+
var idx = $(this).closest('.mapping-row').data('idx');
|
|
456
|
+
mappings[idx].symiBridgeTarget = $(this).is(':checked');
|
|
457
|
+
});
|
|
458
|
+
}
|
|
459
|
+
|
|
372
460
|
// 更新自定义码输入区域的显示
|
|
373
461
|
function updateCustomCodesVisibility(row, brandId, deviceId, idx) {
|
|
374
462
|
row.find('.custom-codes-row').remove();
|
|
463
|
+
row.find('.bridge-config-row').remove();
|
|
464
|
+
|
|
465
|
+
// SYMI空调面板配置
|
|
466
|
+
if (brandId === 'symi' && deviceId === 'climate') {
|
|
467
|
+
var m = mappings[idx];
|
|
468
|
+
var html = '<div class="bridge-config-row" style="width:100%;padding:8px;margin-top:6px;background:#e3f2fd;border:1px dashed #2196f3;border-radius:4px;">';
|
|
469
|
+
html += '<div style="font-size:11px;color:#1976d2;margin-bottom:6px;"><i class="fa fa-cog"></i> SYMI面板配置</div>';
|
|
470
|
+
html += '<div style="display:grid;grid-template-columns:1fr 1fr 1fr 1fr;gap:6px;">';
|
|
471
|
+
html += '<label style="font-size:10px;">本机地址: <input type="number" class="symi-local-addr" value="' + (m.symiLocalAddr || 1) + '" min="0" max="255" style="width:100%;padding:2px 4px;border:1px solid #64b5f6;border-radius:2px;font-size:10px;"></label>';
|
|
472
|
+
html += '<label style="font-size:10px;">设备地址: <input type="number" class="symi-device-addr" value="' + (m.symiDeviceAddr || 1) + '" min="0" max="255" style="width:100%;padding:2px 4px;border:1px solid #64b5f6;border-radius:2px;font-size:10px;"></label>';
|
|
473
|
+
html += '<label style="font-size:10px;">设备通道: <input type="number" class="symi-device-channel" value="' + (m.symiDeviceChannel || 0) + '" min="0" max="255" style="width:100%;padding:2px 4px;border:1px solid #64b5f6;border-radius:2px;font-size:10px;"></label>';
|
|
474
|
+
html += '<label style="font-size:10px;">品牌ID: <input type="number" class="symi-brand-id" value="' + (m.symiBrandId || 0) + '" min="0" max="255" style="width:100%;padding:2px 4px;border:1px solid #64b5f6;border-radius:2px;font-size:10px;"></label>';
|
|
475
|
+
html += '</div>';
|
|
476
|
+
html += '<div style="font-size:11px;color:#1976d2;margin:8px 0 6px 0;"><i class="fa fa-link"></i> 桥接到中弘VRF</div>';
|
|
477
|
+
html += '<div style="display:grid;grid-template-columns:1fr 1fr 1fr 1fr;gap:6px;">';
|
|
478
|
+
html += '<label style="font-size:10px;"><input type="checkbox" class="zh-bridge-target" ' + (m.zhBridgeTarget ? 'checked' : '') + '> 启用桥接</label>';
|
|
479
|
+
html += '<label style="font-size:10px;">从机地址: <input type="number" class="zh-slave-addr" value="' + (m.zhSlaveAddr || 1) + '" min="0" max="255" style="width:100%;padding:2px 4px;border:1px solid #64b5f6;border-radius:2px;font-size:10px;"></label>';
|
|
480
|
+
html += '<label style="font-size:10px;">外机地址: <input type="number" class="zh-outdoor-addr" value="' + (m.zhOutdoorAddr || 1) + '" min="0" max="255" style="width:100%;padding:2px 4px;border:1px solid #64b5f6;border-radius:2px;font-size:10px;"></label>';
|
|
481
|
+
html += '<label style="font-size:10px;">内机地址: <input type="number" class="zh-indoor-addr" value="' + (m.zhIndoorAddr || 1) + '" min="0" max="255" style="width:100%;padding:2px 4px;border:1px solid #64b5f6;border-radius:2px;font-size:10px;"></label>';
|
|
482
|
+
html += '</div>';
|
|
483
|
+
html += '</div>';
|
|
484
|
+
row.append(html);
|
|
485
|
+
bindBridgeEvents();
|
|
486
|
+
return;
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
// 中弘VRF配置
|
|
490
|
+
if (brandId === 'zhonghong' && deviceId === 'climate') {
|
|
491
|
+
var m = mappings[idx];
|
|
492
|
+
var html = '<div class="bridge-config-row" style="width:100%;padding:8px;margin-top:6px;background:#fff3e0;border:1px dashed #ff9800;border-radius:4px;">';
|
|
493
|
+
html += '<div style="font-size:11px;color:#e65100;margin-bottom:6px;"><i class="fa fa-cog"></i> 中弘VRF配置</div>';
|
|
494
|
+
html += '<div style="display:grid;grid-template-columns:1fr 1fr 1fr;gap:6px;">';
|
|
495
|
+
html += '<label style="font-size:10px;">从机地址: <input type="number" class="zh-slave-addr" value="' + (m.zhSlaveAddr || 1) + '" min="0" max="255" style="width:100%;padding:2px 4px;border:1px solid #ffb74d;border-radius:2px;font-size:10px;"></label>';
|
|
496
|
+
html += '<label style="font-size:10px;">外机地址: <input type="number" class="zh-outdoor-addr" value="' + (m.zhOutdoorAddr || 1) + '" min="0" max="255" style="width:100%;padding:2px 4px;border:1px solid #ffb74d;border-radius:2px;font-size:10px;"></label>';
|
|
497
|
+
html += '<label style="font-size:10px;">内机地址: <input type="number" class="zh-indoor-addr" value="' + (m.zhIndoorAddr || 1) + '" min="0" max="255" style="width:100%;padding:2px 4px;border:1px solid #ffb74d;border-radius:2px;font-size:10px;"></label>';
|
|
498
|
+
html += '</div>';
|
|
499
|
+
html += '<div style="font-size:11px;color:#e65100;margin:8px 0 6px 0;"><i class="fa fa-link"></i> 桥接到SYMI面板</div>';
|
|
500
|
+
html += '<div style="display:grid;grid-template-columns:1fr 1fr 1fr 1fr 1fr;gap:6px;">';
|
|
501
|
+
html += '<label style="font-size:10px;"><input type="checkbox" class="symi-bridge-target" ' + (m.symiBridgeTarget ? 'checked' : '') + '> 启用桥接</label>';
|
|
502
|
+
html += '<label style="font-size:10px;">本机地址: <input type="number" class="symi-local-addr" value="' + (m.symiLocalAddr || 1) + '" min="0" max="255" style="width:100%;padding:2px 4px;border:1px solid #ffb74d;border-radius:2px;font-size:10px;"></label>';
|
|
503
|
+
html += '<label style="font-size:10px;">设备地址: <input type="number" class="symi-device-addr" value="' + (m.symiDeviceAddr || 1) + '" min="0" max="255" style="width:100%;padding:2px 4px;border:1px solid #ffb74d;border-radius:2px;font-size:10px;"></label>';
|
|
504
|
+
html += '<label style="font-size:10px;">设备通道: <input type="number" class="symi-device-channel" value="' + (m.symiDeviceChannel || 0) + '" min="0" max="255" style="width:100%;padding:2px 4px;border:1px solid #ffb74d;border-radius:2px;font-size:10px;"></label>';
|
|
505
|
+
html += '<label style="font-size:10px;">品牌ID: <input type="number" class="symi-brand-id" value="' + (m.symiBrandId || 0) + '" min="0" max="255" style="width:100%;padding:2px 4px;border:1px solid #ffb74d;border-radius:2px;font-size:10px;"></label>';
|
|
506
|
+
html += '</div>';
|
|
507
|
+
html += '</div>';
|
|
508
|
+
row.append(html);
|
|
509
|
+
bindBridgeEvents();
|
|
510
|
+
return;
|
|
511
|
+
}
|
|
512
|
+
|
|
375
513
|
if (brandId !== 'custom') return;
|
|
376
514
|
|
|
377
515
|
var codes = mappings[idx].customCodes || {};
|
|
378
|
-
|
|
379
|
-
html
|
|
516
|
+
// 内容区域(默认折叠)
|
|
517
|
+
var html = '<div class="custom-codes-row" style="width:100%;">';
|
|
518
|
+
html += '<div class="custom-codes-content" style="display:none;padding:8px;background:#fff8e1;border:1px dashed #ffa726;border-radius:4px;margin-top:4px;">';
|
|
380
519
|
|
|
381
520
|
if (deviceId === 'custom_switch') {
|
|
382
521
|
html += '<div style="display:grid;grid-template-columns:1fr 1fr;gap:6px;">';
|
|
@@ -405,13 +544,15 @@
|
|
|
405
544
|
html += '</div>';
|
|
406
545
|
|
|
407
546
|
html += '<div style="font-size:10px;color:#f57c00;margin-bottom:4px;">风速控制</div>';
|
|
408
|
-
html += '<div style="display:grid;grid-template-columns:1fr 1fr 1fr;gap:4px;margin-bottom:6px;">';
|
|
547
|
+
html += '<div style="display:grid;grid-template-columns:1fr 1fr 1fr 1fr;gap:4px;margin-bottom:6px;">';
|
|
409
548
|
html += '<label style="font-size:10px;">发高风: <input type="text" class="custom-fan-send-high" value="' + (codes.fanSendHigh || '') + '" placeholder="" maxlength="128" style="width:100%;padding:2px 4px;border:1px solid #ffb74d;border-radius:2px;font-size:9px;font-family:monospace;"></label>';
|
|
410
549
|
html += '<label style="font-size:10px;">发中风: <input type="text" class="custom-fan-send-mid" value="' + (codes.fanSendMid || '') + '" placeholder="" maxlength="128" style="width:100%;padding:2px 4px;border:1px solid #ffb74d;border-radius:2px;font-size:9px;font-family:monospace;"></label>';
|
|
411
550
|
html += '<label style="font-size:10px;">发低风: <input type="text" class="custom-fan-send-low" value="' + (codes.fanSendLow || '') + '" placeholder="" maxlength="128" style="width:100%;padding:2px 4px;border:1px solid #ffb74d;border-radius:2px;font-size:9px;font-family:monospace;"></label>';
|
|
551
|
+
html += '<label style="font-size:10px;">发自动: <input type="text" class="custom-fan-send-auto" value="' + (codes.fanSendAuto || '') + '" placeholder="" maxlength="128" style="width:100%;padding:2px 4px;border:1px solid #ffb74d;border-radius:2px;font-size:9px;font-family:monospace;"></label>';
|
|
412
552
|
html += '<label style="font-size:10px;">收高风: <input type="text" class="custom-fan-recv-high" value="' + (codes.fanRecvHigh || '') + '" placeholder="" maxlength="128" style="width:100%;padding:2px 4px;border:1px solid #ffb74d;border-radius:2px;font-size:9px;font-family:monospace;"></label>';
|
|
413
553
|
html += '<label style="font-size:10px;">收中风: <input type="text" class="custom-fan-recv-mid" value="' + (codes.fanRecvMid || '') + '" placeholder="" maxlength="128" style="width:100%;padding:2px 4px;border:1px solid #ffb74d;border-radius:2px;font-size:9px;font-family:monospace;"></label>';
|
|
414
554
|
html += '<label style="font-size:10px;">收低风: <input type="text" class="custom-fan-recv-low" value="' + (codes.fanRecvLow || '') + '" placeholder="" maxlength="128" style="width:100%;padding:2px 4px;border:1px solid #ffb74d;border-radius:2px;font-size:9px;font-family:monospace;"></label>';
|
|
555
|
+
html += '<label style="font-size:10px;">收自动: <input type="text" class="custom-fan-recv-auto" value="' + (codes.fanRecvAuto || '') + '" placeholder="" maxlength="128" style="width:100%;padding:2px 4px;border:1px solid #ffb74d;border-radius:2px;font-size:9px;font-family:monospace;"></label>';
|
|
415
556
|
html += '</div>';
|
|
416
557
|
|
|
417
558
|
html += '<div style="font-size:10px;color:#f57c00;margin-bottom:4px;">模式控制</div>';
|
|
@@ -458,9 +599,44 @@
|
|
|
458
599
|
html += '<label style="font-size:11px;">触发码: <input type="text" class="custom-code-trigger" value="' + (codes.trigger || '') + '" placeholder="55 01 01..." style="width:200px;padding:2px 4px;border:1px solid #ffb74d;border-radius:2px;font-size:11px;font-family:monospace;"></label>';
|
|
459
600
|
html += '</div>';
|
|
460
601
|
}
|
|
602
|
+
html += '</div>'; // 关闭 custom-codes-content
|
|
461
603
|
html += '</div>';
|
|
462
604
|
row.append(html);
|
|
605
|
+
|
|
463
606
|
bindEvents();
|
|
607
|
+
bindToggleEvents();
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
// 绑定折叠按钮事件
|
|
611
|
+
function bindToggleEvents() {
|
|
612
|
+
$('#mapping-list').find('.btn-toggle-codes').off('click').on('click', function(e) {
|
|
613
|
+
e.preventDefault();
|
|
614
|
+
e.stopPropagation();
|
|
615
|
+
var row = $(this).closest('.mapping-row');
|
|
616
|
+
var content = row.find('.custom-codes-content');
|
|
617
|
+
var btn = $(this);
|
|
618
|
+
if (content.is(':visible')) {
|
|
619
|
+
content.slideUp(200);
|
|
620
|
+
// 更新按钮文字
|
|
621
|
+
var idx = row.data('idx');
|
|
622
|
+
var m = mappings[idx];
|
|
623
|
+
var codes = m.customCodes || {};
|
|
624
|
+
var summary = '未配置';
|
|
625
|
+
if (m.device === 'custom_switch') {
|
|
626
|
+
summary = (codes.sendOn || codes.recvOn || codes.sendOff || codes.recvOff) ? '已配置' : '未配置';
|
|
627
|
+
} else if (m.device === 'custom_curtain') {
|
|
628
|
+
summary = (codes.sendOpen || codes.sendClose || codes.recvOpen || codes.recvClose) ? '已配置' : '未配置';
|
|
629
|
+
} else if (m.device === 'custom_climate') {
|
|
630
|
+
summary = (codes.acSendOn || codes.fanSendHigh || codes.modeSendCool) ? '已配置' : '未配置';
|
|
631
|
+
} else {
|
|
632
|
+
summary = codes.trigger ? '已配置' : '未配置';
|
|
633
|
+
}
|
|
634
|
+
btn.html('<i class="fa fa-code"></i> ' + summary);
|
|
635
|
+
} else {
|
|
636
|
+
content.slideDown(200);
|
|
637
|
+
btn.html('<i class="fa fa-code"></i> 收起');
|
|
638
|
+
}
|
|
639
|
+
});
|
|
464
640
|
}
|
|
465
641
|
|
|
466
642
|
// 添加新映射
|
|
@@ -493,6 +669,30 @@
|
|
|
493
669
|
m.addrHigh = parseInt($(this).find('.addr-high').val()) || 1;
|
|
494
670
|
m.addrLow = parseInt($(this).find('.addr-low').val()) || 1;
|
|
495
671
|
}
|
|
672
|
+
// 保存SYMI空调面板配置
|
|
673
|
+
if (m.brand === 'symi') {
|
|
674
|
+
var row = $(this);
|
|
675
|
+
m.symiLocalAddr = parseInt(row.find('.symi-local-addr').val()) || 1;
|
|
676
|
+
m.symiDeviceAddr = parseInt(row.find('.symi-device-addr').val()) || 1;
|
|
677
|
+
m.symiDeviceChannel = parseInt(row.find('.symi-device-channel').val()) || 0;
|
|
678
|
+
m.symiBrandId = parseInt(row.find('.symi-brand-id').val()) || 0;
|
|
679
|
+
m.zhBridgeTarget = row.find('.zh-bridge-target').is(':checked');
|
|
680
|
+
m.zhSlaveAddr = parseInt(row.find('.zh-slave-addr').val()) || 1;
|
|
681
|
+
m.zhOutdoorAddr = parseInt(row.find('.zh-outdoor-addr').val()) || 1;
|
|
682
|
+
m.zhIndoorAddr = parseInt(row.find('.zh-indoor-addr').val()) || 1;
|
|
683
|
+
}
|
|
684
|
+
// 保存中弘VRF配置
|
|
685
|
+
if (m.brand === 'zhonghong') {
|
|
686
|
+
var row = $(this);
|
|
687
|
+
m.zhSlaveAddr = parseInt(row.find('.zh-slave-addr').val()) || 1;
|
|
688
|
+
m.zhOutdoorAddr = parseInt(row.find('.zh-outdoor-addr').val()) || 1;
|
|
689
|
+
m.zhIndoorAddr = parseInt(row.find('.zh-indoor-addr').val()) || 1;
|
|
690
|
+
m.symiBridgeTarget = row.find('.symi-bridge-target').is(':checked');
|
|
691
|
+
m.symiLocalAddr = parseInt(row.find('.symi-local-addr').val()) || 1;
|
|
692
|
+
m.symiDeviceAddr = parseInt(row.find('.symi-device-addr').val()) || 1;
|
|
693
|
+
m.symiDeviceChannel = parseInt(row.find('.symi-device-channel').val()) || 0;
|
|
694
|
+
m.symiBrandId = parseInt(row.find('.symi-brand-id').val()) || 0;
|
|
695
|
+
}
|
|
496
696
|
// 保存自定义码
|
|
497
697
|
if (m.brand === 'custom') {
|
|
498
698
|
var row = $(this);
|
|
@@ -577,8 +777,9 @@
|
|
|
577
777
|
|
|
578
778
|
#mapping-list { max-height: calc(100vh - 380px); min-height: 200px; overflow-y: auto; }
|
|
579
779
|
.mapping-empty { padding: 15px; text-align: center; color: #999; font-size: 12px; }
|
|
580
|
-
.mapping-row { display: flex;
|
|
581
|
-
.
|
|
780
|
+
.mapping-row { display: flex; flex-direction: column; padding: 6px 8px; margin-bottom: 6px; background: #fff; border: 1px solid #e0e0e0; border-radius: 4px; gap: 6px; }
|
|
781
|
+
.mapping-main { display: flex; align-items: center; width: 100%; gap: 6px; min-width: 0; }
|
|
782
|
+
.mesh-col { flex: 1 1 20%; min-width: 0; display: flex; gap: 4px; }
|
|
582
783
|
.mesh-col .mesh-select { flex: 1; padding: 4px; border: 1px solid #81c784; border-radius: 3px; background: #e8f5e9; font-size: 12px; }
|
|
583
784
|
.mesh-col .mesh-channel { width: 58px; padding: 4px; border: 1px solid #81c784; border-radius: 3px; background: #c8e6c9; font-size: 11px; font-weight: bold; }
|
|
584
785
|
.arrow-col { flex: 0 0 20px; text-align: center; color: #999; }
|
|
@@ -590,7 +791,7 @@
|
|
|
590
791
|
.device-col .rs485-channel { width: 58px; background: #bbdefb; font-size: 11px; font-weight: bold; }
|
|
591
792
|
.addr-col { flex: 0 0 50px; }
|
|
592
793
|
.addr-col input { width: 100%; padding: 4px; border: 1px solid #ccc; border-radius: 3px; text-align: center; font-size: 12px; }
|
|
593
|
-
.del-col { flex: 0 0
|
|
794
|
+
.del-col { flex: 0 0 auto; display: flex; align-items: center; gap: 4px; margin-left: auto; white-space: nowrap; }
|
|
594
795
|
.btn-remove { color: #d32f2f !important; padding: 2px 6px !important; }
|
|
595
796
|
.form-tips { font-size: 11px; }
|
|
596
797
|
.form-tips p { margin: 3px 0; }
|