blob: 3d7d27fbc89747a83b5d42f728a7deab2752a5e0 [file] [log] [blame]
.. _module-pw_web:
---------
pw_web
---------
Pigweed provides an NPM package with modules to build web apps for Pigweed
devices.
Also included is a basic React app that demonstrates using the npm package.
Getting Started
===============
Installation
-------------
If you have a bundler set up, you can install ``pigweedjs`` in your web application by:
.. code-block:: bash
$ npm install --save pigweedjs
After installing, you can import modules from ``pigweedjs`` in this way:
.. code-block:: javascript
import { pw_rpc, pw_tokenizer, Device, WebSerial } from 'pigweedjs';
Import Directly in HTML
^^^^^^^^^^^^^^^^^^^^^^^
If you don't want to set up a bundler, you can also load Pigweed directly in
your HTML page by:
.. code-block:: html
<script src="https://unpkg.com/pigweedjs/dist/index.umd.js"></script>
<script>
const { pw_rpc, pw_hdlc, Device, WebSerial } from Pigweed;
</script>
Getting Started
---------------
Easiest way to get started is to build pw_system demo and run it on a STM32F429I
Discovery board. Discovery board is Pigweed's primary target for development.
Refer to :ref:`target documentation<target-stm32f429i-disc1-stm32cube>` for
instructions on how to build the demo and try things out.
``pigweedjs`` provides a ``Device`` API which simplifies common tasks. Here is
an example to connect to device and call ``EchoService.Echo`` RPC service.
.. code-block:: html
<h1>Hello Pigweed</h1>
<button onclick="connect()">Connect</button>
<button onclick="echo()">Echo RPC</button>
<br /><br />
<code></code>
<script src="https://unpkg.com/pigweedjs/dist/index.umd.js"></script>
<script src="https://unpkg.com/pigweedjs/dist/protos/collection.umd.js"></script>
<script>
const { Device } = Pigweed;
const { ProtoCollection } = PigweedProtoCollection;
const device = new Device(new ProtoCollection());
async function connect(){
await device.connect();
}
async function echo(){
const [status, response] = await device.rpcs.pw.rpc.EchoService.Echo("Hello");
document.querySelector('code').innerText = "Response: " + response;
}
</script>
pw_system demo uses ``pw_log_rpc``; an RPC-based logging solution. pw_system
also uses pw_tokenizer to tokenize strings and save device space. Below is an
example that streams logs using the ``Device`` API.
.. code-block:: html
<h1>Hello Pigweed</h1>
<button onclick="connect()">Connect</button>
<br /><br />
<code></code>
<script src="https://unpkg.com/pigweedjs/dist/index.umd.js"></script>
<script src="https://unpkg.com/pigweedjs/dist/protos/collection.umd.js"></script>
<script>
const { Device, pw_tokenizer } = Pigweed;
const { ProtoCollection } = PigweedProtoCollection;
const tokenDBCsv = `...` // Load token database here
const device = new Device(new ProtoCollection());
const detokenizer = new pw_tokenizer.Detokenizer(tokenDBCsv);
async function connect(){
await device.connect();
const call = device.rpcs.pw.log.Logs.Listen((msg) => {
msg.getEntriesList().forEach((entry) => {
const frame = entry.getMessage();
const detokenized = detokenizer.detokenizeUint8Array(frame);
document.querySelector('code').innerHTML += detokenized + "<br/>";
});
})
}
</script>
The above example requires a token database in CSV format. You can generate one
from the pw_system's ``.elf`` file by running:
.. code-block:: bash
$ pw_tokenizer/py/pw_tokenizer/database.py create \
--database db.csv out/stm32f429i_disc1_stm32cube.size_optimized/obj/pw_system/bin/system_example.elf
You can then load this CSV in JavaScript using ``fetch()`` or by just copying
the contents into the ``tokenDBCsv`` variable in the above example.
Modules
=======
Device
------
Device class is a helper API to connect to a device over serial and call RPCs
easily.
To initialize device, it needs a ``ProtoCollection`` instance. ``pigweedjs``
includes a default one which you can use to get started, you can also generate
one from your own ``.proto`` files using ``pw_proto_compiler``.
``Device`` goes through all RPC methods in the provided ProtoCollection. For
each RPC, it reads all the fields in ``Request`` proto and generates a
JavaScript function that accepts all the fields as it's arguments. It then makes
this function available under ``rpcs.*`` namespaced by its package name.
Device has following public API:
- ``constructor(ProtoCollection, WebSerialTransport <optional>, rpcAddress <optional>)``
- ``connect()`` - Shows browser's WebSerial connection dialog and let's user
make device selection
- ``rpcs.*`` - Device API enumerates all RPC services and methods present in the
provided proto collection and makes them available as callable functions under
``rpcs``. Example: If provided proto collection includes Pigweed's Echo
service ie. ``pw.rpc.EchoService.Echo``, it can be triggered by calling
``device.rpcs.pw.rpc.EchoService.Echo("some message")``. The functions return
a ``Promise`` that resolves an array with status and response.
WebSerialTransport
------------------
To help with connecting to WebSerial and listening for serial data, a helper
class is also included under ``WebSerial.WebSerialTransport``. Here is an
example usage:
.. code-block:: javascript
import { WebSerial, pw_hdlc } from 'pigweedjs';
const transport = new WebSerial.WebSerialTransport();
const decoder = new pw_hdlc.Decoder();
// Present device selection prompt to user
await transport.connect();
// Or connect to an existing `SerialPort`
// await transport.connectPort(port);
// Listen and decode HDLC frames
transport.chunks.subscribe((item) => {
const decoded = decoder.process(item);
for (const frame of decoded) {
if (frame.address === 1) {
const decodedLine = new TextDecoder().decode(frame.data);
console.log(decodedLine);
}
}
});
// Later, close all streams and close the port.
transport.disconnect();
Individual Modules
==================
Following Pigweed modules are included in the NPM package:
- `pw_hdlc <https://pigweed.dev/pw_hdlc/#typescript>`_
- `pw_rpc <https://pigweed.dev/pw_rpc/ts/>`_
- `pw_tokenizer <https://pigweed.dev/pw_tokenizer/#typescript>`_
- `pw_transfer <https://pigweed.dev/pw_transfer/#typescript>`_
Web Console
===========
Pigweed includes a web console that demonstrates `pigweedjs` usage in a
React-based web app. Web console includes a log viewer and a REPL that supports
autocomplete. Here's how to run the web console locally:
.. code-block:: bash
$ cd pw_web/webconsole
$ npm install
$ npm run dev
Log Viewer Component
====================
The NPM package also includes a log viewer component that can be embedded in any
webapp. The component works with Pigweed's RPC stack out-of-the-box but also
supports defining your own log source. See :ref:`module-pw_web-log-viewer` for
component interaction details.
The component is composed of the component itself and a log source. Here is a
simple example app that uses a mock log source:
.. code-block:: html
<div id="log-viewer-container"></div>
<script src="https://unpkg.com/pigweedjs/dist/logging.umd.js"></script>
<script>
const { createLogViewer, MockLogSource } = PigweedLogging;
const logSource = new MockLogSource();
const containerEl = document.querySelector(
'#log-viewer-container'
);
let unsubscribe = createLogViewer(logSource, containerEl);
logSource.start(); // Start producing mock logs
</script>
The code above will render a working log viewer that just streams mock
log entries.
It also comes with an RPC log source with support for detokenization. Here is an
example app using that:
.. code-block:: html
<div id="log-viewer-container"></div>
<script src="https://unpkg.com/pigweedjs/dist/index.umd.js"></script>
<script src="https://unpkg.com/pigweedjs/dist/protos/collection.umd.js"></script>
<script src="https://unpkg.com/pigweedjs/dist/logging.umd.js"></script>
<script>
const { Device, pw_tokenizer } = Pigweed;
const { ProtoCollection } = PigweedProtoCollection;
const { createLogViewer, PigweedRPCLogSource } = PigweedLogging;
const device = new Device(new ProtoCollection());
const logSource = new PigweedRPCLogSource(device, "CSV TOKEN DB HERE");
const containerEl = document.querySelector(
'#log-viewer-container'
);
let unsubscribe = createLogViewer(logSource, containerEl);
</script>
Custom Log Source
-----------------
You can define a custom log source that works with the log viewer component by
just extending the abstract `LogSource` class and emitting the `logEntry` events
like this:
.. code-block:: typescript
import { LogSource, LogEntry, Severity } from 'pigweedjs/logging';
export class MockLogSource extends LogSource {
constructor(){
super();
// Do any initializations here
// ...
// Then emit logs
const log1: LogEntry = {
}
this.publishLogEntry({
severity: Severity.INFO,
timestamp: new Date(),
fields: [
{ key: 'severity', value: severity }
{ key: 'timestamp', value: new Date().toISOString() },
{ key: 'source', value: "LEFT SHOE" },
{ key: 'message', value: "Running mode activated." }
]
});
}
}
After this, you just need to pass your custom log source object
to `createLogViewer()`. See implementation of
`PigweedRPCLogSource <https://cs.opensource.google/pigweed/pigweed/+/main:ts/logging_source_rpc.ts>`_
for reference.
Column Order
------------
Column Order can be defined on initialization with the optional ``columnOrder`` parameter.
Only fields that exist in the Log Source will render as columns in the Log Viewer.
.. code-block:: typescript
createLogViewer(logSource, root, state, logStore, columnOrder)
``columnOrder`` accepts an ``string[]`` and defaults to ``[log_source, time, timestamp]``
.. code-block:: typescript
createLogViewer(logSource, root, state, logStore, ['log_source', 'time', 'timestamp'])
Note, columns will always start with ``severity`` and end with ``message``, these fields do not need to be defined.
Columns are ordered in the following format:
1. ``severity``
2. ``columnOrder``
3. Fields that exist in Log Source but not listed will be added here.
4. ``message``
Color Scheme
------------
The log viewer web component provides the ability to set the color scheme
manually, overriding any default or system preferences.
To set the color scheme, first obtain a reference to the ``log-viewer`` element
in the DOM. A common way to do this is by using ``querySelector()``:
.. code-block:: javascript
const logViewer = document.querySelector('log-viewer');
You can then set the color scheme dynamically by updating the component's
`colorScheme` property or by setting a value for the `colorscheme` HTML attribute.
.. code-block:: javascript
logViewer.colorScheme = 'dark';
.. code-block:: javascript
logViewer.setAttribute('colorscheme', 'dark');
The color scheme can be set to ``'dark'``, ``'light'``, or the default ``'auto'``
which allows the component to adapt to the preferences in the operating system
settings.
Material Icon Font (Subsetting)
-------------------------------
.. inclusive-language: disable
Log Viewer uses a subset of Material Icons Rounded hosted on `GitHub <https://github.com/google/material-design-icons/blob/master/variablefont/MaterialSymbolsRounded%5BFILL%2CGRAD%2Copsz%2Cwght%5D.woff2>`_
with codepoints listed in the `codepoints <https://github.com/google/material-design-icons/blob/master/variablefont/MaterialSymbolsRounded%5BFILL%2CGRAD%2Copsz%2Cwght%5D.codepoints>`_ file
(It's easiest to look up the codepoints at `fonts.google.com <https://fonts.google.com/icons?selected=Material+Symbols+Rounded>`_ e.g. see
the 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)
The following icons with codepoints are curently used:
* delete_sweep e16c
* error e000
* warning f083
* cancel e5c9
* bug_report e868
* view_column e8ec
* brightness_alert f5cf
* wrap_text e25b
* more_vert e5d4
To save load time and bandwidth, we provide a pre-made subset of the font with
just the codepoints we need, which reduces the font size from 3.74MB to 12KB.
We use fonttools (https://github.com/fonttools/fonttools) to create the subset.
To create your own subset, find the codepoints you want to add and:
1. Start a python virtualenv and install fonttools
.. code-block:: bash
virtualenv env
source env/bin/activate
pip install fonttools brotli
2. Download the the raw `MaterialSybmolsRounded woff2 file <https://github.com/google/material-design-icons/tree/master/variablefont>`_
.. code-block:: bash
# line below for example, the url is not stable: e.g.
curl -L -o MaterialSymbolsRounded.woff2 \
"https://github.com/google/material-design-icons/raw/master/variablefont/MaterialSymbolsRounded%5BFILL,GRAD,opsz,wght%5D.woff2"
3. Run fonttools, passing in the unicode codepoints of the necessary glyphs.
(The points for letters a-z, numbers 0-9 and underscore character are
necessary for creating ligatures)
.. warning:: Ensure there are nono spaces in the list of codepoints.
.. code-block:: bash
fonttools subset MaterialSymbolsRounded.woff2 \
--unicodes=5f-7a,30-39,e16c,e000,e002,e8b2,e5c9,e868,e8ec,f083,f5cf,e25b,e5d4 \
--no-layout-closure \
--output-file=material_symbols_rounded_subset.woff2 \
--flavor=woff2
4. Update ``material_symbols_rounded_subset.woff2`` in ``log_viewer/src/assets``
with the new subset
.. inclusive-language: enable
Guides
======
.. toctree::
:maxdepth: 1
testing
log_viewer