367 lines
11 KiB
C++
367 lines
11 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"
|
||
|
||
namespace RDM {
|
||
|
||
/**
|
||
* @brief Responder::Responder
|
||
*/
|
||
Responder::Responder()
|
||
: Device()
|
||
{
|
||
deviceModelID = 1;
|
||
deviceModelDescription = "Basic RDM Responder";
|
||
|
||
// E1.20 required parameters
|
||
// DISC_UNIQUE_BRANCH
|
||
// DISC_MUTE
|
||
// DISC_UN_MUTE
|
||
|
||
// addt'l parameters
|
||
// COMMS_STATUS
|
||
// QUEUED_MESSAGE
|
||
}
|
||
|
||
|
||
/**
|
||
* @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);
|
||
|
||
// RDM::UID::operator== also returns true for broadcast messages
|
||
if (message.destination != id)
|
||
return;
|
||
|
||
// 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
|
||
* @param data
|
||
*/
|
||
void Responder::send(__attribute__((unused)) const std::vector<uint8_t> &data)
|
||
{
|
||
|
||
}
|
||
|
||
|
||
/**
|
||
* @brief Responder::send
|
||
* @param message
|
||
*/
|
||
void Responder::send(Message *message)
|
||
{
|
||
if (!message)
|
||
{
|
||
if (queued_messages_.empty())
|
||
return;
|
||
message = queued_messages_.front();
|
||
queued_messages_.pop();
|
||
}
|
||
|
||
// 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)
|
||
{
|
||
if (message->commandClass != DISCOVERY_COMMAND ||
|
||
message->commandClass != GET_COMMAND ||
|
||
message->commandClass != SET_COMMAND)
|
||
return;
|
||
|
||
// 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;
|
||
|
||
auto response = new Message();
|
||
response->source = id;
|
||
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);
|
||
break;
|
||
case GET_COMMAND:
|
||
rxGet(message, response);
|
||
break;
|
||
case SET_COMMAND:
|
||
rxSet(message, response);
|
||
break;
|
||
default:
|
||
break;
|
||
}
|
||
delete message;
|
||
message = nullptr;
|
||
}
|
||
|
||
|
||
/**
|
||
* @brief Responder::rxDiscovery
|
||
* @param message
|
||
*/
|
||
void Responder::rxDiscovery(__attribute__((unused)) const Message *message,
|
||
Message* response)
|
||
{
|
||
response->commandClass = DISCOVERY_COMMAND_RESPONSE;
|
||
queued_messages_.push(response);
|
||
}
|
||
|
||
|
||
/**
|
||
* @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);
|
||
queued_messages_.push(response);
|
||
return;
|
||
}
|
||
|
||
if (message->subDevice == 0)
|
||
{
|
||
get(message, response);
|
||
queued_messages_.push(response);
|
||
return;
|
||
}
|
||
|
||
if (!sub_devices_.count(message->subDevice))
|
||
{
|
||
response->responseType = RESPONSE_TYPE_NACK_REASON;
|
||
response->appendData<uint16_t>(NR_SUB_DEVICE_OUT_OF_RANGE);
|
||
queued_messages_.push(response);
|
||
return;
|
||
}
|
||
|
||
sub_devices_.at(message->subDevice)->get(message, response);
|
||
queued_messages_.push(response);
|
||
}
|
||
|
||
|
||
/**
|
||
* @brief Responder::rxSet
|
||
* @param message
|
||
*/
|
||
void Responder::rxSet(const Message *message,
|
||
__attribute__((unused)) Message* response)
|
||
{
|
||
if (message->subDevice == 0)
|
||
{
|
||
set(message, response);
|
||
queued_messages_.push(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;
|
||
return;
|
||
}
|
||
|
||
if (!sub_devices_.count(message->subDevice))
|
||
{
|
||
response->responseType = RESPONSE_TYPE_NACK_REASON;
|
||
response->appendData<uint16_t>(NR_SUB_DEVICE_OUT_OF_RANGE);
|
||
queued_messages_.push(response);
|
||
return;
|
||
}
|
||
|
||
sub_devices_.at(message->subDevice)->set(message, response);
|
||
queued_messages_.push(response);
|
||
}
|
||
|
||
|
||
} // namespace RDM
|