2021-08-08 17:25:41 -04:00
|
|
|
|
/*
|
|
|
|
|
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"
|
|
|
|
|
|
|
|
|
|
namespace RDM {
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @brief Responder::Responder
|
|
|
|
|
*/
|
|
|
|
|
Responder::Responder()
|
|
|
|
|
: Device()
|
|
|
|
|
{
|
2021-08-09 15:04:49 -04:00
|
|
|
|
deviceModelID = 1;
|
|
|
|
|
deviceModelDescription = "Basic RDM Responder";
|
|
|
|
|
|
2021-08-08 17:25:41 -04:00
|
|
|
|
// E1.20 required parameters
|
2021-08-09 15:04:49 -04:00
|
|
|
|
// DISC_UNIQUE_BRANCH
|
|
|
|
|
// DISC_MUTE
|
|
|
|
|
// DISC_UN_MUTE
|
|
|
|
|
|
|
|
|
|
// addt'l parameters
|
|
|
|
|
// COMMS_STATUS
|
|
|
|
|
// QUEUED_MESSAGE
|
2021-08-08 17:25:41 -04:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @brief Responder::~Responder
|
|
|
|
|
*/
|
|
|
|
|
Responder::~Responder()
|
|
|
|
|
{
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @brief Responder::receive
|
|
|
|
|
* @param data
|
|
|
|
|
*/
|
|
|
|
|
void Responder::receive(const std::vector<uint8_t> &data)
|
|
|
|
|
{
|
|
|
|
|
if (data.size() < 3) // SC + SC_SUB + LENGTH
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
Message message;
|
|
|
|
|
|
|
|
|
|
// 6.2.1 START Code
|
|
|
|
|
// This field shall contain the defined RDM START Code (SC_RDM). Controllers
|
|
|
|
|
// and Responders shall always send SC_RDM in this slot, and any packet
|
|
|
|
|
// containing a value other than SC_RDM is outside the scope of this standard.
|
|
|
|
|
if (data[0] != SC_RDM)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
// 6.2.2 Sub START Code
|
|
|
|
|
// This field shall contain the Sub-START Code within RDM that defines this
|
|
|
|
|
// packet structure (SC_SUB_MESSAGE). Future versions of this standard which
|
|
|
|
|
// may have additional or different packet structures would use this field to
|
|
|
|
|
// identify the packet structure being used.
|
|
|
|
|
// Controllers shall always send SC_SUB_MESSAGE in this slot, and Responders
|
|
|
|
|
// shall ignore any packets containing other values.
|
|
|
|
|
if (data[1] != SC_SUB_MESSAGE)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
// 6.2.3 Message Length
|
|
|
|
|
// The Message Length value is defined as the number of slots in the RDM
|
|
|
|
|
// Packet including the START Code and excluding the Checksum. Each slot is
|
|
|
|
|
// an 8-bit value.
|
|
|
|
|
// The Message Length field points to the Checksum High Slot.
|
|
|
|
|
uint8_t length = data[2];
|
|
|
|
|
if (length != data.size() - 2)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
// 6.2.4 Destination UID
|
|
|
|
|
// The Destination UID is the UID of the target device(s).
|
|
|
|
|
message.destination.manufacturer = Message::readType<uint16_t>(data, 3);
|
|
|
|
|
message.destination.device = Message::readType<uint32_t>(data, 5);
|
|
|
|
|
|
|
|
|
|
// 6.2.5 Source UID
|
|
|
|
|
// The Source UID is the UID of the device originating this packet.
|
|
|
|
|
message.source.manufacturer = Message::readType<uint16_t>(data, 9);
|
|
|
|
|
message.source.device = Message::readType<uint32_t>(data, 11);
|
|
|
|
|
|
|
|
|
|
// 6.2.6 Transaction Number (TN)
|
|
|
|
|
// Controller generated packets increment this field every time an RDM packet
|
|
|
|
|
// is transmitted.
|
|
|
|
|
// Responders shall reply with their Transaction Number set to the Transaction
|
|
|
|
|
// Number contained in the controller packet to which they are responding.
|
|
|
|
|
message.transaction = data[15];
|
|
|
|
|
|
|
|
|
|
// 6.2.7 Port ID / Response Type
|
|
|
|
|
// This field serves different functions depending on whether the message is
|
|
|
|
|
// being generated by the controller or the responder.
|
|
|
|
|
message.portID = data[16];
|
|
|
|
|
|
|
|
|
|
// 6.2.8 Message Count
|
|
|
|
|
// The message count field is used by a responder to indicate that additional
|
|
|
|
|
// data is now available for collection by a controller. This data (which
|
|
|
|
|
// might be unrelated to the current message transaction) should be collected
|
|
|
|
|
// by the controller using the GET:QUEUED_MESSAGE command.
|
|
|
|
|
message.messageCount = data[17];
|
|
|
|
|
|
|
|
|
|
// 6.2.9 Sub-Device Field
|
|
|
|
|
// Sub-devices should be used in devices containing a repetitive number of
|
|
|
|
|
// similar modules, such as a dimmer rack. The Sub-Device field allows
|
|
|
|
|
// Parameter messages to be addressed to a specific module within the device
|
|
|
|
|
// to set or get properties of that module.
|
|
|
|
|
// The 16-bit sub-device field provides a range of 512 valid sub-devices,
|
|
|
|
|
// addressed from 1 - 512.
|
|
|
|
|
// The value of 0xFFFF is reserved as a SUB_DEVICE_ALL_CALL. A value of 0x0000
|
|
|
|
|
// shall be used to address the root or base properties of the device that do
|
|
|
|
|
// not belong to any sub-device module.
|
|
|
|
|
// The Parameter ID designates which parameter on the sub-device is being
|
|
|
|
|
// addressed. The use of Sub-Devices is described further in Section 9.
|
|
|
|
|
message.subDevice = Message::readType<uint16_t>(data, 18);
|
|
|
|
|
|
|
|
|
|
// 6.2.10.1 Command Class (CC)
|
|
|
|
|
message.commandClass = data[20];
|
|
|
|
|
|
|
|
|
|
// 6.2.10.2 Parameter ID (PID)
|
|
|
|
|
// The Parameter ID is a 16-bit number that identifies a specific type of
|
|
|
|
|
// Parameter Data. The Parameter ID (PID) may represent either a well known
|
|
|
|
|
// Parameter such as those defined in this document, or a
|
|
|
|
|
// Manufacturer-specific parameter whose details are either published by the
|
|
|
|
|
// Manufacturer for third-party support or proprietary for the Manufacturer’s
|
|
|
|
|
// own use.
|
|
|
|
|
message.propertyID = Message::readType<uint16_t>(data, 21);
|
|
|
|
|
|
|
|
|
|
// 6.2.10.3 Parameter Data Length (PDL)
|
|
|
|
|
// The Parameter Data Length (PDL) is the number of slots included in the
|
|
|
|
|
// Parameter Data area that it precedes. When this field is set to 0x00 it
|
|
|
|
|
// indicates that there is no Parameter Data following.
|
|
|
|
|
uint8_t pdl = data[23];
|
|
|
|
|
|
|
|
|
|
// 6.2.10.4 Parameter Data (PD)
|
|
|
|
|
// The Parameter Data is of variable length. The content format is PID dependent.
|
|
|
|
|
if (pdl > 0)
|
|
|
|
|
for (int i = 0; i < pdl; i++)
|
|
|
|
|
message.appendData(data[24+i]);
|
|
|
|
|
if (message.length() != pdl)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
// 6.2.11 Checksum
|
|
|
|
|
// If the checksum field in the packet does not match the calculated checksum,
|
|
|
|
|
// then the packet shall be discarded and no response sent.
|
|
|
|
|
auto checksum = Message::readType<uint16_t>(data, length);
|
|
|
|
|
if (checksum != message.checksum())
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
receive(&message);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @brief Responder::send
|
|
|
|
|
*/
|
|
|
|
|
void Responder::send()
|
|
|
|
|
{
|
|
|
|
|
if (queued_messages_.empty())
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
auto message = queued_messages_.front();
|
|
|
|
|
queued_messages_.pop();
|
|
|
|
|
send(message);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @brief Responder::send
|
|
|
|
|
* @param data
|
|
|
|
|
*/
|
|
|
|
|
void Responder::send(__attribute__((unused)) const std::vector<uint8_t> &data)
|
|
|
|
|
{
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @brief Responder::send
|
|
|
|
|
* @param message
|
|
|
|
|
*/
|
|
|
|
|
void Responder::send(Message *message)
|
|
|
|
|
{
|
|
|
|
|
// 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.
|
|
|
|
|
if (queued_messages_.size() > 255)
|
|
|
|
|
message->messageCount = 255;
|
|
|
|
|
else
|
|
|
|
|
message->messageCount = queued_messages_.size();
|
|
|
|
|
|
|
|
|
|
std::vector<uint8_t> data;
|
|
|
|
|
data.push_back(SC_RDM);
|
|
|
|
|
data.push_back(SC_SUB_MESSAGE);
|
|
|
|
|
data.push_back(24 + message->length());
|
|
|
|
|
Message::writeType<uint16_t>(data, message->destination.manufacturer);
|
|
|
|
|
Message::writeType<uint32_t>(data, message->destination.device);
|
|
|
|
|
Message::writeType<uint16_t>(data, message->source.manufacturer);
|
|
|
|
|
Message::writeType<uint32_t>(data, message->source.device);
|
|
|
|
|
data.push_back(message->transaction);
|
|
|
|
|
data.push_back(message->portID);
|
|
|
|
|
data.push_back(message->messageCount);
|
|
|
|
|
Message::writeType<uint16_t>(data, message->subDevice);
|
|
|
|
|
data.push_back(message->commandClass);
|
|
|
|
|
Message::writeType<uint16_t>(data, message->propertyID);
|
|
|
|
|
data.push_back(message->length());
|
|
|
|
|
data.insert(data.end(), message->data()->begin(), message->data()->end());
|
|
|
|
|
Message::writeType<uint16_t>(data, message->checksum());
|
|
|
|
|
|
|
|
|
|
send(data);
|
|
|
|
|
delete message;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @brief Responder::receive
|
|
|
|
|
* @param message
|
|
|
|
|
*/
|
|
|
|
|
void Responder::receive(const Message *message)
|
|
|
|
|
{
|
|
|
|
|
Message * response = nullptr;
|
|
|
|
|
if (message->commandClass == DISCOVERY_COMMAND ||
|
|
|
|
|
message->commandClass == GET_COMMAND ||
|
|
|
|
|
message->commandClass == SET_COMMAND)
|
|
|
|
|
{
|
|
|
|
|
// 6.2.8.1 Message Count field for Controller Generated Messages
|
|
|
|
|
// The Message Count shall be set to 0x00 in all controller generated requests.
|
|
|
|
|
if (message->messageCount != 0)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
response = new Message();
|
2021-08-09 15:04:49 -04:00
|
|
|
|
response->source = id;
|
2021-08-08 17:25:41 -04:00
|
|
|
|
response->destination = message->source;
|
|
|
|
|
response->subDevice = message->subDevice;
|
|
|
|
|
response->propertyID = message->propertyID;
|
|
|
|
|
response->transaction = message->transaction;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
switch (message->commandClass) {
|
|
|
|
|
case DISCOVERY_COMMAND:
|
|
|
|
|
rxDiscovery(message, response);
|
|
|
|
|
return;
|
|
|
|
|
case DISCOVERY_COMMAND_RESPONSE:
|
|
|
|
|
rxDiscoveryResponse(message);
|
|
|
|
|
return;
|
|
|
|
|
case GET_COMMAND:
|
|
|
|
|
rxGet(message, response);
|
|
|
|
|
return;
|
|
|
|
|
case GET_COMMAND_RESPONSE:
|
|
|
|
|
rxGetResponse(message);
|
|
|
|
|
return;
|
|
|
|
|
case SET_COMMAND:
|
|
|
|
|
rxSet(message, response);
|
|
|
|
|
return;
|
|
|
|
|
case SET_COMMAND_RESPONSE:
|
|
|
|
|
rxSetResponse(message);
|
|
|
|
|
return;
|
|
|
|
|
default:
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @brief Responder::rxDiscovery
|
|
|
|
|
* @param message
|
|
|
|
|
*/
|
|
|
|
|
void Responder::rxDiscovery(__attribute__((unused)) const Message *message,
|
|
|
|
|
Message* response)
|
|
|
|
|
{
|
|
|
|
|
response->commandClass = DISCOVERY_COMMAND_RESPONSE;
|
|
|
|
|
send(response);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @brief Responder::rxDiscoveryResponse
|
|
|
|
|
* @param message
|
|
|
|
|
*/
|
|
|
|
|
void Responder::rxDiscoveryResponse(__attribute__((unused)) const Message *message)
|
|
|
|
|
{
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @brief Responder::rxGet
|
|
|
|
|
* @param message
|
|
|
|
|
*/
|
|
|
|
|
void Responder::rxGet(const Message *message,
|
|
|
|
|
__attribute__((unused)) Message* response)
|
|
|
|
|
{
|
|
|
|
|
// 9.2.2 Using Sub-Devices
|
|
|
|
|
// Broadcast GET commands sent to the SUB_DEVICE_ALL_CALL Sub-Device ID are
|
|
|
|
|
// not allowed. Any responder receiving a GET command sent to this Sub-Device
|
|
|
|
|
// ID shall respond with a NACK with a NACK Reason Code of
|
|
|
|
|
// NR_SUB_DEVICE_OUT_OF_RANGE.
|
|
|
|
|
if (message->subDevice == SUB_DEVICE_ALL_CALL)
|
|
|
|
|
{
|
|
|
|
|
response->responseType = RESPONSE_TYPE_NACK_REASON;
|
|
|
|
|
response->appendData<uint16_t>(NR_SUB_DEVICE_OUT_OF_RANGE);
|
|
|
|
|
send(response);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (message->subDevice == 0)
|
|
|
|
|
{
|
|
|
|
|
get(message, response);
|
|
|
|
|
send(response);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!sub_devices_.count(message->subDevice))
|
|
|
|
|
{
|
|
|
|
|
response->responseType = RESPONSE_TYPE_NACK_REASON;
|
|
|
|
|
response->appendData<uint16_t>(NR_SUB_DEVICE_OUT_OF_RANGE);
|
|
|
|
|
send(response);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
sub_devices_.at(message->subDevice)->get(message, response);
|
|
|
|
|
send(response);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @brief Responder::rxGetResponse
|
|
|
|
|
* @param message
|
|
|
|
|
*/
|
|
|
|
|
void Responder::rxGetResponse(__attribute__((unused)) const Message *message)
|
|
|
|
|
{
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @brief Responder::rxSet
|
|
|
|
|
* @param message
|
|
|
|
|
*/
|
|
|
|
|
void Responder::rxSet(const Message *message,
|
|
|
|
|
__attribute__((unused)) Message* response)
|
|
|
|
|
{
|
|
|
|
|
if (message->subDevice == 0)
|
|
|
|
|
{
|
|
|
|
|
set(message, response);
|
|
|
|
|
send(response);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (message->subDevice == SUB_DEVICE_ALL_CALL)
|
|
|
|
|
{
|
|
|
|
|
for (auto& [num, dev] : sub_devices_)
|
|
|
|
|
{
|
|
|
|
|
Message * rsp = new Message(*response);
|
|
|
|
|
rsp->subDevice = num;
|
|
|
|
|
dev->set(message, rsp);
|
|
|
|
|
queued_messages_.push(rsp);
|
|
|
|
|
}
|
|
|
|
|
delete response;
|
|
|
|
|
send();
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!sub_devices_.count(message->subDevice))
|
|
|
|
|
{
|
|
|
|
|
response->responseType = RESPONSE_TYPE_NACK_REASON;
|
|
|
|
|
response->appendData<uint16_t>(NR_SUB_DEVICE_OUT_OF_RANGE);
|
|
|
|
|
send(response);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
sub_devices_.at(message->subDevice)->set(message, response);
|
|
|
|
|
send(response);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @brief Responder::rxSetResponse
|
|
|
|
|
* @param message
|
|
|
|
|
*/
|
|
|
|
|
void Responder::rxSetResponse(__attribute__((unused)) const Message *message)
|
|
|
|
|
{
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
} // namespace RDM
|