xref: /aosp_15_r20/external/pigweed/docs/showcases/sense/tutorial/host_sim.rst (revision 61c4878ac05f98d0ceed94b57d316916de578985)
1.. _showcase-sense-tutorial-sim:
2
3===================
46. Run the host app
5===================
6Teams creating projects on top of Pigweed often create host versions of
7their apps to speed up development. "Host" means that there's no physical
8embedded device in the loop; a simulated version of the app runs directly
9on a development host computer. :ref:`target-host-device-simulator` is the
10underlying library that makes it possible to simulate apps.
11:ref:`module-pw_console` makes it easy to connect to the simulated app. Try
12out a simulated version of the ``blinky`` bringup app now:
13
14.. _REPL: https://en.wikipedia.org/wiki/Read%E2%80%93eval%E2%80%93print_loop
15
16#. Start the simulated app:
17
18   .. tab-set::
19
20      .. tab-item:: VS Code
21         :sync: vsc
22
23         #. In **Bazel Build Targets** expand **//apps/blinky**, then
24            right-click **:simulator_blinky (host_device_simulator_binary)**,
25            then select **Run target**.
26
27            .. admonition:: Extra macOS setup
28
29               If you see **Do you want the application "bazel" to accept incoming
30               network connections?** click **Allow**. The simulated device needs
31               to connect to local ports.
32
33               .. figure:: https://storage.googleapis.com/pigweed-media/sense/accept_incoming_network_connections.png
34
35            .. figure:: https://storage.googleapis.com/pigweed-media/sense/20240802/run_target.png
36
37            You should see output like this:
38
39            .. code-block:: console
40
41               # ...
42               INFO: Running command line: bazel-bin/apps/blinky/simulator_blinky
43               Awaiting connection on port 33000
44
45            (Your exact port may be different; that's OK.)
46
47         #. Keep this process running. This process is your simulated device.
48            It's listening on a local port for connections. In the next step
49            you connect to the simulated device over the local port.
50
51      .. tab-item:: CLI
52         :sync: cli
53
54         #. Run the following command. You should see
55            ``Awaiting connection on port XXXXX``.
56
57            .. code-block:: console
58
59               $ bazelisk run //apps/blinky:simulator_blinky
60               INFO: Analyzed target //apps/blinky:simulator_blinky (0 packages loaded, 0 targets configured).
61               INFO: Found 1 target...
62               Target //apps/blinky:simulator_blinky up-to-date:
63                 bazel-bin/apps/blinky/simulator_blinky
64               INFO: Elapsed time: 0.140s, Critical Path: 0.00s
65               INFO: 1 process: 1 internal.
66               INFO: Build completed successfully, 1 total action
67               INFO: Running command line: bazel-bin/apps/blinky/simulator_blinky
68               =====================================
69               === Pigweed Sense: Host Simulator ===
70               =====================================
71               Simulator is now running. To connect with a console,
72               either run one in a new terminal:
73
74                  $ bazelisk run //<app>:simulator_console
75
76               where <app> is e.g. blinky, factory, or production, or launch
77               one from VSCode under the 'Bazel Build Targets' explorer tab.
78
79               Press Ctrl-C to exit
80               Awaiting connection on port 33000
81
82            The simulated device is now running on your development host.
83
84         #. Keep this process running. This process is your simulated device.
85            It's listening on a local port for connections. In the next step
86            you connect to the simulated device over the local port.
87
88#. Connect to the simulated app with :ref:`module-pw_console`, Pigweed's
89   extensible interactive console.
90
91   .. tab-set::
92
93      .. tab-item:: VS Code
94         :sync: vsc
95
96         In **Bazel Build Targets** right-click the
97         **:simulator_console (native_binary)** (also under **//apps/blinky**)
98         and then select **Run target**.
99
100      .. tab-item:: CLI
101         :sync: cli
102
103         Open another terminal window or tab and run the following command.
104
105         .. code-block:: console
106
107            $ bazelisk run //apps/blinky:simulator_console
108
109   You should see ``pw_console`` start up like this:
110
111   .. figure:: https://storage.googleapis.com/pigweed-media/sense/20240802/simulator_console.png
112
113#. Look at the **Device Logs** table. You should see the simulated device
114   sending ``LED blinking`` messages every second.
115
116#. Simulate polling the Pico's temperature by typing the following into
117   **Python Repl** (bottom-left pane, look for the ``>>>`` input prompt)
118   and then pressing :kbd:`Enter`:
119
120   .. code-block:: pycon
121
122      >>> device.rpcs.board.Board.OnboardTemp()
123
124   .. admonition:: What's a REPL?
125
126      `REPL`_ stands for Read Eval Print Loop. It's an interactive
127      shell that takes your input, executes it, prints the result
128      of the execution back to your interactive shell, and then
129      repeats the loop. The console in Chrome DevTools is an example
130      of a REPL. Running ``python3`` by itself on a command line
131      opens the Python 3 REPL.
132
133   In the **Python Results** section you should see output like this:
134
135   .. code-block:: pycon
136
137      >>> device.rpcs.board.Board.OnboardTemp()
138      (Status.OK, board.OnboardTempResponse(temp=20.0))
139
140#. Send a command over RPC that toggles the simulated device's LED:
141
142   .. code-block:: pycon
143
144      >>> device.rpcs.blinky.Blinky.ToggleLed()
145      (Status.OK, pw.protobuf.Empty())
146
147   .. admonition:: Exercise
148
149      Can you figure out how to create a new RPC method that
150      blinks the LED twice? See
151      :ref:`showcase-sense-tutorial-appendix-rpc-solution`
152      for a solution.
153
154#. Close ``pw_console``:
155
156   .. tab-set::
157
158      .. tab-item:: VS Code
159         :sync: vsc
160
161         Press :kbd:`Ctrl+D` twice to close ``pw_console`` and then
162         press any key to close the terminal that ``pw_console`` launched in.
163
164      .. tab-item:: CLI
165         :sync: cli
166
167         Press :kbd:`Ctrl+D` twice to close ``pw_console``.
168
169#. Stop running the simulated device:
170
171   .. tab-set::
172
173      .. tab-item:: VS Code
174         :sync: vsc
175
176         Press :kbd:`Ctrl+C` to close the simulated device and then
177         press any key to close the terminal that it launched in.
178
179      .. tab-item:: CLI
180         :sync: cli
181
182         Press :kbd:`Ctrl+C` to close the simulated device.
183
184   .. admonition:: Troubleshooting
185
186      * **Bazel run failed: Unknown error**. You can ignore this.
187        When you close the terminal with :kbd:`Control+C` it sets
188        exit code ``255``, which the Bazel extension interprets as
189        "something went wrong". We're working on closing the simulated
190        device in a cleaner way.
191
192Of course polling a simulated temperature and toggling a simulated LED
193is rather boring but hopefully you can see how much faster your team's
194development can be when you have a simulated version of your embedded
195system to work against.
196
197Let's explore ``pw_console`` a bit more and then we'll move on to
198working with physical devices.
199
200.. _showcase-sense-tutorial-web:
201
202-------------------------
203Try the web-based console
204-------------------------
205``pw_console`` also provides a web-based UI that's high performance,
206accessible, and easy to make plugins for. Try it now:
207
208#. Launch the simulated device again:
209
210   .. tab-set::
211
212      .. tab-item:: VS Code
213         :sync: vsc
214
215         Start up the simulated device again by going to **Bazel Build
216         Targets** right-clicking the **:simulator_blinky (native_binary)** target
217         (under **//apps/blinky**) and then selecting **Run target**.
218
219         .. caution::
220
221            Make sure to run **:simulator_blinky**, not **:simulator_console**.
222            The first target starts the simulated device. The second target
223            attempts to connect to a simulated device. The second target naturally
224            won't work if a simulated device isn't running.
225
226      .. tab-item:: CLI
227         :sync: cli
228
229         .. code-block:: console
230
231            $ bazelisk run //apps/blinky:simulator_blinky
232            # ...
233            INFO: Running command line: bazel-bin/apps/blinky/simulator_blinky
234            Awaiting connection on port 33000
235
236   .. note::
237
238      We had you close the simulated device in the last section and then
239      restart it again here because we're sorting out some issues around
240      simulated devices not accepting new connections reliably.
241
242#. Start the web-based console:
243
244   .. tab-set::
245
246      .. tab-item:: VS Code
247         :sync: vsc
248
249         In **Bazel Build Targets** right-click
250         **:simulator_webconsole (native_binary)** (under **//apps/blinky**)
251         then select **Run target**.
252
253      .. tab-item:: CLI
254         :sync: cli
255
256         Open another terminal window or tab and run the following command.
257
258         .. code-block:: console
259
260            $ bazelisk run //apps/blinky:simulator_webconsole
261
262   You should see the console open in your web browser:
263
264   .. figure:: https://storage.googleapis.com/pigweed-media/sense/20240802/webconsole.png
265
266   In the logs table you should see simulated messages as before.
267
268#. Send an RPC to poll the simulated device's temperature again:
269
270   .. code-block:: pycon
271
272      >>> device.rpcs.board.Board.OnboardTemp()
273
274   .. figure:: https://storage.googleapis.com/pigweed-media/sense/20240802/webconsole_repl.png
275
276#. Type ``"00:00"`` (note the double quotes) into the search bar of either
277   of the two tables to only show logs that occurred in the first minute of
278   logging.
279
280   .. admonition:: Troubleshooting
281
282      **Don't see a search bar?** Click the magnifying glass icon. The
283      search bar is collapsed by default on narrow screens.
284
285   .. note::
286
287      Why two tables? To demonstrate that you can filter the logs in
288      each table by different criteria. You can close a table by
289      clicking the **X** button. You can add even more tables by
290      clicking the three dot button and then selecting **Split right**
291      or **Split down**.
292
293   See :ref:`module-pw_web-log-viewer-filter` to learn more about filtering.
294
295   .. figure:: https://storage.googleapis.com/pigweed-media/sense/20240802/webconsole_filter.png
296
297#. Close the web-based console and simulated app. You're done
298   with them for now. In the terminals where you launched these,
299   each can be closed by pressing :kbd:`Control+C`.
300
301.. _showcase-sense-tutorial-sim-console-more:
302
303Learn more about pw_console
304===========================
305Check out the :ref:`user guide <module-pw_console-user_guide>` to learn more
306about pw_console's navigation shortcuts, features, and configuration options.
307See the :ref:`embedding guide <module-pw_console-embedding>` and
308:ref:`plugin guide <module-pw_console-plugins>` to learn more about
309customizing ``pw_console`` for your project's needs.
310
311Check out :ref:`module-pw_web-log-viewer` for more information about the
312web-based version of ``pw_console``.
313
314.. _showcase-sense-tutorial-sim-summary:
315
316-------
317Summary
318-------
319Being able to run a simulated version of your product directly on your
320development host is another way that Pigweed makes embedded product
321development faster, more robust, and more reliable. For one, it's usually
322just much faster to iterate on code running on your computer versus a
323separate embedded device. For two, if you're bringing a new product to
324market, the hardware for your new device might not even exist yet!
325
326Next, head over to :ref:`showcase-sense-tutorial-sim-summary` to
327get Sense running a real Raspberry Pi Pico.
328
329--------
330Appendix
331--------
332
333.. _showcase-sense-tutorial-appendix-rpc-solution:
334
335Create a BlinkTwice RPC method
336==============================
337Here's one possible solution to the RPC creation exercise in
338:ref:`showcase-sense-tutorial-sim`.
339
340.. tab-set::
341
342   .. tab-item:: blinky.proto
343
344      Declare a ``BlinkTwice()`` protobuf method and
345      ``BlinkTwiceRequest`` protobuf message.
346
347      .. code-block:: protobuf
348
349         // //modules/blinky/blinky.proto
350
351         Service Blinky {
352           // ...
353           rpc BlinkTwice(BlinkTwiceRequest) returns (pw.protobuf.Empty);
354           // ...
355         }
356
357         message BlinkIdleResponse {
358           // ...
359         }
360
361         message BlinkTwiceRequest {}
362
363         message BlinkRequest {
364           // ...
365         }
366
367   .. tab-item:: service.h
368
369      Declare the method handler in the RPC server.
370
371      .. code-block:: c++
372
373         // //modules/blinky/service.h
374
375         // ...
376
377         pw::Status Blink(const blinky_BlinkRequest& request, pw_protobuf_Empty&);
378
379         pw::Status BlinkTwice(const blinky_BlinkTwiceRequest&, pw_protobuf_Empty&);
380
381         pw::Status Pulse(const blinky_CycleRequest& request, pw_protobuf_Empty&);
382
383         // ...
384
385   .. tab-item:: service.cc
386
387      Implement the method handler in the RPC server.
388
389      .. code-block:: c++
390
391         // //modules/blinky/service.cc
392
393         pw::Status BlinkyService::Blink(const blinky_BlinkRequest& request,
394                                         pw_protobuf_Empty&) {
395           // ...
396         }
397
398         pw::Status BlinkyService::BlinkTwice(const blinky_BlinkTwiceRequest&,
399                                              pw_protobuf_Empty&) {
400           return blinky_.BlinkTwice();
401         }
402
403         pw::Status BlinkyService::Pulse(const blinky_CycleRequest& request,
404                                         pw_protobuf_Empty&) {
405           // ...
406         }
407
408   .. tab-item:: blinky.h
409
410      Declare the ``BlinkTwice()`` hardware abstraction layer (HAL) method.
411
412      .. code-block:: c++
413
414         // //modules/blinky/blinky.h
415
416         // ...
417         namespace am {
418          public:
419           // ...
420           pw::Status::BlinkTwice() PW_LOCKS_EXCLUDED(lock_);
421           // ...
422         }  // namespace am
423
424
425   .. tab-item:: blinky.cc
426
427      Implement the ``BlinkTwice()`` HAL method.
428
429      .. code-block:: c++
430
431         // //modules/blinky/blinky.cc
432
433         pw::Status Blinky::Blink(uint32_t blink_count, uint32_t interval_ms) {
434           // ...
435         }
436
437         pw::Status Blinky::BlinkTwice() {
438           uint32_t num_toggles = 4;
439           uint32_t interval_ms = 1000;
440           PW_LOG_INFO(
441               "Blinking %u times at a %ums interval", num_toggles / 2, interval_ms);
442           pw::chrono::SystemClock::duration interval =
443               pw::chrono::SystemClock::for_at_least(
444                   std::chrono::milliseconds(interval_ms));
445           timer_.Cancel();
446           {
447             std::lock_guard lock(lock_);
448             monochrome_led_->TurnOff();
449             num_toggles_ = num_toggles;
450             interval_ = interval;
451           }
452           return ScheduleToggle();
453         }
454
455         void Blinky::Pulse(uint32_t interval_ms) {
456           // ...
457         }
458