ngx-web-serial 0.0.1

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 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';