What’s new in Pigweed: July 2026#
Highlights:
ValueFuture subclassing in pw_async2 - Attach request metadata to futures and resolve them conditionally.
Manage lists of pending futures with ValueListProvider - Resolve pending futures out-of-order, according to custom resolution logic.
Expanded metric types in pw_metric - New
TypedMetricsupport for 64-bit integers, floats, booleans, and tokenized metrics.FreeRTOS Rust bindings - Centralized FreeRTOS Rust FFI bindings in a single
no_stdcrate.STM32 Nucleo-64 board support - Upstream pw_kernel support for the STM32 NUCLEO-F103RB board.
Async and concurrency#
ValueFuture subclassing in pw_async2#
pw::async2::ValueFuture now supports custom derived future
implementations, making it easier to attach request metadata to futures and
resolve them conditionally. CLs: 1
// Define a custom future sub-classing ValueFuture to attach request metadata:
class CustomRequestFuture : public pw::async2::ValueFuture<pw::Status> {
public:
CustomRequestFuture(pw::async2::ValueFuture<pw::Status>&& base,
uint32_t request_id)
: pw::async2::ValueFuture<pw::Status>(std::move(base)),
request_id_(request_id) {}
uint32_t request_id() const { return request_id_; }
private:
uint32_t request_id_;
};
// Use DerivedValueProvider to vend and resolve custom futures:
pw::async2::DerivedValueProvider<CustomRequestFuture> provider;
CustomRequestFuture future = provider.Get(/*request_id=*/42);
// Conditionally resolve based on custom future metadata:
bool resolved =
provider.ResolveIf([](CustomRequestFuture& f) -> std::optional<pw::Status> {
if (f.request_id() == 42) {
return pw::OkStatus();
}
return std::nullopt;
});
Manage lists of pending futures with ValueListProvider#
The new pw::async2::ValueListProvider class makes it easier to implement
custom resolution logic for pending futures. CLs: 1
// Derived future storing requested memory size:
class AllocationFuture : public pw::async2::ValueFuture<pw::ByteSpan> {
public:
AllocationFuture(pw::async2::ValueFuture<pw::ByteSpan>&& base, size_t bytes)
: pw::async2::ValueFuture<pw::ByteSpan>(std::move(base)),
requested_bytes_(bytes) {}
size_t requested_bytes() const { return requested_bytes_; }
private:
size_t requested_bytes_;
};
pw::async2::DerivedValueListProvider<AllocationFuture> provider;
// Tasks request different buffer sizes from memory manager:
AllocationFuture req1 = provider.Get(1024); // Needs 1024 B (1st in line)
AllocationFuture req2 = provider.Get(64); // Needs 64 B (2nd in line)
size_t available_bytes = 128; // Currently 128 B free in memory pool
// Out-of-order allocation: skip req1 (1024 B) and fulfill req2 (64 B):
provider.ResolveFirstMatching(
[&](AllocationFuture& req) -> std::optional<pw::ByteSpan> {
if (req.requested_bytes() <= available_bytes) {
available_bytes -= req.requested_bytes();
return pw::ByteSpan(buffer, req.requested_bytes()); // Fulfills req2
}
return std::nullopt; // Keep req1 pending until RAM is freed
});
Code size improvements in pw_async2 coroutine allocation#
Recent optimizations in pw_async2 coroutine allocation have reduced coroutine code size by 10%. CLs: 1
C++ data structures and utilities#
Reduced code size for FixedDeque POD types#
pw::FixedDeque has a new type-erased Plain Old Data specialization,
resulting in smaller code when working with POD types. CLs: 1
struct CanFrame {
uint32_t id;
uint8_t dlc;
uint8_t data[8];
};
// Fixed-capacity deque for buffering up to 16 CAN frames:
pw::FixedDeque<CanFrame, 16> can_queue;
// Enqueue incoming CAN frame:
can_queue.push_back(CanFrame{0x123, 4, {0x01, 0x02, 0x03, 0x04}});
// Process frame from front of queue:
if (!can_queue.empty()) {
CanFrame frame = can_queue.front();
can_queue.pop_front();
}
System I/O and streams#
MultiBuf shallow copy#
The new pw::multibuf::MultiBuf::ShallowCopy() method enables fast zero-copy
buffer sharing. CLs: 1
#include "pw_multibuf/multibuf.h"
#include "pw_result/result.h"
// Forward buffer to secondary processing task zero-copy:
pw::Status DuplicatePacket(pw::multibuf::MultiBuf& packet) {
PW_TRY_ASSIGN(pw::multibuf::MultiBuf copy, packet.ShallowCopy());
// 'copy' shares underlying buffer data with 'packet' zero-copy
return DispatchPacket(std::move(copy));
}
Logging, debugging, and crash handling#
Expanded metric types in pw_metric#
pw_metric’s TypedMetric now supports 64-bit integers (uint64_t,
int64_t), bool, int32_t, double, and tokenized string metric
types. CLs: 1
PW_METRIC_TYPED(my_group, my_64bit_metric, "my_64bit_metric", uint64_t, 0ULL);
Rust#
FreeRTOS Rust bindings#
The new freertos_sys crate centralizes FreeRTOS Rust FFI bindings into a
single no_std crate and eliminates redundant C++ helper shims across
pw_sync, pw_thread, and pw_time. The generated bindings automatically respect
target-specific configurations, such as
//targets/pw_rp2350/freertos/config/FreeRTOSConfig.h.
CLs: 1
STM32 Nucleo-64 board support#
pw_kernel now supports the NUCLEO-F103RB board, which is based on the STM32F103RB MCU. Userspace is not supported because the STM32F103RB lacks an MPU. CLs: 1
New pw_time and pw_thread backends#
pw_kernel and Zephyr backends are now provided for the pw_time crate. FreeRTOS, pw_kernel, and Zephyr backends are now available for the pw_thread Rust crate. CLs: 1, 2
Human-readable time accessors in pw_time#
Added as_secs(), as_millis(), as_micros(), and as_nanos()
component accessors to pw_time::Duration. CLs: 1
use pw_time::Duration;
fn log_elapsed_time<C: pw_time::Clock>(elapsed: Duration<C>) {
// Convert tick-based duration into human-readable units:
let secs = elapsed.as_secs();
let millis = elapsed.as_millis();
let micros = elapsed.as_micros();
}
Soong rules for Rust crates#
Android Soong build definitions are now provided for the pw_status, pw_stream, pw_tokenizer, and pw_varint Rust crates. CLs: 1
Tokenization#
pw::tokenizer::TokenBytes helper function#
The new TokenBytes C++ utility function formats token hashes directly into
byte arrays. CLs: 1
#include "pw_tokenizer/tokenize.h"
// Tokenize a string log or diagnostic message:
constexpr uint32_t token = PW_TOKEN_STRING("System boot completed");
// Convert 32-bit token into a 4-byte little-endian array for wire transfer:
constexpr std::array<std::byte, 4> token_bytes =
pw::tokenizer::TokenBytes(token);
Toolchains and compilers#
Cortex-M52 support in Zephyr toolchain#
The Bazel Zephyr ARM Clang toolchain now supports Cortex-M52. CLs: 1