bmweb-cli 0.1.3 → 0.1.4

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.
Files changed (2) hide show
  1. package/dist/bmweb.js +59 -17
  2. package/package.json +1 -1
package/dist/bmweb.js CHANGED
@@ -814,6 +814,7 @@ import { createInterface } from "node:readline";
814
814
 
815
815
  // src/serial.ts
816
816
  import { readdirSync as readdirSync2 } from "node:fs";
817
+ var RX_KICK_MS = 4;
817
818
  var PORT_PATTERNS = [
818
819
  /^cu\.usbserial/i,
819
820
  /^cu\.SLAB/i,
@@ -827,8 +828,12 @@ var NodeSerialPort = class {
827
828
  binding = null;
828
829
  /** chunks heard and not yet read */
829
830
  chunks = [];
830
- /** the one read waiting for bytes, when the queue is empty */
831
- waiter = null;
831
+ /** reads waiting for bytes, oldest first, when the queue is empty */
832
+ waiters = [];
833
+ /** the receive kick, running while a read waits (see startKick) */
834
+ kick = null;
835
+ /** a kick ioctl in flight, so they never pile up */
836
+ kicking = false;
832
837
  /** the lines as last set, so a partial setSignals keeps the others */
833
838
  lines = { dtr: false, rts: false, brk: false };
834
839
  /** the wire trace sink, when the CLI wants one */
@@ -864,23 +869,25 @@ var NodeSerialPort = class {
864
869
  async close() {
865
870
  const b = this.binding;
866
871
  this.binding = null;
867
- if (this.waiter) {
868
- const w = this.waiter;
869
- this.waiter = null;
870
- w({ value: void 0, done: true });
871
- }
872
+ this.stopKick();
873
+ this.wakeAll();
872
874
  this.chunks = [];
873
875
  if (b) await b.close();
874
876
  }
877
+ /** Tell every waiting read the port is done, and forget them. */
878
+ wakeAll() {
879
+ const ws = this.waiters;
880
+ this.waiters = [];
881
+ for (const w of ws) w({ value: void 0, done: true });
882
+ }
875
883
  /**
876
884
  * Bytes the binding heard: to the waiting read, else queued.
877
885
  * @param chunk - the bytes
878
886
  */
879
887
  push(chunk) {
880
888
  if (!chunk.length) return;
881
- if (this.waiter) {
882
- const w = this.waiter;
883
- this.waiter = null;
889
+ const w = this.waiters.shift();
890
+ if (w) {
884
891
  w({ value: chunk, done: false });
885
892
  return;
886
893
  }
@@ -916,19 +923,54 @@ var NodeSerialPort = class {
916
923
  if (next) return Promise.resolve({ value: next, done: false });
917
924
  if (!this.binding) return Promise.resolve({ value: void 0, done: true });
918
925
  return new Promise((resolve2) => {
919
- this.waiter = resolve2;
926
+ this.waiters.push(resolve2);
927
+ this.startKick();
920
928
  });
921
929
  }
930
+ /**
931
+ * Make the driver deliver what it has heard.
932
+ *
933
+ * THE BUG THIS FIXES. On macOS the built-in FTDI driver does not wake the
934
+ * reader when bytes arrive: with a read armed and the process simply
935
+ * waiting, an ECU's answer sat in the driver until some OTHER call touched
936
+ * the device (the next write, a modem-line change, close), and only then
937
+ * came out -- measured on a real car as 0 bytes for the first exchange
938
+ * after open and every later exchange delivering the PREVIOUS one's bytes
939
+ * at its start. Polling the modem lines (a TIOCMGET, no wire traffic)
940
+ * every few milliseconds while a read waits makes each answer arrive
941
+ * within the poll interval, first exchange included. The interval never
942
+ * holds the process open and stops itself once no read is waiting.
943
+ */
944
+ startKick() {
945
+ if (this.kick) return;
946
+ const tick = () => {
947
+ const b = this.binding;
948
+ if (!b || !this.waiters.length) {
949
+ this.stopKick();
950
+ return;
951
+ }
952
+ if (this.kicking) return;
953
+ this.kicking = true;
954
+ b.get().catch(() => null).then(() => {
955
+ this.kicking = false;
956
+ });
957
+ };
958
+ this.kick = setInterval(tick, RX_KICK_MS);
959
+ if (typeof this.kick === "object" && "unref" in this.kick)
960
+ this.kick.unref();
961
+ }
962
+ /** Stop the receive kick. */
963
+ stopKick() {
964
+ if (!this.kick) return;
965
+ clearInterval(this.kick);
966
+ this.kick = null;
967
+ }
922
968
  /**
923
969
  * Cancel the reader: a waiting read is told `done`, buffered bytes go.
924
970
  */
925
971
  cancel() {
926
972
  this.chunks = [];
927
- if (this.waiter) {
928
- const w = this.waiter;
929
- this.waiter = null;
930
- w({ value: void 0, done: true });
931
- }
973
+ this.wakeAll();
932
974
  }
933
975
  /**
934
976
  * Write bytes. Resolves when the OS has them, not when they have left the
@@ -2620,7 +2662,7 @@ async function tuiCommand(chassis, sgbd, opts = {}) {
2620
2662
  }
2621
2663
 
2622
2664
  // src/bmweb.ts
2623
- var VERSION = true ? "0.1.3" : "0.0.0-dev";
2665
+ var VERSION = true ? "0.1.4" : "0.0.0-dev";
2624
2666
  var INCLUDE = {
2625
2667
  include: {
2626
2668
  kind: "list",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bmweb-cli",
3
- "version": "0.1.3",
3
+ "version": "0.1.4",
4
4
  "description": "BMWeb's tools as a command line: read INPA .IPO scripts, compile .IPS sources, search the corpus job index, decode and diff shared Garage reports, and, over a K+DCAN cable, run jobs, whole-car scans and INPA screens in the terminal.",
5
5
  "license": "GPL-3.0-only",
6
6
  "type": "module",