serial-core 0.2.0-dev.1 → 0.2.0-dev.3

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.
@@ -39,6 +39,7 @@ export declare class SerialService extends EventEmitter {
39
39
  send(data: string | Buffer, options?: {
40
40
  alias?: string;
41
41
  timeout?: number;
42
+ waitResponse?: boolean;
42
43
  }): Promise<void>;
43
44
  /**
44
45
  * Low-level write logic, handled by QueueManager.
@@ -39,6 +39,7 @@ export declare class SerialService extends EventEmitter {
39
39
  send(data: string | Buffer, options?: {
40
40
  alias?: string;
41
41
  timeout?: number;
42
+ waitResponse?: boolean;
42
43
  }): Promise<void>;
43
44
  /**
44
45
  * Low-level write logic, handled by QueueManager.
@@ -7,6 +7,9 @@ export declare class QueueManager {
7
7
  private queue;
8
8
  private isProcessing;
9
9
  private _currentAlias;
10
+ private waitingForResponse;
11
+ private currentTimeoutTimer;
12
+ private currentItem;
10
13
  private writeHandler;
11
14
  constructor(writeHandler: (data: Buffer | string) => Promise<void>);
12
15
  /**
@@ -16,6 +19,7 @@ export declare class QueueManager {
16
19
  add(data: Buffer | string, options?: {
17
20
  alias?: string;
18
21
  timeout?: number;
22
+ waitResponse?: boolean;
19
23
  }): Promise<void>;
20
24
  /**
21
25
  * Getter for the alias of the command currently processing (or just processed).
@@ -26,8 +30,14 @@ export declare class QueueManager {
26
30
  * Clears the queue (useful on abrupt disconnections)
27
31
  */
28
32
  clear(): void;
33
+ /**
34
+ * Notification from service that data was received.
35
+ * If we are waiting for a response, this signals we can move on.
36
+ */
37
+ notifyResponse(): void;
29
38
  /**
30
39
  * Recursive processing loop
31
40
  */
32
41
  private process;
42
+ private handleResponseTimeout;
33
43
  }
@@ -7,6 +7,9 @@ export declare class QueueManager {
7
7
  private queue;
8
8
  private isProcessing;
9
9
  private _currentAlias;
10
+ private waitingForResponse;
11
+ private currentTimeoutTimer;
12
+ private currentItem;
10
13
  private writeHandler;
11
14
  constructor(writeHandler: (data: Buffer | string) => Promise<void>);
12
15
  /**
@@ -16,6 +19,7 @@ export declare class QueueManager {
16
19
  add(data: Buffer | string, options?: {
17
20
  alias?: string;
18
21
  timeout?: number;
22
+ waitResponse?: boolean;
19
23
  }): Promise<void>;
20
24
  /**
21
25
  * Getter for the alias of the command currently processing (or just processed).
@@ -26,8 +30,14 @@ export declare class QueueManager {
26
30
  * Clears the queue (useful on abrupt disconnections)
27
31
  */
28
32
  clear(): void;
33
+ /**
34
+ * Notification from service that data was received.
35
+ * If we are waiting for a response, this signals we can move on.
36
+ */
37
+ notifyResponse(): void;
29
38
  /**
30
39
  * Recursive processing loop
31
40
  */
32
41
  private process;
42
+ private handleResponseTimeout;
33
43
  }
package/dist/index.cjs CHANGED
@@ -1 +1 @@
1
- var e=Object.create,t=Object.defineProperty,n=Object.getOwnPropertyDescriptor,r=Object.getOwnPropertyNames,i=Object.getPrototypeOf,a=Object.prototype.hasOwnProperty,o=(e,i,o,s)=>{if(i&&typeof i==`object`||typeof i==`function`)for(var c=r(i),l=0,u=c.length,d;l<u;l++)d=c[l],!a.call(e,d)&&d!==o&&t(e,d,{get:(e=>i[e]).bind(null,d),enumerable:!(s=n(i,d))||s.enumerable});return e},s=(n,r,a)=>(a=n==null?{}:e(i(n)),o(r||!n||!n.__esModule?t(a,`default`,{value:n,enumerable:!0}):a,n));const c=s(require(`serialport`)),l=s(require(`events`)),u={DISCONNECTED:`DISCONNECTED`,SCANNING:`SCANNING`,CONNECTING:`CONNECTING`,CONNECTED:`CONNECTED`,RECONNECTING:`RECONNECTING`};var d=class{queue=[];isProcessing=!1;_currentAlias=void 0;writeHandler;constructor(e){this.writeHandler=e}add(e,t){return new Promise((n,r)=>{this.queue.push({data:e,resolve:n,reject:r,alias:t?.alias,timeout:t?.timeout}),this.process()})}get currentAlias(){return this._currentAlias}clear(){this.queue.forEach(e=>e.reject(Error(`Queue cleared due to disconnection`))),this.queue=[],this.isProcessing=!1}async process(){if(this.isProcessing||this.queue.length===0)return;this.isProcessing=!0;let e=this.queue.shift();if(!e){this.isProcessing=!1;return}this._currentAlias=e.alias;let t=null,n=!1;try{e.timeout&&e.timeout>0&&(t=setTimeout(()=>{n=!0,e.reject(Error(`Command timeout after ${e.timeout}ms`)),this.isProcessing=!1,this.process()},e.timeout)),await this.writeHandler(e.data),n||(t&&clearTimeout(t),e.resolve())}catch(r){n||(t&&clearTimeout(t),e.reject(r instanceof Error?r:Error(String(r))))}finally{n||(this.isProcessing=!1,this.process())}}},f=class{static async findPort(e,t){if(!e&&!t)throw Error(`VendorID or ProductID is required for automatic scanning.`);try{let n=await c.SerialPort.list(),r=n.find(n=>{let r=n.vendorId?.toLowerCase()||``,i=n.productId?.toLowerCase()||``,a=(e||``).toLowerCase().replace(`0x`,``),o=(t||``).toLowerCase().replace(`0x`,``),s=e?r.includes(a):!0,c=t?i.includes(o):!0;return s&&c});return r?r.path:null}catch(e){return console.error(`Error scanning ports:`,e),null}}},p=class e{static instance;lockedPorts=new Set;constructor(){}static getInstance(){return e.instance||=new e,e.instance}register(e){return this.lockedPorts.has(e)?!1:(this.lockedPorts.add(e),!0)}unregister(e){this.lockedPorts.delete(e)}isLocked(e){return this.lockedPorts.has(e)}},m=class extends l.EventEmitter{port=null;queue;_status=u.DISCONNECTED;config;intentionalDisconnect=!1;reconnectTimer=null;constructor(e){super(),this.config=e,this.queue=new d(async e=>this.performWrite(e)),this.config.autoConnect&&this.connect()}get status(){return this._status}setStatus(e){this._status!==e&&(this._status=e,this.emit(`status`,e))}async connect(){if(this._status===u.CONNECTED||this._status===u.CONNECTING)return;this.intentionalDisconnect=!1,this.setStatus(u.SCANNING);let e=this.config.path;if(!e)try{let t=await f.findPort(this.config.vendorId,this.config.productId);if(t)e=t;else{this.handleConnectionFailure(`Device not found in scan`);return}}catch(e){this.handleConnectionFailure(`Error scanning: ${e}`);return}if(p.getInstance().isLocked(e)){this.handleConnectionFailure(`Port ${e} occupied by another internal instance`);return}if(!p.getInstance().register(e)){this.handleConnectionFailure(`Could not lock port ${e}`);return}this.setStatus(u.CONNECTING),this.openPort(e)}openPort(e){if(this.port=new c.SerialPort({path:e,baudRate:this.config.baudRate,autoOpen:!1}),this.port.open(t=>{if(t){this.handleConnectionFailure(t.message);return}this.config.handshake?this.performHandshake():(this.setStatus(u.CONNECTED),this.emit(`connected`,{path:e,baudRate:this.config.baudRate}))}),this.config.parser){let e=this.port.pipe(this.config.parser);e.on(`data`,e=>this.emit(`data`,e,this.queue.currentAlias)),e.on(`error`,e=>this.emit(`error`,e))}else this.port.on(`data`,e=>this.emit(`data`,e,this.queue.currentAlias));this.port.on(`error`,e=>{this.emit(`error`,e)}),this.port.on(`close`,()=>{this.cleanup(),this.emit(`disconnected`,this.intentionalDisconnect?`Manual`:`Unexpected`),this.intentionalDisconnect?this.setStatus(u.DISCONNECTED):(this.setStatus(u.RECONNECTING),this.scheduleReconnect())})}async disconnect(){if(this.intentionalDisconnect=!0,this.reconnectTimer&&clearTimeout(this.reconnectTimer),this.port&&this.port.isOpen)return new Promise(e=>{this.port?.close(()=>e())})}send(e,t){return this._status===u.CONNECTED?this.queue.add(e,t):Promise.reject(Error(`Port not connected`))}performWrite(e){return new Promise((t,n)=>{if(!this.port||!this.port.isOpen)return n(Error(`Port closed during write`));let r=!this.port.write(e,e=>{if(e)return n(e);r||t()});r&&this.port.once(`drain`,t)})}handleConnectionFailure(e){this.emit(`error`,Error(`Connection failure: ${e}`)),this.cleanup(),this.intentionalDisconnect||(this.setStatus(u.RECONNECTING),this.scheduleReconnect())}scheduleReconnect(){this.reconnectTimer&&clearTimeout(this.reconnectTimer),this.reconnectTimer=setTimeout(()=>{this.connect()},this.config.reconnectInterval)}cleanup(){this.port&&this.port.path&&p.getInstance().unregister(this.port.path),this.port&&(this.port.removeAllListeners(),this.port=null),this.config.parser&&(this.config.parser.removeAllListeners(`data`),this.config.parser.removeAllListeners(`error`)),this.intentionalDisconnect&&this.queue.clear()}performHandshake(){if(!this.port||!this.port.isOpen||!this.config.handshake)return;let{command:e,pattern:t,timeout:n}=this.config.handshake,r,i=e=>{let n=``;n=Buffer.isBuffer(e)?e.toString():typeof e==`string`?e:String(e);let r=typeof t==`string`?new RegExp(t):t;r.test(n)&&(a(),this.setStatus(u.CONNECTED),this.emit(`connected`,{path:this.port.path,baudRate:this.config.baudRate}))},a=()=>{clearTimeout(r),this.removeListener(`data`,o)},o=e=>i(e);this.on(`data`,o),r=setTimeout(()=>{a(),this.handleConnectionFailure(`Handshake timeout (pattern: ${t})`)},n),this.performWrite(e).catch(e=>{a(),this.handleConnectionFailure(`Error writing handshake: ${e.message}`)})}};function h(e){return Buffer.isBuffer(e)?e:e instanceof Uint8Array?Buffer.from(e.buffer,e.byteOffset,e.byteLength):(Array.isArray(e),Buffer.from(e))}function g(e){return e}function _(e,t=`utf8`){return e.toString(t)}function v(e){return e.toString(`ascii`)}function y(e){return[...e]}exports.PortScanner=f,exports.SerialService=m,exports.SerialStatus=u,exports.toArray=y,exports.toAscii=v,exports.toBuffer=h,exports.toString=_,exports.toUint8Array=g;
1
+ var e=Object.create,t=Object.defineProperty,n=Object.getOwnPropertyDescriptor,r=Object.getOwnPropertyNames,i=Object.getPrototypeOf,a=Object.prototype.hasOwnProperty,o=(e,i,o,s)=>{if(i&&typeof i==`object`||typeof i==`function`)for(var c=r(i),l=0,u=c.length,d;l<u;l++)d=c[l],!a.call(e,d)&&d!==o&&t(e,d,{get:(e=>i[e]).bind(null,d),enumerable:!(s=n(i,d))||s.enumerable});return e},s=(n,r,a)=>(a=n==null?{}:e(i(n)),o(r||!n||!n.__esModule?t(a,`default`,{value:n,enumerable:!0}):a,n));const c=s(require(`serialport`)),l=s(require(`events`)),u={DISCONNECTED:`DISCONNECTED`,SCANNING:`SCANNING`,CONNECTING:`CONNECTING`,CONNECTED:`CONNECTED`,RECONNECTING:`RECONNECTING`};var d=class{queue=[];isProcessing=!1;_currentAlias=void 0;waitingForResponse=!1;currentTimeoutTimer=null;currentItem=null;writeHandler;constructor(e){this.writeHandler=e}add(e,t){return new Promise((n,r)=>{this.queue.push({data:e,resolve:n,reject:r,alias:t?.alias,timeout:t?.timeout,waitResponse:t?.waitResponse}),this.process()})}get currentAlias(){return this._currentAlias}clear(){this.queue.forEach(e=>e.reject(Error(`Queue cleared due to disconnection`))),this.queue=[],this.waitingForResponse&&this.currentItem&&(this.currentTimeoutTimer&&clearTimeout(this.currentTimeoutTimer),this.currentItem.reject(Error(`Queue cleared due to disconnection`))),this.isProcessing=!1,this.waitingForResponse=!1,this.currentItem=null,this.currentTimeoutTimer=null}notifyResponse(){this.waitingForResponse&&this.currentItem&&(this.currentTimeoutTimer&&clearTimeout(this.currentTimeoutTimer),this.waitingForResponse=!1,this.currentTimeoutTimer=null,this.currentItem.resolve(),this.currentItem=null,this.isProcessing=!1,this.process())}async process(){if(this.isProcessing||this.waitingForResponse||this.queue.length===0)return;this.isProcessing=!0;let e=this.queue.shift();if(!e){this.isProcessing=!1;return}this.currentItem=e,this._currentAlias=e.alias;let t=null,n=!1;try{if(!e.waitResponse&&e.timeout&&e.timeout>0&&(t=setTimeout(()=>{n=!0,e.reject(Error(`Write timeout after ${e.timeout}ms`)),this.isProcessing=!1,this.currentItem=null,this.process()},e.timeout)),await this.writeHandler(e.data),n)return;t&&clearTimeout(t),e.waitResponse?(this.waitingForResponse=!0,e.timeout&&e.timeout>0&&(this.currentTimeoutTimer=setTimeout(()=>{this.handleResponseTimeout(e)},e.timeout)),this.isProcessing=!1):(e.resolve(),this.currentItem=null,this.isProcessing=!1,this.process())}catch(r){n||(t&&clearTimeout(t),e.reject(r instanceof Error?r:Error(String(r))),this.waitingForResponse=!1,this.currentItem=null,this.isProcessing=!1,this.process())}}handleResponseTimeout(e){this.waitingForResponse=!1,this.currentTimeoutTimer=null,this.currentItem=null,e.reject(Error(`Response timeout after ${e.timeout}ms`)),this.process()}},f=class{static async findPort(e,t){if(!e&&!t)throw Error(`VendorID or ProductID is required for automatic scanning.`);try{let n=await c.SerialPort.list(),r=n.find(n=>{let r=n.vendorId?.toLowerCase()||``,i=n.productId?.toLowerCase()||``,a=(e||``).toLowerCase().replace(`0x`,``),o=(t||``).toLowerCase().replace(`0x`,``),s=e?r.includes(a):!0,c=t?i.includes(o):!0;return s&&c});return r?r.path:null}catch(e){return console.error(`Error scanning ports:`,e),null}}},p=class e{static instance;lockedPorts=new Set;constructor(){}static getInstance(){return e.instance||=new e,e.instance}register(e){return this.lockedPorts.has(e)?!1:(this.lockedPorts.add(e),!0)}unregister(e){this.lockedPorts.delete(e)}isLocked(e){return this.lockedPorts.has(e)}},m=class extends l.EventEmitter{port=null;queue;_status=u.DISCONNECTED;config;intentionalDisconnect=!1;reconnectTimer=null;constructor(e){super(),this.config=e,this.queue=new d(async e=>this.performWrite(e)),this.config.autoConnect&&this.connect()}get status(){return this._status}setStatus(e){this._status!==e&&(this._status=e,this.emit(`status`,e))}async connect(){if(this._status===u.CONNECTED||this._status===u.CONNECTING)return;this.intentionalDisconnect=!1,this.setStatus(u.SCANNING);let e=this.config.path;if(!e)try{let t=await f.findPort(this.config.vendorId,this.config.productId);if(t)e=t;else{this.handleConnectionFailure(`Device not found in scan`);return}}catch(e){this.handleConnectionFailure(`Error scanning: ${e}`);return}if(p.getInstance().isLocked(e)){this.handleConnectionFailure(`Port ${e} occupied by another internal instance`);return}if(!p.getInstance().register(e)){this.handleConnectionFailure(`Could not lock port ${e}`);return}this.setStatus(u.CONNECTING),this.openPort(e)}openPort(e){if(this.port=new c.SerialPort({path:e,baudRate:this.config.baudRate,autoOpen:!1}),this.port.open(t=>{if(t){this.handleConnectionFailure(t.message);return}this.config.handshake?this.performHandshake():(this.setStatus(u.CONNECTED),this.emit(`connected`,{path:e,baudRate:this.config.baudRate}))}),this.config.parser){let e=this.port.pipe(this.config.parser);e.on(`data`,e=>{this.queue.notifyResponse(),this.emit(`data`,e,this.queue.currentAlias)}),e.on(`error`,e=>this.emit(`error`,e))}else this.port.on(`data`,e=>{this.queue.notifyResponse(),this.emit(`data`,e,this.queue.currentAlias)});this.port.on(`error`,e=>{this.emit(`error`,e)}),this.port.on(`close`,()=>{this.cleanup(),this.emit(`disconnected`,this.intentionalDisconnect?`Manual`:`Unexpected`),this.intentionalDisconnect?this.setStatus(u.DISCONNECTED):(this.setStatus(u.RECONNECTING),this.scheduleReconnect())})}async disconnect(){if(this.intentionalDisconnect=!0,this.reconnectTimer&&clearTimeout(this.reconnectTimer),this.port&&this.port.isOpen)return new Promise(e=>{this.port?.close(()=>e())})}send(e,t){return this._status===u.CONNECTED?this.queue.add(e,t):Promise.reject(Error(`Port not connected`))}performWrite(e){return new Promise((t,n)=>{if(!this.port||!this.port.isOpen)return n(Error(`Port closed during write`));let r=!this.port.write(e,e=>{if(e)return n(e);r||t()});r&&this.port.once(`drain`,t)})}handleConnectionFailure(e){this.emit(`error`,Error(`Connection failure: ${e}`)),this.cleanup(),this.intentionalDisconnect||(this.setStatus(u.RECONNECTING),this.scheduleReconnect())}scheduleReconnect(){this.reconnectTimer&&clearTimeout(this.reconnectTimer),this.reconnectTimer=setTimeout(()=>{this.connect()},this.config.reconnectInterval)}cleanup(){this.port&&this.port.path&&p.getInstance().unregister(this.port.path),this.port&&(this.port.removeAllListeners(),this.port=null),this.config.parser&&(this.config.parser.removeAllListeners(`data`),this.config.parser.removeAllListeners(`error`)),this.intentionalDisconnect&&this.queue.clear()}performHandshake(){if(!this.port||!this.port.isOpen||!this.config.handshake)return;let{command:e,pattern:t,timeout:n}=this.config.handshake,r,i=e=>{let n=``;n=Buffer.isBuffer(e)?e.toString():typeof e==`string`?e:String(e);let r=typeof t==`string`?new RegExp(t):t;r.test(n)&&(a(),this.setStatus(u.CONNECTED),this.emit(`connected`,{path:this.port.path,baudRate:this.config.baudRate}))},a=()=>{clearTimeout(r),this.removeListener(`data`,o)},o=e=>i(e);this.on(`data`,o),r=setTimeout(()=>{a(),this.handleConnectionFailure(`Handshake timeout (pattern: ${t})`)},n),this.performWrite(e).catch(e=>{a(),this.handleConnectionFailure(`Error writing handshake: ${e.message}`)})}};function h(e){return Buffer.isBuffer(e)?e:e instanceof Uint8Array?Buffer.from(e.buffer,e.byteOffset,e.byteLength):(Array.isArray(e),Buffer.from(e))}function g(e){return e}function _(e,t=`utf8`){return e.toString(t)}function v(e){return e.toString(`ascii`)}function y(e){return[...e]}exports.PortScanner=f,exports.SerialService=m,exports.SerialStatus=u,exports.toArray=y,exports.toAscii=v,exports.toBuffer=h,exports.toString=_,exports.toUint8Array=g;
package/dist/index.js CHANGED
@@ -1 +1 @@
1
- import{SerialPort as e}from"serialport";import{EventEmitter as t}from"events";const n={DISCONNECTED:`DISCONNECTED`,SCANNING:`SCANNING`,CONNECTING:`CONNECTING`,CONNECTED:`CONNECTED`,RECONNECTING:`RECONNECTING`};var r=class{queue=[];isProcessing=!1;_currentAlias=void 0;writeHandler;constructor(e){this.writeHandler=e}add(e,t){return new Promise((n,r)=>{this.queue.push({data:e,resolve:n,reject:r,alias:t?.alias,timeout:t?.timeout}),this.process()})}get currentAlias(){return this._currentAlias}clear(){this.queue.forEach(e=>e.reject(Error(`Queue cleared due to disconnection`))),this.queue=[],this.isProcessing=!1}async process(){if(this.isProcessing||this.queue.length===0)return;this.isProcessing=!0;let e=this.queue.shift();if(!e){this.isProcessing=!1;return}this._currentAlias=e.alias;let t=null,n=!1;try{e.timeout&&e.timeout>0&&(t=setTimeout(()=>{n=!0,e.reject(Error(`Command timeout after ${e.timeout}ms`)),this.isProcessing=!1,this.process()},e.timeout)),await this.writeHandler(e.data),n||(t&&clearTimeout(t),e.resolve())}catch(r){n||(t&&clearTimeout(t),e.reject(r instanceof Error?r:Error(String(r))))}finally{n||(this.isProcessing=!1,this.process())}}},i=class{static async findPort(t,n){if(!t&&!n)throw Error(`VendorID or ProductID is required for automatic scanning.`);try{let r=await e.list(),i=r.find(e=>{let r=e.vendorId?.toLowerCase()||``,i=e.productId?.toLowerCase()||``,a=(t||``).toLowerCase().replace(`0x`,``),o=(n||``).toLowerCase().replace(`0x`,``),s=t?r.includes(a):!0,c=n?i.includes(o):!0;return s&&c});return i?i.path:null}catch(e){return console.error(`Error scanning ports:`,e),null}}},a=class e{static instance;lockedPorts=new Set;constructor(){}static getInstance(){return e.instance||=new e,e.instance}register(e){return this.lockedPorts.has(e)?!1:(this.lockedPorts.add(e),!0)}unregister(e){this.lockedPorts.delete(e)}isLocked(e){return this.lockedPorts.has(e)}},o=class extends t{port=null;queue;_status=n.DISCONNECTED;config;intentionalDisconnect=!1;reconnectTimer=null;constructor(e){super(),this.config=e,this.queue=new r(async e=>this.performWrite(e)),this.config.autoConnect&&this.connect()}get status(){return this._status}setStatus(e){this._status!==e&&(this._status=e,this.emit(`status`,e))}async connect(){if(this._status===n.CONNECTED||this._status===n.CONNECTING)return;this.intentionalDisconnect=!1,this.setStatus(n.SCANNING);let e=this.config.path;if(!e)try{let t=await i.findPort(this.config.vendorId,this.config.productId);if(t)e=t;else{this.handleConnectionFailure(`Device not found in scan`);return}}catch(e){this.handleConnectionFailure(`Error scanning: ${e}`);return}if(a.getInstance().isLocked(e)){this.handleConnectionFailure(`Port ${e} occupied by another internal instance`);return}if(!a.getInstance().register(e)){this.handleConnectionFailure(`Could not lock port ${e}`);return}this.setStatus(n.CONNECTING),this.openPort(e)}openPort(t){if(this.port=new e({path:t,baudRate:this.config.baudRate,autoOpen:!1}),this.port.open(e=>{if(e){this.handleConnectionFailure(e.message);return}this.config.handshake?this.performHandshake():(this.setStatus(n.CONNECTED),this.emit(`connected`,{path:t,baudRate:this.config.baudRate}))}),this.config.parser){let e=this.port.pipe(this.config.parser);e.on(`data`,e=>this.emit(`data`,e,this.queue.currentAlias)),e.on(`error`,e=>this.emit(`error`,e))}else this.port.on(`data`,e=>this.emit(`data`,e,this.queue.currentAlias));this.port.on(`error`,e=>{this.emit(`error`,e)}),this.port.on(`close`,()=>{this.cleanup(),this.emit(`disconnected`,this.intentionalDisconnect?`Manual`:`Unexpected`),this.intentionalDisconnect?this.setStatus(n.DISCONNECTED):(this.setStatus(n.RECONNECTING),this.scheduleReconnect())})}async disconnect(){if(this.intentionalDisconnect=!0,this.reconnectTimer&&clearTimeout(this.reconnectTimer),this.port&&this.port.isOpen)return new Promise(e=>{this.port?.close(()=>e())})}send(e,t){return this._status===n.CONNECTED?this.queue.add(e,t):Promise.reject(Error(`Port not connected`))}performWrite(e){return new Promise((t,n)=>{if(!this.port||!this.port.isOpen)return n(Error(`Port closed during write`));let r=!this.port.write(e,e=>{if(e)return n(e);r||t()});r&&this.port.once(`drain`,t)})}handleConnectionFailure(e){this.emit(`error`,Error(`Connection failure: ${e}`)),this.cleanup(),this.intentionalDisconnect||(this.setStatus(n.RECONNECTING),this.scheduleReconnect())}scheduleReconnect(){this.reconnectTimer&&clearTimeout(this.reconnectTimer),this.reconnectTimer=setTimeout(()=>{this.connect()},this.config.reconnectInterval)}cleanup(){this.port&&this.port.path&&a.getInstance().unregister(this.port.path),this.port&&(this.port.removeAllListeners(),this.port=null),this.config.parser&&(this.config.parser.removeAllListeners(`data`),this.config.parser.removeAllListeners(`error`)),this.intentionalDisconnect&&this.queue.clear()}performHandshake(){if(!this.port||!this.port.isOpen||!this.config.handshake)return;let{command:e,pattern:t,timeout:r}=this.config.handshake,i,a=e=>{let r=``;r=Buffer.isBuffer(e)?e.toString():typeof e==`string`?e:String(e);let i=typeof t==`string`?new RegExp(t):t;i.test(r)&&(o(),this.setStatus(n.CONNECTED),this.emit(`connected`,{path:this.port.path,baudRate:this.config.baudRate}))},o=()=>{clearTimeout(i),this.removeListener(`data`,s)},s=e=>a(e);this.on(`data`,s),i=setTimeout(()=>{o(),this.handleConnectionFailure(`Handshake timeout (pattern: ${t})`)},r),this.performWrite(e).catch(e=>{o(),this.handleConnectionFailure(`Error writing handshake: ${e.message}`)})}};function s(e){return Buffer.isBuffer(e)?e:e instanceof Uint8Array?Buffer.from(e.buffer,e.byteOffset,e.byteLength):(Array.isArray(e),Buffer.from(e))}function c(e){return e}function l(e,t=`utf8`){return e.toString(t)}function u(e){return e.toString(`ascii`)}function d(e){return[...e]}export{i as PortScanner,o as SerialService,n as SerialStatus,d as toArray,u as toAscii,s as toBuffer,l as toString,c as toUint8Array};
1
+ import{SerialPort as e}from"serialport";import{EventEmitter as t}from"events";const n={DISCONNECTED:`DISCONNECTED`,SCANNING:`SCANNING`,CONNECTING:`CONNECTING`,CONNECTED:`CONNECTED`,RECONNECTING:`RECONNECTING`};var r=class{queue=[];isProcessing=!1;_currentAlias=void 0;waitingForResponse=!1;currentTimeoutTimer=null;currentItem=null;writeHandler;constructor(e){this.writeHandler=e}add(e,t){return new Promise((n,r)=>{this.queue.push({data:e,resolve:n,reject:r,alias:t?.alias,timeout:t?.timeout,waitResponse:t?.waitResponse}),this.process()})}get currentAlias(){return this._currentAlias}clear(){this.queue.forEach(e=>e.reject(Error(`Queue cleared due to disconnection`))),this.queue=[],this.waitingForResponse&&this.currentItem&&(this.currentTimeoutTimer&&clearTimeout(this.currentTimeoutTimer),this.currentItem.reject(Error(`Queue cleared due to disconnection`))),this.isProcessing=!1,this.waitingForResponse=!1,this.currentItem=null,this.currentTimeoutTimer=null}notifyResponse(){this.waitingForResponse&&this.currentItem&&(this.currentTimeoutTimer&&clearTimeout(this.currentTimeoutTimer),this.waitingForResponse=!1,this.currentTimeoutTimer=null,this.currentItem.resolve(),this.currentItem=null,this.isProcessing=!1,this.process())}async process(){if(this.isProcessing||this.waitingForResponse||this.queue.length===0)return;this.isProcessing=!0;let e=this.queue.shift();if(!e){this.isProcessing=!1;return}this.currentItem=e,this._currentAlias=e.alias;let t=null,n=!1;try{if(!e.waitResponse&&e.timeout&&e.timeout>0&&(t=setTimeout(()=>{n=!0,e.reject(Error(`Write timeout after ${e.timeout}ms`)),this.isProcessing=!1,this.currentItem=null,this.process()},e.timeout)),await this.writeHandler(e.data),n)return;t&&clearTimeout(t),e.waitResponse?(this.waitingForResponse=!0,e.timeout&&e.timeout>0&&(this.currentTimeoutTimer=setTimeout(()=>{this.handleResponseTimeout(e)},e.timeout)),this.isProcessing=!1):(e.resolve(),this.currentItem=null,this.isProcessing=!1,this.process())}catch(r){n||(t&&clearTimeout(t),e.reject(r instanceof Error?r:Error(String(r))),this.waitingForResponse=!1,this.currentItem=null,this.isProcessing=!1,this.process())}}handleResponseTimeout(e){this.waitingForResponse=!1,this.currentTimeoutTimer=null,this.currentItem=null,e.reject(Error(`Response timeout after ${e.timeout}ms`)),this.process()}},i=class{static async findPort(t,n){if(!t&&!n)throw Error(`VendorID or ProductID is required for automatic scanning.`);try{let r=await e.list(),i=r.find(e=>{let r=e.vendorId?.toLowerCase()||``,i=e.productId?.toLowerCase()||``,a=(t||``).toLowerCase().replace(`0x`,``),o=(n||``).toLowerCase().replace(`0x`,``),s=t?r.includes(a):!0,c=n?i.includes(o):!0;return s&&c});return i?i.path:null}catch(e){return console.error(`Error scanning ports:`,e),null}}},a=class e{static instance;lockedPorts=new Set;constructor(){}static getInstance(){return e.instance||=new e,e.instance}register(e){return this.lockedPorts.has(e)?!1:(this.lockedPorts.add(e),!0)}unregister(e){this.lockedPorts.delete(e)}isLocked(e){return this.lockedPorts.has(e)}},o=class extends t{port=null;queue;_status=n.DISCONNECTED;config;intentionalDisconnect=!1;reconnectTimer=null;constructor(e){super(),this.config=e,this.queue=new r(async e=>this.performWrite(e)),this.config.autoConnect&&this.connect()}get status(){return this._status}setStatus(e){this._status!==e&&(this._status=e,this.emit(`status`,e))}async connect(){if(this._status===n.CONNECTED||this._status===n.CONNECTING)return;this.intentionalDisconnect=!1,this.setStatus(n.SCANNING);let e=this.config.path;if(!e)try{let t=await i.findPort(this.config.vendorId,this.config.productId);if(t)e=t;else{this.handleConnectionFailure(`Device not found in scan`);return}}catch(e){this.handleConnectionFailure(`Error scanning: ${e}`);return}if(a.getInstance().isLocked(e)){this.handleConnectionFailure(`Port ${e} occupied by another internal instance`);return}if(!a.getInstance().register(e)){this.handleConnectionFailure(`Could not lock port ${e}`);return}this.setStatus(n.CONNECTING),this.openPort(e)}openPort(t){if(this.port=new e({path:t,baudRate:this.config.baudRate,autoOpen:!1}),this.port.open(e=>{if(e){this.handleConnectionFailure(e.message);return}this.config.handshake?this.performHandshake():(this.setStatus(n.CONNECTED),this.emit(`connected`,{path:t,baudRate:this.config.baudRate}))}),this.config.parser){let e=this.port.pipe(this.config.parser);e.on(`data`,e=>{this.queue.notifyResponse(),this.emit(`data`,e,this.queue.currentAlias)}),e.on(`error`,e=>this.emit(`error`,e))}else this.port.on(`data`,e=>{this.queue.notifyResponse(),this.emit(`data`,e,this.queue.currentAlias)});this.port.on(`error`,e=>{this.emit(`error`,e)}),this.port.on(`close`,()=>{this.cleanup(),this.emit(`disconnected`,this.intentionalDisconnect?`Manual`:`Unexpected`),this.intentionalDisconnect?this.setStatus(n.DISCONNECTED):(this.setStatus(n.RECONNECTING),this.scheduleReconnect())})}async disconnect(){if(this.intentionalDisconnect=!0,this.reconnectTimer&&clearTimeout(this.reconnectTimer),this.port&&this.port.isOpen)return new Promise(e=>{this.port?.close(()=>e())})}send(e,t){return this._status===n.CONNECTED?this.queue.add(e,t):Promise.reject(Error(`Port not connected`))}performWrite(e){return new Promise((t,n)=>{if(!this.port||!this.port.isOpen)return n(Error(`Port closed during write`));let r=!this.port.write(e,e=>{if(e)return n(e);r||t()});r&&this.port.once(`drain`,t)})}handleConnectionFailure(e){this.emit(`error`,Error(`Connection failure: ${e}`)),this.cleanup(),this.intentionalDisconnect||(this.setStatus(n.RECONNECTING),this.scheduleReconnect())}scheduleReconnect(){this.reconnectTimer&&clearTimeout(this.reconnectTimer),this.reconnectTimer=setTimeout(()=>{this.connect()},this.config.reconnectInterval)}cleanup(){this.port&&this.port.path&&a.getInstance().unregister(this.port.path),this.port&&(this.port.removeAllListeners(),this.port=null),this.config.parser&&(this.config.parser.removeAllListeners(`data`),this.config.parser.removeAllListeners(`error`)),this.intentionalDisconnect&&this.queue.clear()}performHandshake(){if(!this.port||!this.port.isOpen||!this.config.handshake)return;let{command:e,pattern:t,timeout:r}=this.config.handshake,i,a=e=>{let r=``;r=Buffer.isBuffer(e)?e.toString():typeof e==`string`?e:String(e);let i=typeof t==`string`?new RegExp(t):t;i.test(r)&&(o(),this.setStatus(n.CONNECTED),this.emit(`connected`,{path:this.port.path,baudRate:this.config.baudRate}))},o=()=>{clearTimeout(i),this.removeListener(`data`,s)},s=e=>a(e);this.on(`data`,s),i=setTimeout(()=>{o(),this.handleConnectionFailure(`Handshake timeout (pattern: ${t})`)},r),this.performWrite(e).catch(e=>{o(),this.handleConnectionFailure(`Error writing handshake: ${e.message}`)})}};function s(e){return Buffer.isBuffer(e)?e:e instanceof Uint8Array?Buffer.from(e.buffer,e.byteOffset,e.byteLength):(Array.isArray(e),Buffer.from(e))}function c(e){return e}function l(e,t=`utf8`){return e.toString(t)}function u(e){return e.toString(`ascii`)}function d(e){return[...e]}export{i as PortScanner,o as SerialService,n as SerialStatus,d as toArray,u as toAscii,s as toBuffer,l as toString,c as toUint8Array};
package/dist/types.d.cts CHANGED
@@ -24,6 +24,7 @@ export interface QueueItem {
24
24
  data: Buffer | string;
25
25
  alias?: string;
26
26
  timeout?: number;
27
+ waitResponse?: boolean;
27
28
  resolve: () => void;
28
29
  reject: (err: Error) => void;
29
30
  }
package/dist/types.d.ts CHANGED
@@ -24,6 +24,7 @@ export interface QueueItem {
24
24
  data: Buffer | string;
25
25
  alias?: string;
26
26
  timeout?: number;
27
+ waitResponse?: boolean;
27
28
  resolve: () => void;
28
29
  reject: (err: Error) => void;
29
30
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "serial-core",
3
3
  "type": "module",
4
- "version": "0.2.0-dev.1",
4
+ "version": "0.2.0-dev.3",
5
5
  "description": "Resilient serial communication service with automatic reconnection and queues.",
6
6
  "author": "Danidoble <danidoble@gmail.com>",
7
7
  "license": "GPL-3.0-only",
@@ -14,12 +14,12 @@
14
14
  "url": "https://github.com/danidoble/serial-core/issues"
15
15
  },
16
16
  "main": "./dist/index.js",
17
- "module": "./dist/index.mjs",
17
+ "module": "./dist/index.js",
18
18
  "types": "./dist/index.d.ts",
19
19
  "exports": {
20
20
  ".": {
21
- "import": "./dist/index.mjs",
22
- "require": "./dist/index.js",
21
+ "import": "./dist/index.js",
22
+ "require": "./dist/index.cjs",
23
23
  "types": "./dist/index.d.ts"
24
24
  }
25
25
  },