Batched Command Network Protocol: a lightweight, real-time binary protocol for robot control.
[
]() [
]()
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++)
for (auto it = pkt.begin_as<bcnp::DriveCmd>(); it != pkt.end_as<bcnp::DriveCmd>(); ++it) {
}
});
void Periodic() {
driveQueue.
Update(std::chrono::steady_clock::now());
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.
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)
Timed message queue for executing duration-based commands.
Zero-copy view into a decoded packet buffer.
Client Side (Python)
from bcnp_messages import DriveCmd, encode_packet
commands = [
DriveCmd(vx=1.0, omega=0.0, durationMs=500),
DriveCmd(vx=0.0, omega=1.5, durationMs=300),
]
packet = encode_packet(commands)
socket.send(packet)
Documentation
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.