ngx-web-serial 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
package/README.md ADDED
@@ -0,0 +1,65 @@
1
+ [![npm version](https://badge.fury.io/js/angular-web-serial.svg?icon=si%3Anpm)](https://badge.fury.io/js/angular-web-serial)
2
+ [![Publish to GitHub Packages](https://github.com/mattfors/ngx-web-serial/actions/workflows/build.yml/badge.svg)](https://github.com/mattfors/ngx-web-serial/actions/workflows/build.yml)
3
+ [![codecov](https://codecov.io/github/mattfors/ngx-web-serial/graph/badge.svg?token=GRL2B8OCW5)](https://codecov.io/github/mattfors/ngx-web-serial)
4
+
5
+ # Angular Web Serial
6
+
7
+ Angular Web Serial is an angular module for connecting to serial devices with the Web Serial API.
8
+
9
+ ## Installation
10
+
11
+ ```shell
12
+ npm i angular-web-serial
13
+ ```
14
+
15
+ ## Usage
16
+ Below is the basic usage of the module. A pipe is used to accumulate the raw data from the serial port.
17
+ ```typescript
18
+ import { Component } from '@angular/core';
19
+ import { AngularSerialService, provideAngularSerial } from '../../../ngx-web-serial/src';
20
+ import { Observable, scan } from 'rxjs';
21
+ import { AsyncPipe } from '@angular/common';
22
+
23
+ @Component({
24
+ selector: 'app-root',
25
+ standalone: true,
26
+ imports: [AsyncPipe],
27
+ providers: [provideAngularSerial()],
28
+ template: `
29
+ <button (click)="open()">Open</button>
30
+ <input #inputField
31
+ type="text"
32
+ (keydown.enter)="write(inputField.value); inputField.value=''"
33
+ placeholder="Type and press Enter">
34
+ <div>
35
+ <textarea [value]="data$ | async" readonly></textarea>
36
+ </div>
37
+ `
38
+ })
39
+ export class AppComponent {
40
+
41
+ data$: Observable<string>;
42
+
43
+ constructor(private serial: AngularSerialService) {
44
+ this.data$ = this.serial.read().pipe(
45
+ scan((acc, value) => acc + value, '')
46
+ );
47
+ }
48
+
49
+ open(): void {
50
+ this.serial.open().subscribe()
51
+ }
52
+
53
+ write(value: string): void {
54
+ this.serial.write(value + '\r').subscribe();
55
+ }
56
+
57
+ }
58
+
59
+ ```
60
+
61
+ ## Mock serial device
62
+ The module can be used with a mock serial device for testing or if you do not have a real serial device. Provide a function which takes and returns a string. In this example, the text transmitted to the serial device will be echoed back with 'Hello'.
63
+ ```typescript
64
+ providers: [provideAngularSerialTest(i => `Hello ${i}!\n`)]
65
+ ```
@@ -0,0 +1,31 @@
1
+ export class MockSerial {
2
+ constructor(responseFunction) {
3
+ this.readableController = null;
4
+ this.responseFunction = responseFunction;
5
+ this.readableStream = new ReadableStream({
6
+ start: (controller) => {
7
+ this.readableController = controller;
8
+ },
9
+ cancel: () => {
10
+ this.readableController = null;
11
+ }
12
+ });
13
+ }
14
+ requestPort(options) {
15
+ return Promise.resolve({
16
+ open: () => Promise.resolve(),
17
+ close: () => this.readableStream.cancel(),
18
+ readable: this.readableStream,
19
+ writable: new WritableStream({
20
+ write: (chunk) => {
21
+ const input = new TextDecoder().decode(chunk);
22
+ const response = this.responseFunction(input);
23
+ if (this.readableController) {
24
+ this.readableController.enqueue(new TextEncoder().encode(response));
25
+ }
26
+ }
27
+ })
28
+ });
29
+ }
30
+ }
31
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoibW9jay1zZXJpYWwuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi8uLi9wcm9qZWN0cy9uZ3gtd2ViLXNlcmlhbC9zcmMvbGliL21vY2stc2VyaWFsLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLE1BQU0sT0FBTyxVQUFVO0lBSXJCLFlBQVksZ0JBQTJDO1FBSC9DLHVCQUFrQixHQUF1RCxJQUFJLENBQUM7UUFJcEYsSUFBSSxDQUFDLGdCQUFnQixHQUFHLGdCQUFnQixDQUFDO1FBQ3pDLElBQUksQ0FBQyxjQUFjLEdBQUcsSUFBSSxjQUFjLENBQUM7WUFDdkMsS0FBSyxFQUFFLENBQUMsVUFBVSxFQUFFLEVBQUU7Z0JBQ3BCLElBQUksQ0FBQyxrQkFBa0IsR0FBRyxVQUFVLENBQUM7WUFDdkMsQ0FBQztZQUNELE1BQU0sRUFBRSxHQUFHLEVBQUU7Z0JBQ1gsSUFBSSxDQUFDLGtCQUFrQixHQUFHLElBQUksQ0FBQztZQUNqQyxDQUFDO1NBQ0YsQ0FBQyxDQUFDO0lBQ0wsQ0FBQztJQUVELFdBQVcsQ0FBQyxPQUFrQztRQUM1QyxPQUFPLE9BQU8sQ0FBQyxPQUFPLENBQUM7WUFDckIsSUFBSSxFQUFFLEdBQUcsRUFBRSxDQUFDLE9BQU8sQ0FBQyxPQUFPLEVBQUU7WUFDN0IsS0FBSyxFQUFFLEdBQUcsRUFBRSxDQUFDLElBQUksQ0FBQyxjQUFjLENBQUMsTUFBTSxFQUFFO1lBQ3pDLFFBQVEsRUFBRSxJQUFJLENBQUMsY0FBYztZQUM3QixRQUFRLEVBQUUsSUFBSSxjQUFjLENBQUM7Z0JBQzNCLEtBQUssRUFBRSxDQUFDLEtBQUssRUFBRSxFQUFFO29CQUNmLE1BQU0sS0FBSyxHQUFHLElBQUksV0FBVyxFQUFFLENBQUMsTUFBTSxDQUFDLEtBQUssQ0FBQyxDQUFDO29CQUM5QyxNQUFNLFFBQVEsR0FBRyxJQUFJLENBQUMsZ0JBQWdCLENBQUMsS0FBSyxDQUFDLENBQUM7b0JBQzlDLElBQUksSUFBSSxDQUFDLGtCQUFrQixFQUFFLENBQUM7d0JBQzVCLElBQUksQ0FBQyxrQkFBa0IsQ0FBQyxPQUFPLENBQUMsSUFBSSxXQUFXLEVBQUUsQ0FBQyxNQUFNLENBQUMsUUFBUSxDQUFDLENBQUMsQ0FBQztvQkFDdEUsQ0FBQztnQkFDSCxDQUFDO2FBQ0YsQ0FBQztTQUNrQixDQUFDLENBQUM7SUFDMUIsQ0FBQztDQUNGIiwic291cmNlc0NvbnRlbnQiOlsiZXhwb3J0IGNsYXNzIE1vY2tTZXJpYWwge1xuICBwcml2YXRlIHJlYWRhYmxlQ29udHJvbGxlcjogUmVhZGFibGVTdHJlYW1EZWZhdWx0Q29udHJvbGxlcjxVaW50OEFycmF5PiB8IG51bGwgPSBudWxsO1xuICByZWFkb25seSByZXNwb25zZUZ1bmN0aW9uOiAoaW5wdXQ6IHN0cmluZykgPT4gc3RyaW5nO1xuICByZWFkb25seSByZWFkYWJsZVN0cmVhbTogUmVhZGFibGVTdHJlYW08VWludDhBcnJheT47XG4gIGNvbnN0cnVjdG9yKHJlc3BvbnNlRnVuY3Rpb246IChpbnB1dDogc3RyaW5nKSA9PiBzdHJpbmcpIHtcbiAgICB0aGlzLnJlc3BvbnNlRnVuY3Rpb24gPSByZXNwb25zZUZ1bmN0aW9uO1xuICAgIHRoaXMucmVhZGFibGVTdHJlYW0gPSBuZXcgUmVhZGFibGVTdHJlYW0oe1xuICAgICAgc3RhcnQ6IChjb250cm9sbGVyKSA9PiB7XG4gICAgICAgIHRoaXMucmVhZGFibGVDb250cm9sbGVyID0gY29udHJvbGxlcjtcbiAgICAgIH0sXG4gICAgICBjYW5jZWw6ICgpID0+IHtcbiAgICAgICAgdGhpcy5yZWFkYWJsZUNvbnRyb2xsZXIgPSBudWxsO1xuICAgICAgfVxuICAgIH0pO1xuICB9XG5cbiAgcmVxdWVzdFBvcnQob3B0aW9ucz86IFNlcmlhbFBvcnRSZXF1ZXN0T3B0aW9ucyk6IFByb21pc2U8U2VyaWFsUG9ydD4ge1xuICAgIHJldHVybiBQcm9taXNlLnJlc29sdmUoe1xuICAgICAgb3BlbjogKCkgPT4gUHJvbWlzZS5yZXNvbHZlKCksXG4gICAgICBjbG9zZTogKCkgPT4gdGhpcy5yZWFkYWJsZVN0cmVhbS5jYW5jZWwoKSxcbiAgICAgIHJlYWRhYmxlOiB0aGlzLnJlYWRhYmxlU3RyZWFtLFxuICAgICAgd3JpdGFibGU6IG5ldyBXcml0YWJsZVN0cmVhbSh7XG4gICAgICAgIHdyaXRlOiAoY2h1bmspID0+IHtcbiAgICAgICAgICBjb25zdCBpbnB1dCA9IG5ldyBUZXh0RGVjb2RlcigpLmRlY29kZShjaHVuayk7XG4gICAgICAgICAgY29uc3QgcmVzcG9uc2UgPSB0aGlzLnJlc3BvbnNlRnVuY3Rpb24oaW5wdXQpO1xuICAgICAgICAgIGlmICh0aGlzLnJlYWRhYmxlQ29udHJvbGxlcikge1xuICAgICAgICAgICAgdGhpcy5yZWFkYWJsZUNvbnRyb2xsZXIuZW5xdWV1ZShuZXcgVGV4dEVuY29kZXIoKS5lbmNvZGUocmVzcG9uc2UpKTtcbiAgICAgICAgICB9XG4gICAgICAgIH1cbiAgICAgIH0pXG4gICAgfSBhcyBhbnkgYXMgU2VyaWFsUG9ydCk7XG4gIH1cbn1cbiJdfQ==
@@ -0,0 +1,124 @@
1
+ import { Inject, Injectable } from '@angular/core';
2
+ import { BehaviorSubject, catchError, filter, firstValueFrom, from, interval, map, Observable, Subject, takeUntil, throwError, timer } from 'rxjs';
3
+ import { DOCUMENT } from '@angular/common';
4
+ import { MockSerial } from './mock-serial';
5
+ import * as i0 from "@angular/core";
6
+ export class NgxWebSerialService {
7
+ constructor(serial, ngZone) {
8
+ this.serial = serial;
9
+ this.ngZone = ngZone;
10
+ this.port = null;
11
+ this.abortController = null;
12
+ this.dataStream = null;
13
+ this.dataSubject = new Subject();
14
+ this.writer = null;
15
+ this.connectedSubject = new BehaviorSubject(false);
16
+ this.sink = {
17
+ write: (chunk) => {
18
+ this.ngZone.run(() => {
19
+ this.dataSubject.next(chunk);
20
+ });
21
+ }
22
+ };
23
+ }
24
+ /**
25
+ * Establishes a connection to a serial port using the Web Serial API.
26
+ */
27
+ open(serialOptions = { baudRate: 9600 }, options) {
28
+ return new Observable((observer) => {
29
+ if (!this.serial) {
30
+ observer.error('Web serial not supported.');
31
+ return;
32
+ }
33
+ this.serial.requestPort(options)
34
+ .then((port) => {
35
+ this.port = port;
36
+ return this.port.open(serialOptions);
37
+ })
38
+ .then(() => {
39
+ if (!this.port?.readable || !this.port?.writable) {
40
+ observer.error('Port is not readable or writable.');
41
+ return;
42
+ }
43
+ this.connectedSubject.next(true);
44
+ this.abortController = new AbortController();
45
+ this.dataStream = new WritableStream(this.sink);
46
+ this.writer = this.port?.writable.getWriter();
47
+ observer.next();
48
+ return this.port.readable
49
+ .pipeThrough(new TextDecoderStream())
50
+ .pipeTo(this.dataStream, { signal: this.abortController.signal })
51
+ .catch(() => this.closePort().catch((err) => observer.error(err)));
52
+ })
53
+ .catch((err) => observer.error(err))
54
+ .finally(() => observer.complete());
55
+ });
56
+ }
57
+ isConnected() {
58
+ return this.connectedSubject.asObservable();
59
+ }
60
+ read() {
61
+ return this.dataSubject.asObservable();
62
+ }
63
+ write(data) {
64
+ if (this.writer) {
65
+ return from(this.writer.write(new TextEncoder().encode(data)));
66
+ }
67
+ return throwError(() => new Error('No writer available.'));
68
+ }
69
+ waitForReadableUnlock(period = 50, timeout = 5000) {
70
+ return firstValueFrom(interval(period).pipe(filter(() => !this.port?.readable?.locked), map(() => undefined), takeUntil(timer(timeout)), catchError(() => throwError(() => new Error('Timeout waiting for readable stream to unlock')))));
71
+ }
72
+ closePort() {
73
+ this.abortController = null;
74
+ if (this.writer) {
75
+ this.writer.releaseLock();
76
+ this.writer = null;
77
+ }
78
+ if (this.port) {
79
+ return this.waitForReadableUnlock()
80
+ .then(() => this.port.close())
81
+ .then(() => {
82
+ this.port = null;
83
+ this.connectedSubject.next(false);
84
+ });
85
+ }
86
+ else {
87
+ return Promise.resolve();
88
+ }
89
+ }
90
+ close() {
91
+ if (this.abortController) {
92
+ this.abortController.abort();
93
+ }
94
+ }
95
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: NgxWebSerialService, deps: [{ token: 'Serial' }, { token: i0.NgZone }], target: i0.ɵɵFactoryTarget.Injectable }); }
96
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: NgxWebSerialService }); }
97
+ }
98
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: NgxWebSerialService, decorators: [{
99
+ type: Injectable
100
+ }], ctorParameters: () => [{ type: undefined, decorators: [{
101
+ type: Inject,
102
+ args: ['Serial']
103
+ }] }, { type: i0.NgZone }] });
104
+ export function provideAngularSerial() {
105
+ return [
106
+ NgxWebSerialService,
107
+ {
108
+ provide: 'Serial',
109
+ useFactory: (document) => document.defaultView?.navigator?.serial,
110
+ deps: [DOCUMENT]
111
+ }
112
+ ];
113
+ }
114
+ export function provideAngularSerialTest(responseFunction) {
115
+ return [
116
+ NgxWebSerialService,
117
+ {
118
+ provide: 'Serial',
119
+ useFactory: () => new MockSerial(responseFunction || ((input) => input)),
120
+ deps: []
121
+ }
122
+ ];
123
+ }
124
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoibmd4LXdlYi1zZXJpYWwuc2VydmljZS5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uLy4uL3Byb2plY3RzL25neC13ZWItc2VyaWFsL3NyYy9saWIvbmd4LXdlYi1zZXJpYWwuc2VydmljZS50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEVBQW1CLE1BQU0sRUFBRSxVQUFVLEVBQW9CLE1BQU0sZUFBZSxDQUFDO0FBQ3RGLE9BQU8sRUFDTCxlQUFlLEVBQ2YsVUFBVSxFQUNWLE1BQU0sRUFDTixjQUFjLEVBQ2QsSUFBSSxFQUNKLFFBQVEsRUFDUixHQUFHLEVBQ0gsVUFBVSxFQUNWLE9BQU8sRUFFUCxTQUFTLEVBQ1QsVUFBVSxFQUNWLEtBQUssRUFDTixNQUFNLE1BQU0sQ0FBQztBQUNkLE9BQU8sRUFBRSxRQUFRLEVBQUUsTUFBTSxpQkFBaUIsQ0FBQztBQUMzQyxPQUFPLEVBQUUsVUFBVSxFQUFFLE1BQU0sZUFBZSxDQUFDOztBQUczQyxNQUFNLE9BQU8sbUJBQW1CO0lBaUI5QixZQUM2QixNQUEwQixFQUM3QyxNQUFjO1FBREssV0FBTSxHQUFOLE1BQU0sQ0FBb0I7UUFDN0MsV0FBTSxHQUFOLE1BQU0sQ0FBUTtRQWpCaEIsU0FBSSxHQUFzQixJQUFJLENBQUM7UUFDL0Isb0JBQWUsR0FBMkIsSUFBSSxDQUFDO1FBQy9DLGVBQVUsR0FBMEIsSUFBSSxDQUFDO1FBQ3pDLGdCQUFXLEdBQW9CLElBQUksT0FBTyxFQUFVLENBQUM7UUFDckQsV0FBTSxHQUFtRCxJQUFJLENBQUM7UUFDOUQscUJBQWdCLEdBQTZCLElBQUksZUFBZSxDQUFVLEtBQUssQ0FBQyxDQUFDO1FBRXhFLFNBQUksR0FBb0I7WUFDdkMsS0FBSyxFQUFFLENBQUMsS0FBVSxFQUFFLEVBQUU7Z0JBQ3BCLElBQUksQ0FBQyxNQUFNLENBQUMsR0FBRyxDQUFDLEdBQUcsRUFBRTtvQkFDbkIsSUFBSSxDQUFDLFdBQVcsQ0FBQyxJQUFJLENBQUMsS0FB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QUFLO1FBQ0gsSUFBSSxJQUFJLENBQUMsZUFBZSxFQUFFLENBQUM7WUFDekIsSUFBSSxDQUFDLGVBQWUsQ0FBQyxLQUFLLEVBQUUsQ0FBQztRQUMvQixDQUFDO0lBQ0gsQ0FBQzsrR0F4R1UsbUJBQW1CLGtCQWtCcEIsUUFBUTttSEFsQlAsbUJBQW1COzs0RkFBbkIsbUJBQW1CO2tCQUQvQixVQUFVOzswQkFtQk4sTUFBTTsyQkFBQyxRQUFROztBQXlGcEIsTUFBTSxVQUFVLG9CQUFvQjtJQUNsQyxPQUFPO1FBQ0wsbUJBQW1CO1FBQ25CO1lBQ0UsT0FBTyxFQUFFLFFBQVE7WUFDakIsVUFBVSxFQUFFLENBQUMsUUFBa0IsRUFBRSxFQUFFLENBQUMsUUFBUSxDQUFDLFdBQVcsRUFBRSxTQUFTLEVBQUUsTUFBTTtZQUMzRSxJQUFJLEVBQUUsQ0FBQyxRQUFRLENBQUM7U0FDakI7S0FDRixDQUFDO0FBQ0osQ0FBQztBQUVELE1BQU0sVUFBVSx3QkFBd0IsQ0FBQyxnQkFBNEM7SUFDbkYsT0FBTztRQUNMLG1CQUFtQjtRQUNuQjtZQUNFLE9BQU8sRUFBRSxRQUFRO1lBQ2pCLFVBQVUsRUFBRSxHQUFHLEVBQUUsQ0FBQyxJQUFJLFVBQVUsQ0FBQyxnQkFBZ0IsSUFBSSxDQUFDLENBQUMsS0FBYSxFQUFFLEVBQUUsQ0FBQyxLQUFLLENBQUMsQ0FBQztZQUNoRixJQUFJLEVBQUUsRUFBRTtTQUNVO0tBQ3JCLENBQUM7QUFDSixDQUFDIiwic291cmNlc0NvbnRlbnQiOlsiaW1wb3J0IHsgRmFjdG9yeVByb3ZpZGVyLCBJbmplY3QsIEluamVjdGFibGUsIE5nWm9uZSwgUHJvdmlkZXIgfSBmcm9tICdAYW5ndWxhci9jb3JlJztcbmltcG9ydCB7XG4gIEJlaGF2aW9yU3ViamVjdCxcbiAgY2F0Y2hFcnJvcixcbiAgZmlsdGVyLFxuICBmaXJzdFZhbHVlRnJvbSxcbiAgZnJvbSxcbiAgaW50ZXJ2YWwsXG4gIG1hcCxcbiAgT2JzZXJ2YWJsZSxcbiAgU3ViamVjdCxcbiAgU3Vic2NyaWJlcixcbiAgdGFrZVVudGlsLFxuICB0aHJvd0Vycm9yLFxuICB0aW1lclxufSBmcm9tICdyeGpzJztcbmltcG9ydCB7IERPQ1VNRU5UIH0gZnJvbSAnQGFuZ3VsYXIvY29tbW9uJztcbmltcG9ydCB7IE1vY2tTZXJpYWwgfSBmcm9tICcuL21vY2stc2VyaWFsJztcblxuQEluamVjdGFibGUoKVxuZXhwb3J0IGNsYXNzIE5neFdlYlNlcmlhbFNlcnZpY2Uge1xuXG4gIHByaXZhdGUgcG9ydDogU2VyaWFsUG9ydCB8IG51bGwgPSBudWxsO1xuICBwcml2YXRlIGFib3J0Q29udHJvbGxlcjogQWJvcnRDb250cm9sbGVyIHwgbnVsbCA9IG51bGw7XG4gIHByaXZhdGUgZGF0YVN0cmVhbTogV3JpdGFibGVTdHJlYW0gfCBudWxsID0gbnVsbDtcbiAgcHJpdmF0ZSBkYXRhU3ViamVjdDogU3ViamVjdDxzdHJpbmc+ID0gbmV3IFN1YmplY3Q8c3RyaW5nPigpO1xuICBwcml2YXRlIHdyaXRlcjogV3JpdGFibGVTdHJlYW1EZWZhdWx0V3JpdGVyPFVpbnQ4QXJyYXk+IHwgbnVsbCA9IG51bGw7XG4gIHByaXZhdGUgY29ubmVjdGVkU3ViamVjdDogQmVoYXZpb3JTdWJqZWN0PGJvb2xlYW4+ID0gbmV3IEJlaGF2aW9yU3ViamVjdDxib29sZWFuPihmYWxzZSk7XG5cbiAgcHJpdmF0ZSByZWFkb25seSBzaW5rOiBVbmRlcmx5aW5nU2luayA9ICB7XG4gICAgd3JpdGU6IChjaHVuazogYW55KSA9PiB7XG4gICAgICB0aGlzLm5nWm9uZS5ydW4oKCkgPT4ge1xuICAgICAgICB0aGlzLmRhdGFTdWJqZWN0Lm5leHQoY2h1bmspO1xuICAgICAgfSk7XG4gICAgfVxuICB9XG5cbiAgY29uc3RydWN0b3IoXG4gICAgQEluamVjdCgnU2VyaWFsJykgcmVhZG9ubHkgc2VyaWFsOiBTZXJpYWwgfCB1bmRlZmluZWQsXG4gICAgcHJpdmF0ZSBuZ1pvbmU6IE5nWm9uZVxuICApIHtcbiAgfVxuXG4gIC8qKlxuICAgKiBFc3RhYmxpc2hlcyBhIGNvbm5lY3Rpb24gdG8gYSBzZXJpYWwgcG9ydCB1c2luZyB0aGUgV2ViIFNlcmlhbCBBUEkuXG4gICAqL1xuICBvcGVuKHNlcmlhbE9wdGlvbnM6IFNlcmlhbE9wdGlvbnMgPSB7IGJhdWRSYXRlOiA5NjAwIH0sIG9wdGlvbnM/OiBTZXJpYWxQb3J0UmVxdWVzdE9wdGlvbnMpOiBPYnNlcnZhYmxlPHZvaWQ+IHtcbiAgICByZXR1cm4gbmV3IE9ic2VydmFibGU8dm9pZD4oKG9ic2VydmVyOiBTdWJzY3JpYmVyPHZvaWQ+KSA9PiB7XG4gICAgICBpZiAoIXRoaXMuc2VyaWFsKSB7XG4gICAgICAgIG9ic2VydmVyLmVycm9yKCdXZWIgc2VyaWFsIG5vdCBzdXBwb3J0ZWQuJyk7XG4gICAgICAgIHJldHVybjtcbiAgICAgIH1cbiAgICAgIHRoaXMuc2VyaWFsLnJlcXVlc3RQb3J0KG9wdGlvbnMpXG4gICAgICAgIC50aGVuKChwb3J0OiBTZXJpYWxQb3J0KSA9PiB7XG4gICAgICAgICAgdGhpcy5wb3J0ID0gcG9ydDtcbiAgICAgICAgICByZXR1cm4gdGhpcy5wb3J0Lm9wZW4oc2VyaWFsT3B0aW9ucyk7XG4gICAgICAgIH0pXG4gICAgICAgIC50aGVuKCgpID0+IHtcbiAgICAgICAgICBpZiAoIXRoaXMucG9ydD8ucmVhZGFibGUgfHwgIXRoaXMucG9ydD8ud3JpdGFibGUpIHtcbiAgICAgICAgICAgIG9ic2VydmVyLmVycm9yKCdQb3J0IGlzIG5vdCByZWFkYWJsZSBvciB3cml0YWJsZS4nKTtcbiAgICAgICAgICAgIHJldHVybjtcbiAgICAgICAgICB9XG4gICAgICAgICAgdGhpcy5jb25uZWN0ZWRTdWJqZWN0Lm5leHQodHJ1ZSk7XG4gICAgICAgICAgdGhpcy5hYm9ydENvbnRyb2xsZXIgPSBuZXcgQWJvcnRDb250cm9sbGVyKCk7XG4gICAgICAgICAgdGhpcy5kYXRhU3RyZWFtID0gbmV3IFdyaXRhYmxlU3RyZWFtKHRoaXMuc2luayk7XG4gICAgICAgICAgdGhpcy53cml0ZXIgPSB0aGlzLnBvcnQ/LndyaXRhYmxlLmdldFdyaXRlcigpO1xuICAgICAgICAgIG9ic2VydmVyLm5leHQoKTtcbiAgICAgICAgICByZXR1cm4gdGhpcy5wb3J0LnJlYWRhYmxlXG4gICAgICAgICAgICAucGlwZVRocm91Z2gobmV3IFRleHREZWNvZGVyU3RyZWFtKCkpXG4gICAgICAgICAgICAucGlwZVRvKHRoaXMuZGF0YVN0cmVhbSwge3NpZ25hbDogdGhpcy5hYm9ydENvbnRyb2xsZXIuc2lnbmFsfSlcbiAgICAgICAgICAgIC5jYXRjaCgoKSA9PiB0aGlzLmNsb3NlUG9ydCgpLmNhdGNoKChlcnIpID0+IG9ic2VydmVyLmVycm9yKGVycikpKTtcbiAgICAgICAgfSlcbiAgICAgICAgLmNhdGNoKChlcnIpID0+IG9ic2VydmVyLmVycm9yKGVycikpXG4gICAgICAgIC5maW5hbGx5KCgpID0+IG9ic2VydmVyLmNvbXBsZXRlKCkpO1xuICAgIH0pO1xuICB9XG5cbiAgaXNDb25uZWN0ZWQoKTogT2JzZXJ2YWJsZTxib29sZWFuPiB7XG4gICAgcmV0dXJuIHRoaXMuY29ubmVjdGVkU3ViamVjdC5hc09ic2VydmFibGUoKTtcbiAgfVxuXG4gIHJlYWQoKTogT2JzZXJ2YWJsZTxzdHJpbmc+IHtcbiAgICByZXR1cm4gdGhpcy5kYXRhU3ViamVjdC5hc09ic2VydmFibGUoKTtcbiAgfVxuXG4gIHdyaXRlKGRhdGE6IHN0cmluZyk6IE9ic2VydmFibGU8dm9pZD4ge1xuICAgIGlmICh0aGlzLndyaXRlcikge1xuICAgICAgcmV0dXJuIGZyb20odGhpcy53cml0ZXIud3JpdGUobmV3IFRleHRFbmNvZGVyKCkuZW5jb2RlKGRhdGEpKSk7XG5cbiAgICB9XG4gICAgcmV0dXJuIHRocm93RXJyb3IoKCkgPT4gbmV3IEVycm9yKCdObyB3cml0ZXIgYXZhaWxhYmxlLicpKTtcbiAgfVxuXG5cbiAgcHJpdmF0ZSB3YWl0Rm9yUmVhZGFibGVVbmxvY2socGVyaW9kOiBudW1iZXIgPSA1MCwgdGltZW91dDogbnVtYmVyID0gNTAwMCk6IFByb21pc2U8dm9pZD4ge1xuICAgIHJldHVybiBmaXJzdFZhbHVlRnJvbShpbnRlcnZhbChwZXJpb2QpLnBpcGUoXG4gICAgICBmaWx0ZXIoKCkgPT4gIXRoaXMucG9ydD8ucmVhZGFibGU/LmxvY2tlZCksXG4gICAgICBtYXAoKCkgPT4gdW5kZWZpbmVkKSxcbiAgICAgIHRha2VVbnRpbCh0aW1lcih0aW1lb3V0KSksXG4gICAgICBjYXRjaEVycm9yKCgpID0+IHRocm93RXJyb3IoKCkgPT4gbmV3IEVycm9yKCdUaW1lb3V0IHdhaXRpbmcgZm9yIHJlYWRhYmxlIHN0cmVhbSB0byB1bmxvY2snKSkpXG4gICAgKSk7XG4gIH1cblxuICBwcml2YXRlIGNsb3NlUG9ydCgpOiBQcm9taXNlPHZvaWQ+IHtcbiAgICB0aGlzLmFib3J0Q29udHJvbGxlciA9IG51bGw7XG4gICAgaWYgKHRoaXMud3JpdGVyKSB7XG4gICAgICB0aGlzLndyaXRlci5yZWxlYXNlTG9jaygpO1xuICAgICAgdGhpcy53cml0ZXIgPSBudWxsO1xuICAgIH1cbiAgICBpZiAodGhpcy5wb3J0KSB7XG4gICAgICByZXR1cm4gdGhpcy53YWl0Rm9yUmVhZGFibGVVbmxvY2soKVxuICAgICAgICAudGhlbigoKSA9PiB0aGlzLnBvcnQhLmNsb3NlKCkpXG4gICAgICAgIC50aGVuKCgpID0+IHtcbiAgICAgICAgICB0aGlzLnBvcnQgPSBudWxsO1xuICAgICAgICAgIHRoaXMuY29ubmVjdGVkU3ViamVjdC5uZXh0KGZhbHNlKTtcbiAgICAgICAgfSk7XG4gICAgfSBlbHNlIHtcbiAgICAgIHJldHVybiBQcm9taXNlLnJlc29sdmUoKTtcbiAgICB9XG4gIH1cbiAgY2xvc2UoKTogdm9pZCB7XG4gICAgaWYgKHRoaXMuYWJvcnRDb250cm9sbGVyKSB7XG4gICAgICB0aGlzLmFib3J0Q29udHJvbGxlci5hYm9ydCgpO1xuICAgIH1cbiAgfVxufVxuXG5leHBvcnQgZnVuY3Rpb24gcHJvdmlkZUFuZ3VsYXJTZXJpYWwoKTogUHJvdmlkZXJbXSB7XG4gIHJldHVybiBbXG4gICAgTmd4V2ViU2VyaWFsU2VydmljZSxcbiAgICB7XG4gICAgICBwcm92aWRlOiAnU2VyaWFsJyxcbiAgICAgIHVzZUZhY3Rvcnk6IChkb2N1bWVudDogRG9jdW1lbnQpID0+IGRvY3VtZW50LmRlZmF1bHRWaWV3Py5uYXZpZ2F0b3I/LnNlcmlhbCxcbiAgICAgIGRlcHM6IFtET0NVTUVOVF1cbiAgICB9XG4gIF07XG59XG5cbmV4cG9ydCBmdW5jdGlvbiBwcm92aWRlQW5ndWxhclNlcmlhbFRlc3QocmVzcG9uc2VGdW5jdGlvbj86IChpbnB1dDogc3RyaW5nKSA9PiBzdHJpbmcpOiBQcm92aWRlcltdIHtcbiAgcmV0dXJuIFtcbiAgICBOZ3hXZWJTZXJpYWxTZXJ2aWNlLFxuICAgIHtcbiAgICAgIHByb3ZpZGU6ICdTZXJpYWwnLFxuICAgICAgdXNlRmFjdG9yeTogKCkgPT4gbmV3IE1vY2tTZXJpYWwocmVzcG9uc2VGdW5jdGlvbiB8fCAoKGlucHV0OiBzdHJpbmcpID0+IGlucHV0KSksXG4gICAgICBkZXBzOiBbXVxuICAgIH0gYXMgRmFjdG9yeVByb3ZpZGVyXG4gIF07XG59XG4iXX0=
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Generated bundle index. Do not edit.
3
+ */
4
+ export * from './public-api';
5
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoibmd4LXdlYi1zZXJpYWwuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi9wcm9qZWN0cy9uZ3gtd2ViLXNlcmlhbC9zcmMvbmd4LXdlYi1zZXJpYWwudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUE7O0dBRUc7QUFFSCxjQUFjLGNBQWMsQ0FBQyIsInNvdXJjZXNDb250ZW50IjpbIi8qKlxuICogR2VuZXJhdGVkIGJ1bmRsZSBpbmRleC4gRG8gbm90IGVkaXQuXG4gKi9cblxuZXhwb3J0ICogZnJvbSAnLi9wdWJsaWMtYXBpJztcbiJdfQ==
@@ -0,0 +1,5 @@
1
+ /*
2
+ * Public API Surface of ngx-web-serial
3
+ */
4
+ export * from './lib/ngx-web-serial.service';
5
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicHVibGljLWFwaS5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uL3Byb2plY3RzL25neC13ZWItc2VyaWFsL3NyYy9wdWJsaWMtYXBpLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBOztHQUVHO0FBRUgsY0FBYyw4QkFBOEIsQ0FBQyIsInNvdXJjZXNDb250ZW50IjpbIi8qXG4gKiBQdWJsaWMgQVBJIFN1cmZhY2Ugb2Ygbmd4LXdlYi1zZXJpYWxcbiAqL1xuXG5leHBvcnQgKiBmcm9tICcuL2xpYi9uZ3gtd2ViLXNlcmlhbC5zZXJ2aWNlJztcbiJdfQ==
@@ -0,0 +1,165 @@
1
+ import * as i0 from '@angular/core';
2
+ import { Injectable, Inject } from '@angular/core';
3
+ import { Subject, BehaviorSubject, Observable, from, throwError, firstValueFrom, interval, filter, map, takeUntil, timer, catchError } from 'rxjs';
4
+ import { DOCUMENT } from '@angular/common';
5
+
6
+ class MockSerial {
7
+ constructor(responseFunction) {
8
+ this.readableController = null;
9
+ this.responseFunction = responseFunction;
10
+ this.readableStream = new ReadableStream({
11
+ start: (controller) => {
12
+ this.readableController = controller;
13
+ },
14
+ cancel: () => {
15
+ this.readableController = null;
16
+ }
17
+ });
18
+ }
19
+ requestPort(options) {
20
+ return Promise.resolve({
21
+ open: () => Promise.resolve(),
22
+ close: () => this.readableStream.cancel(),
23
+ readable: this.readableStream,
24
+ writable: new WritableStream({
25
+ write: (chunk) => {
26
+ const input = new TextDecoder().decode(chunk);
27
+ const response = this.responseFunction(input);
28
+ if (this.readableController) {
29
+ this.readableController.enqueue(new TextEncoder().encode(response));
30
+ }
31
+ }
32
+ })
33
+ });
34
+ }
35
+ }
36
+
37
+ class NgxWebSerialService {
38
+ constructor(serial, ngZone) {
39
+ this.serial = serial;
40
+ this.ngZone = ngZone;
41
+ this.port = null;
42
+ this.abortController = null;
43
+ this.dataStream = null;
44
+ this.dataSubject = new Subject();
45
+ this.writer = null;
46
+ this.connectedSubject = new BehaviorSubject(false);
47
+ this.sink = {
48
+ write: (chunk) => {
49
+ this.ngZone.run(() => {
50
+ this.dataSubject.next(chunk);
51
+ });
52
+ }
53
+ };
54
+ }
55
+ /**
56
+ * Establishes a connection to a serial port using the Web Serial API.
57
+ */
58
+ open(serialOptions = { baudRate: 9600 }, options) {
59
+ return new Observable((observer) => {
60
+ if (!this.serial) {
61
+ observer.error('Web serial not supported.');
62
+ return;
63
+ }
64
+ this.serial.requestPort(options)
65
+ .then((port) => {
66
+ this.port = port;
67
+ return this.port.open(serialOptions);
68
+ })
69
+ .then(() => {
70
+ if (!this.port?.readable || !this.port?.writable) {
71
+ observer.error('Port is not readable or writable.');
72
+ return;
73
+ }
74
+ this.connectedSubject.next(true);
75
+ this.abortController = new AbortController();
76
+ this.dataStream = new WritableStream(this.sink);
77
+ this.writer = this.port?.writable.getWriter();
78
+ observer.next();
79
+ return this.port.readable
80
+ .pipeThrough(new TextDecoderStream())
81
+ .pipeTo(this.dataStream, { signal: this.abortController.signal })
82
+ .catch(() => this.closePort().catch((err) => observer.error(err)));
83
+ })
84
+ .catch((err) => observer.error(err))
85
+ .finally(() => observer.complete());
86
+ });
87
+ }
88
+ isConnected() {
89
+ return this.connectedSubject.asObservable();
90
+ }
91
+ read() {
92
+ return this.dataSubject.asObservable();
93
+ }
94
+ write(data) {
95
+ if (this.writer) {
96
+ return from(this.writer.write(new TextEncoder().encode(data)));
97
+ }
98
+ return throwError(() => new Error('No writer available.'));
99
+ }
100
+ waitForReadableUnlock(period = 50, timeout = 5000) {
101
+ return firstValueFrom(interval(period).pipe(filter(() => !this.port?.readable?.locked), map(() => undefined), takeUntil(timer(timeout)), catchError(() => throwError(() => new Error('Timeout waiting for readable stream to unlock')))));
102
+ }
103
+ closePort() {
104
+ this.abortController = null;
105
+ if (this.writer) {
106
+ this.writer.releaseLock();
107
+ this.writer = null;
108
+ }
109
+ if (this.port) {
110
+ return this.waitForReadableUnlock()
111
+ .then(() => this.port.close())
112
+ .then(() => {
113
+ this.port = null;
114
+ this.connectedSubject.next(false);
115
+ });
116
+ }
117
+ else {
118
+ return Promise.resolve();
119
+ }
120
+ }
121
+ close() {
122
+ if (this.abortController) {
123
+ this.abortController.abort();
124
+ }
125
+ }
126
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: NgxWebSerialService, deps: [{ token: 'Serial' }, { token: i0.NgZone }], target: i0.ɵɵFactoryTarget.Injectable }); }
127
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: NgxWebSerialService }); }
128
+ }
129
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: NgxWebSerialService, decorators: [{
130
+ type: Injectable
131
+ }], ctorParameters: () => [{ type: undefined, decorators: [{
132
+ type: Inject,
133
+ args: ['Serial']
134
+ }] }, { type: i0.NgZone }] });
135
+ function provideAngularSerial() {
136
+ return [
137
+ NgxWebSerialService,
138
+ {
139
+ provide: 'Serial',
140
+ useFactory: (document) => document.defaultView?.navigator?.serial,
141
+ deps: [DOCUMENT]
142
+ }
143
+ ];
144
+ }
145
+ function provideAngularSerialTest(responseFunction) {
146
+ return [
147
+ NgxWebSerialService,
148
+ {
149
+ provide: 'Serial',
150
+ useFactory: () => new MockSerial(responseFunction || ((input) => input)),
151
+ deps: []
152
+ }
153
+ ];
154
+ }
155
+
156
+ /*
157
+ * Public API Surface of ngx-web-serial
158
+ */
159
+
160
+ /**
161
+ * Generated bundle index. Do not edit.
162
+ */
163
+
164
+ export { NgxWebSerialService, provideAngularSerial, provideAngularSerialTest };
165
+ //# sourceMappingURL=ngx-web-serial.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ngx-web-serial.mjs","sources":["../../../projects/ngx-web-serial/src/lib/mock-serial.ts","../../../projects/ngx-web-serial/src/lib/ngx-web-serial.service.ts","../../../projects/ngx-web-serial/src/public-api.ts","../../../projects/ngx-web-serial/src/ngx-web-serial.ts"],"sourcesContent":["export class MockSerial {\n private readableController: ReadableStreamDefaultController<Uint8Array> | null = null;\n readonly responseFunction: (input: string) => string;\n readonly readableStream: ReadableStream<Uint8Array>;\n constructor(responseFunction: (input: string) => string) {\n this.responseFunction = responseFunction;\n this.readableStream = new ReadableStream({\n start: (controller) => {\n this.readableController = controller;\n },\n cancel: () => {\n this.readableController = null;\n }\n });\n }\n\n requestPort(options?: SerialPortRequestOptions): Promise<SerialPort> {\n return Promise.resolve({\n open: () => Promise.resolve(),\n close: () => this.readableStream.cancel(),\n readable: this.readableStream,\n writable: new WritableStream({\n write: (chunk) => {\n const input = new TextDecoder().decode(chunk);\n const response = this.responseFunction(input);\n if (this.readableController) {\n this.readableController.enqueue(new TextEncoder().encode(response));\n }\n }\n })\n } as any as SerialPort);\n }\n}\n","import { FactoryProvider, Inject, Injectable, NgZone, Provider } from '@angular/core';\nimport {\n BehaviorSubject,\n catchError,\n filter,\n firstValueFrom,\n from,\n interval,\n map,\n Observable,\n Subject,\n Subscriber,\n takeUntil,\n throwError,\n timer\n} from 'rxjs';\nimport { DOCUMENT } from '@angular/common';\nimport { MockSerial } from './mock-serial';\n\n@Injectable()\nexport class NgxWebSerialService {\n\n private port: SerialPort | null = null;\n private abortController: AbortController | null = null;\n private dataStream: WritableStream | null = null;\n private dataSubject: Subject<string> = new Subject<string>();\n private writer: WritableStreamDefaultWriter<Uint8Array> | null = null;\n private connectedSubject: BehaviorSubject<boolean> = new BehaviorSubject<boolean>(false);\n\n private readonly sink: UnderlyingSink = {\n write: (chunk: any) => {\n this.ngZone.run(() => {\n this.dataSubject.next(chunk);\n });\n }\n }\n\n constructor(\n @Inject('Serial') readonly serial: Serial | undefined,\n private ngZone: NgZone\n ) {\n }\n\n /**\n * Establishes a connection to a serial port using the Web Serial API.\n */\n open(serialOptions: SerialOptions = { baudRate: 9600 }, options?: SerialPortRequestOptions): Observable<void> {\n return new Observable<void>((observer: Subscriber<void>) => {\n if (!this.serial) {\n observer.error('Web serial not supported.');\n return;\n }\n this.serial.requestPort(options)\n .then((port: SerialPort) => {\n this.port = port;\n return this.port.open(serialOptions);\n })\n .then(() => {\n if (!this.port?.readable || !this.port?.writable) {\n observer.error('Port is not readable or writable.');\n return;\n }\n this.connectedSubject.next(true);\n this.abortController = new AbortController();\n this.dataStream = new WritableStream(this.sink);\n this.writer = this.port?.writable.getWriter();\n observer.next();\n return this.port.readable\n .pipeThrough(new TextDecoderStream())\n .pipeTo(this.dataStream, {signal: this.abortController.signal})\n .catch(() => this.closePort().catch((err) => observer.error(err)));\n })\n .catch((err) => observer.error(err))\n .finally(() => observer.complete());\n });\n }\n\n isConnected(): Observable<boolean> {\n return this.connectedSubject.asObservable();\n }\n\n read(): Observable<string> {\n return this.dataSubject.asObservable();\n }\n\n write(data: string): Observable<void> {\n if (this.writer) {\n return from(this.writer.write(new TextEncoder().encode(data)));\n\n }\n return throwError(() => new Error('No writer available.'));\n }\n\n\n private waitForReadableUnlock(period: number = 50, timeout: number = 5000): Promise<void> {\n return firstValueFrom(interval(period).pipe(\n filter(() => !this.port?.readable?.locked),\n map(() => undefined),\n takeUntil(timer(timeout)),\n catchError(() => throwError(() => new Error('Timeout waiting for readable stream to unlock')))\n ));\n }\n\n private closePort(): Promise<void> {\n this.abortController = null;\n if (this.writer) {\n this.writer.releaseLock();\n this.writer = null;\n }\n if (this.port) {\n return this.waitForReadableUnlock()\n .then(() => this.port!.close())\n .then(() => {\n this.port = null;\n this.connectedSubject.next(false);\n });\n } else {\n return Promise.resolve();\n }\n }\n close(): void {\n if (this.abortController) {\n this.abortController.abort();\n }\n }\n}\n\nexport function provideAngularSerial(): Provider[] {\n return [\n NgxWebSerialService,\n {\n provide: 'Serial',\n useFactory: (document: Document) => document.defaultView?.navigator?.serial,\n deps: [DOCUMENT]\n }\n ];\n}\n\nexport function provideAngularSerialTest(responseFunction?: (input: string) => string): Provider[] {\n return [\n NgxWebSerialService,\n {\n provide: 'Serial',\n useFactory: () => new MockSerial(responseFunction || ((input: string) => input)),\n deps: []\n } as FactoryProvider\n ];\n}\n","/*\n * Public API Surface of ngx-web-serial\n */\n\nexport * from './lib/ngx-web-serial.service';\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":[],"mappings":"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"}
package/index.d.ts ADDED
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Generated bundle index. Do not edit.
3
+ */
4
+ /// <amd-module name="ngx-web-serial" />
5
+ export * from './public-api';
@@ -0,0 +1,8 @@
1
+ /// <reference types="w3c-web-serial" />
2
+ export declare class MockSerial {
3
+ private readableController;
4
+ readonly responseFunction: (input: string) => string;
5
+ readonly readableStream: ReadableStream<Uint8Array>;
6
+ constructor(responseFunction: (input: string) => string);
7
+ requestPort(options?: SerialPortRequestOptions): Promise<SerialPort>;
8
+ }
@@ -0,0 +1,30 @@
1
+ /// <reference types="w3c-web-serial" />
2
+ import { NgZone, Provider } from '@angular/core';
3
+ import { Observable } from 'rxjs';
4
+ import * as i0 from "@angular/core";
5
+ export declare class NgxWebSerialService {
6
+ readonly serial: Serial | undefined;
7
+ private ngZone;
8
+ private port;
9
+ private abortController;
10
+ private dataStream;
11
+ private dataSubject;
12
+ private writer;
13
+ private connectedSubject;
14
+ private readonly sink;
15
+ constructor(serial: Serial | undefined, ngZone: NgZone);
16
+ /**
17
+ * Establishes a connection to a serial port using the Web Serial API.
18
+ */
19
+ open(serialOptions?: SerialOptions, options?: SerialPortRequestOptions): Observable<void>;
20
+ isConnected(): Observable<boolean>;
21
+ read(): Observable<string>;
22
+ write(data: string): Observable<void>;
23
+ private waitForReadableUnlock;
24
+ private closePort;
25
+ close(): void;
26
+ static ɵfac: i0.ɵɵFactoryDeclaration<NgxWebSerialService, never>;
27
+ static ɵprov: i0.ɵɵInjectableDeclaration<NgxWebSerialService>;
28
+ }
29
+ export declare function provideAngularSerial(): Provider[];
30
+ export declare function provideAngularSerialTest(responseFunction?: (input: string) => string): Provider[];
package/package.json ADDED
@@ -0,0 +1,25 @@
1
+ {
2
+ "name": "ngx-web-serial",
3
+ "version": "0.0.1",
4
+ "peerDependencies": {
5
+ "@angular/common": "^17.3.0",
6
+ "@angular/core": "^17.3.0"
7
+ },
8
+ "dependencies": {
9
+ "tslib": "^2.3.0"
10
+ },
11
+ "sideEffects": false,
12
+ "module": "fesm2022/ngx-web-serial.mjs",
13
+ "typings": "index.d.ts",
14
+ "exports": {
15
+ "./package.json": {
16
+ "default": "./package.json"
17
+ },
18
+ ".": {
19
+ "types": "./index.d.ts",
20
+ "esm2022": "./esm2022/ngx-web-serial.mjs",
21
+ "esm": "./esm2022/ngx-web-serial.mjs",
22
+ "default": "./fesm2022/ngx-web-serial.mjs"
23
+ }
24
+ }
25
+ }
@@ -0,0 +1 @@
1
+ export * from './lib/ngx-web-serial.service';