• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1// Copyright 2021 The Pigweed Authors
2//
3// Licensed under the Apache License, Version 2.0 (the "License"); you may not
4// use this file except in compliance with the License. You may obtain a copy of
5// the License at
6//
7//     https://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12// License for the specific language governing permissions and limitations under
13// the License.
14
15/* eslint-env browser */
16import {
17  Button,
18  TextField,
19  makeStyles,
20  Typography,
21  createTheme,
22  ThemeProvider,
23} from '@material-ui/core';
24import {ToggleButtonGroup, ToggleButton} from '@material-ui/lab';
25import {WebSerialTransport} from '../transport/web_serial_transport';
26import {Decoder, Frame, Encoder} from '@pigweed/pw_hdlc';
27import {SerialLog} from './serial_log';
28import {Log} from './log';
29import * as React from 'react';
30import {useState, useRef} from 'react';
31import {Channel, Client, UnaryMethodStub} from '@pigweed/pw_rpc';
32import {Status} from '@pigweed/pw_status';
33import {ProtoCollection} from 'web_proto_collection/generated/ts_proto_collection';
34
35const RPC_ADDRESS = 82;
36
37const darkTheme = createTheme({
38  palette: {
39    type: 'dark',
40    primary: {
41      main: '#42a5f5',
42    },
43    secondary: {
44      main: 'rgb(232, 21, 165)',
45    },
46  },
47});
48
49const useStyles = makeStyles(() => ({
50  root: {
51    display: 'flex',
52    'flex-direction': 'column',
53    'align-items': 'flex-start',
54    color: 'rgba(255, 255, 255, 0.8);',
55    overflow: 'hidden',
56    width: '1000px',
57  },
58  connect: {
59    'margin-top': '16px',
60    'margin-bottom': '20px',
61  },
62  rpc: {
63    margin: '10px',
64  },
65}));
66
67export function App() {
68  const [connected, setConnected] = useState<boolean>(false);
69  const [frames, setFrames] = useState<Frame[]>([]);
70  const [logLines, setLogLines] = useState<string[]>([]);
71  const [echoText, setEchoText] = useState<string>('Hello World');
72  const [logViewer, setLogViewer] = useState<string>('log');
73
74  const classes = useStyles();
75
76  const transportRef = useRef(new WebSerialTransport());
77  const decoderRef = useRef(new Decoder());
78  const encoderRef = useRef(new Encoder());
79  const protoCollectionRef = useRef(new ProtoCollection());
80  const channelsRef = useRef([
81    new Channel(1, (bytes: Uint8Array) => {
82      sendPacket(transportRef.current!, bytes);
83    }),
84  ]);
85  const clientRef = useRef<Client>(
86    Client.fromProtoSet(channelsRef.current!, protoCollectionRef.current!)
87  );
88  const echoService = clientRef
89    .current!.channel()!
90    .methodStub('pw.rpc.EchoService.Echo') as UnaryMethodStub;
91
92  function onConnected() {
93    setConnected(true);
94    transportRef.current!.chunks.subscribe((item: Uint8Array) => {
95      const decoded = decoderRef.current!.process(item);
96      for (const frame of decoded) {
97        setFrames(old => [...old, frame]);
98        if (frame.address === RPC_ADDRESS) {
99          const status = clientRef.current!.processPacket(frame.data);
100        }
101        if (frame.address === 1) {
102          const decodedLine = new TextDecoder().decode(frame.data);
103          const date = new Date();
104          const logLine = `[${date.toLocaleTimeString()}] ${decodedLine}`;
105          setLogLines(old => [...old, logLine]);
106        }
107      }
108    });
109  }
110
111  function sendPacket(
112    transport: WebSerialTransport,
113    packetBytes: Uint8Array
114  ): void {
115    const hdlcBytes = encoderRef.current.uiFrame(RPC_ADDRESS, packetBytes);
116    transport.sendChunk(hdlcBytes);
117  }
118
119  function echo(text: string) {
120    const request = new echoService.method.responseType();
121    request.setMsg(text);
122    echoService
123      .call(request)
124      .then(([status, response]) => {
125        console.log(response.toObject());
126      })
127      .catch(() => {});
128  }
129
130  return (
131    <div className={classes.root}>
132      <ThemeProvider theme={darkTheme}>
133        <Typography variant="h3">Pigweb Demo</Typography>
134        <Button
135          className={classes.connect}
136          disabled={connected}
137          variant="contained"
138          color="primary"
139          onClick={() => {
140            transportRef.current
141              .connect()
142              .then(onConnected)
143              .catch(error => {
144                setConnected(false);
145                console.log(error);
146              });
147          }}
148        >
149          {connected ? 'Connected' : 'Connect'}
150        </Button>
151        <ToggleButtonGroup
152          value={logViewer}
153          onChange={(event, selected) => {
154            setLogViewer(selected);
155          }}
156          exclusive
157        >
158          <ToggleButton value="log">Log Viewer</ToggleButton>
159          <ToggleButton value="serial">Serial Debug</ToggleButton>
160        </ToggleButtonGroup>
161        {logViewer === 'log' ? (
162          <Log lines={logLines} />
163        ) : (
164          <SerialLog frames={frames} />
165        )}
166        <span className={classes.rpc}>
167          <TextField
168            id="echo-text"
169            label="Echo Text"
170            disabled={!connected}
171            value={echoText}
172            onChange={event => {
173              setEchoText(event.target.value);
174            }}
175          ></TextField>
176          <Button
177            disabled={!connected}
178            variant="contained"
179            color="primary"
180            onClick={() => {
181              echo(echoText);
182            }}
183          >
184            Send Echo RPC
185          </Button>
186        </span>
187      </ThemeProvider>
188    </div>
189  );
190}
191