|
| 1 | +#pragma once |
| 2 | + |
| 3 | +#include <cstdint> |
| 4 | +#include <array> |
| 5 | +#include <cstring> |
| 6 | +#include <stdexcept> |
| 7 | +#include <random> |
| 8 | +#include <memory> |
| 9 | +#include <condition_variable> |
| 10 | +#include <mutex> |
| 11 | +#include "behaviortree_cpp/basic_types.h" |
| 12 | +#include "behaviortree_cpp/utils/json.hpp" |
| 13 | + |
| 14 | +namespace BT::Monitor { |
| 15 | + |
| 16 | +/* |
| 17 | + * All the messages exchange with the BT executor are multipart ZMQ request-replies. |
| 18 | + * |
| 19 | + * The first part of the request and the reply have fixed size and are described below. |
| 20 | + * The request and reply must have the same value of the fields: |
| 21 | + * |
| 22 | + * - request_id |
| 23 | + * - request_type |
| 24 | + * - protocol_id |
| 25 | + */ |
| 26 | + |
| 27 | +enum RequestType : uint8_t |
| 28 | +{ |
| 29 | + // Request the entire tree defintion as XML |
| 30 | + FULLTREE = 'T', |
| 31 | + // Request the staus of all the nodes |
| 32 | + STATUS = 'S', |
| 33 | + // retrieve the valus in a set of blackboards |
| 34 | + BLACKBOARD = 'B', |
| 35 | + |
| 36 | + // Groot requests the insertion of a breakpoint |
| 37 | + BREAKPOINT_INSERT = 'I', |
| 38 | + // Groot requests to remove a breakpoint |
| 39 | + BREAKPOINT_REMOVE = 'R', |
| 40 | + // Notify Groot that we reached a breakpoint |
| 41 | + BREAKPOINT_REACHED = 'N', |
| 42 | + // Groot will unlock a breakpoint |
| 43 | + BREAKPOINT_UNLOCK = 'U', |
| 44 | + // receive the existing breakpoints in JSON format |
| 45 | + BREAKPOINTS_DUMP = 'D', |
| 46 | + |
| 47 | + // Remove all breakpoints. To be done before disconnecting Groot |
| 48 | + REMOVE_ALL_BREAKPOINTS = 'A', |
| 49 | + |
| 50 | + DISABLE_ALL_BREAKPOINTS = 'X', |
| 51 | + |
| 52 | + UNDEFINED = 0, |
| 53 | +}; |
| 54 | + |
| 55 | +inline const char* ToString(const RequestType& type) |
| 56 | +{ |
| 57 | + switch(type) |
| 58 | + { |
| 59 | + case RequestType::FULLTREE: return "full_tree"; |
| 60 | + case RequestType::STATUS: return "status"; |
| 61 | + case RequestType::BLACKBOARD: return "blackboard"; |
| 62 | + |
| 63 | + case RequestType::BREAKPOINT_INSERT: return "breakpoint_insert"; |
| 64 | + case RequestType::BREAKPOINT_REMOVE: return "breakpoint_remove"; |
| 65 | + case RequestType::BREAKPOINT_REACHED: return "breakpoint_reached"; |
| 66 | + case RequestType::BREAKPOINT_UNLOCK: return "breakpoint_unlock"; |
| 67 | + case RequestType::REMOVE_ALL_BREAKPOINTS: return "breakpoint_remove_all"; |
| 68 | + case RequestType::BREAKPOINTS_DUMP: return "breakpoints_dump"; |
| 69 | + case RequestType::DISABLE_ALL_BREAKPOINTS: return "disable_breakpoints"; |
| 70 | + |
| 71 | + case RequestType::UNDEFINED: return "undefined"; |
| 72 | + } |
| 73 | + return "undefined"; |
| 74 | +} |
| 75 | + |
| 76 | +constexpr uint8_t kProtocolID = 1; |
| 77 | +using TreeUniqueUUID = std::array<char, 16>; |
| 78 | + |
| 79 | +struct RequestHeader |
| 80 | +{ |
| 81 | + uint32_t unique_id = 0; |
| 82 | + uint8_t protocol = kProtocolID; |
| 83 | + RequestType type = RequestType::UNDEFINED; |
| 84 | + |
| 85 | + static size_t size() { |
| 86 | + return sizeof(uint32_t) + sizeof(uint8_t) + sizeof(uint8_t); |
| 87 | + } |
| 88 | + |
| 89 | + RequestHeader() = default; |
| 90 | + |
| 91 | + RequestHeader(RequestType type): type(type) |
| 92 | + { |
| 93 | + // a random number for request_id will do |
| 94 | + static std::random_device rd; |
| 95 | + std::mt19937 mt(rd()); |
| 96 | + std::uniform_int_distribution<uint32_t> dist; |
| 97 | + unique_id = dist(mt); |
| 98 | + } |
| 99 | + |
| 100 | + bool operator==(const RequestHeader& other) const |
| 101 | + { |
| 102 | + return type == other.type && |
| 103 | + protocol == other.protocol && |
| 104 | + unique_id == other.unique_id; |
| 105 | + } |
| 106 | + bool operator!=(const RequestHeader& other) const |
| 107 | + { |
| 108 | + return !(*this == other); |
| 109 | + } |
| 110 | +}; |
| 111 | + |
| 112 | +struct ReplyHeader |
| 113 | +{ |
| 114 | + RequestHeader request; |
| 115 | + TreeUniqueUUID tree_id; |
| 116 | + |
| 117 | + static size_t size() { |
| 118 | + return RequestHeader::size() + 16; |
| 119 | + } |
| 120 | + |
| 121 | + ReplyHeader() { |
| 122 | + tree_id.fill(0); |
| 123 | + } |
| 124 | +}; |
| 125 | + |
| 126 | +template <typename T> inline |
| 127 | +unsigned Serialize(char* buffer, unsigned offset, T value) |
| 128 | +{ |
| 129 | + memcpy(buffer + offset, &value, sizeof(T)); |
| 130 | + return sizeof(T); |
| 131 | +} |
| 132 | + |
| 133 | +template <typename T> inline |
| 134 | + unsigned Deserialize(const char* buffer, unsigned offset, T& value) |
| 135 | +{ |
| 136 | + memcpy(reinterpret_cast<char*>(&value), buffer + offset, sizeof(T)); |
| 137 | + return sizeof(T); |
| 138 | +} |
| 139 | + |
| 140 | + |
| 141 | +inline std::string SerializeHeader(const RequestHeader& header) |
| 142 | +{ |
| 143 | + std::string buffer; |
| 144 | + buffer.resize(6); |
| 145 | + unsigned offset = 0; |
| 146 | + offset += Serialize(buffer.data(), offset, header.protocol); |
| 147 | + offset += Serialize(buffer.data(), offset, uint8_t(header.type)); |
| 148 | + offset += Serialize(buffer.data(), offset, header.unique_id); |
| 149 | + return buffer; |
| 150 | +} |
| 151 | + |
| 152 | +inline std::string SerializeHeader(const ReplyHeader& header) |
| 153 | +{ |
| 154 | + // copy the first part directly (6 bytes) |
| 155 | + std::string buffer = SerializeHeader(header.request); |
| 156 | + // add the following 16 bytes |
| 157 | + unsigned const offset = 6; |
| 158 | + buffer.resize(offset + 16); |
| 159 | + Serialize(buffer.data(), offset, header.tree_id); |
| 160 | + return buffer; |
| 161 | +} |
| 162 | + |
| 163 | +inline RequestHeader DeserializeRequestHeader(const std::string& buffer) |
| 164 | +{ |
| 165 | + RequestHeader header; |
| 166 | + unsigned offset = 0; |
| 167 | + offset += Deserialize(buffer.data(), offset, header.protocol); |
| 168 | + uint8_t type; |
| 169 | + offset += Deserialize(buffer.data(), offset, type); |
| 170 | + header.type = static_cast<Monitor::RequestType>(type); |
| 171 | + offset += Deserialize(buffer.data(), offset, header.unique_id); |
| 172 | + return header; |
| 173 | +} |
| 174 | + |
| 175 | + |
| 176 | +inline ReplyHeader DeserializeReplyHeader(const std::string& buffer) |
| 177 | +{ |
| 178 | + ReplyHeader header; |
| 179 | + header.request = DeserializeRequestHeader(buffer); |
| 180 | + unsigned const offset = 6; |
| 181 | + Deserialize(buffer.data(), offset, header.tree_id); |
| 182 | + return header; |
| 183 | +} |
| 184 | + |
| 185 | +struct Breakpoint |
| 186 | +{ |
| 187 | + using Ptr = std::shared_ptr<Breakpoint>; |
| 188 | + |
| 189 | + // used to enable/disable the breakpoint |
| 190 | + bool enabled = true; |
| 191 | + |
| 192 | + uint16_t node_uid = 0; |
| 193 | + |
| 194 | + // interactive breakpoints are unblucked using unlockBreakpoint() |
| 195 | + bool is_interactive = true; |
| 196 | + |
| 197 | + // used by interactive breakpoints to wait for unlocking |
| 198 | + std::condition_variable wakeup; |
| 199 | + |
| 200 | + std::mutex mutex; |
| 201 | + |
| 202 | + // set to true to unlock an interactive breakpoint |
| 203 | + bool ready = false; |
| 204 | + |
| 205 | + // once finished self-destroy |
| 206 | + bool remove_when_done = false; |
| 207 | + |
| 208 | + // result to be returned |
| 209 | + NodeStatus desired_status = NodeStatus::SKIPPED; |
| 210 | +}; |
| 211 | + |
| 212 | + |
| 213 | +void to_json(nlohmann::json& js, const Breakpoint& bp) { |
| 214 | + js = nlohmann::json { |
| 215 | + {"enabled", bp.enabled}, |
| 216 | + {"uid", bp.node_uid}, |
| 217 | + {"interactive", bp.is_interactive}, |
| 218 | + {"once", bp.remove_when_done}, |
| 219 | + {"desired_status", toStr(bp.desired_status)} |
| 220 | + }; |
| 221 | +} |
| 222 | + |
| 223 | +void from_json(const nlohmann::json& js, Breakpoint& bp) { |
| 224 | + js.at("enabled").get_to(bp.enabled); |
| 225 | + js.at("uid").get_to(bp.node_uid); |
| 226 | + js.at("interactive").get_to(bp.is_interactive); |
| 227 | + js.at("once").get_to(bp.remove_when_done); |
| 228 | + const std::string desired_value = js.at("desired_status").get<std::string>(); |
| 229 | + bp.desired_status = convertFromString<NodeStatus>(desired_value); |
| 230 | +} |
| 231 | + |
| 232 | +} |
0 commit comments