blob: e30cc64a60afe6ad8307fa77868b9dc528f1e3ed [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;
}