/* responder.cpp Copyright (c) 2021 Kevin Matz (kevin.matz@gmail.com) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #include "responder.h" #include #include namespace RDM { /** * @brief Responder::Responder * @param id */ Responder::Responder(UID id) : Device(id) , control_field(0) { deviceModelID = 1; deviceModelDescription = "Basic RDM Responder"; subdevice_flag = true; Parameter *parameter; /// \cite RDM 7.5 Discovery Unique Branch Message (DISC_UNIQUE_BRANCH) parameter = addParameter(DISC_UNIQUE_BRANCH); parameter->onDisc([this](MsgPair msg){actionDiscoverUniqueBranch(msg);}); /// \cite RDM 7.6.3 Discovery Mute Message (DISC_MUTE) /// A responder port shall set its Mute flag when it receives this message /// containing its UID, or a broadcast address. parameter = addParameter(DISC_MUTE); parameter->onDisc([this](MsgPair msg){actionDiscoveryMute(msg);}); /// \cite RDM 7.6.4 Discovery Un-Mute Message (DISC_UN_MUTE) /// A responder port shall clear its Mute flag when it receives this message /// containing its UID, or a broadcast address. parameter = addParameter(DISC_UN_MUTE); parameter->onDisc([this](MsgPair msg){actionDiscoveryUnmute(msg);}); /// \cite RDM 10.2.1 Communication Status (COMMS_STATUS) /// The COMMS_STATUS parameter is used to collect information that may be /// useful in analyzing the integrity of the communication system. parameter = addParameter(COMMS_STATUS); parameter->onGet([this](MsgPair msg){actionGetCommsStatus(msg);}); parameter->onSet([this](MsgPair msg){actionSetCommsStatus(msg);}); /// \cite RDM 10.3.1 Get Queued Message (QUEUED_MESSAGE) /// The QUEUED_MESSAGE parameter shall be used to retrieve a message from the /// responder’s message queue. The Message Count field of all response /// messages defines the number of messages that are queued in the responder. /// Each QUEUED_MESSAGE response shall be composed of a single message response. parameter = addParameter(QUEUED_MESSAGE); parameter->onGet([this](MsgPair msg){actionGetQueuedMessage(msg);}); /// \cite RDM 10.3.2 Get Status Messages (STATUS_MESSAGES) /// This parameter is used to collect Status or Error information /// from a device. parameter = addParameter(STATUS_MESSAGES); parameter->onGet([this](MsgPair msg){actionGetStatusMessages(msg);}); /// \cite RDM 10.3.3 Get Status ID Description (STATUS_ID_DESCRIPTION) /// This parameter is used to request an ASCII text description of a given /// Status ID. The description may be up to 32 characters. parameter = addParameter(STATUS_ID_DESCRIPTION); parameter->onGet([this](MsgPair msg){actionGetStatusIdDescription(msg);}); } Responder::~Responder() { } /** * @brief Responder::send * @param buffer */ void Responder::send(const std::vector &buffer) { if (auto sp = sender_.lock()) (*sp)(buffer); // sender still exists } /** * @brief Responder::send * @param response */ void Responder::send(const MsgPtr response) { if (!response) return; if (response->do_not_send) return; /// \cite RDM 6.3.4 Negative Acknowledge (RESPONSE_TYPE_NACK_REASON) /// The response RESPONSE_TYPE_NACK_REASON shall only be used in conjunction with the /// Command Classes GET_COMMAND_RESPONSE & SET_COMMAND_RESPONSE. if (response->responseType == RESPONSE_TYPE_NACK_REASON && !(response->mdb.cc == GET_COMMAND_RESPONSE || response->mdb.cc == SET_COMMAND_RESPONSE)) return; response->source = uid; /// \cite RDM 6.2.8.2 Message Count field for Responder Generated Messages /// If a responder has more than 255 messages queued, then the Message Count /// field shall remain at 255 until the number of queued messages is reduced /// below that number. response->messageCount = std::min(queued_messages_.size(), (size_t)std::numeric_limits::max()); std::vector data; response->write(data); send(data); } /** * @brief Set the message sending callback function. * @param cb Callback function. * @return Token that the owner of the callback must keep for it's lifetime. */ std::shared_ptr Responder::setSender(std::function&)> cb) { // wrap the callback with a shared pointer auto sp = std::make_shared&)>>(std::move(cb)); // store callback (as a weak pointer) sender_ = sp; // return token that caller must keep throughout it's scope return sp; } /** * @brief Responder::receive * @param buffer */ void Responder::receive(const std::vector &buffer) { auto msg = std::make_shared(); msg->read(buffer); receive(msg); } /** * @brief Responder::receive * @param message */ void Responder::receive(const MsgPtr message) { if (message->short_message) { short_message_counter_ = short_message_counter_ == std::numeric_limits::max() ? short_message_counter_ : short_message_counter_++; return; // ignore unreadable messages } // RDM::UID::operator== also returns true for broadcast messages if (message->destination != uid // not for me || message->incorrect_sc // not RDM || message->incorrect_sub_sc // not a supported RDM format ) return; // ignore message if (message->length_mismatch) length_mismatch_counter_ = length_mismatch_counter_ == std::numeric_limits::max() ? length_mismatch_counter_ : length_mismatch_counter_++; if (message->checksum_fail) checksum_fail_counter_ = checksum_fail_counter_ == std::numeric_limits::max() ? checksum_fail_counter_ : checksum_fail_counter_++; if (message->do_not_send) // read error that prevents NAK return; uint8_t response_cc; switch (message->mdb.cc) { case DISCOVERY_COMMAND: response_cc = DISCOVERY_COMMAND_RESPONSE; break; case GET_COMMAND: response_cc = GET_COMMAND_RESPONSE; break; case SET_COMMAND: response_cc = SET_COMMAND_RESPONSE; break; default: return; // responder can ignore _RESPONSE class messages } auto response = std::make_shared(); response->destination = message->source; // send response to sender of message response->subDevice = message->subDevice; // copy sub-device response->mdb.pid = message->mdb.pid; // copy PID response->tn = message->tn; // this reply is a response to the received message response->responseType = RESPONSE_TYPE_ACK; // preset type to ack response->mdb.cc = response_cc; // appropriate command class /// \cite RDM 5.3 Broadcast Message Addressing /// When Broadcast Addressing is used for non-Discovery messages, the /// responders shall not send a response. if (message->destination.isBroadcast() && response->mdb.cc != DISCOVERY_COMMAND_RESPONSE) response->do_not_send = true; if (message->failure_mode) response->nak(NR_FORMAT_ERROR); // nak on any failure modes else dispatch({message, response}); send(response); // send the reply } /** * @brief Responder::reset * @param hard */ void Responder::reset(bool hard) { Device::reset(hard); /// \cite RDM 10.11.2 Reset Device (RESET_DEVICE) /// This parameter shall also clear the Discovery Mute flag. discovery_mute_flag_ = false; } /** * @brief Responder::actionDiscoverUniqueBranch * @param msg */ void Responder::actionDiscoverUniqueBranch(MsgPair msg) { auto [command, response] = msg; if (discovery_mute_flag_) { response->do_not_send = true; return; } UID lower, upper; lower.manufacturer = Message::readType(command->mdb.pd, 0); lower.device = Message::readType(command->mdb.pd, 2); upper.manufacturer = Message::readType(command->mdb.pd, 6); upper.device = Message::readType(command->mdb.pd, 8); if (uid.uid() < lower.uid()) { response->do_not_send = true; return; } if (uid.uid() > upper.uid()) { response->do_not_send = true; return; } response->appendParameterData(uid.manufacturer); response->appendParameterData(uid.device); } /** * @brief Responder::actionDiscoveryMute * @param msg */ void Responder::actionDiscoveryMute(MsgPair msg) { auto [command, response] = msg; discovery_mute_flag_ = true; controller_uid_ = command->source; response->appendParameterData(control_field); } /** * @brief Responder::actionDiscoveryUnmute * @param msg */ void Responder::actionDiscoveryUnmute(MsgPair msg) { auto [_, response] = msg; discovery_mute_flag_ = false; response->appendParameterData(control_field); } /** * @brief Responder::actionGetCommsStatus * @param msg */ void Responder::actionGetCommsStatus(MsgPair msg) { auto [_, response] = msg; response->appendParameterData(short_message_counter_); response->appendParameterData(length_mismatch_counter_); response->appendParameterData(checksum_fail_counter_); } /** * @brief Responder::actionSetCommsStatus * @param msg */ void Responder::actionSetCommsStatus(MsgPair msg) { auto [_, response] = msg; short_message_counter_ = 0; length_mismatch_counter_ = 0; checksum_fail_counter_ = 0; } /** * @brief Responder::actionGetQueuedMessage * @param msg */ void Responder::actionGetQueuedMessage(MsgPair msg) { auto [_, response] = msg; if (queued_messages_.empty()) { response->mdb.pid = STATUS_MESSAGES.pid; actionGetStatusMessages(msg); return; } response = queued_messages_.front(); queued_messages_.pop_front(); last_status_message_ = response; } /** * @brief Responder::actionGetStatusMessages * @param msg */ void Responder::actionGetStatusMessages(MsgPair msg) { auto [command, response] = msg; uint8_t queue = command->mdb.pd.front(); /// Due to the maximum packet length limitation, the total number of status messages sent /// within a single message cannot exceed 25. int count = 25; /// \cite RDM 10.3.2 The Status Type STATUS_GET_LAST_MESSAGE shall be used by the Controller /// to request the retransmission of the last sent Status Message or Queued Message. if (queue == STATUS_GET_LAST_MESSAGE) { if (last_status_message_) response = last_status_message_; else response->nak(NR_DATA_OUT_OF_RANGE); return; } else { /// Status is considered successfully delivered when the responder receives a /// Status Type Requested other than STATUS_GET_LAST_MESSAGE. last_status_message_ = nullptr; } // _CLEARED responses sit in the same queue as their status setting counterparts queue &= 0xf; auto reportStatusQueue = [msg, &count] (std::queue &q) { auto [_, response] = msg; while(!q.empty() && count >= 0) { count--; if (count < 0) break; auto status = q.front(); q.pop(); response->mdb.pd.insert(response->mdb.pd.end(), std::begin(status->bytes), std::end(status->bytes)); } }; while (queue > STATUS_NONE && count >= 0) { if (queued_statuses.count(queue)) reportStatusQueue(queued_statuses.at(queue)); for (auto& [_, dev] : sub_devices_) if (dev->queued_statuses.count(queue)) reportStatusQueue(dev->queued_statuses.at(queue)); queue--; } if (count < 0) response->responseType = RESPONSE_TYPE_ACK_OVERFLOW; /// The responder shall maintain reported status information until it has been successfully /// delivered to the controller. last_status_message_ = response; } /** * @brief Responder::actionGetStatusIdDescription * @param msg */ void Responder::actionGetStatusIdDescription(MsgPair msg) { auto [command, response] = msg; uint16_t status = Message::readType(command->mdb.pd, 0); std::string label = RDM::StatusMessageDescription(status); for (size_t i = 0; i < label.size(); i++) { if (i > 32) break; response->appendParameterData(label.at(i)); } } } // namespace RDM