blob: b7080abc85ab0f4414746c54090f2da9da9fe02f [file] [log] [blame]
// Copyright 2020 The Pigweed Authors
// Licensed under the Apache License, Version 2.0 (the "License"); you may not
// use this file except in compliance with the License. You may obtain a copy of
// the License at
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations under
// the License.
#include <array>
#include <span>
#include <string_view>
#include "pw_board_led/led.h"
#include "pw_hdlc/encoder.h"
#include "pw_hdlc/rpc_channel.h"
#include "pw_hdlc/rpc_packets.h"
#include "pw_log/log.h"
#include "pw_rpc/echo_service_nanopb.h"
#include "pw_rpc/server.h"
#include "pw_spin_delay/delay.h"
#include "pw_stream/sys_io_stream.h"
#include "remoticon/remoticon_service_nanopb.h"
// ------------------- superloop data -------------------
// This is some "application" state that eventually exported by RPCs.
unsigned superloop_iterations;
// ------------------- pw_rpc subsystem setup -------------------
// There are multiple ways to plumb pw_rpc in your product. In the future,
// Pigweed may offer an optional pre-canned setup; but for now, you must
// manually snap together the modular pieces.
// The RPC system in this code is layered as:
// UART --> pw_sys_io ------> hdlc -------> pw_rpc
// (phy) (transport)
// HDLC converts the raw UART/serial byte stream into a packet stream. Then RPC
// operates at the packet level.
// This is just one way to configure pw_rpc, which is designed to be flexible
// and work over wh atever physical or logical transport you have available.
constexpr size_t kMaxTransmissionUnit = 256; // bytes
// Used to write HDLC data to pw::sys_io. This is an implementation of the
// pw::stream::Stream interface.
pw::stream::SysIoWriter sys_io_writer;
// Set up the output channel for the pw_rpc server to use. This one happens to
// implement the packet in / packet out with HDLC. pw_rpc can use any
// ChannelOptput implementation, including custom ones for your product.
pw::hdlc::RpcChannelOutputBuffer<kMaxTransmissionUnit> hdlc_channel_output(
sys_io_writer, pw::hdlc::kDefaultRpcAddress, "HDLC channel");
// A pw::rpc::Server can have multiple channels (e.g. a UART and a BLE
// connection). In this case, there is only one (HDLC over UART).
pw::rpc::Channel channels[] = {
// Declare the pw_rpc server with the HDLC channel.
pw::rpc::Server server(channels);
// Declare a buffer for decoding incoming HDLC frames.
std::array<std::byte, kMaxTransmissionUnit> input_buffer;
// Decoder object consumes bytes and return if a HDLC packet was completed.
pw::hdlc::Decoder hdlc_decoder(input_buffer);
// ------------------- pw_rpc service registration -------------------
pw::rpc::EchoService echo_service;
remoticon::SuperloopService superloop_service(superloop_iterations);
// TODO FOR WORKSHOP: Declare your service object here!
void RegisterServices() {
// TODO FOR WORKSHOP: Register your service here!
// ------------------- pw_rpc -------------------
constexpr unsigned kHdlcChannelForRpc = pw::hdlc::kDefaultRpcAddress;
constexpr unsigned kHdlcChannelForLogs = 1;
void ParseByteFromUartAndHandleRpcs() {
// Read a byte from the UART if one is available; if not, bail.
std::byte data;
if (!pw::sys_io::TryReadByte(&data).ok()) {
// Byte received. Send the byte to the HDLC decoder; see if a packet finished.
auto result = hdlc_decoder.Process(data);
// Packet didn't parse correctly, so ignore it. In production, this should
// perhaps log or increment a metric (see pw_metric) to track bad packets.
if (!result.ok()) {
// POST-WORKSHOP EXERCISE: Add a tracking metric for bad packets, and
// expose the metric via the pw_metric RPC service. This will require
// making some metrics objects, incrementing them; then creating and
// registering a metric RPC service.
// See
PW_LOG_INFO("Got complete HDLC packet");
// A frame was completed.
pw::hdlc::Frame& hdlc_frame = result.value();
if (hdlc_frame.address() != kHdlcChannelForRpc) {
// We ignore frames that are for unknown addresses, but you could put
// some code here if you wanted to stream custom data from PC --> device.
PW_LOG_WARN("Got packet with no destination; address: %d",
// Packet was validated and correct (CRC, etc); so send it to the RPC server.
// The RPC server may send response packets before returning from this call.
server.ProcessPacket(, hdlc_channel_output);
// TODO FOR WORKSHOP: Add an RPC to change the blink time.
int state = 0;
int counter = 0;
// TODO FOR WORKSHOP: Change this value. 5M is good for Teensy 4.0; what value
// is good for the Discovery?
int counter_max = 5'000'000;
void Blink() {
// Toggle the state if needed.
counter += 1;
// PW_LOG_INFO("Counter: %d", counter);
if (counter < counter_max) {
// Haven't hit a toggle event yet; bail.
state = 1 - state;
counter = 0;
if (state == 0) {
PW_LOG_INFO("Blink High!");
} else {
PW_LOG_INFO("Blink Low!");
// TODO FOR WORKSHOP: Why doesn't this work, when of Blink() above does?
void BlinkNoWorky() {
PW_LOG_INFO("Blink High!");
PW_LOG_INFO("Blink Low!");
int main() {
PW_LOG_INFO("Registering pw_rpc services");
// Superloop!
while (true) {
// Toggle the LED if needed.
// BlinkNoWorky(); // Pop quiz: This doesn't work. Why?
// Examine incoming serial byte; if a packet finished, send it to RPC.
// Increment the number of iterations.
return 0;