xref: /aosp_15_r20/external/pigweed/ts/device/index.ts (revision 61c4878ac05f98d0ceed94b57d316916de578985)
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