1*61c4878aSAndroid Build Coastguard Worker// Copyright 2022 The Pigweed Authors 2*61c4878aSAndroid Build Coastguard Worker// 3*61c4878aSAndroid Build Coastguard Worker// Licensed under the Apache License, Version 2.0 (the "License"); you may not 4*61c4878aSAndroid Build Coastguard Worker// use this file except in compliance with the License. You may obtain a copy of 5*61c4878aSAndroid Build Coastguard Worker// the License at 6*61c4878aSAndroid Build Coastguard Worker// 7*61c4878aSAndroid Build Coastguard Worker// https://www.apache.org/licenses/LICENSE-2.0 8*61c4878aSAndroid Build Coastguard Worker// 9*61c4878aSAndroid Build Coastguard Worker// Unless required by applicable law or agreed to in writing, software 10*61c4878aSAndroid Build Coastguard Worker// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11*61c4878aSAndroid Build Coastguard Worker// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12*61c4878aSAndroid Build Coastguard Worker// License for the specific language governing permissions and limitations under 13*61c4878aSAndroid Build Coastguard Worker// the License. 14*61c4878aSAndroid Build Coastguard Worker 15*61c4878aSAndroid Build Coastguard Worker/* eslint-env browser */ 16*61c4878aSAndroid Build Coastguard Workerimport { Subject } from 'rxjs'; 17*61c4878aSAndroid Build Coastguard Workerimport type { 18*61c4878aSAndroid Build Coastguard Worker SerialConnectionEvent, 19*61c4878aSAndroid Build Coastguard Worker SerialPort, 20*61c4878aSAndroid Build Coastguard Worker Serial, 21*61c4878aSAndroid Build Coastguard Worker SerialPortRequestOptions, 22*61c4878aSAndroid Build Coastguard Worker SerialOptions, 23*61c4878aSAndroid Build Coastguard Worker} from 'pigweedjs/types/serial'; 24*61c4878aSAndroid Build Coastguard Worker/** 25*61c4878aSAndroid Build Coastguard Worker * AsyncQueue is a queue that allows values to be dequeued 26*61c4878aSAndroid Build Coastguard Worker * before they are enqueued, returning a promise that resolves 27*61c4878aSAndroid Build Coastguard Worker * once the value is available. 28*61c4878aSAndroid Build Coastguard Worker */ 29*61c4878aSAndroid Build Coastguard Workerclass AsyncQueue<T> { 30*61c4878aSAndroid Build Coastguard Worker private queue: T[] = []; 31*61c4878aSAndroid Build Coastguard Worker private requestQueue: Array<(val: T) => unknown> = []; 32*61c4878aSAndroid Build Coastguard Worker 33*61c4878aSAndroid Build Coastguard Worker /** 34*61c4878aSAndroid Build Coastguard Worker * Enqueue val into the queue. 35*61c4878aSAndroid Build Coastguard Worker * @param {T} val 36*61c4878aSAndroid Build Coastguard Worker */ 37*61c4878aSAndroid Build Coastguard Worker enqueue(val: T) { 38*61c4878aSAndroid Build Coastguard Worker const callback = this.requestQueue.shift(); 39*61c4878aSAndroid Build Coastguard Worker if (callback) { 40*61c4878aSAndroid Build Coastguard Worker callback(val); 41*61c4878aSAndroid Build Coastguard Worker } else { 42*61c4878aSAndroid Build Coastguard Worker this.queue.push(val); 43*61c4878aSAndroid Build Coastguard Worker } 44*61c4878aSAndroid Build Coastguard Worker } 45*61c4878aSAndroid Build Coastguard Worker 46*61c4878aSAndroid Build Coastguard Worker /** 47*61c4878aSAndroid Build Coastguard Worker * Dequeue a value from the queue, returning a promise 48*61c4878aSAndroid Build Coastguard Worker * if the queue is empty. 49*61c4878aSAndroid Build Coastguard Worker */ 50*61c4878aSAndroid Build Coastguard Worker async dequeue(): Promise<T> { 51*61c4878aSAndroid Build Coastguard Worker const val = this.queue.shift(); 52*61c4878aSAndroid Build Coastguard Worker if (val !== undefined) { 53*61c4878aSAndroid Build Coastguard Worker return val; 54*61c4878aSAndroid Build Coastguard Worker } else { 55*61c4878aSAndroid Build Coastguard Worker const queuePromise = new Promise<T>((resolve) => { 56*61c4878aSAndroid Build Coastguard Worker this.requestQueue.push(resolve); 57*61c4878aSAndroid Build Coastguard Worker }); 58*61c4878aSAndroid Build Coastguard Worker return queuePromise; 59*61c4878aSAndroid Build Coastguard Worker } 60*61c4878aSAndroid Build Coastguard Worker } 61*61c4878aSAndroid Build Coastguard Worker} 62*61c4878aSAndroid Build Coastguard Worker 63*61c4878aSAndroid Build Coastguard Worker/** 64*61c4878aSAndroid Build Coastguard Worker * SerialPortMock is a mock for Chrome's upcoming SerialPort interface. 65*61c4878aSAndroid Build Coastguard Worker * Since pw_web only depends on a subset of the interface, this mock 66*61c4878aSAndroid Build Coastguard Worker * only implements that subset. 67*61c4878aSAndroid Build Coastguard Worker */ 68*61c4878aSAndroid Build Coastguard Workerclass SerialPortMock implements SerialPort { 69*61c4878aSAndroid Build Coastguard Worker private deviceData = new AsyncQueue<{ 70*61c4878aSAndroid Build Coastguard Worker data?: Uint8Array; 71*61c4878aSAndroid Build Coastguard Worker done?: boolean; 72*61c4878aSAndroid Build Coastguard Worker error?: Error; 73*61c4878aSAndroid Build Coastguard Worker }>(); 74*61c4878aSAndroid Build Coastguard Worker 75*61c4878aSAndroid Build Coastguard Worker /** 76*61c4878aSAndroid Build Coastguard Worker * Simulate the device sending data to the browser. 77*61c4878aSAndroid Build Coastguard Worker * @param {Uint8Array} data 78*61c4878aSAndroid Build Coastguard Worker */ 79*61c4878aSAndroid Build Coastguard Worker dataFromDevice(data: Uint8Array) { 80*61c4878aSAndroid Build Coastguard Worker this.deviceData.enqueue({ data }); 81*61c4878aSAndroid Build Coastguard Worker } 82*61c4878aSAndroid Build Coastguard Worker 83*61c4878aSAndroid Build Coastguard Worker /** 84*61c4878aSAndroid Build Coastguard Worker * Simulate the device closing the connection with the browser. 85*61c4878aSAndroid Build Coastguard Worker */ 86*61c4878aSAndroid Build Coastguard Worker closeFromDevice() { 87*61c4878aSAndroid Build Coastguard Worker this.deviceData.enqueue({ done: true }); 88*61c4878aSAndroid Build Coastguard Worker } 89*61c4878aSAndroid Build Coastguard Worker 90*61c4878aSAndroid Build Coastguard Worker /** 91*61c4878aSAndroid Build Coastguard Worker * Simulate an error in the device's read stream. 92*61c4878aSAndroid Build Coastguard Worker * @param {Error} error 93*61c4878aSAndroid Build Coastguard Worker */ 94*61c4878aSAndroid Build Coastguard Worker errorFromDevice(error: Error) { 95*61c4878aSAndroid Build Coastguard Worker this.deviceData.enqueue({ error }); 96*61c4878aSAndroid Build Coastguard Worker } 97*61c4878aSAndroid Build Coastguard Worker 98*61c4878aSAndroid Build Coastguard Worker /** 99*61c4878aSAndroid Build Coastguard Worker * An rxjs subject tracking data sent to the (fake) device. 100*61c4878aSAndroid Build Coastguard Worker */ 101*61c4878aSAndroid Build Coastguard Worker dataToDevice = new Subject<Uint8Array>(); 102*61c4878aSAndroid Build Coastguard Worker 103*61c4878aSAndroid Build Coastguard Worker /** 104*61c4878aSAndroid Build Coastguard Worker * The ReadableStream of bytes from the device. 105*61c4878aSAndroid Build Coastguard Worker */ 106*61c4878aSAndroid Build Coastguard Worker readable = new ReadableStream<Uint8Array>({ 107*61c4878aSAndroid Build Coastguard Worker pull: async (controller) => { 108*61c4878aSAndroid Build Coastguard Worker const { data, done, error } = await this.deviceData.dequeue(); 109*61c4878aSAndroid Build Coastguard Worker if (done) { 110*61c4878aSAndroid Build Coastguard Worker controller.close(); 111*61c4878aSAndroid Build Coastguard Worker return; 112*61c4878aSAndroid Build Coastguard Worker } 113*61c4878aSAndroid Build Coastguard Worker if (error) { 114*61c4878aSAndroid Build Coastguard Worker throw error; 115*61c4878aSAndroid Build Coastguard Worker } 116*61c4878aSAndroid Build Coastguard Worker if (data) { 117*61c4878aSAndroid Build Coastguard Worker controller.enqueue(data); 118*61c4878aSAndroid Build Coastguard Worker } 119*61c4878aSAndroid Build Coastguard Worker }, 120*61c4878aSAndroid Build Coastguard Worker }); 121*61c4878aSAndroid Build Coastguard Worker 122*61c4878aSAndroid Build Coastguard Worker /** 123*61c4878aSAndroid Build Coastguard Worker * The WritableStream of bytes to the device. 124*61c4878aSAndroid Build Coastguard Worker */ 125*61c4878aSAndroid Build Coastguard Worker writable = new WritableStream<Uint8Array>({ 126*61c4878aSAndroid Build Coastguard Worker write: (chunk) => { 127*61c4878aSAndroid Build Coastguard Worker this.dataToDevice.next(chunk); 128*61c4878aSAndroid Build Coastguard Worker }, 129*61c4878aSAndroid Build Coastguard Worker }); 130*61c4878aSAndroid Build Coastguard Worker 131*61c4878aSAndroid Build Coastguard Worker /** 132*61c4878aSAndroid Build Coastguard Worker * A spy for opening the serial port. 133*61c4878aSAndroid Build Coastguard Worker */ 134*61c4878aSAndroid Build Coastguard Worker open = jest.fn(async (options?: SerialOptions) => { 135*61c4878aSAndroid Build Coastguard Worker // Do nothing. 136*61c4878aSAndroid Build Coastguard Worker }); 137*61c4878aSAndroid Build Coastguard Worker 138*61c4878aSAndroid Build Coastguard Worker /** 139*61c4878aSAndroid Build Coastguard Worker * A spy for closing the serial port. 140*61c4878aSAndroid Build Coastguard Worker */ 141*61c4878aSAndroid Build Coastguard Worker close = jest.fn(() => { 142*61c4878aSAndroid Build Coastguard Worker // Do nothing. 143*61c4878aSAndroid Build Coastguard Worker }); 144*61c4878aSAndroid Build Coastguard Worker} 145*61c4878aSAndroid Build Coastguard Worker 146*61c4878aSAndroid Build Coastguard Workerexport class SerialMock implements Serial { 147*61c4878aSAndroid Build Coastguard Worker serialPort = new SerialPortMock(); 148*61c4878aSAndroid Build Coastguard Worker dataToDevice = this.serialPort.dataToDevice; 149*61c4878aSAndroid Build Coastguard Worker dataFromDevice = (data: Uint8Array) => { 150*61c4878aSAndroid Build Coastguard Worker this.serialPort.dataFromDevice(data); 151*61c4878aSAndroid Build Coastguard Worker }; 152*61c4878aSAndroid Build Coastguard Worker closeFromDevice = () => { 153*61c4878aSAndroid Build Coastguard Worker this.serialPort.closeFromDevice(); 154*61c4878aSAndroid Build Coastguard Worker }; 155*61c4878aSAndroid Build Coastguard Worker errorFromDevice = (error: Error) => { 156*61c4878aSAndroid Build Coastguard Worker this.serialPort.errorFromDevice(error); 157*61c4878aSAndroid Build Coastguard Worker }; 158*61c4878aSAndroid Build Coastguard Worker 159*61c4878aSAndroid Build Coastguard Worker /** 160*61c4878aSAndroid Build Coastguard Worker * Request the port from the browser. 161*61c4878aSAndroid Build Coastguard Worker */ 162*61c4878aSAndroid Build Coastguard Worker async requestPort(options?: SerialPortRequestOptions) { 163*61c4878aSAndroid Build Coastguard Worker return this.serialPort; 164*61c4878aSAndroid Build Coastguard Worker } 165*61c4878aSAndroid Build Coastguard Worker 166*61c4878aSAndroid Build Coastguard Worker // The rest of the methods are unimplemented 167*61c4878aSAndroid Build Coastguard Worker // and only exist to ensure SerialMock implements Serial 168*61c4878aSAndroid Build Coastguard Worker 169*61c4878aSAndroid Build Coastguard Worker onconnect(): ((this: this, ev: SerialConnectionEvent) => any) | null { 170*61c4878aSAndroid Build Coastguard Worker throw new Error('Method not implemented.'); 171*61c4878aSAndroid Build Coastguard Worker } 172*61c4878aSAndroid Build Coastguard Worker 173*61c4878aSAndroid Build Coastguard Worker ondisconnect(): ((this: this, ev: SerialConnectionEvent) => any) | null { 174*61c4878aSAndroid Build Coastguard Worker throw new Error('Method not implemented.'); 175*61c4878aSAndroid Build Coastguard Worker } 176*61c4878aSAndroid Build Coastguard Worker 177*61c4878aSAndroid Build Coastguard Worker getPorts(): Promise<SerialPort[]> { 178*61c4878aSAndroid Build Coastguard Worker throw new Error('Method not implemented.'); 179*61c4878aSAndroid Build Coastguard Worker } 180*61c4878aSAndroid Build Coastguard Worker 181*61c4878aSAndroid Build Coastguard Worker addEventListener( 182*61c4878aSAndroid Build Coastguard Worker type: 'connect' | 'disconnect', 183*61c4878aSAndroid Build Coastguard Worker listener: (this: this, ev: SerialConnectionEvent) => any, 184*61c4878aSAndroid Build Coastguard Worker useCapture?: boolean, 185*61c4878aSAndroid Build Coastguard Worker ): void; 186*61c4878aSAndroid Build Coastguard Worker 187*61c4878aSAndroid Build Coastguard Worker addEventListener( 188*61c4878aSAndroid Build Coastguard Worker type: string, 189*61c4878aSAndroid Build Coastguard Worker listener: EventListener | EventListenerObject | null, 190*61c4878aSAndroid Build Coastguard Worker options?: boolean | AddEventListenerOptions, 191*61c4878aSAndroid Build Coastguard Worker ): void; 192*61c4878aSAndroid Build Coastguard Worker 193*61c4878aSAndroid Build Coastguard Worker addEventListener(type: any, listener: any, options?: any) { 194*61c4878aSAndroid Build Coastguard Worker console.info('Silently skipping event listeners in mock mode.'); 195*61c4878aSAndroid Build Coastguard Worker } 196*61c4878aSAndroid Build Coastguard Worker 197*61c4878aSAndroid Build Coastguard Worker removeEventListener( 198*61c4878aSAndroid Build Coastguard Worker type: 'connect' | 'disconnect', 199*61c4878aSAndroid Build Coastguard Worker callback: (this: this, ev: SerialConnectionEvent) => any, 200*61c4878aSAndroid Build Coastguard Worker useCapture?: boolean, 201*61c4878aSAndroid Build Coastguard Worker ): void; 202*61c4878aSAndroid Build Coastguard Worker 203*61c4878aSAndroid Build Coastguard Worker removeEventListener( 204*61c4878aSAndroid Build Coastguard Worker type: string, 205*61c4878aSAndroid Build Coastguard Worker callback: EventListener | EventListenerObject | null, 206*61c4878aSAndroid Build Coastguard Worker options?: boolean | EventListenerOptions, 207*61c4878aSAndroid Build Coastguard Worker ): void; 208*61c4878aSAndroid Build Coastguard Worker 209*61c4878aSAndroid Build Coastguard Worker removeEventListener(type: any, callback: any, options?: any) { 210*61c4878aSAndroid Build Coastguard Worker throw new Error('Method not implemented.'); 211*61c4878aSAndroid Build Coastguard Worker } 212*61c4878aSAndroid Build Coastguard Worker 213*61c4878aSAndroid Build Coastguard Worker dispatchEvent(event: Event): boolean { 214*61c4878aSAndroid Build Coastguard Worker throw new Error('Method not implemented.'); 215*61c4878aSAndroid Build Coastguard Worker } 216*61c4878aSAndroid Build Coastguard Worker} 217