index-esm.mjs 9.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271
  1. import debugFactory from 'debug';
  2. const debug = debugFactory('serialport/binding-mock');
  3. let ports = {};
  4. let serialNumber = 0;
  5. function resolveNextTick() {
  6. return new Promise(resolve => process.nextTick(() => resolve()));
  7. }
  8. class CanceledError extends Error {
  9. constructor(message) {
  10. super(message);
  11. this.canceled = true;
  12. }
  13. }
  14. const MockBinding = {
  15. reset() {
  16. ports = {};
  17. serialNumber = 0;
  18. },
  19. // Create a mock port
  20. createPort(path, options = {}) {
  21. serialNumber++;
  22. const optWithDefaults = Object.assign({ echo: false, record: false, manufacturer: 'The J5 Robotics Company', vendorId: undefined, productId: undefined, maxReadSize: 1024 }, options);
  23. ports[path] = {
  24. data: Buffer.alloc(0),
  25. echo: optWithDefaults.echo,
  26. record: optWithDefaults.record,
  27. readyData: optWithDefaults.readyData,
  28. maxReadSize: optWithDefaults.maxReadSize,
  29. info: {
  30. path,
  31. manufacturer: optWithDefaults.manufacturer,
  32. serialNumber: `${serialNumber}`,
  33. pnpId: undefined,
  34. locationId: undefined,
  35. vendorId: optWithDefaults.vendorId,
  36. productId: optWithDefaults.productId,
  37. },
  38. };
  39. debug(serialNumber, 'created port', JSON.stringify({ path, opt: options }));
  40. },
  41. async list() {
  42. debug(null, 'list');
  43. return Object.values(ports).map(port => port.info);
  44. },
  45. async open(options) {
  46. var _a;
  47. if (!options || typeof options !== 'object' || Array.isArray(options)) {
  48. throw new TypeError('"options" is not an object');
  49. }
  50. if (!options.path) {
  51. throw new TypeError('"path" is not a valid port');
  52. }
  53. if (!options.baudRate) {
  54. throw new TypeError('"baudRate" is not a valid baudRate');
  55. }
  56. const openOptions = Object.assign({ dataBits: 8, lock: true, stopBits: 1, parity: 'none', rtscts: false, xon: false, xoff: false, xany: false, hupcl: true }, options);
  57. const { path } = openOptions;
  58. debug(null, `open: opening path ${path}`);
  59. const port = ports[path];
  60. await resolveNextTick();
  61. if (!port) {
  62. throw new Error(`Port does not exist - please call MockBinding.createPort('${path}') first`);
  63. }
  64. const serialNumber = port.info.serialNumber;
  65. if ((_a = port.openOpt) === null || _a === void 0 ? void 0 : _a.lock) {
  66. debug(serialNumber, 'open: Port is locked cannot open');
  67. throw new Error('Port is locked cannot open');
  68. }
  69. debug(serialNumber, `open: opened path ${path}`);
  70. port.openOpt = Object.assign({}, openOptions);
  71. return new MockPortBinding(port, openOptions);
  72. },
  73. };
  74. /**
  75. * Mock bindings for pretend serialport access
  76. */
  77. class MockPortBinding {
  78. constructor(port, openOptions) {
  79. this.port = port;
  80. this.openOptions = openOptions;
  81. this.pendingRead = null;
  82. this.isOpen = true;
  83. this.lastWrite = null;
  84. this.recording = Buffer.alloc(0);
  85. this.writeOperation = null; // in flight promise or null
  86. this.serialNumber = port.info.serialNumber;
  87. if (port.readyData) {
  88. const data = port.readyData;
  89. process.nextTick(() => {
  90. if (this.isOpen) {
  91. debug(this.serialNumber, 'emitting ready data');
  92. this.emitData(data);
  93. }
  94. });
  95. }
  96. }
  97. // Emit data on a mock port
  98. emitData(data) {
  99. if (!this.isOpen || !this.port) {
  100. throw new Error('Port must be open to pretend to receive data');
  101. }
  102. const bufferData = Buffer.isBuffer(data) ? data : Buffer.from(data);
  103. debug(this.serialNumber, 'emitting data - pending read:', Boolean(this.pendingRead));
  104. this.port.data = Buffer.concat([this.port.data, bufferData]);
  105. if (this.pendingRead) {
  106. process.nextTick(this.pendingRead);
  107. this.pendingRead = null;
  108. }
  109. }
  110. async close() {
  111. debug(this.serialNumber, 'close');
  112. if (!this.isOpen) {
  113. throw new Error('Port is not open');
  114. }
  115. const port = this.port;
  116. if (!port) {
  117. throw new Error('already closed');
  118. }
  119. port.openOpt = undefined;
  120. // reset data on close
  121. port.data = Buffer.alloc(0);
  122. debug(this.serialNumber, 'port is closed');
  123. this.serialNumber = undefined;
  124. this.isOpen = false;
  125. if (this.pendingRead) {
  126. this.pendingRead(new CanceledError('port is closed'));
  127. }
  128. }
  129. async read(buffer, offset, length) {
  130. if (!Buffer.isBuffer(buffer)) {
  131. throw new TypeError('"buffer" is not a Buffer');
  132. }
  133. if (typeof offset !== 'number' || isNaN(offset)) {
  134. throw new TypeError(`"offset" is not an integer got "${isNaN(offset) ? 'NaN' : typeof offset}"`);
  135. }
  136. if (typeof length !== 'number' || isNaN(length)) {
  137. throw new TypeError(`"length" is not an integer got "${isNaN(length) ? 'NaN' : typeof length}"`);
  138. }
  139. if (buffer.length < offset + length) {
  140. throw new Error('buffer is too small');
  141. }
  142. if (!this.isOpen) {
  143. throw new Error('Port is not open');
  144. }
  145. debug(this.serialNumber, 'read', length, 'bytes');
  146. await resolveNextTick();
  147. if (!this.isOpen || !this.port) {
  148. throw new CanceledError('Read canceled');
  149. }
  150. if (this.port.data.length <= 0) {
  151. return new Promise((resolve, reject) => {
  152. this.pendingRead = err => {
  153. if (err) {
  154. return reject(err);
  155. }
  156. this.read(buffer, offset, length).then(resolve, reject);
  157. };
  158. });
  159. }
  160. const lengthToRead = this.port.maxReadSize > length ? length : this.port.maxReadSize;
  161. const data = this.port.data.slice(0, lengthToRead);
  162. const bytesRead = data.copy(buffer, offset);
  163. this.port.data = this.port.data.slice(lengthToRead);
  164. debug(this.serialNumber, 'read', bytesRead, 'bytes');
  165. return { bytesRead, buffer };
  166. }
  167. async write(buffer) {
  168. if (!Buffer.isBuffer(buffer)) {
  169. throw new TypeError('"buffer" is not a Buffer');
  170. }
  171. if (!this.isOpen || !this.port) {
  172. debug('write', 'error port is not open');
  173. throw new Error('Port is not open');
  174. }
  175. debug(this.serialNumber, 'write', buffer.length, 'bytes');
  176. if (this.writeOperation) {
  177. throw new Error('Overlapping writes are not supported and should be queued by the serialport object');
  178. }
  179. this.writeOperation = (async () => {
  180. await resolveNextTick();
  181. if (!this.isOpen || !this.port) {
  182. throw new Error('Write canceled');
  183. }
  184. const data = (this.lastWrite = Buffer.from(buffer)); // copy
  185. if (this.port.record) {
  186. this.recording = Buffer.concat([this.recording, data]);
  187. }
  188. if (this.port.echo) {
  189. process.nextTick(() => {
  190. if (this.isOpen) {
  191. this.emitData(data);
  192. }
  193. });
  194. }
  195. this.writeOperation = null;
  196. debug(this.serialNumber, 'writing finished');
  197. })();
  198. return this.writeOperation;
  199. }
  200. async update(options) {
  201. if (typeof options !== 'object') {
  202. throw TypeError('"options" is not an object');
  203. }
  204. if (typeof options.baudRate !== 'number') {
  205. throw new TypeError('"options.baudRate" is not a number');
  206. }
  207. debug(this.serialNumber, 'update');
  208. if (!this.isOpen || !this.port) {
  209. throw new Error('Port is not open');
  210. }
  211. await resolveNextTick();
  212. if (this.port.openOpt) {
  213. this.port.openOpt.baudRate = options.baudRate;
  214. }
  215. }
  216. async set(options) {
  217. if (typeof options !== 'object') {
  218. throw new TypeError('"options" is not an object');
  219. }
  220. debug(this.serialNumber, 'set');
  221. if (!this.isOpen) {
  222. throw new Error('Port is not open');
  223. }
  224. await resolveNextTick();
  225. }
  226. async get() {
  227. debug(this.serialNumber, 'get');
  228. if (!this.isOpen) {
  229. throw new Error('Port is not open');
  230. }
  231. await resolveNextTick();
  232. return {
  233. cts: true,
  234. dsr: false,
  235. dcd: false,
  236. };
  237. }
  238. async getBaudRate() {
  239. var _a;
  240. debug(this.serialNumber, 'getBaudRate');
  241. if (!this.isOpen || !this.port) {
  242. throw new Error('Port is not open');
  243. }
  244. await resolveNextTick();
  245. if (!((_a = this.port.openOpt) === null || _a === void 0 ? void 0 : _a.baudRate)) {
  246. throw new Error('Internal Error');
  247. }
  248. return {
  249. baudRate: this.port.openOpt.baudRate,
  250. };
  251. }
  252. async flush() {
  253. debug(this.serialNumber, 'flush');
  254. if (!this.isOpen || !this.port) {
  255. throw new Error('Port is not open');
  256. }
  257. await resolveNextTick();
  258. this.port.data = Buffer.alloc(0);
  259. }
  260. async drain() {
  261. debug(this.serialNumber, 'drain');
  262. if (!this.isOpen) {
  263. throw new Error('Port is not open');
  264. }
  265. await this.writeOperation;
  266. await resolveNextTick();
  267. }
  268. }
  269. export { CanceledError, MockBinding, MockPortBinding };