@kb1rma/libcomm14cux-ts 0.2.0-beta.7 → 0.2.1-beta.9

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/README.md CHANGED
@@ -99,6 +99,8 @@ All ECU access is memory reads and writes:
99
99
  - A single read returns 1–16, 80, 100, 400 or 512 bytes. Longer reads are split into chunks.
100
100
  - When the next read uses the same length and falls in the same 64-byte block as the last coarse address, the coarse-address step is skipped. libcomm14cux compares a 64-byte window starting at the last (unaligned) address instead, which can read from the wrong block; this library deliberately does not.
101
101
  - Any echo mismatch or timeout fails the operation and clears the coarse-address cache.
102
+ - After a failure the ECU may still be waiting for the rest of a command, and would store the next byte it receives if that command was a write. So the next operation first waits until the line has been quiet for `commandResetMs` (an `Ecu` option, default 500 ms), discarding any late bytes, so that the ECU drops the half-received command. libcomm14cux does not do this; this library deliberately does.
103
+ - A write that fails may still have reached the ECU: the value can be stored even though its echo was lost or garbled. Treat a failed write as "may have been written".
102
104
  - Long reads (such as a ROM dump) can be stopped with `ecu.cancelRead()`, as in libcomm14cux.
103
105
 
104
106
  ### CommandQueue
@@ -171,4 +171,10 @@ export declare const PurgeValveState: {
171
171
  export type PurgeValveState = (typeof PurgeValveState)[keyof typeof PurgeValveState];
172
172
  /** Default per-read silence timeout, matching libcomm14cux (100 ms). */
173
173
  export declare const DEFAULT_READ_TIMEOUT_MS = 100;
174
+ /**
175
+ * Default quiet time after a failed command (500 ms). The ECU drops a
176
+ * half-received command after 256 passes of its main loop with nothing
177
+ * received; this is a conservative estimate of how long that takes.
178
+ */
179
+ export declare const DEFAULT_COMMAND_RESET_MS = 500;
174
180
  //# sourceMappingURL=constants.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../src/constants.ts"],"names":[],"mappings":"AAKA,0CAA0C;AAC1C,eAAO,MAAM,IAAI,OAAO,CAAC;AAEzB,qEAAqE;AACrE,eAAO,MAAM,iBAAiB,QAAQ,CAAC;AAEvC,sDAAsD;AACtD,eAAO,MAAM,gBAAgB,KAAK,CAAC;AAEnC,qDAAqD;AACrD,eAAO,MAAM,aAAa,IAAI,CAAC;AAE/B,mFAAmF;AACnF,eAAO,MAAM,SAAS;;;;;;CAMZ,CAAC;AAEX,kEAAkE;AAClE,eAAO,MAAM,cAAc;;;;;CAKjB,CAAC;AAEX,8DAA8D;AAC9D,eAAO,MAAM,QAAQ;;;CAGX,CAAC;AAEX,oFAAoF;AACpF,eAAO,MAAM,YAAY;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAwDf,CAAC;AAEX,6DAA6D;AAC7D,eAAO,MAAM,0BAA0B,MAAQ,CAAC;AAEhD,4EAA4E;AAC5E,eAAO,MAAM,qBAAqB;;;;CAIxB,CAAC;AAEX,oCAAoC;AACpC,eAAO,MAAM,IAAI;;;;;CAKP,CAAC;AACX;;GAEG;AACH,MAAM,MAAM,IAAI,GAAG,CAAC,OAAO,IAAI,CAAC,CAAC,MAAM,OAAO,IAAI,CAAC,CAAC;AAEpD,4BAA4B;AAC5B,eAAO,MAAM,IAAI;;;CAA+B,CAAC;AACjD;;GAEG;AACH,MAAM,MAAM,IAAI,GAAG,CAAC,OAAO,IAAI,CAAC,CAAC,MAAM,OAAO,IAAI,CAAC,CAAC;AAEpD,wCAAwC;AACxC,eAAO,MAAM,cAAc;;;CAAyC,CAAC;AACrE;;GAEG;AACH,MAAM,MAAM,cAAc,GACxB,CAAC,OAAO,cAAc,CAAC,CAAC,MAAM,OAAO,cAAc,CAAC,CAAC;AAEvD,8BAA8B;AAC9B,eAAO,MAAM,YAAY;;;CAA0C,CAAC;AACpE;;GAEG;AACH,MAAM,MAAM,YAAY,GAAG,CAAC,OAAO,YAAY,CAAC,CAAC,MAAM,OAAO,YAAY,CAAC,CAAC;AAE5E,qDAAqD;AACrD,eAAO,MAAM,WAAW;;;CAAwC,CAAC;AACjE;;GAEG;AACH,MAAM,MAAM,WAAW,GAAG,CAAC,OAAO,WAAW,CAAC,CAAC,MAAM,OAAO,WAAW,CAAC,CAAC;AAEzE,2DAA2D;AAC3D,eAAO,MAAM,eAAe;;;CAAyC,CAAC;AACtE;;GAEG;AACH,MAAM,MAAM,eAAe,GACzB,CAAC,OAAO,eAAe,CAAC,CAAC,MAAM,OAAO,eAAe,CAAC,CAAC;AAEzD,mEAAmE;AACnE,eAAO,MAAM,aAAa;;;;;CAAmD,CAAC;AAC9E;;GAEG;AACH,MAAM,MAAM,aAAa,GAAG,CAAC,OAAO,aAAa,CAAC,CAAC,MAAM,OAAO,aAAa,CAAC,CAAC;AAE/E,iDAAiD;AACjD,eAAO,MAAM,eAAe;;;;CAA+C,CAAC;AAC5E;;GAEG;AACH,MAAM,MAAM,eAAe,GACzB,CAAC,OAAO,eAAe,CAAC,CAAC,MAAM,OAAO,eAAe,CAAC,CAAC;AAEzD,wEAAwE;AACxE,eAAO,MAAM,uBAAuB,MAAM,CAAC"}
1
+ {"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../src/constants.ts"],"names":[],"mappings":"AAKA,0CAA0C;AAC1C,eAAO,MAAM,IAAI,OAAO,CAAC;AAEzB,qEAAqE;AACrE,eAAO,MAAM,iBAAiB,QAAQ,CAAC;AAEvC,sDAAsD;AACtD,eAAO,MAAM,gBAAgB,KAAK,CAAC;AAEnC,qDAAqD;AACrD,eAAO,MAAM,aAAa,IAAI,CAAC;AAE/B,mFAAmF;AACnF,eAAO,MAAM,SAAS;;;;;;CAMZ,CAAC;AAEX,kEAAkE;AAClE,eAAO,MAAM,cAAc;;;;;CAKjB,CAAC;AAEX,8DAA8D;AAC9D,eAAO,MAAM,QAAQ;;;CAGX,CAAC;AAEX,oFAAoF;AACpF,eAAO,MAAM,YAAY;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAwDf,CAAC;AAEX,6DAA6D;AAC7D,eAAO,MAAM,0BAA0B,MAAQ,CAAC;AAEhD,4EAA4E;AAC5E,eAAO,MAAM,qBAAqB;;;;CAIxB,CAAC;AAEX,oCAAoC;AACpC,eAAO,MAAM,IAAI;;;;;CAKP,CAAC;AACX;;GAEG;AACH,MAAM,MAAM,IAAI,GAAG,CAAC,OAAO,IAAI,CAAC,CAAC,MAAM,OAAO,IAAI,CAAC,CAAC;AAEpD,4BAA4B;AAC5B,eAAO,MAAM,IAAI;;;CAA+B,CAAC;AACjD;;GAEG;AACH,MAAM,MAAM,IAAI,GAAG,CAAC,OAAO,IAAI,CAAC,CAAC,MAAM,OAAO,IAAI,CAAC,CAAC;AAEpD,wCAAwC;AACxC,eAAO,MAAM,cAAc;;;CAAyC,CAAC;AACrE;;GAEG;AACH,MAAM,MAAM,cAAc,GACxB,CAAC,OAAO,cAAc,CAAC,CAAC,MAAM,OAAO,cAAc,CAAC,CAAC;AAEvD,8BAA8B;AAC9B,eAAO,MAAM,YAAY;;;CAA0C,CAAC;AACpE;;GAEG;AACH,MAAM,MAAM,YAAY,GAAG,CAAC,OAAO,YAAY,CAAC,CAAC,MAAM,OAAO,YAAY,CAAC,CAAC;AAE5E,qDAAqD;AACrD,eAAO,MAAM,WAAW;;;CAAwC,CAAC;AACjE;;GAEG;AACH,MAAM,MAAM,WAAW,GAAG,CAAC,OAAO,WAAW,CAAC,CAAC,MAAM,OAAO,WAAW,CAAC,CAAC;AAEzE,2DAA2D;AAC3D,eAAO,MAAM,eAAe;;;CAAyC,CAAC;AACtE;;GAEG;AACH,MAAM,MAAM,eAAe,GACzB,CAAC,OAAO,eAAe,CAAC,CAAC,MAAM,OAAO,eAAe,CAAC,CAAC;AAEzD,mEAAmE;AACnE,eAAO,MAAM,aAAa;;;;;CAAmD,CAAC;AAC9E;;GAEG;AACH,MAAM,MAAM,aAAa,GAAG,CAAC,OAAO,aAAa,CAAC,CAAC,MAAM,OAAO,aAAa,CAAC,CAAC;AAE/E,iDAAiD;AACjD,eAAO,MAAM,eAAe;;;;CAA+C,CAAC;AAC5E;;GAEG;AACH,MAAM,MAAM,eAAe,GACzB,CAAC,OAAO,eAAe,CAAC,CAAC,MAAM,OAAO,eAAe,CAAC,CAAC;AAEzD,wEAAwE;AACxE,eAAO,MAAM,uBAAuB,MAAM,CAAC;AAE3C;;;;GAIG;AACH,eAAO,MAAM,wBAAwB,MAAM,CAAC"}
package/dist/constants.js CHANGED
@@ -119,4 +119,10 @@ export const DataOffsetRev = { Unset: 0, RevA: 1, RevB: 2, RevC: 3 };
119
119
  export const PurgeValveState = { Closed: 0, Toggling: 1, Open: 2 };
120
120
  /** Default per-read silence timeout, matching libcomm14cux (100 ms). */
121
121
  export const DEFAULT_READ_TIMEOUT_MS = 100;
122
+ /**
123
+ * Default quiet time after a failed command (500 ms). The ECU drops a
124
+ * half-received command after 256 passes of its main loop with nothing
125
+ * received; this is a conservative estimate of how long that takes.
126
+ */
127
+ export const DEFAULT_COMMAND_RESET_MS = 500;
122
128
  //# sourceMappingURL=constants.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"constants.js","sourceRoot":"","sources":["../src/constants.ts"],"names":[],"mappings":"AAAA,wCAAwC;AACxC,4EAA4E;AAC5E,+DAA+D;AAC/D,4DAA4D;AAE5D,0CAA0C;AAC1C,MAAM,CAAC,MAAM,IAAI,GAAG,IAAI,CAAC;AAEzB,qEAAqE;AACrE,MAAM,CAAC,MAAM,iBAAiB,GAAG,KAAK,CAAC;AAEvC,sDAAsD;AACtD,MAAM,CAAC,MAAM,gBAAgB,GAAG,EAAE,CAAC;AAEnC,qDAAqD;AACrD,MAAM,CAAC,MAAM,aAAa,GAAG,CAAC,CAAC;AAE/B,mFAAmF;AACnF,MAAM,CAAC,MAAM,SAAS,GAAG;IACvB,MAAM,EAAE,MAAM;IACd,MAAM,EAAE,MAAM;IACd,MAAM,EAAE,MAAM;IACd,MAAM,EAAE,MAAM;IACd,MAAM,EAAE,MAAM;CACN,CAAC;AAEX,kEAAkE;AAClE,MAAM,CAAC,MAAM,cAAc,GAAG;IAC5B,MAAM,EAAE,IAAI;IACZ,MAAM,EAAE,IAAI;IACZ,MAAM,EAAE,IAAI;IACZ,MAAM,EAAE,IAAI;CACJ,CAAC;AAEX,8DAA8D;AAC9D,MAAM,CAAC,MAAM,QAAQ,GAAG;IACtB,GAAG,EAAE,MAAM;IACX,OAAO,EAAE,IAAI;CACL,CAAC;AAEX,oFAAoF;AACpF,MAAM,CAAC,MAAM,YAAY,GAAG;IAC1B,UAAU,EAAE,MAAM;IAClB,QAAQ,EAAE,MAAM;IAChB,KAAK,EAAE,MAAM;IACb,4BAA4B,EAAE,MAAM;IACpC,6BAA6B,EAAE,MAAM;IACrC,6BAA6B,EAAE,MAAM;IACrC,8BAA8B,EAAE,MAAM;IACtC,UAAU,EAAE,MAAM;IAClB,uBAAuB,EAAE,MAAM;IAC/B,WAAW,EAAE,MAAM;IACnB,iBAAiB,EAAE,MAAM;IACzB,gBAAgB,EAAE,MAAM;IACxB,WAAW,EAAE,MAAM;IACnB,kBAAkB,EAAE,MAAM;IAC1B,wBAAwB,EAAE,MAAM;IAChC,mBAAmB,EAAE,MAAM;IAC3B,kBAAkB,EAAE,MAAM;IAC1B,QAAQ,EAAE,MAAM;IAChB,eAAe,EAAE,MAAM;IACvB,QAAQ,EAAE,MAAM;IAChB,gBAAgB,EAAE,MAAM;IACxB,SAAS,EAAE,MAAM;IACjB,QAAQ,EAAE,MAAM;IAChB,SAAS,EAAE,MAAM;IACjB,QAAQ,EAAE,MAAM;IAChB,QAAQ,EAAE,MAAM;IAChB,iBAAiB,EAAE,MAAM;IACzB,eAAe,EAAE,MAAM;IACvB,sBAAsB,EAAE,MAAM;IAC9B,sBAAsB,EAAE,MAAM;IAC9B,sBAAsB,EAAE,MAAM;IAC9B,sBAAsB,EAAE,MAAM;IAC9B,sBAAsB,EAAE,MAAM;IAC9B,sBAAsB,EAAE,MAAM;IAC9B,YAAY,EAAE,MAAM;IACpB,sBAAsB,EAAE,MAAM;IAC9B,QAAQ,EAAE,MAAM;IAChB,WAAW,EAAE,MAAM;IACnB,WAAW,EAAE,MAAM;IACnB,WAAW,EAAE,MAAM;IACnB,WAAW,EAAE,MAAM;IACnB,WAAW,EAAE,MAAM;IACnB,WAAW,EAAE,MAAM;IACnB,WAAW,EAAE,MAAM;IACnB,WAAW,EAAE,MAAM;IACnB,WAAW,EAAE,MAAM;IACnB,WAAW,EAAE,MAAM;IACnB,YAAY,EAAE,MAAM;IACpB,aAAa,EAAE,MAAM;IACrB,SAAS,EAAE,MAAM;IACjB,gBAAgB,EAAE,MAAM;IACxB,eAAe,EAAE,MAAM;IACvB,kBAAkB,EAAE,MAAM;IAC1B,uBAAuB,EAAE,MAAM;IAC/B,aAAa,EAAE,MAAM;CACb,CAAC;AAEX,6DAA6D;AAC7D,MAAM,CAAC,MAAM,0BAA0B,GAAG,KAAK,CAAC;AAEhD,4EAA4E;AAC5E,MAAM,CAAC,MAAM,qBAAqB,GAAG;IACnC,CAAC,EAAE,IAAI;IACP,CAAC,EAAE,IAAI;IACP,CAAC,EAAE,MAAM;CACD,CAAC;AAEX,oCAAoC;AACpC,MAAM,CAAC,MAAM,IAAI,GAAG;IAClB,SAAS,EAAE,CAAC;IACZ,aAAa,EAAE,CAAC;IAChB,cAAc,EAAE,CAAC;IACjB,aAAa,EAAE,CAAC;CACR,CAAC;AAMX,4BAA4B;AAC5B,MAAM,CAAC,MAAM,IAAI,GAAG,EAAE,GAAG,EAAE,CAAC,EAAE,IAAI,EAAE,CAAC,EAAW,CAAC;AAMjD,wCAAwC;AACxC,MAAM,CAAC,MAAM,cAAc,GAAG,EAAE,SAAS,EAAE,CAAC,EAAE,QAAQ,EAAE,CAAC,EAAW,CAAC;AAOrE,8BAA8B;AAC9B,MAAM,CAAC,MAAM,YAAY,GAAG,EAAE,UAAU,EAAE,CAAC,EAAE,QAAQ,EAAE,CAAC,EAAW,CAAC;AAMpE,qDAAqD;AACrD,MAAM,CAAC,MAAM,WAAW,GAAG,EAAE,MAAM,EAAE,CAAC,EAAE,UAAU,EAAE,CAAC,EAAW,CAAC;AAMjE,2DAA2D;AAC3D,MAAM,CAAC,MAAM,eAAe,GAAG,EAAE,QAAQ,EAAE,CAAC,EAAE,SAAS,EAAE,CAAC,EAAW,CAAC;AAOtE,mEAAmE;AACnE,MAAM,CAAC,MAAM,aAAa,GAAG,EAAE,KAAK,EAAE,CAAC,EAAE,IAAI,EAAE,CAAC,EAAE,IAAI,EAAE,CAAC,EAAE,IAAI,EAAE,CAAC,EAAW,CAAC;AAM9E,iDAAiD;AACjD,MAAM,CAAC,MAAM,eAAe,GAAG,EAAE,MAAM,EAAE,CAAC,EAAE,QAAQ,EAAE,CAAC,EAAE,IAAI,EAAE,CAAC,EAAW,CAAC;AAO5E,wEAAwE;AACxE,MAAM,CAAC,MAAM,uBAAuB,GAAG,GAAG,CAAC","sourcesContent":["// SPDX-License-Identifier: GPL-3.0-only\n// Derived from libcomm14cux (https://github.com/colinbourassa/libcomm14cux)\n// Copyright (C) Colin Bourassa. Licensed under the GNU GPL v3.\n// Ported to TypeScript and modified for comm14cux-ts, 2026.\n\n/** Baud rate of the 14CUX serial port. */\nexport const BAUD = 7812;\n\n/** Baud rate used by ECUs running modified double-speed firmware. */\nexport const BAUD_DOUBLE_SPEED = 15625;\n\n/** Number of engine speed ranges in the fuel maps. */\nexport const FUEL_MAP_COLUMNS = 16;\n\n/** Number of engine load ranges in the fuel maps. */\nexport const FUEL_MAP_ROWS = 8;\n\n/** Byte quantities that may be requested in a single read (`c14cux_readcount`). */\nexport const ReadCount = {\n Count0: 0x0010,\n Count1: 0x0050,\n Count2: 0x0064,\n Count3: 0x0190,\n Count4: 0x0200,\n} as const;\n\n/** Length codes sent to the ECU for the fixed read quantities. */\nexport const ReadCountValue = {\n Count1: 0x10,\n Count2: 0x11,\n Count3: 0x12,\n Count4: 0x13,\n} as const;\n\n/** Sizes in bytes of fixed-size structures within the ROM. */\nexport const DataSize = {\n ROM: 0x4000,\n FuelMap: 0x80,\n} as const;\n\n/** Important memory offsets in the ECU's address space (`c14cux_memory_offset`). */\nexport const MemoryOffset = {\n ROMAddress: 0xc000,\n RPMTable: 0xc800,\n Port1: 0x0002,\n LongTermLambdaFuelingTrimOdd: 0x0042,\n LongTermLambdaFuelingTrimEven: 0x0046,\n ShortTermLambdaFuelingTrimOdd: 0x0065,\n ShortTermLambdaFuelingTrimEven: 0x0067,\n FaultCodes: 0x0049,\n ThrottleMinimumPosition: 0x0051,\n MainVoltage: 0x0055,\n MassAirflowDirect: 0x0057,\n ThrottlePosition: 0x005f,\n CoolantTemp: 0x006a,\n IdleBypassPosition: 0x006d,\n EngineSpeedInstantaneous: 0x007a,\n EngineSpeedFiltered: 0x007c,\n InjectorPulseWidth: 0x0082,\n Bits008A: 0x008a,\n PurgeValveState: 0x0096,\n Bits00DD: 0x00dd,\n TransmissionGear: 0x2000,\n RoadSpeed: 0x2003,\n FuelTemp: 0x2006,\n RowScaler: 0x200a,\n RPMLimit: 0x200c,\n IdleMode: 0x2047,\n MassAirflowLinear: 0x204d,\n TargetIdleSpeed: 0x2051,\n RevBMainVoltageFactorA: 0xc79b,\n RevBMainVoltageFactorB: 0xc79c,\n RevBMainVoltageFactorC: 0xc79d,\n RevCMainVoltageFactorA: 0xc7c3,\n RevCMainVoltageFactorB: 0xc7c4,\n RevCMainVoltageFactorC: 0xc7c5,\n MAFRowScaler: 0xc1c7,\n Map0RowScalerInitValue: 0xc1c9,\n FuelMap0: 0xc000,\n NewFuelMap1: 0xc267,\n NewFuelMap2: 0xc379,\n NewFuelMap3: 0xc48b,\n NewFuelMap4: 0xc59d,\n NewFuelMap5: 0xc6af,\n OldFuelMap1: 0xc23f,\n OldFuelMap2: 0xc351,\n OldFuelMap3: 0xc463,\n OldFuelMap4: 0xc575,\n OldFuelMap5: 0xc687,\n TuneRevision: 0xffe9,\n ChecksumFixer: 0xffeb,\n TuneIdent: 0xffec,\n CurrentFuelMapId: 0x202c,\n FuelMapRowIndex: 0x005b,\n FuelMapColumnIndex: 0x005c,\n IdleAirControlStepCount: 0x0075,\n FuelPumpTimer: 0x00af,\n} as const;\n\n/** Offset from the start of a fuel map to its row scaler. */\nexport const FUEL_MAP_ROW_SCALER_OFFSET = 0x10a;\n\n/** Fixed main-voltage coefficients for very old ECUs (1990 and earlier). */\nexport const RevAMainVoltageFactor = {\n A: 0x64,\n B: 0xbd,\n C: 0x6180,\n} as const;\n\n/** Transmission gear selections. */\nexport const Gear = {\n NoReading: 0,\n ParkOrNeutral: 1,\n DriveOrReverse: 2,\n ManualGearbox: 3,\n} as const;\n/**\n * Union of the {@link Gear} values.\n */\nexport type Gear = (typeof Gear)[keyof typeof Gear];\n\n/** The two engine banks. */\nexport const Bank = { Odd: 0, Even: 1 } as const;\n/**\n * Union of the {@link Bank} values.\n */\nexport type Bank = (typeof Bank)[keyof typeof Bank];\n\n/** Types of lambda trim for fueling. */\nexport const LambdaTrimType = { ShortTerm: 0, LongTerm: 1 } as const;\n/**\n * Union of the {@link LambdaTrimType} values.\n */\nexport type LambdaTrimType =\n (typeof LambdaTrimType)[keyof typeof LambdaTrimType];\n\n/** Fueling feedback modes. */\nexport const FeedbackMode = { ClosedLoop: 0, OpenLoop: 1 } as const;\n/**\n * Union of the {@link FeedbackMode} values.\n */\nexport type FeedbackMode = (typeof FeedbackMode)[keyof typeof FeedbackMode];\n\n/** The two means of reading a value from the MAF. */\nexport const AirflowType = { Direct: 0, Linearized: 1 } as const;\n/**\n * Union of the {@link AirflowType} values.\n */\nexport type AirflowType = (typeof AirflowType)[keyof typeof AirflowType];\n\n/** The two methods of interpreting a throttle position. */\nexport const ThrottlePosType = { Absolute: 0, Corrected: 1 } as const;\n/**\n * Union of the {@link ThrottlePosType} values.\n */\nexport type ThrottlePosType =\n (typeof ThrottlePosType)[keyof typeof ThrottlePosType];\n\n/** Revisions of the ROM data layout (`c14cux_data_offset_rev`). */\nexport const DataOffsetRev = { Unset: 0, RevA: 1, RevB: 2, RevC: 3 } as const;\n/**\n * Union of the {@link DataOffsetRev} values.\n */\nexport type DataOffsetRev = (typeof DataOffsetRev)[keyof typeof DataOffsetRev];\n\n/** States of the carbon canister purge valve. */\nexport const PurgeValveState = { Closed: 0, Toggling: 1, Open: 2 } as const;\n/**\n * Union of the {@link PurgeValveState} values.\n */\nexport type PurgeValveState =\n (typeof PurgeValveState)[keyof typeof PurgeValveState];\n\n/** Default per-read silence timeout, matching libcomm14cux (100 ms). */\nexport const DEFAULT_READ_TIMEOUT_MS = 100;\n"]}
1
+ {"version":3,"file":"constants.js","sourceRoot":"","sources":["../src/constants.ts"],"names":[],"mappings":"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","sourcesContent":["// SPDX-License-Identifier: GPL-3.0-only\n// Derived from libcomm14cux (https://github.com/colinbourassa/libcomm14cux)\n// Copyright (C) Colin Bourassa. Licensed under the GNU GPL v3.\n// Ported to TypeScript and modified for comm14cux-ts, 2026.\n\n/** Baud rate of the 14CUX serial port. */\nexport const BAUD = 7812;\n\n/** Baud rate used by ECUs running modified double-speed firmware. */\nexport const BAUD_DOUBLE_SPEED = 15625;\n\n/** Number of engine speed ranges in the fuel maps. */\nexport const FUEL_MAP_COLUMNS = 16;\n\n/** Number of engine load ranges in the fuel maps. */\nexport const FUEL_MAP_ROWS = 8;\n\n/** Byte quantities that may be requested in a single read (`c14cux_readcount`). */\nexport const ReadCount = {\n Count0: 0x0010,\n Count1: 0x0050,\n Count2: 0x0064,\n Count3: 0x0190,\n Count4: 0x0200,\n} as const;\n\n/** Length codes sent to the ECU for the fixed read quantities. */\nexport const ReadCountValue = {\n Count1: 0x10,\n Count2: 0x11,\n Count3: 0x12,\n Count4: 0x13,\n} as const;\n\n/** Sizes in bytes of fixed-size structures within the ROM. */\nexport const DataSize = {\n ROM: 0x4000,\n FuelMap: 0x80,\n} as const;\n\n/** Important memory offsets in the ECU's address space (`c14cux_memory_offset`). */\nexport const MemoryOffset = {\n ROMAddress: 0xc000,\n RPMTable: 0xc800,\n Port1: 0x0002,\n LongTermLambdaFuelingTrimOdd: 0x0042,\n LongTermLambdaFuelingTrimEven: 0x0046,\n ShortTermLambdaFuelingTrimOdd: 0x0065,\n ShortTermLambdaFuelingTrimEven: 0x0067,\n FaultCodes: 0x0049,\n ThrottleMinimumPosition: 0x0051,\n MainVoltage: 0x0055,\n MassAirflowDirect: 0x0057,\n ThrottlePosition: 0x005f,\n CoolantTemp: 0x006a,\n IdleBypassPosition: 0x006d,\n EngineSpeedInstantaneous: 0x007a,\n EngineSpeedFiltered: 0x007c,\n InjectorPulseWidth: 0x0082,\n Bits008A: 0x008a,\n PurgeValveState: 0x0096,\n Bits00DD: 0x00dd,\n TransmissionGear: 0x2000,\n RoadSpeed: 0x2003,\n FuelTemp: 0x2006,\n RowScaler: 0x200a,\n RPMLimit: 0x200c,\n IdleMode: 0x2047,\n MassAirflowLinear: 0x204d,\n TargetIdleSpeed: 0x2051,\n RevBMainVoltageFactorA: 0xc79b,\n RevBMainVoltageFactorB: 0xc79c,\n RevBMainVoltageFactorC: 0xc79d,\n RevCMainVoltageFactorA: 0xc7c3,\n RevCMainVoltageFactorB: 0xc7c4,\n RevCMainVoltageFactorC: 0xc7c5,\n MAFRowScaler: 0xc1c7,\n Map0RowScalerInitValue: 0xc1c9,\n FuelMap0: 0xc000,\n NewFuelMap1: 0xc267,\n NewFuelMap2: 0xc379,\n NewFuelMap3: 0xc48b,\n NewFuelMap4: 0xc59d,\n NewFuelMap5: 0xc6af,\n OldFuelMap1: 0xc23f,\n OldFuelMap2: 0xc351,\n OldFuelMap3: 0xc463,\n OldFuelMap4: 0xc575,\n OldFuelMap5: 0xc687,\n TuneRevision: 0xffe9,\n ChecksumFixer: 0xffeb,\n TuneIdent: 0xffec,\n CurrentFuelMapId: 0x202c,\n FuelMapRowIndex: 0x005b,\n FuelMapColumnIndex: 0x005c,\n IdleAirControlStepCount: 0x0075,\n FuelPumpTimer: 0x00af,\n} as const;\n\n/** Offset from the start of a fuel map to its row scaler. */\nexport const FUEL_MAP_ROW_SCALER_OFFSET = 0x10a;\n\n/** Fixed main-voltage coefficients for very old ECUs (1990 and earlier). */\nexport const RevAMainVoltageFactor = {\n A: 0x64,\n B: 0xbd,\n C: 0x6180,\n} as const;\n\n/** Transmission gear selections. */\nexport const Gear = {\n NoReading: 0,\n ParkOrNeutral: 1,\n DriveOrReverse: 2,\n ManualGearbox: 3,\n} as const;\n/**\n * Union of the {@link Gear} values.\n */\nexport type Gear = (typeof Gear)[keyof typeof Gear];\n\n/** The two engine banks. */\nexport const Bank = { Odd: 0, Even: 1 } as const;\n/**\n * Union of the {@link Bank} values.\n */\nexport type Bank = (typeof Bank)[keyof typeof Bank];\n\n/** Types of lambda trim for fueling. */\nexport const LambdaTrimType = { ShortTerm: 0, LongTerm: 1 } as const;\n/**\n * Union of the {@link LambdaTrimType} values.\n */\nexport type LambdaTrimType =\n (typeof LambdaTrimType)[keyof typeof LambdaTrimType];\n\n/** Fueling feedback modes. */\nexport const FeedbackMode = { ClosedLoop: 0, OpenLoop: 1 } as const;\n/**\n * Union of the {@link FeedbackMode} values.\n */\nexport type FeedbackMode = (typeof FeedbackMode)[keyof typeof FeedbackMode];\n\n/** The two means of reading a value from the MAF. */\nexport const AirflowType = { Direct: 0, Linearized: 1 } as const;\n/**\n * Union of the {@link AirflowType} values.\n */\nexport type AirflowType = (typeof AirflowType)[keyof typeof AirflowType];\n\n/** The two methods of interpreting a throttle position. */\nexport const ThrottlePosType = { Absolute: 0, Corrected: 1 } as const;\n/**\n * Union of the {@link ThrottlePosType} values.\n */\nexport type ThrottlePosType =\n (typeof ThrottlePosType)[keyof typeof ThrottlePosType];\n\n/** Revisions of the ROM data layout (`c14cux_data_offset_rev`). */\nexport const DataOffsetRev = { Unset: 0, RevA: 1, RevB: 2, RevC: 3 } as const;\n/**\n * Union of the {@link DataOffsetRev} values.\n */\nexport type DataOffsetRev = (typeof DataOffsetRev)[keyof typeof DataOffsetRev];\n\n/** States of the carbon canister purge valve. */\nexport const PurgeValveState = { Closed: 0, Toggling: 1, Open: 2 } as const;\n/**\n * Union of the {@link PurgeValveState} values.\n */\nexport type PurgeValveState =\n (typeof PurgeValveState)[keyof typeof PurgeValveState];\n\n/** Default per-read silence timeout, matching libcomm14cux (100 ms). */\nexport const DEFAULT_READ_TIMEOUT_MS = 100;\n\n/**\n * Default quiet time after a failed command (500 ms). The ECU drops a\n * half-received command after 256 passes of its main loop with nothing\n * received; this is a conservative estimate of how long that takes.\n */\nexport const DEFAULT_COMMAND_RESET_MS = 500;\n"]}
package/dist/ecu.d.ts CHANGED
@@ -13,6 +13,15 @@ export interface EcuOptions {
13
13
  * libcomm14cux.
14
14
  */
15
15
  readTimeoutMs?: number;
16
+ /**
17
+ * After a command fails part-way, the ECU may still be waiting for the rest
18
+ * of it, and would take the next byte sent as that rest (for a write, as the
19
+ * value to store). So before the next command, the `Ecu` waits until the
20
+ * line has been quiet this long, in milliseconds, discarding anything that
21
+ * arrives late. It should exceed the time the ECU takes to drop a
22
+ * half-received command. Defaults to 500.
23
+ */
24
+ commandResetMs?: number;
16
25
  }
17
26
  /**
18
27
  * A fuel map read from the ECU.
package/dist/ecu.d.ts.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"ecu.d.ts","sourceRoot":"","sources":["../src/ecu.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"ecu.d.ts","sourceRoot":"","sources":["../src/ecu.ts"],"names":[],"mappings":"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"}
package/dist/ecu.js CHANGED
@@ -3,7 +3,7 @@
3
3
  // Copyright (C) Colin Bourassa. Licensed under the GNU GPL v3.
4
4
  // Ported to TypeScript and modified for comm14cux-ts, 2026.
5
5
  import { be16 } from './bytes.js';
6
- import { AirflowType, Bank, DataOffsetRev, DataSize, DEFAULT_READ_TIMEOUT_MS, FUEL_MAP_COLUMNS, MemoryOffset, RevAMainVoltageFactor, ThrottlePosType, } from './constants.js';
6
+ import { AirflowType, Bank, DataOffsetRev, DataSize, DEFAULT_COMMAND_RESET_MS, DEFAULT_READ_TIMEOUT_MS, FUEL_MAP_COLUMNS, MemoryOffset, RevAMainVoltageFactor, ThrottlePosType, } from './constants.js';
7
7
  import { decodeAcCompressor, decodeFuelPumpRelay, decodeIdleMode, decodeMilOn, decodePurgeValveState, decodeScreenHeater, } from './decoders/flags.js';
8
8
  import { decodeEngineRpm, pulseWidthToRpm } from './decoders/engineSpeed.js';
9
9
  import { decodeGear } from './decoders/gear.js';
@@ -69,7 +69,7 @@ export class Ecu {
69
69
  */
70
70
  constructor(transport, options = {}) {
71
71
  this.#transport = transport;
72
- this.#protocol = new Protocol(transport, options.readTimeoutMs ?? DEFAULT_READ_TIMEOUT_MS);
72
+ this.#protocol = new Protocol(transport, options.readTimeoutMs ?? DEFAULT_READ_TIMEOUT_MS, options.commandResetMs ?? DEFAULT_COMMAND_RESET_MS);
73
73
  }
74
74
  /**
75
75
  * Returns the version of this library (`c14cux_getLibraryVersion`).
package/dist/ecu.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"ecu.js","sourceRoot":"","sources":["../src/ecu.ts"],"names":[],"mappings":"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","sourcesContent":["// SPDX-License-Identifier: GPL-3.0-only\n// Derived from libcomm14cux (https://github.com/colinbourassa/libcomm14cux)\n// Copyright (C) Colin Bourassa. Licensed under the GNU GPL v3.\n// Ported to TypeScript and modified for comm14cux-ts, 2026.\n\nimport { be16 } from './bytes.js';\nimport {\n AirflowType,\n Bank,\n DataOffsetRev,\n DataSize,\n DEFAULT_READ_TIMEOUT_MS,\n FUEL_MAP_COLUMNS,\n MemoryOffset,\n RevAMainVoltageFactor,\n ThrottlePosType,\n type Gear,\n type PurgeValveState,\n} from './constants.js';\nimport {\n decodeAcCompressor,\n decodeFuelPumpRelay,\n decodeIdleMode,\n decodeMilOn,\n decodePurgeValveState,\n decodeScreenHeater,\n} from './decoders/flags.js';\nimport { decodeEngineRpm, pulseWidthToRpm } from './decoders/engineSpeed.js';\nimport { decodeGear } from './decoders/gear.js';\nimport { decodeIdleBypassPosition } from './decoders/idleBypass.js';\nimport { decodeMafDirect, decodeMafLinear } from './decoders/airflow.js';\nimport { decodeMainVoltage } from './decoders/mainVoltage.js';\nimport { decodeRoadSpeedMph } from './decoders/roadSpeed.js';\nimport { decodeTemperatureF } from './decoders/temperature.js';\nimport {\n decodeThrottlePosition,\n assertThrottleReading,\n} from './decoders/throttle.js';\nimport {\n decodeCoTrimVoltage,\n decodeLambdaTrim,\n} from './decoders/lambdaTrim.js';\nimport {\n FAULT_CODE_BLOCK_SIZE,\n decodeFaultCodes,\n type FaultCodes,\n} from './decoders/faultCodes.js';\nimport {\n decodeCurrentFuelMap,\n decodeFuelMapColumnIndex,\n decodeFuelMapRowIndex,\n assertFuelMapId,\n fuelMapLocation,\n type KnownDataOffsetRev,\n rpmTableEntryAddress,\n rpmTableSlot,\n type FuelMapIndex,\n} from './decoders/fuelMap.js';\nimport { classifyOldRevision, isRevC } from './decoders/romRevision.js';\nimport {\n decodeTuneRevision,\n type TuneRevision,\n} from './decoders/tuneRevision.js';\nimport { InvalidReadingError, NotConnectedError } from './errors.js';\nimport { Protocol } from './protocol/protocol.js';\nimport { CommandQueue } from './queue.js';\nimport type { Transport } from './transport/types.js';\nimport { LIBRARY_VERSION, type Version } from './version.js';\n\n/**\n * Options for {@link Ecu}.\n */\nexport interface EcuOptions {\n /**\n * Silence timeout for each read, in milliseconds. Defaults to 100, as in\n * libcomm14cux.\n */\n readTimeoutMs?: number;\n}\n\n/**\n * A fuel map read from the ECU.\n */\nexport interface FuelMap {\n /**\n * The map: 128 bytes, 8 rows by 16 columns.\n */\n data: Uint8Array;\n /**\n * The adjustment factor stored after the map data.\n */\n adjustmentFactor: number;\n /**\n * The value used to scale map values by row position.\n */\n rowScaler: number;\n}\n\n/**\n * Connection to a 14CUX ECU. Mirrors the public API of libcomm14cux: each\n * `c14cux_*` function is a method here, with its out-parameters returned as\n * values and its `false` result thrown as an error.\n *\n * Every public operation is queued, so calls may be made concurrently.\n * {@link Ecu.cancelRead} is the exception: it acts immediately.\n *\n * Unless a method says otherwise, it rejects with:\n * - {@link NotConnectedError} if {@link Ecu.connect} has not been called;\n * - {@link TimeoutError} if the ECU stops responding;\n * - {@link ProtocolError} if the ECU echoes a command byte incorrectly.\n *\n * All of these extend {@link Comm14cuxError}.\n *\n * @example\n * ```ts\n * const ecu = new Ecu(new WebSerialTransport(port));\n *\n * await ecu.connect();\n * console.log(await ecu.getEngineRPM());\n * await ecu.disconnect();\n * ```\n */\nexport class Ecu {\n readonly #transport: Transport;\n readonly #protocol: Protocol;\n readonly #queue = new CommandQueue();\n #connected = false;\n // Operations are numbered when called. cancelRead() cancels the multi-chunk\n // reads of every operation numbered up to #cancelledThrough.\n #requested = 0;\n #running = 0;\n #cancelledThrough = 0;\n #promRev: KnownDataOffsetRev | undefined = undefined;\n #voltageFactorA = 0;\n #voltageFactorB = 0;\n #voltageFactorC = 0;\n\n /**\n * Creates a connection object. Nothing is sent until {@link Ecu.connect}.\n *\n * @param transport - The byte-level link to the ECU, for example a\n * {@link WebSerialTransport} or a {@link SimulatedTransport}.\n * @param options - Optional settings.\n */\n constructor(transport: Transport, options: EcuOptions = {}) {\n this.#transport = transport;\n this.#protocol = new Protocol(\n transport,\n options.readTimeoutMs ?? DEFAULT_READ_TIMEOUT_MS,\n );\n }\n\n /**\n * Returns the version of this library (`c14cux_getLibraryVersion`).\n *\n * @returns The major, minor and patch version numbers.\n */\n static getLibraryVersion(): Version {\n return { ...LIBRARY_VERSION };\n }\n\n // ---- connection ---------------------------------------------------------\n\n /**\n * Reports whether the transport is open (`c14cux_isConnected`).\n *\n * @returns `true` after a successful {@link Ecu.connect} and before {@link Ecu.disconnect}.\n */\n isConnected(): boolean {\n return this.#connected;\n }\n\n /**\n * Opens the transport (`c14cux_connect`). Does nothing if already connected.\n *\n * Unlike the C function this takes no device path or baud rate; those\n * belong to the {@link Transport}.\n *\n * @returns A promise that resolves when the operation is complete.\n * @throws Whatever the transport's `open()` rejects with, for example if the serial port is busy.\n */\n connect(): Promise<void> {\n return this.#queue.run(async () => {\n if (!this.#connected) {\n await this.#transport.open();\n this.#connected = true;\n }\n });\n }\n\n /**\n * Closes the transport (`c14cux_disconnect`) and forgets cached ROM details. Does nothing if not connected.\n *\n * Waits for any operation in progress to finish first.\n *\n * @returns A promise that resolves when the operation is complete.\n * @throws Whatever the transport's `close()` rejects with.\n */\n disconnect(): Promise<void> {\n return this.#queue.run(async () => {\n if (this.#connected) {\n await this.#transport.close();\n this.#connected = false;\n this.#protocol.resetCache();\n // A different ECU may be connected next time.\n this.#promRev = undefined;\n this.#voltageFactorA = 0;\n this.#voltageFactorB = 0;\n this.#voltageFactorC = 0;\n }\n });\n }\n\n /**\n * Cancels every multi-chunk read requested before this call (`c14cux_cancelRead`).\n *\n * A read in progress stops after the chunk currently in flight; a read still\n * waiting in the queue is cancelled before anything is sent. Each rejects\n * with {@link ReadCancelledError}. Reads short enough for a single chunk\n * (every getter except {@link Ecu.getFuelMap}) are not affected, and nor\n * is anything requested after this call.\n *\n * Unlike every other method this acts immediately instead of waiting in the\n * queue.\n */\n cancelRead(): void {\n this.#cancelledThrough = this.#requested;\n }\n\n // ---- raw access ---------------------------------------------------------\n\n /**\n * Reads a block of ECU memory (`c14cux_readMem`).\n *\n * Reads longer than the ECU can return in one command are split into\n * chunks automatically.\n *\n * @param addr - First address to read, 0 to 0xFFFF.\n * @param length - Number of bytes to read. `addr + length` must not exceed 0x10000.\n * @returns The bytes read, `length` long.\n * @throws {@link NotConnectedError} if the ECU is not connected.\n * @throws {@link TimeoutError} if the ECU stops responding.\n * @throws {@link ProtocolError} if the ECU echoes a command byte incorrectly.\n * @throws {@link RangeError} if the address or length is out of range.\n * @throws {@link ReadCancelledError} if {@link Ecu.cancelRead} was called before the read finished.\n */\n readMem(addr: number, length: number): Promise<Uint8Array> {\n return this.#run(() => this.#read(addr, length));\n }\n\n /**\n * Writes one byte to ECU memory (`c14cux_writeMem`).\n *\n * Writing to ECU memory can affect a running engine; use with care.\n *\n * @param addr - Address to write, 0 to 0xFFFF.\n * @param value - Byte to write, 0 to 255.\n * @returns A promise that resolves when the operation is complete.\n * @throws {@link NotConnectedError} if the ECU is not connected.\n * @throws {@link TimeoutError} if the ECU stops responding.\n * @throws {@link ProtocolError} if the ECU echoes a command byte incorrectly.\n * @throws {@link RangeError} if the address or value is out of range.\n */\n writeMem(addr: number, value: number): Promise<void> {\n return this.#run(() => this.#protocol.writeMem(addr, value));\n }\n\n /**\n * Reads the entire 16 KiB firmware image from 0xC000 (`c14cux_dumpROM`).\n *\n * This takes several seconds; use {@link Ecu.cancelRead} to stop it.\n *\n * @returns The 0x4000 bytes of ROM.\n * @throws {@link NotConnectedError} if the ECU is not connected.\n * @throws {@link TimeoutError} if the ECU stops responding.\n * @throws {@link ProtocolError} if the ECU echoes a command byte incorrectly.\n * @throws {@link ReadCancelledError} if {@link Ecu.cancelRead} was called before the read finished.\n */\n dumpROM(): Promise<Uint8Array> {\n return this.readMem(MemoryOffset.ROMAddress, DataSize.ROM);\n }\n\n // ---- simple readings ----------------------------------------------------\n\n /**\n * Reads the vehicle road speed (`c14cux_getRoadSpeed`).\n *\n * @returns Road speed in whole miles per hour (the ECU reports km/h; the value is converted and truncated).\n * @throws {@link NotConnectedError} if the ECU is not connected.\n * @throws {@link TimeoutError} if the ECU stops responding.\n * @throws {@link ProtocolError} if the ECU echoes a command byte incorrectly.\n */\n getRoadSpeed(): Promise<number> {\n return this.#run(async () =>\n decodeRoadSpeedMph(await this.#byte(MemoryOffset.RoadSpeed)),\n );\n }\n\n /**\n * Reads the engine coolant temperature (`c14cux_getCoolantTemp`).\n *\n * @returns Temperature in degrees Fahrenheit.\n * @throws {@link NotConnectedError} if the ECU is not connected.\n * @throws {@link TimeoutError} if the ECU stops responding.\n * @throws {@link ProtocolError} if the ECU echoes a command byte incorrectly.\n */\n getCoolantTemp(): Promise<number> {\n return this.#run(async () =>\n decodeTemperatureF(await this.#byte(MemoryOffset.CoolantTemp)),\n );\n }\n\n /**\n * Reads the fuel temperature (`c14cux_getFuelTemp`).\n *\n * @returns Temperature in degrees Fahrenheit.\n * @throws {@link NotConnectedError} if the ECU is not connected.\n * @throws {@link TimeoutError} if the ECU stops responding.\n * @throws {@link ProtocolError} if the ECU echoes a command byte incorrectly.\n */\n getFuelTemp(): Promise<number> {\n return this.#run(async () =>\n decodeTemperatureF(await this.#byte(MemoryOffset.FuelTemp)),\n );\n }\n\n /**\n * Reads the mass airflow meter (`c14cux_getMAFReading`).\n *\n * @param type - {@link AirflowType.Direct} changes linearly with sensor voltage but logarithmically with airflow; {@link AirflowType.Linearized} changes linearly with airflow.\n * @returns Airflow as a fraction from 0 to 1 of the highest possible measurement.\n * @throws {@link NotConnectedError} if the ECU is not connected.\n * @throws {@link TimeoutError} if the ECU stops responding.\n * @throws {@link ProtocolError} if the ECU echoes a command byte incorrectly.\n * @throws {@link InvalidReadingError} if the ECU returns a value outside the range its firmware can produce.\n */\n getMAFReading(type: AirflowType): Promise<number> {\n return this.#run(async () =>\n type === AirflowType.Direct\n ? decodeMafDirect(await this.#word(MemoryOffset.MassAirflowDirect))\n : decodeMafLinear(await this.#word(MemoryOffset.MassAirflowLinear)),\n );\n }\n\n /**\n * Reads the engine speed (`c14cux_getEngineRPM`).\n *\n * @returns Engine speed in revolutions per minute; 0 when the ignition is on but the engine is not running.\n * @throws {@link NotConnectedError} if the ECU is not connected.\n * @throws {@link TimeoutError} if the ECU stops responding.\n * @throws {@link ProtocolError} if the ECU echoes a command byte incorrectly.\n * @throws {@link InvalidReadingError} if the ECU returns a value outside the range its firmware can produce. (a zero pulse width)\n */\n getEngineRPM(): Promise<number> {\n return this.#run(async () =>\n decodeEngineRpm(await this.#word(MemoryOffset.EngineSpeedFiltered)),\n );\n }\n\n /**\n * Reads the rev limit (`c14cux_getRPMLimit`).\n *\n * @returns The RPM limit in revolutions per minute.\n * @throws {@link NotConnectedError} if the ECU is not connected.\n * @throws {@link TimeoutError} if the ECU stops responding.\n * @throws {@link ProtocolError} if the ECU echoes a command byte incorrectly.\n * @throws {@link InvalidReadingError} if the ECU returns a value outside the range its firmware can produce. (a zero pulse width)\n */\n getRPMLimit(): Promise<number> {\n return this.#run(async () =>\n pulseWidthToRpm(await this.#word(MemoryOffset.RPMLimit)),\n );\n }\n\n /**\n * Reads the current target idle speed (`c14cux_getTargetIdle`).\n *\n * @returns Target idle speed in revolutions per minute.\n * @throws {@link NotConnectedError} if the ECU is not connected.\n * @throws {@link TimeoutError} if the ECU stops responding.\n * @throws {@link ProtocolError} if the ECU echoes a command byte incorrectly.\n */\n getTargetIdle(): Promise<number> {\n return this.#run(() => this.#word(MemoryOffset.TargetIdleSpeed));\n }\n\n /**\n * Reads the throttle position (`c14cux_getThrottlePosition`).\n *\n * @param type - {@link ThrottlePosType.Absolute} is a simple fraction of the maximum ADC reading; {@link ThrottlePosType.Corrected} is adjusted so the lowest value the ECU has seen reads as 0.\n * @returns Throttle position as a fraction from 0 (closed) to 1 (wide open).\n * @throws {@link NotConnectedError} if the ECU is not connected.\n * @throws {@link TimeoutError} if the ECU stops responding.\n * @throws {@link ProtocolError} if the ECU echoes a command byte incorrectly.\n * @throws {@link InvalidReadingError} if the ECU returns a value outside the range its firmware can produce.\n */\n getThrottlePosition(type: ThrottlePosType): Promise<number> {\n return this.#run(async () => {\n const raw = await this.#word(MemoryOffset.ThrottlePosition);\n\n assertThrottleReading(raw);\n\n const minimum =\n type === ThrottlePosType.Corrected\n ? await this.#word(MemoryOffset.ThrottleMinimumPosition)\n : 0;\n\n return decodeThrottlePosition(raw, minimum);\n });\n }\n\n /**\n * Reads the transmission gear selection (`c14cux_getGearSelection`).\n *\n * @returns Park/neutral, drive/reverse, or manual gearbox (which does not report a gear).\n * @throws {@link NotConnectedError} if the ECU is not connected.\n * @throws {@link TimeoutError} if the ECU stops responding.\n * @throws {@link ProtocolError} if the ECU echoes a command byte incorrectly.\n */\n getGearSelection(): Promise<Gear> {\n return this.#run(async () =>\n decodeGear(await this.#byte(MemoryOffset.TransmissionGear)),\n );\n }\n\n /**\n * Reads the idle bypass (idle air control) motor position (`c14cux_getIdleBypassMotorPosition`).\n *\n * @returns Position as a fraction from 0 (closed) to 1 (widest opening).\n * @throws {@link NotConnectedError} if the ECU is not connected.\n * @throws {@link TimeoutError} if the ECU stops responding.\n * @throws {@link ProtocolError} if the ECU echoes a command byte incorrectly.\n */\n getIdleBypassMotorPosition(): Promise<number> {\n return this.#run(async () =>\n decodeIdleBypassPosition(\n await this.#byte(MemoryOffset.IdleBypassPosition),\n ),\n );\n }\n\n /**\n * Reads the injector pulse width (`c14cux_getInjectorPulseWidth`).\n *\n * The ECU uses one location for both banks, and which bank it describes at\n * the moment of the read is nondeterministic. It is most useful in open-loop\n * mode, where both banks match.\n *\n * @returns Pulse width in microseconds.\n * @throws {@link NotConnectedError} if the ECU is not connected.\n * @throws {@link TimeoutError} if the ECU stops responding.\n * @throws {@link ProtocolError} if the ECU echoes a command byte incorrectly.\n */\n getInjectorPulseWidth(): Promise<number> {\n return this.#run(() => this.#word(MemoryOffset.InjectorPulseWidth));\n }\n\n // ---- fuel trims ---------------------------------------------------------\n\n /**\n * Reads the short-term lambda fueling trim (`c14cux_getLambdaTrimShort`). A larger number means more fuel.\n *\n * @param bank - Which engine bank to read.\n * @returns Trim in counts, from -256 to 255.\n * @throws {@link NotConnectedError} if the ECU is not connected.\n * @throws {@link TimeoutError} if the ECU stops responding.\n * @throws {@link ProtocolError} if the ECU echoes a command byte incorrectly.\n */\n getLambdaTrimShort(bank: Bank): Promise<number> {\n return this.#run(async () =>\n decodeLambdaTrim(\n await this.#word(\n bank === Bank.Odd\n ? MemoryOffset.ShortTermLambdaFuelingTrimOdd\n : MemoryOffset.ShortTermLambdaFuelingTrimEven,\n ),\n ),\n );\n }\n\n /**\n * Reads the long-term lambda fueling trim (`c14cux_getLambdaTrimLong`). A larger number means more fuel.\n *\n * @param bank - Which engine bank to read.\n * @returns Trim in counts, from -256 to 255.\n * @throws {@link NotConnectedError} if the ECU is not connected.\n * @throws {@link TimeoutError} if the ECU stops responding.\n * @throws {@link ProtocolError} if the ECU echoes a command byte incorrectly.\n */\n getLambdaTrimLong(bank: Bank): Promise<number> {\n return this.#run(async () =>\n decodeLambdaTrim(\n await this.#word(\n bank === Bank.Odd\n ? MemoryOffset.LongTermLambdaFuelingTrimOdd\n : MemoryOffset.LongTermLambdaFuelingTrimEven,\n ),\n ),\n );\n }\n\n /**\n * Reads the MAF CO trim voltage (`c14cux_getCOTrimVoltage`).\n *\n * @returns Voltage in volts.\n * @throws {@link NotConnectedError} if the ECU is not connected.\n * @throws {@link TimeoutError} if the ECU stops responding.\n * @throws {@link ProtocolError} if the ECU echoes a command byte incorrectly.\n */\n getCOTrimVoltage(): Promise<number> {\n return this.#run(async () =>\n decodeCoTrimVoltage(\n await this.#word(MemoryOffset.LongTermLambdaFuelingTrimEven),\n ),\n );\n }\n\n // ---- main voltage -------------------------------------------------------\n\n /**\n * Reads the voltage supplied to the ECU (`c14cux_getMainVoltage`).\n *\n * The first call also reads the ECU's ROM to find the coefficients needed to\n * reverse its ADC computation; they are cached until {@link Ecu.disconnect}.\n *\n * @returns Main relay voltage in volts.\n * @throws {@link NotConnectedError} if the ECU is not connected.\n * @throws {@link TimeoutError} if the ECU stops responding.\n * @throws {@link ProtocolError} if the ECU echoes a command byte incorrectly.\n * @throws {@link InvalidReadingError} if the ECU returns a value outside the range its firmware can produce. (coefficients of zero, or a reading that does not fit them)\n */\n getMainVoltage(): Promise<number> {\n return this.#run(async () => {\n if (\n this.#voltageFactorA === 0 ||\n this.#voltageFactorB === 0 ||\n this.#voltageFactorC === 0\n ) {\n await this.#readVoltageCoefficients();\n }\n\n return decodeMainVoltage(\n await this.#word(MemoryOffset.MainVoltage),\n this.#voltageFactorA,\n this.#voltageFactorB,\n this.#voltageFactorC,\n );\n });\n }\n\n // ---- fuel maps ----------------------------------------------------------\n\n /**\n * Reads a fuel map (`c14cux_getFuelMap`).\n *\n * The first call also detects the ROM's data layout, which determines where\n * maps 1 to 5 are stored.\n *\n * @param fuelMapId - Map to read, 0 to 5.\n * @returns The 128 bytes of map data (8 rows by 16 columns), the adjustment factor stored after it, and the row scaler.\n * @throws {@link NotConnectedError} if the ECU is not connected.\n * @throws {@link TimeoutError} if the ECU stops responding.\n * @throws {@link ProtocolError} if the ECU echoes a command byte incorrectly.\n * @throws {@link RangeError} if `fuelMapId` is not 0 to 5 (no I/O is performed).\n * @throws {@link ReadCancelledError} if {@link Ecu.cancelRead} was called before the map data was read.\n */\n getFuelMap(fuelMapId: number): Promise<FuelMap> {\n try {\n assertFuelMapId(fuelMapId);\n } catch (error) {\n return Promise.reject(error as Error);\n }\n\n return this.#run(async () => {\n const location = fuelMapLocation(\n fuelMapId,\n await this.#determineDataOffsets(),\n );\n\n const data = await this.#read(location.offset, DataSize.FuelMap);\n const adjustmentFactor = await this.#word(\n location.offset + DataSize.FuelMap,\n );\n const rowScaler = await this.#byte(location.scalerOffset);\n\n return { data, adjustmentFactor, rowScaler };\n });\n }\n\n /**\n * Reads which fuel map is in use (`c14cux_getCurrentFuelMap`).\n *\n * Selected by a tune resistor in the harness on non-NAS Land Rovers;\n * unmodified NAS vehicles are locked to map 5.\n *\n * @returns The map id, 0 to 5.\n * @throws {@link NotConnectedError} if the ECU is not connected.\n * @throws {@link TimeoutError} if the ECU stops responding.\n * @throws {@link ProtocolError} if the ECU echoes a command byte incorrectly.\n * @throws {@link InvalidReadingError} if the ECU returns a value outside the range its firmware can produce.\n */\n getCurrentFuelMap(): Promise<number> {\n return this.#run(async () =>\n decodeCurrentFuelMap(await this.#byte(MemoryOffset.CurrentFuelMapId)),\n );\n }\n\n /**\n * Reads the current fuel map row index (`c14cux_getFuelMapRowIndex`).\n *\n * @returns The row index (0 to 7) and the row weighting.\n * @throws {@link NotConnectedError} if the ECU is not connected.\n * @throws {@link TimeoutError} if the ECU stops responding.\n * @throws {@link ProtocolError} if the ECU echoes a command byte incorrectly.\n * @throws {@link InvalidReadingError} if the ECU returns a value outside the range its firmware can produce.\n */\n getFuelMapRowIndex(): Promise<FuelMapIndex> {\n return this.#run(async () =>\n decodeFuelMapRowIndex(await this.#byte(MemoryOffset.FuelMapRowIndex)),\n );\n }\n\n /**\n * Reads the current fuel map column index (`c14cux_getFuelMapColumnIndex`).\n *\n * @returns The column index (0 to 15) and the column weighting.\n * @throws {@link NotConnectedError} if the ECU is not connected.\n * @throws {@link TimeoutError} if the ECU stops responding.\n * @throws {@link ProtocolError} if the ECU echoes a command byte incorrectly.\n * @throws {@link InvalidReadingError} if the ECU returns a value outside the range its firmware can produce.\n */\n getFuelMapColumnIndex(): Promise<FuelMapIndex> {\n return this.#run(async () =>\n decodeFuelMapColumnIndex(\n await this.#byte(MemoryOffset.FuelMapColumnIndex),\n ),\n );\n }\n\n /**\n * Reads the table of RPM thresholds that divides engine speed into the sixteen fuel map columns (`c14cux_getRpmTable`).\n *\n * @returns Sixteen RPM values, indexed as in the C library (column 0 is last).\n * @throws {@link NotConnectedError} if the ECU is not connected.\n * @throws {@link TimeoutError} if the ECU stops responding.\n * @throws {@link ProtocolError} if the ECU echoes a command byte incorrectly.\n * @throws {@link InvalidReadingError} if the ECU returns a value outside the range its firmware can produce. (a zero pulse width)\n */\n getRpmTable(): Promise<number[]> {\n return this.#run(async () => {\n const table: number[] = new Array<number>(FUEL_MAP_COLUMNS).fill(0);\n\n for (let column = 0; column < FUEL_MAP_COLUMNS; column++) {\n table[rpmTableSlot(column)] = pulseWidthToRpm(\n await this.#word(rpmTableEntryAddress(column)),\n );\n }\n\n return table;\n });\n }\n\n // ---- faults and state ---------------------------------------------------\n\n /**\n * Reads the stored fault codes (`c14cux_getFaultCodes`).\n *\n * @returns A flag for each fault; `true` means the fault is set.\n * @throws {@link NotConnectedError} if the ECU is not connected.\n * @throws {@link TimeoutError} if the ECU stops responding.\n * @throws {@link ProtocolError} if the ECU echoes a command byte incorrectly.\n */\n getFaultCodes(): Promise<FaultCodes> {\n return this.#run(async () =>\n decodeFaultCodes(\n await this.#read(MemoryOffset.FaultCodes, FAULT_CODE_BLOCK_SIZE),\n ),\n );\n }\n\n /**\n * Clears the stored fault codes by writing zero to each byte of the fault block (`c14cux_clearFaultCodes`).\n *\n * Stops at the first failed write, so the codes may be partly cleared.\n *\n * @returns A promise that resolves when the operation is complete.\n * @throws {@link NotConnectedError} if the ECU is not connected.\n * @throws {@link TimeoutError} if the ECU stops responding.\n * @throws {@link ProtocolError} if the ECU echoes a command byte incorrectly.\n */\n clearFaultCodes(): Promise<void> {\n return this.#run(async () => {\n for (let i = 0; i < FAULT_CODE_BLOCK_SIZE; i++) {\n await this.#protocol.writeMem(MemoryOffset.FaultCodes + i, 0x00);\n }\n });\n }\n\n /**\n * Reads the state of the line driving the fuel pump relay (`c14cux_getFuelPumpRelayState`).\n *\n * @returns `true` when the relay is closed (pump running).\n * @throws {@link NotConnectedError} if the ECU is not connected.\n * @throws {@link TimeoutError} if the ECU stops responding.\n * @throws {@link ProtocolError} if the ECU echoes a command byte incorrectly.\n */\n getFuelPumpRelayState(): Promise<boolean> {\n return this.#run(async () =>\n decodeFuelPumpRelay(await this.#byte(MemoryOffset.Port1)),\n );\n }\n\n /**\n * Reads the malfunction indicator lamp state (`c14cux_isMILOn`). A lit MIL implies at least one fault code, but not every fault lights it.\n *\n * @returns `true` when the MIL is lit.\n * @throws {@link NotConnectedError} if the ECU is not connected.\n * @throws {@link TimeoutError} if the ECU stops responding.\n * @throws {@link ProtocolError} if the ECU echoes a command byte incorrectly.\n */\n isMILOn(): Promise<boolean> {\n return this.#run(async () =>\n decodeMilOn(await this.#byte(MemoryOffset.Port1)),\n );\n }\n\n /**\n * Reads whether the ECU is driving an idle speed (`c14cux_getIdleMode`).\n *\n * @returns `true` in idle mode.\n * @throws {@link NotConnectedError} if the ECU is not connected.\n * @throws {@link TimeoutError} if the ECU stops responding.\n * @throws {@link ProtocolError} if the ECU echoes a command byte incorrectly.\n */\n getIdleMode(): Promise<boolean> {\n return this.#run(async () =>\n decodeIdleMode(await this.#byte(MemoryOffset.IdleMode)),\n );\n }\n\n /**\n * Reads the state of the carbon canister purge valve (`c14cux_getPurgeValveState`).\n *\n * @returns Closed, toggling, or open.\n * @throws {@link NotConnectedError} if the ECU is not connected.\n * @throws {@link TimeoutError} if the ECU stops responding.\n * @throws {@link ProtocolError} if the ECU echoes a command byte incorrectly.\n */\n getPurgeValveState(): Promise<PurgeValveState> {\n return this.#run(async () =>\n decodePurgeValveState(await this.#word(MemoryOffset.PurgeValveState)),\n );\n }\n\n /**\n * Reads the heated screen state (`c14cux_getScreenHeaterState`).\n *\n * @returns `true` when the screen heater is on.\n * @throws {@link NotConnectedError} if the ECU is not connected.\n * @throws {@link TimeoutError} if the ECU stops responding.\n * @throws {@link ProtocolError} if the ECU echoes a command byte incorrectly.\n */\n getScreenHeaterState(): Promise<boolean> {\n return this.#run(async () =>\n decodeScreenHeater(await this.#byte(MemoryOffset.Bits00DD)),\n );\n }\n\n /**\n * Reads the A/C compressor load input (`c14cux_getACCompressorState`).\n *\n * @returns `true` when the compressor is on.\n * @throws {@link NotConnectedError} if the ECU is not connected.\n * @throws {@link TimeoutError} if the ECU stops responding.\n * @throws {@link ProtocolError} if the ECU echoes a command byte incorrectly.\n */\n getACCompressorState(): Promise<boolean> {\n return this.#run(async () =>\n decodeAcCompressor(await this.#byte(MemoryOffset.Bits008A)),\n );\n }\n\n /**\n * Reads the tune identification from the ROM (`c14cux_getTuneRevision`).\n *\n * @returns The decimal tune number, checksum fixer byte and ident word.\n * @throws {@link NotConnectedError} if the ECU is not connected.\n * @throws {@link TimeoutError} if the ECU stops responding.\n * @throws {@link ProtocolError} if the ECU echoes a command byte incorrectly.\n */\n getTuneRevision(): Promise<TuneRevision> {\n return this.#run(async () =>\n decodeTuneRevision(await this.#read(MemoryOffset.TuneRevision, 5)),\n );\n }\n\n // ---- actuators ----------------------------------------------------------\n\n /**\n * Closes the fuel pump relay for a single timeout period of about two seconds (`c14cux_runFuelPump`).\n *\n * Affects a running engine; use with care.\n *\n * @returns A promise that resolves when the operation is complete.\n * @throws {@link NotConnectedError} if the ECU is not connected.\n * @throws {@link TimeoutError} if the ECU stops responding.\n * @throws {@link ProtocolError} if the ECU echoes a command byte incorrectly.\n */\n runFuelPump(): Promise<void> {\n return this.#run(async () => {\n const port1 = await this.#byte(MemoryOffset.Port1);\n\n await this.#protocol.writeMem(MemoryOffset.FuelPumpTimer, 0xff);\n await this.#protocol.writeMem(MemoryOffset.Port1, port1 & 0xbf);\n });\n }\n\n /**\n * Commands the idle air control motor to move (`c14cux_driveIdleAirControlMotor`).\n *\n * Unlike the C function this reports failure if any step fails. Affects a\n * running engine; use with care.\n *\n * @param direction - 0 opens the valve; any other value closes it.\n * @param steps - Number of steps to travel, 0 to 255.\n * @returns A promise that resolves when the operation is complete.\n * @throws {@link NotConnectedError} if the ECU is not connected.\n * @throws {@link TimeoutError} if the ECU stops responding.\n * @throws {@link ProtocolError} if the ECU echoes a command byte incorrectly.\n * @throws {@link RangeError} if `steps` is not an integer from 0 to 255 (no I/O is performed).\n */\n driveIdleAirControlMotor(direction: number, steps: number): Promise<void> {\n // Checked up front: the direction bit is written before the step count.\n if (!Number.isInteger(steps) || steps < 0 || steps > 0xff) {\n return Promise.reject(new RangeError(`Invalid step count: ${steps}`));\n }\n\n return this.#run(async () => {\n const bits = await this.#byte(MemoryOffset.Bits008A);\n\n await this.#protocol.writeMem(\n MemoryOffset.Bits008A,\n direction === 0 ? bits & 0xfe : bits | 0x01,\n );\n await this.#protocol.writeMem(\n MemoryOffset.IdleAirControlStepCount,\n steps,\n );\n });\n }\n\n // ---- internals ----------------------------------------------------------\n\n #run<T>(task: () => Promise<T>): Promise<T> {\n const ticket = ++this.#requested;\n\n return this.#queue.run(() => {\n if (!this.#connected) {\n return Promise.reject(new NotConnectedError('Not connected to ECU'));\n }\n\n this.#running = ticket;\n\n return task();\n });\n }\n\n #read(addr: number, length: number): Promise<Uint8Array> {\n return this.#protocol.readMem(\n addr,\n length,\n () => this.#running <= this.#cancelledThrough,\n );\n }\n\n async #byte(addr: number): Promise<number> {\n return (await this.#read(addr, 1))[0] ?? 0;\n }\n\n async #word(addr: number): Promise<number> {\n return be16(await this.#read(addr, 2));\n }\n\n /**\n * Works out the ROM's data layout (`c14cux_determineDataOffsets`) and caches\n * it. Does nothing if already known.\n *\n * @returns The ROM's data layout.\n */\n async #determineDataOffsets(): Promise<KnownDataOffsetRev> {\n if (this.#promRev !== undefined) {\n return this.#promRev;\n }\n\n const firstRow = await this.#read(\n MemoryOffset.OldFuelMap1,\n FUEL_MAP_COLUMNS,\n );\n\n // Very old units (about 1990 and earlier) hardcode some main-voltage\n // factors, so check for that as well.\n this.#promRev = isRevC(firstRow)\n ? DataOffsetRev.RevC\n : classifyOldRevision(\n await this.#byte(MemoryOffset.RevBMainVoltageFactorA),\n );\n\n return this.#promRev;\n }\n\n async #readVoltageCoefficients(): Promise<void> {\n const rev = await this.#determineDataOffsets();\n\n let a: number;\n let b: number;\n let c: number;\n\n if (rev === DataOffsetRev.RevA) {\n ({ A: a, B: b, C: c } = RevAMainVoltageFactor);\n } else {\n const revC = rev === DataOffsetRev.RevC;\n\n a = await this.#byte(\n revC\n ? MemoryOffset.RevCMainVoltageFactorA\n : MemoryOffset.RevBMainVoltageFactorA,\n );\n b = await this.#byte(\n revC\n ? MemoryOffset.RevCMainVoltageFactorB\n : MemoryOffset.RevBMainVoltageFactorB,\n );\n c = await this.#word(\n revC\n ? MemoryOffset.RevCMainVoltageFactorC\n : MemoryOffset.RevBMainVoltageFactorC,\n );\n }\n\n if (a === 0 || b === 0 || c === 0) {\n throw new InvalidReadingError('ECU reports zero main-voltage factors');\n }\n\n this.#voltageFactorA = a;\n this.#voltageFactorB = b;\n this.#voltageFactorC = c;\n }\n}\n"]}
1
+ {"version":3,"file":"ecu.js","sourceRoot":"","sources":["../src/ecu.ts"],"names":[],"mappings":"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","sourcesContent":["// SPDX-License-Identifier: GPL-3.0-only\n// Derived from libcomm14cux (https://github.com/colinbourassa/libcomm14cux)\n// Copyright (C) Colin Bourassa. Licensed under the GNU GPL v3.\n// Ported to TypeScript and modified for comm14cux-ts, 2026.\n\nimport { be16 } from './bytes.js';\nimport {\n AirflowType,\n Bank,\n DataOffsetRev,\n DataSize,\n DEFAULT_COMMAND_RESET_MS,\n DEFAULT_READ_TIMEOUT_MS,\n FUEL_MAP_COLUMNS,\n MemoryOffset,\n RevAMainVoltageFactor,\n ThrottlePosType,\n type Gear,\n type PurgeValveState,\n} from './constants.js';\nimport {\n decodeAcCompressor,\n decodeFuelPumpRelay,\n decodeIdleMode,\n decodeMilOn,\n decodePurgeValveState,\n decodeScreenHeater,\n} from './decoders/flags.js';\nimport { decodeEngineRpm, pulseWidthToRpm } from './decoders/engineSpeed.js';\nimport { decodeGear } from './decoders/gear.js';\nimport { decodeIdleBypassPosition } from './decoders/idleBypass.js';\nimport { decodeMafDirect, decodeMafLinear } from './decoders/airflow.js';\nimport { decodeMainVoltage } from './decoders/mainVoltage.js';\nimport { decodeRoadSpeedMph } from './decoders/roadSpeed.js';\nimport { decodeTemperatureF } from './decoders/temperature.js';\nimport {\n decodeThrottlePosition,\n assertThrottleReading,\n} from './decoders/throttle.js';\nimport {\n decodeCoTrimVoltage,\n decodeLambdaTrim,\n} from './decoders/lambdaTrim.js';\nimport {\n FAULT_CODE_BLOCK_SIZE,\n decodeFaultCodes,\n type FaultCodes,\n} from './decoders/faultCodes.js';\nimport {\n decodeCurrentFuelMap,\n decodeFuelMapColumnIndex,\n decodeFuelMapRowIndex,\n assertFuelMapId,\n fuelMapLocation,\n type KnownDataOffsetRev,\n rpmTableEntryAddress,\n rpmTableSlot,\n type FuelMapIndex,\n} from './decoders/fuelMap.js';\nimport { classifyOldRevision, isRevC } from './decoders/romRevision.js';\nimport {\n decodeTuneRevision,\n type TuneRevision,\n} from './decoders/tuneRevision.js';\nimport { InvalidReadingError, NotConnectedError } from './errors.js';\nimport { Protocol } from './protocol/protocol.js';\nimport { CommandQueue } from './queue.js';\nimport type { Transport } from './transport/types.js';\nimport { LIBRARY_VERSION, type Version } from './version.js';\n\n/**\n * Options for {@link Ecu}.\n */\nexport interface EcuOptions {\n /**\n * Silence timeout for each read, in milliseconds. Defaults to 100, as in\n * libcomm14cux.\n */\n readTimeoutMs?: number;\n /**\n * After a command fails part-way, the ECU may still be waiting for the rest\n * of it, and would take the next byte sent as that rest (for a write, as the\n * value to store). So before the next command, the `Ecu` waits until the\n * line has been quiet this long, in milliseconds, discarding anything that\n * arrives late. It should exceed the time the ECU takes to drop a\n * half-received command. Defaults to 500.\n */\n commandResetMs?: number;\n}\n\n/**\n * A fuel map read from the ECU.\n */\nexport interface FuelMap {\n /**\n * The map: 128 bytes, 8 rows by 16 columns.\n */\n data: Uint8Array;\n /**\n * The adjustment factor stored after the map data.\n */\n adjustmentFactor: number;\n /**\n * The value used to scale map values by row position.\n */\n rowScaler: number;\n}\n\n/**\n * Connection to a 14CUX ECU. Mirrors the public API of libcomm14cux: each\n * `c14cux_*` function is a method here, with its out-parameters returned as\n * values and its `false` result thrown as an error.\n *\n * Every public operation is queued, so calls may be made concurrently.\n * {@link Ecu.cancelRead} is the exception: it acts immediately.\n *\n * Unless a method says otherwise, it rejects with:\n * - {@link NotConnectedError} if {@link Ecu.connect} has not been called;\n * - {@link TimeoutError} if the ECU stops responding;\n * - {@link ProtocolError} if the ECU echoes a command byte incorrectly.\n *\n * All of these extend {@link Comm14cuxError}.\n *\n * @example\n * ```ts\n * const ecu = new Ecu(new WebSerialTransport(port));\n *\n * await ecu.connect();\n * console.log(await ecu.getEngineRPM());\n * await ecu.disconnect();\n * ```\n */\nexport class Ecu {\n readonly #transport: Transport;\n readonly #protocol: Protocol;\n readonly #queue = new CommandQueue();\n #connected = false;\n // Operations are numbered when called. cancelRead() cancels the multi-chunk\n // reads of every operation numbered up to #cancelledThrough.\n #requested = 0;\n #running = 0;\n #cancelledThrough = 0;\n #promRev: KnownDataOffsetRev | undefined = undefined;\n #voltageFactorA = 0;\n #voltageFactorB = 0;\n #voltageFactorC = 0;\n\n /**\n * Creates a connection object. Nothing is sent until {@link Ecu.connect}.\n *\n * @param transport - The byte-level link to the ECU, for example a\n * {@link WebSerialTransport} or a {@link SimulatedTransport}.\n * @param options - Optional settings.\n */\n constructor(transport: Transport, options: EcuOptions = {}) {\n this.#transport = transport;\n this.#protocol = new Protocol(\n transport,\n options.readTimeoutMs ?? DEFAULT_READ_TIMEOUT_MS,\n options.commandResetMs ?? DEFAULT_COMMAND_RESET_MS,\n );\n }\n\n /**\n * Returns the version of this library (`c14cux_getLibraryVersion`).\n *\n * @returns The major, minor and patch version numbers.\n */\n static getLibraryVersion(): Version {\n return { ...LIBRARY_VERSION };\n }\n\n // ---- connection ---------------------------------------------------------\n\n /**\n * Reports whether the transport is open (`c14cux_isConnected`).\n *\n * @returns `true` after a successful {@link Ecu.connect} and before {@link Ecu.disconnect}.\n */\n isConnected(): boolean {\n return this.#connected;\n }\n\n /**\n * Opens the transport (`c14cux_connect`). Does nothing if already connected.\n *\n * Unlike the C function this takes no device path or baud rate; those\n * belong to the {@link Transport}.\n *\n * @returns A promise that resolves when the operation is complete.\n * @throws Whatever the transport's `open()` rejects with, for example if the serial port is busy.\n */\n connect(): Promise<void> {\n return this.#queue.run(async () => {\n if (!this.#connected) {\n await this.#transport.open();\n this.#connected = true;\n }\n });\n }\n\n /**\n * Closes the transport (`c14cux_disconnect`) and forgets cached ROM details. Does nothing if not connected.\n *\n * Waits for any operation in progress to finish first.\n *\n * @returns A promise that resolves when the operation is complete.\n * @throws Whatever the transport's `close()` rejects with.\n */\n disconnect(): Promise<void> {\n return this.#queue.run(async () => {\n if (this.#connected) {\n await this.#transport.close();\n this.#connected = false;\n this.#protocol.resetCache();\n // A different ECU may be connected next time.\n this.#promRev = undefined;\n this.#voltageFactorA = 0;\n this.#voltageFactorB = 0;\n this.#voltageFactorC = 0;\n }\n });\n }\n\n /**\n * Cancels every multi-chunk read requested before this call (`c14cux_cancelRead`).\n *\n * A read in progress stops after the chunk currently in flight; a read still\n * waiting in the queue is cancelled before anything is sent. Each rejects\n * with {@link ReadCancelledError}. Reads short enough for a single chunk\n * (every getter except {@link Ecu.getFuelMap}) are not affected, and nor\n * is anything requested after this call.\n *\n * Unlike every other method this acts immediately instead of waiting in the\n * queue.\n */\n cancelRead(): void {\n this.#cancelledThrough = this.#requested;\n }\n\n // ---- raw access ---------------------------------------------------------\n\n /**\n * Reads a block of ECU memory (`c14cux_readMem`).\n *\n * Reads longer than the ECU can return in one command are split into\n * chunks automatically.\n *\n * @param addr - First address to read, 0 to 0xFFFF.\n * @param length - Number of bytes to read. `addr + length` must not exceed 0x10000.\n * @returns The bytes read, `length` long.\n * @throws {@link NotConnectedError} if the ECU is not connected.\n * @throws {@link TimeoutError} if the ECU stops responding.\n * @throws {@link ProtocolError} if the ECU echoes a command byte incorrectly.\n * @throws {@link RangeError} if the address or length is out of range.\n * @throws {@link ReadCancelledError} if {@link Ecu.cancelRead} was called before the read finished.\n */\n readMem(addr: number, length: number): Promise<Uint8Array> {\n return this.#run(() => this.#read(addr, length));\n }\n\n /**\n * Writes one byte to ECU memory (`c14cux_writeMem`).\n *\n * Writing to ECU memory can affect a running engine; use with care.\n *\n * @param addr - Address to write, 0 to 0xFFFF.\n * @param value - Byte to write, 0 to 255.\n * @returns A promise that resolves when the operation is complete.\n * @throws {@link NotConnectedError} if the ECU is not connected.\n * @throws {@link TimeoutError} if the ECU stops responding.\n * @throws {@link ProtocolError} if the ECU echoes a command byte incorrectly.\n * @throws {@link RangeError} if the address or value is out of range.\n */\n writeMem(addr: number, value: number): Promise<void> {\n return this.#run(() => this.#protocol.writeMem(addr, value));\n }\n\n /**\n * Reads the entire 16 KiB firmware image from 0xC000 (`c14cux_dumpROM`).\n *\n * This takes several seconds; use {@link Ecu.cancelRead} to stop it.\n *\n * @returns The 0x4000 bytes of ROM.\n * @throws {@link NotConnectedError} if the ECU is not connected.\n * @throws {@link TimeoutError} if the ECU stops responding.\n * @throws {@link ProtocolError} if the ECU echoes a command byte incorrectly.\n * @throws {@link ReadCancelledError} if {@link Ecu.cancelRead} was called before the read finished.\n */\n dumpROM(): Promise<Uint8Array> {\n return this.readMem(MemoryOffset.ROMAddress, DataSize.ROM);\n }\n\n // ---- simple readings ----------------------------------------------------\n\n /**\n * Reads the vehicle road speed (`c14cux_getRoadSpeed`).\n *\n * @returns Road speed in whole miles per hour (the ECU reports km/h; the value is converted and truncated).\n * @throws {@link NotConnectedError} if the ECU is not connected.\n * @throws {@link TimeoutError} if the ECU stops responding.\n * @throws {@link ProtocolError} if the ECU echoes a command byte incorrectly.\n */\n getRoadSpeed(): Promise<number> {\n return this.#run(async () =>\n decodeRoadSpeedMph(await this.#byte(MemoryOffset.RoadSpeed)),\n );\n }\n\n /**\n * Reads the engine coolant temperature (`c14cux_getCoolantTemp`).\n *\n * @returns Temperature in degrees Fahrenheit.\n * @throws {@link NotConnectedError} if the ECU is not connected.\n * @throws {@link TimeoutError} if the ECU stops responding.\n * @throws {@link ProtocolError} if the ECU echoes a command byte incorrectly.\n */\n getCoolantTemp(): Promise<number> {\n return this.#run(async () =>\n decodeTemperatureF(await this.#byte(MemoryOffset.CoolantTemp)),\n );\n }\n\n /**\n * Reads the fuel temperature (`c14cux_getFuelTemp`).\n *\n * @returns Temperature in degrees Fahrenheit.\n * @throws {@link NotConnectedError} if the ECU is not connected.\n * @throws {@link TimeoutError} if the ECU stops responding.\n * @throws {@link ProtocolError} if the ECU echoes a command byte incorrectly.\n */\n getFuelTemp(): Promise<number> {\n return this.#run(async () =>\n decodeTemperatureF(await this.#byte(MemoryOffset.FuelTemp)),\n );\n }\n\n /**\n * Reads the mass airflow meter (`c14cux_getMAFReading`).\n *\n * @param type - {@link AirflowType.Direct} changes linearly with sensor voltage but logarithmically with airflow; {@link AirflowType.Linearized} changes linearly with airflow.\n * @returns Airflow as a fraction from 0 to 1 of the highest possible measurement.\n * @throws {@link NotConnectedError} if the ECU is not connected.\n * @throws {@link TimeoutError} if the ECU stops responding.\n * @throws {@link ProtocolError} if the ECU echoes a command byte incorrectly.\n * @throws {@link InvalidReadingError} if the ECU returns a value outside the range its firmware can produce.\n */\n getMAFReading(type: AirflowType): Promise<number> {\n return this.#run(async () =>\n type === AirflowType.Direct\n ? decodeMafDirect(await this.#word(MemoryOffset.MassAirflowDirect))\n : decodeMafLinear(await this.#word(MemoryOffset.MassAirflowLinear)),\n );\n }\n\n /**\n * Reads the engine speed (`c14cux_getEngineRPM`).\n *\n * @returns Engine speed in revolutions per minute; 0 when the ignition is on but the engine is not running.\n * @throws {@link NotConnectedError} if the ECU is not connected.\n * @throws {@link TimeoutError} if the ECU stops responding.\n * @throws {@link ProtocolError} if the ECU echoes a command byte incorrectly.\n * @throws {@link InvalidReadingError} if the ECU returns a value outside the range its firmware can produce. (a zero pulse width)\n */\n getEngineRPM(): Promise<number> {\n return this.#run(async () =>\n decodeEngineRpm(await this.#word(MemoryOffset.EngineSpeedFiltered)),\n );\n }\n\n /**\n * Reads the rev limit (`c14cux_getRPMLimit`).\n *\n * @returns The RPM limit in revolutions per minute.\n * @throws {@link NotConnectedError} if the ECU is not connected.\n * @throws {@link TimeoutError} if the ECU stops responding.\n * @throws {@link ProtocolError} if the ECU echoes a command byte incorrectly.\n * @throws {@link InvalidReadingError} if the ECU returns a value outside the range its firmware can produce. (a zero pulse width)\n */\n getRPMLimit(): Promise<number> {\n return this.#run(async () =>\n pulseWidthToRpm(await this.#word(MemoryOffset.RPMLimit)),\n );\n }\n\n /**\n * Reads the current target idle speed (`c14cux_getTargetIdle`).\n *\n * @returns Target idle speed in revolutions per minute.\n * @throws {@link NotConnectedError} if the ECU is not connected.\n * @throws {@link TimeoutError} if the ECU stops responding.\n * @throws {@link ProtocolError} if the ECU echoes a command byte incorrectly.\n */\n getTargetIdle(): Promise<number> {\n return this.#run(() => this.#word(MemoryOffset.TargetIdleSpeed));\n }\n\n /**\n * Reads the throttle position (`c14cux_getThrottlePosition`).\n *\n * @param type - {@link ThrottlePosType.Absolute} is a simple fraction of the maximum ADC reading; {@link ThrottlePosType.Corrected} is adjusted so the lowest value the ECU has seen reads as 0.\n * @returns Throttle position as a fraction from 0 (closed) to 1 (wide open).\n * @throws {@link NotConnectedError} if the ECU is not connected.\n * @throws {@link TimeoutError} if the ECU stops responding.\n * @throws {@link ProtocolError} if the ECU echoes a command byte incorrectly.\n * @throws {@link InvalidReadingError} if the ECU returns a value outside the range its firmware can produce.\n */\n getThrottlePosition(type: ThrottlePosType): Promise<number> {\n return this.#run(async () => {\n const raw = await this.#word(MemoryOffset.ThrottlePosition);\n\n assertThrottleReading(raw);\n\n const minimum =\n type === ThrottlePosType.Corrected\n ? await this.#word(MemoryOffset.ThrottleMinimumPosition)\n : 0;\n\n return decodeThrottlePosition(raw, minimum);\n });\n }\n\n /**\n * Reads the transmission gear selection (`c14cux_getGearSelection`).\n *\n * @returns Park/neutral, drive/reverse, or manual gearbox (which does not report a gear).\n * @throws {@link NotConnectedError} if the ECU is not connected.\n * @throws {@link TimeoutError} if the ECU stops responding.\n * @throws {@link ProtocolError} if the ECU echoes a command byte incorrectly.\n */\n getGearSelection(): Promise<Gear> {\n return this.#run(async () =>\n decodeGear(await this.#byte(MemoryOffset.TransmissionGear)),\n );\n }\n\n /**\n * Reads the idle bypass (idle air control) motor position (`c14cux_getIdleBypassMotorPosition`).\n *\n * @returns Position as a fraction from 0 (closed) to 1 (widest opening).\n * @throws {@link NotConnectedError} if the ECU is not connected.\n * @throws {@link TimeoutError} if the ECU stops responding.\n * @throws {@link ProtocolError} if the ECU echoes a command byte incorrectly.\n */\n getIdleBypassMotorPosition(): Promise<number> {\n return this.#run(async () =>\n decodeIdleBypassPosition(\n await this.#byte(MemoryOffset.IdleBypassPosition),\n ),\n );\n }\n\n /**\n * Reads the injector pulse width (`c14cux_getInjectorPulseWidth`).\n *\n * The ECU uses one location for both banks, and which bank it describes at\n * the moment of the read is nondeterministic. It is most useful in open-loop\n * mode, where both banks match.\n *\n * @returns Pulse width in microseconds.\n * @throws {@link NotConnectedError} if the ECU is not connected.\n * @throws {@link TimeoutError} if the ECU stops responding.\n * @throws {@link ProtocolError} if the ECU echoes a command byte incorrectly.\n */\n getInjectorPulseWidth(): Promise<number> {\n return this.#run(() => this.#word(MemoryOffset.InjectorPulseWidth));\n }\n\n // ---- fuel trims ---------------------------------------------------------\n\n /**\n * Reads the short-term lambda fueling trim (`c14cux_getLambdaTrimShort`). A larger number means more fuel.\n *\n * @param bank - Which engine bank to read.\n * @returns Trim in counts, from -256 to 255.\n * @throws {@link NotConnectedError} if the ECU is not connected.\n * @throws {@link TimeoutError} if the ECU stops responding.\n * @throws {@link ProtocolError} if the ECU echoes a command byte incorrectly.\n */\n getLambdaTrimShort(bank: Bank): Promise<number> {\n return this.#run(async () =>\n decodeLambdaTrim(\n await this.#word(\n bank === Bank.Odd\n ? MemoryOffset.ShortTermLambdaFuelingTrimOdd\n : MemoryOffset.ShortTermLambdaFuelingTrimEven,\n ),\n ),\n );\n }\n\n /**\n * Reads the long-term lambda fueling trim (`c14cux_getLambdaTrimLong`). A larger number means more fuel.\n *\n * @param bank - Which engine bank to read.\n * @returns Trim in counts, from -256 to 255.\n * @throws {@link NotConnectedError} if the ECU is not connected.\n * @throws {@link TimeoutError} if the ECU stops responding.\n * @throws {@link ProtocolError} if the ECU echoes a command byte incorrectly.\n */\n getLambdaTrimLong(bank: Bank): Promise<number> {\n return this.#run(async () =>\n decodeLambdaTrim(\n await this.#word(\n bank === Bank.Odd\n ? MemoryOffset.LongTermLambdaFuelingTrimOdd\n : MemoryOffset.LongTermLambdaFuelingTrimEven,\n ),\n ),\n );\n }\n\n /**\n * Reads the MAF CO trim voltage (`c14cux_getCOTrimVoltage`).\n *\n * @returns Voltage in volts.\n * @throws {@link NotConnectedError} if the ECU is not connected.\n * @throws {@link TimeoutError} if the ECU stops responding.\n * @throws {@link ProtocolError} if the ECU echoes a command byte incorrectly.\n */\n getCOTrimVoltage(): Promise<number> {\n return this.#run(async () =>\n decodeCoTrimVoltage(\n await this.#word(MemoryOffset.LongTermLambdaFuelingTrimEven),\n ),\n );\n }\n\n // ---- main voltage -------------------------------------------------------\n\n /**\n * Reads the voltage supplied to the ECU (`c14cux_getMainVoltage`).\n *\n * The first call also reads the ECU's ROM to find the coefficients needed to\n * reverse its ADC computation; they are cached until {@link Ecu.disconnect}.\n *\n * @returns Main relay voltage in volts.\n * @throws {@link NotConnectedError} if the ECU is not connected.\n * @throws {@link TimeoutError} if the ECU stops responding.\n * @throws {@link ProtocolError} if the ECU echoes a command byte incorrectly.\n * @throws {@link InvalidReadingError} if the ECU returns a value outside the range its firmware can produce. (coefficients of zero, or a reading that does not fit them)\n */\n getMainVoltage(): Promise<number> {\n return this.#run(async () => {\n if (\n this.#voltageFactorA === 0 ||\n this.#voltageFactorB === 0 ||\n this.#voltageFactorC === 0\n ) {\n await this.#readVoltageCoefficients();\n }\n\n return decodeMainVoltage(\n await this.#word(MemoryOffset.MainVoltage),\n this.#voltageFactorA,\n this.#voltageFactorB,\n this.#voltageFactorC,\n );\n });\n }\n\n // ---- fuel maps ----------------------------------------------------------\n\n /**\n * Reads a fuel map (`c14cux_getFuelMap`).\n *\n * The first call also detects the ROM's data layout, which determines where\n * maps 1 to 5 are stored.\n *\n * @param fuelMapId - Map to read, 0 to 5.\n * @returns The 128 bytes of map data (8 rows by 16 columns), the adjustment factor stored after it, and the row scaler.\n * @throws {@link NotConnectedError} if the ECU is not connected.\n * @throws {@link TimeoutError} if the ECU stops responding.\n * @throws {@link ProtocolError} if the ECU echoes a command byte incorrectly.\n * @throws {@link RangeError} if `fuelMapId` is not 0 to 5 (no I/O is performed).\n * @throws {@link ReadCancelledError} if {@link Ecu.cancelRead} was called before the map data was read.\n */\n getFuelMap(fuelMapId: number): Promise<FuelMap> {\n try {\n assertFuelMapId(fuelMapId);\n } catch (error) {\n return Promise.reject(error as Error);\n }\n\n return this.#run(async () => {\n const location = fuelMapLocation(\n fuelMapId,\n await this.#determineDataOffsets(),\n );\n\n const data = await this.#read(location.offset, DataSize.FuelMap);\n const adjustmentFactor = await this.#word(\n location.offset + DataSize.FuelMap,\n );\n const rowScaler = await this.#byte(location.scalerOffset);\n\n return { data, adjustmentFactor, rowScaler };\n });\n }\n\n /**\n * Reads which fuel map is in use (`c14cux_getCurrentFuelMap`).\n *\n * Selected by a tune resistor in the harness on non-NAS Land Rovers;\n * unmodified NAS vehicles are locked to map 5.\n *\n * @returns The map id, 0 to 5.\n * @throws {@link NotConnectedError} if the ECU is not connected.\n * @throws {@link TimeoutError} if the ECU stops responding.\n * @throws {@link ProtocolError} if the ECU echoes a command byte incorrectly.\n * @throws {@link InvalidReadingError} if the ECU returns a value outside the range its firmware can produce.\n */\n getCurrentFuelMap(): Promise<number> {\n return this.#run(async () =>\n decodeCurrentFuelMap(await this.#byte(MemoryOffset.CurrentFuelMapId)),\n );\n }\n\n /**\n * Reads the current fuel map row index (`c14cux_getFuelMapRowIndex`).\n *\n * @returns The row index (0 to 7) and the row weighting.\n * @throws {@link NotConnectedError} if the ECU is not connected.\n * @throws {@link TimeoutError} if the ECU stops responding.\n * @throws {@link ProtocolError} if the ECU echoes a command byte incorrectly.\n * @throws {@link InvalidReadingError} if the ECU returns a value outside the range its firmware can produce.\n */\n getFuelMapRowIndex(): Promise<FuelMapIndex> {\n return this.#run(async () =>\n decodeFuelMapRowIndex(await this.#byte(MemoryOffset.FuelMapRowIndex)),\n );\n }\n\n /**\n * Reads the current fuel map column index (`c14cux_getFuelMapColumnIndex`).\n *\n * @returns The column index (0 to 15) and the column weighting.\n * @throws {@link NotConnectedError} if the ECU is not connected.\n * @throws {@link TimeoutError} if the ECU stops responding.\n * @throws {@link ProtocolError} if the ECU echoes a command byte incorrectly.\n * @throws {@link InvalidReadingError} if the ECU returns a value outside the range its firmware can produce.\n */\n getFuelMapColumnIndex(): Promise<FuelMapIndex> {\n return this.#run(async () =>\n decodeFuelMapColumnIndex(\n await this.#byte(MemoryOffset.FuelMapColumnIndex),\n ),\n );\n }\n\n /**\n * Reads the table of RPM thresholds that divides engine speed into the sixteen fuel map columns (`c14cux_getRpmTable`).\n *\n * @returns Sixteen RPM values, indexed as in the C library (column 0 is last).\n * @throws {@link NotConnectedError} if the ECU is not connected.\n * @throws {@link TimeoutError} if the ECU stops responding.\n * @throws {@link ProtocolError} if the ECU echoes a command byte incorrectly.\n * @throws {@link InvalidReadingError} if the ECU returns a value outside the range its firmware can produce. (a zero pulse width)\n */\n getRpmTable(): Promise<number[]> {\n return this.#run(async () => {\n const table: number[] = new Array<number>(FUEL_MAP_COLUMNS).fill(0);\n\n for (let column = 0; column < FUEL_MAP_COLUMNS; column++) {\n table[rpmTableSlot(column)] = pulseWidthToRpm(\n await this.#word(rpmTableEntryAddress(column)),\n );\n }\n\n return table;\n });\n }\n\n // ---- faults and state ---------------------------------------------------\n\n /**\n * Reads the stored fault codes (`c14cux_getFaultCodes`).\n *\n * @returns A flag for each fault; `true` means the fault is set.\n * @throws {@link NotConnectedError} if the ECU is not connected.\n * @throws {@link TimeoutError} if the ECU stops responding.\n * @throws {@link ProtocolError} if the ECU echoes a command byte incorrectly.\n */\n getFaultCodes(): Promise<FaultCodes> {\n return this.#run(async () =>\n decodeFaultCodes(\n await this.#read(MemoryOffset.FaultCodes, FAULT_CODE_BLOCK_SIZE),\n ),\n );\n }\n\n /**\n * Clears the stored fault codes by writing zero to each byte of the fault block (`c14cux_clearFaultCodes`).\n *\n * Stops at the first failed write, so the codes may be partly cleared.\n *\n * @returns A promise that resolves when the operation is complete.\n * @throws {@link NotConnectedError} if the ECU is not connected.\n * @throws {@link TimeoutError} if the ECU stops responding.\n * @throws {@link ProtocolError} if the ECU echoes a command byte incorrectly.\n */\n clearFaultCodes(): Promise<void> {\n return this.#run(async () => {\n for (let i = 0; i < FAULT_CODE_BLOCK_SIZE; i++) {\n await this.#protocol.writeMem(MemoryOffset.FaultCodes + i, 0x00);\n }\n });\n }\n\n /**\n * Reads the state of the line driving the fuel pump relay (`c14cux_getFuelPumpRelayState`).\n *\n * @returns `true` when the relay is closed (pump running).\n * @throws {@link NotConnectedError} if the ECU is not connected.\n * @throws {@link TimeoutError} if the ECU stops responding.\n * @throws {@link ProtocolError} if the ECU echoes a command byte incorrectly.\n */\n getFuelPumpRelayState(): Promise<boolean> {\n return this.#run(async () =>\n decodeFuelPumpRelay(await this.#byte(MemoryOffset.Port1)),\n );\n }\n\n /**\n * Reads the malfunction indicator lamp state (`c14cux_isMILOn`). A lit MIL implies at least one fault code, but not every fault lights it.\n *\n * @returns `true` when the MIL is lit.\n * @throws {@link NotConnectedError} if the ECU is not connected.\n * @throws {@link TimeoutError} if the ECU stops responding.\n * @throws {@link ProtocolError} if the ECU echoes a command byte incorrectly.\n */\n isMILOn(): Promise<boolean> {\n return this.#run(async () =>\n decodeMilOn(await this.#byte(MemoryOffset.Port1)),\n );\n }\n\n /**\n * Reads whether the ECU is driving an idle speed (`c14cux_getIdleMode`).\n *\n * @returns `true` in idle mode.\n * @throws {@link NotConnectedError} if the ECU is not connected.\n * @throws {@link TimeoutError} if the ECU stops responding.\n * @throws {@link ProtocolError} if the ECU echoes a command byte incorrectly.\n */\n getIdleMode(): Promise<boolean> {\n return this.#run(async () =>\n decodeIdleMode(await this.#byte(MemoryOffset.IdleMode)),\n );\n }\n\n /**\n * Reads the state of the carbon canister purge valve (`c14cux_getPurgeValveState`).\n *\n * @returns Closed, toggling, or open.\n * @throws {@link NotConnectedError} if the ECU is not connected.\n * @throws {@link TimeoutError} if the ECU stops responding.\n * @throws {@link ProtocolError} if the ECU echoes a command byte incorrectly.\n */\n getPurgeValveState(): Promise<PurgeValveState> {\n return this.#run(async () =>\n decodePurgeValveState(await this.#word(MemoryOffset.PurgeValveState)),\n );\n }\n\n /**\n * Reads the heated screen state (`c14cux_getScreenHeaterState`).\n *\n * @returns `true` when the screen heater is on.\n * @throws {@link NotConnectedError} if the ECU is not connected.\n * @throws {@link TimeoutError} if the ECU stops responding.\n * @throws {@link ProtocolError} if the ECU echoes a command byte incorrectly.\n */\n getScreenHeaterState(): Promise<boolean> {\n return this.#run(async () =>\n decodeScreenHeater(await this.#byte(MemoryOffset.Bits00DD)),\n );\n }\n\n /**\n * Reads the A/C compressor load input (`c14cux_getACCompressorState`).\n *\n * @returns `true` when the compressor is on.\n * @throws {@link NotConnectedError} if the ECU is not connected.\n * @throws {@link TimeoutError} if the ECU stops responding.\n * @throws {@link ProtocolError} if the ECU echoes a command byte incorrectly.\n */\n getACCompressorState(): Promise<boolean> {\n return this.#run(async () =>\n decodeAcCompressor(await this.#byte(MemoryOffset.Bits008A)),\n );\n }\n\n /**\n * Reads the tune identification from the ROM (`c14cux_getTuneRevision`).\n *\n * @returns The decimal tune number, checksum fixer byte and ident word.\n * @throws {@link NotConnectedError} if the ECU is not connected.\n * @throws {@link TimeoutError} if the ECU stops responding.\n * @throws {@link ProtocolError} if the ECU echoes a command byte incorrectly.\n */\n getTuneRevision(): Promise<TuneRevision> {\n return this.#run(async () =>\n decodeTuneRevision(await this.#read(MemoryOffset.TuneRevision, 5)),\n );\n }\n\n // ---- actuators ----------------------------------------------------------\n\n /**\n * Closes the fuel pump relay for a single timeout period of about two seconds (`c14cux_runFuelPump`).\n *\n * Affects a running engine; use with care.\n *\n * @returns A promise that resolves when the operation is complete.\n * @throws {@link NotConnectedError} if the ECU is not connected.\n * @throws {@link TimeoutError} if the ECU stops responding.\n * @throws {@link ProtocolError} if the ECU echoes a command byte incorrectly.\n */\n runFuelPump(): Promise<void> {\n return this.#run(async () => {\n const port1 = await this.#byte(MemoryOffset.Port1);\n\n await this.#protocol.writeMem(MemoryOffset.FuelPumpTimer, 0xff);\n await this.#protocol.writeMem(MemoryOffset.Port1, port1 & 0xbf);\n });\n }\n\n /**\n * Commands the idle air control motor to move (`c14cux_driveIdleAirControlMotor`).\n *\n * Unlike the C function this reports failure if any step fails. Affects a\n * running engine; use with care.\n *\n * @param direction - 0 opens the valve; any other value closes it.\n * @param steps - Number of steps to travel, 0 to 255.\n * @returns A promise that resolves when the operation is complete.\n * @throws {@link NotConnectedError} if the ECU is not connected.\n * @throws {@link TimeoutError} if the ECU stops responding.\n * @throws {@link ProtocolError} if the ECU echoes a command byte incorrectly.\n * @throws {@link RangeError} if `steps` is not an integer from 0 to 255 (no I/O is performed).\n */\n driveIdleAirControlMotor(direction: number, steps: number): Promise<void> {\n // Checked up front: the direction bit is written before the step count.\n if (!Number.isInteger(steps) || steps < 0 || steps > 0xff) {\n return Promise.reject(new RangeError(`Invalid step count: ${steps}`));\n }\n\n return this.#run(async () => {\n const bits = await this.#byte(MemoryOffset.Bits008A);\n\n await this.#protocol.writeMem(\n MemoryOffset.Bits008A,\n direction === 0 ? bits & 0xfe : bits | 0x01,\n );\n await this.#protocol.writeMem(\n MemoryOffset.IdleAirControlStepCount,\n steps,\n );\n });\n }\n\n // ---- internals ----------------------------------------------------------\n\n #run<T>(task: () => Promise<T>): Promise<T> {\n const ticket = ++this.#requested;\n\n return this.#queue.run(() => {\n if (!this.#connected) {\n return Promise.reject(new NotConnectedError('Not connected to ECU'));\n }\n\n this.#running = ticket;\n\n return task();\n });\n }\n\n #read(addr: number, length: number): Promise<Uint8Array> {\n return this.#protocol.readMem(\n addr,\n length,\n () => this.#running <= this.#cancelledThrough,\n );\n }\n\n async #byte(addr: number): Promise<number> {\n return (await this.#read(addr, 1))[0] ?? 0;\n }\n\n async #word(addr: number): Promise<number> {\n return be16(await this.#read(addr, 2));\n }\n\n /**\n * Works out the ROM's data layout (`c14cux_determineDataOffsets`) and caches\n * it. Does nothing if already known.\n *\n * @returns The ROM's data layout.\n */\n async #determineDataOffsets(): Promise<KnownDataOffsetRev> {\n if (this.#promRev !== undefined) {\n return this.#promRev;\n }\n\n const firstRow = await this.#read(\n MemoryOffset.OldFuelMap1,\n FUEL_MAP_COLUMNS,\n );\n\n // Very old units (about 1990 and earlier) hardcode some main-voltage\n // factors, so check for that as well.\n this.#promRev = isRevC(firstRow)\n ? DataOffsetRev.RevC\n : classifyOldRevision(\n await this.#byte(MemoryOffset.RevBMainVoltageFactorA),\n );\n\n return this.#promRev;\n }\n\n async #readVoltageCoefficients(): Promise<void> {\n const rev = await this.#determineDataOffsets();\n\n let a: number;\n let b: number;\n let c: number;\n\n if (rev === DataOffsetRev.RevA) {\n ({ A: a, B: b, C: c } = RevAMainVoltageFactor);\n } else {\n const revC = rev === DataOffsetRev.RevC;\n\n a = await this.#byte(\n revC\n ? MemoryOffset.RevCMainVoltageFactorA\n : MemoryOffset.RevBMainVoltageFactorA,\n );\n b = await this.#byte(\n revC\n ? MemoryOffset.RevCMainVoltageFactorB\n : MemoryOffset.RevBMainVoltageFactorB,\n );\n c = await this.#word(\n revC\n ? MemoryOffset.RevCMainVoltageFactorC\n : MemoryOffset.RevBMainVoltageFactorC,\n );\n }\n\n if (a === 0 || b === 0 || c === 0) {\n throw new InvalidReadingError('ECU reports zero main-voltage factors');\n }\n\n this.#voltageFactorA = a;\n this.#voltageFactorB = b;\n this.#voltageFactorC = c;\n }\n}\n"]}
package/dist/index.d.ts CHANGED
@@ -3,6 +3,7 @@ export * from './constants.js';
3
3
  export * from './errors.js';
4
4
  export type { Transport } from './transport/types.js';
5
5
  export { SimulatedTransport } from './transport/simulated.js';
6
+ export type { MemoryWriteFault } from './transport/simulated.js';
6
7
  export { WebSerialTransport, type WebSerialTransportOptions, } from './transport/webSerial.js';
7
8
  export { FAULT_CODE_NUMBERS } from './decoders/faultCodes.js';
8
9
  export type { FaultCodes, FaultCodeName } from './decoders/faultCodes.js';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,GAAG,EAAE,KAAK,UAAU,EAAE,KAAK,OAAO,EAAE,MAAM,UAAU,CAAC;AAC9D,cAAc,gBAAgB,CAAC;AAC/B,cAAc,aAAa,CAAC;AAC5B,YAAY,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAC;AACtD,OAAO,EAAE,kBAAkB,EAAE,MAAM,0BAA0B,CAAC;AAC9D,OAAO,EACL,kBAAkB,EAClB,KAAK,yBAAyB,GAC/B,MAAM,0BAA0B,CAAC;AAClC,OAAO,EAAE,kBAAkB,EAAE,MAAM,0BAA0B,CAAC;AAC9D,YAAY,EAAE,UAAU,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAC;AAC1E,YAAY,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAC;AAC1D,YAAY,EAAE,YAAY,EAAE,MAAM,4BAA4B,CAAC;AAC/D,YAAY,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,GAAG,EAAE,KAAK,UAAU,EAAE,KAAK,OAAO,EAAE,MAAM,UAAU,CAAC;AAC9D,cAAc,gBAAgB,CAAC;AAC/B,cAAc,aAAa,CAAC;AAC5B,YAAY,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAC;AACtD,OAAO,EAAE,kBAAkB,EAAE,MAAM,0BAA0B,CAAC;AAC9D,YAAY,EAAE,gBAAgB,EAAE,MAAM,0BAA0B,CAAC;AACjE,OAAO,EACL,kBAAkB,EAClB,KAAK,yBAAyB,GAC/B,MAAM,0BAA0B,CAAC;AAClC,OAAO,EAAE,kBAAkB,EAAE,MAAM,0BAA0B,CAAC;AAC9D,YAAY,EAAE,UAAU,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAC;AAC1E,YAAY,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAC;AAC1D,YAAY,EAAE,YAAY,EAAE,MAAM,4BAA4B,CAAC;AAC/D,YAAY,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC"}
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,wCAAwC;AACxC,+CAA+C;AAE/C,OAAO,EAAE,GAAG,EAAiC,MAAM,UAAU,CAAC;AAC9D,cAAc,gBAAgB,CAAC;AAC/B,cAAc,aAAa,CAAC;AAE5B,OAAO,EAAE,kBAAkB,EAAE,MAAM,0BAA0B,CAAC;AAC9D,OAAO,EACL,kBAAkB,GAEnB,MAAM,0BAA0B,CAAC;AAClC,OAAO,EAAE,kBAAkB,EAAE,MAAM,0BAA0B,CAAC","sourcesContent":["// SPDX-License-Identifier: GPL-3.0-only\n// Copyright (C) 2026 comm14cux-ts contributors\n\nexport { Ecu, type EcuOptions, type FuelMap } from './ecu.js';\nexport * from './constants.js';\nexport * from './errors.js';\nexport type { Transport } from './transport/types.js';\nexport { SimulatedTransport } from './transport/simulated.js';\nexport {\n WebSerialTransport,\n type WebSerialTransportOptions,\n} from './transport/webSerial.js';\nexport { FAULT_CODE_NUMBERS } from './decoders/faultCodes.js';\nexport type { FaultCodes, FaultCodeName } from './decoders/faultCodes.js';\nexport type { FuelMapIndex } from './decoders/fuelMap.js';\nexport type { TuneRevision } from './decoders/tuneRevision.js';\nexport type { Version } from './version.js';\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,wCAAwC;AACxC,+CAA+C;AAE/C,OAAO,EAAE,GAAG,EAAiC,MAAM,UAAU,CAAC;AAC9D,cAAc,gBAAgB,CAAC;AAC/B,cAAc,aAAa,CAAC;AAE5B,OAAO,EAAE,kBAAkB,EAAE,MAAM,0BAA0B,CAAC;AAE9D,OAAO,EACL,kBAAkB,GAEnB,MAAM,0BAA0B,CAAC;AAClC,OAAO,EAAE,kBAAkB,EAAE,MAAM,0BAA0B,CAAC","sourcesContent":["// SPDX-License-Identifier: GPL-3.0-only\n// Copyright (C) 2026 comm14cux-ts contributors\n\nexport { Ecu, type EcuOptions, type FuelMap } from './ecu.js';\nexport * from './constants.js';\nexport * from './errors.js';\nexport type { Transport } from './transport/types.js';\nexport { SimulatedTransport } from './transport/simulated.js';\nexport type { MemoryWriteFault } from './transport/simulated.js';\nexport {\n WebSerialTransport,\n type WebSerialTransportOptions,\n} from './transport/webSerial.js';\nexport { FAULT_CODE_NUMBERS } from './decoders/faultCodes.js';\nexport type { FaultCodes, FaultCodeName } from './decoders/faultCodes.js';\nexport type { FuelMapIndex } from './decoders/fuelMap.js';\nexport type { TuneRevision } from './decoders/tuneRevision.js';\nexport type { Version } from './version.js';\n"]}
@@ -14,8 +14,10 @@ export declare class Protocol {
14
14
  *
15
15
  * @param transport - The link to the ECU.
16
16
  * @param timeoutMs - Silence timeout for each read, in milliseconds.
17
+ * @param commandResetMs - Quiet time to wait for after a failed command,
18
+ * in milliseconds.
17
19
  */
18
- constructor(transport: Transport, timeoutMs?: number);
20
+ constructor(transport: Transport, timeoutMs?: number, commandResetMs?: number);
19
21
  /** Forgets the last coarse address, forcing the next read to set it. */
20
22
  resetCache(): void;
21
23
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"protocol.d.ts","sourceRoot":"","sources":["../../src/protocol/protocol.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAC;AAQvD;;;;;;;GAOG;AACH,qBAAa,QAAQ;;IAMnB;;;;;OAKG;gBACS,SAAS,EAAE,SAAS,EAAE,SAAS,SAA0B;IAKrE,wEAAwE;IACxE,UAAU,IAAI,IAAI;IAKlB;;;;;;;;;;;;;OAaG;IACG,OAAO,CACX,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,MAAM,EACd,WAAW,EAAE,MAAM,OAAO,GACzB,OAAO,CAAC,UAAU,CAAC;IAkDtB;;;;;;;;OAQG;IACG,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAa1D;;;;;;;;OAQG;IACG,aAAa,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;CAgB/D"}
1
+ {"version":3,"file":"protocol.d.ts","sourceRoot":"","sources":["../../src/protocol/protocol.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAC;AAWvD;;;;;;;GAOG;AACH,qBAAa,QAAQ;;IAQnB;;;;;;;OAOG;gBAED,SAAS,EAAE,SAAS,EACpB,SAAS,SAA0B,EACnC,cAAc,SAA2B;IAO3C,wEAAwE;IACxE,UAAU,IAAI,IAAI;IAKlB;;;;;;;;;;;;;OAaG;IACG,OAAO,CACX,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,MAAM,EACd,WAAW,EAAE,MAAM,OAAO,GACzB,OAAO,CAAC,UAAU,CAAC;IAsDtB;;;;;;;;OAQG;IACG,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAoB1D;;;;;;;;OAQG;IACG,aAAa,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;CA8C/D"}
@@ -2,13 +2,16 @@
2
2
  // Derived from libcomm14cux (https://github.com/colinbourassa/libcomm14cux)
3
3
  // Copyright (C) Colin Bourassa. Licensed under the GNU GPL v3.
4
4
  // Ported to TypeScript and modified for comm14cux-ts, 2026.
5
- import { DEFAULT_READ_TIMEOUT_MS } from '../constants.js';
6
- import { ProtocolError, ReadCancelledError } from '../errors.js';
5
+ import { DEFAULT_COMMAND_RESET_MS, DEFAULT_READ_TIMEOUT_MS, } from '../constants.js';
6
+ import { ProtocolError, ReadCancelledError, TimeoutError } from '../errors.js';
7
7
  import { nextRead } from './readCount.js';
8
8
  const ADDRESS_SPACE = 0x10000;
9
9
  const COARSE_WINDOW = 64;
10
10
  // Writes use the coarse-address command with a length code of 0.
11
11
  const WRITE_LENGTH_CODE = 0;
12
+ // The longest the ECU can keep talking after a failed command: a whole
13
+ // 512-byte read reply, plus a few echoes.
14
+ const MAX_LATE_BYTES = 0x200 + 4;
12
15
  /**
13
16
  * Memory read and write commands over a `Transport`
14
17
  * (`protocol.c`: `c14cux_readMem`, `c14cux_writeMem`).
@@ -20,6 +23,8 @@ const WRITE_LENGTH_CODE = 0;
20
23
  export class Protocol {
21
24
  #transport;
22
25
  #timeoutMs;
26
+ #commandResetMs;
27
+ #resyncNeeded = false;
23
28
  #lastReadCoarseAddress = 0;
24
29
  #lastReadQuantity = 0;
25
30
  /**
@@ -27,10 +32,13 @@ export class Protocol {
27
32
  *
28
33
  * @param transport - The link to the ECU.
29
34
  * @param timeoutMs - Silence timeout for each read, in milliseconds.
35
+ * @param commandResetMs - Quiet time to wait for after a failed command,
36
+ * in milliseconds.
30
37
  */
31
- constructor(transport, timeoutMs = DEFAULT_READ_TIMEOUT_MS) {
38
+ constructor(transport, timeoutMs = DEFAULT_READ_TIMEOUT_MS, commandResetMs = DEFAULT_COMMAND_RESET_MS) {
32
39
  this.#transport = transport;
33
40
  this.#timeoutMs = timeoutMs;
41
+ this.#commandResetMs = commandResetMs;
34
42
  }
35
43
  /** Forgets the last coarse address, forcing the next read to set it. */
36
44
  resetCache() {
@@ -61,6 +69,7 @@ export class Protocol {
61
69
  const multiChunk = nextRead(length, 0).count < length;
62
70
  let totalRead = 0;
63
71
  try {
72
+ await this.#resync();
64
73
  while (totalRead < length) {
65
74
  if (multiChunk && isCancelled()) {
66
75
  throw new ReadCancelledError('Read cancelled');
@@ -85,6 +94,8 @@ export class Protocol {
85
94
  }
86
95
  catch (error) {
87
96
  this.resetCache();
97
+ // Cancellation happens between chunks, when the ECU is idle.
98
+ this.#resyncNeeded ||= !(error instanceof ReadCancelledError);
88
99
  throw error;
89
100
  }
90
101
  return result;
@@ -104,9 +115,16 @@ export class Protocol {
104
115
  throw new RangeError(`Invalid value: ${value}`);
105
116
  }
106
117
  this.resetCache();
107
- await this.setCoarseAddr(addr, WRITE_LENGTH_CODE);
108
- await this.#sendEchoed(0x80 | (addr & 0x3f));
109
- await this.#sendEchoed(value);
118
+ try {
119
+ await this.#resync();
120
+ await this.setCoarseAddr(addr, WRITE_LENGTH_CODE);
121
+ await this.#sendEchoed(0x80 | (addr & 0x3f));
122
+ await this.#sendEchoed(value);
123
+ }
124
+ catch (error) {
125
+ this.#resyncNeeded = true;
126
+ throw error;
127
+ }
110
128
  }
111
129
  /**
112
130
  * Sets the coarse address and length code for the next command
@@ -121,6 +139,32 @@ export class Protocol {
121
139
  await this.#sendEchoed(((code << 2) | (addr >> 14)) & 0xff);
122
140
  await this.#sendEchoed((addr >> 6) & 0xff);
123
141
  }
142
+ /**
143
+ * After a failed command the ECU may still be waiting for the rest of it,
144
+ * and would take the next byte sent as that rest: for a write, as the value
145
+ * to store. Waits until the line has been quiet long enough for the ECU to
146
+ * drop the half-received command, discarding any late bytes.
147
+ *
148
+ * @throws {@link ProtocolError} if the ECU never goes quiet.
149
+ */
150
+ async #resync() {
151
+ if (!this.#resyncNeeded) {
152
+ return;
153
+ }
154
+ for (let i = 0; i <= MAX_LATE_BYTES; i++) {
155
+ try {
156
+ await this.#transport.read(1, this.#commandResetMs);
157
+ }
158
+ catch (error) {
159
+ if (!(error instanceof TimeoutError)) {
160
+ throw error;
161
+ }
162
+ this.#resyncNeeded = false;
163
+ return;
164
+ }
165
+ }
166
+ throw new ProtocolError('ECU did not go quiet after a failed command');
167
+ }
124
168
  async #sendEchoed(byte) {
125
169
  await this.#transport.write(Uint8Array.of(byte));
126
170
  const [echo] = await this.#transport.read(1, this.#timeoutMs);
@@ -1 +1 @@
1
- {"version":3,"file":"protocol.js","sourceRoot":"","sources":["../../src/protocol/protocol.ts"],"names":[],"mappings":"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","sourcesContent":["// SPDX-License-Identifier: GPL-3.0-only\n// Derived from libcomm14cux (https://github.com/colinbourassa/libcomm14cux)\n// Copyright (C) Colin Bourassa. Licensed under the GNU GPL v3.\n// Ported to TypeScript and modified for comm14cux-ts, 2026.\nimport { DEFAULT_READ_TIMEOUT_MS } from '../constants.js';\nimport { ProtocolError, ReadCancelledError } from '../errors.js';\nimport type { Transport } from '../transport/types.js';\nimport { nextRead } from './readCount.js';\n\nconst ADDRESS_SPACE = 0x10000;\nconst COARSE_WINDOW = 64;\n// Writes use the coarse-address command with a length code of 0.\nconst WRITE_LENGTH_CODE = 0;\n\n/**\n * Memory read and write commands over a `Transport`\n * (`protocol.c`: `c14cux_readMem`, `c14cux_writeMem`).\n *\n * This class is not re-entrant: callers must serialise operations (the\n * `Ecu` class does so with a `CommandQueue`). Cancellation is decided by the\n * caller, through the `isCancelled` callback given to `readMem`.\n */\nexport class Protocol {\n readonly #transport: Transport;\n readonly #timeoutMs: number;\n #lastReadCoarseAddress = 0;\n #lastReadQuantity = 0;\n\n /**\n * Creates a protocol handler.\n *\n * @param transport - The link to the ECU.\n * @param timeoutMs - Silence timeout for each read, in milliseconds.\n */\n constructor(transport: Transport, timeoutMs = DEFAULT_READ_TIMEOUT_MS) {\n this.#transport = transport;\n this.#timeoutMs = timeoutMs;\n }\n\n /** Forgets the last coarse address, forcing the next read to set it. */\n resetCache(): void {\n this.#lastReadCoarseAddress = 0;\n this.#lastReadQuantity = 0;\n }\n\n /**\n * Reads `length` bytes starting at `addr`, splitting long reads into chunks\n * (`c14cux_readMem`).\n *\n * @param addr - First address to read, 0 to 0xFFFF.\n * @param length - Number of bytes to read. `addr + length` must not exceed 0x10000.\n * @param isCancelled - Checked before each chunk of a multi-chunk read; a\n * read that fits in one chunk is never cancelled.\n * @returns The bytes read.\n * @throws {@link RangeError} if the address or length is out of range.\n * @throws {@link ReadCancelledError} if `isCancelled` returns `true`.\n * @throws {@link ProtocolError} if an echo is wrong.\n * @throws {@link TimeoutError} if the ECU stops responding.\n */\n async readMem(\n addr: number,\n length: number,\n isCancelled: () => boolean,\n ): Promise<Uint8Array> {\n assertUint16(addr, 'address');\n assertUint16(length, 'length');\n\n if (addr + length > ADDRESS_SPACE) {\n throw new RangeError('Read extends past the end of the address space');\n }\n\n const result = new Uint8Array(length);\n const multiChunk = nextRead(length, 0).count < length;\n let totalRead = 0;\n\n try {\n while (totalRead < length) {\n if (multiChunk && isCancelled()) {\n throw new ReadCancelledError('Read cancelled');\n }\n\n const { count: quantity, code } = nextRead(length, totalRead);\n const chunkAddr = addr + totalRead;\n\n // The ECU latches only the 64-byte-aligned block (addr >> 6), so the\n // final command byte alone is enough only within that same block.\n const lastByteOnly =\n quantity === this.#lastReadQuantity &&\n Math.trunc(chunkAddr / COARSE_WINDOW) ===\n Math.trunc(this.#lastReadCoarseAddress / COARSE_WINDOW);\n\n if (!lastByteOnly) {\n await this.setCoarseAddr(chunkAddr, code);\n this.#lastReadCoarseAddress = chunkAddr;\n }\n\n // The ECU does not echo the read command; it starts sending data.\n await this.#transport.write(Uint8Array.of(0xc0 | (chunkAddr & 0x3f)));\n result.set(\n await this.#transport.read(quantity, this.#timeoutMs),\n totalRead,\n );\n totalRead += quantity;\n this.#lastReadQuantity = quantity;\n }\n } catch (error) {\n this.resetCache();\n throw error;\n }\n\n return result;\n }\n\n /**\n * Writes one byte to ECU memory (`c14cux_writeMem`).\n *\n * @param addr - Address to write, 0 to 0xFFFF.\n * @param value - Byte to write, 0 to 255.\n * @throws {@link RangeError} if the address or value is out of range.\n * @throws {@link ProtocolError} if an echo is wrong.\n * @throws {@link TimeoutError} if the ECU stops responding.\n */\n async writeMem(addr: number, value: number): Promise<void> {\n assertUint16(addr, 'address');\n\n if (!Number.isInteger(value) || value < 0 || value > 0xff) {\n throw new RangeError(`Invalid value: ${value}`);\n }\n\n this.resetCache();\n await this.setCoarseAddr(addr, WRITE_LENGTH_CODE);\n await this.#sendEchoed(0x80 | (addr & 0x3f));\n await this.#sendEchoed(value);\n }\n\n /**\n * Sets the coarse address and length code for the next command\n * (`c14cux_setCoarseAddr`).\n *\n * @param addr - Address of the read or write, 0 to 0xFFFF.\n * @param code - Length code of the following read, or 0 for a write.\n * @throws {@link ProtocolError} if an echo is wrong.\n * @throws {@link TimeoutError} if the ECU stops responding.\n */\n async setCoarseAddr(addr: number, code: number): Promise<void> {\n await this.#sendEchoed(((code << 2) | (addr >> 14)) & 0xff);\n await this.#sendEchoed((addr >> 6) & 0xff);\n }\n\n async #sendEchoed(byte: number): Promise<void> {\n await this.#transport.write(Uint8Array.of(byte));\n\n const [echo] = await this.#transport.read(1, this.#timeoutMs);\n\n if (echo !== byte) {\n throw new ProtocolError(\n `Echo mismatch: sent 0x${byte.toString(16)}, received 0x${(echo ?? 0).toString(16)}`,\n );\n }\n }\n}\n\nfunction assertUint16(value: number, name: string): void {\n if (!Number.isInteger(value) || value < 0 || value >= ADDRESS_SPACE) {\n throw new RangeError(`Invalid ${name}: ${value}`);\n }\n}\n"]}
1
+ {"version":3,"file":"protocol.js","sourceRoot":"","sources":["../../src/protocol/protocol.ts"],"names":[],"mappings":"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","sourcesContent":["// SPDX-License-Identifier: GPL-3.0-only\n// Derived from libcomm14cux (https://github.com/colinbourassa/libcomm14cux)\n// Copyright (C) Colin Bourassa. Licensed under the GNU GPL v3.\n// Ported to TypeScript and modified for comm14cux-ts, 2026.\nimport {\n DEFAULT_COMMAND_RESET_MS,\n DEFAULT_READ_TIMEOUT_MS,\n} from '../constants.js';\nimport { ProtocolError, ReadCancelledError, TimeoutError } from '../errors.js';\nimport type { Transport } from '../transport/types.js';\nimport { nextRead } from './readCount.js';\n\nconst ADDRESS_SPACE = 0x10000;\nconst COARSE_WINDOW = 64;\n// Writes use the coarse-address command with a length code of 0.\nconst WRITE_LENGTH_CODE = 0;\n// The longest the ECU can keep talking after a failed command: a whole\n// 512-byte read reply, plus a few echoes.\nconst MAX_LATE_BYTES = 0x200 + 4;\n\n/**\n * Memory read and write commands over a `Transport`\n * (`protocol.c`: `c14cux_readMem`, `c14cux_writeMem`).\n *\n * This class is not re-entrant: callers must serialise operations (the\n * `Ecu` class does so with a `CommandQueue`). Cancellation is decided by the\n * caller, through the `isCancelled` callback given to `readMem`.\n */\nexport class Protocol {\n readonly #transport: Transport;\n readonly #timeoutMs: number;\n readonly #commandResetMs: number;\n #resyncNeeded = false;\n #lastReadCoarseAddress = 0;\n #lastReadQuantity = 0;\n\n /**\n * Creates a protocol handler.\n *\n * @param transport - The link to the ECU.\n * @param timeoutMs - Silence timeout for each read, in milliseconds.\n * @param commandResetMs - Quiet time to wait for after a failed command,\n * in milliseconds.\n */\n constructor(\n transport: Transport,\n timeoutMs = DEFAULT_READ_TIMEOUT_MS,\n commandResetMs = DEFAULT_COMMAND_RESET_MS,\n ) {\n this.#transport = transport;\n this.#timeoutMs = timeoutMs;\n this.#commandResetMs = commandResetMs;\n }\n\n /** Forgets the last coarse address, forcing the next read to set it. */\n resetCache(): void {\n this.#lastReadCoarseAddress = 0;\n this.#lastReadQuantity = 0;\n }\n\n /**\n * Reads `length` bytes starting at `addr`, splitting long reads into chunks\n * (`c14cux_readMem`).\n *\n * @param addr - First address to read, 0 to 0xFFFF.\n * @param length - Number of bytes to read. `addr + length` must not exceed 0x10000.\n * @param isCancelled - Checked before each chunk of a multi-chunk read; a\n * read that fits in one chunk is never cancelled.\n * @returns The bytes read.\n * @throws {@link RangeError} if the address or length is out of range.\n * @throws {@link ReadCancelledError} if `isCancelled` returns `true`.\n * @throws {@link ProtocolError} if an echo is wrong.\n * @throws {@link TimeoutError} if the ECU stops responding.\n */\n async readMem(\n addr: number,\n length: number,\n isCancelled: () => boolean,\n ): Promise<Uint8Array> {\n assertUint16(addr, 'address');\n assertUint16(length, 'length');\n\n if (addr + length > ADDRESS_SPACE) {\n throw new RangeError('Read extends past the end of the address space');\n }\n\n const result = new Uint8Array(length);\n const multiChunk = nextRead(length, 0).count < length;\n let totalRead = 0;\n\n try {\n await this.#resync();\n\n while (totalRead < length) {\n if (multiChunk && isCancelled()) {\n throw new ReadCancelledError('Read cancelled');\n }\n\n const { count: quantity, code } = nextRead(length, totalRead);\n const chunkAddr = addr + totalRead;\n\n // The ECU latches only the 64-byte-aligned block (addr >> 6), so the\n // final command byte alone is enough only within that same block.\n const lastByteOnly =\n quantity === this.#lastReadQuantity &&\n Math.trunc(chunkAddr / COARSE_WINDOW) ===\n Math.trunc(this.#lastReadCoarseAddress / COARSE_WINDOW);\n\n if (!lastByteOnly) {\n await this.setCoarseAddr(chunkAddr, code);\n this.#lastReadCoarseAddress = chunkAddr;\n }\n\n // The ECU does not echo the read command; it starts sending data.\n await this.#transport.write(Uint8Array.of(0xc0 | (chunkAddr & 0x3f)));\n result.set(\n await this.#transport.read(quantity, this.#timeoutMs),\n totalRead,\n );\n totalRead += quantity;\n this.#lastReadQuantity = quantity;\n }\n } catch (error) {\n this.resetCache();\n // Cancellation happens between chunks, when the ECU is idle.\n this.#resyncNeeded ||= !(error instanceof ReadCancelledError);\n throw error;\n }\n\n return result;\n }\n\n /**\n * Writes one byte to ECU memory (`c14cux_writeMem`).\n *\n * @param addr - Address to write, 0 to 0xFFFF.\n * @param value - Byte to write, 0 to 255.\n * @throws {@link RangeError} if the address or value is out of range.\n * @throws {@link ProtocolError} if an echo is wrong.\n * @throws {@link TimeoutError} if the ECU stops responding.\n */\n async writeMem(addr: number, value: number): Promise<void> {\n assertUint16(addr, 'address');\n\n if (!Number.isInteger(value) || value < 0 || value > 0xff) {\n throw new RangeError(`Invalid value: ${value}`);\n }\n\n this.resetCache();\n\n try {\n await this.#resync();\n await this.setCoarseAddr(addr, WRITE_LENGTH_CODE);\n await this.#sendEchoed(0x80 | (addr & 0x3f));\n await this.#sendEchoed(value);\n } catch (error) {\n this.#resyncNeeded = true;\n throw error;\n }\n }\n\n /**\n * Sets the coarse address and length code for the next command\n * (`c14cux_setCoarseAddr`).\n *\n * @param addr - Address of the read or write, 0 to 0xFFFF.\n * @param code - Length code of the following read, or 0 for a write.\n * @throws {@link ProtocolError} if an echo is wrong.\n * @throws {@link TimeoutError} if the ECU stops responding.\n */\n async setCoarseAddr(addr: number, code: number): Promise<void> {\n await this.#sendEchoed(((code << 2) | (addr >> 14)) & 0xff);\n await this.#sendEchoed((addr >> 6) & 0xff);\n }\n\n /**\n * After a failed command the ECU may still be waiting for the rest of it,\n * and would take the next byte sent as that rest: for a write, as the value\n * to store. Waits until the line has been quiet long enough for the ECU to\n * drop the half-received command, discarding any late bytes.\n *\n * @throws {@link ProtocolError} if the ECU never goes quiet.\n */\n async #resync(): Promise<void> {\n if (!this.#resyncNeeded) {\n return;\n }\n\n for (let i = 0; i <= MAX_LATE_BYTES; i++) {\n try {\n await this.#transport.read(1, this.#commandResetMs);\n } catch (error) {\n if (!(error instanceof TimeoutError)) {\n throw error;\n }\n\n this.#resyncNeeded = false;\n\n return;\n }\n }\n\n throw new ProtocolError('ECU did not go quiet after a failed command');\n }\n\n async #sendEchoed(byte: number): Promise<void> {\n await this.#transport.write(Uint8Array.of(byte));\n\n const [echo] = await this.#transport.read(1, this.#timeoutMs);\n\n if (echo !== byte) {\n throw new ProtocolError(\n `Echo mismatch: sent 0x${byte.toString(16)}, received 0x${(echo ?? 0).toString(16)}`,\n );\n }\n }\n}\n\nfunction assertUint16(value: number, name: string): void {\n if (!Number.isInteger(value) || value < 0 || value >= ADDRESS_SPACE) {\n throw new RangeError(`Invalid ${name}: ${value}`);\n }\n}\n"]}