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