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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,5 +1,8 @@
1
1
  # serial-core
2
2
 
3
+ [![npm version](https://img.shields.io/npm/v/serial-core.svg)](https://www.npmjs.com/package/serial-core)
4
+ [![License: GPL-3.0](https://img.shields.io/badge/License-GPL%203.0-blue.svg)](https://opensource.org/licenses/GPL-3.0)
5
+
3
6
  > Resilient serial communication service with automatic reconnection and queues.
4
7
 
5
8
 
@@ -15,6 +15,7 @@ export declare class SerialService extends EventEmitter {
15
15
  protected intentionalDisconnect: boolean;
16
16
  protected reconnectTimer: NodeJS.Timeout | null;
17
17
  constructor(config: SerialConfig);
18
+ get autoConnect(): boolean;
18
19
  get status(): SerialStatus;
19
20
  /**
20
21
  * Updates status and emits the corresponding event.
@@ -15,6 +15,7 @@ export declare class SerialService extends EventEmitter {
15
15
  protected intentionalDisconnect: boolean;
16
16
  protected reconnectTimer: NodeJS.Timeout | null;
17
17
  constructor(config: SerialConfig);
18
+ get autoConnect(): boolean;
18
19
  get status(): SerialStatus;
19
20
  /**
20
21
  * Updates status and emits the corresponding event.
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;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;
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 autoConnect(){return this.config.autoConnect}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;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};
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 autoConnect(){return this.config.autoConnect}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/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "serial-core",
3
3
  "type": "module",
4
- "version": "0.2.0-dev.3",
4
+ "version": "0.2.0-dev.4",
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",