Wire protocols¶
btview implements two protocols. Everything here is decode/encode only — see
src/btview/protocol/, which has no socket code in it at all, and
tests/test_protocol.py, which pins every layout below.
| BehaviorTree.CPP 4.x | BehaviorTree.CPP 3.x | |
|---|---|---|
| Publisher | BT::Groot2Publisher |
BT::PublisherZMQ |
| Sockets | REP on port, PUB on port + 1 |
REP on server_port, PUB on publisher_port |
| Defaults | 1667 (and 1668) |
REP 1667, PUB 1666 |
| Tree encoding | XML | FlatBuffers |
| Status delivery | client polls | server pushes |
| Transitions | derived by diffing | sent explicitly |
btview flag |
--protocol v4 (default) |
--protocol v3 |
Groot2 (BehaviorTree.CPP 4.x)¶
Reference: include/behaviortree_cpp/loggers/groot2_protocol.h and
src/loggers/groot2_publisher.cpp.
Request header — 6 bytes¶
| Offset | Field | Type | Notes |
|---|---|---|---|
| 0 | protocol |
uint8 |
kProtocolID = 2 |
| 1 | type |
uint8 |
ASCII request tag |
| 2–5 | unique_id |
uint32 |
little-endian |
Request tags (BT::Monitor::RequestType):
| Tag | Name | Implemented by btview |
|---|---|---|
T |
FULLTREE |
yes |
S |
STATUS |
yes |
B |
BLACKBOARD |
yes |
I R A D H |
hook management | no |
N U |
breakpoints | no |
O P |
recording / transitions | no |
Reply — multipart¶
- Header, 22 bytes: the 6-byte request header echoed back, then a 16-byte tree UUID.
- Payload: depends on the request.
An error reply is a two-frame message whose first frame is the literal error; btview
raises the second frame as the message.
FULLTREE¶
Payload is the whole tree as XML — the output of WriteTreeToXML(tree, true, true), which
means:
- every node carries
_uid(the runtime id the status buffer refers to) and_fullpath; - each instantiated subtree is written as its own
<BehaviorTree ID="...">block; - a
<TreeNodesModel>section declares each registration asAction,Condition,Control,DecoratororSubTree.
btview inlines SubTree nodes from the matching block, so the view matches execution
rather than the file layout. Repeated instantiations consume successive blocks with the same
ID. Hand-written XML without _uid still renders; those nodes just never get a status.
STATUS¶
Payload is 3 bytes per node, in tree order:
Status values are BT::NodeStatus: 0 IDLE, 1 RUNNING, 2 SUCCESS, 3 FAILURE,
4 SKIPPED.
A node that is idle now but produced a result earlier is reported as 10 + previous
— so 12 means "idle, and it succeeded". btview decodes that into a status plus a
remembered result and dims the row instead of showing a bare IDLE, which is what lets you
see which branch already finished. Bytes outside 0..4 and 10..14 degrade to IDLE.
BLACKBOARD¶
Request is two frames: the header, then blackboard names joined with ; (empty means all).
The reply payload is a JSON object serialised with MessagePack
(nlohmann::json::to_msgpack). Not every build answers this; btview surfaces the error
rather than hanging.
Implementation note:
btview's blackboard support is written to the upstream source and verified against its own mock, not against every BehaviorTree.CPP build in the wild. If your publisher answers differently, please open an issue with the raw payload.
BehaviorTree.CPP 3.x (PublisherZMQ)¶
Reference: include/behaviortree_cpp_v3/loggers/bt_zmq_publisher.h.
Tree — REP socket¶
Send any message; the reply is a FlatBuffers buffer whose root table is
Serialization.BehaviorTree (schema: fbs/BT_logger.fbs, the same
file BehaviorTree.CPP generates its C++ from). btview reads it with the FlatBuffers
runtime and hand-written accessors — the field slots live in
bt_logger_fb.py and must stay in sync with the
schema.
Relevant fields: root_uid, nodes[] (uid, children_uid[], status, instance_name,
registration_name, port_remaps[]) and node_models[] (registration_name, type).
Status — PUB socket¶
One message per flush():
| Bytes | Field |
|---|---|
| 4 | uint32 LE — size of the status region |
| n | status region: 3 bytes per node, same layout as Groot2 |
| 4 | uint32 LE — number of transitions |
| 12 each | transition records |
A transition record is int32 t_sec, int32 t_usec, uint16 uid, int8 prev_status,
int8 status — 12 bytes, little-endian (BT::SerializeTransition).
v3 has no "idle, remembering" encoding: status bytes are plain NodeStatus values, and
there is no SKIPPED.
btview drains the SUB queue on each poll, keeps the newest snapshot, and reports every
transition seen — so on v3 nothing is lost between polls.
Constraints¶
Upstream allows only one PublisherZMQ per process (a global guard), and the two ports must
differ. btview rejects equal ports up front rather than failing obscurely later.
Testing against btview¶
btview mock implements the publisher side of both protocols with a real ticking tree, so
you can develop a client, verify a network path, or point Groot2 itself at something while
the robot is in pieces:
The mock implements FULLTREE, STATUS and BLACKBOARD, and answers anything else with a
protocol error. It does not bind the v4 PUB port (port + 1), which only carries
breakpoint notifications that btview does not consume.