blob: cea9bf6a368319ef8c0f268a84e15c0ef517e4a5 [file] [log] [blame]
// Copyright 2024 The Pigweed Authors
//
// Licensed under the Apache License, Version 2.0 (the "License"); you may not
// use this file except in compliance with the License. You may obtain a copy of
// the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations under
// the License.
import { WebSerial, pw_hdlc, pw_rpc, pw_status } from "pigweedjs";
import { ProtoCollection } from "../../protos/collection/collection";
import { BlinkRequest } from "../../protos/collection/blinky/blinky_pb";
import {
OnboardTempStreamRequest,
OnboardTempResponse,
} from "../../protos/collection/board/board_pb";
import {
MeasureStreamRequest,
Measurement,
} from "../../protos/collection/air_sensor/air_sensor_pb";
import { State } from "../../protos/collection/state_manager/state_manager_pb";
class RPCService {
transport;
decoder;
encoder;
rpcAddress;
channels;
client;
blinkService;
boardTempService;
measureService;
stateService;
constructor(rpcAddress = 82) {
this.transport = new WebSerial.WebSerialTransport();
this.decoder = new pw_hdlc.Decoder();
this.encoder = new pw_hdlc.Encoder();
this.rpcAddress = rpcAddress;
this.channels = [
new pw_rpc.Channel(1, (bytes: Uint8Array) => {
const hdlcBytes = this.encoder.uiFrame(this.rpcAddress, bytes);
this.transport.sendChunk(hdlcBytes);
}),
];
this.client = pw_rpc.Client.fromProtoSet(
this.channels,
new ProtoCollection(),
);
this.blinkService = this.client.channel().methodStub("blinky.Blinky.Blink");
this.boardTempService = this.client
.channel()
.methodStub("board.Board.OnboardTempStream");
this.measureService = this.client
.channel()
.methodStub("air_sensor.AirSensor.MeasureStream");
this.stateService = this.client
.channel()
.methodStub("state_manager.StateManager.GetState");
}
async connect() {
await this.transport.connect();
this.transport.chunks.subscribe((item: any) => {
const decoded = this.decoder.process(item);
for (const frame of decoded) {
if (frame.address === this.rpcAddress) {
this.client.processPacket(frame.data);
}
}
});
}
async disconnect() {
await this.transport.disconnect();
}
async blink(times: number, intervalMs: number = 300) {
const req = new BlinkRequest();
req.setBlinkCount(times);
req.setIntervalMs(intervalMs);
const [status, response] = await this.blinkService.call(req);
}
async streamTemp(onTemp?: (temp: number) => void) {
const req = new OnboardTempStreamRequest();
req.setSampleIntervalMs(500);
await this.boardTempService.invoke(
req,
(m: OnboardTempResponse) => {
console.log(m.toObject());
if (onTemp) onTemp(m.getTemp());
},
undefined,
(err) => {
console.error(err);
},
);
}
async streamMeasure(onMeasure?: (measurement: Measurement) => void) {
// We check if this RPC exists on device,
// if not we fallback to just onboardTemp.
return new Promise(async (resolve, reject) => {
let resolveCalled = false;
const req = new MeasureStreamRequest();
req.setSampleIntervalMs(500);
await this.measureService.invoke(
req,
(m: Measurement) => {
if (!resolveCalled) {
resolveCalled = true;
resolve(true);
}
if (onMeasure) onMeasure(m);
},
undefined,
(status: pw_status.Status) => {
if ((status = pw_status.Status.NOT_FOUND)) {
reject(status);
}
},
);
});
}
async getState(): Promise<State> {
const [status, response] = await this.stateService.call();
return response;
}
}
// We keep a singleton of this service.
const rpc = new RPCService();
export function getRpcService(): RPCService {
return rpc;
}