├── .idea
├── bluetoothprinter.iml
├── modules.xml
└── workspace.xml
├── README.md
├── app.js
├── app.json
├── app.wxss
├── img
├── screen1.PNG
└── screen2.PNG
├── miniprogram_npm
└── text-encoding
│ ├── index.js
│ └── index.js.map
├── package-lock.json
├── pages
├── index
│ ├── index.js
│ ├── index.json
│ ├── index.wxml
│ └── index.wxss
└── logs
│ ├── logs.js
│ ├── logs.json
│ ├── logs.wxml
│ └── logs.wxss
├── printer
├── commands.js
├── printerjobs.js
└── printerutil.js
├── project.config.json
└── utils
└── util.js
/.idea/bluetoothprinter.iml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/.idea/modules.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/.idea/workspace.xml:
--------------------------------------------------------------------------------
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 |
61 |
62 |
63 |
64 | function
65 | =>
66 | 0A
67 | 1B
68 | buffer
69 | PrinterJob
70 | COLOR
71 | 21
72 | FEED_CONTROL_SEQUENCES
73 | CS_DEFAULT
74 | TXT_FONT_
75 | 25
76 | ff
77 | 24
78 | the code point
79 | utf
80 | gbk
81 | '\x
82 | '
83 | \x
84 | ab2hex
85 | CHARACTER_SPACING
86 | getBLEDeviceServices
87 | onBLEConnectionStateChange
88 | openBluetoothAdapter
89 | onBluetoothAdapterStateChange
90 | _discoveryStarted
91 | startBluetoothDevicesDiscovery
92 | connected
93 | name
94 |
95 |
96 | [0x
97 | ]
98 | ,0x
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 | true
128 | DEFINITION_ORDER
129 |
130 |
131 |
132 |
133 |
134 |
135 |
136 |
137 |
138 |
139 |
140 |
141 |
142 |
143 |
144 |
145 |
146 |
147 | General
148 |
149 |
150 | JavaScript
151 |
152 |
153 | Naming conventionsJavaScript
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 | 1543547960238
243 |
244 |
245 | 1543547960238
246 |
247 |
248 |
249 |
250 |
251 |
252 |
253 |
254 |
255 | 1543832773328
256 |
257 |
258 |
259 | 1543832773328
260 |
261 |
262 | 1543834392860
263 |
264 |
265 |
266 | 1543834392860
267 |
268 |
269 | 1544080328425
270 |
271 |
272 |
273 | 1544080328425
274 |
275 |
276 | 1544155442455
277 |
278 |
279 |
280 | 1544155442455
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 |
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 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ## 小程序蓝牙打印
2 | 微信小程序蓝牙打印示例,代码参考[微信小程序示例](https://github.com/wechat-miniprogram/miniprogram-demo)。官方Demo总比网上随便找的强吧。
3 |
4 | * 测试打印机:[得力DL-581PW热敏票据打印机](https://item.jd.com/4606603.html)
5 | * 测试设备:iPhone 6s
6 |
7 | ### 效果图
8 |
9 |


10 |
11 |
12 | ### 流程
13 | * 初始化蓝牙模块 `wx.openBluetoothAdapter()`
14 | * 搜寻附近的蓝牙外围设备 `wx.startBluetoothDevicesDiscovery()`
15 | * 监听寻找到新设备的事件 `wx.onBluetoothDeviceFound()`
16 | * 连接低功耗蓝牙设备 `wx.createBLEConnection()`
17 | * 获取蓝牙设备服务 `wx.getBLEDeviceServices()`
18 | * 获取蓝牙设备服务的特征值 `wx.getBLEDeviceCharacteristics()`
19 | * 向低功耗蓝牙设备特征值中写入二进制数据 `wx.writeBLECharacteristicValue()`
20 | * 关闭蓝牙模块 `wx.closeBluetoothAdapter()`
21 |
22 | ### 注意点
23 | **1.与蓝牙设备通信很重要的就是找到对应的Characteristic。如何找到这个Characteristic?**
24 | 目前只能一个个去试!!!如果有更好的做法请告诉我。
25 |
26 | **2.遇到过Characteristic是支持write的,且写入成功,但是没有任何响应的情况。**
27 | 原因未知。试试下一个特征值。
28 |
29 | **3.写入数据包过大时,存在写入失败,但是却成功打印的情况。**
30 | 根据[小程序文档](https://developers.weixin.qq.com/miniprogram/dev/api/wx.writeBLECharacteristicValue.html):
31 | > 并行调用多次会存在写失败的可能性。
32 | 小程序不会对写入数据包大小做限制,但系统与蓝牙设备会限制蓝牙4.0单次传输的数据大小,超过最大字节数后会发生写入错误,建议每次写入不超过20字节。
33 | 若单次写入数据过长,iOS 上存在系统不会有任何回调的情况(包括错误回调)。
34 |
35 | 所以我们需要对写入数据做分包处理,对写入操作做延时调用
36 | ```javascript
37 | let buffer;
38 | const maxChunk = 20;
39 | const delay = 20;
40 | for (let i = 0, j = 0, length = buffer.byteLength; i < length; i += maxChunk, j++) {
41 | let subPackage = buffer.slice(i, i + maxChunk <= length ? (i + maxChunk) : length);
42 | setTimeout(this._writeBLECharacteristicValue, j * delay, subPackage);
43 | }
44 | ```
45 |
46 | **4.如何获取ArrayBuffer?**
47 | ```javascript
48 | // 存储需要发送的数据,元素用2位16进制表示
49 | let arr = [];
50 | // 将数组转换为8位无符号整型数组
51 | let bufferView = new Uint8Array(arr);
52 | let buffer = bufferView.buffer;
53 | ```
54 |
55 | **5.如何驱动打印机?**
56 | 现在大多数 POS 打印都采用 ESC/POS 指令集,一般情况下使用ESC/POS 指令集即可。
57 |
58 | **6.如何打印出同一行内,一部分内容居左,另一部分居右的效果?**
59 | 这个说出来你可能不信,是算出来的,中间用空格填充。一开始我也以为有什么什么指令。后来发现想多了。
60 |
61 | **7.打印出来的中文乱码?**
62 | 使用[text-encoding](https://github.com/inexorabletash/text-encoding)中文进行编码。
63 |
64 | ### TODO?
65 | * 打印图片
66 | * 打印二维码
67 | * 打印条码
68 |
69 | ### 参考
70 | * [微信小程序API](https://developers.weixin.qq.com/miniprogram/dev/api/)
71 | * [微信小程序示例](https://github.com/wechat-miniprogram/miniprogram-demo)
72 | * [低功耗蓝牙能力](https://developers.weixin.qq.com/community/develop/doc/0008acd004ccd86b37d649ee55b009?highLine=%25E8%2593%259D%25E7%2589%2599)
73 | * [微信小程序 - 蓝牙接口](https://www.jianshu.com/p/d01dbca67461)
74 | * [ESC(POS)打印控制命令](http://www.xmjjdz.com/downloads/manual/cn/ESC(POS)%E6%89%93%E5%8D%B0%E6%8E%A7%E5%88%B6%E5%91%BD%E4%BB%A4.pdf)
75 | * [ESCPOS](https://github.com/song940/node-escpos)
76 |
--------------------------------------------------------------------------------
/app.js:
--------------------------------------------------------------------------------
1 | //app.js
2 | App({
3 | onLaunch: function () {
4 | // 展示本地存储能力
5 | var logs = wx.getStorageSync('logs') || []
6 | logs.unshift(Date.now())
7 | wx.setStorageSync('logs', logs)
8 |
9 | // 登录
10 | wx.login({
11 | success: res => {
12 | // 发送 res.code 到后台换取 openId, sessionKey, unionId
13 | }
14 | })
15 | // 获取用户信息
16 | wx.getSetting({
17 | success: res => {
18 | if (res.authSetting['scope.userInfo']) {
19 | // 已经授权,可以直接调用 getUserInfo 获取头像昵称,不会弹框
20 | wx.getUserInfo({
21 | success: res => {
22 | // 可以将 res 发送给后台解码出 unionId
23 | this.globalData.userInfo = res.userInfo
24 |
25 | // 由于 getUserInfo 是网络请求,可能会在 Page.onLoad 之后才返回
26 | // 所以此处加入 callback 以防止这种情况
27 | if (this.userInfoReadyCallback) {
28 | this.userInfoReadyCallback(res)
29 | }
30 | }
31 | })
32 | }
33 | }
34 | })
35 | },
36 | globalData: {
37 | userInfo: null
38 | }
39 | })
--------------------------------------------------------------------------------
/app.json:
--------------------------------------------------------------------------------
1 | {
2 | "pages":[
3 | "pages/index/index",
4 | "pages/logs/logs"
5 | ],
6 | "window":{
7 | "backgroundTextStyle":"light",
8 | "navigationBarBackgroundColor": "#fff",
9 | "navigationBarTitleText": "WeChat",
10 | "navigationBarTextStyle":"black"
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/app.wxss:
--------------------------------------------------------------------------------
1 | /**app.wxss**/
2 | page {
3 | background-color: #F8F8F8;
4 | height: 100%;
5 | }
6 |
7 | .container {
8 | min-height: 100%;
9 | display: flex;
10 | flex-direction: column;
11 | align-items: center;
12 | }
--------------------------------------------------------------------------------
/img/screen1.PNG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/benioZhang/miniprogram-bluetoothprinter/52b77608373e708c42dfdf446a0f017fa3020813/img/screen1.PNG
--------------------------------------------------------------------------------
/img/screen2.PNG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/benioZhang/miniprogram-bluetoothprinter/52b77608373e708c42dfdf446a0f017fa3020813/img/screen2.PNG
--------------------------------------------------------------------------------
/miniprogram_npm/text-encoding/index.js.map:
--------------------------------------------------------------------------------
1 | {"version":3,"sources":["..\\..\\node_modules\\text-encoding\\index.js","..\\..\\node_modules\\text-encoding\\lib\\encoding.js","..\\..\\node_modules\\text-encoding\\lib\\encoding-indexes.js"],"names":[],"mappings":";;;;;;;AAAA;AACA;AACA;AACA,ACHA;ADIA,ACHA;ADIA,ACHA;ADIA,AENA,ADGA;ADIA,AENA,ADGA;ADIA,AENA,ADGA;ADIA,AENA,ADGA;ACFA,ADGA;ACFA,ADGA;ACFA,ADGA;ACFA,ADGA;ACFA,ADGA;ACFA,ADGA;ACFA,ADGA;ACFA,ADGA;ACFA,ADGA;ACFA,ADGA;ACFA,ADGA;ACFA,ADGA;ACFA,ADGA;ACFA,ADGA;ACFA,ADGA;ACFA,ADGA;ACFA,ADGA;ACFA,ADGA;ACFA,ADGA;ACFA,ADGA;ACFA,ADGA;ACFA,ADGA;ACFA,ADGA;ACFA,ADGA;ACFA,ADGA;ACFA,ADGA;ACFA,ADGA;ACFA,ADGA;ACFA,ADGA;ACFA,ADGA;ACFA,ADGA;ACFA,ADGA;ACFA,ADGA;ACFA,ADGA;ACFA,ADGA;ACFA,ADGA;ACFA,ADGA;ACFA,ADGA;ACFA,ADGA;ACFA,ADGA;ACFA,ADGA;ACFA,ADGA;ACFA,ADGA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA","file":"index.js"}
--------------------------------------------------------------------------------
/package-lock.json:
--------------------------------------------------------------------------------
1 | {
2 | "requires": true,
3 | "lockfileVersion": 1,
4 | "dependencies": {
5 | "text-encoding": {
6 | "version": "0.7.0",
7 | "resolved": "https://registry.npmjs.org/text-encoding/-/text-encoding-0.7.0.tgz",
8 | "integrity": "sha512-oJQ3f1hrOnbRLOcwKz0Liq2IcrvDeZRHXhd9RgLrsT+DjWY/nty1Hi7v3dtkaEYbPYe0mUoOfzRrMwfXXwgPUA=="
9 | }
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/pages/index/index.js:
--------------------------------------------------------------------------------
1 | const LAST_CONNECTED_DEVICE = 'last_connected_device'
2 | const PrinterJobs = require('../../printer/printerjobs')
3 | const printerUtil = require('../../printer/printerutil')
4 |
5 | function inArray(arr, key, val) {
6 | for (let i = 0; i < arr.length; i++) {
7 | if (arr[i][key] === val) {
8 | return i
9 | }
10 | }
11 | return -1
12 | }
13 |
14 | // ArrayBuffer转16进度字符串示例
15 | function ab2hex(buffer) {
16 | const hexArr = Array.prototype.map.call(
17 | new Uint8Array(buffer),
18 | function (bit) {
19 | return ('00' + bit.toString(16)).slice(-2)
20 | }
21 | )
22 | return hexArr.join(',')
23 | }
24 |
25 | function str2ab(str) {
26 | // Convert str to ArrayBuff and write to printer
27 | let buffer = new ArrayBuffer(str.length)
28 | let dataView = new DataView(buffer)
29 | for (let i = 0; i < str.length; i++) {
30 | dataView.setUint8(i, str.charAt(i).charCodeAt(0))
31 | }
32 | return buffer;
33 | }
34 |
35 | Page({
36 | data: {
37 | devices: [],
38 | connected: false,
39 | chs: []
40 | },
41 | onUnload() {
42 | this.closeBluetoothAdapter()
43 | },
44 | openBluetoothAdapter() {
45 | if (!wx.openBluetoothAdapter) {
46 | wx.showModal({
47 | title: '提示',
48 | content: '当前微信版本过低,无法使用该功能,请升级到最新微信版本后重试。'
49 | })
50 | return
51 | }
52 | wx.openBluetoothAdapter({
53 | success: (res) => {
54 | console.log('openBluetoothAdapter success', res)
55 | this.startBluetoothDevicesDiscovery()
56 | },
57 | fail: (res) => {
58 | console.log('openBluetoothAdapter fail', res)
59 | if (res.errCode === 10001) {
60 | wx.showModal({
61 | title: '错误',
62 | content: '未找到蓝牙设备, 请打开蓝牙后重试。',
63 | showCancel: false
64 | })
65 | wx.onBluetoothAdapterStateChange((res) => {
66 | console.log('onBluetoothAdapterStateChange', res)
67 | if (res.available) {
68 | // 取消监听,否则stopBluetoothDevicesDiscovery后仍会继续触发onBluetoothAdapterStateChange,
69 | // 导致再次调用startBluetoothDevicesDiscovery
70 | wx.onBluetoothAdapterStateChange(() => {
71 | });
72 | this.startBluetoothDevicesDiscovery()
73 | }
74 | })
75 | }
76 | }
77 | })
78 | wx.onBLEConnectionStateChange((res) => {
79 | // 该方法回调中可以用于处理连接意外断开等异常情况
80 | console.log('onBLEConnectionStateChange', `device ${res.deviceId} state has changed, connected: ${res.connected}`)
81 | this.setData({
82 | connected: res.connected
83 | })
84 | if (!res.connected) {
85 | wx.showModal({
86 | title: '错误',
87 | content: '蓝牙连接已断开',
88 | showCancel: false
89 | })
90 | }
91 | });
92 | },
93 | getBluetoothAdapterState() {
94 | wx.getBluetoothAdapterState({
95 | success: (res) => {
96 | console.log('getBluetoothAdapterState', res)
97 | if (res.discovering) {
98 | this.onBluetoothDeviceFound()
99 | } else if (res.available) {
100 | this.startBluetoothDevicesDiscovery()
101 | }
102 | }
103 | })
104 | },
105 | startBluetoothDevicesDiscovery() {
106 | if (this._discoveryStarted) {
107 | return
108 | }
109 | this._discoveryStarted = true
110 | wx.startBluetoothDevicesDiscovery({
111 | success: (res) => {
112 | console.log('startBluetoothDevicesDiscovery success', res)
113 | this.onBluetoothDeviceFound()
114 | },
115 | fail: (res) => {
116 | console.log('startBluetoothDevicesDiscovery fail', res)
117 | }
118 | })
119 | },
120 | stopBluetoothDevicesDiscovery() {
121 | wx.stopBluetoothDevicesDiscovery({
122 | complete: () => {
123 | console.log('stopBluetoothDevicesDiscovery')
124 | this._discoveryStarted = false
125 | }
126 | })
127 | },
128 | onBluetoothDeviceFound() {
129 | wx.onBluetoothDeviceFound((res) => {
130 | res.devices.forEach(device => {
131 | if (!device.name && !device.localName) {
132 | return
133 | }
134 | const foundDevices = this.data.devices
135 | const idx = inArray(foundDevices, 'deviceId', device.deviceId)
136 | const data = {}
137 | if (idx === -1) {
138 | data[`devices[${foundDevices.length}]`] = device
139 | } else {
140 | data[`devices[${idx}]`] = device
141 | }
142 | this.setData(data)
143 | })
144 | })
145 | },
146 | createBLEConnection(e) {
147 | const ds = e.currentTarget.dataset
148 | const deviceId = ds.deviceId
149 | const name = ds.name
150 | this._createBLEConnection(deviceId, name)
151 | },
152 | _createBLEConnection(deviceId, name) {
153 | wx.showLoading()
154 | wx.createBLEConnection({
155 | deviceId,
156 | success: () => {
157 | console.log('createBLEConnection success');
158 | this.setData({
159 | connected: true,
160 | name,
161 | deviceId,
162 | })
163 | this.getBLEDeviceServices(deviceId)
164 | wx.setStorage({
165 | key: LAST_CONNECTED_DEVICE,
166 | data: name + ':' + deviceId
167 | })
168 | },
169 | complete() {
170 | wx.hideLoading()
171 | },
172 | fail: (res) => {
173 | console.log('createBLEConnection fail', res)
174 | }
175 | })
176 | this.stopBluetoothDevicesDiscovery()
177 | },
178 | closeBLEConnection() {
179 | wx.closeBLEConnection({
180 | deviceId: this.data.deviceId
181 | })
182 | this.setData({
183 | connected: false,
184 | chs: [],
185 | canWrite: false,
186 | })
187 | },
188 | getBLEDeviceServices(deviceId) {
189 | wx.getBLEDeviceServices({
190 | deviceId,
191 | success: (res) => {
192 | console.log('getBLEDeviceServices', res)
193 | for (let i = 0; i < res.services.length; i++) {
194 | if (res.services[i].isPrimary) {
195 | this.getBLEDeviceCharacteristics(deviceId, res.services[i].uuid)
196 | return
197 | }
198 | }
199 | }
200 | })
201 | },
202 | getBLEDeviceCharacteristics(deviceId, serviceId) {
203 | wx.getBLEDeviceCharacteristics({
204 | deviceId,
205 | serviceId,
206 | success: (res) => {
207 | console.log('getBLEDeviceCharacteristics success', res.characteristics)
208 | // 这里会存在特征值是支持write,写入成功但是没有任何反应的情况
209 | // 只能一个个去试
210 | for (let i = 0; i < res.characteristics.length; i++) {
211 | const item = res.characteristics[i]
212 | if (item.properties.write) {
213 | this.setData({
214 | canWrite: true
215 | })
216 | this._deviceId = deviceId
217 | this._serviceId = serviceId
218 | this._characteristicId = item.uuid
219 | break;
220 | }
221 | }
222 | },
223 | fail(res) {
224 | console.error('getBLEDeviceCharacteristics', res)
225 | }
226 | })
227 | },
228 | writeBLECharacteristicValue() {
229 | let printerJobs = new PrinterJobs();
230 | printerJobs
231 | .print('2018年12月5日17:34')
232 | .print(printerUtil.fillLine())
233 | .setAlign('ct')
234 | .setSize(2, 2)
235 | .print('#20饿了么外卖')
236 | .setSize(1, 1)
237 | .print('切尔西Chelsea')
238 | .setSize(2, 2)
239 | .print('在线支付(已支付)')
240 | .setSize(1, 1)
241 | .print('订单号:5415221202244734')
242 | .print('下单时间:2017-07-07 18:08:08')
243 | .setAlign('lt')
244 | .print(printerUtil.fillAround('一号口袋'))
245 | .print(printerUtil.inline('意大利茄汁一面 * 1', '15'))
246 | .print(printerUtil.fillAround('其他'))
247 | .print('餐盒费:1')
248 | .print('[赠送康师傅冰红茶] * 1')
249 | .print(printerUtil.fillLine())
250 | .setAlign('rt')
251 | .print('原价:¥16')
252 | .print('总价:¥16')
253 | .setAlign('lt')
254 | .print(printerUtil.fillLine())
255 | .print('备注')
256 | .print("无")
257 | .print(printerUtil.fillLine())
258 | .println();
259 |
260 | let buffer = printerJobs.buffer();
261 | console.log('ArrayBuffer', 'length: ' + buffer.byteLength, ' hex: ' + ab2hex(buffer));
262 | // 1.并行调用多次会存在写失败的可能性
263 | // 2.建议每次写入不超过20字节
264 | // 分包处理,延时调用
265 | const maxChunk = 20;
266 | const delay = 20;
267 | for (let i = 0, j = 0, length = buffer.byteLength; i < length; i += maxChunk, j++) {
268 | let subPackage = buffer.slice(i, i + maxChunk <= length ? (i + maxChunk) : length);
269 | setTimeout(this._writeBLECharacteristicValue, j * delay, subPackage);
270 | }
271 | },
272 | _writeBLECharacteristicValue(buffer) {
273 | wx.writeBLECharacteristicValue({
274 | deviceId: this._deviceId,
275 | serviceId: this._serviceId,
276 | characteristicId: this._characteristicId,
277 | value: buffer,
278 | success(res) {
279 | console.log('writeBLECharacteristicValue success', res)
280 | },
281 | fail(res) {
282 | console.log('writeBLECharacteristicValue fail', res)
283 | }
284 | })
285 | },
286 | closeBluetoothAdapter() {
287 | wx.closeBluetoothAdapter()
288 | this._discoveryStarted = false
289 | },
290 | onLoad(options) {
291 | const lastDevice = wx.getStorageSync(LAST_CONNECTED_DEVICE);
292 | this.setData({
293 | lastDevice: lastDevice
294 | })
295 | },
296 | createBLEConnectionWithDeviceId(e) {
297 | // 小程序在之前已有搜索过某个蓝牙设备,并成功建立连接,可直接传入之前搜索获取的 deviceId 直接尝试连接该设备
298 | const device = this.data.lastDevice
299 | if (!device) {
300 | return
301 | }
302 | const index = device.indexOf(':');
303 | const name = device.substring(0, index);
304 | const deviceId = device.substring(index + 1, device.length);
305 | console.log('createBLEConnectionWithDeviceId', name + ':' + deviceId)
306 | wx.openBluetoothAdapter({
307 | success: (res) => {
308 | console.log('openBluetoothAdapter success', res)
309 | this._createBLEConnection(deviceId, name)
310 | },
311 | fail: (res) => {
312 | console.log('openBluetoothAdapter fail', res)
313 | if (res.errCode === 10001) {
314 | wx.showModal({
315 | title: '错误',
316 | content: '未找到蓝牙设备, 请打开蓝牙后重试。',
317 | showCancel: false
318 | })
319 | wx.onBluetoothAdapterStateChange((res) => {
320 | console.log('onBluetoothAdapterStateChange', res)
321 | if (res.available) {
322 | // 取消监听
323 | wx.onBluetoothAdapterStateChange(() => {
324 | });
325 | this._createBLEConnection(deviceId, name)
326 | }
327 | })
328 | }
329 | }
330 | })
331 | }
332 | })
--------------------------------------------------------------------------------
/pages/index/index.json:
--------------------------------------------------------------------------------
1 | {
2 | "navigationBarTitleText": "蓝牙打印"
3 | }
--------------------------------------------------------------------------------
/pages/index/index.wxml:
--------------------------------------------------------------------------------
1 |
2 | module.exports.max = function(n1, n2) {
3 | return Math.max(n1, n2)
4 | }
5 | module.exports.len = function(arr) {
6 | arr = arr || [];
7 | return arr.length;
8 | }
9 |
10 |
11 |
12 | 已发现 {{devices.length}} 个外围设备:
13 |
14 |
15 | {{item.name}}
16 | 信号强度: {{item.RSSI}}dBm ({{utils.max(0, item.RSSI + 100)}}%)
17 | UUID: {{item.deviceId}}
18 | Service数量: {{utils.len(item.advertisServiceUUIDs)}}
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 | 最近连接的设备
28 | {{lastDevice}}
29 |
30 |
31 |
32 |
33 |
34 |
35 | 已连接到 {{name}}
36 |
37 | 特性UUID: {{item.uuid}}
38 | 特性值: {{item.value}}
39 |
40 |
41 |
42 |
45 |
46 |
47 |
48 |
--------------------------------------------------------------------------------
/pages/index/index.wxss:
--------------------------------------------------------------------------------
1 | .page-section {
2 | display: flex;
3 | flex-direction: column;
4 | align-items: center;
5 | width: 100%;
6 | box-sizing: border-box;
7 | border-bottom: 2rpx solid #EEE;
8 | padding: 30rpx 0;
9 | }
10 |
11 | .devices-summary {
12 | padding: 10rpx;
13 | font-size: 30rpx;
14 | }
15 |
16 | .device-list {
17 | height: 300rpx;
18 | }
19 |
20 | .device-item {
21 | border-bottom: 1rpx solid #EEE;
22 | padding: 10rpx;
23 | color: #666;
24 | }
25 |
26 | .device-item-hover {
27 | background-color: rgba(0, 0, 0, .1);
28 | }
29 |
30 | .btn-area {
31 | box-sizing: border-box;
32 | width: 100%;
33 | padding: 0 30rpx;
34 | margin: 30rpx 0;
35 | }
36 |
37 | .connected-area {
38 | font-size: 22rpx;
39 | }
40 |
41 | .connected-info {}
42 |
43 | .input-area {
44 | background: #fff;
45 | margin-top: 10rpx;
46 | width: 100%;
47 | }
48 |
49 | .input {
50 | font-size: 28rpx;
51 | height: 2.58823529em;
52 | min-height: 2.58823529em;
53 | line-height: 2.58823529em;
54 | padding: 10rpx;
55 | }
--------------------------------------------------------------------------------
/pages/logs/logs.js:
--------------------------------------------------------------------------------
1 | //logs.js
2 | const util = require('../../utils/util.js')
3 |
4 | Page({
5 | data: {
6 | logs: []
7 | },
8 | onLoad: function () {
9 | this.setData({
10 | logs: (wx.getStorageSync('logs') || []).map(log => {
11 | return util.formatTime(new Date(log))
12 | })
13 | })
14 | }
15 | })
16 |
--------------------------------------------------------------------------------
/pages/logs/logs.json:
--------------------------------------------------------------------------------
1 | {
2 | "navigationBarTitleText": "查看启动日志"
3 | }
--------------------------------------------------------------------------------
/pages/logs/logs.wxml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | {{index + 1}}. {{log}}
5 |
6 |
7 |
--------------------------------------------------------------------------------
/pages/logs/logs.wxss:
--------------------------------------------------------------------------------
1 | .log-list {
2 | display: flex;
3 | flex-direction: column;
4 | padding: 40rpx;
5 | }
6 | .log-item {
7 | margin: 10rpx;
8 | }
9 |
--------------------------------------------------------------------------------
/printer/commands.js:
--------------------------------------------------------------------------------
1 | /**
2 | * 修改自https://github.com/song940/node-escpos/blob/master/commands.js
3 | * ESC/POS _ (Constants)
4 | */
5 | var _ = {
6 | LF: [0x0a],
7 | FS: [0x1c],
8 | FF: [0x0c],
9 | GS: [0x1d],
10 | DLE: [0x10],
11 | EOT: [0x04],
12 | NUL: [0x00],
13 | ESC: [0x1b],
14 | EOL: '\n',
15 | };
16 |
17 | /**
18 | * [FEED_CONTROL_SEQUENCES Feed control sequences]
19 | * @type {Object}
20 | */
21 | _.FEED_CONTROL_SEQUENCES = {
22 | CTL_LF: [0x0a], // Print and line feed
23 | CTL_GLF: [0x4a, 0x00], // Print and feed paper (without spaces between lines)
24 | CTL_FF: [0x0c], // Form feed
25 | CTL_CR: [0x0d], // Carriage return
26 | CTL_HT: [0x09], // Horizontal tab
27 | CTL_VT: [0x0b], // Vertical tab
28 | };
29 |
30 | _.CHARACTER_SPACING = {
31 | CS_DEFAULT: [0x1b, 0x20, 0x00],
32 | CS_SET: [0x1b, 0x20]
33 | };
34 |
35 | _.LINE_SPACING = {
36 | LS_DEFAULT: [0x1b, 0x32],
37 | LS_SET: [0x1b, 0x33]
38 | };
39 |
40 | /**
41 | * [HARDWARE Printer hardware]
42 | * @type {Object}
43 | */
44 | _.HARDWARE = {
45 | HW_INIT: [0x1b, 0x40], // Clear data in buffer and reset modes
46 | HW_SELECT: [0x1b, 0x3d, 0x01], // Printer select
47 | HW_RESET: [0x1b, 0x3f, 0x0a, 0x00], // Reset printer hardware
48 | };
49 |
50 | /**
51 | * [CASH_DRAWER Cash Drawer]
52 | * @type {Object}
53 | */
54 | _.CASH_DRAWER = {
55 | CD_KICK_2: [0x1b, 0x70, 0x00], // Sends a pulse to pin 2 []
56 | CD_KICK_5: [0x1b, 0x70, 0x01], // Sends a pulse to pin 5 []
57 | };
58 |
59 | /**
60 | * [MARGINS Margins sizes]
61 | * @type {Object}
62 | */
63 | _.MARGINS = {
64 | BOTTOM: [0x1b, 0x4f], // Fix bottom size
65 | LEFT: [0x1b, 0x6c], // Fix left size
66 | RIGHT: [0x1b, 0x51], // Fix right size
67 | };
68 |
69 | /**
70 | * [PAPER Paper]
71 | * @type {Object}
72 | */
73 | _.PAPER = {
74 | PAPER_FULL_CUT: [0x1d, 0x56, 0x00], // Full cut paper
75 | PAPER_PART_CUT: [0x1d, 0x56, 0x01], // Partial cut paper
76 | PAPER_CUT_A: [0x1d, 0x56, 0x41], // Partial cut paper
77 | PAPER_CUT_B: [0x1d, 0x56, 0x42], // Partial cut paper
78 | };
79 |
80 | /**
81 | * [TEXT_FORMAT Text format]
82 | * @type {Object}
83 | */
84 | _.TEXT_FORMAT = {
85 | TXT_NORMAL: [0x1b, 0x21, 0x00], // Normal text
86 | TXT_2HEIGHT: [0x1b, 0x21, 0x10], // Double height text
87 | TXT_2WIDTH: [0x1b, 0x21, 0x20], // Double width text
88 | TXT_4SQUARE: [0x1b, 0x21, 0x30], // Double width & height text
89 |
90 | TXT_UNDERL_OFF: [0x1b, 0x2d, 0x00], // Underline font OFF
91 | TXT_UNDERL_ON: [0x1b, 0x2d, 0x01], // Underline font 1-dot ON
92 | TXT_UNDERL2_ON: [0x1b, 0x2d, 0x02], // Underline font 2-dot ON
93 | TXT_BOLD_OFF: [0x1b, 0x45, 0x00], // Bold font OFF
94 | TXT_BOLD_ON: [0x1b, 0x45, 0x01], // Bold font ON
95 | TXT_ITALIC_OFF: [0x1b, 0x35], // Italic font ON
96 | TXT_ITALIC_ON: [0x1b, 0x34], // Italic font ON
97 |
98 | TXT_FONT_A: [0x1b, 0x4d, 0x00], // Font type A
99 | TXT_FONT_B: [0x1b, 0x4d, 0x01], // Font type B
100 | TXT_FONT_C: [0x1b, 0x4d, 0x02], // Font type C
101 |
102 | TXT_ALIGN_LT: [0x1b, 0x61, 0x00], // Left justification
103 | TXT_ALIGN_CT: [0x1b, 0x61, 0x01], // Centering
104 | TXT_ALIGN_RT: [0x1b, 0x61, 0x02], // Right justification
105 | };
106 |
107 | /**
108 | * [BARCODE_FORMAT Barcode format]
109 | * @type {Object}
110 | */
111 | _.BARCODE_FORMAT = {
112 | BARCODE_TXT_OFF: [0x1d, 0x48, 0x00], // HRI barcode chars OFF
113 | BARCODE_TXT_ABV: [0x1d, 0x48, 0x01], // HRI barcode chars above
114 | BARCODE_TXT_BLW: [0x1d, 0x48, 0x02], // HRI barcode chars below
115 | BARCODE_TXT_BTH: [0x1d, 0x48, 0x03], // HRI barcode chars both above and below
116 |
117 | BARCODE_FONT_A: [0x1d, 0x66, 0x00], // Font type A for HRI barcode chars
118 | BARCODE_FONT_B: [0x1d, 0x66, 0x01], // Font type B for HRI barcode chars
119 |
120 | BARCODE_HEIGHT: function (height) { // Barcode Height [1-255]
121 | return [0x1d, 0x68, height];
122 | },
123 | BARCODE_WIDTH: function (width) { // Barcode Width [2-6]
124 | return [0x1d, 0x77, width];
125 | },
126 | BARCODE_HEIGHT_DEFAULT: [0x1d, 0x68, 0x64], // Barcode height default:100
127 | BARCODE_WIDTH_DEFAULT: [0x1d, 0x77, 0x01], // Barcode width default:1
128 |
129 | BARCODE_UPC_A: [0x1d, 0x6b, 0x00], // Barcode type UPC-A
130 | BARCODE_UPC_E: [0x1d, 0x6b, 0x01], // Barcode type UPC-E
131 | BARCODE_EAN13: [0x1d, 0x6b, 0x02], // Barcode type EAN13
132 | BARCODE_EAN8: [0x1d, 0x6b, 0x03], // Barcode type EAN8
133 | BARCODE_CODE39: [0x1d, 0x6b, 0x04], // Barcode type CODE39
134 | BARCODE_ITF: [0x1d, 0x6b, 0x05], // Barcode type ITF
135 | BARCODE_NW7: [0x1d, 0x6b, 0x06], // Barcode type NW7
136 | BARCODE_CODE93: [0x1d, 0x6b, 0x48], // Barcode type CODE93
137 | BARCODE_CODE128: [0x1d, 0x6b, 0x49], // Barcode type CODE128
138 | };
139 |
140 | /**
141 | * [IMAGE_FORMAT Image format]
142 | * @type {Object}
143 | */
144 | _.IMAGE_FORMAT = {
145 | S_RASTER_N: [0x1d, 0x76, 0x30, 0x00], // Set raster image normal size
146 | S_RASTER_2W: [0x1d, 0x76, 0x30, 0x01], // Set raster image double width
147 | S_RASTER_2H: [0x1d, 0x76, 0x30, 0x02], // Set raster image double height
148 | S_RASTER_Q: [0x1d, 0x76, 0x30, 0x03], // Set raster image quadruple
149 | };
150 |
151 | /**
152 | * [BITMAP_FORMAT description]
153 | * @type {Object}
154 | */
155 | _.BITMAP_FORMAT = {
156 | BITMAP_S8: [0x1b, 0x2a, 0x00],
157 | BITMAP_D8: [0x1b, 0x2a, 0x01],
158 | BITMAP_S24: [0x1b, 0x2a, 0x20],
159 | BITMAP_D24: [0x1b, 0x2a, 0x21]
160 | };
161 |
162 | /**
163 | * [GSV0_FORMAT description]
164 | * @type {Object}
165 | */
166 | _.GSV0_FORMAT = {
167 | GSV0_NORMAL: [0x1d, 0x76, 0x30, 0x00],
168 | GSV0_DW: [0x1d, 0x76, 0x30, 0x01],
169 | GSV0_DH: [0x1d, 0x76, 0x30, 0x02],
170 | GSV0_DWDH: [0x1d, 0x76, 0x30, 0x03]
171 | };
172 |
173 | /**
174 | * [BEEP description]
175 | * @type {string}
176 | */
177 | _.BEEP = [0x1b, 0x42]; // Printer Buzzer pre hex
178 |
179 | /**
180 | * [COLOR description]
181 | * @type {Object}
182 | */
183 |
184 | _.COLOR = {
185 | 0: [0x1b, 0x72, 0x00], // black
186 | 1: [0x1b, 0x72, 0x01] // red
187 | };
188 |
189 | /**
190 | * [exports description]
191 | * @type {[type]}
192 | */
193 | module.exports = _;
--------------------------------------------------------------------------------
/printer/printerjobs.js:
--------------------------------------------------------------------------------
1 | const commands = require('commands');
2 | const TextEncoder = require('text-encoding/index').TextEncoder;
3 |
4 | const printerJobs = function () {
5 | this._queue = Array.from(commands.HARDWARE.HW_INIT);
6 | this._encoder = new TextEncoder("gb2312", {NONSTANDARD_allowLegacyEncoding: true});
7 | this._enqueue = function (cmd) {
8 | this._queue.push.apply(this._queue, cmd);
9 | }
10 | };
11 |
12 | /**
13 | * 增加打印内容
14 | * @param {string} content 文字内容
15 | */
16 | printerJobs.prototype.text = function (content) {
17 | if (content) {
18 | let uint8Array = this._encoder.encode(content);
19 | let encoded = Array.from(uint8Array);
20 | this._enqueue(encoded);
21 | }
22 | return this;
23 | };
24 |
25 | /**
26 | * 打印文字
27 | * @param {string} content 文字内容
28 | */
29 | printerJobs.prototype.print = function (content) {
30 | this.text(content);
31 | this._enqueue(commands.LF);
32 | return this;
33 | };
34 |
35 | /**
36 | * 打印文字并换行
37 | * @param {string} content 文字内容
38 | */
39 | printerJobs.prototype.println = function (content = '') {
40 | return this.print(content + commands.EOL);
41 | };
42 |
43 | /**
44 | * 设置对齐方式
45 | * @param {string} align 对齐方式 LT/CT/RT
46 | */
47 | printerJobs.prototype.setAlign = function (align) {
48 | this._enqueue(commands.TEXT_FORMAT['TXT_ALIGN_' + align.toUpperCase()]);
49 | return this;
50 | };
51 |
52 | /**
53 | * 设置字体
54 | * @param {string} family A/B/C
55 | */
56 | printerJobs.prototype.setFont = function (family) {
57 | this._enqueue(commands.TEXT_FORMAT['TXT_FONT_' + family.toUpperCase()]);
58 | return this;
59 | };
60 |
61 | /**
62 | * 设定字体尺寸
63 | * @param {number} width 字体宽度 1~2
64 | * @param {number} height 字体高度 1~2
65 | */
66 | printerJobs.prototype.setSize = function (width, height) {
67 | if (2 >= width && 2 >= height) {
68 | this._enqueue(commands.TEXT_FORMAT.TXT_NORMAL);
69 | if (2 === width && 2 === height) {
70 | this._enqueue(commands.TEXT_FORMAT.TXT_4SQUARE);
71 | } else if (1 === width && 2 === height) {
72 | this._enqueue(commands.TEXT_FORMAT.TXT_2HEIGHT);
73 | } else if (2 === width && 1 === height) {
74 | this._enqueue(commands.TEXT_FORMAT.TXT_2WIDTH);
75 | }
76 | }
77 | return this;
78 | };
79 |
80 | /**
81 | * 设定字体是否加粗
82 | * @param {boolean} bold
83 | */
84 | printerJobs.prototype.setBold = function (bold) {
85 | if (typeof bold !== 'boolean') {
86 | bold = true;
87 | }
88 | this._enqueue(bold ? commands.TEXT_FORMAT.TXT_BOLD_ON : commands.TEXT_FORMAT.TXT_BOLD_OFF);
89 | return this;
90 | };
91 |
92 | /**
93 | * 设定是否开启下划线
94 | * @param {boolean} underline
95 | */
96 | printerJobs.prototype.setUnderline = function (underline) {
97 | if (typeof underline !== 'boolean') {
98 | underline = true;
99 | }
100 | this._enqueue(underline ? commands.TEXT_FORMAT.TXT_UNDERL_ON : commands.TEXT_FORMAT.TXT_UNDERL_OFF);
101 | return this;
102 | };
103 |
104 | /**
105 | * 设置行间距为 n 点行,默认值行间距是 30 点
106 | * @param {number} n 0≤n≤255
107 | */
108 | printerJobs.prototype.setLineSpacing = function (n) {
109 | if (n === undefined || n === null) {
110 | this._enqueue(commands.LINE_SPACING.LS_DEFAULT);
111 | } else {
112 | this._enqueue(commands.LINE_SPACING.LS_SET);
113 | this._enqueue([n]);
114 | }
115 | return this;
116 | };
117 |
118 | /**
119 | * 打印空行
120 | * @param {number} n
121 | */
122 | printerJobs.prototype.lineFeed = function (n = 1) {
123 | return this.print(new Array(n).fill(commands.EOL).join(''));
124 | };
125 |
126 | /**
127 | * 设置字体颜色,需要打印机支持
128 | * @param {number} color - 0 默认颜色黑色 1 红色
129 | */
130 | printerJobs.prototype.setColor = function (color) {
131 | this._enqueue(commands.COLOR[color === 1 ? 1 : 0]);
132 | return this;
133 | };
134 |
135 | /**
136 | * https://support.loyverse.com/hardware/printers/use-the-beeper-in-a-escpos-printers
137 | * 蜂鸣警报,需要打印机支持
138 | * @param {number} n 蜂鸣次数,1-9
139 | * @param {number} t 蜂鸣长短,1-9
140 | */
141 | printerJobs.prototype.beep = function (n, t) {
142 | this._enqueue(commands.BEEP);
143 | this._enqueue([n, t]);
144 | return this;
145 | };
146 |
147 | /**
148 | * 清空任务
149 | */
150 | printerJobs.prototype.clear = function () {
151 | this._queue = Array.from(commands.HARDWARE.HW_INIT);
152 | return this;
153 | };
154 |
155 | /**
156 | * 返回ArrayBuffer
157 | */
158 | printerJobs.prototype.buffer = function () {
159 | return new Uint8Array(this._queue).buffer;
160 | };
161 |
162 | module.exports = printerJobs;
--------------------------------------------------------------------------------
/printer/printerutil.js:
--------------------------------------------------------------------------------
1 | // 打印机纸宽58mm,页的宽度384,字符宽度为1,每行最多盛放32个字符
2 | const PAGE_WIDTH = 384;
3 | const MAX_CHAR_COUNT_EACH_LINE = 32;
4 |
5 | /**
6 | * @param str
7 | * @returns {boolean} str是否全是中文
8 | */
9 | function isChinese(str) {
10 | return /^[\u4e00-\u9fa5]$/.test(str);
11 | }
12 |
13 | /**
14 | * 返回字符串宽度(1个中文=2个英文字符)
15 | * @param str
16 | * @returns {number}
17 | */
18 | function getStringWidth(str) {
19 | let width = 0;
20 | for (let i = 0, len = str.length; i < len; i++) {
21 | width += isChinese(str.charAt(i)) ? 2 : 1;
22 | }
23 | return width;
24 | }
25 |
26 | /**
27 | * 同一行输出str1, str2,str1居左, str2居右
28 | * @param {string} str1 内容1
29 | * @param {string} str2 内容2
30 | * @param {number} fontWidth 字符宽度 1/2
31 | * @param {string} fillWith str1 str2之间的填充字符
32 | *
33 | */
34 | function inline(str1, str2, fillWith = ' ', fontWidth = 1) {
35 | const lineWidth = MAX_CHAR_COUNT_EACH_LINE / fontWidth;
36 | // 需要填充的字符数量
37 | let fillCount = lineWidth - (getStringWidth(str1) + getStringWidth(str2)) % lineWidth;
38 | let fillStr = new Array(fillCount).fill(fillWith.charAt(0)).join('');
39 | return str1 + fillStr + str2;
40 | }
41 |
42 | /**
43 | * 用字符填充一整行
44 | * @param {string} fillWith 填充字符
45 | * @param {number} fontWidth 字符宽度 1/2
46 | */
47 | function fillLine(fillWith = '-', fontWidth = 1) {
48 | const lineWidth = MAX_CHAR_COUNT_EACH_LINE / fontWidth;
49 | return new Array(lineWidth).fill(fillWith.charAt(0)).join('');
50 | }
51 |
52 | /**
53 | * 文字内容居中,左右用字符填充
54 | * @param {string} str 文字内容
55 | * @param {number} fontWidth 字符宽度 1/2
56 | * @param {string} fillWith str1 str2之间的填充字符
57 | */
58 | function fillAround(str, fillWith = '-', fontWidth = 1) {
59 | const lineWidth = MAX_CHAR_COUNT_EACH_LINE / fontWidth;
60 | let strWidth = getStringWidth(str);
61 | // 内容已经超过一行了,没必要填充
62 | if (strWidth >= lineWidth) {
63 | return str;
64 | }
65 | // 需要填充的字符数量
66 | let fillCount = lineWidth - strWidth;
67 | // 左侧填充的字符数量
68 | let leftCount = Math.round(fillCount / 2);
69 | // 两侧的填充字符,需要考虑左边需要填充,右边不需要填充的情况
70 | let fillStr = new Array(leftCount).fill(fillWith.charAt(0)).join('');
71 | return fillStr + str + fillStr.substr(0, fillCount - leftCount);
72 | }
73 |
74 | module.exports = {
75 | inline: inline,
76 | fillLine: fillLine,
77 | fillAround: fillAround,
78 | };
--------------------------------------------------------------------------------
/project.config.json:
--------------------------------------------------------------------------------
1 | {
2 | "description": "项目配置文件",
3 | "packOptions": {
4 | "ignore": []
5 | },
6 | "setting": {
7 | "urlCheck": true,
8 | "es6": true,
9 | "postcss": true,
10 | "minified": true,
11 | "newFeature": true,
12 | "nodeModules": true
13 | },
14 | "compileType": "miniprogram",
15 | "libVersion": "2.4.0",
16 | "appid": "wx9813ba0436bd5a97",
17 | "projectname": "bluetoothprinter",
18 | "debugOptions": {
19 | "hidedInDevtools": []
20 | },
21 | "isGameTourist": false,
22 | "condition": {
23 | "search": {
24 | "current": -1,
25 | "list": []
26 | },
27 | "conversation": {
28 | "current": -1,
29 | "list": []
30 | },
31 | "game": {
32 | "currentL": -1,
33 | "list": []
34 | },
35 | "miniprogram": {
36 | "current": -1,
37 | "list": []
38 | }
39 | }
40 | }
--------------------------------------------------------------------------------
/utils/util.js:
--------------------------------------------------------------------------------
1 | const formatTime = date => {
2 | const year = date.getFullYear()
3 | const month = date.getMonth() + 1
4 | const day = date.getDate()
5 | const hour = date.getHours()
6 | const minute = date.getMinutes()
7 | const second = date.getSeconds()
8 |
9 | return [year, month, day].map(formatNumber).join('/') + ' ' + [hour, minute, second].map(formatNumber).join(':')
10 | }
11 |
12 | const formatNumber = n => {
13 | n = n.toString()
14 | return n[1] ? n : '0' + n
15 | }
16 |
17 | module.exports = {
18 | formatTime: formatTime
19 | }
20 |
--------------------------------------------------------------------------------