Repository navigation
Expand file tree
/
Copy pathindex.html
More file actions
1822 lines (1793 loc) · 147 KB
/
Copy pathindex.html
File metadata and controls
1822 lines (1793 loc) · 147 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover">
<meta name="theme-color" content="#f5f5f7" media="(prefers-color-scheme: light)">
<meta name="theme-color" content="#1c1c1e" media="(prefers-color-scheme: dark)">
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="default">
<title>Web Serial Debug</title>
<link href="https://cdn.bootcdn.net/ajax/libs/twitter-bootstrap/5.2.3/css/bootstrap.min.css" rel="stylesheet">
<link href="https://cdn.bootcdn.net/ajax/libs/bootstrap-icons/1.11.0/font/bootstrap-icons.css" rel="stylesheet">
<script src="https://cdn.bootcdn.net/ajax/libs/twitter-bootstrap/5.2.3/js/bootstrap.bundle.min.js"></script>
<link rel="stylesheet" href="css/style.css">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@xterm/xterm@5.5.0/css/xterm.css">
<link rel="icon" href="imgs/logo.svg" type="image/svg+xml">
<link rel="mask-icon" href="imgs/logo.svg" color="#007aff">
<script src="js/platform.js"></script>
<script>
(function () {
// 主题三档:light / dark 为手动选择,其余(未设置)跟随系统;首屏前就定下,避免闪烁
var t = localStorage.getItem('serial-debug-theme')
if (t !== 'light' && t !== 'dark') {
t = window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light'
}
document.documentElement.setAttribute('data-theme', t)
// 界面布局(经典/现代)与双路视图(列表/气泡):同样首屏前定下;缺省即经典、列表
var layout = 'classic'
var dualView = 'list'
try {
if (localStorage.getItem('serial-debug-layout') === 'modern') layout = 'modern'
if (localStorage.getItem('serial-debug-dual-view') === 'bubble') dualView = 'bubble'
} catch (e) { /* 存储不可用时按默认 */ }
// 平台画像不提供的布局退回画像允许的第一个(桌面客户端只有现代、移动端只有经典),不改存储
if (window.AppPlatform && !window.AppPlatform.allowsLayout(layout)) layout = window.AppPlatform.fallbackLayout()
document.documentElement.dataset.layout = layout
document.documentElement.dataset.dualView = dualView
})()
</script>
<script>
hideTop = function () {
var header = document.querySelector('.bd-header');
header.remove();
document.documentElement.style.setProperty('--top-height', '0px');
}
</script>
<script>
!function (p) { "use strict"; !function (t) { var s = window, e = document, i = p, c = "".concat("https:" === e.location.protocol ? "https://" : "http://", "sdk.51.la/js-sdk-pro.min.js"), n = e.createElement("script"), r = e.getElementsByTagName("script")[0]; n.type = "text/javascript", n.setAttribute("charset", "UTF-8"), n.async = !0, n.src = c, n.id = "LA_COLLECT", i.d = n; var o = function () { s.LA.ids.push(i) }; s.LA ? s.LA.ids && o() : (s.LA = p, s.LA.ids = [], o()), r.parentNode.insertBefore(n, r) }() }({ id: "JuSxXIvOMGPBMkpo", ck: "JuSxXIvOMGPBMkpo" });
</script>
</head>
<body>
<div id="login-overlay" class="login-overlay">
<div class="login-card">
<div class="login-logo">
<img src="imgs/logo.svg" width="48" height="38" alt="Web Serial">
<h2>Web Serial Debug</h2>
</div>
<div class="login-form">
<input type="email" id="login-email" class="login-input" placeholder="邮箱" autocomplete="email">
<input type="password" id="login-password" class="login-input" placeholder="密码" autocomplete="current-password">
<p id="login-error" class="login-error"></p>
<button id="login-submit" class="login-btn">登 录</button>
</div>
</div>
</div>
<div id="issue-tip" class="issue-tip" data-feature="shell.browser" hidden>
<i class="bi bi-info-circle issue-tip-icon"></i>
<div class="issue-tip-body">
<strong>欢迎使用 Web Serial Debug</strong>
<p>如发现 Bug 或有改进建议,欢迎到
<a href="https://github.com/Roomen/web-serial-debug/issues" target="_blank" rel="noopener">GitHub Issues</a>
提交反馈,感谢支持!
</p>
</div>
<button type="button" id="issue-tip-close" class="issue-tip-close" title="关闭" aria-label="关闭">
<i class="bi bi-x-lg"></i>
</button>
</div>
<header class="bd-header bg-dark py-3 d-flex align-items-stretch border-bottom border-dark">
<div class="container-fluid d-flex align-items-center">
<h1 class="d-flex align-items-center fs-4 text-white mb-0">
<img src="imgs/logo.svg" width="38" height="30" class="me-3" alt="Web Serial">
Web Serial Debug
</h1>
<span class="ms-auto">
<a href="javascript:hideTop()" class="link-light ml-2">隐藏头部</a>
</span>
</div>
</header>
<div id="app-shell" class="d-flex">
<!--最左侧工具导航栏-->
<nav id="activity-rail">
<div class="rail-brand" title="Web Serial Debug">
<img src="imgs/logo.svg" width="26" height="21" alt="Web Serial">
</div>
<div class="rail-top">
<button class="rail-item active" data-view="view-serial" title="串口调试">
<i class="bi bi-terminal"></i>
<span class="rail-label">串口调试</span>
</button>
<button class="rail-item" data-view="view-fw-pack" data-feature="view.firmware-pack" title="固件打包">
<i class="bi bi-box-seam"></i>
<span class="rail-label">固件打包</span>
</button>
<button class="rail-item" data-view="view-blu" data-feature="view.power" title="功耗分析">
<i class="bi bi-speedometer2"></i>
<span class="rail-label">功耗分析</span>
</button>
</div>
<div class="rail-bottom">
<div class="dropend rail-config">
<button class="rail-item" id="rail-config-btn" type="button" title="配置:导入 / 导出 / 重置"
data-bs-toggle="dropdown" aria-expanded="false">
<i class="bi bi-sliders"></i>
<span class="rail-label">配置</span>
</button>
<ul class="dropdown-menu rail-config-menu" aria-labelledby="rail-config-btn">
<li class="rail-config-layout" data-feature="layout.switch">
<span class="rail-config-layout-title">界面布局</span>
<div class="ctl-seg" id="serial-layout-seg" role="group" aria-label="界面布局">
<button type="button" id="serial-layout-classic" aria-pressed="true">经典</button>
<button type="button" id="serial-layout-modern" aria-pressed="false">现代</button>
</div>
</li>
<li><hr class="dropdown-divider"></li>
<li><button class="dropdown-item" type="button" id="serial-import"><i class="bi bi-folder-plus"></i> 导入配置…</button></li>
<li><button class="dropdown-item" type="button" id="serial-export"><i class="bi bi-folder-symlink"></i> 导出配置</button></li>
<li><hr class="dropdown-divider"></li>
<li><button class="dropdown-item text-danger" type="button" id="serial-reset"><i class="bi bi-arrow-counterclockwise"></i> 重置所有参数…</button></li>
</ul>
</div>
<div class="rail-divider"></div>
<div class="theme-switch" id="theme-switch" role="radiogroup" aria-label="主题">
<button type="button" role="radio" data-theme-choice="auto" title="主题:跟随系统" aria-label="跟随系统"><i class="bi bi-circle-half"></i></button>
<button type="button" role="radio" data-theme-choice="light" title="主题:浅色" aria-label="浅色"><i class="bi bi-sun"></i></button>
<button type="button" role="radio" data-theme-choice="dark" title="主题:深色" aria-label="深色"><i class="bi bi-moon-stars"></i></button>
</div>
<a href="https://github.com/Roomen/web-serial-debug/issues/new" target="_blank" rel="noopener" class="rail-item" data-feature="shell.browser" title="提交建议 / Bug">
<i class="bi bi-chat-dots"></i>
<span class="rail-label">反馈</span>
</a>
<a href="https://github.com/Roomen/web-serial-debug" target="_blank" rel="noopener" class="rail-item" data-feature="shell.browser" title="GitHub 仓库">
<i class="bi bi-github"></i>
<span class="rail-label">GitHub</span>
</a>
<a href="https://serial.logan825.org/download/" target="_blank" rel="noopener" class="rail-item" id="rail-download" data-feature="shell.browser" title="下载客户端">
<i class="bi bi-download"></i>
<span class="rail-label">客户端</span>
</a>
<script>
// 预览站(serial-dev)下载预览版客户端,其余(正式站、本地)下载正式版;现代布局的配置菜单抄这里的地址,所以要在它挂载前定好
if (/^serial-dev\./.test(location.hostname)) document.getElementById('rail-download').href = 'https://serial-dev.logan825.org/download/'
</script>
<div class="rail-version" id="app-version" title="应用版本"></div>
</div>
</nav>
<!--视图容器-->
<div id="views">
<!--串口调试视图-->
<div id="view-serial" class="app-view active">
<!--顶部连接条-->
<div id="serial-connect-bar">
<!-- 单路/双路模式切换 -->
<div class="connect-bar-section connect-bar-mode" data-feature="lane.dual">
<div class="serial-mode-toggle ctl-seg">
<button class="serial-mode-btn active" data-mode="single" id="serial-mode-single">单路</button>
<button class="serial-mode-btn" data-mode="dual" id="serial-mode-dual">双路</button>
</div>
</div>
<!-- 串口参数:单/双路共享(不可放在 display:none 的单路区内) -->
<div class="connect-bar-section connect-bar-params">
<div class="dropdown" id="serial-params-dropdown">
<button type="button" class="serial-params-summary" id="serial-params-summary"
data-bs-toggle="dropdown" data-bs-auto-close="outside" aria-expanded="false"
title="串口参数(单路/双路共用)">
<i class="bi bi-sliders2"></i>
<span id="serial-params-summary-text">115200 8-N-1</span>
<i class="bi bi-chevron-down serial-params-summary-arrow"></i>
</button>
<div class="dropdown-menu" id="serial-params-popover">
<div class="serial-settings-card">
<div class="serial-settings-title">
<i class="bi bi-sliders2"></i> 串口参数
</div>
<!-- 仅双路:A/B 两路的参数与协议各自独立,这里选正在设置哪一路(现代布局由每路菜单决定,不显示) -->
<div class="serial-lane-head" id="serial-params-lane" hidden>
<div class="ctl-seg serial-lane-seg" id="serial-params-lane-seg" role="group" aria-label="设置哪一路">
<button type="button" data-sid="A" aria-pressed="true">A路</button>
<button type="button" data-sid="B" aria-pressed="false">B路</button>
</div>
<span class="serial-lane-hint">两路参数、协议各自独立</span>
</div>
<div class="serial-lane-proto" id="serial-lane-proto-row" hidden>
<label class="serial-field-label" for="serial-lane-protocol"><i class="bi bi-diagram-3"></i> 协议</label>
<select class="form-select serial-field-input" id="serial-lane-protocol" aria-label="本路协议">
<option value="sek">SEK(默认)</option>
</select>
</div>
<div class="serial-field">
<label class="serial-field-label"><i class="bi bi-speedometer2"></i> 波特率</label>
<div class="serial-baud-combo" id="baud-combo">
<input type="text" id="serial-baud" class="form-control serial-field-input" placeholder="选择或输入" value="115200" inputmode="numeric" pattern="[0-9]*" autocomplete="off">
<i class="bi bi-chevron-down serial-baud-arrow"></i>
<ul class="serial-baud-dropdown" id="baud-dropdown">
<li data-value="2400">2400</li>
<li data-value="4800">4800</li>
<li data-value="9600">9600</li>
<li data-value="14400">14400</li>
<li data-value="19200">19200</li>
<li data-value="28800">28800</li>
<li data-value="38400">38400</li>
<li data-value="57600">57600</li>
<li data-value="76800">76800</li>
<li data-value="115200">115200</li>
<li data-value="230400">230400</li>
<li data-value="460800">460800</li>
<li data-value="921600">921600</li>
</ul>
</div>
</div>
<div class="serial-field">
<label class="serial-field-label"><i class="bi bi-123"></i> 数据位</label>
<select class="form-select serial-field-input" id="serial-data-bits">
<option>8</option><option>7</option>
</select>
</div>
<div class="serial-field">
<label class="serial-field-label"><i class="bi bi-stop-circle"></i> 停止位</label>
<select class="form-select serial-field-input" id="serial-stop-bits">
<option>1</option><option>2</option>
</select>
</div>
<div class="serial-field">
<label class="serial-field-label"><i class="bi bi-shield-check"></i> 校验位</label>
<select class="form-select serial-field-input" id="serial-parity">
<Option value="none">None</Option><Option value="even">Even</Option><Option value="odd">Odd</Option>
</select>
</div>
<div class="serial-field">
<label class="serial-field-label"><i class="bi bi-database"></i> 缓冲区</label>
<input type="number" id="serial-buffer-size" class="form-control serial-field-input" placeholder="1024" list="buffer-size-list" max="1677216" value="1024">
<datalist id="buffer-size-list">
<option>255</option><option>512</option><option>1024</option><option>2048</option><option>4096</option><option>8192</option>
</datalist>
</div>
<div class="serial-field">
<label class="serial-field-label"><i class="bi bi-send-check"></i> 流控制</label>
<select class="form-select serial-field-input" id="serial-flow-control">
<Option value="none">None</Option><Option value="hardware">HardWare</Option>
</select>
</div>
</div>
</div>
</div>
</div>
<!-- 单路模式控件 -->
<div class="connect-bar-section connect-bar-main" id="serial-single-ctrl">
<div class="serial-device-chip is-empty" id="serial-chip">
<div id="serial-status">
<div class="serial-status-indicator disconnected">
<span class="serial-status-dot"></span>
<span class="serial-status-text">未连接</span>
</div>
</div>
<button class="btn serial-port-btn" id="serial-select-port">
<i class="bi bi-usb-plug"></i> 选择串口
</button>
<button class="btn serial-open-btn" id="serial-open-or-close" title="连接" aria-label="连接" hidden>
<i class="bi bi-plug"></i>
</button>
</div>
</div>
<!-- 双路模式控件(默认隐藏) -->
<div class="connect-bar-section connect-bar-dual" id="serial-dual-ctrl" style="display:none">
<!-- 会话 A -->
<div class="dual-session-row">
<input class="dual-session-label" id="serial-session-a-label" data-session="A" value="A路" maxlength="8" title="A路 标签(可编辑)">
<div class="serial-device-chip is-empty" id="serial-chip-a">
<div class="dual-status" id="serial-status-a">
<div class="serial-status-indicator disconnected">
<span class="serial-status-dot"></span>
<span class="serial-status-text">未连接</span>
</div>
</div>
<button class="btn serial-port-btn dual-port-btn" id="serial-select-port-a">
<i class="bi bi-usb-plug"></i> 选择
</button>
<button class="btn serial-open-btn dual-open-btn" id="serial-open-or-close-a" title="连接" aria-label="A路 连接" hidden>
<i class="bi bi-plug"></i>
</button>
</div>
</div>
<!-- 会话 B -->
<div class="dual-session-row">
<input class="dual-session-label" id="serial-session-b-label" data-session="B" value="B路" maxlength="8" title="B路 标签(可编辑)">
<div class="serial-device-chip is-empty" id="serial-chip-b">
<div class="dual-status" id="serial-status-b">
<div class="serial-status-indicator disconnected">
<span class="serial-status-dot"></span>
<span class="serial-status-text">未连接</span>
</div>
</div>
<button class="btn serial-port-btn dual-port-btn" id="serial-select-port-b">
<i class="bi bi-usb-plug"></i> 选择
</button>
<button class="btn serial-open-btn dual-open-btn" id="serial-open-or-close-b" title="连接" aria-label="B路 连接" hidden>
<i class="bi bi-plug"></i>
</button>
</div>
</div>
<!-- 主发口(与 A/B 同行靠右) -->
<div class="dual-active-send">
<span class="dual-active-send-label">主发:</span>
<div class="dual-send-toggle ctl-seg" id="serial-active-send" role="group" aria-label="选择主发送口">
<button type="button" class="dual-send-btn active" data-sid="S" aria-label="单路串口">单</button>
<button type="button" class="dual-send-btn" data-sid="A" aria-label="A路">A</button>
<button type="button" class="dual-send-btn" data-sid="B" aria-label="B路">B</button>
</div>
</div>
</div>
<!-- 串口预设:单路 / 双路都显示 -->
<div class="connect-bar-section connect-bar-preset">
<!-- 串口预设:单路存参数 + 协议,双路存两路参数 + 协议 + 路标签,一键套用(js/dual-config.js) -->
<div class="dropdown dual-preset" id="serial-dual-preset">
<button type="button" class="dual-preset-btn" id="serial-dual-preset-btn" data-bs-toggle="dropdown" data-bs-auto-close="outside" aria-expanded="false" title="串口预设:保存或套用串口参数与协议(双路含两路与路标签,不含设备)">
<i class="bi bi-bookmarks"></i><span>预设</span>
</button>
<div class="dropdown-menu dual-preset-menu" id="serial-dual-preset-menu">
<div class="dual-preset-title">串口预设<span id="serial-dual-preset-sub">参数 · 协议,不含设备</span></div>
<div class="dual-preset-list" id="serial-dual-preset-list" role="list"></div>
<form class="dual-preset-save" id="serial-dual-preset-form">
<input type="text" class="form-control form-control-sm" id="serial-dual-preset-name" maxlength="24" placeholder="给当前串口配置起个名字" aria-label="预设名称" autocomplete="off">
<button type="submit" class="btn btn-sm btn-primary">保存当前</button>
</form>
</div>
</div>
</div>
<!-- 右侧:当前协议(全局唯一的协议选择) + 命令面板入口 -->
<div class="connect-bar-section connect-bar-tools">
<label class="connect-bar-proto" title="当前协议:决定日志解析、协议下发与测试面板使用的协议">
<span class="connect-bar-proto-label">协议</span>
<select class="form-select form-select-sm" id="serial-protocol-select" aria-label="当前协议">
<option value="sek">SEK(默认)</option>
</select>
</label>
<button type="button" class="btn connect-bar-palette" id="wb-palette-btn" title="命令面板(Ctrl/⌘+K)">
<i class="bi bi-command"></i><span class="connect-bar-palette-text">命令</span><kbd>K</kbd>
</button>
</div>
<input type="file" id="serial-import-file" style="display: none;" accept="application/json" />
</div>
<div id="main">
<!--中间串口日志-->
<div id="log-main">
<!-- 按日志列自身宽度(容器查询)分档换行/紧凑,不按视口断点:右栏打开时视口宽但日志列窄 -->
<div class="serial-log-header d-flex justify-content-between flex-wrap align-items-center pt-1 pb-1 mb-1 flex-shrink-0">
<div class="serial-log-headline">
<h1 class="h4 serial-log-title">
<i class="bi bi-journal-text"></i> 串口日志
</h1>
<div class="ctl-seg" id="serial-log-view-seg" role="group" aria-label="日志视图">
<button type="button" id="serial-log-view-row" aria-pressed="true">行日志</button>
<button type="button" id="serial-log-view-term" aria-pressed="false">终端</button>
</div>
<span class="serial-log-legend" id="serial-log-legend" aria-hidden="true">
<span class="serial-log-legend-item" data-leg="left" id="serial-log-legend-left">TX</span>
<span class="serial-log-legend-item" data-leg="right" id="serial-log-legend-right">RX</span>
</span>
<!-- 仅现代布局 + 双路 + 行日志时显示(见 css 末尾):双路合流的显示方式 -->
<div class="ctl-seg" id="serial-dual-view-seg" role="group" aria-label="双路显示方式">
<button type="button" id="serial-dual-view-list" aria-pressed="true">列表</button>
<button type="button" id="serial-dual-view-bubble" aria-pressed="false">时间线</button>
</div>
</div>
<div class="btn-toolbar">
<div class="dropdown me-2" id="serial-log-settings">
<button type="button" class="serial-log-settings-summary" id="serial-log-settings-btn"
data-bs-toggle="dropdown" data-bs-auto-close="outside" aria-expanded="false"
title="日志设置(格式 / 分包 / 行数)">
<i class="bi bi-sliders2"></i>
<span id="serial-log-settings-text">Hex · 200ms · 10000行</span>
<i class="bi bi-chevron-down serial-log-settings-arrow"></i>
</button>
<div class="dropdown-menu" id="serial-log-settings-popover">
<div class="serial-settings-card">
<div class="serial-settings-title">
<i class="bi bi-journal-text"></i> 日志设置
</div>
<div class="serial-field">
<span class="serial-field-label">格式</span>
<div class="serial-log-format-controls">
<button type="button" class="ctl-chip-btn" id="serial-log-fmt-hex" aria-pressed="true">HEX</button>
<button type="button" class="ctl-chip-btn" id="serial-log-fmt-text" aria-pressed="false">TEXT</button>
<button type="button" class="ctl-chip-btn" id="serial-log-ansi" aria-pressed="false">ANSI 彩色</button>
<button type="button" class="ctl-chip-btn" id="serial-log-fmt-parse" aria-pressed="false" title="按当前协议解析每一帧,可与 HEX/TEXT 同时显示或单独显示">解析</button>
</div>
</div>
<div class="serial-field">
<label class="serial-field-label" for="serial-timer-out">分包</label>
<div class="ctl-seg serial-split-seg" role="group" aria-label="分包方式">
<button type="button" id="serial-split-time" aria-pressed="true" title="按字节间隔超时分包">超时</button>
<button type="button" id="serial-split-line" aria-pressed="false" title="按换行(\n / \r\n)分行,抓模组文本日志用;超时只兜底没有换行的残行">换行</button>
</div>
<input type="number" id="serial-timer-out" class="form-control serial-field-input"
placeholder="0不分包" value="200" min="0" title="分包超时(ms),0 表示不分包">
</div>
<div class="serial-field">
<label class="serial-field-label" for="serial-max-rows">行数</label>
<input type="number" id="serial-max-rows" class="form-control serial-field-input"
min="100" step="100" value="10000" title="日志超过该行数后从顶部裁剪">
</div>
</div>
</div>
</div>
<div class="btn-group serial-log-actions">
<button type="button" class="btn btn-sm btn-outline-secondary ctl-toggle" id="serial-auto-scroll" aria-pressed="true" title="日志自动滚动到底部" aria-label="自动滚动"><i class="bi bi-chevron-bar-down"></i><span class="serial-log-action-text">自动滚动</span></button>
<button class="btn btn-sm btn-outline-secondary" id="serial-clear" title="清空日志" aria-label="清空"><i class="bi bi-trash3"></i><span class="serial-log-action-text">清空</span></button>
<button class="btn btn-sm btn-outline-secondary" id="serial-copy" title="复制日志" aria-label="复制"><i class="bi bi-copy"></i><span class="serial-log-action-text">复制</span></button>
<button class="btn btn-sm btn-outline-secondary" id="serial-save" title="导出日志" aria-label="导出"><i class="bi bi-download"></i><span class="serial-log-action-text">导出</span></button>
</div>
</div>
</div>
<!-- 单路/双路各一套日志,同时只显示当前模式;双路仍是一个合流窗(分色) -->
<div id="serial-log-panes">
<div id="serial-log-mode-single" class="serial-log-mode">
<div id="serial-logs-single" class="serial-logs"></div>
<div id="serial-term-single" class="serial-term-host" hidden></div>
</div>
<div id="serial-log-mode-dual" class="serial-log-mode" hidden>
<div id="serial-logs-dual" class="serial-logs is-dual"></div>
<div id="serial-term-dual" class="serial-term-host" hidden></div>
</div>
</div>
<!--协议解析底部面板:顶缝拖高度,header 只负责折叠-->
<div id="serial-parse-panel" class="is-empty collapsed">
<div id="serial-parse-resizer" title="拖动调整高度"></div>
<div id="serial-parse-header" title="点击折叠/展开">
<span class="serial-parse-title">
<i class="bi bi-chevron-right serial-parse-chevron"></i>
<span class="wb-bottom-tabs" id="wb-bottom-tabs" role="tablist" aria-label="底部面板"></span>
</span>
<span class="serial-parse-actions">
<button class="wb-dock-action ctl-toggle" id="serial-parse-lock" type="button" aria-pressed="false"
title="锁定:点击日志行时不自动展开协议解析" aria-label="锁定协议解析面板">
<i class="bi bi-unlock"></i>
</button>
<button class="btn btn-sm btn-outline-secondary" id="serial-parse-clear"
type="button">清空</button>
<button class="wb-dock-action" id="wb-bottom-move" type="button" title="移到右侧" hidden>
<i class="bi bi-layout-sidebar-reverse"></i>
</button>
<button class="wb-dock-action" id="wb-bottom-close" type="button" title="收起底部面板">
<i class="bi bi-chevron-down"></i>
</button>
</span>
</div>
<div id="serial-parse-body">
<div id="serial-parse-input">
<div id="serial-protocol-hexview" class="sk-hex-dump is-empty" tabindex="0" role="button"
aria-label="协议 HEX 输入区,点击读取剪贴板">
<div class="sk-hex-empty" aria-hidden="true">
<div class="sk-hex-empty-main">
<i class="bi bi-clipboard-plus"></i>
<span>点击允许读取剪贴板</span>
</div>
<div class="sk-hex-empty-sub">首次需浏览器授权一次,之后点击不再弹窗 · 也可 Ctrl/Cmd+V</div>
</div>
</div>
</div>
<div id="serial-protocol-output" class="rounded p-2 overflow-auto">
</div>
</div>
<!-- 停靠到底部的其它面板挂在这里 -->
<div id="wb-bottom-body" class="tab-content wb-dock-body"></div>
</div>
<div id="serial-send-panel" class="serial-send-area">
<div id="serial-send-header" title="点击折叠/展开">
<span class="serial-send-title">
<i class="bi bi-chevron-down serial-send-chevron"></i>
<i class="bi bi-send"></i> 串口发送
<span class="serial-send-target" id="serial-send-target" hidden></span>
</span>
</div>
<div id="serial-send-body">
<div class="send-compose">
<textarea class="form-control serial-send-textarea" rows="2" id="serial-send-content"
spellcheck="false" placeholder="输入 HEX,如 41 54 0D 0A"></textarea>
<button id="serial-send" class="btn btn-primary serial-send-btn" type="button">
<span class="send-btn-main"><i class="bi bi-send-fill"></i> 发送</span>
<span class="send-btn-kbd" id="serial-send-kbd">Ctrl+Enter</span>
</button>
</div>
<div class="send-options">
<label class="ctl-chip ctl-chip--mono" title="按 HEX 解析输入内容(空格可有可无)">
<input type="checkbox" id="serial-hex-send"><span>HEX</span>
</label>
<label class="ctl-chip ctl-chip--mono" title="发送时在末尾追加回车 0D;与 \n 同时勾选即追加 0D 0A">
<input type="checkbox" id="serial-add-cr"><span>+ \r</span>
</label>
<label class="ctl-chip ctl-chip--mono" title="发送时在末尾追加换行 0A;与 \r 同时勾选即追加 0D 0A">
<input type="checkbox" id="serial-add-lf"><span>+ \n</span>
</label>
<label class="ctl-chip ctl-chip--mono" title="发送时在末尾追加 NUL 00;追加顺序为 CR、LF、NUL">
<input type="checkbox" id="serial-add-nul"><span>+ \0</span>
</label>
<label class="ctl-chip ctl-chip--mono ctl-chip--run" title="勾选后立即按间隔重复发送,取消勾选即停止">
<input type="checkbox" id="serial-loop-send"><span><i class="bi bi-repeat"></i> 循环</span>
</label>
<div class="input-group input-group-sm send-loop-time" id="serial-loop-send-time-group">
<input type="number" id="serial-loop-send-time" class="form-control" min="10" value="1000" aria-label="循环发送间隔(毫秒)">
<span class="input-group-text">ms</span>
</div>
<span class="send-info" id="serial-send-info" aria-live="polite"></span>
<div class="dropdown">
<button class="btn btn-sm send-tool" type="button" id="serial-send-history-btn" data-bs-toggle="dropdown"
aria-expanded="false" title="发送历史(输入框里按 ↑ / ↓ 翻阅)" aria-label="发送历史">
<i class="bi bi-clock-history"></i>
</button>
<ul class="dropdown-menu dropdown-menu-end send-history-menu" id="serial-send-history-menu"></ul>
</div>
<button class="btn btn-sm send-tool" type="button" id="serial-send-clear" title="清空输入" aria-label="清空输入">
<i class="bi bi-x-lg"></i>
</button>
</div>
</div>
</div>
</div>
<!--右侧串口工具-->
<div id="serial-tools" class="sidebar">
<button class="toggle-button" data-pane="right" title="拖动调整宽度 · 点击隐藏/打开边栏">
<i class="bi bi-chevron-compact-right"></i>
</button>
<div class="collapse show overflow-hidden sidebar-body">
<!-- 右侧停靠区:标题栏 + 当前面板;面板由 js/workbench.js 注册与切换 -->
<div class="wb-dock-head" id="wb-right-head">
<span class="wb-dock-title" id="wb-right-title"></span>
<button class="wb-dock-action" id="wb-right-move" type="button" title="移到底部">
<i class="bi bi-layout-split"></i>
</button>
<button class="wb-dock-action" id="wb-right-close" type="button" title="收起右侧面板">
<i class="bi bi-chevron-bar-right"></i>
</button>
</div>
<div class="tab-content flex-fill wb-dock-body" id="nav-tabContent">
<div class="tab-pane show active d-flex flex-column" id="nav-quick-send" role="tabpanel">
<div class="quick-send-toolbar">
<div class="input-group input-group-sm quick-send-group">
<span class="input-group-text">分组</span>
<select class="form-select" id="serial-quick-send">
</select>
</div>
<button id="serial-quick-send-add" class="btn btn-sm btn-outline-secondary" type="button" title="在当前分组末尾增加一条">
<i class="bi bi-plus-lg"></i> 增加
</button>
<div class="dropdown">
<button class="btn btn-sm btn-outline-secondary quick-send-more" type="button" data-bs-toggle="dropdown" aria-expanded="false" title="分组管理 / 导入导出" aria-label="分组管理">
<i class="bi bi-three-dots"></i>
</button>
<ul class="dropdown-menu dropdown-menu-end quick-send-menu">
<li><button class="dropdown-item" type="button" id="serial-quick-send-add-group"><i class="bi bi-folder-plus"></i> 新建分组…</button></li>
<li><button class="dropdown-item" type="button" id="serial-quick-send-rename-group"><i class="bi bi-pencil"></i> 重命名分组…</button></li>
<li><button class="dropdown-item text-danger" type="button" id="serial-quick-send-remove-group"><i class="bi bi-trash"></i> 删除当前分组</button></li>
<li><hr class="dropdown-divider"></li>
<li><button class="dropdown-item" type="button" id="serial-quick-send-import-btn"><i class="bi bi-box-arrow-in-down"></i> 导入到当前分组…</button></li>
<li><button class="dropdown-item" type="button" id="serial-quick-send-export"><i class="bi bi-box-arrow-up"></i> 导出当前分组</button></li>
</ul>
</div>
<input type="file" id="serial-quick-send-import" style="display: none;"
accept="application/json" />
</div>
<div class="quick-send-head">
<span class="col-content">内容(单击编辑)</span>
<span class="col-name" title="双击发送按钮可改名">发送 · 双击改名</span>
<span class="col-hex" title="HEX 模式">HEX</span>
</div>
<div id="serial-quick-send-content"
class="flex-grow-1 overflow-auto">
</div>
</div>
<div class="tab-pane d-flex flex-column flex-grow-1" id="nav-protocol" role="tabpanel">
<div class="protocol-basic-card mb-2">
<div class="protocol-basic-row">
<div class="protocol-switch-inline">
<div class="form-check form-switch ctl-switch" title="开启后日志 HEX 视图的每个字节可悬停查看其协议字段含义,不展开文本">
<input class="form-check-input" type="checkbox" id="serial-protocol-hover">
<label class="form-check-label" for="serial-protocol-hover">悬停提示</label>
</div>
</div>
</div>
<details class="protocol-advanced" id="serial-protocol-advanced">
<summary class="protocol-advanced-summary">
<i class="bi bi-chevron-right protocol-advanced-chevron"></i>
高级 · 加解密与密钥
</summary>
<div class="protocol-advanced-body">
<div class="protocol-advanced-grid">
<div class="input-group input-group-sm">
<span class="input-group-text">加解密</span>
<select class="form-select" id="serial-protocol-decrypt">
<option value="auto">自动</option>
<option value="always">强制解密</option>
<option value="never">不解密</option>
</select>
</div>
<div class="input-group input-group-sm" title="报文加解密算法 AES128/256-ECB">
<span class="input-group-text">加密方式</span>
<select class="form-select" id="serial-protocol-enc-type">
<option value="aes128">AES-128-ECB</option>
<option value="aes256">AES-256-ECB</option>
</select>
</div>
</div>
<div class="input-group input-group-sm mt-2" id="sk-key-ascii-group">
<span class="input-group-text">密钥(ASCII)</span>
<input type="text" class="form-control" id="serial-protocol-key-ascii"
placeholder="解析/下发共用 · 16/32 字节">
</div>
<div class="input-group input-group-sm mt-2" id="sk-key-hex-group">
<span class="input-group-text">密钥(HEX,优先)</span>
<input type="text" class="form-control" id="serial-protocol-key-hex"
placeholder="如 01020304… · 下发加密需勾选下方开关">
</div>
</div>
</details>
</div>
<div class="protocol-down-card mb-2" id="sk-down-card">
<h6 class="pane-section-title"><i class="bi bi-arrow-down-circle"></i> 下行下发</h6>
<div class="d-flex flex-wrap align-items-center mb-2 gap-2">
<div class="input-group input-group-sm" style="width: auto; flex-shrink: 0;">
<span class="input-group-text">功能码</span>
<select class="form-select" id="serial-protocol-down-func">
<option value="0x03" selected>信息查询 0x03</option>
<option value="0x01">参数设置 0x01</option>
<option value="0x11">指令操作 0x11</option>
</select>
</div>
<div class="input-group input-group-sm" style="width: auto; min-width: 120px;">
<span class="input-group-text">常用指令</span>
<select class="form-select" id="serial-protocol-down-preset" title="选择常用指令">
</select>
</div>
<div class="input-group input-group-sm" id="serial-protocol-down-param-group" style="display:none;flex:0 0 100%;">
<span class="input-group-text" id="serial-protocol-down-param-label">参数</span>
<input type="text" class="form-control" id="serial-protocol-down-param-val" style="width:100px;" placeholder="值">
<select class="form-select form-select-sm" id="serial-protocol-down-param-sel" style="display:none;width:130px;"></select>
<span class="input-group-text" id="serial-protocol-down-param-unit" style="font-size:0.7rem;"></span>
<div id="serial-protocol-down-param-multi" style="display:none;flex:0 0 100%;margin-top:4px;"></div>
</div>
</div>
<textarea class="form-control form-control-sm" rows="4" id="serial-protocol-down-tlv"
placeholder='TLV JSON,例如:[{"tag":10,"items":[{"id":1,"value":[]}]}],value 可为空数组(查询) 或 十六进制字符串 或 字节数组'></textarea>
<div class="d-flex flex-wrap align-items-center gap-2 mt-1">
<div class="form-check form-switch ctl-switch" title="关闭时始终明文下发(ctrl 不置加密位);开启后用上方密钥加密数据域。解析/解密密钥可单独保留。">
<input class="form-check-input" type="checkbox" id="serial-protocol-down-encrypt">
<label class="form-check-label" for="serial-protocol-down-encrypt">下发加密</label>
</div>
<button class="btn btn-sm btn-outline-secondary" id="serial-protocol-build">生成HEX</button>
<button class="btn btn-sm btn-primary flex-grow-1" id="serial-protocol-send">立即下发</button>
</div>
<textarea class="form-control form-control-sm mt-1 sk-hex-copyable" rows="3" id="serial-protocol-down-preview"
readonly placeholder="待发送的完整 HEX(空格分隔,点击可复制)" title="点击复制生成的 HEX"></textarea>
</div>
<div class="protocol-down-card mb-2" id="gz-down-card" style="display:none;">
<h6 class="pane-section-title"><i class="bi bi-arrow-down-circle"></i> 工装下行下发</h6>
<p class="sk-rw-hint mb-2">红外空口产测帧 · A5 + Len + 设备类型 + 命令 + 信息 + XOR · 按工位选择设备后下发</p>
<div class="d-flex flex-wrap align-items-center mb-2 gap-2">
<div class="input-group input-group-sm" style="width: auto;">
<span class="input-group-text">工位</span>
<select class="form-select" id="gz-down-station">
<option value="1" selected>工位1</option>
<option value="2">工位2</option>
</select>
</div>
<div class="input-group input-group-sm" style="width: auto; min-width: 16rem; flex: 1 1 16rem;">
<span class="input-group-text">设备类型</span>
<select class="form-select" id="gz-down-dev"></select>
</div>
<div class="input-group input-group-sm" style="width: auto; min-width: 12rem;">
<span class="input-group-text">命令</span>
<select class="form-select" id="gz-down-cmd"></select>
</div>
</div>
<div class="d-flex flex-wrap align-items-center mb-2 gap-2" id="gz-down-uid-group" style="display:none;">
<div class="input-group input-group-sm" style="width: auto;" id="gz-down-uid-op-wrap">
<span class="input-group-text">唯一码操作</span>
<select class="form-select" id="gz-down-uid-op">
<option value="0">0 不操作</option>
<option value="1" selected>1 强制写入</option>
<option value="2">2 自动写入</option>
</select>
</div>
<div class="input-group input-group-sm" style="width: auto; min-width: 14rem; flex: 1 1 14rem;">
<span class="input-group-text">唯一码</span>
<input type="text" class="form-control" id="gz-down-uid" placeholder="7 字节 HEX,如 01 02 03 04 05 06 07">
</div>
</div>
<div class="d-flex flex-wrap align-items-center mb-2 gap-2" id="gz-down-ad-group" style="display:none;">
<div class="input-group input-group-sm" style="width: auto;">
<span class="input-group-text">校准电压 mV</span>
<input type="number" class="form-control" id="gz-down-ad-mv" value="400" min="0" max="65535" style="width:7rem;">
</div>
</div>
<textarea class="form-control form-control-sm" rows="2" id="gz-down-info"
placeholder="信息域 HEX(部分命令由上方参数自动生成)"></textarea>
<div class="small text-danger" id="gz-down-err"></div>
<div class="d-flex flex-wrap align-items-center gap-2 mt-1">
<button class="btn btn-sm btn-outline-secondary" id="gz-down-build">生成HEX</button>
<button class="btn btn-sm btn-primary flex-grow-1" id="gz-down-send">立即下发</button>
</div>
<textarea class="form-control form-control-sm mt-1 sk-hex-copyable" rows="2" id="gz-down-preview"
readonly placeholder="待发送的完整 HEX(空格分隔,点击可复制)" title="点击复制生成的 HEX"></textarea>
</div>
<div class="protocol-down-card mb-2 sk-rw-card" id="gz-auto-card" style="display:none;">
<h6 class="pane-section-title">
<i class="bi bi-play-circle"></i> 一键工位测试
<span class="sk-rw-proto-badge">工装</span>
</h6>
<p class="sk-rw-hint mb-2">按所选工位/设备顺序:查询 → 休眠(末步必休眠)· 失败即停 · 整轮通过后唯一码自动 +1</p>
<div class="d-flex flex-wrap align-items-center mb-2 gap-2">
<div class="input-group input-group-sm" style="width: auto;">
<span class="input-group-text">工位</span>
<select class="form-select" id="gz-auto-station">
<option value="1" selected>工位1</option>
<option value="2">工位2</option>
</select>
</div>
<div class="input-group input-group-sm" style="width: auto; min-width: 16rem; flex: 1 1 16rem;">
<span class="input-group-text">设备类型</span>
<select class="form-select" id="gz-auto-dev"></select>
</div>
</div>
<div class="sk-rw-controls d-flex flex-wrap gap-2 mb-2">
<div class="input-group input-group-sm" style="width:auto;">
<span class="input-group-text">超时ms</span>
<input type="number" class="form-control" id="gz-auto-timeout" value="3000" min="500" max="60000" style="width:5.5rem;">
</div>
<div class="input-group input-group-sm" style="width:auto;">
<span class="input-group-text">间隔ms</span>
<input type="number" class="form-control" id="gz-auto-gap" value="200" min="0" max="10000" style="width:5rem;">
</div>
</div>
<div class="d-flex flex-wrap align-items-center gap-3 mb-2">
<div class="form-check form-switch ctl-switch" title="在查询前先下发设置唯一码(0x0A)">
<input class="form-check-input" type="checkbox" id="gz-auto-set-uid">
<label class="form-check-label" for="gz-auto-set-uid">先写唯一码</label>
</div>
</div>
<div class="d-flex flex-wrap align-items-center mb-2 gap-2" id="gz-auto-uid-group" style="display:none;">
<div class="input-group input-group-sm" style="width: auto; display:none;" id="gz-auto-uid-op-wrap">
<span class="input-group-text">查询写码</span>
<select class="form-select" id="gz-auto-uid-op">
<option value="1" selected>1 强制写入</option>
<option value="2">2 自动写入</option>
</select>
</div>
<div class="input-group input-group-sm" style="width: auto; min-width: 14rem; flex: 1 1 14rem;">
<span class="input-group-text">唯一码</span>
<input type="text" class="form-control" id="gz-auto-uid" placeholder="7 字节 HEX,如 01 02 03 04 05 06 07">
</div>
</div>
<div class="d-flex gap-2 mb-2">
<button class="btn btn-sm btn-primary flex-grow-1" id="gz-auto-start">开始测试</button>
<button class="btn btn-sm btn-outline-danger" id="gz-auto-stop" disabled>停止</button>
</div>
<div class="sk-rw-progress mb-1">
<div class="sk-rw-progress-bar" id="gz-auto-progress-bar">0/0</div>
</div>
<div class="sk-rw-status" id="gz-auto-status">就绪</div>
<div class="sk-rw-summary" id="gz-auto-summary"></div>
<div class="sk-rw-log" id="gz-auto-log"></div>
</div>
<div class="protocol-down-card mb-2" id="wmbus-down-card" style="display:none;">
<h6 class="pane-section-title"><i class="bi bi-arrow-down-circle"></i> W-MBUS 下行下发</h6>
<p class="sk-rw-hint mb-2">EN13757-3/-7 私有 profile · 全程 AES-128-CBC 加密 + AES-128-CMAC 鉴权,无明文选项</p>
<div class="d-flex flex-wrap align-items-center mb-2 gap-2">
<div class="input-group input-group-sm" style="width: auto;">
<span class="input-group-text">密钥角色</span>
<select class="form-select" id="wmbus-down-keyid">
<option value="0">0 公开读(无需密钥)</option>
<option value="1">1 操作员</option>
<option value="2">2 管理员</option>
</select>
</div>
<div class="input-group input-group-sm" style="width: auto;">
<span class="input-group-text">命令</span>
<select class="form-select" id="wmbus-down-cmd"></select>
</div>
</div>
<div class="d-flex flex-wrap align-items-center mb-2 gap-2">
<div class="input-group input-group-sm" style="width: auto;" title="固件 wmbus_ids.h 中 WMBUS_MANUFACTURER_ID 临时值,量产前需向 FLAG 注册核定后替换">
<span class="input-group-text">厂商</span>
<select class="form-select" id="wmbus-down-manuf">
<option value="AB4C">SEK(临时码 0x4CAB)</option>
</select>
</div>
<div class="input-group input-group-sm" style="width: auto;">
<span class="input-group-text">表号后8位</span>
<input type="text" class="form-control" id="wmbus-down-meterid" style="width:120px;" maxlength="8" placeholder="纯数字,不足补0">
</div>
<div class="input-group input-group-sm" style="width: auto;">
<span class="input-group-text">计数器MCNT</span>
<input type="number" class="form-control" id="wmbus-down-mcnt" style="width:100px;" min="1">
</div>
</div>
<div class="input-group input-group-sm mb-2" id="wmbus-down-key-group" style="display:none;">
<span class="input-group-text" id="wmbus-down-key-label">密钥</span>
<input type="text" class="form-control" id="wmbus-down-key-ascii" placeholder="ASCII · 留空=全0">
<input type="text" class="form-control" id="wmbus-down-key-hex" placeholder="HEX 32位(优先) · 留空=全0">
</div>
<div class="input-group input-group-sm mb-2" id="wmbus-down-param-group" style="display:none;">
<span class="input-group-text" id="wmbus-down-param-label">参数</span>
<input type="text" class="form-control" id="wmbus-down-param-val" style="width:120px;" placeholder="值">
<select class="form-select" id="wmbus-down-param-sel" style="display:none;"></select>
</div>
<div class="flex-wrap align-items-center mb-2 gap-2" id="wmbus-down-ip-group" style="display:none;">
<div class="input-group input-group-sm" style="width: auto;">
<span class="input-group-text">类型</span>
<select class="form-select" id="wmbus-down-ip-type">
<option value="0">IPv4</option>
<option value="1">IPv6</option>
<option value="2">URL</option>
</select>
</div>
<div class="input-group input-group-sm" style="width: auto;">
<span class="input-group-text" id="wmbus-down-ip-addr-label">地址(IPv4)</span>
<input type="text" class="form-control" id="wmbus-down-ip-addr" style="width:200px;" placeholder="如 192.168.1.1">
</div>
<div class="input-group input-group-sm" style="width: auto;">
<span class="input-group-text">端口</span>
<input type="number" class="form-control" id="wmbus-down-ip-port" style="width:100px;" min="0" max="4294967295">
</div>
</div>
<textarea class="form-control form-control-sm" rows="2" id="wmbus-down-payload"
placeholder="载荷 HEX(不含CMD),按上方命令自动生成,可手动覆盖"></textarea>
<div class="small text-danger" id="wmbus-down-err"></div>
<div class="d-flex flex-wrap align-items-center gap-2 mt-1">
<button class="btn btn-sm btn-outline-secondary" id="wmbus-down-build">生成HEX</button>
<button class="btn btn-sm btn-primary flex-grow-1" id="wmbus-down-send">立即下发</button>
</div>
</div>
<div class="protocol-down-card mb-2" id="cjt188-down-card" style="display:none;">
<h6 class="pane-section-title"><i class="bi bi-arrow-down-circle"></i> <span id="cjt188-down-title">188协议下行下发</span></h6>
<p class="sk-rw-hint mb-2">68 10 表号7B 控制码 数据长度 数据标识 序号 数据 校验和 16 · 工具扮演平台/主机角色</p>
<div class="d-flex flex-wrap align-items-center mb-2 gap-2">
<div class="input-group input-group-sm" style="width: auto; min-width: 16rem; flex: 1 1 16rem;">
<span class="input-group-text">表号(7B HEX)</span>
<input type="text" class="form-control" id="cjt188-down-addr" title="全 AA = 广播,所有设备都会应答" placeholder="AA AA AA AA AA AA AA = 广播(所有设备都会应答)">
<button class="btn btn-outline-secondary" type="button" id="cjt188-down-addr-reset" title="重置为广播地址(全 AA)"><i class="bi bi-arrow-repeat"></i></button>
</div>
</div>
<div class="d-flex flex-wrap align-items-center mb-2 gap-2">
<div class="input-group input-group-sm" style="width: auto;">
<span class="input-group-text">序号</span>
<input type="number" class="form-control" id="cjt188-down-seq" min="0" max="255" style="width:5rem;" title="每次生成HEX/立即下发后自动+1(可手动改)">
</div>
<label class="ctl-chip" title="帧头前发送 FE FE FE, 不参与校验和">
<input type="checkbox" id="cjt188-down-preamble" checked>
<span>前导码 FE FE FE</span>
</label>
<div class="input-group input-group-sm" style="width: auto;">
<span class="input-group-text">功能码</span>
<select class="form-select" id="cjt188-down-cmd">
<option value="0x01" selected>0x01 读数据</option>
<option value="0x03">0x03 读表号</option>
<option value="0x04">0x04 阀控</option>
<option value="time" title="CJ/T 188 写标准时间, 地址全 AA 为广播校时">0x04 写标准时间 (A015)</option>
<option value="0x15">0x15 写表号</option>
<option value="0x16">0x16 写底度</option>
</select>
</div>
</div>
<div class="input-group input-group-sm mb-2" id="cjt188-down-param-group" style="display:none;">
<span class="input-group-text" id="cjt188-down-param-label">参数</span>
<input type="text" class="form-control" id="cjt188-down-param-val" style="width:120px;" placeholder="值">
<select class="form-select" id="cjt188-down-param-sel" style="display:none;"></select>
</div>
<div class="small text-danger" id="cjt188-down-err"></div>
<div class="d-flex flex-wrap align-items-center gap-2 mt-1">
<button class="btn btn-sm btn-outline-secondary" id="cjt188-down-build">生成HEX</button>
<button class="btn btn-sm btn-primary flex-grow-1" id="cjt188-down-send">立即下发</button>
</div>
<textarea class="form-control form-control-sm mt-1 sk-hex-copyable" rows="2" id="cjt188-down-preview"
readonly placeholder="待发送的完整 HEX(空格分隔,点击可复制)" title="点击复制生成的 HEX"></textarea>
</div>
<div class="protocol-down-card mb-2 sk-rw-card" id="sk-rw-card" data-dock-own="rw">
<h6 class="pane-section-title">
<i class="bi bi-shuffle"></i> 随机读写测试
<span class="sk-rw-proto-badge" id="sk-rw-proto-name">SEK</span>
</h6>
<p class="sk-rw-hint mb-2" id="sk-rw-case-hint">跟随上方协议 · seed=随机序列种子</p>
<div class="sk-rw-controls d-flex flex-wrap gap-2 mb-2">
<div class="input-group input-group-sm" style="width:auto;">
<span class="input-group-text">步数</span>
<input type="number" class="form-control" id="sk-rw-steps" value="40" min="5" max="500" style="width:4.5rem;" title="随机指令步数">
</div>
<div class="input-group input-group-sm" style="width:auto;">
<span class="input-group-text">间隔ms</span>
<input type="number" class="form-control" id="sk-rw-gap" value="200" min="0" max="10000" style="width:5rem;">
</div>
<div class="input-group input-group-sm" style="width:auto;">
<span class="input-group-text">超时ms</span>
<input type="number" class="form-control" id="sk-rw-timeout" value="5000" min="1000" max="60000" style="width:5.5rem;">
</div>
<div class="input-group input-group-sm" style="width:auto;" title="伪随机种子:相同 seed 生成相同指令序列,便于复现问题;留空自动生成">
<span class="input-group-text">seed</span>
<input type="number" class="form-control" id="sk-rw-seed" placeholder="自动" style="width:6rem;">
</div>
</div>
<div class="d-flex flex-wrap align-items-center gap-3 mb-2">
<div class="form-check form-switch ctl-switch">
<input class="form-check-input" type="checkbox" id="sk-rw-continue" checked>
<label class="form-check-label" for="sk-rw-continue">失败继续</label>
</div>
<div class="form-check form-switch ctl-switch">
<input class="form-check-input" type="checkbox" id="sk-rw-restore" checked>
<label class="form-check-label" for="sk-rw-restore">写后恢复原值</label>
</div>
</div>
<div class="d-flex gap-2 mb-2">
<button class="btn btn-sm btn-primary flex-grow-1" id="sk-rw-start">开始测试</button>
<button class="btn btn-sm btn-outline-danger" id="sk-rw-stop" disabled>停止</button>
</div>
<div class="sk-rw-progress mb-1">
<div class="sk-rw-progress-bar" id="sk-rw-progress-bar">0/0</div>
</div>
<div class="sk-rw-status" id="sk-rw-status">就绪</div>
<div class="sk-rw-summary" id="sk-rw-summary"></div>
<div class="sk-rw-log" id="sk-rw-log"></div>
</div>
<div class="protocol-down-card mb-2 sk-rw-card" id="sk-batch-card" data-dock-own="batch">
<h6 class="pane-section-title">
<i class="bi bi-list-check"></i> 批量配置写入
<span class="sk-rw-proto-badge" id="sk-batch-proto-name">SEK</span>
</h6>
<p class="sk-rw-hint mb-2">从常用指令预设中添加配置项, 组成清单按顺序逐条下发并整包回读确认 · 仅 SEK 协议</p>
<div class="d-flex flex-wrap align-items-center gap-2 mb-2">
<div class="input-group input-group-sm" style="width:auto;">
<span class="input-group-text">添加配置</span>
<select class="form-select" id="sk-batch-preset-select" style="min-width:12rem;"></select>
</div>
<button class="btn btn-sm btn-outline-secondary" id="sk-batch-add">添加</button>
</div>
<div class="sk-batch-list" id="sk-batch-list"></div>
<div class="d-flex flex-wrap align-items-center gap-3 mb-2 mt-2">
<div class="input-group input-group-sm" style="width:auto;">
<span class="input-group-text">超时ms</span>
<input type="number" class="form-control" id="sk-batch-timeout" value="5000" min="1000" max="60000" style="width:5.5rem;">
</div>
<div class="input-group input-group-sm" style="width:auto;">
<span class="input-group-text">间隔ms</span>
<input type="number" class="form-control" id="sk-batch-gap" value="200" min="0" max="10000" style="width:5rem;">
</div>
<div class="form-check form-switch ctl-switch">
<input class="form-check-input" type="checkbox" id="sk-batch-continue" checked>
<label class="form-check-label" for="sk-batch-continue">失败继续</label>
</div>
</div>
<div class="d-flex gap-2 mb-2">
<button class="btn btn-sm btn-primary flex-grow-1" id="sk-batch-primary">写入并确认下一台</button>
<button class="btn btn-sm btn-outline-danger" id="sk-batch-stop" disabled>停止</button>
<button class="btn btn-sm btn-outline-secondary" id="sk-batch-reset">重置计数</button>
</div>
<div class="sk-rw-progress mb-1">
<div class="sk-rw-progress-bar" id="sk-batch-progress-bar">0/0</div>
</div>
<div class="sk-rw-status" id="sk-batch-status">就绪</div>
<div class="sk-rw-summary" id="sk-batch-summary"></div>
<div class="sk-batch-readback" id="sk-batch-readback"></div>
<div class="sk-rw-log" id="sk-batch-log"></div>
</div>
</div>
<div class="tab-pane d-flex flex-column" id="nav-firmware" role="tabpanel">
<div class="d-flex flex-column flex-grow-1 min-h-0">
<div class="fw-upgrade-card">
<h6 class="pane-section-title"><i class="bi bi-file-earmark-binary"></i> 固件文件</h6>
<input type="file" id="fw-file" class="d-none" accept=".bin,.hex,.fw">
<div class="fw-upgrade-dropzone" id="fw-drop-zone">
<div class="fw-upgrade-dropzone-inner">
<i class="bi bi-cloud-upload fw-upgrade-drop-icon"></i>
<div class="fw-upgrade-drop-text">拖拽固件文件到此处</div>
<div class="fw-upgrade-drop-hint">或 <button class="fw-drop-browse" id="fw-select">点击浏览</button></div>
</div>
</div>
<div class="fw-upgrade-file d-none" id="fw-file-card">
<i class="bi bi-file-earmark-binary fw-upgrade-file-icon"></i>
<div class="fw-upgrade-file-detail">