Standalone nodes
Constantly adding and running nodes through the node stack is highly inefficient during development. In that scenario, you want to be able to run your node as a regular Rust/Python program and use your favorite IDE to debug it.
Debugging a node
Section titled “Debugging a node”While debugging the nodes based on the logs can be quite helpful, nothing beats the ability to fire up the debugger to inspect the code that is supposed to run inside the peppy node stack.
To support this, peppy can run a node in “standalone mode”—it communicates with other nodes in the stack but runs as a regular program outside of it, allowing you to use standard debugging tools.
Let’s create a new node:
-
Initialize the node:
Terminal window peppy node init --toolchain uv standaloneTerminal window peppy node init --toolchain cargo standalone -
Navigate into the directory:
Terminal window cd standalone
with the following configuration:
{ schema_version: 1, manifest: { name: "standalone", tag: "0.1.0", language: "python", }, process: { add_cmd: [ "uv", "sync" ], start_cmd: [ "uv", "run", "standalone" ] }, // A bunch of fake parameters required to start our node parameters: { device: { physical: "string", sim: "string", priority: "string" }, video: { frame_rate: "u16", resolution: { width: "u16", height: "u16", }, encoding: "string", }, }, interfaces: {}}{ schema_version: 1, manifest: { name: "standalone", tag: "0.1.0", language: "rust", }, process: { add_cmd: [ "cargo", "build", "--release" ], start_cmd: [ "./target/release/standalone" ] }, // A bunch of fake parameters required to start our node parameters: { device: { physical: "string", sim: "string", priority: "string" }, video: { frame_rate: "u16", resolution: { width: "u16", height: "u16", }, encoding: "string", }, }, interfaces: {}}Now sync the node:
peppy node syncNow if you try to run the node:
uv run standaloneYou’ll run into the following error:
RuntimeError: missing required parameter(s) for standalone mode: device, video. Provide them via StandaloneConfig().with_parameters()cargo runYou’ll run into the following error:
Error: ParameterDeserialization(ParameterDeserializationError(["device", "video"]))These parameters are usually provided during peppy node start, but since we want this node to run as a standalone program, we need to pass them outside of the peppy daemon environment.
We can define our parameters in a params.json file at the root of the project:
{ "device": { "physical": "/dev/video0", "sim": "virtual_camera", "priority": "high" }, "video": { "frame_rate": 30, "resolution": { "width": 1920, "height": 1080 }, "encoding": "h264" }}Then modify the source file to read from this file:
import json
from peppygen import NodeBuilder, NodeRunner, StandaloneConfigfrom peppygen.parameters import Parameters
async def setup(params: Parameters, node_runner: NodeRunner): print("Inside the setup callback!")
def main(): # Parameters can also be defined directly in code: # # from peppygen.parameters import Device, Video, VideoResolution # # params = Parameters( # device=Device( # physical="/dev/video0", # sim="virtual_camera", # priority="high", # ), # video=Video( # frame_rate=30, # resolution=VideoResolution( # width=1920, # height=1080, # ), # encoding="h264", # ), # )
with open("params.json") as f: params = json.load(f)
standalone_config = StandaloneConfig().with_parameters(params) NodeBuilder().standalone(standalone_config).run(setup)
if __name__ == "__main__": main()Now if we run the following command again:
uv run standaloneuse peppygen::{NodeBuilder, Parameters, Result};use peppylib::runtime::StandaloneConfig;
fn main() -> Result<()> { // Parameters can also be defined directly in code: // // use peppygen::parameters::{device::Device, video::{Video, VideoResolution}}; // // let params = Parameters { // device: Device { // physical: "/dev/video0".to_string(), // sim: "virtual_camera".to_string(), // priority: "high".to_string(), // }, // video: Video { // frame_rate: 30, // resolution: VideoResolution { // width: 1920, // height: 1080, // }, // encoding: "h264".to_string(), // }, // };
let json = std::fs::read_to_string("params.json") .expect("failed to read params.json"); let params: Parameters = serde_json::from_str(&json) .expect("failed to parse params.json");
let standalone_config = StandaloneConfig::new().with_parameters(¶ms); NodeBuilder::new() .standalone(standalone_config) .run(|args: Parameters, node_runner| async { println!("Inside the run closure!"); let _ = args; let _ = node_runner; Ok(()) })}Now if we run the following command again:
cargo runThe node should run without a crash.
The standalone object allows us to load parameters from an external JSON file and pass them to the node, which in turn allows us to run our node as a regular Rust/Python program.
Note that the standalone config is completely ignored when a node is run with peppy node start, all parameters provided during the node start operation take precedence.