-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathindex.html
More file actions
2318 lines (1999 loc) · 132 KB
/
index.html
File metadata and controls
2318 lines (1999 loc) · 132 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>
<title>ZAPBOX⚡️WEB INSTALLER</title>
<!-- Primary Meta Tags -->
<meta name="description" content="Flash & config your ZapBox from the browser">
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0" />
<!-- Open Graph -->
<meta property="og:locale" content="en_US" />
<meta property="og:type" content="website" />
<meta property="og:title" content="ZAPBOX⚡️WEB INSTALLER" />
<meta property="og:description"
content="Flash & config your ZapBox from the browser" />
<meta property="og:url" content="https://installer.zapbox.space/" />
<meta property="og:image" content="https://installer.zapbox.space/assets/zapbox-social.webp" />
<!-- Twitter Meta Tags -->
<meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:url" content="https://installer.zapbox.space">
<meta name="twitter:title" content="ZAPBOX⚡️WEB INSTALLER" />
<meta name="twitter:description" content="Flash & config your ZapBox from the browser">
<meta name="twitter:image" content="https://installer.zapbox.space/assets/zapbox-social.webp" />
<link rel="icon" href="./assets/favicon.webp" sizes="32x32">
<link rel="stylesheet" href="./assets/style.css">
<script src="./assets/serial.js?v=2"></script>
<script src="./assets/config-common.js"></script>
<script type="module" src="https://unpkg.com/esp-web-tools@9/dist/web/install-button.js?module"></script>
</head>
<body>
<!-- Image Popup Modal -->
<div id="help-image-modal" class="modal" onclick="closeHelpModal()">
<div class="modal-content">
<img src="./assets/Help-DeviceString.webp" alt="Help - Device String" onclick="closeHelpModal()">
</div>
</div>
<div class="wrapper">
<noscript>
<p>
I'm sorry! For this to actully work you have to enable JavaScript in your browser.
</p>
</noscript>
<h1 style="margin-bottom: 5px;">ZAPBOX⚡️WEB INSTALLER (with Display)</h1>
<p style="margin: 10px 0 0 0; color: #666;"><i>Use with LILYGO T-Display-S3. Headless version <a href="./headless/">see here</a>.</i></p>
<div style="float: right; text-align: center; margin: 0 0 10px 20px;">
<a href="./assets/e-layout-viewer.html" target="_blank">
<img src="./assets/E-Layout-ZapBox-Compact.webp" alt="E-Layout ZapBox Compact" style="max-width: 167px; width: 100%; border-radius: 6px; display: block;">
</a>
<p style="margin: 4px 0 0 0; font-size: 13px; color: #666;">Example E-Layout</p>
<p style="margin: 4px 0 0 0; font-size: 13px;"><a href="https://github.com/AxelHamburch/ZapBox?tab=readme-ov-file#electrical-layout" target="_blank" rel="noopener noreferrer">More e-layouts</a></p>
</div>
<div style="margin-bottom: 30px;"></div>
<h2 id="flash">1- Flash firmware</h2>
<ul>
<li>Connect your ZapBox to the front USB port</li>
<li>Leave the latest firmware version selected</li>
<li>Press <span style="background-color: #e0e0e0; padding: 2px 6px; border-radius: 3px;">🔥 Flash</span> - connect and follow screen instructions</li>
</ul>
<!-- ##### Version selector and flash button ##### -->
<div class="buttons" style="display: flex; gap: 10px; align-items: center; margin-top: 5px;">
<select id="firmware-version" style="padding: 10px; font-size: 16px; border-radius: 4px;">
<option value="./firmware/v946837/manifest.json">v946837 (Latest - ✨ Features & 🛠️ Bug Fixes)</option>
<option value="./firmware/v945816/manifest.json">v945816 (NFC both-boltcard mode & NFC auto-reset ⚡)</option>
<option value="./firmware/v944600/manifest.json">v944600 (More sensor functionality 🏪⚙️)</option>
<option value="./firmware/v944382/manifest.json">v944382 (Monitoring Product Blockage for vending machines 🏪)</option>
<option value="./firmware/v944368/manifest.json">v944368 (NFC card emulation & Bolt Card improvements ⚡)</option>
<option value="./firmware/v943679/manifest.json">v943679 (Servo OFA mode & 404 auto-retry ⚙️)</option>
<option value="./firmware/v943376/manifest.json">v943376 (NFC retry & config read fix 🛠️)</option>
<option value="./firmware/v941575/manifest.json">v941575 (Servo motor support ⚙️)</option>
<option value="./firmware/v940645/manifest.json">v940645 (Action time countdown ⏱️)</option>
<option value="./firmware/v940411/manifest.json">v940411 (New themes & UI improvements⚡)</option>
<option value="./firmware/v939873/manifest.json">v939873 (NFC UX improvements & navy-white theme)</option>
<option value="./firmware/v939845/manifest.json">v939845 (Light sleep, split timers & combined power modes)</option>
<option value="./firmware/v939756/manifest.json">v939756 (NFC error details & dynamic font sizing)</option>
<option value="./firmware/v939296/manifest.json">v939296 (Display thread-safety & NFC fixes 🛡️)</option>
<option value="./firmware/v938908/manifest.json">v938908 (NTAG21x/LNURL NFC tag support 🎉)</option>
<option value="./firmware/v938689/manifest.json">v938689 (NFC & serial connection fix 🛠️)</option>
<option value="./firmware/v938617/manifest.json">v938617 (NFC Bolt Card support & zapbox_extension 🎉)</option>
<option value="./firmware/v938321/manifest.json">v938321 (Add ambient lighting switch for channel 4)</option>
<option value="./firmware/v937549/manifest.json">v937549 (Bug fixes & remove Light Sleep mode)</option>
<option value="./firmware/v936746/manifest.json">v936746 (Advanced error detection)</option>
<option value="./firmware/v936258/manifest.json">v936258 (Add option vending machine with light barrier)</option>
<option value="./firmware/v933714/manifest.json">v933714 (Add "ZapBox ready! 🎉" log status)</option>
<option value="./firmware/v933545/manifest.json">v933545 (Display fixes & payment queue system)</option>
<option value="./firmware/v930750/manifest.json">v930750 (White Paper 📄 Release)</option>
<option value="./firmware/v930331/manifest.json">v930331 (Gold theme, navigation fix, BTC retry logic)</option>
<option value="./firmware/v930053/manifest.json">v930053 (Bug fixes, improvements and BTC ORANGE theme)</option>
<option value="./firmware/v929880/manifest.json">v929880 (Code Refactoring Release 🔧)</option>
<option value="./firmware/v929761/manifest.json">v929761 (Add bug fixes and external LED button support 🔌💡)</option>
<option value="./firmware/v929303/manifest.json">v929303 (Add BTC-Ticker 🚀 and bug fixes)</option>
<option value="./firmware/v929126/manifest.json">v929126 (Add Touch control and Multi-Channel-Control Mode)</option>
<option value="./firmware/v928945/manifest.json">v928945 (Proactive error detection and optimized startup)</option>
<option value="./firmware/v928559/manifest.json">v928559 (Small fixes and add BLACK & LIGHT GREY color theme)</option>
<option value="./firmware/v927737/manifest.json">v927737 (Core dump support + fix config mode after deep sleep)</option>
<option value="./firmware/v927726/manifest.json">v927726 (Add power saving modes with screensaver and deep sleep)</option>
<option value="./firmware/v927292/manifest.json">v927292 (Add 4-step error handling and more cool stuff 🤙)</option>
<option value="./firmware/v926800/manifest.json">v926800 (Add options for color and special mode (blinking, etc.))</option>
<option value="./firmware/v926705/manifest.json">v926705 (Add Threshold Mode with configurable payment trigger)</option>
<option value="./firmware/v926666/manifest.json">v926666 (Add wss:// support for LNbits v1.3.1 and bS_extension v1.1.2)</option>
<option value="./firmware/v926561/manifest.json">v926561 (Fix some bugs and edit documentation)</option>
<option value="./firmware/v926531/manifest.json">v926531 (Initial Release - LNbits v0.12.12 / LNURLdevice v.0.6.9)</option>
</select>
<esp-web-install-button id="flash-button" manifest="./firmware/v946837/manifest.json?_t=0">
<button slot="activate">🔥 Flash</button>
<span slot="unsupported">Your browser does not support installing things on ESP devices. Use Google Chrome or Microsoft Edge.</span>
<span slot="not-allowed">Ah snap, you are not allowed to use this on HTTP!</span>
</esp-web-install-button>
</div>
<p><i><strong>Note:</strong> The configuration data can be deleted using "Erase device." Otherwise, it will be retained even with the new firmware. LOGS & CONSOLE is no needed.</i></p>
<script>
// Append timestamp to manifest URL to prevent browser caching of manifest + firmware files
function nocacheManifest(url) {
return url.split('?')[0] + '?_t=' + Date.now();
}
// Set cache-busting timestamp on initial load
(function() {
const flashButton = document.getElementById('flash-button');
const sel = document.getElementById('firmware-version');
flashButton.setAttribute('manifest', nocacheManifest(sel.value));
})();
// Update manifest (with cache buster) when version is changed
document.getElementById('firmware-version').addEventListener('change', function(e) {
const flashButton = document.getElementById('flash-button');
const url = nocacheManifest(e.target.value);
flashButton.setAttribute('manifest', url);
console.log('Manifest changed to:', url);
});
</script>
<hr>
<h2 id="prepare-connection">2- Prepare connection</h2>
<p>When you flash a new ESP32 or use “Erase device.” the ZapBox automatically starts in configuration mode and displays the following message: “CONF / SERIAL CONFIG MODE.”</p>
<p>→ The ZapBox is now ready to receive configuration data. ⚙️</p>
<p>If parameters have already been stored and need to be changed, you can also open CONF mode retrospectively:</p>
<ul>
<li>Press and hold NEXT or BOOT button for 5 seconds.</li>
<li>Press the LED button once briefly and then hold it down for at least 5 seconds.</li>
<li>On a touchscreen, touch the HELP button at least 5 times in quick succession.</li>
</ul>
<p><i><strong>Note:</strong> The connection will remain active for 3 minutes (180 seconds) before it is automatically disconnected. If data is already available. You can terminate the connection prematurely by pressing the NEXT button, touching the display, or pressing the external button. </i></p>
<hr>
<h2 id="load-config">3- Load config values</h2>
<ul>
<li>Press <span style="background-color: #e0e0e0; padding: 2px 6px; border-radius: 3px;">🔌 Connect</span> - select the connection and connect.</li>
<li>Check whether the ZapBox is in config mode.⚙️✅</li>
<li>If values are already available, press <span style="background-color: #e0e0e0; padding: 2px 6px; border-radius: 3px;">📖 Read Config</span>.</li>
<li>Define your configuration.📝 At least Wi-Fi and device data.</li>
<li>Press <span style="background-color: #e0e0e0; padding: 2px 6px; border-radius: 3px;">🔥 Write Config</span> to write the configuration.</li>
<li>Press <span style="background-color: #e0e0e0; padding: 2px 6px; border-radius: 3px;">🔄 Restart</span> to restart while staying connected.</li>
<li>Press <span style="background-color: #e0e0e0; padding: 2px 6px; border-radius: 3px;">🔌 Disconnect</span> or refresh website to close connection.</li>
</ul>
<div class="buttons">
<button id="connect-button" onclick="onConnectButtonClick()">🔌 Connect</button>
<button id="read-config-button" onclick="onReadConfigButtonClick()" disabled>📖 Read Config</button>
<button id="write-config-button" onclick="onWriteConfigButtonClick()" disabled>🔥 Write Config</button>
<button id="soft-reset-button" onclick="onSoftResetButtonClick()" disabled>🔄 Restart</button>
<button id="restart-button" onclick="onRestartButtonClick()" disabled>🔌 Disconnect</button>
</div>
<div class="input-wrapper" style="background-color: #CCFFCC; padding: 20px; margin: 20px -20px; border-radius: 8px;">
<div>
<label for="ssid">WiFi SSID</label>
<input type="text" name="ssid" id="ssid" placeholder="SSID" autocomplete="off">
</div>
<div>
<label for="wifiPassword">WiFi password</label>
<input type="text" name="wifiPassword" id="wifiPassword" placeholder="password" autocomplete="off">
</div>
<div style="display: flex; gap: 20px; align-items: flex-start; grid-column: span 2;">
<div style="flex: 2;">
<label for="socket">Device settings string</label>
<input type="text" name="socket" id="socket" placeholder="ws://demo.lnbits.com/ap..." autocomplete="off">
<p style="margin: -13px 0 0 0; font-size: 11px; color: #888; font-style: italic;">How do I get the device string? Use Bitcoin Switch or ZapBox extension → <a href="#" id="help-device-string-link" style="color: #888; text-decoration: underline;">Help</a></p>
</div>
<div style="flex: 1;">
<label>Status</label>
<div style="display: flex; flex-direction: column; gap: 8px; margin-top: 10px;">
<div id="connection-status" style="padding: 6px 14px; border-radius: 5px; background-color: #e0e0e0; color: #888; font-weight: bold; white-space: nowrap; font-size: 13px;">❌ Not connected</div>
<div id="config-mode-status" style="padding: 6px 14px; border-radius: 5px; background-color: #e0e0e0; color: #888; font-weight: bold; white-space: nowrap; font-size: 13px;">❌ No config mode</div>
</div>
</div>
</div>
</div>
<div class="debug-console" id="consoleDebug" style="margin-top: 20px; margin-bottom: 20px;"></div>
<!-- Export & Save Section -->
<div style="background-color: #E8E8E8; padding: 20px; margin: 20px -20px; border-radius: 8px;">
<div style="display: flex; align-items: center; gap: 16px; margin-bottom: 12px;">
<span style="font-weight: 600; white-space: nowrap;">Export log files:</span>
<button onclick="printLogs()" style="padding: 10px 18px; font-size: 15px; background: #f7931a; color: #fff; border: none; border-radius: 5px; cursor: pointer; white-space: nowrap;">🖨️ Print Logs</button>
<button onclick="downloadLogs()" style="padding: 10px 18px; font-size: 15px; background: #555; color: #fff; border: none; border-radius: 5px; cursor: pointer; white-space: nowrap;">💾 Download .txt</button>
</div>
<div style="display: flex; align-items: center; gap: 16px;">
<span style="font-weight: 600; white-space: nowrap;">Save your settings:</span>
<button onclick="printConfig()" style="padding: 10px 18px; font-size: 15px; background: #f7931a; color: #fff; border: none; border-radius: 5px; cursor: pointer; white-space: nowrap;">🖨️ Print Config</button>
<div>
<label style="display: flex; align-items: center; gap: 6px; font-size: 13px; cursor: pointer; font-weight: normal; margin-bottom: 8px;">
<input type="checkbox" id="print-wifi" style="display: inline-block; width: auto; margin: 0; padding: 0; flex-shrink: 0;"> Print WiFi data
</label>
<label style="display: flex; align-items: center; gap: 6px; font-size: 13px; cursor: pointer; font-weight: normal;">
<input type="checkbox" id="print-device-string" style="display: inline-block; width: auto; margin: 0; padding: 0; flex-shrink: 0;"> Print Device settings string
</label>
</div>
</div>
</div>
<!-- Optional settings and functions Section -->
<div style="background-color: #FFE5CC; padding: 30px 20px; margin: 15px -20px 0 -20px; border-radius: 8px 8px 0 0;">
<div style="display: flex; align-items: center; gap: 15px; margin-bottom: -10px;">
<h3 style="margin: 0; color: #000; font-weight: bold; white-space: nowrap;">Optional settings and functions</h3>
<hr style="flex: 1; border: none; border-top: 1px solid #888; margin: 0;">
</div>
<div class="input-wrapper">
<div>
<label for="theme">Display color options</label>
<select name="theme" id="theme">
<option value="btcorange-black">BTC ORANGE & BLACK (inv. QR-Code)</option>
<option value="zapbox">GOLD & BLACK (inv. QR-Code)</option>
<option value="black-white" selected>BLACK & WHITE (default)</option>
<option value="black-lightgrey">BLACK & LIGHT GREY (for dark environments)</option>
<option value="black-darkgrey">BLACK & DARK GREY (for very dark environments)</option>
<option value="darkgreen-lightgrey">DARKGREEN & LIGHTGREY (scan-friendly)</option>
<option value="darkblue-lightgrey">DARKBLUE & LIGHTGREY (scan-friendly)</option>
<option value="white-navy">WHITE & NAVY (inv. QR-Code)</option>
<option value="black-blue">BLACK & BLUE</option>
<option value="black-btcorange">BLACK & BTC ORANGE</option>
<option value="black-darkcyan">BLACK & DARKCYAN</option>
<option value="black-green">BLACK & GREEN</option>
<option value="black-olive">BLACK & OLIVE</option>
<option value="black-orange">BLACK & ORANGE</option>
<option value="black-red">BLACK & RED</option>
<option value="brown-orange">BROWN & ORANGE</option>
<option value="darkcyan-cyan">DARKCYAN & CYAN</option>
<option value="darkgrey-btcorange">DARK GREY & BTC ORANGE</option>
<option value="darkgreen-green">DARKGREEN & GREEN</option>
<option value="maroon-magenta">MAROON & MAGENTA</option>
<option value="orange-brown">ORANGE & BROWN</option>
<option value="red-green">RED & GREEN</option>
<option value="white-darkcyan">WHITE & DARKCYAN</option>
</select>
</div>
<div>
<label for="orientation">Screen orientation</label>
<select name="orientation" id="orientation">
<option value="h" selected>Horizontal (default)</option>
<option value="v">Vertical</option>
<option value="hi">Horizontal (inverse)</option>
<option value="vi">Vertical (inverse)</option>
</select>
</div>
<div>
<label for="qrFormat">QR-Code Format</label>
<select name="qrFormat" id="qrFormat">
<option value="bech32" selected>LNURL in bech32 format (default)</option>
<option value="lud17">LNURL in LUD-17 format (raw)</option>
</select>
</div>
</div>
<p style="margin-top: 5px; margin-bottom: 5px; color: #888;"><i><strong>Note:</strong> The default is Bech32. LUD-17 is easier to scan, but is not yet supported by all types of wallets.</i></p>
<div class="input-wrapper">
<div>
<label for="externalButton">LED button</label>
<select name="externalButton" id="externalButton">
<option value="no" selected>No (default)</option>
<option value="yes">Yes</option>
</select>
</div>
</div>
<p style="margin-top: 10px; color: #888;"><i><strong>Note:</strong> The LED button YES function hides the texts HELP and NEXT on the display.</i></p>
<p style="margin-top: 10px; color: #888; margin-left: 20px;">
<i>Function of the LED button - press: <br><br>
• once => Change display / product<br>
• once and hold => Help page<br>
• several times => Report page<br>
• once briefly and then hold at least 5 seconds => CONF page</i>
</p>
<!-- Section Separator -->
<hr style="margin: 40px 0 30px 0; border: none; border-top: 1px solid #888;">
<div class="input-wrapper">
<div style="grid-column: span 2; display: flex; gap: 20px; align-items: center;">
<label for="multiControl" style="flex: 0 0 15%; margin: 0;">ZapBox Mode</label>
<select name="multiControl" id="multiControl" style="flex: 1; margin: 0;">
<option value="off" selected>Compact ( 1 channel - pin 12 [& 13] - default)</option>
<option value="duo">Duo (2 channels - pin 12/13)</option>
<option value="quattro">Quattro & ZapOMat (4 channels - pin 12/13/10/11)</option>
<option value="servo">Servo (4 channels - 2x relay (pin 12/11) & 2x servo (pin 13/10))</option>
</select>
</div>
</div>
<p style="margin-top: 10px; color: #888;"><i><strong>ZapBox Mode:</strong> Activate up to 4 GPIO pins (12, 13, 10, 11) with unique QR codes. Product labels and LNURLs are automatically retrieved from your LNbits switch configuration. Use NEXT button, touch gestures (swipe ←→) or external button (if available) to navigate between products. Servo mode requires additional settings. See below.</i></p>
<p style="margin-top: 10px; color: #888;"><i><strong>Note:</strong> The Threshold function is compatible with the default Compact (1 channel) mode and with Servo mode when using "One for All" activation option.</i></p>
<p style="margin-top: 10px; color: #888;"><i><strong>-> To enable the servo configuration, please select the ZapBox Mode - Servo</strong> </i></p>
<!-- Servo Configuration (visible only in servo mode) -->
<div id="servoConfigSection" style="margin-top: 15px; padding: 12px; border: 1px solid #555; border-radius: 6px;">
<p style="margin: 0 0 10px 0; color: #ccc; font-weight: bold;">Servo Configuration</p>
<p style="margin: 0 0 15px 0; color: #888; font-size: 11px;"><i><strong>Up to 4 channels can be controlled.</strong> <br><br>
<strong>Pin 12:</strong> Relay (on/off, standard)<br>
<strong>Pin 13:</strong> Configurable (off/relay/180° servo/360° servo)<br>
<strong>Pin 10:</strong> Configurable (off/relay/180° servo/360° servo)<br>
<strong>Pin 11:</strong> Relay (on/off, also usable for ambient lighting)<br><br>
The "One for All" function allows all channels to be triggered simultaneously. To do this, you only need to configure pin 12 and, if necessary, a servo. In addition, the channels can also be configured and activated individually. To do this, the pins must be configured in LNbits and the servos must be set up. </i></p>
<!-- Relay activation -->
<div class="input-wrapper">
<div>
<label for="servoRelayMode">Activation options</label>
<select name="servoRelayMode" id="servoRelayMode" disabled>
<option value="one-for-all" selected>One for All – all channels triggered by Pin 12 (default)</option>
<option value="relay1">Relay 1 (Pin 12) active</option>
<option value="both">Relay 1 (Pin 12) and Relay 2 (Pin 11) active</option>
<option value="off">Relays deactive (Activate at least one servo)</option>
</select>
<p style="margin: -13px 0 0 0; font-size: 11px; color: #888; font-style: italic;">The servos are activated by setting a parameter value ≠ 0.</p>
</div>
<div id="ofaNoteDiv" style="display: flex; align-items: flex-start;">
<p style="margin: 0; padding-left: 10px; font-size: 11px; color: #888; font-style: italic; border-left: 3px solid #888;"><strong>Note on One-for-All Mode:</strong> If only GPIO pin 12 is configured in LNbits, all other outputs will switch using the same timing. However, once a GPIO pin (13, 10, or 11) has its own configuration entry and therefore its own timing, this timing will be used for that pin independently of how pin 12 is switched.</p>
</div>
</div>
<!-- GPIO Pin 13 Mode Selection -->
<div class="input-wrapper">
<div>
<label for="pin13Mode">Mode GPIO Pin 13</label>
<select name="pin13Mode" id="pin13Mode" disabled>
<option value="off" selected>Off (default)</option>
<option value="servo180">180° servo (0°-180° positioning)</option>
<option value="servo360">360° servo (360° rotation)</option>
<option value="relay">Relay function (external relay)</option>
</select>
<p style="margin: -13px 0 0 0; font-size: 11px; color: #888; font-style: italic;">Select the function for GPIO Pin 13</p>
</div>
</div>
<!-- Pin 13 - 180° Servo Configuration -->
<div id="pin13Servo180Section" style="display: none; margin-top: 10px; padding: 12px; background: #f0f0f0; border-radius: 6px;">
<p style="margin: 0 0 10px 0; color: #333; font-weight: bold; font-size: 12px;">Pin 13 - 180° Servo Configuration</p>
<div class="input-wrapper">
<div>
<label for="pin13Servo180Start">Start angle (°)</label>
<input type="number" id="pin13Servo180Start" name="pin13Servo180Start" min="0" max="180" value="0" disabled>
<p style="margin: -13px 0 0 0; font-size: 11px; color: #888;">Positioning 0°–180° or 180°–0°</p>
</div>
<div>
<label for="pin13Servo180End">End angle (°)</label>
<input type="number" id="pin13Servo180End" name="pin13Servo180End" min="0" max="180" value="0" disabled>
</div>
<div>
<label for="pin13Servo180Duration">Sweep duration (ms)</label>
<input type="number" id="pin13Servo180Duration" name="pin13Servo180Duration" min="0" max="10000" value="0" disabled>
<p style="margin: -13px 0 0 0; font-size: 11px; color: #888; font-style: italic;">0 = max speed / 1000 = 1 second</p>
</div>
</div>
<p style="margin: 8px 0 0 0; font-size: 11px; color: #bbb; font-style: italic;">The 180° servo returns to start position after completing the switch function.</p>
</div>
<!-- Pin 13 - 360° Servo Configuration -->
<div id="pin13Servo360Section" style="display: none; margin-top: 10px; padding: 12px; background: #f0f0f0; border-radius: 6px;">
<p style="margin: 0 0 10px 0; color: #333; font-weight: bold; font-size: 12px;">Pin 13 - 360° Servo Configuration</p>
<div class="input-wrapper">
<div>
<label for="pin13Servo360Speed">Speed (0–180)</label>
<input type="number" id="pin13Servo360Speed" name="pin13Servo360Speed" min="0" max="180" value="0" disabled>
<p style="margin: -13px 0 0 0; font-size: 11px; color: #888;">90 = stop / <90 = CCW / >90 = CW</p>
</div>
<div>
<label for="pin13Servo360Duration">Spin duration (ms)</label>
<input type="number" id="pin13Servo360Duration" name="pin13Servo360Duration" min="0" max="10000" value="0" disabled>
<p style="margin: -13px 0 0 0; font-size: 11px; color: #888; font-style: italic;">0 = spin until action time ends</p>
</div>
</div>
</div>
<!-- GPIO Pin 10 Mode Selection -->
<div class="input-wrapper" style="margin-top: 15px;">
<div>
<label for="pin10Mode">Mode GPIO Pin 10</label>
<select name="pin10Mode" id="pin10Mode" disabled>
<option value="off" selected>Off (default)</option>
<option value="servo180">180° servo (0°-180° positioning)</option>
<option value="servo360">360° servo (360° rotation)</option>
<option value="relay">Relay function (external relay)</option>
</select>
<p style="margin: -13px 0 0 0; font-size: 11px; color: #888; font-style: italic;">Select the function for GPIO Pin 10</p>
</div>
</div>
<!-- Pin 10 - 180° Servo Configuration -->
<div id="pin10Servo180Section" style="display: none; margin-top: 10px; padding: 12px; background: #f0f0f0; border-radius: 6px;">
<p style="margin: 0 0 10px 0; color: #333; font-weight: bold; font-size: 12px;">Pin 10 - 180° Servo Configuration</p>
<div class="input-wrapper">
<div>
<label for="pin10Servo180Start">Start angle (°)</label>
<input type="number" id="pin10Servo180Start" name="pin10Servo180Start" min="0" max="180" value="0" disabled>
<p style="margin: -13px 0 0 0; font-size: 11px; color: #888;">Positioning 0°–180° or 180°–0°</p>
</div>
<div>
<label for="pin10Servo180End">End angle (°)</label>
<input type="number" id="pin10Servo180End" name="pin10Servo180End" min="0" max="180" value="0" disabled>
</div>
<div>
<label for="pin10Servo180Duration">Sweep duration (ms)</label>
<input type="number" id="pin10Servo180Duration" name="pin10Servo180Duration" min="0" max="10000" value="0" disabled>
<p style="margin: -13px 0 0 0; font-size: 11px; color: #888; font-style: italic;">0 = max speed / 1000 = 1 second</p>
</div>
</div>
<p style="margin: 8px 0 0 0; font-size: 11px; color: #bbb; font-style: italic;">The 180° servo returns to start position after completing the switch function.</p>
</div>
<!-- Pin 10 - 360° Servo Configuration -->
<div id="pin10Servo360Section" style="display: none; margin-top: 10px; padding: 12px; background: #f0f0f0; border-radius: 6px;">
<p style="margin: 0 0 10px 0; color: #333; font-weight: bold; font-size: 12px;">Pin 10 - 360° Servo Configuration</p>
<div class="input-wrapper">
<div>
<label for="pin10Servo360Speed">Speed (0–180)</label>
<input type="number" id="pin10Servo360Speed" name="pin10Servo360Speed" min="0" max="180" value="0" disabled>
<p style="margin: -13px 0 0 0; font-size: 11px; color: #888;">90 = stop / <90 = CCW / >90 = CW</p>
</div>
<div>
<label for="pin10Servo360Duration">Spin duration (ms)</label>
<input type="number" id="pin10Servo360Duration" name="pin10Servo360Duration" min="0" max="10000" value="0" disabled>
<p style="margin: -13px 0 0 0; font-size: 11px; color: #888; font-style: italic;">0 = spin until action time ends</p>
</div>
</div>
</div>
<p style="margin: 15px 0 0 0; font-size: 11px; color: #888; font-style: italic;">
Servos become active when their values are non-zero. "Relay function (external relay)" uses Pin 13/10 as a standard relay output — an external relay module must be connected. Special Mode (blink, pulse, etc.) works for relays (Pin 12/11) and relay-function pins (Pin 13/10), but not for servo pins.
</p>
</div>
<div class="input-wrapper">
<div>
<label for="lightBarrier">Special features for the vending machine</label>
<select name="lightBarrier" id="lightBarrier">
<option value="no" selected>No (default)</option>
<option value="yes">Stop the advance (Product ejected, slide stopped)</option>
<option value="monitor">Monitoring product blockage (Remove product to resume)</option>
<option value="level">Level Monitoring (empty bin)</option>
</select>
<p style="margin: -13px 0 0 0; font-size: 11px; color: #888; font-style: italic;">NPN input on Pin 2 (light barrier / limit switch)</p>
</div>
<div>
<label for="channel4Ambient">Channel 4 - Ambient lighting switch</label>
<select name="channel4Ambient" id="channel4Ambient">
<option value="normal" selected>Off (default)</option>
<option value="ambient">Ambient lighting switch (pin 11)</option>
</select>
<p style="margin: -13px 0 0 0; font-size: 11px; color: #888; font-style: italic;">Pin 11 is always ON, except in screensaver mode.</p>
</div>
</div>
<p style="margin-top: 10px; color: #888;"><i><strong>Notes:</strong><br>
<strong>Stop the advance:</strong> A trigger from e.g. a light barrier (NPN) stops the action after 2 seconds at the earliest.<br>
<strong>Monitoring product blockage:</strong> The (NPN) input (light barrier/limit switch) displays the message "PRODUCT IN THE EJECTOR - Remove the product" and no further payment is possible until the input is free again.<br>
<strong>Level monitoring:</strong> The (NPN) input monitors the fill level of a magazine or container. As long as Pin 2 is pulled LOW (sensor active), everything is fine. As soon as Pin 2 is HIGH (sensor free), all payment options are blocked and the message "THE SUPPLY BIN IS EMPTY - Please restock it" is displayed. Payments are re-enabled automatically once the sensor is active again.</i></p>
<p style="margin-top: 10px; color: #888;"><i><strong>Note on "Ambient lighting switch":</strong> With pre-selection enabled, channel 4 is switched depending on the display backlight. Whenever the display is on, channel 4 is activated, thus, for example, the lighting for a vending machine. If the display backlight switches off because the screensaver mode is active, channel 4 is also deactivated.</i></p>
<!-- Section Separator -->
<hr style="margin: 40px 0 30px 0; border: none; border-top: 1px solid #888;">
<!-- Special Mode Section -->
<div class="input-wrapper">
<div>
<label for="specialMode">Special Mode</label>
<select name="specialMode" id="specialMode">
<option value="standard" selected>Standard (simple on/off - default)</option>
<option value="blink">Blink (1 Hz, 1:1)</option>
<option value="pulse">Pulse (2 Hz, 1:4)</option>
<option value="fast-blink">Strobe (5 Hz, 1:1)</option>
<option value="custom">Custom (manual settings)</option>
</select>
<p style="margin: -13px 0 0 0; font-size: 11px; color: #888; font-style: italic;">Choose pulsing/blinking mode for relay control</p>
</div>
<div id="frequencyDiv" style="display: none;">
<label for="frequency">Frequency (Hz)</label>
<input type="number" name="frequency" id="frequency" placeholder="0.1 to 10" step="0.1" min="0.1" max="10" autocomplete="off">
<p style="margin: -13px 0 0 0; font-size: 11px; color: #888; font-style: italic;">Cycles per second (0.1 - 10 Hz)</p>
</div>
<div id="dutyCycleDiv" style="display: none;">
<label for="dutyCycleRatio">Duty Cycle Ratio</label>
<input type="number" name="dutyCycleRatio" id="dutyCycleRatio" placeholder="0.1 to 10" step="0.1" min="0.1" max="10" autocomplete="off">
<p style="margin: -13px 0 0 0; font-size: 11px; color: #888; font-style: italic;">ON:OFF ratio (0.25 = 1:4, 1.0 = 1:1, 2.0 = 2:1)</p>
</div>
</div>
<p style="margin-top: 20px; color: #888;"><i><strong>Note:</strong> Special modes control how the relay switches during the configured duration. "Standard" means simple on/off. Other modes create pulsing/blinking patterns with configurable frequency and duty cycle.</i></p>
<!-- Section Separator -->
<hr style="margin: 40px 0 30px 0; border: none; border-top: 1px solid #888;">
<!-- BTC-Ticker Section -->
<div class="input-wrapper">
<div>
<label for="btcTicker">BTC-Ticker Mode</label>
<select name="btcTicker" id="btcTicker">
<option value="off">OFF</option>
<option value="always">ON - always</option>
<option value="selecting" selected>ON - when selecting (default)</option>
</select>
<p style="margin: -13px 0 0 0; font-size: 11px; color: #888; font-style: italic;">Control when Bitcoin price/block info is shown</p>
</div>
<div>
<label for="currency">Currency (ISO Code)</label>
<input type="text" name="currency" id="currency" placeholder="USD" maxlength="3" autocomplete="off">
<p style="margin: -13px 0 0 0; font-size: 11px; color: #888; font-style: italic;">E.g. USD, EUR, GBP, JPY, CHF, TRY, CAD, BRL, CUP, etc.</p>
</div>
</div>
<p style="margin-top: 20px; color: #888;"><i><strong>OFF:</strong> No ticker shown (Duo/Quattro: productSelectionScreen; Single: only QR code)</i></p>
<p style="margin-top: 10px; color: #888;"><i><strong>ON - always:</strong> Ticker overlays screen, navigate with NEXT/swipe to show products temporarily (returns after timeout)</i></p>
<p style="margin-top: 10px; color: #888;"><i><strong>ON - when selecting:</strong> Single: Ticker only visible for a short time after NEXT/swipe. Duo/Quattro: productSelectionScreen → products → ticker (at end of product cycle)</i></p>
<!-- Section Separator -->
<hr style="margin: 40px 0 30px 0; border: none; border-top: 1px solid #888;">
<!-- Threshold Mode Section -->
<div class="input-wrapper">
<div>
<label for="thresholdKey">Threshold Mode - Wallet Invoice/Read Key</label>
<input type="text" name="thresholdKey" id="thresholdKey" placeholder="e.g. c9e68ab036.." autocomplete="off">
<p style="margin: -13px 0 0 0; font-size: 11px; color: #888; font-style: italic;">leave empty for normal mode (default)</p>
</div>
<div>
<label for="thresholdAmount">Threshold value in satoshi</label>
<input type="number" name="thresholdAmount" id="thresholdAmount" placeholder="e.g. 21" autocomplete="off">
<p style="margin: -13px 0 0 0; font-size: 11px; color: transparent; font-style: italic; user-select: none;">placeholder</p>
</div>
<div>
<label for="thresholdPin">GPIO pin to be controlled</label>
<input type="number" name="thresholdPin" id="thresholdPin" placeholder="e.g. 12" value="12" autocomplete="off">
<p style="margin: -13px 0 0 0; font-size: 11px; color: #888; font-style: italic;">12 (default)</p>
</div>
<div>
<label for="thresholdTime">Control time for GPIO pin (ms)</label>
<input type="number" name="thresholdTime" id="thresholdTime" placeholder="e.g. 3000" autocomplete="off">
</div>
<div>
<label for="thresholdLnurl">LNURL or LIGHTNING ADDRESS for QR-Code</label>
<input type="text" name="thresholdLnurl" id="thresholdLnurl" placeholder="LNURL or lightning address" autocomplete="off">
</div>
</div>
<p style="margin-top: 20px; color: #888;"><i><strong>Note:</strong> As soon as something is entered in the "Wallet Invoice/Read Key" field, "THRESHOLD MODE" becomes active. "NORMAL MODE" then no longer works. The data specified in the bitcoinSwitch extension, such as amount, PIN, and time, is ignored. Only a payment to the wallet with the invoice key counts.</i></p>
<p style="margin-top: 10px; color: #888;"><i><strong>How can I use this?</strong> Either by receiving payment directly into the wallet or by using the "Pay Links" extension. There you can get a static LNURL and even a lightning address. Payments to these addresses then trigger the pin, provided the threshold value is reached or exceeded.</i></p>
<!-- Section Separator -->
<hr style="margin: 40px 0 30px 0; border: none; border-top: 1px solid #888;">
<!-- Screensaver and Deep Sleep Section -->
<div class="input-wrapper">
<div>
<label for="screensaver">Screensaver</label>
<select name="screensaver" id="screensaver">
<option value="off" selected>OFF (default)</option>
<option value="backlight">ON (backlight off)</option>
</select>
</div>
<div>
<label for="activationTime">Time until activation - Screensaver</label>
<input type="number" name="activationTime" id="activationTime" min="1" max="120" placeholder="5" autocomplete="off">
<p style="margin: -13px 0 0 0; font-size: 11px; color: #888; font-style: italic;">1-120 minutes (default: 5)</p>
</div>
<div>
<label for="deepSleep">Deep sleep</label>
<select name="deepSleep" id="deepSleep">
<option value="off" selected>OFF (default)</option>
<option value="freeze">Freeze (deep sleep, ~0.01-0.15mA)</option>
<option value="light">Light sleep (~0.8-2mA, LED button wake-up)</option>
</select>
</div>
<div>
<label for="deepSleepTime">Time until activation - Deep sleep</label>
<input type="number" name="deepSleepTime" id="deepSleepTime" min="1" max="120" placeholder="30" autocomplete="off">
<p style="margin: -13px 0 0 0; font-size: 11px; color: #888; font-style: italic;">1-120 minutes (default: 30)</p>
</div>
</div>
<p style="margin-top: 20px; color: #888;"><i><strong>Use Cases:</strong> Energy saving (screensaver ~80-90%, light sleep ~99%, freeze ~99.9%), reducing device heat, extending display lifespan in always-on scenarios.</i></p>
<p style="margin-top: 10px; color: #888;"><i><strong>⚡ Light sleep</strong> is recommended for devices with an external LED button. In freeze mode, only BOOT and HELP buttons can wake the device.</i></p>
<p style="margin-top: 10px; color: #888;"><i><strong>Note:</strong> T-Display-S3 with touch, only the screensaver mode can be used. The ESP32 cannot be reactivated via touch while in Deep Sleep mode.</i></p>
<!-- Section Separator -->
<hr style="margin: 40px 0 30px 0; border: none; border-top: 1px solid #888;">
<!-- NFC Bolt Card Section -->
<div class="input-wrapper">
<div>
<label for="nfcMode">NFC mode - NFC options and support</label>
<select name="nfcMode" id="nfcMode">
<option value="boltcard" selected>NFC Bolt Cards support only (default)</option>
<option value="emulation">NFC card emulation for mobile phones only</option>
<option value="both">Both, standard mobile phones, Bolt Cards extra</option>
<option value="both-boltcard">Both, standard Bolt Cards, mobile phones extra</option>
<option value="off">NFC off</option>
</select>
</div>
</div>
<p style="margin-top: 5px; color: #888;"><i><b>NFC Bolt Cards</b> work reliably and securely with all major Lightning wallets and PoS systems. This is the recommended default.<br><br><b>NFC card emulation</b> (experimental): The ZapBox emulates an NFC tag so customers can pay by tapping their phone. Currently only supported by <a href="https://phoenix.acinq.co/" target="_blank" rel="noopener noreferrer">Phoenix Wallet</a>, and even then, only with certain limitations. Other wallets may not even recognize the NFC tag. For best compatibility, use the QR code format <b>"LNURL in LUD-17"</b> instead.</i></p>
<p style="margin-top: 5px; color: #888;"><i>The NFC Bolt Card functionality requires the LNbits extension "<a href="https://github.com/AxelHamburch/zapbox_extension" target="_blank" rel="noopener noreferrer">zapbox_extension</a>". The ZapBox automatically detects which extension is installed on your server – no manual configuration required. If "zapbox_extension" is active, NFC Bolt Card payments are available out of the box. If only "bitcoinswitch_extension" (Bitcoin Switch) is installed, the ZapBox switches to that automatically.</i></p>
<p style="margin-top: 10px; color: #888;"><i>Repositories from where the extensions can be downloaded:<br><a href="https://installer.zapbox.space/extensions.json" target="_blank" rel="noopener noreferrer">https://installer.zapbox.space/extensions.json</a></i></p>
<a href="./assets/NFC-Module-PN532.webp" target="_blank">
<img class="center-img" src="./assets/NFC-Module-PN532.webp" alt="PN532 NFC Module wiring" />
</a>
</div>
<!-- Error Detection Section -->
<div style="background-color: #FFFFCC; padding: 30px 20px; margin: 0 -20px;">
<h2 id="error-detection">Error Detection & Report</h2>
<p>The ZapBox features a hierarchical error detection system with automatic diagnostics. Errors are displayed on screen with abbreviations:</p>
<table style="width: 100%; border-collapse: collapse; margin: 20px 0;">
<thead>
<tr style="background-color: #f0f0f0;">
<th style="border: 1px solid #ddd; padding: 8px; text-align: left;">Priority</th>
<th style="border: 1px solid #ddd; padding: 8px; text-align: left;">Error Type</th>
<th style="border: 1px solid #ddd; padding: 8px; text-align: left;">Abbreviation</th>
<th style="border: 1px solid #ddd; padding: 8px; text-align: left;">Description</th>
</tr>
</thead>
<tbody>
<tr>
<td style="border: 1px solid #ddd; padding: 8px;">1 (Highest)</td>
<td style="border: 1px solid #ddd; padding: 8px; white-space: nowrap;"><strong>NO WIFI</strong></td>
<td style="border: 1px solid #ddd; padding: 8px;">NW</td>
<td style="border: 1px solid #ddd; padding: 8px;">WiFi network not connected<br>→ Wifi data correct?<br>→ WiFi signal too weak?</td>
</tr>
<tr style="background-color: #f9f9f9;">
<td style="border: 1px solid #ddd; padding: 8px;">2</td>
<td style="border: 1px solid #ddd; padding: 8px; white-space: nowrap;"><strong>NO INTERNET</strong></td>
<td style="border: 1px solid #ddd; padding: 8px;">NI</td>
<td style="border: 1px solid #ddd; padding: 8px;">Internet connectivity lost<br>→ Internet accessible?</td>
</tr>
<tr>
<td style="border: 1px solid #ddd; padding: 8px;">3</td>
<td style="border: 1px solid #ddd; padding: 8px; white-space: nowrap;"><strong>NO SERVER</strong></td>
<td style="border: 1px solid #ddd; padding: 8px;">NS</td>
<td style="border: 1px solid #ddd; padding: 8px;">LNbits server unreachable<br>-> Server hardware down?<br>-> Device string correct?</td>
</tr>
<tr style="background-color: #f9f9f9;">
<td style="border: 1px solid #ddd; padding: 8px;">4 (Lowest)</td>
<td style="border: 1px solid #ddd; padding: 8px; white-space: nowrap;"><strong>NO WEBSOCKET</strong></td>
<td style="border: 1px solid #ddd; padding: 8px;">NWS</td>
<td style="border: 1px solid #ddd; padding: 8px;">WebSocket protocol/handshake failure<br>-> LNbits down?<br>-> Device string correct?</td>
</tr>
</tbody>
</table>
<p><i><strong>Note:</strong> Higher priority errors override lower priority error displays. For example, if WiFi is down, all other checks are skipped and only "NO WIFI" is shown.</i></p>
<h3>Error Report Page</h3>
<p>All errors with their occurrence counts (0-99) are logged on the Report page. To access the Report page:</p>
<ul>
<li><strong>Press HELP button twice</strong> in quick succession</li>
<li><strong>Press LED button four times</strong> (if external LED button is available)</li>
</ul>
<h3>Common Wallet Error</h3>
<p>If a wallet scanning the QR code shows an error message, here's what it means:</p>
<p><strong>"bitcoinswitch ... is disabled"</strong> → The Bitcoin Switch was actively disabled in LNbits</p>
<p><strong>"No active bitcoinswitch connections"</strong> → The handshake between the wallet and ZapBox failed. -> Wait or restart the ZapBox to re-establish the connection.</p>
<h3>Other Errors</h3>
<p><strong>Relay does not switch, but payment was successful</strong> → Wrong GPIO pin configured in LNbits?</p>
<h3>NFC Extension Mismatch</h3>
<p>If you tap an NFC Bolt Card or NTAG21x tag and see <strong>"NFC not supported"</strong> on the display (shown for 5 seconds), and the serial log shows:</p>
<pre style="background-color: #f0f0f0; padding: 10px; border-radius: 5px; overflow-x: auto; font-size: 13px;">[WARN][NFC] Bolt Card tap detected – NFC is not supported by the active extension (bitcoinswitch).
[WARN][NFC] To use NFC / Bolt Cards, switch to zapbox_extension (apiPath = "zapbox").</pre>
<p>This means the device is configured with the <strong>bitcoinswitch_extension</strong>, which does not have an NFC endpoint (<code>/api/v1/nfc/</code>). NFC payments are only supported by the <strong>zapbox_extension</strong>.</p>
<p><strong>Solution:</strong></p>
<ul>
<li>In LNbits, create a device using the <strong>zapbox_extension</strong> instead of bitcoinswitch</li>
<li>Copy the new device string and reconfigure the ZapBox</li>
<li>The device string format is the same – only the server-side extension changes</li>
</ul>
<p><i>This is <strong>not</strong> a hardware problem – the NFC reader works fine. It’s purely a server-side configuration issue.</i></p>
<h3>Typical NFC Error Messages</h3>
<p><strong><code>{"detail":"LNURLW callback error: Payment failed - "}</code></strong><br>
The sending wallet and the receiving wallet in LNbits are the same. The Bolt Card wallet and the receiving wallet must be <strong>different</strong> wallets in LNbits.</p>
</div>
<!-- Troubleshoot Section -->
<div style="background-color: #CCE5FF; padding: 30px 20px; margin: 0 -20px; border-radius: 0 0 8px 8px;">
<h2 id="troubleshoot">Troubleshoot</h2>
<h3 id="mostimportant">Connection problems:</h3>
<ul>
<li>Check that the USB cable is plugged directly into the USB port of the ESP32 (behind the flap) and not into the USB port of the power supply.</li>
<li>Check/replace the cable. There are USB cables that have no data lines and are only for power supply.</li>
<li>If you encounter connection problems due to interface drivers with the pop-up window and the message “No port selected” please read this help page: <a href="https://ereignishorizont.xyz/en/bitcoinswitch_en/#76_Notes_on_Setting_Up_and_Checking_Drivers" target="_blank" rel="noopener noreferrer">"7.6 Notes on Setting Up and Checking Drivers"</a></li>
<li>Double connection: Check whether a connection already exists via another browser window. Close one window.</li>
<li>A website refresh with CTRL+F5 or CTRL+SHIFT+R can also help.</li>
<li>Message: “Installation failed ⚠️ Your ESP32 board is not supported” -> The firmware you want to flash is not compatible with the hardware. Check that you are using the correct web installer.</li>
<li>If nothing else helps, do a complete restart by erasing all data on the ESP32. Use <a href="https://espressif.github.io/esptool-js/" target="_blank" rel="noopener noreferrer">https://espressif.github.io/esptool-js/</a>. → Press: <strong>Connect</strong> > <strong>Erase Flash</strong> and reflash the ZapBox.</li>
</ul>
<h3 id="hardwareproblems">Further note:</h3>
<p>Some ESP32s must first be put into mode before flashing or transferring data. Otherwise, they will refuse to transfer. Try these:</p>
<ul>
<li>Connect the board via the USB cable</li>
<li>Press and hold the BOOT button</li>
<li>While still pressing the BOOT button, press RESET</li>
<li>Release the RESET button</li>
<li>Release the BOOT button</li>
<li><a href="#flash">Go to step 1</a></li>
</ul>
<img class="center-img" src="./assets/troubleshoot.jpg" />
<h3 id="pinout">Pinout:</h3>
<a href="./assets/Pinout.webp" target="_blank">
<img class="center-img" src="./assets/Pinout.webp" />
</a>
<p style="text-align: center; margin-top: 10px;"><i>Source: <a href="https://lilygo.cc/products/t-display-s3?variant=42589373268149" target="_blank" rel="noopener noreferrer">lilygo.cc</a></i></p>
<h3>Helpful links</h3>
<p>
Main page: <a href="https://zapbox.space" target="_blank" rel="noopener noreferrer">https://zapbox.space</a><br>
Web installer – T-Display-S3: <a href="https://installer.zapbox.space" target="_blank" rel="noopener noreferrer">https://installer.zapbox.space</a><br>
Web installer – Headless (esp32dev): <a href="https://installer.zapbox.space/headless/" target="_blank" rel="noopener noreferrer">https://installer.zapbox.space/headless/</a><br>
Further documentation: <a href="https://ereignishorizont.xyz/en/zapbox-en/" target="_blank" rel="noopener noreferrer">https://ereignishorizont.xyz/en/zapbox-en/</a><br>
GitHub – ZapBox: <a href="https://github.com/AxelHamburch/ZapBox" target="_blank" rel="noopener noreferrer">https://github.com/AxelHamburch/ZapBox</a><br>
GitHub – zapbox_extension: <a href="https://github.com/AxelHamburch/zapbox_extension" target="_blank" rel="noopener noreferrer">https://github.com/AxelHamburch/zapbox_extension</a><br>
Extension manifest source: <a href="https://installer.zapbox.space/extensions.json" target="_blank" rel="noopener noreferrer">https://installer.zapbox.space/extensions.json</a>
</p>
<hr style="margin: 20px 0; border: none; border-top: 1px solid #ccc;">
<p>
LNbits: <a href="https://lnbits.com/" target="_blank" rel="noopener noreferrer">https://lnbits.com/</a><br>
GitHub – bitcoinswitch_extension: <a href="https://github.com/lnbits/bitcoinswitch_extension" target="_blank" rel="noopener noreferrer">https://github.com/lnbits/bitcoinswitch_extension</a><br>
bitcoinSwitch web installer: <a href="https://bitcoinswitch.lnbits.com/" target="_blank" rel="noopener noreferrer">https://bitcoinswitch.lnbits.com/</a><br>
MakerBits Telegram Gruppe: <a href="https://t.me/makerbits" target="_blank" rel="noopener noreferrer">https://t.me/makerbits</a>
</p>
</div>
<hr>
<footer>
<p>Built on the shoulders of giants by <a href="https://njump.to/npub1yynthqj2zv663t0v8jtmtnwm34ak2fwe6zys043weqlnxgyuw7qqnk23ds" target="_blank" rel="noopener noreferrer">@axelhamburch</a> | <a href="https://github.com/AxelHamburch/ZapBox" target="_blank" rel="noopener noreferrer">Github repo</a> | <a href="https://zapbox.space/" target="_blank" rel="noopener noreferrer">zapbox.space</a></p>
</footer>
</div>
<script>
const consoleDebug = document.getElementById('consoleDebug');
const ssid = document.getElementById('ssid');
const wifiPassword = document.getElementById('wifiPassword');
const socket = document.getElementById('socket');
const qrFormat = document.getElementById('qrFormat');
const orientation = document.getElementById('orientation');
const theme = document.getElementById('theme');
const externalButton = document.getElementById('externalButton');
const specialMode = document.getElementById('specialMode');
const frequency = document.getElementById('frequency');
const dutyCycleRatio = document.getElementById('dutyCycleRatio');
const frequencyDiv = document.getElementById('frequencyDiv');
const dutyCycleDiv = document.getElementById('dutyCycleDiv');
const thresholdKey = document.getElementById('thresholdKey');
const thresholdAmount = document.getElementById('thresholdAmount');
const thresholdPin = document.getElementById('thresholdPin');
const thresholdTime = document.getElementById('thresholdTime');
const thresholdLnurl = document.getElementById('thresholdLnurl');
const screensaver = document.getElementById('screensaver');
const deepSleep = document.getElementById('deepSleep');
const activationTime = document.getElementById('activationTime');
const multiControl = document.getElementById('multiControl');
const lightBarrier = document.getElementById('lightBarrier');
const btcTicker = document.getElementById('btcTicker');
const currency = document.getElementById('currency');
const channel4Ambient = document.getElementById('channel4Ambient');
const deepSleepTime = document.getElementById('deepSleepTime');
// Servo config elements
const servoRelayMode = document.getElementById('servoRelayMode');
const servoConfigSection = document.getElementById('servoConfigSection');
// Pin 13 mode and config
const pin13Mode = document.getElementById('pin13Mode');
const pin13Servo180Section = document.getElementById('pin13Servo180Section');
const pin13Servo180Start = document.getElementById('pin13Servo180Start');
const pin13Servo180End = document.getElementById('pin13Servo180End');
const pin13Servo180Duration = document.getElementById('pin13Servo180Duration');
const pin13Servo360Section = document.getElementById('pin13Servo360Section');
const pin13Servo360Speed = document.getElementById('pin13Servo360Speed');
const pin13Servo360Duration = document.getElementById('pin13Servo360Duration');
// Pin 10 mode and config
const pin10Mode = document.getElementById('pin10Mode');
const pin10Servo180Section = document.getElementById('pin10Servo180Section');
const pin10Servo180Start = document.getElementById('pin10Servo180Start');
const pin10Servo180End = document.getElementById('pin10Servo180End');
const pin10Servo180Duration = document.getElementById('pin10Servo180Duration');
const pin10Servo360Section = document.getElementById('pin10Servo360Section');
const pin10Servo360Speed = document.getElementById('pin10Servo360Speed');
const pin10Servo360Duration = document.getElementById('pin10Servo360Duration');
// NFC config
const nfcMode = document.getElementById('nfcMode');
// Show/hide custom frequency/duty cycle inputs based on special mode selection
specialMode.addEventListener('change', function() {
if (specialMode.value === 'custom') {
frequencyDiv.style.display = 'block';
dutyCycleDiv.style.display = 'block';
} else {
frequencyDiv.style.display = 'none';
dutyCycleDiv.style.display = 'none';
}
});
let config = [
{
"name": "ssid",
"value": ""
},
{
"name": "wifipassword",
"value": ""
},
{
"name": "socket",
"value": ""
},
{
"name": "qrFormat",
"value": "bech32"
},
{
"name": "orientation",
"value": "h"
},
{
"name": "theme",
"value": "zapbox"
},
{
"name": "thresholdKey",
"value": ""
},
{
"name": "thresholdAmount",
"value": ""
},
{
"name": "thresholdPin",
"value": ""
},
{
"name": "thresholdTime",
"value": ""
},
{
"name": "thresholdLnurl",
"value": ""
},
{
"name": "specialMode",
"value": "standard"
},
{
"name": "frequency",
"value": "1.0"
},
{
"name": "dutyCycleRatio",
"value": "1.0"
},
{
"name": "screensaver",
"value": "off"
},
{
"name": "deepSleep",
"value": "off"
},
{
"name": "activationTime",
"value": "5"
},
{
"name": "multiControl",
"value": "off"
},
{
"name": "btcTicker",
"value": "selecting"
},
{
"name": "lightBarrier",
"value": "no"
},
{
"name": "currency",
"value": "USD"
},
{
"name": "externalButton",
"value": "no"
},
{
"name": "channel4Ambient",
"value": "normal"
},
{
"name": "deepSleepTime",
"value": "30"
},
{
"name": "pin13Mode",
"value": "off"
},
{
"name": "pin13Servo180Start",
"value": "0"
},
{
"name": "pin13Servo180End",
"value": "0"
},
{
"name": "pin13Servo180Duration",
"value": "0"
},
{
"name": "pin13Servo360Speed",
"value": "0"
},
{
"name": "pin13Servo360Duration",
"value": "0"
},
{
"name": "servoRelayMode",
"value": "one-for-all"
},
{
"name": "pin10Mode",
"value": "off"
},
{
"name": "pin10Servo180Start",
"value": "0"
},
{
"name": "pin10Servo180End",
"value": "0"
},
{