BCNP 3.2.1
Batched Command Network Protocol
Loading...
Searching...
No Matches
BCNP Core

‍Batched Command Network Protocol: a lightweight, real-time binary protocol for robot control.

[Protocol Version]() [C++ Standard]()

BCNP enables reliable, low-latency command streaming between control stations and robots. It's designed for FRC robotics but works anywhere you need efficient binary messaging with timing guarantees.

Features

  • Schema-driven: Define messages in JSON, generate C++/Python code automatically
  • Real-time safe: Optional zero-allocation mode with StaticVector storage
  • Batched commands: Send multiple timestamped commands in a single packet
  • Integrity checking: CRC32 validation on every packet
  • Connection monitoring: Automatic timeout detection and safe shutdown
  • Full duplex: Bidirectional telemetry and command streaming simultaneously

Architecture

┌─────────────────────────────────────────────────────────────────┐
│ src/bcnp/ │
│ ├── packet.h/cpp — Wire format, encoding/decoding │
│ ├── stream_parser.h/cpp — Byte stream reassembly & framing │
│ ├── dispatcher.h/cpp — Route packets to message handlers │
│ ├── message_queue.h — Timed command execution queue │
│ ├── telemetry_accumulator.h — Batched sensor data transmission │
│ └── transport/ — TCP/UDP adapters (platform-specific) │
└─────────────────────────────────────────────────────────────────┘
  • src/bcnp/: Core protocol library (pure C++17, no platform dependencies)
  • src/bcnp/transport/: Transport adapters that connect byte streams to the dispatcher

Quick Start

1. Define Your Message Types

Create a messages.json file describing your command and telemetry structures:

{
"version": "3.2",
"namespace": "bcnp",
"messages": [
{
"id": 1,
"name": "DriveCmd",
"description": "Differential drive velocity command",
"fields": [
{"name": "vx", "type": "float32", "scale": 10000, "unit": "m/s"},
{"name": "omega", "type": "float32", "scale": 10000, "unit": "rad/s"},
{"name": "durationMs", "type": "uint16", "unit": "ms"}
]
}
]
}

2. Generate Code

python schema/bcnp_codegen.py messages.json --cpp generated --python examples

This creates:

3. Integrate with Your Project

Option A: CMake Subdirectory (Recommended)

# Your project's CMakeLists.txt
set(BCNP_SCHEMA_FILE "${CMAKE_SOURCE_DIR}/src/messages.json" CACHE FILEPATH "")
set(BCNP_GENERATED_DIR "${CMAKE_BINARY_DIR}/generated" CACHE PATH "")
add_subdirectory(libraries/BCNP)
add_executable(robot src/main.cpp)
target_link_libraries(robot PRIVATE bcnp_core)

CMake automatically regenerates code when the schema changes.

Option B: FRC GradleRIO

// build.gradle
model {
components {
frcUserProgram(NativeExecutableSpec) {
binaries.all {
cppCompiler.args "-I${projectDir}/libraries/BCNP/src"
cppCompiler.args "-I${projectDir}/generated"
}
}
}
}

Run codegen before building:

python libraries/BCNP/schema/bcnp_codegen.py src/messages.json --cpp generated

Option C: Standalone Build

cmake -S . -B build
cmake --build build
ctest --test-dir build

Usage Example

Robot Side (C++)

// Create dispatcher and command queue
// Register handler for DriveCmd packets
dispatcher.RegisterHandler<bcnp::DriveCmd>([&](const bcnp::PacketView& pkt) {
for (auto it = pkt.begin_as<bcnp::DriveCmd>(); it != pkt.end_as<bcnp::DriveCmd>(); ++it) {
driveQueue.Push(*it);
}
driveQueue.NotifyReceived(std::chrono::steady_clock::now());
});
// In your control loop:
void Periodic() {
// Feed bytes from transport
dispatcher.PushBytes(rxBuffer, rxLength);
// Update queue timing
driveQueue.Update(std::chrono::steady_clock::now());
// Execute active command
if (auto cmd = driveQueue.ActiveMessage()) {
drivetrain.Drive(cmd->vx, cmd->omega);
} else {
drivetrain.Stop();
}
}
Generic timed message queue for any message type with durationMs field.
void Update(Clock::time_point now)
Update queue state - call once per control loop iteration.
std::optional< MsgType > ActiveMessage() const
Get the currently executing message.
bool Push(const MsgType &message)
Add a message to the back of the queue.
void NotifyReceived(Clock::time_point now)
Notify that messages were received from the network.
Parses BCNP stream and dispatches packets to registered handlers.
Definition dispatcher.h:49
void PushBytes(const uint8_t *data, std::size_t length)
Feed raw bytes from transport (thread-safe)
void RegisterHandler(PacketHandler handler)
Register a handler for a message type (by type)
Definition dispatcher.h:60
Timed message queue for executing duration-based commands.
Zero-copy view into a decoded packet buffer.
Definition packet.h:170

Client Side (Python)

from bcnp_messages import DriveCmd, encode_packet
# Create commands with durations
commands = [
DriveCmd(vx=1.0, omega=0.0, durationMs=500), # Forward 0.5s
DriveCmd(vx=0.0, omega=1.5, durationMs=300), # Turn 0.3s
]
# Encode and send
packet = encode_packet(commands)
socket.send(packet)

Documentation

Document Description
protocol.md Full protocol specification and wire format
schema/schema.md Schema format and code generation guide

Diagnostics

BCNP provides diagnostics for debugging communication issues:

  • StreamParser::ErrorInfo: Reports error code, byte offset, and consecutive error count
  • MessageQueue::GetMetrics(): Tracks messages received, overflows, and skipped commands
  • PacketDispatcher::ParseErrorCount(): Cumulative parse error counter

Publish to SmartDashboard for real-time monitoring:

frc::SmartDashboard::PutBoolean("Network/Connected", dispatcher.IsConnected(now));
frc::SmartDashboard::PutNumber("Network/ParseErrors", dispatcher.ParseErrorCount());
frc::SmartDashboard::PutString("Network/SchemaHash", "0x" + std::to_string(bcnp::kSchemaHash));
uint64_t ParseErrorCount() const
Get parse error count.
bool IsConnected(Clock::time_point now) const
Check if any packets received recently.
constexpr uint32_t kSchemaHash

License

MIT License — See [LICENSE](LICENSE) for details.