@v5x/serial 0.5.6 → 0.5.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,17 +1,3 @@
1
- var __defProp = Object.defineProperty;
2
- var __returnValue = (v) => v;
3
- function __exportSetter(name, newValue) {
4
- this[name] = __returnValue.bind(null, newValue);
5
- }
6
- var __export = (target, all) => {
7
- for (var name in all)
8
- __defProp(target, name, {
9
- get: all[name],
10
- enumerable: true,
11
- configurable: true,
12
- set: __exportSetter.bind(all, name)
13
- });
14
- };
15
1
  var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
16
2
  get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
17
3
  }) : x)(function(x) {
@@ -105,6 +91,7 @@ var AckType;
105
91
  AckType2[AckType2["CDC2_NACK_FILE_SYS_FULL"] = 220] = "CDC2_NACK_FILE_SYS_FULL";
106
92
  AckType2[AckType2["TIMEOUT"] = 256] = "TIMEOUT";
107
93
  AckType2[AckType2["WRITE_ERROR"] = 257] = "WRITE_ERROR";
94
+ AckType2[AckType2["NOT_CONNECTED"] = 258] = "NOT_CONNECTED";
108
95
  })(AckType ||= {});
109
96
  var SmartDeviceType;
110
97
  ((SmartDeviceType2) => {
@@ -287,8 +274,15 @@ class VexEventEmitter {
287
274
  this.handlerMap.set(eventName, listeners);
288
275
  }
289
276
  emit(eventName, data) {
277
+ const listeners = this.handlerMap.get(eventName);
278
+ if (listeners === undefined || listeners.length === 0)
279
+ return;
280
+ if (listeners.length === 1) {
281
+ listeners[0](data);
282
+ return;
283
+ }
290
284
  const errors = [];
291
- for (const callback of [...this.handlerMap.get(eventName) ?? []]) {
285
+ for (const callback of [...listeners]) {
292
286
  try {
293
287
  callback(data);
294
288
  } catch (error) {
@@ -312,472 +306,21 @@ class VexEventTarget {
312
306
  this.emitter = new VexEventEmitter;
313
307
  }
314
308
  emit(eventName, data) {
315
- this.emitter.emit(this.normalizeEventName(eventName), data);
309
+ this.emitter.emit(eventName, data);
316
310
  }
317
311
  on(eventName, listener) {
318
- this.emitter.on(this.normalizeEventName(eventName), listener);
312
+ this.emitter.on(eventName, listener);
319
313
  }
320
314
  remove(eventName, listener) {
321
- this.emitter.remove(this.normalizeEventName(eventName), listener);
315
+ this.emitter.remove(eventName, listener);
322
316
  }
323
317
  clearListeners() {
324
318
  this.emitter.clearListeners();
325
319
  }
326
- normalizeEventName(eventName) {
327
- return String(eventName);
328
- }
329
- }
330
-
331
- // ../../node_modules/.bun/neverthrow@8.2.0/node_modules/neverthrow/dist/index.es.js
332
- var defaultErrorConfig = {
333
- withStackTrace: false
334
- };
335
- var createNeverThrowError = (message, result, config = defaultErrorConfig) => {
336
- const data = result.isOk() ? { type: "Ok", value: result.value } : { type: "Err", value: result.error };
337
- const maybeStack = config.withStackTrace ? new Error().stack : undefined;
338
- return {
339
- data,
340
- message,
341
- stack: maybeStack
342
- };
343
- };
344
- function __awaiter(thisArg, _arguments, P, generator) {
345
- function adopt(value) {
346
- return value instanceof P ? value : new P(function(resolve) {
347
- resolve(value);
348
- });
349
- }
350
- return new (P || (P = Promise))(function(resolve, reject) {
351
- function fulfilled(value) {
352
- try {
353
- step(generator.next(value));
354
- } catch (e) {
355
- reject(e);
356
- }
357
- }
358
- function rejected(value) {
359
- try {
360
- step(generator["throw"](value));
361
- } catch (e) {
362
- reject(e);
363
- }
364
- }
365
- function step(result) {
366
- result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected);
367
- }
368
- step((generator = generator.apply(thisArg, _arguments || [])).next());
369
- });
370
- }
371
- function __values(o) {
372
- var s = typeof Symbol === "function" && Symbol.iterator, m = s && o[s], i = 0;
373
- if (m)
374
- return m.call(o);
375
- if (o && typeof o.length === "number")
376
- return {
377
- next: function() {
378
- if (o && i >= o.length)
379
- o = undefined;
380
- return { value: o && o[i++], done: !o };
381
- }
382
- };
383
- throw new TypeError(s ? "Object is not iterable." : "Symbol.iterator is not defined.");
384
- }
385
- function __await(v) {
386
- return this instanceof __await ? (this.v = v, this) : new __await(v);
387
- }
388
- function __asyncGenerator(thisArg, _arguments, generator) {
389
- if (!Symbol.asyncIterator)
390
- throw new TypeError("Symbol.asyncIterator is not defined.");
391
- var g = generator.apply(thisArg, _arguments || []), i, q = [];
392
- return i = Object.create((typeof AsyncIterator === "function" ? AsyncIterator : Object).prototype), verb("next"), verb("throw"), verb("return", awaitReturn), i[Symbol.asyncIterator] = function() {
393
- return this;
394
- }, i;
395
- function awaitReturn(f) {
396
- return function(v) {
397
- return Promise.resolve(v).then(f, reject);
398
- };
399
- }
400
- function verb(n, f) {
401
- if (g[n]) {
402
- i[n] = function(v) {
403
- return new Promise(function(a, b) {
404
- q.push([n, v, a, b]) > 1 || resume(n, v);
405
- });
406
- };
407
- if (f)
408
- i[n] = f(i[n]);
409
- }
410
- }
411
- function resume(n, v) {
412
- try {
413
- step(g[n](v));
414
- } catch (e) {
415
- settle(q[0][3], e);
416
- }
417
- }
418
- function step(r) {
419
- r.value instanceof __await ? Promise.resolve(r.value.v).then(fulfill, reject) : settle(q[0][2], r);
420
- }
421
- function fulfill(value) {
422
- resume("next", value);
423
- }
424
- function reject(value) {
425
- resume("throw", value);
426
- }
427
- function settle(f, v) {
428
- if (f(v), q.shift(), q.length)
429
- resume(q[0][0], q[0][1]);
430
- }
431
- }
432
- function __asyncDelegator(o) {
433
- var i, p;
434
- return i = {}, verb("next"), verb("throw", function(e) {
435
- throw e;
436
- }), verb("return"), i[Symbol.iterator] = function() {
437
- return this;
438
- }, i;
439
- function verb(n, f) {
440
- i[n] = o[n] ? function(v) {
441
- return (p = !p) ? { value: __await(o[n](v)), done: false } : f ? f(v) : v;
442
- } : f;
443
- }
444
- }
445
- function __asyncValues(o) {
446
- if (!Symbol.asyncIterator)
447
- throw new TypeError("Symbol.asyncIterator is not defined.");
448
- var m = o[Symbol.asyncIterator], i;
449
- return m ? m.call(o) : (o = typeof __values === "function" ? __values(o) : o[Symbol.iterator](), i = {}, verb("next"), verb("throw"), verb("return"), i[Symbol.asyncIterator] = function() {
450
- return this;
451
- }, i);
452
- function verb(n) {
453
- i[n] = o[n] && function(v) {
454
- return new Promise(function(resolve, reject) {
455
- v = o[n](v), settle(resolve, reject, v.done, v.value);
456
- });
457
- };
458
- }
459
- function settle(resolve, reject, d, v) {
460
- Promise.resolve(v).then(function(v2) {
461
- resolve({ value: v2, done: d });
462
- }, reject);
463
- }
464
- }
465
- class ResultAsync {
466
- constructor(res) {
467
- this._promise = res;
468
- }
469
- static fromSafePromise(promise) {
470
- const newPromise = promise.then((value) => new Ok(value));
471
- return new ResultAsync(newPromise);
472
- }
473
- static fromPromise(promise, errorFn) {
474
- const newPromise = promise.then((value) => new Ok(value)).catch((e) => new Err(errorFn(e)));
475
- return new ResultAsync(newPromise);
476
- }
477
- static fromThrowable(fn, errorFn) {
478
- return (...args) => {
479
- return new ResultAsync((() => __awaiter(this, undefined, undefined, function* () {
480
- try {
481
- return new Ok(yield fn(...args));
482
- } catch (error) {
483
- return new Err(errorFn ? errorFn(error) : error);
484
- }
485
- }))());
486
- };
487
- }
488
- static combine(asyncResultList) {
489
- return combineResultAsyncList(asyncResultList);
490
- }
491
- static combineWithAllErrors(asyncResultList) {
492
- return combineResultAsyncListWithAllErrors(asyncResultList);
493
- }
494
- map(f) {
495
- return new ResultAsync(this._promise.then((res) => __awaiter(this, undefined, undefined, function* () {
496
- if (res.isErr()) {
497
- return new Err(res.error);
498
- }
499
- return new Ok(yield f(res.value));
500
- })));
501
- }
502
- andThrough(f) {
503
- return new ResultAsync(this._promise.then((res) => __awaiter(this, undefined, undefined, function* () {
504
- if (res.isErr()) {
505
- return new Err(res.error);
506
- }
507
- const newRes = yield f(res.value);
508
- if (newRes.isErr()) {
509
- return new Err(newRes.error);
510
- }
511
- return new Ok(res.value);
512
- })));
513
- }
514
- andTee(f) {
515
- return new ResultAsync(this._promise.then((res) => __awaiter(this, undefined, undefined, function* () {
516
- if (res.isErr()) {
517
- return new Err(res.error);
518
- }
519
- try {
520
- yield f(res.value);
521
- } catch (e) {}
522
- return new Ok(res.value);
523
- })));
524
- }
525
- orTee(f) {
526
- return new ResultAsync(this._promise.then((res) => __awaiter(this, undefined, undefined, function* () {
527
- if (res.isOk()) {
528
- return new Ok(res.value);
529
- }
530
- try {
531
- yield f(res.error);
532
- } catch (e) {}
533
- return new Err(res.error);
534
- })));
535
- }
536
- mapErr(f) {
537
- return new ResultAsync(this._promise.then((res) => __awaiter(this, undefined, undefined, function* () {
538
- if (res.isOk()) {
539
- return new Ok(res.value);
540
- }
541
- return new Err(yield f(res.error));
542
- })));
543
- }
544
- andThen(f) {
545
- return new ResultAsync(this._promise.then((res) => {
546
- if (res.isErr()) {
547
- return new Err(res.error);
548
- }
549
- const newValue = f(res.value);
550
- return newValue instanceof ResultAsync ? newValue._promise : newValue;
551
- }));
552
- }
553
- orElse(f) {
554
- return new ResultAsync(this._promise.then((res) => __awaiter(this, undefined, undefined, function* () {
555
- if (res.isErr()) {
556
- return f(res.error);
557
- }
558
- return new Ok(res.value);
559
- })));
560
- }
561
- match(ok, _err) {
562
- return this._promise.then((res) => res.match(ok, _err));
563
- }
564
- unwrapOr(t) {
565
- return this._promise.then((res) => res.unwrapOr(t));
566
- }
567
- safeUnwrap() {
568
- return __asyncGenerator(this, arguments, function* safeUnwrap_1() {
569
- return yield __await(yield __await(yield* __asyncDelegator(__asyncValues(yield __await(this._promise.then((res) => res.safeUnwrap()))))));
570
- });
571
- }
572
- then(successCallback, failureCallback) {
573
- return this._promise.then(successCallback, failureCallback);
574
- }
575
- [Symbol.asyncIterator]() {
576
- return __asyncGenerator(this, arguments, function* _a() {
577
- const result = yield __await(this._promise);
578
- if (result.isErr()) {
579
- yield yield __await(errAsync(result.error));
580
- }
581
- return yield __await(result.value);
582
- });
583
- }
584
- }
585
- function errAsync(err) {
586
- return new ResultAsync(Promise.resolve(new Err(err)));
587
- }
588
- var fromPromise = ResultAsync.fromPromise;
589
- var fromSafePromise = ResultAsync.fromSafePromise;
590
- var fromAsyncThrowable = ResultAsync.fromThrowable;
591
- var combineResultList = (resultList) => {
592
- let acc = ok([]);
593
- for (const result of resultList) {
594
- if (result.isErr()) {
595
- acc = err(result.error);
596
- break;
597
- } else {
598
- acc.map((list) => list.push(result.value));
599
- }
600
- }
601
- return acc;
602
- };
603
- var combineResultAsyncList = (asyncResultList) => ResultAsync.fromSafePromise(Promise.all(asyncResultList)).andThen(combineResultList);
604
- var combineResultListWithAllErrors = (resultList) => {
605
- let acc = ok([]);
606
- for (const result of resultList) {
607
- if (result.isErr() && acc.isErr()) {
608
- acc.error.push(result.error);
609
- } else if (result.isErr() && acc.isOk()) {
610
- acc = err([result.error]);
611
- } else if (result.isOk() && acc.isOk()) {
612
- acc.value.push(result.value);
613
- }
614
- }
615
- return acc;
616
- };
617
- var combineResultAsyncListWithAllErrors = (asyncResultList) => ResultAsync.fromSafePromise(Promise.all(asyncResultList)).andThen(combineResultListWithAllErrors);
618
- var Result;
619
- (function(Result2) {
620
- function fromThrowable(fn, errorFn) {
621
- return (...args) => {
622
- try {
623
- const result = fn(...args);
624
- return ok(result);
625
- } catch (e) {
626
- return err(errorFn ? errorFn(e) : e);
627
- }
628
- };
629
- }
630
- Result2.fromThrowable = fromThrowable;
631
- function combine(resultList) {
632
- return combineResultList(resultList);
633
- }
634
- Result2.combine = combine;
635
- function combineWithAllErrors(resultList) {
636
- return combineResultListWithAllErrors(resultList);
637
- }
638
- Result2.combineWithAllErrors = combineWithAllErrors;
639
- })(Result || (Result = {}));
640
- function ok(value) {
641
- return new Ok(value);
642
- }
643
- function err(err2) {
644
- return new Err(err2);
645
- }
646
- class Ok {
647
- constructor(value) {
648
- this.value = value;
649
- }
650
- isOk() {
651
- return true;
652
- }
653
- isErr() {
654
- return !this.isOk();
655
- }
656
- map(f) {
657
- return ok(f(this.value));
658
- }
659
- mapErr(_f) {
660
- return ok(this.value);
661
- }
662
- andThen(f) {
663
- return f(this.value);
664
- }
665
- andThrough(f) {
666
- return f(this.value).map((_value) => this.value);
667
- }
668
- andTee(f) {
669
- try {
670
- f(this.value);
671
- } catch (e) {}
672
- return ok(this.value);
673
- }
674
- orTee(_f) {
675
- return ok(this.value);
676
- }
677
- orElse(_f) {
678
- return ok(this.value);
679
- }
680
- asyncAndThen(f) {
681
- return f(this.value);
682
- }
683
- asyncAndThrough(f) {
684
- return f(this.value).map(() => this.value);
685
- }
686
- asyncMap(f) {
687
- return ResultAsync.fromSafePromise(f(this.value));
688
- }
689
- unwrapOr(_v) {
690
- return this.value;
691
- }
692
- match(ok2, _err) {
693
- return ok2(this.value);
694
- }
695
- safeUnwrap() {
696
- const value = this.value;
697
- return function* () {
698
- return value;
699
- }();
700
- }
701
- _unsafeUnwrap(_) {
702
- return this.value;
703
- }
704
- _unsafeUnwrapErr(config) {
705
- throw createNeverThrowError("Called `_unsafeUnwrapErr` on an Ok", this, config);
706
- }
707
- *[Symbol.iterator]() {
708
- return this.value;
709
- }
710
320
  }
711
321
 
712
- class Err {
713
- constructor(error) {
714
- this.error = error;
715
- }
716
- isOk() {
717
- return false;
718
- }
719
- isErr() {
720
- return !this.isOk();
721
- }
722
- map(_f) {
723
- return err(this.error);
724
- }
725
- mapErr(f) {
726
- return err(f(this.error));
727
- }
728
- andThrough(_f) {
729
- return err(this.error);
730
- }
731
- andTee(_f) {
732
- return err(this.error);
733
- }
734
- orTee(f) {
735
- try {
736
- f(this.error);
737
- } catch (e) {}
738
- return err(this.error);
739
- }
740
- andThen(_f) {
741
- return err(this.error);
742
- }
743
- orElse(f) {
744
- return f(this.error);
745
- }
746
- asyncAndThen(_f) {
747
- return errAsync(this.error);
748
- }
749
- asyncAndThrough(_f) {
750
- return errAsync(this.error);
751
- }
752
- asyncMap(_f) {
753
- return errAsync(this.error);
754
- }
755
- unwrapOr(v) {
756
- return v;
757
- }
758
- match(_ok, err2) {
759
- return err2(this.error);
760
- }
761
- safeUnwrap() {
762
- const error = this.error;
763
- return function* () {
764
- yield err(error);
765
- throw new Error("Do not use this generator out of `safeTry`");
766
- }();
767
- }
768
- _unsafeUnwrap(config) {
769
- throw createNeverThrowError("Called `_unsafeUnwrap` on an Err", this, config);
770
- }
771
- _unsafeUnwrapErr(_) {
772
- return this.error;
773
- }
774
- *[Symbol.iterator]() {
775
- const self = this;
776
- yield self;
777
- return self;
778
- }
779
- }
780
- var fromThrowable = Result.fromThrowable;
322
+ // src/VexConnection.ts
323
+ import { err, ok, ResultAsync } from "neverthrow";
781
324
 
782
325
  // src/VexPacketBase.ts
783
326
  var MATCH_STATUS_ALT_ACK = 167;
@@ -872,79 +415,115 @@ class CrcGenerator {
872
415
  }
873
416
  }
874
417
 
875
- // src/VexPacketModels.ts
876
- var exports_VexPacketModels = {};
877
- __export(exports_VexPacketModels, {
878
- WriteKeyValueReplyD2HPacket: () => WriteKeyValueReplyD2HPacket,
879
- WriteKeyValueH2DPacket: () => WriteKeyValueH2DPacket,
880
- WriteFileReplyD2HPacket: () => WriteFileReplyD2HPacket,
881
- WriteFileH2DPacket: () => WriteFileH2DPacket,
882
- UpdateMatchModeH2DPacket: () => UpdateMatchModeH2DPacket,
883
- SystemVersionReplyD2HPacket: () => SystemVersionReplyD2HPacket,
884
- SystemVersionH2DPacket: () => SystemVersionH2DPacket,
885
- SendDashTouchReplyD2HPacket: () => SendDashTouchReplyD2HPacket,
886
- SendDashTouchH2DPacket: () => SendDashTouchH2DPacket,
887
- SelectDashReplyD2HPacket: () => SelectDashReplyD2HPacket,
888
- SelectDashH2DPacket: () => SelectDashH2DPacket,
889
- ScreenCaptureReplyD2HPacket: () => ScreenCaptureReplyD2HPacket,
890
- ScreenCaptureH2DPacket: () => ScreenCaptureH2DPacket,
891
- ReadLogPageReplyD2HPacket: () => ReadLogPageReplyD2HPacket,
892
- ReadLogPageH2DPacket: () => ReadLogPageH2DPacket,
893
- ReadKeyValueReplyD2HPacket: () => ReadKeyValueReplyD2HPacket,
894
- ReadKeyValueH2DPacket: () => ReadKeyValueH2DPacket,
895
- ReadFileReplyD2HPacket: () => ReadFileReplyD2HPacket,
896
- ReadFileH2DPacket: () => ReadFileH2DPacket,
897
- Query1ReplyD2HPacket: () => Query1ReplyD2HPacket,
898
- Query1H2DPacket: () => Query1H2DPacket,
899
- MatchStatusReplyD2HPacket: () => MatchStatusReplyD2HPacket,
900
- MatchModeReplyD2HPacket: () => MatchModeReplyD2HPacket,
901
- LoadFileActionReplyD2HPacket: () => LoadFileActionReplyD2HPacket,
902
- LoadFileActionH2DPacket: () => LoadFileActionH2DPacket,
903
- LinkFileReplyD2HPacket: () => LinkFileReplyD2HPacket,
904
- LinkFileH2DPacket: () => LinkFileH2DPacket,
905
- InitFileTransferReplyD2HPacket: () => InitFileTransferReplyD2HPacket,
906
- InitFileTransferH2DPacket: () => InitFileTransferH2DPacket,
907
- GetSystemStatusReplyD2HPacket: () => GetSystemStatusReplyD2HPacket,
908
- GetSystemStatusH2DPacket: () => GetSystemStatusH2DPacket,
909
- GetSystemFlagsReplyD2HPacket: () => GetSystemFlagsReplyD2HPacket,
910
- GetSystemFlagsH2DPacket: () => GetSystemFlagsH2DPacket,
911
- GetSlot5to8InfoReplyD2HPacket: () => GetSlot5to8InfoReplyD2HPacket,
912
- GetSlot5to8InfoH2DPacket: () => GetSlot5to8InfoH2DPacket,
913
- GetSlot1to4InfoReplyD2HPacket: () => GetSlot1to4InfoReplyD2HPacket,
914
- GetSlot1to4InfoH2DPacket: () => GetSlot1to4InfoH2DPacket,
915
- GetRadioStatusReplyD2HPacket: () => GetRadioStatusReplyD2HPacket,
916
- GetRadioStatusH2DPacket: () => GetRadioStatusH2DPacket,
917
- GetRadioModeH2DPacket: () => GetRadioModeH2DPacket,
918
- GetProgramSlotInfoReplyD2HPacket: () => GetProgramSlotInfoReplyD2HPacket,
919
- GetProgramSlotInfoH2DPacket: () => GetProgramSlotInfoH2DPacket,
920
- GetMatchStatusH2DPacket: () => GetMatchStatusH2DPacket,
921
- GetLogCountReplyD2HPacket: () => GetLogCountReplyD2HPacket,
922
- GetLogCountH2DPacket: () => GetLogCountH2DPacket,
923
- GetFileMetadataReplyD2HPacket: () => GetFileMetadataReplyD2HPacket,
924
- GetFileMetadataH2DPacket: () => GetFileMetadataH2DPacket,
925
- GetFdtStatusReplyD2HPacket: () => GetFdtStatusReplyD2HPacket,
926
- GetFdtStatusH2DPacket: () => GetFdtStatusH2DPacket,
927
- GetDirectoryFileCountReplyD2HPacket: () => GetDirectoryFileCountReplyD2HPacket,
928
- GetDirectoryFileCountH2DPacket: () => GetDirectoryFileCountH2DPacket,
929
- GetDirectoryEntryReplyD2HPacket: () => GetDirectoryEntryReplyD2HPacket,
930
- GetDirectoryEntryH2DPacket: () => GetDirectoryEntryH2DPacket,
931
- GetDeviceStatusReplyD2HPacket: () => GetDeviceStatusReplyD2HPacket,
932
- GetDeviceStatusH2DPacket: () => GetDeviceStatusH2DPacket,
933
- FileFormatReplyD2HPacket: () => FileFormatReplyD2HPacket,
934
- FileFormatH2DPacket: () => FileFormatH2DPacket,
935
- FileControlReplyD2HPacket: () => FileControlReplyD2HPacket,
936
- FileControlH2DPacket: () => FileControlH2DPacket,
937
- FileClearUpReplyD2HPacket: () => FileClearUpReplyD2HPacket,
938
- FileClearUpH2DPacket: () => FileClearUpH2DPacket,
939
- FactoryStatusReplyD2HPacket: () => FactoryStatusReplyD2HPacket,
940
- FactoryStatusH2DPacket: () => FactoryStatusH2DPacket,
941
- FactoryEnableReplyD2HPacket: () => FactoryEnableReplyD2HPacket,
942
- FactoryEnableH2DPacket: () => FactoryEnableH2DPacket,
943
- ExitFileTransferReplyD2HPacket: () => ExitFileTransferReplyD2HPacket,
944
- ExitFileTransferH2DPacket: () => ExitFileTransferH2DPacket,
945
- EraseFileReplyD2HPacket: () => EraseFileReplyD2HPacket,
946
- EraseFileH2DPacket: () => EraseFileH2DPacket
947
- });
418
+ // src/VexPacketEncoder.ts
419
+ var textEncoder = new TextEncoder;
420
+ var HEADER_TO_DEVICE = Uint8Array.of(201, 54, 184, 71);
421
+ function encodeFixedText(value, field, maxBytes) {
422
+ const encoded = textEncoder.encode(value);
423
+ if (encoded.byteLength > maxBytes) {
424
+ throw new RangeError(`${field} must be at most ${maxBytes} UTF-8 bytes`);
425
+ }
426
+ return encoded;
427
+ }
428
+
429
+ class PacketEncoder {
430
+ static HEADERS_LENGTH = 4;
431
+ static HEADER_TO_DEVICE = [201, 54, 184, 71];
432
+ static HEADER_TO_HOST = [170, 85];
433
+ static J2000_EPOCH = 946684800;
434
+ vexVersion = 0;
435
+ crcgen = new CrcGenerator;
436
+ allPacketsTable = new Map;
437
+ static getInstance() {
438
+ Packet.ENCODER ??= new PacketEncoder;
439
+ return Packet.ENCODER;
440
+ }
441
+ constructor() {}
442
+ registerPacketTypes(types) {
443
+ for (const type of types) {
444
+ let byExtendedId = this.allPacketsTable.get(type.COMMAND_ID);
445
+ if (byExtendedId === undefined) {
446
+ byExtendedId = new Map;
447
+ this.allPacketsTable.set(type.COMMAND_ID, byExtendedId);
448
+ }
449
+ byExtendedId.set(type.COMMAND_EXTENDED_ID, type);
450
+ }
451
+ }
452
+ getPacketType(commandId, commandExtendedId) {
453
+ if (commandId === undefined)
454
+ return;
455
+ return this.allPacketsTable.get(commandId)?.get(commandExtendedId);
456
+ }
457
+ createHeader(buf) {
458
+ if (buf === undefined || buf.byteLength < PacketEncoder.HEADERS_LENGTH) {
459
+ buf = new ArrayBuffer(PacketEncoder.HEADERS_LENGTH);
460
+ }
461
+ const h = new Uint8Array(buf);
462
+ h.set(HEADER_TO_DEVICE);
463
+ return h;
464
+ }
465
+ cdcCommand(cmd) {
466
+ const h = new Uint8Array(5);
467
+ h.set(HEADER_TO_DEVICE);
468
+ h[4] = cmd;
469
+ return h;
470
+ }
471
+ cdcCommandWithData(cmd, data) {
472
+ const h = new Uint8Array(6 + data.length);
473
+ h.set(HEADER_TO_DEVICE);
474
+ h[4] = cmd;
475
+ h[5] = data.length;
476
+ h.set(data, 6);
477
+ return h;
478
+ }
479
+ cdc2Command(cmd, ext) {
480
+ const h = new Uint8Array(9);
481
+ h.set(HEADER_TO_DEVICE);
482
+ h[4] = cmd;
483
+ h[5] = ext;
484
+ h[6] = 0;
485
+ this.appendCrc16(h);
486
+ return h;
487
+ }
488
+ cdc2CommandBufferLength(data) {
489
+ return PacketEncoder.HEADERS_LENGTH + data.length + 5 + (data.length > 127 ? 1 : 0);
490
+ }
491
+ cdc2CommandWithData(cmd, ext, data) {
492
+ const h = new Uint8Array(this.cdc2CommandBufferLength(data));
493
+ h.set(HEADER_TO_DEVICE);
494
+ h[4] = cmd;
495
+ h[5] = ext;
496
+ if (data.length < 128) {
497
+ h[6] = data.length;
498
+ h.set(data, 7);
499
+ } else {
500
+ h[6] = data.length >>> 8 | 128;
501
+ h[7] = data.length & 255;
502
+ h.set(data, 8);
503
+ }
504
+ this.appendCrc16(h);
505
+ return h;
506
+ }
507
+ appendCrc16(h) {
508
+ const crc = this.crcgen.crc16(h.subarray(0, h.length - 2), 0);
509
+ h[h.length - 2] = crc >>> 8;
510
+ h[h.length - 1] = crc & 255;
511
+ }
512
+ validateHeader(data) {
513
+ return data[0] === PacketEncoder.HEADER_TO_HOST[0] && data[1] === PacketEncoder.HEADER_TO_HOST[1];
514
+ }
515
+ validateMessageCdc(data) {
516
+ const crc = (data[data.byteLength - 2] << 8) + data[data.byteLength - 1];
517
+ return this.crcgen.crc16(data.subarray(0, data.byteLength - 2), 0) === crc;
518
+ }
519
+ getPayloadSize(data) {
520
+ const a = data[3];
521
+ return (a & 128) === 0 ? a : ((a & 127) << 8) + data[4];
522
+ }
523
+ getHostHeaderLength(data) {
524
+ return (data[3] & 128) === 0 ? 4 : 5;
525
+ }
526
+ }
948
527
 
949
528
  // src/VexFirmwareVersion.ts
950
529
  class VexFirmwareVersion {
@@ -1015,7 +594,7 @@ class PacketView extends DataView {
1015
594
  super(buffer, offset, length);
1016
595
  }
1017
596
  static fromPacket(packet) {
1018
- const view = new PacketView(packet.data.buffer, packet.data.byteOffset);
597
+ const view = new PacketView(packet.data.buffer, packet.data.byteOffset, packet.data.byteLength);
1019
598
  view.position = packet.ackIndex + 1;
1020
599
  return view;
1021
600
  }
@@ -1077,12 +656,12 @@ class PacketView extends DataView {
1077
656
  }
1078
657
 
1079
658
  // src/VexPacketModels.ts
1080
- var textEncoder = new TextEncoder;
659
+ var textEncoder2 = new TextEncoder;
1081
660
  function filePayload(a, b, fileName) {
1082
661
  const payload = new Uint8Array(26);
1083
662
  payload[0] = a;
1084
663
  payload[1] = b;
1085
- payload.set(encodeFixedText(fileName, "Filename", 23), 2);
664
+ payload.set(encodeFixedText(fileName, "Filename", 24), 2);
1086
665
  return payload;
1087
666
  }
1088
667
  var clamp100 = (value) => value !== undefined && value > 100 ? 100 : value;
@@ -1150,7 +729,7 @@ class InitFileTransferH2DPacket extends DeviceBoundPacket {
1150
729
  const timestamp = (Date.now() / 1000 >>> 0) - PacketEncoder.J2000_EPOCH;
1151
730
  view.setUint32(20, timestamp, true);
1152
731
  payload.set(version.toUint8Array(), 24);
1153
- payload.set(encodeFixedText(name, "Filename", 23), 28);
732
+ payload.set(encodeFixedText(name, "Filename", 24), 28);
1154
733
  super(payload);
1155
734
  }
1156
735
  }
@@ -1345,7 +924,7 @@ class WriteKeyValueH2DPacket extends DeviceBoundPacket {
1345
924
  static COMMAND_EXTENDED_ID = 47;
1346
925
  constructor(key, value) {
1347
926
  const strk = encodeFixedText(key, "Key", 31);
1348
- const strv = textEncoder.encode(value);
927
+ const strv = textEncoder2.encode(value);
1349
928
  if (strk.byteLength + strv.byteLength + 20 > 32767) {
1350
929
  throw new RangeError("Key and value are too large for a protocol packet");
1351
930
  }
@@ -1508,7 +1087,7 @@ class ReadFileReplyD2HPacket extends HostBoundPacket {
1508
1087
  const view = PacketView.fromPacket(this);
1509
1088
  this.addr = view.nextUint32();
1510
1089
  this.length = this.payloadSize - 8;
1511
- this.buf = this.data.slice(view.position, view.position + this.length).buffer;
1090
+ this.buf = this.data.subarray(view.position, view.position + this.length);
1512
1091
  }
1513
1092
  }
1514
1093
 
@@ -1808,185 +1387,95 @@ class ReadKeyValueReplyD2HPacket extends HostBoundPacket {
1808
1387
  value;
1809
1388
  constructor(data) {
1810
1389
  super(data);
1811
- this.value = PacketView.fromPacket(this).nextVarNTBS(255);
1812
- }
1813
- }
1814
-
1815
- class WriteKeyValueReplyD2HPacket extends HostBoundPacket {
1816
- static COMMAND_ID = 86;
1817
- static COMMAND_EXTENDED_ID = 47;
1818
- }
1819
-
1820
- class GetSlot1to4InfoReplyD2HPacket extends HostBoundPacket {
1821
- static COMMAND_ID = 86;
1822
- static COMMAND_EXTENDED_ID = 49;
1823
- slotFlags;
1824
- slots;
1825
- constructor(data, start = 1) {
1826
- super(data);
1827
- const view = PacketView.fromPacket(this);
1828
- this.slotFlags = view.nextUint8();
1829
- this.slots = [];
1830
- for (let i = 0;i < 4; i++) {
1831
- if ((this.slotFlags & 1 << start - 1 + i) === 0)
1832
- continue;
1833
- const icon = view.nextUint16();
1834
- const nameLen = view.nextUint8();
1835
- this.slots.push({
1836
- slot: start + i,
1837
- icon,
1838
- name: view.nextString(nameLen)
1839
- });
1840
- }
1841
- }
1842
- }
1843
-
1844
- class GetSlot5to8InfoReplyD2HPacket extends GetSlot1to4InfoReplyD2HPacket {
1845
- static COMMAND_ID = 86;
1846
- static COMMAND_EXTENDED_ID = 50;
1847
- slotStartIndex = 5;
1848
- constructor(data) {
1849
- super(data, 5);
1850
- }
1851
- }
1852
-
1853
- class FactoryStatusReplyD2HPacket extends HostBoundPacket {
1854
- static COMMAND_ID = 86;
1855
- static COMMAND_EXTENDED_ID = 241;
1856
- status;
1857
- percent;
1858
- constructor(data) {
1859
- super(data);
1860
- const view = PacketView.fromPacket(this);
1861
- this.status = view.nextUint8();
1862
- this.percent = view.nextUint8();
1863
- }
1864
- }
1865
-
1866
- class FactoryEnableReplyD2HPacket extends HostBoundPacket {
1867
- static COMMAND_ID = 86;
1868
- static COMMAND_EXTENDED_ID = 255;
1869
- }
1870
-
1871
- // src/VexPacketEncoder.ts
1872
- var textEncoder2 = new TextEncoder;
1873
- var HEADER_TO_DEVICE = Uint8Array.of(201, 54, 184, 71);
1874
- function encodeFixedText(value, field, maxBytes) {
1875
- const encoded = textEncoder2.encode(value);
1876
- if (encoded.byteLength > maxBytes) {
1877
- throw new RangeError(`${field} must be at most ${maxBytes} UTF-8 bytes`);
1878
- }
1879
- return encoded;
1880
- }
1881
-
1882
- class PacketEncoder {
1883
- static HEADERS_LENGTH = 4;
1884
- static HEADER_TO_DEVICE = [201, 54, 184, 71];
1885
- static HEADER_TO_HOST = [170, 85];
1886
- static J2000_EPOCH = 946684800;
1887
- vexVersion = 0;
1888
- crcgen = new CrcGenerator;
1889
- allPacketsTable = new Map;
1890
- static getInstance() {
1891
- Packet.ENCODER ??= new PacketEncoder;
1892
- return Packet.ENCODER;
1893
- }
1894
- constructor() {
1895
- for (const packet of Object.values(exports_VexPacketModels)) {
1896
- if (typeof packet === "function" && packet.prototype instanceof HostBoundPacket) {
1897
- const type = packet;
1898
- let byExtendedId = this.allPacketsTable.get(type.COMMAND_ID);
1899
- if (byExtendedId === undefined) {
1900
- byExtendedId = new Map;
1901
- this.allPacketsTable.set(type.COMMAND_ID, byExtendedId);
1902
- }
1903
- byExtendedId.set(type.COMMAND_EXTENDED_ID, type);
1904
- }
1905
- }
1906
- }
1907
- getPacketType(commandId, commandExtendedId) {
1908
- if (commandId === undefined)
1909
- return;
1910
- return this.allPacketsTable.get(commandId)?.get(commandExtendedId);
1911
- }
1912
- createHeader(buf) {
1913
- if (buf === undefined || buf.byteLength < PacketEncoder.HEADERS_LENGTH) {
1914
- buf = new ArrayBuffer(PacketEncoder.HEADERS_LENGTH);
1915
- }
1916
- const h = new Uint8Array(buf);
1917
- h.set(HEADER_TO_DEVICE);
1918
- return h;
1919
- }
1920
- cdcCommand(cmd) {
1921
- const h = new Uint8Array(5);
1922
- h.set(HEADER_TO_DEVICE);
1923
- h[4] = cmd;
1924
- return h;
1925
- }
1926
- cdcCommandWithData(cmd, data) {
1927
- const h = new Uint8Array(6 + data.length);
1928
- h.set(HEADER_TO_DEVICE);
1929
- h[4] = cmd;
1930
- h[5] = data.length;
1931
- h.set(data, 6);
1932
- return h;
1933
- }
1934
- cdc2Command(cmd, ext) {
1935
- const h = new Uint8Array(9);
1936
- h.set(HEADER_TO_DEVICE);
1937
- h[4] = cmd;
1938
- h[5] = ext;
1939
- h[6] = 0;
1940
- this.appendCrc16(h);
1941
- return h;
1942
- }
1943
- cdc2CommandBufferLength(data) {
1944
- return PacketEncoder.HEADERS_LENGTH + data.length + 5 + (data.length > 127 ? 1 : 0);
1945
- }
1946
- cdc2CommandWithData(cmd, ext, data) {
1947
- const h = new Uint8Array(this.cdc2CommandBufferLength(data));
1948
- h.set(HEADER_TO_DEVICE);
1949
- h[4] = cmd;
1950
- h[5] = ext;
1951
- if (data.length < 128) {
1952
- h[6] = data.length;
1953
- h.set(data, 7);
1954
- } else {
1955
- h[6] = data.length >>> 8 | 128;
1956
- h[7] = data.length & 255;
1957
- h.set(data, 8);
1958
- }
1959
- this.appendCrc16(h);
1960
- return h;
1961
- }
1962
- appendCrc16(h) {
1963
- const crc = this.crcgen.crc16(h.subarray(0, h.length - 2), 0);
1964
- h[h.length - 2] = crc >>> 8;
1965
- h[h.length - 1] = crc & 255;
1966
- }
1967
- validateHeader(data) {
1968
- return data[0] === PacketEncoder.HEADER_TO_HOST[0] && data[1] === PacketEncoder.HEADER_TO_HOST[1];
1390
+ this.value = PacketView.fromPacket(this).nextVarNTBS(255);
1969
1391
  }
1970
- validateMessageCdc(data) {
1971
- const crc = (data[data.byteLength - 2] << 8) + data[data.byteLength - 1];
1972
- return this.crcgen.crc16(data.subarray(0, data.byteLength - 2), 0) === crc;
1392
+ }
1393
+
1394
+ class WriteKeyValueReplyD2HPacket extends HostBoundPacket {
1395
+ static COMMAND_ID = 86;
1396
+ static COMMAND_EXTENDED_ID = 47;
1397
+ }
1398
+
1399
+ class GetSlot1to4InfoReplyD2HPacket extends HostBoundPacket {
1400
+ static COMMAND_ID = 86;
1401
+ static COMMAND_EXTENDED_ID = 49;
1402
+ slotFlags;
1403
+ slots;
1404
+ constructor(data, start = 1) {
1405
+ super(data);
1406
+ const view = PacketView.fromPacket(this);
1407
+ this.slotFlags = view.nextUint8();
1408
+ this.slots = [];
1409
+ for (let i = 0;i < 4; i++) {
1410
+ if ((this.slotFlags & 1 << start - 1 + i) === 0)
1411
+ continue;
1412
+ const icon = view.nextUint16();
1413
+ const nameLen = view.nextUint8();
1414
+ this.slots.push({
1415
+ slot: start + i,
1416
+ icon,
1417
+ name: view.nextString(nameLen)
1418
+ });
1419
+ }
1973
1420
  }
1974
- getPayloadSize(data) {
1975
- const a = data[3];
1976
- return (a & 128) === 0 ? a : ((a & 127) << 8) + data[4];
1421
+ }
1422
+
1423
+ class GetSlot5to8InfoReplyD2HPacket extends GetSlot1to4InfoReplyD2HPacket {
1424
+ static COMMAND_ID = 86;
1425
+ static COMMAND_EXTENDED_ID = 50;
1426
+ slotStartIndex = 5;
1427
+ constructor(data) {
1428
+ super(data, 5);
1977
1429
  }
1978
- getHostHeaderLength(data) {
1979
- return (data[3] & 128) === 0 ? 4 : 5;
1430
+ }
1431
+
1432
+ class FactoryStatusReplyD2HPacket extends HostBoundPacket {
1433
+ static COMMAND_ID = 86;
1434
+ static COMMAND_EXTENDED_ID = 241;
1435
+ status;
1436
+ percent;
1437
+ constructor(data) {
1438
+ super(data);
1439
+ const view = PacketView.fromPacket(this);
1440
+ this.status = view.nextUint8();
1441
+ this.percent = view.nextUint8();
1980
1442
  }
1981
1443
  }
1982
1444
 
1983
- // src/VexConnection.ts
1984
- var thePacketEncoder = PacketEncoder.getInstance();
1445
+ class FactoryEnableReplyD2HPacket extends HostBoundPacket {
1446
+ static COMMAND_ID = 86;
1447
+ static COMMAND_EXTENDED_ID = 255;
1448
+ }
1449
+
1450
+ // src/VexScreenCapture.ts
1985
1451
  var SCREEN_CAPTURE_HEIGHT = 272;
1986
1452
  var SCREEN_CAPTURE_WIDTH = 480;
1987
1453
  var SCREEN_CAPTURE_CHANNELS = 3;
1988
1454
  var SCREEN_CAPTURE_MESSAGE_WIDTH = 512;
1989
1455
  var SCREEN_CAPTURE_MESSAGE_CHANNELS = 4;
1456
+ var SCREEN_CAPTURE_FRAMEBUFFER_SIZE = SCREEN_CAPTURE_MESSAGE_WIDTH * SCREEN_CAPTURE_HEIGHT * SCREEN_CAPTURE_MESSAGE_CHANNELS;
1457
+ function convertScreenCapture(framebuffer) {
1458
+ if (framebuffer.length !== SCREEN_CAPTURE_FRAMEBUFFER_SIZE) {
1459
+ throw new Error(`bad screen capture framebuffer size: ${framebuffer.length}; expected ${SCREEN_CAPTURE_FRAMEBUFFER_SIZE}`);
1460
+ }
1461
+ const pixels = new Uint8Array(SCREEN_CAPTURE_WIDTH * SCREEN_CAPTURE_HEIGHT * SCREEN_CAPTURE_CHANNELS);
1462
+ let source = 0;
1463
+ let target = 0;
1464
+ for (let row = 0;row < SCREEN_CAPTURE_HEIGHT; row++) {
1465
+ for (let column = 0;column < SCREEN_CAPTURE_WIDTH; column++) {
1466
+ pixels[target] = framebuffer[source + 2];
1467
+ pixels[target + 1] = framebuffer[source + 1];
1468
+ pixels[target + 2] = framebuffer[source];
1469
+ source += SCREEN_CAPTURE_MESSAGE_CHANNELS;
1470
+ target += SCREEN_CAPTURE_CHANNELS;
1471
+ }
1472
+ source += (SCREEN_CAPTURE_MESSAGE_WIDTH - SCREEN_CAPTURE_WIDTH) * SCREEN_CAPTURE_MESSAGE_CHANNELS;
1473
+ }
1474
+ return pixels;
1475
+ }
1476
+
1477
+ // src/VexConnection.ts
1478
+ var thePacketEncoder = PacketEncoder.getInstance();
1990
1479
 
1991
1480
  class VexSerialConnection extends VexEventTarget {
1992
1481
  filters = [{ usbVendorId: 10376 }];
@@ -1994,12 +1483,31 @@ class VexSerialConnection extends VexEventTarget {
1994
1483
  reader;
1995
1484
  port;
1996
1485
  serial;
1997
- callbacksQueue = [];
1486
+ pendingCallbacks = new Map;
1487
+ rawCallbacks = {
1488
+ head: undefined,
1489
+ tail: undefined
1490
+ };
1491
+ pendingCommandTails = new Map;
1998
1492
  _onPortDisconnect = null;
1999
1493
  _closePromise = null;
1494
+ _openPromise = null;
1495
+ _isClosing = false;
2000
1496
  _wasConnected = false;
2001
1497
  fileTransferTail = Promise.resolve();
2002
1498
  fileTransferDepth = 0;
1499
+ get callbacksQueue() {
1500
+ const callbacks = [];
1501
+ for (const queue of this.pendingCallbacks.values()) {
1502
+ for (let callback = queue.head;callback; callback = callback.next) {
1503
+ callbacks.push(callback);
1504
+ }
1505
+ }
1506
+ for (let callback = this.rawCallbacks.head;callback; callback = callback.next) {
1507
+ callbacks.push(callback);
1508
+ }
1509
+ return callbacks;
1510
+ }
2003
1511
  get isConnected() {
2004
1512
  return this.port !== undefined && this.reader !== undefined && this.writer !== undefined;
2005
1513
  }
@@ -2011,27 +1519,45 @@ class VexSerialConnection extends VexEventTarget {
2011
1519
  this.serial = serial;
2012
1520
  }
2013
1521
  reportWarning(message, details) {
2014
- this.emit("warning", { message, details });
1522
+ this.emitSafely("warning", {
1523
+ message,
1524
+ details
1525
+ });
1526
+ }
1527
+ emitSafely(eventName, data) {
1528
+ try {
1529
+ this.emit(eventName, data);
1530
+ } catch {}
2015
1531
  }
2016
1532
  async close() {
2017
1533
  if (this._closePromise)
2018
1534
  return this._closePromise;
2019
- if (!this._hasOpenResources())
2020
- return;
2021
- this._closePromise = this._doClose();
1535
+ this._isClosing = true;
1536
+ const closing = this._closeAfterOpen();
1537
+ this._closePromise = closing;
2022
1538
  try {
2023
- await this._closePromise;
1539
+ await closing;
2024
1540
  } finally {
2025
- this._closePromise = null;
1541
+ if (this._closePromise === closing)
1542
+ this._closePromise = null;
1543
+ this._isClosing = false;
2026
1544
  }
2027
1545
  }
1546
+ async _closeAfterOpen() {
1547
+ const opening = this._openPromise;
1548
+ if (opening !== null)
1549
+ await opening;
1550
+ if (!this._hasOpenResources())
1551
+ return;
1552
+ await this._doClose();
1553
+ }
2028
1554
  _hasOpenResources() {
2029
- return this.port !== undefined || this.reader !== undefined || this.writer !== undefined || this._onPortDisconnect !== null || this.callbacksQueue.length > 0;
1555
+ return this.port !== undefined || this.reader !== undefined || this.writer !== undefined || this._onPortDisconnect !== null || this.hasPendingCallbacks();
2030
1556
  }
2031
1557
  async _doClose() {
2032
- for (const callback of this.callbacksQueue.splice(0)) {
1558
+ for (const callback of this.drainPendingCallbacks()) {
2033
1559
  clearTimeout(callback.timeout);
2034
- callback.callback(255 /* CDC2_NACK */);
1560
+ callback.callback(258 /* NOT_CONNECTED */);
2035
1561
  }
2036
1562
  const onDisconnect = this._onPortDisconnect;
2037
1563
  this._onPortDisconnect = null;
@@ -2080,11 +1606,22 @@ class VexSerialConnection extends VexEventTarget {
2080
1606
  }
2081
1607
  if (this._wasConnected) {
2082
1608
  this._wasConnected = false;
2083
- this.emit("disconnected", undefined);
1609
+ this.emitSafely("disconnected", undefined);
2084
1610
  }
2085
1611
  }
2086
1612
  open(use = 0, askUser = true) {
2087
- return new ResultAsync(this._open(use, askUser));
1613
+ if (this._openPromise !== null)
1614
+ return new ResultAsync(this._openPromise);
1615
+ const opening = this._open(use, askUser);
1616
+ this._openPromise = opening;
1617
+ opening.then(() => {
1618
+ if (this._openPromise === opening)
1619
+ this._openPromise = null;
1620
+ }, () => {
1621
+ if (this._openPromise === opening)
1622
+ this._openPromise = null;
1623
+ });
1624
+ return new ResultAsync(opening);
2088
1625
  }
2089
1626
  async _open(use, askUser) {
2090
1627
  if (this._closePromise !== null)
@@ -2117,38 +1654,63 @@ class VexSerialConnection extends VexEventTarget {
2117
1654
  this.reader = this.port.readable.getReader();
2118
1655
  this.startReader();
2119
1656
  this._wasConnected = true;
2120
- this.emit("connected", undefined);
1657
+ this.emitSafely("connected", undefined);
2121
1658
  return ok("opened");
2122
1659
  } catch (e) {
2123
- await this.close();
1660
+ await this._doClose();
2124
1661
  return err(toVexSerialError(e, "io"));
2125
1662
  }
2126
1663
  }
2127
1664
  async writeDataAsync(rawData, timeout = 1000) {
1665
+ if (rawData instanceof DeviceBoundPacket) {
1666
+ return this.serializeCommand(rawData.commandId, rawData.commandExtendedId, () => this.writeDataAsyncUnserialized(rawData, timeout));
1667
+ }
1668
+ return this.writeDataAsyncUnserialized(rawData, timeout);
1669
+ }
1670
+ async serializeCommand(commandId, commandExtendedId, operation) {
1671
+ const key = `${commandId}:${commandExtendedId ?? ""}`;
1672
+ const previous = this.pendingCommandTails.get(key) ?? Promise.resolve();
1673
+ let release = () => {};
1674
+ const current = new Promise((resolve) => {
1675
+ release = resolve;
1676
+ });
1677
+ this.pendingCommandTails.set(key, current);
1678
+ await previous;
1679
+ try {
1680
+ return await operation();
1681
+ } finally {
1682
+ release();
1683
+ if (this.pendingCommandTails.get(key) === current) {
1684
+ this.pendingCommandTails.delete(key);
1685
+ }
1686
+ }
1687
+ }
1688
+ async writeDataAsyncUnserialized(rawData, timeout) {
2128
1689
  return new Promise((resolve) => {
2129
- if (this.writer === undefined) {
2130
- resolve(255 /* CDC2_NACK */);
1690
+ if (this.writer === undefined || this._isClosing) {
1691
+ resolve(258 /* NOT_CONNECTED */);
2131
1692
  return;
2132
1693
  }
2133
1694
  const data = rawData instanceof DeviceBoundPacket ? rawData.data : rawData;
1695
+ const queue = rawData instanceof DeviceBoundPacket ? this.getPendingQueue(rawData.commandId, rawData.commandExtendedId, true) : this.rawCallbacks;
2134
1696
  const cb = {
1697
+ active: true,
2135
1698
  callback: resolve,
2136
1699
  timeout: setTimeout(() => {
2137
- const index = this.callbacksQueue.indexOf(cb);
2138
- if (index === -1)
1700
+ if (!this.removePendingCallback(cb))
2139
1701
  return;
2140
- this.callbacksQueue.splice(index, 1);
2141
1702
  cb.callback(256 /* TIMEOUT */);
2142
1703
  }, timeout),
1704
+ next: undefined,
1705
+ previous: undefined,
1706
+ queue,
2143
1707
  wantedCommandId: rawData instanceof DeviceBoundPacket ? rawData.commandId : undefined,
2144
1708
  wantedCommandExId: rawData instanceof DeviceBoundPacket ? rawData.commandExtendedId : undefined
2145
1709
  };
2146
- this.callbacksQueue.push(cb);
1710
+ this.enqueuePendingCallback(cb);
2147
1711
  this.writer.write(data).catch(() => {
2148
- const index = this.callbacksQueue.indexOf(cb);
2149
- if (index === -1)
1712
+ if (!this.removePendingCallback(cb))
2150
1713
  return;
2151
- this.callbacksQueue.splice(index, 1);
2152
1714
  clearTimeout(cb.timeout);
2153
1715
  resolve(257 /* WRITE_ERROR */);
2154
1716
  });
@@ -2159,6 +1721,9 @@ class VexSerialConnection extends VexEventTarget {
2159
1721
  const result = await this.writeDataAsync(packet, timeout);
2160
1722
  if (result instanceof ReplyType)
2161
1723
  return ok(result);
1724
+ if (result === 258 /* NOT_CONNECTED */) {
1725
+ return err(new VexNotConnectedError);
1726
+ }
2162
1727
  return err(new VexProtocolError(expectedReplyMessage(packet, ReplyType, result), typeof result === "number" ? result : undefined));
2163
1728
  })());
2164
1729
  }
@@ -2169,56 +1734,48 @@ class VexSerialConnection extends VexEventTarget {
2169
1734
  const { value: readData, done: isDone } = await this.reader.read();
2170
1735
  if (isDone)
2171
1736
  throw new Error("No data");
2172
- cache = binaryArrayJoin(cache, readData);
1737
+ cache.append(readData);
2173
1738
  }
2174
- return cache;
2175
1739
  }
2176
1740
  async startReader() {
2177
- let cache = new Uint8Array([]);
1741
+ const cache = new ReceiveBuffer;
2178
1742
  let sliceIdx = 0;
2179
1743
  for (;; )
2180
1744
  try {
2181
- cache = await this.readData(cache, 5);
1745
+ await this.readData(cache, 5);
2182
1746
  sliceIdx = 0;
2183
- while (!thePacketEncoder.validateHeader(cache)) {
2184
- const nextHeader = cache.findIndex((byte, index) => index > 0 && byte === PacketEncoder.HEADER_TO_HOST[0] && cache[index + 1] === PacketEncoder.HEADER_TO_HOST[1]);
1747
+ while (!thePacketEncoder.validateHeader(cache.bytes)) {
1748
+ const bytes = cache.bytes;
1749
+ const nextHeader = bytes.findIndex((byte, index) => index > 0 && byte === PacketEncoder.HEADER_TO_HOST[0] && bytes[index + 1] === PacketEncoder.HEADER_TO_HOST[1]);
2185
1750
  if (nextHeader >= 0) {
2186
- cache = cache.slice(nextHeader);
1751
+ cache.discard(nextHeader);
2187
1752
  } else {
2188
- cache = cache.slice(cache.at(-1) === PacketEncoder.HEADER_TO_HOST[0] ? -1 : cache.length);
1753
+ cache.discard(bytes.at(-1) === PacketEncoder.HEADER_TO_HOST[0] ? -1 : bytes.length);
2189
1754
  }
2190
- cache = await this.readData(cache, 5);
1755
+ await this.readData(cache, 5);
2191
1756
  }
2192
- const payloadExpectedSize = thePacketEncoder.getPayloadSize(cache);
2193
- const n = thePacketEncoder.getHostHeaderLength(cache);
1757
+ const payloadExpectedSize = thePacketEncoder.getPayloadSize(cache.bytes);
1758
+ const n = thePacketEncoder.getHostHeaderLength(cache.bytes);
2194
1759
  const totalSize = n + payloadExpectedSize;
2195
- cache = await this.readData(cache, totalSize);
1760
+ await this.readData(cache, totalSize);
2196
1761
  sliceIdx = totalSize;
2197
- const cmdId = cache[2];
1762
+ const packet = cache.copy(totalSize);
1763
+ const cmdId = packet[2];
2198
1764
  const hasExtId = cmdId === 88 || cmdId === 86;
2199
- const cmdExId = hasExtId ? cache[n] : undefined;
2200
- const ack = cache[n + 1];
1765
+ const cmdExId = hasExtId ? packet[n] : undefined;
1766
+ const ack = packet[n + 1];
2201
1767
  if (hasExtId) {
2202
- if (!thePacketEncoder.validateMessageCdc(cache))
2203
- throw new Error("Invalid message CDC");
2204
- }
2205
- let matchIdx = -1;
2206
- let untypedIdx = -1;
2207
- for (let i = 0;i < this.callbacksQueue.length; i++) {
2208
- const candidate = this.callbacksQueue[i];
2209
- if (candidate.wantedCommandId === undefined && candidate.wantedCommandExId === undefined) {
2210
- if (untypedIdx === -1)
2211
- untypedIdx = i;
1768
+ if (!thePacketEncoder.validateMessageCdc(packet)) {
1769
+ this.reportWarning("discarding a reply with an invalid CDC CRC", {
1770
+ commandId: cmdId,
1771
+ commandExtendedId: cmdExId,
1772
+ ack
1773
+ });
2212
1774
  continue;
2213
1775
  }
2214
- if ((candidate.wantedCommandId === undefined || candidate.wantedCommandId === cmdId) && (candidate.wantedCommandExId === undefined || candidate.wantedCommandExId === cmdExId)) {
2215
- matchIdx = i;
2216
- break;
2217
- }
2218
1776
  }
2219
- if (matchIdx === -1)
2220
- matchIdx = untypedIdx;
2221
- if (matchIdx === -1) {
1777
+ const callbackInfo = this.shiftPendingCallback(cmdId, cmdExId) ?? this.shiftRawCallback();
1778
+ if (callbackInfo === undefined) {
2222
1779
  this.reportWarning("received a reply with no matching request", {
2223
1780
  commandId: cmdId,
2224
1781
  commandExtendedId: cmdExId,
@@ -2226,38 +1783,117 @@ class VexSerialConnection extends VexEventTarget {
2226
1783
  });
2227
1784
  continue;
2228
1785
  }
2229
- const callbackInfo = this.callbacksQueue[matchIdx];
2230
1786
  const wantedCmdId = callbackInfo.wantedCommandId;
2231
1787
  const wantedCmdExId = callbackInfo.wantedCommandExId;
2232
- const data = cache.slice(0, sliceIdx);
2233
1788
  const PackageType = thePacketEncoder.getPacketType(wantedCmdId, wantedCmdExId);
2234
1789
  if (wantedCmdId === undefined || PackageType === undefined) {
2235
1790
  if (wantedCmdId !== undefined) {
2236
1791
  this.reportWarning("no packet class is registered for the wanted command", { commandId: wantedCmdId, commandExtendedId: wantedCmdExId });
2237
1792
  }
2238
- callbackInfo.callback(data.buffer.slice(data.byteOffset, data.byteOffset + data.byteLength));
1793
+ callbackInfo.callback(packet.buffer);
2239
1794
  } else {
2240
- if (!hasExtId || PackageType.isValidPacket(data, n)) {
2241
- callbackInfo.callback(new PackageType(data));
1795
+ if (PackageType.isValidPacket(packet, n)) {
1796
+ callbackInfo.callback(new PackageType(packet));
2242
1797
  } else {
2243
1798
  this.reportWarning("reply failed packet validation; delivering its ack instead", { commandId: cmdId, commandExtendedId: cmdExId, ack });
2244
1799
  callbackInfo.callback(ack);
2245
1800
  }
2246
1801
  }
2247
1802
  clearTimeout(callbackInfo.timeout);
2248
- this.callbacksQueue.splice(matchIdx, 1);
2249
1803
  } catch (e) {
2250
1804
  if (!(e instanceof Error && e.message === "No data")) {
2251
1805
  this.reportWarning("reader loop stopped by a read error", {
2252
1806
  error: e,
2253
- pendingBytes: cache
1807
+ pendingBytes: cache.bytes
2254
1808
  });
2255
1809
  }
2256
1810
  await this.close();
2257
1811
  break;
2258
1812
  } finally {
2259
- cache = cache.slice(sliceIdx);
1813
+ cache.discard(sliceIdx);
1814
+ }
1815
+ }
1816
+ pendingKey(commandId, commandExtendedId) {
1817
+ return `${commandId}:${commandExtendedId ?? ""}`;
1818
+ }
1819
+ getPendingQueue(commandId, commandExtendedId, create) {
1820
+ const key = this.pendingKey(commandId, commandExtendedId);
1821
+ let queue = this.pendingCallbacks.get(key);
1822
+ if (queue === undefined && create) {
1823
+ const created = { head: undefined, tail: undefined };
1824
+ this.pendingCallbacks.set(key, created);
1825
+ queue = created;
1826
+ }
1827
+ return queue ?? { head: undefined, tail: undefined };
1828
+ }
1829
+ enqueuePendingCallback(callback) {
1830
+ const tail = callback.queue.tail;
1831
+ callback.previous = tail;
1832
+ if (tail === undefined)
1833
+ callback.queue.head = callback;
1834
+ else
1835
+ tail.next = callback;
1836
+ callback.queue.tail = callback;
1837
+ }
1838
+ removePendingCallback(callback) {
1839
+ if (!callback.active)
1840
+ return false;
1841
+ callback.active = false;
1842
+ const { queue, previous, next } = callback;
1843
+ if (previous === undefined)
1844
+ queue.head = next;
1845
+ else
1846
+ previous.next = next;
1847
+ if (next === undefined)
1848
+ queue.tail = previous;
1849
+ else
1850
+ next.previous = previous;
1851
+ callback.previous = undefined;
1852
+ callback.next = undefined;
1853
+ if (queue !== this.rawCallbacks && queue.head === undefined && callback.wantedCommandId !== undefined) {
1854
+ this.pendingCallbacks.delete(this.pendingKey(callback.wantedCommandId, callback.wantedCommandExId));
1855
+ }
1856
+ return true;
1857
+ }
1858
+ shiftPendingCallback(commandId, commandExtendedId) {
1859
+ const queue = this.getPendingQueue(commandId, commandExtendedId, false);
1860
+ const callback = queue.head;
1861
+ if (callback !== undefined)
1862
+ this.removePendingCallback(callback);
1863
+ return callback;
1864
+ }
1865
+ shiftRawCallback() {
1866
+ const callback = this.rawCallbacks.head;
1867
+ if (callback !== undefined)
1868
+ this.removePendingCallback(callback);
1869
+ return callback;
1870
+ }
1871
+ hasPendingCallbacks() {
1872
+ return this.pendingCallbacks.size > 0 || this.rawCallbacks.head !== undefined;
1873
+ }
1874
+ drainPendingCallbacks() {
1875
+ const callbacks = [];
1876
+ for (const queue of this.pendingCallbacks.values()) {
1877
+ for (let callback = queue.head;callback; ) {
1878
+ const next = callback.next;
1879
+ callback.active = false;
1880
+ callback.previous = undefined;
1881
+ callback.next = undefined;
1882
+ callbacks.push(callback);
1883
+ callback = next;
2260
1884
  }
1885
+ }
1886
+ for (let callback = this.rawCallbacks.head;callback; ) {
1887
+ const next = callback.next;
1888
+ callback.active = false;
1889
+ callback.previous = undefined;
1890
+ callback.next = undefined;
1891
+ callbacks.push(callback);
1892
+ callback = next;
1893
+ }
1894
+ this.pendingCallbacks.clear();
1895
+ this.rawCallbacks = { head: undefined, tail: undefined };
1896
+ return callbacks;
2261
1897
  }
2262
1898
  query1() {
2263
1899
  return this.request(new Query1H2DPacket, Query1ReplyD2HPacket, 100);
@@ -2375,7 +2011,9 @@ class V5SerialConnection extends VexSerialConnection {
2375
2011
  let bufferOffset = 0;
2376
2012
  const fileBuf = new Uint8Array(fileSize);
2377
2013
  while (bufferOffset < fileSize) {
2378
- const requestedSize = Math.min(bufferChunkSize, fileSize - bufferOffset);
2014
+ const remainingSize = fileSize - bufferOffset;
2015
+ const chunkSize = Math.min(bufferChunkSize, remainingSize);
2016
+ const requestedSize = chunkSize + 3 & ~3;
2379
2017
  const p2Result = await this.request(new ReadFileH2DPacket(nextAddress, requestedSize), ReadFileReplyD2HPacket, 3000);
2380
2018
  if (p2Result.isErr())
2381
2019
  throw p2Result.error;
@@ -2386,9 +2024,10 @@ class V5SerialConnection extends VexSerialConnection {
2386
2024
  if (p2.length <= 0 || p2.length > requestedSize || p2.buf.byteLength !== p2.length) {
2387
2025
  throw new VexTransferError(`ReadFileReplyD2HPacket returned invalid length ${p2.length}`);
2388
2026
  }
2389
- fileBuf.set(new Uint8Array(p2.buf), bufferOffset);
2390
- bufferOffset += p2.length;
2391
- nextAddress += p2.length;
2027
+ const receivedSize = Math.min(p2.length, remainingSize);
2028
+ fileBuf.set(p2.buf.subarray(0, receivedSize), bufferOffset);
2029
+ bufferOffset += receivedSize;
2030
+ nextAddress += receivedSize;
2392
2031
  progressCallback?.(bufferOffset, fileSize);
2393
2032
  }
2394
2033
  transferFailed = false;
@@ -2543,7 +2182,7 @@ class V5SerialConnection extends VexSerialConnection {
2543
2182
  filename: "screen",
2544
2183
  vendor: 15 /* SYS */,
2545
2184
  loadAddress: 0,
2546
- size: SCREEN_CAPTURE_MESSAGE_WIDTH * SCREEN_CAPTURE_HEIGHT * SCREEN_CAPTURE_MESSAGE_CHANNELS
2185
+ size: SCREEN_CAPTURE_FRAMEBUFFER_SIZE
2547
2186
  }, 2 /* FILE_TARGET_CBUF */, progressCallback);
2548
2187
  if (framebuffer.isErr())
2549
2188
  return err(framebuffer.error);
@@ -2568,34 +2207,60 @@ class V5SerialConnection extends VexSerialConnection {
2568
2207
  return this.request(new SelectDashH2DPacket(screen, port), SelectDashReplyD2HPacket);
2569
2208
  }
2570
2209
  }
2571
- function binaryArrayJoin(left, right) {
2572
- const leftSize = left != null ? left.byteLength : 0;
2573
- const rightSize = right != null ? right.byteLength : 0;
2574
- const all = new Uint8Array(leftSize + rightSize);
2575
- if (all.length === 0)
2576
- return new Uint8Array;
2577
- if (left != null)
2578
- all.set(new Uint8Array(left), 0);
2579
- if (right != null)
2580
- all.set(new Uint8Array(right), leftSize);
2581
- return all;
2210
+
2211
+ class ReceiveBuffer {
2212
+ storage = new Uint8Array(0);
2213
+ start = 0;
2214
+ end = 0;
2215
+ get byteLength() {
2216
+ return this.end - this.start;
2217
+ }
2218
+ get bytes() {
2219
+ return this.storage.subarray(this.start, this.end);
2220
+ }
2221
+ append(chunk) {
2222
+ this.reserve(chunk.byteLength);
2223
+ this.storage.set(chunk, this.end);
2224
+ this.end += chunk.byteLength;
2225
+ }
2226
+ copy(length) {
2227
+ return this.bytes.slice(0, length);
2228
+ }
2229
+ discard(length) {
2230
+ this.start += length < 0 ? this.byteLength + length : length;
2231
+ if (this.start < 0)
2232
+ this.start = 0;
2233
+ if (this.start > this.end)
2234
+ this.start = this.end;
2235
+ if (this.start === this.end) {
2236
+ this.start = 0;
2237
+ this.end = 0;
2238
+ } else if (this.start >= 4096 && this.start * 2 >= this.storage.length) {
2239
+ this.storage.copyWithin(0, this.start, this.end);
2240
+ this.end -= this.start;
2241
+ this.start = 0;
2242
+ }
2243
+ }
2244
+ reserve(additional) {
2245
+ const required = this.byteLength + additional;
2246
+ if (this.storage.length - this.end >= additional)
2247
+ return;
2248
+ if (this.storage.length >= required) {
2249
+ this.storage.copyWithin(0, this.start, this.end);
2250
+ this.end = this.byteLength;
2251
+ this.start = 0;
2252
+ return;
2253
+ }
2254
+ const storage = new Uint8Array(Math.max(required, Math.max(64, this.storage.length * 2)));
2255
+ storage.set(this.bytes);
2256
+ this.end = this.byteLength;
2257
+ this.start = 0;
2258
+ this.storage = storage;
2259
+ }
2582
2260
  }
2583
2261
  function getTransferChunkSize(windowSize) {
2584
2262
  return windowSize > 0 && windowSize <= USER_PROG_CHUNK_SIZE ? windowSize : USER_PROG_CHUNK_SIZE;
2585
2263
  }
2586
- function convertScreenCapture(framebuffer) {
2587
- const pixels = new Uint8Array(SCREEN_CAPTURE_WIDTH * SCREEN_CAPTURE_HEIGHT * SCREEN_CAPTURE_CHANNELS);
2588
- for (let row = 0;row < SCREEN_CAPTURE_HEIGHT; row++) {
2589
- for (let column = 0;column < SCREEN_CAPTURE_WIDTH; column++) {
2590
- const source = (row * SCREEN_CAPTURE_MESSAGE_WIDTH + column) * SCREEN_CAPTURE_MESSAGE_CHANNELS;
2591
- const target = (row * SCREEN_CAPTURE_WIDTH + column) * SCREEN_CAPTURE_CHANNELS;
2592
- pixels[target] = framebuffer[source + 2] ?? 0;
2593
- pixels[target + 1] = framebuffer[source + 1] ?? 0;
2594
- pixels[target + 2] = framebuffer[source] ?? 0;
2595
- }
2596
- }
2597
- return pixels;
2598
- }
2599
2264
  function wrapTransfer(conn, operation) {
2600
2265
  return new ResultAsync(conn.withFileTransfer(async () => {
2601
2266
  try {
@@ -2618,7 +2283,11 @@ function expectedReplyMessage(packet, ReplyType, reply) {
2618
2283
  function ackTypeName(ackType) {
2619
2284
  return `AckType.${AckType[ackType] ?? "UNKNOWN"} (${ackType})`;
2620
2285
  }
2286
+ // src/VexDeviceState.ts
2287
+ import { err as err4, ok as ok4, ResultAsync as ResultAsync4 } from "neverthrow";
2288
+
2621
2289
  // src/VexFirmware.ts
2290
+ import { err as err2, errAsync, ok as ok2, ResultAsync as ResultAsync2 } from "neverthrow";
2622
2291
  var MAX_CATALOG_BYTES = 4 * 1024;
2623
2292
  var MAX_VEXOS_BYTES = 64 * 1024 * 1024;
2624
2293
  var MAX_FIRMWARE_IMAGE_BYTES = 32 * 1024 * 1024;
@@ -2631,7 +2300,7 @@ function downloadFileFromInternet(link, options = {}) {
2631
2300
  if (timeout < 0) {
2632
2301
  return errAsync(new VexInvalidArgumentError("timeout must be non-negative"));
2633
2302
  }
2634
- return new ResultAsync(runDownload(link, maxBytes, timeout));
2303
+ return new ResultAsync2(runDownload(link, maxBytes, timeout));
2635
2304
  }
2636
2305
  async function runDownload(link, maxBytes, timeout) {
2637
2306
  const controller = new AbortController;
@@ -2641,20 +2310,20 @@ async function runDownload(link, maxBytes, timeout) {
2641
2310
  try {
2642
2311
  response = await fetch(link, { signal: controller.signal });
2643
2312
  } catch (e) {
2644
- return err(new VexDownloadError(`failed to download ${link} (${e instanceof Error ? e.message : String(e)})`));
2313
+ return err2(new VexDownloadError(`failed to download ${link} (${e instanceof Error ? e.message : String(e)})`));
2645
2314
  }
2646
2315
  if (!response.ok) {
2647
- return err(new VexDownloadError(`failed to download ${link} (${response.status})`));
2316
+ return err2(new VexDownloadError(`failed to download ${link} (${response.status})`));
2648
2317
  }
2649
2318
  const declaredLength = response.headers.get("content-length");
2650
2319
  if (declaredLength !== null) {
2651
2320
  const declared = Number.parseInt(declaredLength, 10);
2652
2321
  if (!Number.isNaN(declared) && declared > 0 && declared > maxBytes) {
2653
- return err(new VexDownloadError(`declared content length ${declared} exceeds limit ${maxBytes} for ${link}`));
2322
+ return err2(new VexDownloadError(`declared content length ${declared} exceeds limit ${maxBytes} for ${link}`));
2654
2323
  }
2655
2324
  }
2656
2325
  if (response.body == null) {
2657
- return err(new VexDownloadError(`no response body for ${link}`));
2326
+ return err2(new VexDownloadError(`no response body for ${link}`));
2658
2327
  }
2659
2328
  const reader = response.body.getReader();
2660
2329
  const chunks = [];
@@ -2671,7 +2340,7 @@ async function runDownload(link, maxBytes, timeout) {
2671
2340
  try {
2672
2341
  await reader.cancel();
2673
2342
  } catch {}
2674
- return err(new VexDownloadError(`downloaded body exceeds limit ${maxBytes} for ${link}`));
2343
+ return err2(new VexDownloadError(`downloaded body exceeds limit ${maxBytes} for ${link}`));
2675
2344
  }
2676
2345
  chunks.push(value);
2677
2346
  }
@@ -2686,7 +2355,7 @@ async function runDownload(link, maxBytes, timeout) {
2686
2355
  result.set(chunk, offset);
2687
2356
  offset += chunk.byteLength;
2688
2357
  }
2689
- return ok(result.buffer);
2358
+ return ok2(result.buffer);
2690
2359
  } finally {
2691
2360
  clearTimeout(timer);
2692
2361
  }
@@ -2698,23 +2367,23 @@ function sleepUntilAsync(f, timeout, interval = 20) {
2698
2367
  if (interval <= 0) {
2699
2368
  return errAsync(new VexInvalidArgumentError("interval must be positive"));
2700
2369
  }
2701
- return new ResultAsync(runSleepUntilAsync(f, timeout, interval));
2370
+ return new ResultAsync2(runSleepUntilAsync(f, timeout, interval));
2702
2371
  }
2703
2372
  async function runSleepUntilAsync(f, timeout, interval) {
2704
2373
  const deadline = Date.now() + timeout;
2705
2374
  while (Date.now() <= deadline) {
2706
2375
  try {
2707
2376
  if (await f())
2708
- return ok(true);
2377
+ return ok2(true);
2709
2378
  } catch (e) {
2710
- return err(toVexSerialError(e, "io"));
2379
+ return err2(toVexSerialError(e, "io"));
2711
2380
  }
2712
2381
  const remaining = deadline - Date.now();
2713
2382
  if (remaining <= 0)
2714
2383
  break;
2715
2384
  await sleepInner(Math.min(interval, remaining));
2716
2385
  }
2717
- return ok(false);
2386
+ return ok2(false);
2718
2387
  }
2719
2388
  function sleepUntil(f, timeout, interval = 20) {
2720
2389
  if (timeout < 0) {
@@ -2723,29 +2392,29 @@ function sleepUntil(f, timeout, interval = 20) {
2723
2392
  if (interval <= 0) {
2724
2393
  return errAsync(new VexInvalidArgumentError("interval must be positive"));
2725
2394
  }
2726
- return new ResultAsync(runSleepUntil(f, timeout, interval));
2395
+ return new ResultAsync2(runSleepUntil(f, timeout, interval));
2727
2396
  }
2728
2397
  async function runSleepUntil(f, timeout, interval) {
2729
2398
  const deadline = Date.now() + timeout;
2730
2399
  while (Date.now() <= deadline) {
2731
2400
  try {
2732
2401
  if (f())
2733
- return ok(true);
2402
+ return ok2(true);
2734
2403
  } catch (e) {
2735
- return err(toVexSerialError(e, "io"));
2404
+ return err2(toVexSerialError(e, "io"));
2736
2405
  }
2737
2406
  const remaining = deadline - Date.now();
2738
2407
  if (remaining <= 0)
2739
2408
  break;
2740
2409
  await sleepInner(Math.min(interval, remaining));
2741
2410
  }
2742
- return ok(false);
2411
+ return ok2(false);
2743
2412
  }
2744
2413
  function sleep(ms) {
2745
2414
  if (ms < 0) {
2746
2415
  return errAsync(new VexInvalidArgumentError("ms must be non-negative"));
2747
2416
  }
2748
- return ResultAsync.fromSafePromise(sleepInner(ms));
2417
+ return ResultAsync2.fromSafePromise(sleepInner(ms));
2749
2418
  }
2750
2419
  async function sleepInner(ms) {
2751
2420
  return new Promise((resolve) => setTimeout(resolve, ms));
@@ -2755,7 +2424,7 @@ async function flashFactoryImage(conn, options, pcb) {
2755
2424
  pcb(`FACTORY ENB ${label}`, 0, 0);
2756
2425
  const enableReply = await conn.request(new FactoryEnableH2DPacket, FactoryEnableReplyD2HPacket);
2757
2426
  if (enableReply.isErr())
2758
- return err(enableReply.error);
2427
+ return err2(enableReply.error);
2759
2428
  const writeRequest = {
2760
2429
  filename: "null.bin",
2761
2430
  vendor: 1 /* USER */,
@@ -2766,26 +2435,26 @@ async function flashFactoryImage(conn, options, pcb) {
2766
2435
  autoRun: true,
2767
2436
  linkedFile: undefined
2768
2437
  };
2769
- const upload = await conn.uploadFileToDevice(writeRequest, (c, t) => {
2438
+ const upload = await conn.uploadFileToDeviceUnlocked(writeRequest, (c, t) => {
2770
2439
  pcb(`UPLOAD ${label}`, c, t);
2771
2440
  });
2772
2441
  if (upload.isErr())
2773
- return err(upload.error);
2442
+ return err2(upload.error);
2774
2443
  if (!upload.value) {
2775
- return err(new VexFirmwareError(`${label} upload was rejected by device`));
2444
+ return err2(new VexFirmwareError(`${label} upload was rejected by device`));
2776
2445
  }
2777
2446
  const deadline = Date.now() + 120000;
2778
2447
  while (Date.now() < deadline) {
2779
2448
  const statusReply = await conn.request(new FactoryStatusH2DPacket, FactoryStatusReplyD2HPacket, 1e4);
2780
2449
  if (statusReply.isErr())
2781
- return err(statusReply.error);
2450
+ return err2(statusReply.error);
2782
2451
  reportFactoryStatus(label, statusReply.value, pcb);
2783
2452
  if (statusReply.value.status === 0 && statusReply.value.percent === 100) {
2784
- return ok(undefined);
2453
+ return ok2(undefined);
2785
2454
  }
2786
2455
  await sleepInner(500);
2787
2456
  }
2788
- return err(new VexFirmwareError(`${label} factory status timed out`));
2457
+ return err2(new VexFirmwareError(`${label} factory status timed out`));
2789
2458
  }
2790
2459
  function reportFactoryStatus(label, reply, pcb) {
2791
2460
  switch (reply.status) {
@@ -2850,13 +2519,13 @@ async function extractFirmwareImages(usingVersion, vexos) {
2850
2519
  return ordered;
2851
2520
  }
2852
2521
  function uploadFirmware(state, publicUrl = "https://content.vexrobotics.com/vexos/public/V5/", usingVersion, progressCallback) {
2853
- return new ResultAsync(runUploadFirmware(state, publicUrl, usingVersion, progressCallback));
2522
+ return new ResultAsync2(runUploadFirmware(state, publicUrl, usingVersion, progressCallback));
2854
2523
  }
2855
2524
  async function runUploadFirmware(state, publicUrl, usingVersion, progressCallback) {
2856
2525
  const device = state._instance;
2857
2526
  const conn = device.connection;
2858
2527
  if (conn == null || !conn.isConnected) {
2859
- return err(new VexNotConnectedError);
2528
+ return err2(new VexNotConnectedError);
2860
2529
  }
2861
2530
  const pcb = progressCallback ?? (() => {});
2862
2531
  let version = usingVersion;
@@ -2866,20 +2535,20 @@ async function runUploadFirmware(state, publicUrl, usingVersion, progressCallbac
2866
2535
  maxBytes: MAX_CATALOG_BYTES
2867
2536
  });
2868
2537
  if (catalog.isErr())
2869
- return err(catalog.error);
2538
+ return err2(catalog.error);
2870
2539
  version = new TextDecoder().decode(catalog.value).trim();
2871
2540
  pcb("FETCH CATALOG", 1, 1);
2872
2541
  }
2873
2542
  if (!/^[A-Za-z0-9._-]+$/.test(version)) {
2874
- return err(new VexFirmwareError(`invalid VEXos version: ${version}`));
2543
+ return err2(new VexFirmwareError(`invalid VEXos version: ${version}`));
2875
2544
  }
2876
2545
  pcb("FETCH VEXOS", 0, 1);
2877
2546
  const vexosResult = await downloadFileFromInternet(publicUrl + version + ".vexos", { maxBytes: MAX_VEXOS_BYTES });
2878
2547
  if (vexosResult.isErr())
2879
- return err(vexosResult.error);
2548
+ return err2(vexosResult.error);
2880
2549
  const vexos = vexosResult.value;
2881
2550
  if (vexos.byteLength === 0) {
2882
- return err(new VexFirmwareError("VEXos archive is empty"));
2551
+ return err2(new VexFirmwareError("VEXos archive is empty"));
2883
2552
  }
2884
2553
  pcb("FETCH VEXOS", 1, 1);
2885
2554
  pcb("UNZIP VEXOS", 0, 1);
@@ -2888,52 +2557,59 @@ async function runUploadFirmware(state, publicUrl, usingVersion, progressCallbac
2888
2557
  images = await extractFirmwareImages(version, vexos);
2889
2558
  } catch (e) {
2890
2559
  if (e instanceof VexSerialError)
2891
- return err(e);
2892
- return err(toVexSerialError(e, "firmware"));
2560
+ return err2(e);
2561
+ return err2(toVexSerialError(e, "firmware"));
2893
2562
  }
2894
2563
  pcb("UNZIP VEXOS", 1, 1);
2895
2564
  return state.withRefreshPaused(async () => {
2896
- const boot = images.find((image) => image.name.endsWith("BOOT.bin"));
2897
- if (boot === undefined) {
2898
- return err(new VexFirmwareError("VEXos archive is missing BOOT.bin"));
2899
- }
2900
- const assertImage = images.find((image) => image.name.endsWith("assets.bin"));
2901
- if (assertImage === undefined) {
2902
- return err(new VexFirmwareError("VEXos archive is missing assets.bin"));
2903
- }
2904
- const bootFlash = await flashFactoryImage(conn, {
2905
- image: boot,
2906
- label: "BOOT",
2907
- downloadTarget: 14 /* FILE_TARGET_B1 */
2908
- }, pcb);
2909
- if (bootFlash.isErr())
2910
- return err(bootFlash.error);
2911
- const assetsFlash = await flashFactoryImage(conn, {
2912
- image: assertImage,
2913
- label: "ASSETS",
2914
- downloadTarget: 13 /* FILE_TARGET_A1 */
2915
- }, pcb);
2916
- if (assetsFlash.isErr())
2917
- return err(assetsFlash.error);
2918
- return ok(true);
2565
+ try {
2566
+ return await conn.withFileTransfer(async () => {
2567
+ const boot = images.find((image) => image.name.endsWith("BOOT.bin"));
2568
+ if (boot === undefined) {
2569
+ return err2(new VexFirmwareError("VEXos archive is missing BOOT.bin"));
2570
+ }
2571
+ const assertImage = images.find((image) => image.name.endsWith("assets.bin"));
2572
+ if (assertImage === undefined) {
2573
+ return err2(new VexFirmwareError("VEXos archive is missing assets.bin"));
2574
+ }
2575
+ const bootFlash = await flashFactoryImage(conn, {
2576
+ image: boot,
2577
+ label: "BOOT",
2578
+ downloadTarget: 14 /* FILE_TARGET_B1 */
2579
+ }, pcb);
2580
+ if (bootFlash.isErr())
2581
+ return err2(bootFlash.error);
2582
+ const assetsFlash = await flashFactoryImage(conn, {
2583
+ image: assertImage,
2584
+ label: "ASSETS",
2585
+ downloadTarget: 13 /* FILE_TARGET_A1 */
2586
+ }, pcb);
2587
+ if (assetsFlash.isErr())
2588
+ return err2(assetsFlash.error);
2589
+ return ok2(true);
2590
+ });
2591
+ } catch (e) {
2592
+ return err2(toVexSerialError(e, "firmware"));
2593
+ }
2919
2594
  });
2920
2595
  }
2921
2596
 
2922
2597
  // src/VexTransfers.ts
2598
+ import { err as err3, ok as ok3, ResultAsync as ResultAsync3 } from "neverthrow";
2923
2599
  function getValue(state, key) {
2924
- return new ResultAsync((async () => {
2600
+ return new ResultAsync3((async () => {
2925
2601
  const conn = state._instance.connection;
2926
2602
  if (conn == null || !conn.isConnected) {
2927
- return err(new VexNotConnectedError);
2603
+ return err3(new VexNotConnectedError);
2928
2604
  }
2929
2605
  return conn.request(new ReadKeyValueH2DPacket(key), ReadKeyValueReplyD2HPacket).map((result) => result.value);
2930
2606
  })());
2931
2607
  }
2932
2608
  function setValue(state, key, value) {
2933
- return new ResultAsync((async () => {
2609
+ return new ResultAsync3((async () => {
2934
2610
  const conn = state._instance.connection;
2935
2611
  if (conn == null || !conn.isConnected) {
2936
- return err(new VexNotConnectedError);
2612
+ return err3(new VexNotConnectedError);
2937
2613
  }
2938
2614
  return conn.request(new WriteKeyValueH2DPacket(key, value), WriteKeyValueReplyD2HPacket).map(() => {
2939
2615
  return;
@@ -2941,19 +2617,19 @@ function setValue(state, key, value) {
2941
2617
  })());
2942
2618
  }
2943
2619
  function listFiles(state, vendor = 1 /* USER */) {
2944
- return new ResultAsync((async () => {
2620
+ return new ResultAsync3((async () => {
2945
2621
  const conn = state._instance.connection;
2946
2622
  if (conn == null || !conn.isConnected) {
2947
- return err(new VexNotConnectedError);
2623
+ return err3(new VexNotConnectedError);
2948
2624
  }
2949
2625
  const countResult = await conn.request(new GetDirectoryFileCountH2DPacket(vendor), GetDirectoryFileCountReplyD2HPacket);
2950
2626
  if (countResult.isErr())
2951
- return err(countResult.error);
2627
+ return err3(countResult.error);
2952
2628
  const files = [];
2953
2629
  for (let i = 0;i < countResult.value.count; i++) {
2954
2630
  const entryResult = await conn.request(new GetDirectoryEntryH2DPacket(i), GetDirectoryEntryReplyD2HPacket);
2955
2631
  if (entryResult.isErr())
2956
- return err(entryResult.error);
2632
+ return err3(entryResult.error);
2957
2633
  if (entryResult.value.file != null) {
2958
2634
  files.push({
2959
2635
  filename: entryResult.value.file.filename,
@@ -2967,18 +2643,18 @@ function listFiles(state, vendor = 1 /* USER */) {
2967
2643
  });
2968
2644
  }
2969
2645
  }
2970
- return ok(files);
2646
+ return ok3(files);
2971
2647
  })());
2972
2648
  }
2973
2649
  function listProgram(state) {
2974
- return new ResultAsync((async () => {
2650
+ return new ResultAsync3((async () => {
2975
2651
  const conn = state._instance.connection;
2976
2652
  if (conn == null || !conn.isConnected) {
2977
- return err(new VexNotConnectedError);
2653
+ return err3(new VexNotConnectedError);
2978
2654
  }
2979
2655
  const files = await listFiles(state, 1 /* USER */);
2980
2656
  if (files.isErr())
2981
- return err(files.error);
2657
+ return err3(files.error);
2982
2658
  const programList = [];
2983
2659
  const iniFiles = files.value.filter((file) => file.filename.endsWith(".ini"));
2984
2660
  for (let i = 0;i < iniFiles.length; i++) {
@@ -3006,14 +2682,14 @@ function listProgram(state) {
3006
2682
  }
3007
2683
  programList.push(program);
3008
2684
  }
3009
- return ok(programList);
2685
+ return ok3(programList);
3010
2686
  })());
3011
2687
  }
3012
2688
  function readFile(state, request, downloadTarget = 1 /* FILE_TARGET_QSPI */, progressCallback) {
3013
- return new ResultAsync((async () => {
2689
+ return new ResultAsync3((async () => {
3014
2690
  const conn = state._instance.connection;
3015
2691
  if (conn == null || !conn.isConnected) {
3016
- return err(new VexNotConnectedError);
2692
+ return err3(new VexNotConnectedError);
3017
2693
  }
3018
2694
  let handle;
3019
2695
  if (typeof request === "string") {
@@ -3025,31 +2701,31 @@ function readFile(state, request, downloadTarget = 1 /* FILE_TARGET_QSPI */, pro
3025
2701
  })());
3026
2702
  }
3027
2703
  function removeFile(state, request) {
3028
- return new ResultAsync((async () => {
2704
+ return new ResultAsync3((async () => {
3029
2705
  const conn = state._instance.connection;
3030
2706
  if (conn == null || !conn.isConnected) {
3031
- return err(new VexNotConnectedError);
2707
+ return err3(new VexNotConnectedError);
3032
2708
  }
3033
2709
  return state.withRefreshPaused(() => conn.removeFile(request));
3034
2710
  })());
3035
2711
  }
3036
2712
  function removeAllFiles(state) {
3037
- return new ResultAsync((async () => {
2713
+ return new ResultAsync3((async () => {
3038
2714
  const conn = state._instance.connection;
3039
2715
  if (conn == null || !conn.isConnected) {
3040
- return err(new VexNotConnectedError);
2716
+ return err3(new VexNotConnectedError);
3041
2717
  }
3042
2718
  return state.withRefreshPaused(() => conn.removeAllFiles());
3043
2719
  })());
3044
2720
  }
3045
2721
  function uploadProgram(state, iniConfig, binFileBuf, coldFileBuf, progressCallback) {
3046
- return new ResultAsync(runUploadProgram(state, iniConfig, binFileBuf, coldFileBuf, progressCallback));
2722
+ return new ResultAsync3(runUploadProgram(state, iniConfig, binFileBuf, coldFileBuf, progressCallback));
3047
2723
  }
3048
2724
  async function runUploadProgram(state, iniConfig, binFileBuf, coldFileBuf, progressCallback) {
3049
2725
  const device = state._instance;
3050
2726
  const conn = device.connection;
3051
2727
  if (conn == null || !conn.isConnected) {
3052
- return err(new VexNotConnectedError);
2728
+ return err3(new VexNotConnectedError);
3053
2729
  }
3054
2730
  let switchedToDownload = false;
3055
2731
  return state.withRefreshPaused(async () => {
@@ -3058,46 +2734,46 @@ async function runUploadProgram(state, iniConfig, binFileBuf, coldFileBuf, progr
3058
2734
  await sleep(250);
3059
2735
  const refreshed = await device.refresh();
3060
2736
  if (refreshed.isErr() || !refreshed.value) {
3061
- return err(new VexProtocolError("device is unavailable"));
2737
+ return err3(new VexProtocolError("device is unavailable"));
3062
2738
  }
3063
2739
  progressCallback("CHANNEL", 0, 1);
3064
2740
  const p1 = await device.radio.changeChannel(1 /* DOWNLOAD */);
3065
2741
  if (p1.isErr())
3066
- return err(p1.error);
2742
+ return err3(p1.error);
3067
2743
  switchedToDownload = true;
3068
2744
  await sleep(250);
3069
2745
  const transferred = await sleepUntilAsync(async () => (await conn?.getSystemStatus(150))?.isOk() ?? false, 1e4, 200);
3070
2746
  if (transferred.isErr())
3071
- return err(transferred.error);
2747
+ return err3(transferred.error);
3072
2748
  if (!transferred.value) {
3073
- return err(new VexProtocolError("channel switch timed out"));
2749
+ return err3(new VexProtocolError("channel switch timed out"));
3074
2750
  }
3075
2751
  progressCallback("CHANNEL", 1, 1);
3076
2752
  }
3077
2753
  const p2 = await conn.uploadProgramToDevice(iniConfig, binFileBuf, coldFileBuf, progressCallback);
3078
2754
  if (p2.isErr())
3079
- return err(p2.error);
2755
+ return err3(p2.error);
3080
2756
  if (!p2.value)
3081
- return err(new VexProtocolError("program upload rejected"));
2757
+ return err3(new VexProtocolError("program upload rejected"));
3082
2758
  if (device.isV5Controller) {
3083
2759
  if (!device.brain.isAvailable) {
3084
- return err(new VexProtocolError("brain unavailable after upload"));
2760
+ return err3(new VexProtocolError("brain unavailable after upload"));
3085
2761
  }
3086
2762
  progressCallback("CHANNEL", 0, 1);
3087
2763
  const p3 = await device.radio.changeChannel(0 /* PIT */);
3088
2764
  if (p3.isErr())
3089
- return err(p3.error);
2765
+ return err3(p3.error);
3090
2766
  switchedToDownload = false;
3091
2767
  await sleep(250);
3092
2768
  const transferred = await sleepUntilAsync(async () => (await conn?.getSystemStatus(150))?.isOk() ?? false, 1e4, 200);
3093
2769
  if (transferred.isErr())
3094
- return err(transferred.error);
2770
+ return err3(transferred.error);
3095
2771
  if (!transferred.value) {
3096
- return err(new VexProtocolError("channel switch timed out"));
2772
+ return err3(new VexProtocolError("channel switch timed out"));
3097
2773
  }
3098
2774
  progressCallback("CHANNEL", 1, 1);
3099
2775
  }
3100
- return ok(true);
2776
+ return ok3(true);
3101
2777
  } finally {
3102
2778
  if (switchedToDownload) {
3103
2779
  await device.radio.changeChannel(0 /* PIT */);
@@ -3106,19 +2782,19 @@ async function runUploadProgram(state, iniConfig, binFileBuf, coldFileBuf, progr
3106
2782
  });
3107
2783
  }
3108
2784
  function writeFile(state, request, progressCallback) {
3109
- return new ResultAsync((async () => {
2785
+ return new ResultAsync3((async () => {
3110
2786
  const conn = state._instance.connection;
3111
2787
  if (conn == null || !conn.isConnected) {
3112
- return err(new VexNotConnectedError);
2788
+ return err3(new VexNotConnectedError);
3113
2789
  }
3114
2790
  return state.withRefreshPaused(() => conn.uploadFileToDevice(request, progressCallback));
3115
2791
  })());
3116
2792
  }
3117
2793
  function captureScreen(state, progressCallback) {
3118
- return new ResultAsync((async () => {
2794
+ return new ResultAsync3((async () => {
3119
2795
  const conn = state._instance.connection;
3120
2796
  if (conn == null || !conn.isConnected) {
3121
- return err(new VexNotConnectedError);
2797
+ return err3(new VexNotConnectedError);
3122
2798
  }
3123
2799
  return state.withRefreshPaused(() => conn.captureScreen(progressCallback));
3124
2800
  })());
@@ -3232,42 +2908,42 @@ class V5Brain {
3232
2908
  this.setActiveProgram(value).mapErr(() => {});
3233
2909
  }
3234
2910
  setActiveProgram(value) {
3235
- return new ResultAsync((async () => {
2911
+ return new ResultAsync4((async () => {
3236
2912
  if (this.state.brain.activeProgram === value)
3237
- return ok(undefined);
2913
+ return ok4(undefined);
3238
2914
  const conn = this.state._instance.connection;
3239
2915
  if (conn == null)
3240
- return err(new VexNotConnectedError);
2916
+ return err4(new VexNotConnectedError);
3241
2917
  const result = value === 0 ? await conn.stopProgram() : await conn.loadProgram(value);
3242
2918
  if (result.isErr())
3243
- return err(result.error);
2919
+ return err4(result.error);
3244
2920
  this.state.brain.activeProgram = value;
3245
- return ok(undefined);
2921
+ return ok4(undefined);
3246
2922
  })());
3247
2923
  }
3248
2924
  runProgram(slot) {
3249
- return new ResultAsync((async () => {
2925
+ return new ResultAsync4((async () => {
3250
2926
  const conn = this.state._instance.connection;
3251
2927
  if (conn == null)
3252
- return err(new VexNotConnectedError);
2928
+ return err4(new VexNotConnectedError);
3253
2929
  const reply = await conn.runProgram(slot);
3254
2930
  if (reply.isErr())
3255
- return err(reply.error);
2931
+ return err4(reply.error);
3256
2932
  if (typeof slot === "number")
3257
2933
  this.state.brain.activeProgram = slot;
3258
- return ok(undefined);
2934
+ return ok4(undefined);
3259
2935
  })());
3260
2936
  }
3261
2937
  stopProgram() {
3262
- return new ResultAsync((async () => {
2938
+ return new ResultAsync4((async () => {
3263
2939
  const conn = this.state._instance.connection;
3264
2940
  if (conn == null)
3265
- return err(new VexNotConnectedError);
2941
+ return err4(new VexNotConnectedError);
3266
2942
  const reply = await conn.stopProgram();
3267
2943
  if (reply.isErr())
3268
- return err(reply.error);
2944
+ return err4(reply.error);
3269
2945
  this.state.brain.activeProgram = 0;
3270
- return ok(undefined);
2946
+ return ok4(undefined);
3271
2947
  })());
3272
2948
  }
3273
2949
  get battery() {
@@ -3440,10 +3116,10 @@ class V5Radio {
3440
3116
  return this.state.radio.latency;
3441
3117
  }
3442
3118
  changeChannel(channel) {
3443
- return new ResultAsync((async () => {
3119
+ return new ResultAsync4((async () => {
3444
3120
  const conn = this.state._instance.connection;
3445
3121
  if (conn == null || !conn.isConnected) {
3446
- return err(new VexNotConnectedError);
3122
+ return err4(new VexNotConnectedError);
3447
3123
  }
3448
3124
  return conn.request(new FileControlH2DPacket(1, channel), FileControlReplyD2HPacket).map(() => {
3449
3125
  return;
@@ -3453,6 +3129,7 @@ class V5Radio {
3453
3129
  }
3454
3130
 
3455
3131
  // src/VexDevice.ts
3132
+ import { err as err5, ok as ok5, ResultAsync as ResultAsync5 } from "neverthrow";
3456
3133
  function unrefTimerIfPossible(timer) {
3457
3134
  if (typeof timer !== "object" || timer === null || !("unref" in timer))
3458
3135
  return;
@@ -3460,6 +3137,11 @@ function unrefTimerIfPossible(timer) {
3460
3137
  if (typeof unref === "function")
3461
3138
  unref.call(timer);
3462
3139
  }
3140
+ function describePort(port) {
3141
+ const info = port.getInfo();
3142
+ const identifier = info.path ?? info.id ?? info.serialNumber;
3143
+ return typeof identifier === "string" ? identifier : undefined;
3144
+ }
3463
3145
 
3464
3146
  class V5SerialDevice extends VexSerialDevice {
3465
3147
  autoReconnect = true;
@@ -3469,8 +3151,11 @@ class V5SerialDevice extends VexSerialDevice {
3469
3151
  _refreshInterval;
3470
3152
  state = new V5SerialDeviceState(this);
3471
3153
  _disposed = false;
3154
+ _lifecycleGeneration = 0;
3155
+ _disconnectListener;
3472
3156
  _refreshGeneration = 0;
3473
3157
  _autoRefresh = false;
3158
+ _refreshIntervalMs = 200;
3474
3159
  _isLastRefreshComplete = true;
3475
3160
  _brain = new V5Brain(this.state);
3476
3161
  _controllers = [
@@ -3479,8 +3164,15 @@ class V5SerialDevice extends VexSerialDevice {
3479
3164
  ];
3480
3165
  _radio = new V5Radio(this.state);
3481
3166
  _deviceFacades = [];
3482
- constructor(defaultSerial, autoRefresh = false) {
3167
+ _emitSafely(eventName, data) {
3168
+ try {
3169
+ this.emit(eventName, data);
3170
+ } catch {}
3171
+ }
3172
+ constructor(defaultSerial, options = false) {
3483
3173
  super(defaultSerial);
3174
+ const autoRefresh = typeof options === "boolean" ? options : options.autoRefresh ?? false;
3175
+ this.refreshIntervalMs = typeof options === "boolean" ? 200 : options.refreshIntervalMs ?? 200;
3484
3176
  this.autoRefresh = autoRefresh;
3485
3177
  }
3486
3178
  get autoRefresh() {
@@ -3496,6 +3188,21 @@ class V5SerialDevice extends VexSerialDevice {
3496
3188
  this._stopRefreshInterval();
3497
3189
  }
3498
3190
  }
3191
+ get refreshIntervalMs() {
3192
+ return this._refreshIntervalMs;
3193
+ }
3194
+ set refreshIntervalMs(value) {
3195
+ if (!Number.isFinite(value) || value <= 0) {
3196
+ throw new VexInvalidArgumentError("refreshIntervalMs must be a positive finite number");
3197
+ }
3198
+ if (this._refreshIntervalMs === value)
3199
+ return;
3200
+ this._refreshIntervalMs = value;
3201
+ if (this._refreshInterval !== undefined) {
3202
+ this._stopRefreshInterval();
3203
+ this._startRefreshInterval();
3204
+ }
3205
+ }
3499
3206
  _startRefreshInterval() {
3500
3207
  if (this._refreshInterval !== undefined || this._disposed)
3501
3208
  return;
@@ -3513,16 +3220,16 @@ class V5SerialDevice extends VexSerialDevice {
3513
3220
  try {
3514
3221
  const r = await this.refresh();
3515
3222
  if (r.isErr())
3516
- this.emit("error", r.error);
3223
+ this._emitSafely("error", r.error);
3517
3224
  } catch (error) {
3518
- this.emit("error", error);
3225
+ this._emitSafely("error", error);
3519
3226
  } finally {
3520
3227
  this._isLastRefreshComplete = true;
3521
3228
  }
3522
3229
  })();
3523
3230
  }
3524
3231
  }
3525
- }, 200);
3232
+ }, this._refreshIntervalMs);
3526
3233
  unrefTimerIfPossible(this._refreshInterval);
3527
3234
  }
3528
3235
  _stopRefreshInterval() {
@@ -3561,82 +3268,149 @@ class V5SerialDevice extends VexSerialDevice {
3561
3268
  this.setMatchMode(value).mapErr(() => {});
3562
3269
  }
3563
3270
  setMatchMode(mode) {
3564
- return new ResultAsync((async () => {
3271
+ return new ResultAsync5((async () => {
3565
3272
  const reply = await this.connection?.setMatchMode(mode);
3566
3273
  if (reply === undefined)
3567
- return err(new VexNotConnectedError);
3274
+ return err5(new VexNotConnectedError);
3568
3275
  if (reply.isErr())
3569
- return err(reply.error);
3276
+ return err5(reply.error);
3570
3277
  this.state.matchMode = mode;
3571
- return ok(undefined);
3278
+ return ok5(undefined);
3572
3279
  })());
3573
3280
  }
3574
3281
  get radio() {
3575
3282
  return this._radio;
3576
3283
  }
3577
3284
  mockTouch(x, y, press) {
3578
- return new ResultAsync((async () => {
3285
+ return new ResultAsync5((async () => {
3579
3286
  const reply = await this.connection?.mockTouch(x, y, press);
3580
3287
  if (reply === undefined)
3581
- return err(new VexNotConnectedError);
3288
+ return err5(new VexNotConnectedError);
3582
3289
  if (reply.isErr())
3583
- return err(reply.error);
3584
- return ok(undefined);
3290
+ return err5(reply.error);
3291
+ return ok5(undefined);
3585
3292
  })());
3586
3293
  }
3587
3294
  connect(conn) {
3588
- return new ResultAsync(this._connect(conn));
3295
+ if (this._disposed) {
3296
+ return new ResultAsync5(Promise.resolve(this._staleLifecycleResult()));
3297
+ }
3298
+ if (this.isConnected)
3299
+ return new ResultAsync5(Promise.resolve(ok5(undefined)));
3300
+ const generation = ++this._lifecycleGeneration;
3301
+ return new ResultAsync5(this._connect(conn, generation));
3589
3302
  }
3590
- async _connect(conn) {
3303
+ async _connect(conn, generation = this._lifecycleGeneration) {
3304
+ if (!this._isLifecycleCurrent(generation)) {
3305
+ return this._staleLifecycleResult();
3306
+ }
3591
3307
  if (this.isConnected)
3592
- return ok(undefined);
3308
+ return ok5(undefined);
3593
3309
  if (conn != null) {
3594
3310
  if (!conn.isConnected) {
3595
3311
  const opened = await conn.open();
3312
+ if (!this._isLifecycleCurrent(generation)) {
3313
+ await conn.close();
3314
+ return this._staleLifecycleResult();
3315
+ }
3596
3316
  if (opened.isErr() || opened.value !== "opened") {
3597
- return err(new VexIoError("failed to open the supplied connection"));
3317
+ return err5(new VexIoError("failed to open the supplied connection"));
3598
3318
  }
3599
3319
  }
3600
3320
  const q = await conn.query1();
3321
+ if (!this._isLifecycleCurrent(generation)) {
3322
+ await conn.close();
3323
+ return this._staleLifecycleResult();
3324
+ }
3601
3325
  if (q.isErr()) {
3602
3326
  await conn.close();
3603
- return err(q.error);
3327
+ return err5(q.error);
3328
+ }
3329
+ if (!this._commitConnection(conn, generation)) {
3330
+ await conn.close();
3331
+ return this._staleLifecycleResult();
3604
3332
  }
3605
- this.connection = conn;
3606
3333
  } else {
3607
3334
  let tryIdx = 0;
3335
+ let canRequestPort = true;
3336
+ const attemptedPorts = new Set;
3337
+ const attemptedPortNames = [];
3608
3338
  while (true) {
3609
- const c = new V5SerialConnection(this.defaultSerial);
3610
- const result = await c.open(tryIdx++, true);
3339
+ if (!this._isLifecycleCurrent(generation)) {
3340
+ return this._staleLifecycleResult();
3341
+ }
3342
+ const c = this.createConnection();
3343
+ let result = await c.open(tryIdx++, false);
3344
+ if (!this._isLifecycleCurrent(generation)) {
3345
+ await c.close();
3346
+ return this._staleLifecycleResult();
3347
+ }
3348
+ if (result.isOk() && result.value === "no-port" && canRequestPort) {
3349
+ canRequestPort = false;
3350
+ result = await c.open(tryIdx, true);
3351
+ if (!this._isLifecycleCurrent(generation)) {
3352
+ await c.close();
3353
+ return this._staleLifecycleResult();
3354
+ }
3355
+ }
3611
3356
  if (result.isErr()) {
3612
3357
  await c.close();
3613
- return err(result.error);
3358
+ return err5(result.error);
3614
3359
  }
3615
3360
  if (result.value === "no-port") {
3616
- return err(new VexNotConnectedError("no V5 device was found"));
3361
+ const attempted = attemptedPortNames.length ? `; attempted ${attemptedPortNames.join(", ")}` : "";
3362
+ return err5(new VexNotConnectedError(`no responsive V5 device was found${attempted}`));
3617
3363
  }
3618
3364
  if (result.value === "busy") {
3619
3365
  await c.close();
3620
- continue;
3366
+ return err5(new VexNotConnectedError("the selected V5 serial port is busy"));
3367
+ }
3368
+ const port = c.port;
3369
+ if (port !== undefined && attemptedPorts.has(port)) {
3370
+ await c.close();
3371
+ const portName = describePort(port);
3372
+ return err5(new VexNotConnectedError(portName === undefined ? "the selected serial port did not respond as a V5 device" : `serial port ${portName} did not respond as a V5 device`));
3373
+ }
3374
+ if (port !== undefined) {
3375
+ attemptedPorts.add(port);
3376
+ const portName = describePort(port);
3377
+ if (portName !== undefined)
3378
+ attemptedPortNames.push(portName);
3621
3379
  }
3622
3380
  const q = await c.query1();
3381
+ if (!this._isLifecycleCurrent(generation)) {
3382
+ await c.close();
3383
+ return this._staleLifecycleResult();
3384
+ }
3623
3385
  if (q.isErr()) {
3624
3386
  await c.close();
3625
3387
  continue;
3626
3388
  }
3627
- this.connection = c;
3389
+ if (!this._commitConnection(c, generation)) {
3390
+ await c.close();
3391
+ return this._staleLifecycleResult();
3392
+ }
3628
3393
  break;
3629
3394
  }
3630
3395
  }
3631
- if (!this.isConnected)
3632
- return err(new VexNotConnectedError);
3633
- await this.doAfterConnect();
3634
- return ok(undefined);
3396
+ const connection = this.connection;
3397
+ if (!this._isLifecycleCurrent(generation) || connection == null) {
3398
+ return this._staleLifecycleResult();
3399
+ }
3400
+ if (!connection.isConnected)
3401
+ return err5(new VexNotConnectedError);
3402
+ const initialized = await this.doAfterConnect(connection, generation);
3403
+ if (initialized.isErr())
3404
+ return initialized;
3405
+ return ok5(undefined);
3635
3406
  }
3636
3407
  async disconnect() {
3408
+ this._lifecycleGeneration++;
3409
+ this._refreshGeneration++;
3637
3410
  this._isDisconnecting = true;
3638
3411
  const connection = this.connection;
3639
3412
  this.connection = undefined;
3413
+ this._detachDisconnectListener();
3640
3414
  try {
3641
3415
  await connection?.close();
3642
3416
  } finally {
@@ -3650,13 +3424,25 @@ class V5SerialDevice extends VexSerialDevice {
3650
3424
  await this.disconnect();
3651
3425
  }
3652
3426
  reconnect(timeout = 0) {
3653
- return new ResultAsync(this._reconnect(timeout));
3427
+ if (this._disposed) {
3428
+ return new ResultAsync5(Promise.resolve(this._staleLifecycleResult()));
3429
+ }
3430
+ if (timeout < 0) {
3431
+ return new ResultAsync5(Promise.resolve(err5(new VexInvalidArgumentError("timeout must be non-negative"))));
3432
+ }
3433
+ if (this.isConnected)
3434
+ return new ResultAsync5(Promise.resolve(ok5(undefined)));
3435
+ const generation = this._isReconnecting ? this._lifecycleGeneration : ++this._lifecycleGeneration;
3436
+ return new ResultAsync5(this._reconnect(timeout, generation));
3654
3437
  }
3655
- async _reconnect(timeout) {
3438
+ async _reconnect(timeout, generation = this._lifecycleGeneration) {
3439
+ if (!this._isLifecycleCurrent(generation)) {
3440
+ return this._staleLifecycleResult();
3441
+ }
3656
3442
  if (this.isConnected)
3657
- return ok(undefined);
3443
+ return ok5(undefined);
3658
3444
  if (timeout < 0) {
3659
- return err(new VexInvalidArgumentError("timeout must be non-negative"));
3445
+ return err5(new VexInvalidArgumentError("timeout must be non-negative"));
3660
3446
  }
3661
3447
  const endTime = Date.now() + timeout;
3662
3448
  if (this._isReconnecting) {
@@ -3665,22 +3451,32 @@ class V5SerialDevice extends VexSerialDevice {
3665
3451
  } else {
3666
3452
  const waited = await sleepUntil(() => !this._isReconnecting, timeout);
3667
3453
  if (waited.isErr() || !waited.value) {
3668
- return err(new VexNotConnectedError);
3454
+ return err5(new VexNotConnectedError);
3669
3455
  }
3670
3456
  }
3457
+ if (!this._isLifecycleCurrent(generation)) {
3458
+ return this._staleLifecycleResult();
3459
+ }
3671
3460
  if (this.isConnected)
3672
- return ok(undefined);
3461
+ return ok5(undefined);
3673
3462
  }
3674
3463
  this._isReconnecting = true;
3675
3464
  try {
3676
3465
  while (timeout === 0 || Date.now() < endTime) {
3677
3466
  let tryIdx = 0;
3678
3467
  while (true) {
3679
- const c = new V5SerialConnection(this.defaultSerial);
3468
+ if (!this._isLifecycleCurrent(generation)) {
3469
+ return this._staleLifecycleResult();
3470
+ }
3471
+ const c = this.createConnection();
3680
3472
  const result = await c.open(tryIdx++, false);
3473
+ if (!this._isLifecycleCurrent(generation)) {
3474
+ await c.close();
3475
+ return this._staleLifecycleResult();
3476
+ }
3681
3477
  if (result.isErr()) {
3682
3478
  await c.close();
3683
- return err(result.error);
3479
+ return err5(result.error);
3684
3480
  }
3685
3481
  if (result.value === "no-port")
3686
3482
  break;
@@ -3689,6 +3485,10 @@ class V5SerialDevice extends VexSerialDevice {
3689
3485
  continue;
3690
3486
  }
3691
3487
  const status = await c.getSystemStatus(200);
3488
+ if (!this._isLifecycleCurrent(generation)) {
3489
+ await c.close();
3490
+ return this._staleLifecycleResult();
3491
+ }
3692
3492
  if (status.isErr()) {
3693
3493
  await c.close();
3694
3494
  continue;
@@ -3697,24 +3497,39 @@ class V5SerialDevice extends VexSerialDevice {
3697
3497
  await c.close();
3698
3498
  continue;
3699
3499
  }
3700
- this.connection = c;
3500
+ if (!this._commitConnection(c, generation)) {
3501
+ await c.close();
3502
+ return this._staleLifecycleResult();
3503
+ }
3701
3504
  break;
3702
3505
  }
3703
3506
  if (this.isConnected)
3704
3507
  break;
3705
3508
  const getPortCount = async () => (await this.defaultSerial.getPorts()).length;
3706
3509
  const portsCount = await getPortCount();
3510
+ if (!this._isLifecycleCurrent(generation)) {
3511
+ return this._staleLifecycleResult();
3512
+ }
3707
3513
  await sleepUntilAsync(async () => await getPortCount() !== portsCount, 1000);
3514
+ if (!this._isLifecycleCurrent(generation)) {
3515
+ return this._staleLifecycleResult();
3516
+ }
3708
3517
  }
3709
3518
  } catch (e) {
3710
- return err(toVexSerialError(e));
3519
+ return err5(toVexSerialError(e));
3711
3520
  } finally {
3712
3521
  this._isReconnecting = false;
3713
3522
  }
3714
- if (!this.isConnected)
3715
- return err(new VexNotConnectedError);
3716
- await this.doAfterConnect();
3717
- return ok(undefined);
3523
+ const connection = this.connection;
3524
+ if (!this._isLifecycleCurrent(generation) || connection == null) {
3525
+ return this._staleLifecycleResult();
3526
+ }
3527
+ if (!connection.isConnected)
3528
+ return err5(new VexNotConnectedError);
3529
+ const initialized = await this.doAfterConnect(connection, generation);
3530
+ if (initialized.isErr())
3531
+ return initialized;
3532
+ return ok5(undefined);
3718
3533
  }
3719
3534
  async waitForReconnectToFinish() {
3720
3535
  while (this._isReconnecting) {
@@ -3723,61 +3538,96 @@ class V5SerialDevice extends VexSerialDevice {
3723
3538
  return;
3724
3539
  }
3725
3540
  }
3726
- async doAfterConnect() {
3727
- if (this.connection == null)
3728
- return;
3729
- this.connection.on("disconnected", (_data) => {
3730
- if (this._isDisconnecting)
3541
+ createConnection() {
3542
+ return new V5SerialConnection(this.defaultSerial);
3543
+ }
3544
+ _isLifecycleCurrent(generation) {
3545
+ return generation === this._lifecycleGeneration && !this._disposed;
3546
+ }
3547
+ _staleLifecycleResult() {
3548
+ return err5(new VexNotConnectedError("connection attempt was superseded"));
3549
+ }
3550
+ _commitConnection(connection, generation) {
3551
+ if (!this._isLifecycleCurrent(generation))
3552
+ return false;
3553
+ this._detachDisconnectListener();
3554
+ this.connection = connection;
3555
+ return true;
3556
+ }
3557
+ _detachDisconnectListener() {
3558
+ const subscription = this._disconnectListener;
3559
+ this._disconnectListener = undefined;
3560
+ subscription?.connection.remove("disconnected", subscription.listener);
3561
+ }
3562
+ async doAfterConnect(connection, generation) {
3563
+ if (!this._isLifecycleCurrent(generation) || this.connection !== connection) {
3564
+ return this._staleLifecycleResult();
3565
+ }
3566
+ const listener = () => {
3567
+ if (this._isDisconnecting || !this._isLifecycleCurrent(generation) || this.connection !== connection) {
3731
3568
  return;
3732
- this.emit("disconnected", undefined);
3733
- if (this.autoReconnect) {
3734
- this.reconnect().mapErr((e) => this.emit("error", e));
3735
3569
  }
3736
- });
3737
- await this.refresh();
3570
+ this._lifecycleGeneration++;
3571
+ this._refreshGeneration++;
3572
+ this.connection = undefined;
3573
+ this._detachDisconnectListener();
3574
+ this._emitSafely("disconnected", undefined);
3575
+ if (this.autoReconnect && !this._disposed) {
3576
+ this.reconnect().mapErr((error) => this._emitSafely("error", error));
3577
+ }
3578
+ };
3579
+ connection.on("disconnected", listener);
3580
+ this._disconnectListener = { connection, listener };
3581
+ const refreshed = await this.refresh();
3582
+ if (!this._isLifecycleCurrent(generation) || this.connection !== connection || !connection.isConnected) {
3583
+ return this._staleLifecycleResult();
3584
+ }
3585
+ if (refreshed.isErr()) {
3586
+ await this._discardConnection(connection, generation);
3587
+ return err5(refreshed.error);
3588
+ }
3589
+ if (!refreshed.value) {
3590
+ await this._discardConnection(connection, generation);
3591
+ return err5(new VexNotConnectedError("initial device refresh did not produce a current snapshot"));
3592
+ }
3593
+ return ok5(undefined);
3594
+ }
3595
+ async _discardConnection(connection, generation) {
3596
+ if (!this._isLifecycleCurrent(generation) || this.connection !== connection) {
3597
+ return;
3598
+ }
3599
+ this._lifecycleGeneration++;
3600
+ this._refreshGeneration++;
3601
+ this.connection = undefined;
3602
+ this._detachDisconnectListener();
3603
+ await connection.close();
3738
3604
  }
3739
3605
  refresh() {
3740
- return new ResultAsync(this._refresh());
3606
+ return new ResultAsync5(this._refresh());
3741
3607
  }
3742
3608
  async _refresh() {
3743
3609
  if (this._disposed)
3744
- return ok(false);
3610
+ return ok5(false);
3745
3611
  const generation = ++this._refreshGeneration;
3746
3612
  const conn = this.connection;
3747
3613
  if (conn == null || !conn.isConnected) {
3748
3614
  this._applySnapshotIfCurrent(generation, { isAvailable: false });
3749
- return ok(false);
3750
- }
3751
- const ssPacket = await conn.getSystemStatus();
3752
- if (generation !== this._refreshGeneration || this._disposed)
3753
- return ok(false);
3754
- if (ssPacket.isErr()) {
3755
- this._applySnapshotIfCurrent(generation, { isAvailable: false });
3756
- return ok(false);
3757
- }
3758
- const sfPacket = await conn.getSystemFlags();
3759
- if (generation !== this._refreshGeneration || this._disposed)
3760
- return ok(false);
3761
- if (sfPacket.isErr()) {
3762
- this._applySnapshotIfCurrent(generation, { isAvailable: false });
3763
- return ok(false);
3764
- }
3765
- const rdPacket = await conn.getRadioStatus();
3766
- if (generation !== this._refreshGeneration || this._disposed)
3767
- return ok(false);
3768
- if (rdPacket.isErr()) {
3769
- this._applySnapshotIfCurrent(generation, { isAvailable: false });
3770
- return ok(false);
3771
- }
3772
- const dsPacket = await conn.getDeviceStatus();
3615
+ return ok5(false);
3616
+ }
3617
+ const [ssPacket, sfPacket, rdPacket, dsPacket] = await Promise.all([
3618
+ conn.getSystemStatus(),
3619
+ conn.getSystemFlags(),
3620
+ conn.getRadioStatus(),
3621
+ conn.getDeviceStatus()
3622
+ ]);
3773
3623
  if (generation !== this._refreshGeneration || this._disposed)
3774
- return ok(false);
3775
- if (dsPacket.isErr()) {
3624
+ return ok5(false);
3625
+ if (ssPacket.isErr() || sfPacket.isErr() || rdPacket.isErr() || dsPacket.isErr()) {
3776
3626
  this._applySnapshotIfCurrent(generation, { isAvailable: false });
3777
- return ok(false);
3627
+ return ok5(false);
3778
3628
  }
3779
3629
  const snapshot = this._buildSnapshot(ssPacket.value, sfPacket.value, rdPacket.value, dsPacket.value);
3780
- return ok(this._applySnapshotIfCurrent(generation, snapshot));
3630
+ return ok5(this._applySnapshotIfCurrent(generation, snapshot));
3781
3631
  }
3782
3632
  _buildSnapshot(ssPacket, sfPacket, rdPacket, dsPacket) {
3783
3633
  const flags2 = ssPacket.sysflags[2];
@@ -3829,7 +3679,7 @@ class V5SerialDevice extends VexSerialDevice {
3829
3679
  {
3830
3680
  battery: controller1Battery,
3831
3681
  isAvailable: controller1Available,
3832
- isCharging: (flags2 & 128) !== 0
3682
+ isCharging: undefined
3833
3683
  }
3834
3684
  ],
3835
3685
  radio: {
@@ -3856,7 +3706,26 @@ class V5SerialDevice extends VexSerialDevice {
3856
3706
  }
3857
3707
  this.state.matchMode = snapshot.matchMode;
3858
3708
  this.state.isFieldControllerConnected = snapshot.isFieldControllerConnected;
3859
- Object.assign(this.state.brain, snapshot.brain);
3709
+ const brain = this.state.brain;
3710
+ brain.activeProgram = snapshot.brain.activeProgram;
3711
+ brain.battery.batteryPercent = snapshot.brain.battery.batteryPercent;
3712
+ brain.battery.isCharging = snapshot.brain.battery.isCharging;
3713
+ brain.button.isPressed = snapshot.brain.button.isPressed;
3714
+ brain.button.isDoublePressed = snapshot.brain.button.isDoublePressed;
3715
+ if (brain.cpu0Version.compare(snapshot.brain.cpu0Version) !== 0) {
3716
+ brain.cpu0Version = snapshot.brain.cpu0Version;
3717
+ }
3718
+ if (brain.cpu1Version.compare(snapshot.brain.cpu1Version) !== 0) {
3719
+ brain.cpu1Version = snapshot.brain.cpu1Version;
3720
+ }
3721
+ brain.isAvailable = snapshot.brain.isAvailable;
3722
+ brain.settings.isScreenReversed = snapshot.brain.settings.isScreenReversed;
3723
+ brain.settings.isWhiteTheme = snapshot.brain.settings.isWhiteTheme;
3724
+ brain.settings.usingLanguage = snapshot.brain.settings.usingLanguage;
3725
+ if (brain.systemVersion.compare(snapshot.brain.systemVersion) !== 0) {
3726
+ brain.systemVersion = snapshot.brain.systemVersion;
3727
+ }
3728
+ brain.uniqueId = snapshot.brain.uniqueId;
3860
3729
  Object.assign(this.state.controllers[0], snapshot.controllers[0]);
3861
3730
  Object.assign(this.state.controllers[1], snapshot.controllers[1]);
3862
3731
  Object.assign(this.state.radio, snapshot.radio);
@@ -3865,10 +3734,18 @@ class V5SerialDevice extends VexSerialDevice {
3865
3734
  if (device != null)
3866
3735
  next[device.port] = device;
3867
3736
  }
3868
- this.state.devices = next;
3737
+ if (!sameSmartDeviceSlots(this.state.devices, next)) {
3738
+ this.state.devices = next;
3739
+ }
3869
3740
  return true;
3870
3741
  }
3871
3742
  }
3743
+ function sameSmartDeviceSlots(left, right) {
3744
+ return left.length === right.length && left.every((device, index) => {
3745
+ const next = right[index];
3746
+ return device === next || device !== undefined && next !== undefined && device.port === next.port && device.type === next.type && device.status === next.status && device.betaversion === next.betaversion && device.version === next.version && device.bootversion === next.bootversion;
3747
+ });
3748
+ }
3872
3749
  // src/VexIniConfig.ts
3873
3750
  class BaseIniBuilder {
3874
3751
  str = "";
@@ -3877,7 +3754,7 @@ class BaseIniBuilder {
3877
3754
  `;
3878
3755
  }
3879
3756
  addComment(comment) {
3880
- this.addLine("; " + comment);
3757
+ this.addLine(comment.length === 0 ? ";" : "; " + comment);
3881
3758
  return this;
3882
3759
  }
3883
3760
  getContent() {
@@ -3895,18 +3772,23 @@ class IniSectionBuilder extends BaseIniBuilder {
3895
3772
  this.object = object;
3896
3773
  this.keyTransform = keyTransform;
3897
3774
  }
3898
- addSingleObjProperty(key, maxValueLength) {
3899
- const value = this.object[key];
3900
- if (value == null || value.toString().length === 0)
3775
+ addSingleObjProperty(key, maxValueLength, omitIfEmpty = false) {
3776
+ const rawValue = this.object[key];
3777
+ if (rawValue == null)
3901
3778
  return;
3779
+ const value = rawValue.toString();
3780
+ if (omitIfEmpty && value.length === 0)
3781
+ return;
3782
+ if (/["\u0000-\u001f\u007f]/.test(value)) {
3783
+ throw new Error(`INI value for ${this.name}.${String(key)} contains an unsupported character`);
3784
+ }
3902
3785
  const formattedKey = this.keyTransform(key).padEnd(12).slice(0, 12);
3903
- const trimmedVal = value.toString().slice(0, maxValueLength);
3904
- const escapedVal = trimmedVal.replace(/["\\\u0000-\u001f\u007f]/g, (character) => `\\x${character.charCodeAt(0).toString(16).padStart(2, "0")}`);
3905
- this.addLine(`${formattedKey} = "${escapedVal}"`);
3786
+ const trimmedValue = value.slice(0, maxValueLength);
3787
+ this.addLine(`${formattedKey} = "${trimmedValue}"`);
3906
3788
  }
3907
- addObjProperty(key, maxValueLength) {
3789
+ addObjProperty(key, maxValueLength, omitIfEmpty = false) {
3908
3790
  for (const k of Array.isArray(key) ? key : [key]) {
3909
- this.addSingleObjProperty(k, maxValueLength);
3791
+ this.addSingleObjProperty(k, maxValueLength, omitIfEmpty);
3910
3792
  }
3911
3793
  return this;
3912
3794
  }
@@ -3924,6 +3806,11 @@ class IniFileBuilder extends BaseIniBuilder {
3924
3806
  this.str += section.getContent();
3925
3807
  return this;
3926
3808
  }
3809
+ addOptionalSection(section) {
3810
+ if (section.getContent().length === 0)
3811
+ return this;
3812
+ return this.addComment("").addSection(section);
3813
+ }
3927
3814
  }
3928
3815
 
3929
3816
  class ProgramIniConfig {
@@ -3954,7 +3841,7 @@ class ProgramIniConfig {
3954
3841
  if (this.program.date.length === 0) {
3955
3842
  this.setProgramDate(new Date);
3956
3843
  }
3957
- return new IniFileBuilder().addComment("").addComment("VEX program ini file").addComment("Generated by Vex V5 Serial Protocol Library").addComment("").addSection(new IniSectionBuilder("project", this.project).addObjProperty("ide", 16)).addComment("").addSection(new IniSectionBuilder("program", this.program).addObjProperty("name", 32).addObjProperty("description", 256).addObjProperty("icon", 16).addObjProperty("iconalt", 16).addObjProperty("slot", 16)).addComment("").addSection(new IniSectionBuilder("config", this.config, (k) => "port_" + this.dec2(k)).addAllObjProps()).addComment("").addSection(new IniSectionBuilder("controller_1", this.controller1).addAllObjProps()).addComment("").addSection(new IniSectionBuilder("controller_2", this.controller2).addAllObjProps()).getContent();
3844
+ return new IniFileBuilder().addComment("").addComment("VEX program ini file").addComment("Generated by Vex V5 Serial Protocol Library").addComment("").addSection(new IniSectionBuilder("project", this.project).addObjProperty("version").addObjProperty("ide", 16).addObjProperty("file")).addComment("").addSection(new IniSectionBuilder("program", this.program).addObjProperty("version").addObjProperty("name", 32).addObjProperty("slot").addObjProperty("icon", 16).addObjProperty("iconalt", 16, true).addObjProperty("description", 256).addObjProperty("date").addObjProperty("timezone")).addComment("").addSection(new IniSectionBuilder("config", this.config, (k) => "port_" + this.dec2(k)).addAllObjProps()).addOptionalSection(new IniSectionBuilder("controller_1", this.controller1).addAllObjProps()).addOptionalSection(new IniSectionBuilder("controller_2", this.controller2).addAllObjProps()).getContent();
3958
3845
  }
3959
3846
  dec2(value) {
3960
3847
  return (value % 100).toString(10).padStart(2, "0").toUpperCase();
@@ -4089,5 +3976,5 @@ export {
4089
3976
  AckType
4090
3977
  };
4091
3978
 
4092
- //# debugId=658BAE3B57868EE064756E2164756E21
3979
+ //# debugId=048E789F43ADDE3C64756E2164756E21
4093
3980
  //# sourceMappingURL=index.js.map