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 Workerimport { setPathOnObject } from './object_set'; 16*61c4878aSAndroid Build Coastguard Workerimport { Decoder, Encoder } from 'pigweedjs/pw_hdlc'; 17*61c4878aSAndroid Build Coastguard Workerimport { 18*61c4878aSAndroid Build Coastguard Worker Client, 19*61c4878aSAndroid Build Coastguard Worker Channel, 20*61c4878aSAndroid Build Coastguard Worker ServiceClient, 21*61c4878aSAndroid Build Coastguard Worker UnaryMethodStub, 22*61c4878aSAndroid Build Coastguard Worker MethodStub, 23*61c4878aSAndroid Build Coastguard Worker ServerStreamingMethodStub, 24*61c4878aSAndroid Build Coastguard Worker Call, 25*61c4878aSAndroid Build Coastguard Worker} from 'pigweedjs/pw_rpc'; 26*61c4878aSAndroid Build Coastguard Workerimport { WebSerialTransport } from '../transport/web_serial_transport'; 27*61c4878aSAndroid Build Coastguard Workerimport { ProtoCollection } from 'pigweedjs/pw_protobuf_compiler'; 28*61c4878aSAndroid Build Coastguard Workerimport { Message } from 'google-protobuf'; 29*61c4878aSAndroid Build Coastguard Worker 30*61c4878aSAndroid Build Coastguard Workerfunction protoFieldToMethodName(fieldName: string) { 31*61c4878aSAndroid Build Coastguard Worker return fieldName.split('_').map(titleCase).join(''); 32*61c4878aSAndroid Build Coastguard Worker} 33*61c4878aSAndroid Build Coastguard Workerfunction titleCase(title: string) { 34*61c4878aSAndroid Build Coastguard Worker return title.charAt(0).toUpperCase() + title.slice(1); 35*61c4878aSAndroid Build Coastguard Worker} 36*61c4878aSAndroid Build Coastguard Worker 37*61c4878aSAndroid Build Coastguard Workerinterface RPCUnderlyingSource extends UnderlyingSource { 38*61c4878aSAndroid Build Coastguard Worker call?: Call; 39*61c4878aSAndroid Build Coastguard Worker} 40*61c4878aSAndroid Build Coastguard Worker 41*61c4878aSAndroid Build Coastguard Workerexport class RPCReadableStream<R = any> extends ReadableStream<R> { 42*61c4878aSAndroid Build Coastguard Worker constructor(private underlyingSource: RPCUnderlyingSource) { 43*61c4878aSAndroid Build Coastguard Worker super(underlyingSource); 44*61c4878aSAndroid Build Coastguard Worker } 45*61c4878aSAndroid Build Coastguard Worker 46*61c4878aSAndroid Build Coastguard Worker get call(): Call { 47*61c4878aSAndroid Build Coastguard Worker return this.underlyingSource.call!; 48*61c4878aSAndroid Build Coastguard Worker } 49*61c4878aSAndroid Build Coastguard Worker 50*61c4878aSAndroid Build Coastguard Worker override cancel(): Promise<void> { 51*61c4878aSAndroid Build Coastguard Worker this.call.cancel(); 52*61c4878aSAndroid Build Coastguard Worker return Promise.resolve(); 53*61c4878aSAndroid Build Coastguard Worker } 54*61c4878aSAndroid Build Coastguard Worker} 55*61c4878aSAndroid Build Coastguard Worker 56*61c4878aSAndroid Build Coastguard Workerexport class Device { 57*61c4878aSAndroid Build Coastguard Worker private protoCollection: ProtoCollection; 58*61c4878aSAndroid Build Coastguard Worker private transport: WebSerialTransport; 59*61c4878aSAndroid Build Coastguard Worker private decoder: Decoder; 60*61c4878aSAndroid Build Coastguard Worker private encoder: Encoder; 61*61c4878aSAndroid Build Coastguard Worker private rpcAddress: number; 62*61c4878aSAndroid Build Coastguard Worker private nameToMethodArgumentsMap: any; 63*61c4878aSAndroid Build Coastguard Worker client: Client; 64*61c4878aSAndroid Build Coastguard Worker rpcs: any; 65*61c4878aSAndroid Build Coastguard Worker 66*61c4878aSAndroid Build Coastguard Worker constructor( 67*61c4878aSAndroid Build Coastguard Worker protoCollection: ProtoCollection, 68*61c4878aSAndroid Build Coastguard Worker transport: WebSerialTransport = new WebSerialTransport(), 69*61c4878aSAndroid Build Coastguard Worker channel = 1, 70*61c4878aSAndroid Build Coastguard Worker rpcAddress = 82, 71*61c4878aSAndroid Build Coastguard Worker ) { 72*61c4878aSAndroid Build Coastguard Worker this.transport = transport; 73*61c4878aSAndroid Build Coastguard Worker this.rpcAddress = rpcAddress; 74*61c4878aSAndroid Build Coastguard Worker this.protoCollection = protoCollection; 75*61c4878aSAndroid Build Coastguard Worker this.decoder = new Decoder(); 76*61c4878aSAndroid Build Coastguard Worker this.encoder = new Encoder(); 77*61c4878aSAndroid Build Coastguard Worker this.nameToMethodArgumentsMap = {}; 78*61c4878aSAndroid Build Coastguard Worker const channels = [ 79*61c4878aSAndroid Build Coastguard Worker new Channel(channel, (bytes) => { 80*61c4878aSAndroid Build Coastguard Worker const hdlcBytes = this.encoder.uiFrame(this.rpcAddress, bytes); 81*61c4878aSAndroid Build Coastguard Worker this.transport.sendChunk(hdlcBytes); 82*61c4878aSAndroid Build Coastguard Worker }), 83*61c4878aSAndroid Build Coastguard Worker ]; 84*61c4878aSAndroid Build Coastguard Worker this.client = Client.fromProtoSet(channels, this.protoCollection); 85*61c4878aSAndroid Build Coastguard Worker 86*61c4878aSAndroid Build Coastguard Worker this.setupRpcs(); 87*61c4878aSAndroid Build Coastguard Worker } 88*61c4878aSAndroid Build Coastguard Worker 89*61c4878aSAndroid Build Coastguard Worker async connect() { 90*61c4878aSAndroid Build Coastguard Worker await this.transport.connect(); 91*61c4878aSAndroid Build Coastguard Worker this.transport.chunks.subscribe((item) => { 92*61c4878aSAndroid Build Coastguard Worker const decoded = this.decoder.process(item); 93*61c4878aSAndroid Build Coastguard Worker for (const frame of decoded) { 94*61c4878aSAndroid Build Coastguard Worker if (frame.address === this.rpcAddress) { 95*61c4878aSAndroid Build Coastguard Worker this.client.processPacket(frame.data); 96*61c4878aSAndroid Build Coastguard Worker } 97*61c4878aSAndroid Build Coastguard Worker } 98*61c4878aSAndroid Build Coastguard Worker }); 99*61c4878aSAndroid Build Coastguard Worker } 100*61c4878aSAndroid Build Coastguard Worker 101*61c4878aSAndroid Build Coastguard Worker getMethodArguments(fullPath: string) { 102*61c4878aSAndroid Build Coastguard Worker return this.nameToMethodArgumentsMap[fullPath]; 103*61c4878aSAndroid Build Coastguard Worker } 104*61c4878aSAndroid Build Coastguard Worker 105*61c4878aSAndroid Build Coastguard Worker private setupRpcs() { 106*61c4878aSAndroid Build Coastguard Worker const rpcMap = {}; 107*61c4878aSAndroid Build Coastguard Worker const channel = this.client.channel()!; 108*61c4878aSAndroid Build Coastguard Worker const servicesKeys = Array.from(channel.services.keys()); 109*61c4878aSAndroid Build Coastguard Worker servicesKeys.forEach((serviceKey) => { 110*61c4878aSAndroid Build Coastguard Worker setPathOnObject( 111*61c4878aSAndroid Build Coastguard Worker rpcMap, 112*61c4878aSAndroid Build Coastguard Worker serviceKey, 113*61c4878aSAndroid Build Coastguard Worker this.mapServiceMethods(channel.services.get(serviceKey)!), 114*61c4878aSAndroid Build Coastguard Worker ); 115*61c4878aSAndroid Build Coastguard Worker }); 116*61c4878aSAndroid Build Coastguard Worker this.rpcs = rpcMap; 117*61c4878aSAndroid Build Coastguard Worker } 118*61c4878aSAndroid Build Coastguard Worker 119*61c4878aSAndroid Build Coastguard Worker private mapServiceMethods(service: ServiceClient) { 120*61c4878aSAndroid Build Coastguard Worker const methodMap: { [index: string]: any } = {}; 121*61c4878aSAndroid Build Coastguard Worker const methodKeys = Array.from(service.methodsByName.keys()); 122*61c4878aSAndroid Build Coastguard Worker methodKeys 123*61c4878aSAndroid Build Coastguard Worker .filter( 124*61c4878aSAndroid Build Coastguard Worker (method: any) => 125*61c4878aSAndroid Build Coastguard Worker service.methodsByName.get(method) instanceof UnaryMethodStub || 126*61c4878aSAndroid Build Coastguard Worker service.methodsByName.get(method) instanceof 127*61c4878aSAndroid Build Coastguard Worker ServerStreamingMethodStub, 128*61c4878aSAndroid Build Coastguard Worker ) 129*61c4878aSAndroid Build Coastguard Worker .forEach((key) => { 130*61c4878aSAndroid Build Coastguard Worker const fn = this.createMethodWrapper(service.methodsByName.get(key)!); 131*61c4878aSAndroid Build Coastguard Worker methodMap[key] = fn; 132*61c4878aSAndroid Build Coastguard Worker }); 133*61c4878aSAndroid Build Coastguard Worker return methodMap; 134*61c4878aSAndroid Build Coastguard Worker } 135*61c4878aSAndroid Build Coastguard Worker 136*61c4878aSAndroid Build Coastguard Worker private createMethodWrapper(realMethod: MethodStub) { 137*61c4878aSAndroid Build Coastguard Worker if (realMethod instanceof UnaryMethodStub) { 138*61c4878aSAndroid Build Coastguard Worker return this.createUnaryMethodWrapper(realMethod); 139*61c4878aSAndroid Build Coastguard Worker } else if (realMethod instanceof ServerStreamingMethodStub) { 140*61c4878aSAndroid Build Coastguard Worker return this.createServerStreamingMethodWrapper(realMethod); 141*61c4878aSAndroid Build Coastguard Worker } 142*61c4878aSAndroid Build Coastguard Worker throw new Error(`Unknown method: ${realMethod}`); 143*61c4878aSAndroid Build Coastguard Worker } 144*61c4878aSAndroid Build Coastguard Worker 145*61c4878aSAndroid Build Coastguard Worker private createUnaryMethodWrapper(realMethod: UnaryMethodStub) { 146*61c4878aSAndroid Build Coastguard Worker const call = async (request: Message, timeout?: number) => { 147*61c4878aSAndroid Build Coastguard Worker return await realMethod.call(request, timeout); 148*61c4878aSAndroid Build Coastguard Worker }; 149*61c4878aSAndroid Build Coastguard Worker const createRequest = () => { 150*61c4878aSAndroid Build Coastguard Worker return new realMethod.method.requestType(); 151*61c4878aSAndroid Build Coastguard Worker }; 152*61c4878aSAndroid Build Coastguard Worker return { call, createRequest }; 153*61c4878aSAndroid Build Coastguard Worker } 154*61c4878aSAndroid Build Coastguard Worker 155*61c4878aSAndroid Build Coastguard Worker private createServerStreamingMethodWrapper( 156*61c4878aSAndroid Build Coastguard Worker realMethod: ServerStreamingMethodStub, 157*61c4878aSAndroid Build Coastguard Worker ) { 158*61c4878aSAndroid Build Coastguard Worker const call = (request: Message) => { 159*61c4878aSAndroid Build Coastguard Worker const source: RPCUnderlyingSource = { 160*61c4878aSAndroid Build Coastguard Worker start(controller: ReadableStreamDefaultController) { 161*61c4878aSAndroid Build Coastguard Worker this.call = realMethod.invoke( 162*61c4878aSAndroid Build Coastguard Worker request, 163*61c4878aSAndroid Build Coastguard Worker (msg) => { 164*61c4878aSAndroid Build Coastguard Worker controller.enqueue(msg); 165*61c4878aSAndroid Build Coastguard Worker }, 166*61c4878aSAndroid Build Coastguard Worker () => { 167*61c4878aSAndroid Build Coastguard Worker controller.close(); 168*61c4878aSAndroid Build Coastguard Worker }, 169*61c4878aSAndroid Build Coastguard Worker ); 170*61c4878aSAndroid Build Coastguard Worker }, 171*61c4878aSAndroid Build Coastguard Worker cancel() { 172*61c4878aSAndroid Build Coastguard Worker this.call!.cancel(); 173*61c4878aSAndroid Build Coastguard Worker }, 174*61c4878aSAndroid Build Coastguard Worker }; 175*61c4878aSAndroid Build Coastguard Worker return new RPCReadableStream<Message>(source); 176*61c4878aSAndroid Build Coastguard Worker }; 177*61c4878aSAndroid Build Coastguard Worker const createRequest = () => { 178*61c4878aSAndroid Build Coastguard Worker return new realMethod.method.requestType(); 179*61c4878aSAndroid Build Coastguard Worker }; 180*61c4878aSAndroid Build Coastguard Worker return { call, createRequest }; 181*61c4878aSAndroid Build Coastguard Worker } 182*61c4878aSAndroid Build Coastguard Worker} 183