• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1.. _module-pw_web:
2
3---------
4pw_web
5---------
6Pigweed provides an NPM package with modules to build web apps for Pigweed
7devices.
8
9Also included is a basic React app that demonstrates using the npm package.
10
11Getting Started
12===============
13
14Installation
15-------------
16If you have a bundler set up, you can install ``pigweedjs`` in your web application by:
17
18.. code-block:: bash
19
20   $ npm install --save pigweedjs
21
22
23After installing, you can import modules from ``pigweedjs`` in this way:
24
25.. code-block:: javascript
26
27   import { pw_rpc, pw_tokenizer, Device, WebSerial } from 'pigweedjs';
28
29Import Directly in HTML
30^^^^^^^^^^^^^^^^^^^^^^^
31If you don't want to set up a bundler, you can also load Pigweed directly in
32your HTML page by:
33
34.. code-block:: html
35
36   <script src="https://unpkg.com/pigweedjs/dist/index.umd.js"></script>
37   <script>
38     const { pw_rpc, pw_hdlc, Device, WebSerial } from Pigweed;
39   </script>
40
41Getting Started
42---------------
43Easiest way to get started is to build pw_system demo and run it on a STM32F429I
44Discovery board. Discovery board is Pigweed's primary target for development.
45Refer to :ref:`target documentation<target-stm32f429i-disc1-stm32cube>` for
46instructions on how to build the demo and try things out.
47
48``pigweedjs`` provides a ``Device`` API which simplifies common tasks. Here is
49an example to connect to device and call ``EchoService.Echo`` RPC service.
50
51.. code-block:: html
52
53   <h1>Hello Pigweed</h1>
54   <button onclick="connect()">Connect</button>
55   <button onclick="echo()">Echo RPC</button>
56   <br /><br />
57   <code></code>
58   <script src="https://unpkg.com/pigweedjs/dist/index.umd.js"></script>
59   <script src="https://unpkg.com/pigweedjs/dist/protos/collection.umd.js"></script>
60   <script>
61     const { Device } = Pigweed;
62     const { ProtoCollection } = PigweedProtoCollection;
63
64     const device = new Device(new ProtoCollection());
65
66     async function connect(){
67       await device.connect();
68     }
69
70     async function echo(){
71       const [status, response] = await device.rpcs.pw.rpc.EchoService.Echo("Hello");
72       document.querySelector('code').innerText = "Response: " + response;
73     }
74   </script>
75
76pw_system demo uses ``pw_log_rpc``; an RPC-based logging solution. pw_system
77also uses pw_tokenizer to tokenize strings and save device space. Below is an
78example that streams logs using the ``Device`` API.
79
80.. code-block:: html
81
82   <h1>Hello Pigweed</h1>
83   <button onclick="connect()">Connect</button>
84   <br /><br />
85   <code></code>
86   <script src="https://unpkg.com/pigweedjs/dist/index.umd.js"></script>
87   <script src="https://unpkg.com/pigweedjs/dist/protos/collection.umd.js"></script>
88   <script>
89     const { Device, pw_tokenizer } = Pigweed;
90     const { ProtoCollection } = PigweedProtoCollection;
91     const tokenDBCsv = `...` // Load token database here
92
93     const device = new Device(new ProtoCollection());
94     const detokenizer = new pw_tokenizer.Detokenizer(tokenDBCsv);
95
96     async function connect(){
97       await device.connect();
98       const call = device.rpcs.pw.log.Logs.Listen((msg) => {
99         msg.getEntriesList().forEach((entry) => {
100           const frame = entry.getMessage();
101           const detokenized = detokenizer.detokenizeUint8Array(frame);
102           document.querySelector('code').innerHTML += detokenized + "<br/>";
103         });
104       })
105     }
106   </script>
107
108The above example requires a token database in CSV format. You can generate one
109from the pw_system's ``.elf`` file by running:
110
111.. code-block:: bash
112
113   $ pw_tokenizer/py/pw_tokenizer/database.py create \
114   --database db.csv out/stm32f429i_disc1_stm32cube.size_optimized/obj/pw_system/bin/system_example.elf
115
116You can then load this CSV in JavaScript using ``fetch()`` or by just copying
117the contents into the ``tokenDBCsv`` variable in the above example.
118
119Modules
120=======
121
122Device
123------
124Device class is a helper API to connect to a device over serial and call RPCs
125easily.
126
127To initialize device, it needs a ``ProtoCollection`` instance. ``pigweedjs``
128includes a default one which you can use to get started, you can also generate
129one from your own ``.proto`` files using ``pw_proto_compiler``.
130
131``Device`` goes through all RPC methods in the provided ProtoCollection. For
132each RPC, it reads all the fields in ``Request`` proto and generates a
133JavaScript function that accepts all the fields as it's arguments. It then makes
134this function available under ``rpcs.*`` namespaced by its package name.
135
136Device has following public API:
137
138- ``constructor(ProtoCollection, WebSerialTransport <optional>, rpcAddress <optional>)``
139- ``connect()`` - Shows browser's WebSerial connection dialog and let's user
140  make device selection
141- ``rpcs.*`` - Device API enumerates all RPC services and methods present in the
142  provided proto collection and makes them available as callable functions under
143  ``rpcs``. Example: If provided proto collection includes Pigweed's Echo
144  service ie. ``pw.rpc.EchoService.Echo``, it can be triggered by calling
145  ``device.rpcs.pw.rpc.EchoService.Echo("some message")``. The functions return
146  a ``Promise`` that resolves an array with status and response.
147
148WebSerialTransport
149------------------
150To help with connecting to WebSerial and listening for serial data, a helper
151class is also included under ``WebSerial.WebSerialTransport``. Here is an
152example usage:
153
154.. code-block:: javascript
155
156   import { WebSerial, pw_hdlc } from 'pigweedjs';
157
158   const transport = new WebSerial.WebSerialTransport();
159   const decoder = new pw_hdlc.Decoder();
160
161   // Present device selection prompt to user
162   await transport.connect();
163
164   // Or connect to an existing `SerialPort`
165   // await transport.connectPort(port);
166
167   // Listen and decode HDLC frames
168   transport.chunks.subscribe((item) => {
169     const decoded = decoder.process(item);
170     for (const frame of decoded) {
171       if (frame.address === 1) {
172         const decodedLine = new TextDecoder().decode(frame.data);
173         console.log(decodedLine);
174       }
175     }
176   });
177
178   // Later, close all streams and close the port.
179   transport.disconnect();
180
181Individual Modules
182==================
183Following Pigweed modules are included in the NPM package:
184
185- `pw_hdlc <https://pigweed.dev/pw_hdlc/#typescript>`_
186- `pw_rpc <https://pigweed.dev/pw_rpc/ts/>`_
187- `pw_tokenizer <https://pigweed.dev/pw_tokenizer/#typescript>`_
188- `pw_transfer <https://pigweed.dev/pw_transfer/#typescript>`_
189
190Web Console
191===========
192Pigweed includes a web console that demonstrates `pigweedjs` usage in a
193React-based web app. Web console includes a log viewer and a REPL that supports
194autocomplete. Here's how to run the web console locally:
195
196.. code-block:: bash
197
198   $ cd pw_web/webconsole
199   $ npm install
200   $ npm run dev
201
202Log Viewer Component
203====================
204The NPM package also includes a log viewer component that can be embedded in any
205webapp. The component works with Pigweed's RPC stack out-of-the-box but also
206supports defining your own log source. See :ref:`module-pw_web-log-viewer` for
207component interaction details.
208
209The component is composed of the component itself and a log source. Here is a
210simple example app that uses a mock log source:
211
212.. code-block:: html
213
214   <div id="log-viewer-container"></div>
215   <script src="https://unpkg.com/pigweedjs/dist/logging.umd.js"></script>
216   <script>
217
218     const { createLogViewer, MockLogSource } = PigweedLogging;
219     const logSource = new MockLogSource();
220     const containerEl = document.querySelector(
221       '#log-viewer-container'
222     );
223
224     let unsubscribe = createLogViewer(logSource, containerEl);
225     logSource.start(); // Start producing mock logs
226
227   </script>
228
229The code above will render a working log viewer that just streams mock
230log entries.
231
232It also comes with an RPC log source with support for detokenization. Here is an
233example app using that:
234
235.. code-block:: html
236
237   <div id="log-viewer-container"></div>
238   <script src="https://unpkg.com/pigweedjs/dist/index.umd.js"></script>
239   <script src="https://unpkg.com/pigweedjs/dist/protos/collection.umd.js"></script>
240   <script src="https://unpkg.com/pigweedjs/dist/logging.umd.js"></script>
241   <script>
242
243     const { Device, pw_tokenizer } = Pigweed;
244     const { ProtoCollection } = PigweedProtoCollection;
245     const { createLogViewer, PigweedRPCLogSource } = PigweedLogging;
246
247     const device = new Device(new ProtoCollection());
248     const logSource = new PigweedRPCLogSource(device, "CSV TOKEN DB HERE");
249     const containerEl = document.querySelector(
250       '#log-viewer-container'
251     );
252
253     let unsubscribe = createLogViewer(logSource, containerEl);
254
255   </script>
256
257Custom Log Source
258-----------------
259You can define a custom log source that works with the log viewer component by
260just extending the abstract `LogSource` class and emitting the `logEntry` events
261like this:
262
263.. code-block:: typescript
264
265   import { LogSource, LogEntry, Severity } from 'pigweedjs/logging';
266
267   export class MockLogSource extends LogSource {
268     constructor(){
269       super();
270       // Do any initializations here
271       // ...
272       // Then emit logs
273       const log1: LogEntry = {
274
275       }
276       this.publishLogEntry({
277         severity: Severity.INFO,
278         timestamp: new Date(),
279         fields: [
280           { key: 'severity', value: severity }
281           { key: 'timestamp', value: new Date().toISOString() },
282           { key: 'source', value: "LEFT SHOE" },
283           { key: 'message', value: "Running mode activated." }
284         ]
285       });
286     }
287   }
288
289After this, you just need to pass your custom log source object
290to `createLogViewer()`. See implementation of
291`PigweedRPCLogSource <https://cs.opensource.google/pigweed/pigweed/+/main:ts/logging_source_rpc.ts>`_
292for reference.
293
294Column Order
295------------
296Column Order can be defined on initialization with the optional ``columnOrder`` parameter.
297Only fields that exist in the Log Source will render as columns in the Log Viewer.
298
299.. code-block:: typescript
300
301   createLogViewer(logSource, root, state, logStore, columnOrder)
302
303``columnOrder`` accepts an ``string[]`` and defaults to ``[log_source, time, timestamp]``
304
305.. code-block:: typescript
306
307   createLogViewer(logSource, root, state, logStore, ['log_source', 'time', 'timestamp'])
308
309Note, columns will always start with ``severity`` and end with ``message``, these fields do not need to be defined.
310Columns are ordered in the following format:
311
3121. ``severity``
3132. ``columnOrder``
3143. Fields that exist in Log Source but not listed will be added here.
3154. ``message``
316
317Color Scheme
318------------
319The log viewer web component provides the ability to set the color scheme
320manually, overriding any default or system preferences.
321
322To set the color scheme, first obtain a reference to the ``log-viewer`` element
323in the DOM. A common way to do this is by using ``querySelector()``:
324
325.. code-block:: javascript
326
327   const logViewer = document.querySelector('log-viewer');
328
329You can then set the color scheme dynamically by updating the component's
330`colorScheme` property or by setting a value for the `colorscheme` HTML attribute.
331
332.. code-block:: javascript
333
334   logViewer.colorScheme = 'dark';
335
336.. code-block:: javascript
337
338   logViewer.setAttribute('colorscheme', 'dark');
339
340The color scheme can be set to ``'dark'``, ``'light'``, or the default ``'auto'``
341which allows the component to adapt to the preferences in the operating system
342settings.
343
344Material Icon Font (Subsetting)
345-------------------------------
346.. inclusive-language: disable
347
348The Log Viewer uses a subset of the Material Symbols Rounded icon font fetched via the `Google Fonts API <https://developers.google.com/fonts/docs/css2#forming_api_urls>`_. However, we also provide a subset of this font for offline usage at `GitHub <https://github.com/google/material-design-icons/blob/master/variablefont/MaterialSymbolsRounded%5BFILL%2CGRAD%2Copsz%2Cwght%5D.woff2>`_
349with codepoints listed in the `codepoints <https://github.com/google/material-design-icons/blob/master/variablefont/MaterialSymbolsRounded%5BFILL%2CGRAD%2Copsz%2Cwght%5D.codepoints>`_ file.
350
351(It's easiest to look up the codepoints at `fonts.google.com <https://fonts.google.com/icons?selected=Material+Symbols+Rounded>`_ e.g. see
352the sidebar shows the Codepoint for `"home" <https://fonts.google.com/icons?selected=Material+Symbols+Rounded:home:FILL@0;wght@0;GRAD@0;opsz@NaN>`_ is e88a).
353
354The following icons with codepoints are curently used:
355
356* delete_sweep e16c
357* error e000
358* warning f083
359* cancel e5c9
360* bug_report e868
361* view_column e8ec
362* brightness_alert f5cf
363* wrap_text e25b
364* more_vert e5d4
365
366To save load time and bandwidth, we provide a pre-made subset of the font with
367just the codepoints we need, which reduces the font size from 3.74MB to 12KB.
368
369We use fonttools (https://github.com/fonttools/fonttools) to create the subset.
370To create your own subset, find the codepoints you want to add and:
371
3721. Start a python virtualenv and install fonttools
373
374.. code-block:: bash
375
376   virtualenv env
377   source env/bin/activate
378   pip install fonttools brotli
379
3802. Download the the raw `MaterialSybmolsRounded woff2 file <https://github.com/google/material-design-icons/tree/master/variablefont>`_
381
382.. code-block:: bash
383
384   # line below for example, the url is not stable: e.g.
385   curl -L -o MaterialSymbolsRounded.woff2 \
386     "https://github.com/google/material-design-icons/raw/master/variablefont/MaterialSymbolsRounded%5BFILL,GRAD,opsz,wght%5D.woff2"
387
3883. Run fonttools, passing in the unicode codepoints of the necessary glyphs.
389   (The points for letters a-z, numbers 0-9 and underscore character are
390   necessary for creating ligatures)
391
392.. warning::  Ensure there are nono spaces in the list of codepoints.
393.. code-block:: bash
394
395   fonttools subset MaterialSymbolsRounded.woff2 \
396      --unicodes=5f-7a,30-39,e16c,e000,e002,e8b2,e5c9,e868,e8ec,f083,f5cf,e25b,e5d4 \
397      --no-layout-closure \
398      --output-file=material_symbols_rounded_subset.woff2 \
399      --flavor=woff2
400
4014. Update ``material_symbols_rounded_subset.woff2`` in ``log_viewer/src/assets``
402   with the new subset
403
404.. inclusive-language: enable
405
406Guides
407======
408
409.. toctree::
410  :maxdepth: 1
411
412  testing
413  log_viewer
414