286 lines
10 KiB
C++
286 lines
10 KiB
C++
/*
|
||
message.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 "message.h"
|
||
|
||
namespace RDM {
|
||
|
||
/**
|
||
* @brief Message::Message
|
||
*/
|
||
Message::Message()
|
||
: failure_mode(0)
|
||
{
|
||
|
||
}
|
||
|
||
|
||
/**
|
||
* @brief Use a vector to populate the Message fields.
|
||
* @param buffer
|
||
*/
|
||
void Message::read(const std::vector<uint8_t> &buffer)
|
||
{
|
||
|
||
/// \cite RDM 10.3 Collection of Queued and Status Messages
|
||
/// Short Message - This field shall be incremented any time the message terminates (either
|
||
/// due to a BREAK or timeout condition occurring) before a complete Destination UID has
|
||
/// been received.
|
||
short_message = !(buffer.size() >= 9);
|
||
buffer_underrun = !(buffer.size() >= MESSAGE_MINIMUM_LENGTH+2);
|
||
|
||
if (short_message)
|
||
return;
|
||
|
||
/// \cite RDM 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.
|
||
incorrect_sc = !(buffer[0] == SC_RDM);
|
||
|
||
/// \cite RDM 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.
|
||
incorrect_sub_sc = !(buffer[1] == SC_SUB_MESSAGE);
|
||
|
||
/// \cite RDM 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 = buffer[2];
|
||
length_mismatch = !(buffer.size() == length+2);
|
||
|
||
/// \cite RDM 6.2.4 Destination UID
|
||
/// The Destination UID is the UID of the target device(s).
|
||
destination.manufacturer = readType<uint16_t>(buffer, 3);
|
||
destination.device = readType<uint32_t>(buffer, 5);
|
||
|
||
if (buffer_underrun)
|
||
return;
|
||
|
||
/// \cite RDM 6.2.5 Source UID
|
||
/// The Source UID is the UID of the device originating this packet.
|
||
source.manufacturer = readType<uint16_t>(buffer, 9);
|
||
source.device = readType<uint32_t>(buffer, 11);
|
||
|
||
/// \cite RDM 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.
|
||
tn = buffer[15];
|
||
|
||
/// \cite RDM 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.
|
||
portID = buffer[16];
|
||
|
||
/// \cite RDM 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.
|
||
messageCount = buffer[17];
|
||
|
||
/// \cite RDM 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.
|
||
subDevice = readType<uint16_t>(buffer, 18);
|
||
|
||
/// \cite RDM 6.2.10.1 Command Class (CC)
|
||
mdb.cc = buffer[20];
|
||
|
||
/// \cite RDM 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.
|
||
mdb.pid = readType<uint16_t>(buffer, 21);
|
||
|
||
/// \cite RDM 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 = buffer[23];
|
||
pdl_mismatch = !(buffer.size() == MESSAGE_MINIMUM_LENGTH+pdl+2);
|
||
|
||
/// \cite RDM 6.2.10.4 Parameter Data (PD)
|
||
/// The Parameter Data is of variable length.
|
||
if (pdl && !pdl_mismatch)
|
||
for (uint i = 0; i < pdl; i++)
|
||
appendParameterData(buffer[24+i]);
|
||
|
||
/// \cite RDM 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 chksum = readType<uint16_t>(buffer, buffer[2]);
|
||
checksum_fail = !(chksum == checksum());
|
||
}
|
||
|
||
|
||
/**
|
||
* @brief Write the formatted Message fields to a vector.
|
||
* @param buffer
|
||
*
|
||
* > \cite RDM 6.2 Packet Format
|
||
* > All RDM packets shall use the following message structure, with the exception of the
|
||
* > Discovery Unique Branch response message. The format of the Discovery Unique Branch
|
||
* > message is detailed in Section 7.5 Discovery Unique Branch Message.
|
||
*/
|
||
void Message::write(std::vector<uint8_t> &buffer) const
|
||
{
|
||
if (mdb.cc == DISCOVERY_COMMAND_RESPONSE &&
|
||
mdb.pid == DISC_UNIQUE_BRANCH.pid)
|
||
return writeDiscBranch(buffer);
|
||
|
||
buffer.reserve(buffer.size()+MESSAGE_MINIMUM_LENGTH+mdb.pdl()+2); // pre-allocate necessary memory
|
||
buffer.push_back(SC_RDM);
|
||
buffer.push_back(SC_SUB_MESSAGE);
|
||
buffer.push_back(MESSAGE_MINIMUM_LENGTH+mdb.pdl());
|
||
writeType(buffer, destination.manufacturer);
|
||
writeType(buffer, destination.device);
|
||
writeType(buffer, source.manufacturer);
|
||
writeType(buffer, source.device);
|
||
buffer.push_back(tn);
|
||
buffer.push_back(portID);
|
||
buffer.push_back(messageCount);
|
||
writeType(buffer, subDevice);
|
||
buffer.push_back(mdb.cc);
|
||
writeType(buffer, mdb.pid);
|
||
buffer.push_back(mdb.pdl());
|
||
buffer.insert(buffer.end(), mdb.pd.begin(), mdb.pd.end());
|
||
writeType(buffer, checksum(buffer));
|
||
}
|
||
|
||
|
||
/**
|
||
* @brief Write the specialized DISC_UNIQUE_BRANCH RESPONSE format'd fields to a vector.
|
||
* @param buffer
|
||
*
|
||
* > \cite 7.5 Discovery Unique Branch Message.
|
||
* > The response message has a number of exceptions to the normal Packet structure to minimize
|
||
* > the effect of collisions on legacy devices from appearing as a BREAK, NULL START Code, and
|
||
* > data. The format of the DISC_UNIQUE_BRANCH Response Packet is described in Table 7-1.
|
||
*/
|
||
void Message::writeDiscBranch(std::vector<uint8_t> &buffer) const
|
||
{
|
||
if (mdb.cc != DISCOVERY_COMMAND_RESPONSE ||
|
||
mdb.pid != DISC_UNIQUE_BRANCH.pid)
|
||
return write(buffer);
|
||
|
||
buffer.reserve(buffer.size() + MESSAGE_MINIMUM_LENGTH); // pre-allocate necessary memory
|
||
|
||
for (int i = 0; i < 7; i++)
|
||
buffer.push_back(0xfe); //!< Response Preamble bytes
|
||
buffer.push_back(0xaa); //!< Preamble separator byte
|
||
|
||
uint16_t sum = 0;
|
||
for (const auto &v : mdb.pd)
|
||
addSum_(sum, v);
|
||
|
||
std::vector<uint8_t> d = mdb.pd;
|
||
writeType<uint16_t>(d, sum);
|
||
|
||
for ( uint8_t& v : d)
|
||
{
|
||
buffer.push_back(v | 0xaa);
|
||
buffer.push_back(v | 0x55);
|
||
}
|
||
}
|
||
|
||
|
||
/**
|
||
* @brief Message::nak
|
||
* @param reason
|
||
*/
|
||
void Message::nak(uint16_t reason)
|
||
{
|
||
mdb.pd.clear();
|
||
responseType = RESPONSE_TYPE_NACK_REASON;
|
||
appendParameterData(reason);
|
||
}
|
||
|
||
|
||
/**
|
||
* @brief \cite RDM 6.2.11 Checksum
|
||
* @return
|
||
*
|
||
* \cite RDM The Checksum field is the unsigned, modulo 0x10000, 16-bit additive
|
||
* checksum of the entire packet’s slot data, including START Code. The checksum
|
||
* is an additive sum of the 8-bit fields into a 16-bit response value.
|
||
*/
|
||
uint16_t Message::checksum() const
|
||
{
|
||
uint16_t sum = 0;
|
||
addSum_(sum, SC_RDM);
|
||
addSum_(sum, SC_SUB_MESSAGE);
|
||
addSum_(sum, MESSAGE_MINIMUM_LENGTH+mdb.pdl());
|
||
addSum_(sum, destination.manufacturer);
|
||
addSum_(sum, destination.device);
|
||
addSum_(sum, source.manufacturer);
|
||
addSum_(sum, source.device);
|
||
addSum_(sum, tn);
|
||
addSum_(sum, portID);
|
||
addSum_(sum, messageCount);
|
||
addSum_(sum, subDevice);
|
||
addSum_(sum, mdb.cc);
|
||
addSum_(sum, mdb.pid);
|
||
addSum_(sum, mdb.pdl());
|
||
for (const auto &byte : mdb.pd)
|
||
addSum_(sum, byte);
|
||
return sum;
|
||
}
|
||
|
||
|
||
/**
|
||
* @brief Message::checksum
|
||
* @param data
|
||
* @return
|
||
*/
|
||
uint16_t Message::checksum(std::vector<uint8_t> data)
|
||
{
|
||
uint16_t sum = 0;
|
||
for (const auto &byte : data)
|
||
sum += byte;
|
||
return sum;
|
||
}
|
||
|
||
} // namespace RDM
|