286 lines
8.6 KiB
C++
286 lines
8.6 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 Message::Message
|
||
* @param obj
|
||
*/
|
||
Message::Message(const Message &obj)
|
||
: source(obj.source)
|
||
, destination(obj.destination)
|
||
, transaction(obj.transaction)
|
||
, portID(obj.portID)
|
||
, messageCount(obj.messageCount)
|
||
, subDevice(obj.subDevice)
|
||
, propertyID(obj.propertyID)
|
||
, failure_mode(0)
|
||
{
|
||
data_.insert(data_.end(), obj.data()->begin(), obj.data()->end());
|
||
}
|
||
|
||
|
||
/**
|
||
* @brief Message::Message
|
||
* @param data
|
||
*/
|
||
Message::Message(const std::vector<u_int8_t> &data)
|
||
: failure_mode(0)
|
||
{
|
||
read(data);
|
||
}
|
||
|
||
|
||
/**
|
||
* @brief Message::~Message
|
||
*/
|
||
Message::~Message()
|
||
{
|
||
|
||
}
|
||
|
||
|
||
/**
|
||
* @brief Message::read
|
||
* @param data
|
||
*/
|
||
void Message::read(const std::vector<uint8_t> &data)
|
||
{
|
||
if (data.size() < 9) // SC + SC_SUB + LENGTH + DESTINATION
|
||
{
|
||
short_message = true;
|
||
return;
|
||
}
|
||
|
||
// 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)
|
||
{
|
||
incorrect_sc = true;
|
||
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)
|
||
{
|
||
incorrect_sub_sc = true;
|
||
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 + 2 != data.size())
|
||
{
|
||
length_mismatch = true;
|
||
return;
|
||
}
|
||
|
||
// 6.2.4 Destination UID
|
||
// The Destination UID is the UID of the target device(s).
|
||
destination.manufacturer = Message::readType<uint16_t>(data, 3);
|
||
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.
|
||
source.manufacturer = Message::readType<uint16_t>(data, 9);
|
||
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.
|
||
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.
|
||
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.
|
||
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.
|
||
subDevice = Message::readType<uint16_t>(data, 18);
|
||
|
||
// 6.2.10.1 Command Class (CC)
|
||
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.
|
||
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.
|
||
for (int i = 0; i < pdl; i++)
|
||
appendData<uint8_t>(data[24+i]);
|
||
|
||
// 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 = Message::readType<uint16_t>(data, data.size() - 2);
|
||
if (chksum != checksum())
|
||
{
|
||
checksum_fail = true;
|
||
return;
|
||
}
|
||
}
|
||
|
||
|
||
/**
|
||
* @brief Message::write
|
||
* @param data
|
||
*/
|
||
void Message::write(std::vector<uint8_t> &data) const
|
||
{
|
||
data.push_back(SC_RDM);
|
||
data.push_back(SC_SUB_MESSAGE);
|
||
data.push_back(24 + length());
|
||
Message::writeType<uint16_t>(data, destination.manufacturer);
|
||
Message::writeType<uint32_t>(data, destination.device);
|
||
Message::writeType<uint16_t>(data, source.manufacturer);
|
||
Message::writeType<uint32_t>(data, source.device);
|
||
data.push_back(transaction);
|
||
data.push_back(portID);
|
||
data.push_back(messageCount);
|
||
Message::writeType<uint16_t>(data, subDevice);
|
||
data.push_back(commandClass);
|
||
Message::writeType<uint16_t>(data, propertyID);
|
||
data.push_back(length());
|
||
data.insert(data.end(), data_.begin(), data_.end());
|
||
Message::writeType<uint16_t>(data, checksum());
|
||
}
|
||
|
||
|
||
/**
|
||
* @brief Message::nak
|
||
* @param reason
|
||
*/
|
||
void Message::nak(uint16_t reason)
|
||
{
|
||
data_.clear();
|
||
responseType = RESPONSE_TYPE_NACK_REASON;
|
||
appendData<uint16_t>(reason);
|
||
|
||
}
|
||
|
||
|
||
/**
|
||
* @brief Message::checksum
|
||
* @return
|
||
*/
|
||
uint16_t Message::checksum() const
|
||
{
|
||
uint16_t sum = 0;
|
||
|
||
sum = addSum_<uint8_t> (sum, SC_RDM);
|
||
sum = addSum_<uint8_t> (sum, SC_SUB_MESSAGE);
|
||
sum = addSum_<uint8_t> (sum, 24 + length());
|
||
sum = addSum_<uint16_t>(sum, source.manufacturer);
|
||
sum = addSum_<uint32_t>(sum, source.device);
|
||
sum = addSum_<uint16_t>(sum, destination.manufacturer);
|
||
sum = addSum_<uint32_t>(sum, destination.device);
|
||
sum = addSum_<uint8_t> (sum, transaction);
|
||
sum = addSum_<uint8_t> (sum, portID);
|
||
sum = addSum_<uint8_t> (sum, messageCount);
|
||
sum = addSum_<uint16_t>(sum, subDevice);
|
||
sum = addSum_<uint8_t> (sum, commandClass);
|
||
sum = addSum_<uint16_t>(sum, propertyID);
|
||
sum = addSum_<uint8_t> (sum, length());
|
||
for (uint8_t val : data_)
|
||
sum = addSum_<uint8_t>(sum, val);
|
||
|
||
return sum;
|
||
}
|
||
|
||
|
||
/**
|
||
* @brief Message::requiredLength
|
||
* @param length
|
||
* @param response
|
||
* @return
|
||
*/
|
||
bool Message::requiredLength(const size_t length, Message *response) const
|
||
{
|
||
if (data_.size() != length)
|
||
{
|
||
response->nak(NR_FORMAT_ERROR);
|
||
return false;
|
||
}
|
||
return true;
|
||
}
|
||
|
||
} // namespace RDM
|