442 lines
13 KiB
C++
442 lines
13 KiB
C++
/*
|
||
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 <algorithm>
|
||
#include <limits>
|
||
|
||
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<uint8_t> &buffer) const
|
||
{
|
||
if (auto sp = sender_.lock())
|
||
(*sp)(buffer); // sender still exists
|
||
}
|
||
|
||
|
||
/**
|
||
* @brief Responder::send
|
||
* @param response
|
||
*/
|
||
void Responder::send(const MsgPtr response) const
|
||
{
|
||
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;
|
||
|
||
/// \cite RDM 6.2.5 Source UID
|
||
/// The Source UID is the UID of the device originating this packet.
|
||
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<uint8_t>::max());
|
||
|
||
std::vector<uint8_t> buffer;
|
||
response->write(buffer);
|
||
send(buffer);
|
||
}
|
||
|
||
|
||
/**
|
||
* @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<void> Responder::setSender(std::function<void(const std::vector<uint8_t>&)> cb)
|
||
{
|
||
// wrap the callback with a shared pointer
|
||
auto sp = std::make_shared<std::function<void(const std::vector<uint8_t>&)>>(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<uint8_t> &buffer)
|
||
{
|
||
auto msg = std::make_shared<Message>();
|
||
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<uint16_t>::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<uint16_t>::max()
|
||
? length_mismatch_counter_
|
||
: length_mismatch_counter_++;
|
||
|
||
if (message->checksum_fail)
|
||
checksum_fail_counter_ = checksum_fail_counter_ == std::numeric_limits<uint16_t>::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<Message>();
|
||
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<uint16_t>(command->mdb.pd, 0);
|
||
lower.device = Message::readType<uint32_t>(command->mdb.pd, 2);
|
||
upper.manufacturer = Message::readType<uint16_t>(command->mdb.pd, 6);
|
||
upper.device = Message::readType<uint32_t>(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<StatusPtr> &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<uint16_t>(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
|