1
0
Fork 0

split device types

This commit is contained in:
Kevin Matz 2023-05-02 13:00:19 -04:00
parent 887ce0f5a4
commit 9985f21cb2
9 changed files with 786 additions and 828 deletions

View File

@ -7,6 +7,7 @@ configure_file(../../config.h.in config.h)
target_sources(${PROJECT_NAME}
PUBLIC
basicdevice.h
rdm_controller.h
device.h
message.h
@ -14,8 +15,10 @@ target_sources(${PROJECT_NAME}
parameterdescription.h
responder.h
sensor.h
subdevice.h
status.h
PRIVATE
basicdevice.cpp
rdm_controller.cpp
device.cpp
E1.37-1.h
@ -27,6 +30,7 @@ target_sources(${PROJECT_NAME}
rdm.h
responder.cpp
sensor.cpp
subdevice.cpp
uid.h
)

View File

@ -0,0 +1,305 @@
/*
rdm/basicdevice.cpp
Copyright (c) 2023 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 "E1.37-1.h"
#include "config.h"
#include "basicdevice.h"
namespace RDM {
BasicDevice::BasicDevice()
: deviceManufacturerLabel(MY_ESTA_MANUFACTURER_LABEL)
, deviceModelDescription("Basic RDM Sub-Device")
, deviceModelID(0)
, deviceProductCategory(PRODUCT_CATEGORY_NOT_DECLARED)
, status_reporting_threshold_(STATUS_ADVISORY)
{
queued_statuses.emplace(STATUS_ADVISORY, std::queue<StatusPtr>());
queued_statuses.emplace(STATUS_WARNING, std::queue<StatusPtr>());
queued_statuses.emplace(STATUS_ERROR, std::queue<StatusPtr>());
Parameter *parameter;
/// \cite RDM 9.2.3 Required Sub-Device Messages
/// Devices supporting the use of sub-devices shall support the
/// SUPPORTED_PARAMETERS message in order for the controller to determine
/// which additional messages are supported by the sub-devices.
parameter = addParameter(SUPPORTED_PARAMETERS);
parameter->onGet([this](MsgPair msg){actionGetSupportedParameters(msg);});
parameter = addParameter(PARAMETER_DESCRIPTION);
parameter->onGet([this](MsgPair msg){actionGetParameterDescription(msg);});
/// \cite RDM 10.5.1 Get Device Info (DEVICE_INFO)
/// This parameter is used to retrieve a variety of information about the
/// device that is normally required by a controller.
parameter = addParameter(DEVICE_INFO);
parameter->onGet([this](MsgPair msg){actionGetDeviceInfo(msg);});
/// \cite RDM 10.5.9 Get Software Version Label (SOFTWARE_VERSION_LABEL)
/// This parameter is used to get a descriptive ASCII text label for the
/// devices operating software version.
parameter = addParameter(SOFTWARE_VERSION_LABEL);
parameter->onGet([this](MsgPair msg){actionGetSoftwareVersionLabel(msg);});
/// \cite RDM 10.11.1 Get/Set Identify Device (IDENTIFY_DEVICE)
/// This parameter is used for the user to physically identify the device
/// represented by the UID.
parameter = addParameter(IDENTIFY_DEVICE);
parameter->onGet([this](MsgPair msg){actionGetIdentifyDevice(msg);});
parameter->onSet([this](MsgPair msg){actionSetIdentifyDevice(msg);});
/// \cite RDM 10.11.2 Reset Device (RESET_DEVICE)
/// This parameter is used to instruct the responder to reset itself.
parameter = addParameter(RESET_DEVICE);
parameter->onSet([this](MsgPair msg){actionSetResetDevice(msg);});
}
BasicDevice::~BasicDevice()
{
for (auto& [_, parameter] : parameters_)
delete parameter;
for (auto sensor : sensors_)
delete sensor;
}
/**
* @brief BasicDevice::identify
* @param state
*/
void BasicDevice::identify(bool state)
{
identifying_ = state;
}
/**
* @brief BasicDevice::reset
* @param hard
*/
void BasicDevice::reset(bool hard)
{
(void)hard;
}
bool BasicDevice::hasManufacturerPIDs() const
{
auto [_, parameter] = *parameters_.cend()--;
return parameter->isManufacturer();
}
/**
* @brief BasicDevice::dispatch
* @param msg
*/
void BasicDevice::dispatch(MsgPair msg)
{
auto [call, response] = msg;
if (!parameters_.count(call->mdb.pid))
return response->nak(NR_UNKNOWN_PID);
if (call->mdb.pid == last_rx_pid_)
++ack_overflow_page_;
else
{
ack_overflow_page_ = 0;
last_rx_pid_ = call->mdb.pid;
}
parameters_.at(call->mdb.pid)->dispatch(msg);
}
/**
* @brief BasicDevice::enqueueStatus
* @param status
*/
void BasicDevice::enqueueStatus(StatusPtr status)
{
auto queue = status->status_type & 0xf;
if (!queued_statuses.count(queue))
return;
if (queue >= status_reporting_threshold_)
queued_statuses.at(queue).push(status);
}
/**
* @brief BasicDevice::addParameter
* @param metadata
* @return
*/
Parameter *BasicDevice::addParameter(const ParameterDescription metadata)
{
if (parameters_.count(metadata.pid))
return parameters_.at(metadata.pid);
auto [it, _] = parameters_.emplace(metadata.pid, new Parameter(metadata));
return it->second;
}
/**
* @brief BasicDevice::actionGetSupportedParameters
* @param msg
*/
void BasicDevice::actionGetSupportedParameters(MsgPair msg)
{
auto [_, response] = msg;
unsigned int count = parameters_.size();
unsigned int length = count * sizeof(PID);
unsigned int lastPage = length / 0xfe;
unsigned int first = ack_overflow_page_ * ( 0xfe / sizeof(PID) );
if (first >= count) {
ack_overflow_page_ = 0;
first = 0;
}
auto it = parameters_.cbegin();
if (first != 0)
std::advance(it, first);
while (it != parameters_.cend() && response->mdb.pdl() < 0xfe)
{
auto [pid, _] = *it;
switch (pid) {
case DMX_PERSONALITY.pid:
case DMX_PERSONALITY_DESCRIPTION.pid:
case DMX_START_ADDRESS.pid:
case DMX_BLOCK_ADDRESS.pid:
case DMX_STARTUP_MODE.pid:
case DMX_FAIL_MODE.pid:
if (!hasDMXaddress())
continue;
break;
case PARAMETER_DESCRIPTION.pid:
if (!hasManufacturerPIDs())
continue;
break;
case SENSOR_VALUE.pid:
case SENSOR_DEFINITION.pid:
case RECORD_SENSORS.pid:
if (!hasSensors())
continue;
break;
default:
break;
}
response->appendParameterData(pid);
it++;
}
if (length > 0xfe && ack_overflow_page_ != lastPage)
response->responseType = RESPONSE_TYPE_ACK_OVERFLOW;
}
/**
* @brief BasicDevice::actionGetParameterDescription
* @param msg
*/
void BasicDevice::actionGetParameterDescription(MsgPair msg)
{
auto [command, response] = msg;
/// \todo return a Parameter Description
response->nak(NR_UNSUPPORTED_COMMAND_CLASS);
}
/**
* @brief BasicDevice::actionGetDeviceInfo
* @param msg
*/
void BasicDevice::actionGetDeviceInfo(MsgPair msg)
{
auto [_, response] = msg;
response->appendParameterData(RDM_PROTOCOL_VERSION);
response->appendParameterData(deviceModelID);
response->appendParameterData(deviceProductCategory);
response->appendParameterData(LIB_VERSION);
response->appendParameterData((uint16_t)0);
response->appendParameterData((uint16_t)0);
response->appendParameterData((uint16_t)0);
response->appendParameterData((uint16_t)0);
response->appendParameterData((uint16_t)0);
response->appendParameterData<uint8_t>(sensors_.size());
}
/**
* @brief BasicDevice::actionGetSoftwareVersionLabel
* @param msg
*/
void BasicDevice::actionGetSoftwareVersionLabel(MsgPair msg)
{
auto [_, response] = msg;
std::string label = std::string(LIB_VERSION_LABEL);
for (size_t i = 0; i < label.size() && i <= 32; i++)
response->appendParameterData(label.at(i));
}
/**
* @brief BasicDevice::actionGetIdentifyDevice
* @param msg
*/
void BasicDevice::actionGetIdentifyDevice(MsgPair msg)
{
auto [_, response] = msg;
response->appendParameterData<uint8_t>(identifying_);
}
/**
* @brief BasicDevice::actionSetIdentifyDevice
* @param msg
*/
void BasicDevice::actionSetIdentifyDevice(MsgPair msg)
{
auto [command, _] = msg;
identify(command->mdb.pd.front());
}
/**
* @brief BasicDevice::actionSetResetDevice
* @param msg
*/
void BasicDevice::actionSetResetDevice(MsgPair msg)
{
auto [command, response] = msg;
switch (command->mdb.pd.front()) {
case 0x01:
reset(false);
break;
case 0xff:
reset(true);
break;
default:
return response->nak(NR_DATA_OUT_OF_RANGE);
}
}
} // namespace RDM

View File

@ -0,0 +1,91 @@
/*
rdm/basicdevice.h
Copyright (c) 2023 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.
*/
#pragma once
#include "message.h"
#include "parameter.h"
#include "sensor.h"
#include "status.h"
#include <list>
#include <map>
#include <queue>
namespace RDM {
/**
* @brief The BasicDevice class
*/
class BasicDevice
{
public:
explicit BasicDevice();
virtual ~BasicDevice();
std::string deviceManufacturerLabel; //!< manufacturer label
std::string deviceModelDescription; //!< model description
uint16_t deviceModelID; //!< model ID number
uint16_t deviceProductCategory; //!< device category
virtual void identify(bool state);
virtual void reset(bool hard);
protected:
friend class Device;
friend class Responder;
virtual bool hasManufacturerPIDs() const; //!< device has non-standard PIDs @return
virtual bool hasDMXaddress() const { return false; } //!< device has a DMX address @return
virtual bool hasSubDevices() const { return false; } //!< device has subdevices @return
virtual bool hasSensors() const { return !sensors_.empty(); } //!< device as sensors @return
virtual void dispatch(MsgPair msg);
void enqueueStatus(StatusPtr status);
Parameter *addParameter(const ParameterDescription metadata);
virtual void actionGetSupportedParameters(MsgPair msg);
virtual void actionGetParameterDescription(MsgPair msg);
virtual void actionGetDeviceInfo(MsgPair msg);
virtual void actionGetSoftwareVersionLabel(MsgPair msg);
virtual void actionGetIdentifyDevice(MsgPair msg);
virtual void actionSetIdentifyDevice(MsgPair msg);
virtual void actionSetResetDevice(MsgPair msg);
/// \cite RDM 10.3.2 The responder shall maintain reported status information until it has
/// been successfully delivered to the controller.
std::unordered_map<uint8_t, std::queue<StatusPtr>> queued_statuses; //!< outbound status queue
private:
bool identifying_ = false;
uint8_t status_reporting_threshold_;
uint16_t last_rx_pid_ = 0;
uint32_t ack_overflow_page_ = 0;
std::map<PID, Parameter*> parameters_; //!< parameters
std::vector<Sensor*> sensors_; //!< sensors
};
} // namespace RDM

View File

@ -22,148 +22,89 @@
SOFTWARE.
*/
#include "config.h"
#include "device.h"
#include "config.h"
namespace RDM {
/**
* @brief Device::Device
* @param id
* @param parent
*/
Device::Device(UID id, Device* parent)
: DMX::Device()
Device::Device(UID id)
: RDM::BasicDevice()
, DMX::Device()
, uid(id)
, deviceManufacturerLabel(MY_ESTA_MANUFACTURER_LABEL)
, deviceModelDescription("Basic RDM Device")
, deviceModelID(0)
, deviceProductCategory(PRODUCT_CATEGORY_NOT_DECLARED)
, parent_(parent)
, status_reporting_threshold_(STATUS_ADVISORY)
{
queued_statuses_.emplace(STATUS_ADVISORY, std::queue<StatusPtr>());
queued_statuses_.emplace(STATUS_WARNING, std::queue<StatusPtr>());
queued_statuses_.emplace(STATUS_ERROR, std::queue<StatusPtr>());
deviceModelDescription = "Basic RDM Device";
Parameter *pid;
/// \cite RDM 9.2.3 Required Sub-Device Messages
/// Devices supporting the use of sub-devices shall support the
/// SUPPORTED_PARAMETERS message in order for the controller to determine
/// which additional messages are supported by the sub-devices.
pid = addParameter(SUPPORTED_PARAMETERS);
pid->getAction(std::bind(&Device::actionGetSupportedParameters, this,
std::placeholders::_1, std::placeholders::_2));
Parameter *parameter;
/// \cite RDM 10.3.4 Clear Status ID (CLEAR_STATUS_ID)
/// This parameter is used to clear the status message queue.
pid = addParameter(CLEAR_STATUS_ID);
pid->getAction(std::bind(&Device::actionSetClearStatusId, this,
std::placeholders::_1, std::placeholders::_2));
parameter = addParameter(CLEAR_STATUS_ID);
parameter->onGet([this](MsgPair msg){actionSetClearStatusId(msg);});
/// \cite RDM 10.3.5 Get/Set Sub-Device Status Reporting Threshold
/// (SUB_DEVICE_STATUS_REPORT_THRESHOLD)
/// This parameter is used to set the verbosity of Sub-Device reporting using
/// the Status Type codes as enumerated in Table A-4 .
pid = addParameter(SUB_DEVICE_STATUS_REPORT_THRESHOLD);
pid->getAction(std::bind(&Device::actionGetSubdeviceThreshold, this,
std::placeholders::_1, std::placeholders::_2));
pid->setAction(std::bind(&Device::actionSetSubdeviceThreshold, this,
std::placeholders::_1, std::placeholders::_2));
/// \cite RDM 10.5.1 Get Device Info (DEVICE_INFO)
/// This parameter is used to retrieve a variety of information about the
/// device that is normally required by a controller.
pid = addParameter(DEVICE_INFO);
pid->getAction(std::bind(&Device::actionGetDeviceInfo, this,
std::placeholders::_1, std::placeholders::_2));
parameter = addParameter(SUB_DEVICE_STATUS_REPORT_THRESHOLD);
parameter->onGet([this](MsgPair msg){actionGetSubdeviceThreshold(msg);});
parameter->onSet([this](MsgPair msg){actionSetSubdeviceThreshold(msg);});
/// \cite RDM 10.5.2 Get Product Detail ID List (PRODUCT_DETAIL_ID_LIST)
/// This parameter shall be used for requesting technology details for a
/// device.
pid = addParameter(PRODUCT_DETAIL_ID_LIST);
pid->getAction(std::bind(&Device::actionGetProductDetailIdList, this,
std::placeholders::_1, std::placeholders::_2));
parameter = addParameter(PRODUCT_DETAIL_ID_LIST);
parameter->onGet([this](MsgPair msg){actionGetProductDetailIdList(msg);});
/// \cite RDM 10.5.3 Get Device Model Description (DEVICE_MODEL_DESCRIPTION)
/// This parameter provides a text description of up to 32 characters for the
/// device model type.
pid = addParameter(DEVICE_MODEL_DESCRIPTION);
pid->getAction(std::bind(&Device::actionGetDevModelDescription, this,
std::placeholders::_1, std::placeholders::_2));
parameter = addParameter(DEVICE_MODEL_DESCRIPTION);
parameter->onGet([this](MsgPair msg){actionGetDevModelDescription(msg);});
/// \cite RDM 10.5.4 Get Manufacturer Label (MANUFACTURER_LABEL)
/// This parameter provides an ASCII text response with the Manufacturer name
/// for the device of up to 32 characters.
pid = addParameter(MANUFACTURER_LABEL);
pid->getAction(std::bind(&Device::actionGetManufacturerLabel, this,
std::placeholders::_1, std::placeholders::_2));
parameter = addParameter(MANUFACTURER_LABEL);
parameter->onGet([this](MsgPair msg){actionGetManufacturerLabel(msg);});
/// \cite RDM 10.5.7 Get Language Capabilities (LANGUAGE_CAPABILITIES)
/// This parameter is used to identify languages that the device supports for
/// using the LANGUAGE parameter.
pid = addParameter(LANGUAGE_CAPABILITIES);
pid->getAction(std::bind(&Device::actionGetLanguage, this,
std::placeholders::_1, std::placeholders::_2));
parameter = addParameter(LANGUAGE_CAPABILITIES);
parameter->onGet([this](MsgPair msg){actionGetLanguage(msg);});
/// \cite RDM 10.5.8 Get/Set Language (LANGUAGE)
/// This parameter is used to change the language of the messages from
/// the device.
pid = addParameter(LANGUAGE);
pid->getAction(std::bind(&Device::actionGetLanguage, this,
std::placeholders::_1, std::placeholders::_2));
pid->setAction(std::bind(&Device::actionSetLanguage, this,
std::placeholders::_1, std::placeholders::_2));
/// \cite RDM 10.5.9 Get Software Version Label (SOFTWARE_VERSION_LABEL)
/// This parameter is used to get a descriptive ASCII text label for the
/// devices operating software version.
pid = addParameter(SOFTWARE_VERSION_LABEL);
pid->getAction(std::bind(&Device::actionGetSoftwareVersionLabel, this,
std::placeholders::_1, std::placeholders::_2));
parameter = addParameter(LANGUAGE);
parameter->onGet([this](MsgPair msg){actionGetLanguage(msg);});
parameter->onSet([this](MsgPair msg){actionSetLanguage(msg);});
/// \cite RDM 10.6.1 Get/Set DMX512 Personality (DMX_PERSONALITY)
/// This parameter is used to set the responders DMX512 Personality.
pid = addParameter(DMX_PERSONALITY);
pid->getAction(std::bind(&Device::actionGetDmxPersonality, this,
std::placeholders::_1, std::placeholders::_2));
pid->setAction(std::bind(&Device::actionSetDmxPersonality, this,
std::placeholders::_1, std::placeholders::_2));
parameter = addParameter(DMX_PERSONALITY);
parameter->onGet([this](MsgPair msg){actionGetDmxPersonality(msg);});
parameter->onSet([this](MsgPair msg){actionSetDmxPersonality(msg);});
/// \cite RDM 10.6.2 Get DMX512 Personality Description (DMX_PERSONALITY_DESCRIPTION)
/// This parameter is used to get a descriptive ASCII text label for a given
/// DMX512 Personality.
pid = addParameter(DMX_PERSONALITY_DESCRIPTION);
pid->getAction(std::bind(&Device::actionGetDmxPersonalityDesc, this,
std::placeholders::_1, std::placeholders::_2));
parameter = addParameter(DMX_PERSONALITY_DESCRIPTION);
parameter->onGet([this](MsgPair msg){actionGetDmxPersonalityDesc(msg);});
/// \cite RDM 10.6.3 Get/Set DMX512 Starting Address (DMX_START_ADDRESS)
/// This parameter is used to set or get the DMX512 start address.
pid = addParameter(DMX_START_ADDRESS);
pid->getAction(std::bind(&Device::actionGetDmxStartAddress, this,
std::placeholders::_1, std::placeholders::_2));
pid->setAction(std::bind(&Device::actionSetDmxStartAddress, this,
std::placeholders::_1, std::placeholders::_2));
parameter = addParameter(DMX_START_ADDRESS);
parameter->onGet([this](MsgPair msg){actionGetDmxStartAddress(msg);});
parameter->onSet([this](MsgPair msg){actionSetDmxStartAddress(msg);});
/// \cite RDM 10.7.1 Get Sensor Definition (SENSOR_DEFINITION)
/// This parameter is used to retrieve the definition of a specific sensor.
pid = addParameter(SENSOR_DEFINITION);
pid->getAction(std::bind(&Device::actionSensorDispatch, this,
std::placeholders::_1, std::placeholders::_2));
parameter = addParameter(SENSOR_DEFINITION);
parameter->onGet([this](MsgPair msg){actionSensorDispatch(msg);});
/// \cite RDM 10.7.2 Get/Set Sensor (SENSOR_VALUE)
/// This parameter shall be used to retrieve or reset sensor data.
pid = addParameter(SENSOR_VALUE);
pid->getAction(std::bind(&Device::actionSensorDispatch, this,
std::placeholders::_1, std::placeholders::_2));
pid->setAction(std::bind(&Device::actionSensorDispatch, this,
std::placeholders::_1, std::placeholders::_2));
parameter = addParameter(SENSOR_VALUE);
parameter->onGet([this](MsgPair msg){actionSensorDispatch(msg);});
parameter->onSet([this](MsgPair msg){actionSensorDispatch(msg);});
/// \cite RDM 10.7.3 Record Sensors (RECORD_SENSORS)
/// This parameter instructs devices such as dimming racks that monitor load
/// changes to store the current value for monitoring sensor changes.
pid = addParameter(RECORD_SENSORS);
pid->setAction(std::bind(&Device::actionSensorDispatch, this,
std::placeholders::_1, std::placeholders::_2));
/// \cite RDM 10.11.1 Get/Set Identify Device (IDENTIFY_DEVICE)
/// This parameter is used for the user to physically identify the device
/// represented by the UID.
pid = addParameter(IDENTIFY_DEVICE);
pid->getAction(std::bind(&Device::actionGetIdentifyDevice, this,
std::placeholders::_1, std::placeholders::_2));
pid->setAction(std::bind(&Device::actionSetIdentifyDevice, this,
std::placeholders::_1, std::placeholders::_2));
/// \cite RDM 10.11.2 Reset Device (RESET_DEVICE)
/// This parameter is used to instruct the responder to reset itself.
pid = addParameter(RESET_DEVICE);
pid->setAction(std::bind(&Device::actionSetResetDevice, this,
std::placeholders::_1, std::placeholders::_2));
parameter = addParameter(RECORD_SENSORS);
parameter->onSet([this](MsgPair msg){actionSensorDispatch(msg);});
}
@ -172,12 +113,6 @@ Device::Device(UID id, Device* parent)
*/
Device::~Device()
{
for( auto& [_, device] : sub_devices_)
delete device;
for (auto& [_, parameter] : parameters_)
delete parameter;
for (auto sensor : sensors_)
delete sensor;
}
@ -185,17 +120,23 @@ Device::~Device()
* @brief Device::addSubDevice
* @param number
* @param dev
*
* Root devices (ie. Respoders) shall override with a meaningful implimentation
*/
void Device::addSubDevice(uint16_t number, Device *dev)
void Device::addSubDevice(uint16_t number, std::shared_ptr<SubDevice> dev)
{
if (parent_)
return;
if (sub_devices_.count(number))
delete sub_devices_.at(number);
sub_devices_[number] = dev;
sub_devices_.at(number) = dev;
else
sub_devices_.emplace(number, dev);
}
/**
* @brief Device::deleteSubDevice
* @param number
*/
void Device::deleteSubDevice(uint16_t number)
{
sub_devices_.erase(number);
}
@ -204,11 +145,8 @@ void Device::addSubDevice(uint16_t number, Device *dev)
* @param number
* @return
*/
Device* Device::subDevice(uint16_t number)
std::shared_ptr<SubDevice> Device::subDevice(uint16_t number)
{
if (parent_)
return nullptr;
if (sub_devices_.count(number))
return sub_devices_.at(number);
return nullptr;
@ -221,17 +159,51 @@ Device* Device::subDevice(uint16_t number)
*/
uint16_t Device::subDeviceCount() const
{
if (parent_)
return parent_->subDeviceCount();
return sub_devices_.size();
}
/**
* @brief Device::addProductDetailId
* @brief Device::dispatch
* @param msg
*/
void Device::dispatch(MsgPair msg)
{
auto [call, response] = msg;
if (call->subDevice == 0)
return BasicDevice::dispatch(msg);
else if (call->mdb.cc == DISCOVERY_COMMAND)
return response->nak(NR_SUB_DEVICE_OUT_OF_RANGE);
if (call->subDevice == SUB_DEVICE_ALL_CALL)
{
/// \cite RDM 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 (call->mdb.cc == GET_COMMAND)
return response->nak(NR_SUB_DEVICE_OUT_OF_RANGE);
response->do_not_send = true;
for (auto& [_, dev] : sub_devices_)
dev->dispatch(msg);
return;
}
if (sub_devices_.count(call->subDevice))
return sub_devices_.at(call->subDevice)->dispatch(msg);
response->nak(NR_SUB_DEVICE_OUT_OF_RANGE);
}
/**
* @brief Device::addProductDetail
* @param id
*/
void Device::addProductDetailId(uint16_t id)
void Device::addProductDetail(uint16_t id)
{
product_detail_list_.push_back(id);
while (product_detail_list_.size() > 6)
@ -239,67 +211,6 @@ void Device::addProductDetailId(uint16_t id)
}
/**
* @brief Device::addParameter
* @param id
* @return
*/
Parameter *Device::addParameter(const PID id)
{
if (parameters_.count(id))
return parameters_.at(id);
auto [it, _] = parameters_.emplace(id, new Parameter(id));
return it->second;
}
/**
* @brief Device::get
* @param message
* @param response
*/
void Device::get(const MsgPtr message, MsgPtr response)
{
if (!actionPrep_(message, response))
return;
parameters_.at(message->mdb.pid)->get(message, response);
}
/**
* @brief Device::set
* @param message
* @param response
*/
void Device::set(const MsgPtr message, MsgPtr response)
{
if (!actionPrep_(message, response))
return;
parameters_.at(message->mdb.pid)->set(message, response);
}
/**
* @brief Device::identify
* @param state
*/
void Device::identify(bool state)
{
identifying_ = state;
}
/**
* @brief Device::reset
* @param hard
*/
void Device::reset(bool hard)
{
(void)hard;
}
/**
* @brief Device::enqueueMessage
* @param message
@ -307,10 +218,6 @@ void Device::reset(bool hard)
*/
void Device::enqueueMessage(MsgPtr message, bool urgent)
{
// only root devices have a message queue
if (parent_)
return;
message->destination = controller_uid_;
if (urgent)
@ -320,112 +227,9 @@ void Device::enqueueMessage(MsgPtr message, bool urgent)
}
/**
* @brief Device::enqueueStatus
* @param status
*/
void Device::enqueueStatus(StatusPtr status)
void Device::actionGetDeviceInfo(MsgPair msg)
{
switch (status->status_type) {
case STATUS_ERROR:
if (status_reporting_threshold_ != STATUS_ADVISORY &&
status_reporting_threshold_ != STATUS_WARNING)
queued_statuses_.at(STATUS_ERROR).push(status);
break;
case STATUS_ERROR_CLEARED:
if (status_reporting_threshold_ != STATUS_ADVISORY &&
status_reporting_threshold_ != STATUS_WARNING)
queued_statuses_.at(STATUS_ERROR).push(status);
break;
case STATUS_WARNING:
if (status_reporting_threshold_ != STATUS_ADVISORY)
queued_statuses_.at(STATUS_WARNING).push(status);
break;
case STATUS_WARNING_CLEARED:
if (status_reporting_threshold_ != STATUS_ADVISORY)
queued_statuses_.at(STATUS_WARNING).push(status);
break;
case STATUS_ADVISORY:
queued_statuses_.at(STATUS_ADVISORY).push(status);
break;
case STATUS_ADVISORY_CLEARED:
queued_statuses_.at(STATUS_ADVISORY).push(status);
break;
default:
break;
}
}
/**
* @brief Device::actionPrep_
* @param message
* @param response
* @return
*/
bool Device::actionPrep_(const MsgPtr message, MsgPtr response)
{
if (!parameters_.count(message->mdb.pid))
{
response->nak(NR_UNKNOWN_PID);
return false;
}
if (message->mdb.pid == last_rx_pid_)
++ack_overflow_page;
else {
ack_overflow_page = 0;
last_rx_pid_ = message->mdb.pid;
}
return true;
}
/**
* @brief Device::actionGetSupportedParameters
* @param message
* @param response
*/
void Device::actionGetSupportedParameters(const MsgPtr message, MsgPtr response)
{
if (message->mdb.pdl() != 0)
return response->nak(NR_FORMAT_ERROR);
unsigned int count = parameters_.size();
unsigned int length = count * sizeof(PID);
unsigned int lastPage = length / 0xfe;
unsigned int first = ack_overflow_page * ( 0xfe / sizeof(PID) );
if (first >= count) {
ack_overflow_page = 0;
first = 0;
}
auto pid = parameters_.begin();
if (first != 0)
std::advance(pid, first);
while (pid != parameters_.end() && response->mdb.pdl() < 0xfe)
{
response->appendParameterData(pid->first);
pid++;
}
if (length > 0xfe && ack_overflow_page != lastPage)
response->responseType = RESPONSE_TYPE_ACK_OVERFLOW;
else
response->responseType = RESPONSE_TYPE_ACK;
}
/**
* @brief Device::actionGetDeviceInfo
* @param message
* @param response
*/
void Device::actionGetDeviceInfo(const MsgPtr message, MsgPtr response)
{
if (message->mdb.pdl() != 0)
return response->nak(NR_FORMAT_ERROR);
auto [_, response] = msg;
response->appendParameterData(RDM_PROTOCOL_VERSION);
response->appendParameterData(deviceModelID);
response->appendParameterData(deviceProductCategory);
@ -436,69 +240,52 @@ void Device::actionGetDeviceInfo(const MsgPtr message, MsgPtr response)
response->appendParameterData(DMX::Device::address());
response->appendParameterData(subDeviceCount());
response->appendParameterData<uint8_t>(sensors_.size());
response->responseType = RESPONSE_TYPE_ACK;
}
/**
* @brief Device::actionSetClearStatusId
* @param message
* @param response
* @param msg
*/
void Device::actionSetClearStatusId(const MsgPtr message, MsgPtr response)
void Device::actionSetClearStatusId([[maybe_unused]]MsgPair msg)
{
if (message->mdb.pdl() != 0)
return response->nak(NR_FORMAT_ERROR);
for (auto& [_, queue] : queued_statuses_)
for (auto& [_, queue] : queued_statuses)
while (!queue.empty())
queue.pop();
response->responseType = RESPONSE_TYPE_ACK;
}
/**
* @brief Device::actionGetSubdeviceThreshold
* @param message
* @param response
* @param msg
*/
void Device::actionGetSubdeviceThreshold(const MsgPtr message, MsgPtr response)
void Device::actionGetSubdeviceThreshold(MsgPair msg)
{
if (message->mdb.pdl() != 0)
return response->nak(NR_FORMAT_ERROR);
auto [_, response] = msg;
response->appendParameterData(status_reporting_threshold_);
response->responseType = RESPONSE_TYPE_ACK;
}
/**
* @brief Device::actionSetSubdeviceThreshold
* @param message
* @param response
* @param msg
*/
void Device::actionSetSubdeviceThreshold(const MsgPtr message, MsgPtr response)
void Device::actionSetSubdeviceThreshold(MsgPair msg)
{
if (message->mdb.pdl() != 1)
return response->nak(NR_FORMAT_ERROR);
uint8_t threshold = message->mdb.pd.front();
auto [command, response] = msg;
uint8_t threshold = command->mdb.pd.front();
switch (threshold) {
case STATUS_ERROR:
status_reporting_threshold_ = threshold;
while (!queued_statuses_.at(STATUS_WARNING).empty())
queued_statuses_.at(STATUS_WARNING).pop();
while (!queued_statuses_.at(STATUS_ADVISORY).empty())
queued_statuses_.at(STATUS_ADVISORY).pop();
while (!queued_statuses.at(STATUS_WARNING).empty())
queued_statuses.at(STATUS_WARNING).pop();
while (!queued_statuses.at(STATUS_ADVISORY).empty())
queued_statuses.at(STATUS_ADVISORY).pop();
break;
case STATUS_WARNING:
status_reporting_threshold_ = threshold;
while (!queued_statuses_.at(STATUS_ADVISORY).empty())
queued_statuses_.at(STATUS_ADVISORY).pop();
while (!queued_statuses.at(STATUS_ADVISORY).empty())
queued_statuses.at(STATUS_ADVISORY).pop();
break;
case STATUS_ADVISORY:
status_reporting_threshold_ = threshold;
@ -507,128 +294,80 @@ void Device::actionSetSubdeviceThreshold(const MsgPtr message, MsgPtr response)
response->nak(NR_DATA_OUT_OF_RANGE);
return;
}
response->responseType = RESPONSE_TYPE_ACK;
}
/**
* @brief Device::actionGetProductDetailIdList
* @param message
* @param response
* @param msg
*/
void Device::actionGetProductDetailIdList(const MsgPtr message, MsgPtr response)
void Device::actionGetProductDetailIdList(MsgPair msg)
{
if (message->mdb.pdl() != 0)
return response->nak(NR_FORMAT_ERROR);
auto [_, response] = msg;
if (product_detail_list_.empty())
response->appendParameterData(PRODUCT_DETAIL_NOT_DECLARED);
else
for (const auto detail : product_detail_list_)
response->appendParameterData(detail);
response->responseType = RESPONSE_TYPE_ACK;
}
/**
* @brief Device::actionGetDevModelDescription
* @param message
* @param response
* @param msg
*/
void Device::actionGetDevModelDescription(const MsgPtr message, MsgPtr response)
void Device::actionGetDevModelDescription(MsgPair msg)
{
if (message->mdb.pdl() != 0)
return response->nak(NR_FORMAT_ERROR);
auto [_, response] = msg;
for (size_t i = 0; i < deviceModelDescription.size() && i <= 32; i++)
response->appendParameterData(deviceModelDescription.at(i));
response->responseType = RESPONSE_TYPE_ACK;
}
/**
* @brief Device::actionGetManufacturerLabel
* @param message
* @param response
* @param msg
*/
void Device::actionGetManufacturerLabel(const MsgPtr message, MsgPtr response)
void Device::actionGetManufacturerLabel(MsgPair msg)
{
if (message->mdb.pdl() != 0)
return response->nak(NR_FORMAT_ERROR);
auto [_, response] = msg;
for (size_t i = 0; i < deviceManufacturerLabel.size() && i <= 32; i++)
response->appendParameterData(deviceManufacturerLabel.at(i));
response->responseType = RESPONSE_TYPE_ACK;
}
/**
* @brief Device::actionGetLanguage
* @param message
* @param response
* @param msg
*/
void Device::actionGetLanguage(const MsgPtr message, MsgPtr response)
void Device::actionGetLanguage(MsgPair msg)
{
if (message->mdb.pdl() != 0)
return response->nak(NR_FORMAT_ERROR);
auto [_, response] = msg;
response->appendParameterData('e');
response->appendParameterData('n');
response->responseType = RESPONSE_TYPE_ACK;
}
/**
* @brief Device::actionSetLanguage
* @param message
* @param response
* @param msg
*/
void Device::actionSetLanguage(const MsgPtr message, MsgPtr response)
void Device::actionSetLanguage(MsgPair msg)
{
if (message->mdb.pdl() != 2)
return response->nak(NR_FORMAT_ERROR);
if (message->mdb.pd[0] != 'e' ||
message->mdb.pd[1] != 'n')
auto [command, response] = msg;
if (command->mdb.pd[0] != 'e' ||
command->mdb.pd[1] != 'n')
return response->nak(NR_DATA_OUT_OF_RANGE);
response->responseType = RESPONSE_TYPE_ACK;
}
/**
* @brief Device::actionGetSoftwareVersionLabel
* @param message
* @param response
*/
void Device::actionGetSoftwareVersionLabel(const MsgPtr message, MsgPtr response)
{
if (message->mdb.pdl() != 0)
return response->nak(NR_FORMAT_ERROR);
response->responseType = RESPONSE_TYPE_ACK;
std::string label = std::string(LIB_VERSION_LABEL);
for (size_t i = 0; i < label.size() && i <= 32; i++)
response->appendParameterData(label.at(i));
}
/**
* @brief Device::actionGetDmxPersonality
* @param message
* @param response
* @param msg
*/
void Device::actionGetDmxPersonality(const MsgPtr message, MsgPtr response)
void Device::actionGetDmxPersonality(MsgPair msg)
{
if (message->mdb.pdl() != 0)
return response->nak(NR_FORMAT_ERROR);
response->responseType = RESPONSE_TYPE_ACK;
auto [_, response] = msg;
response->appendParameterData(DMX::Device::personality());
response->appendParameterData(DMX::Device::personalityCount());
}
@ -636,41 +375,30 @@ void Device::actionGetDmxPersonality(const MsgPtr message, MsgPtr response)
/**
* @brief Device::actionSetDmxPersonality
* @param message
* @param response
* @param msg
*/
void Device::actionSetDmxPersonality(const MsgPtr message, MsgPtr response)
void Device::actionSetDmxPersonality(MsgPair msg)
{
if (message->mdb.pdl() != 1)
return response->nak(NR_FORMAT_ERROR);
uint8_t mode = message->mdb.pd.front();
auto [command, response] = msg;
uint8_t mode = command->mdb.pd.front();
if ( mode == 0 || mode > DMX::Device::personalityCount())
return response->nak(NR_DATA_OUT_OF_RANGE);
setPersonality(mode);
response->responseType = RESPONSE_TYPE_ACK;
}
/**
* @brief Device::actionGetDmxPersonalityDesc
* @param message
* @param response
* @param msg
*/
void Device::actionGetDmxPersonalityDesc(const MsgPtr message, MsgPtr response)
void Device::actionGetDmxPersonalityDesc(MsgPair msg)
{
if (message->mdb.pdl() != 1)
return response->nak(NR_FORMAT_ERROR);
uint8_t mode = message->mdb.pd.front();
auto [command, response] = msg;
uint8_t mode = command->mdb.pd.front();
if ( mode == 0 || mode > DMX::Device::personalityCount())
return response->nak(NR_DATA_OUT_OF_RANGE);
response->responseType = RESPONSE_TYPE_ACK;
response->appendParameterData(mode);
response->appendParameterData(personalities_.at(mode)->footprint());
for (size_t i = 0; i < personalities_.at(mode)->description().size(); i++)
@ -684,15 +412,11 @@ void Device::actionGetDmxPersonalityDesc(const MsgPtr message, MsgPtr response)
/**
* @brief Device::actionGetDmxStartAddress
* @param message
* @param response
* @param msg
*/
void Device::actionGetDmxStartAddress(const MsgPtr message, MsgPtr response)
void Device::actionGetDmxStartAddress(MsgPair msg)
{
if (message->mdb.pdl() != 0)
return response->nak(NR_FORMAT_ERROR);
response->responseType = RESPONSE_TYPE_ACK;
auto [_, response] = msg;
if (footprint() == 0)
response->appendParameterData<uint16_t>(0xFFFF);
else
@ -702,36 +426,27 @@ void Device::actionGetDmxStartAddress(const MsgPtr message, MsgPtr response)
/**
* @brief Device::actionSetDmxStartAddress
* @param message
* @param response
* @param msg
*/
void Device::actionSetDmxStartAddress(const MsgPtr message, MsgPtr response)
void Device::actionSetDmxStartAddress(MsgPair msg)
{
if (message->mdb.pdl() != 2)
return response->nak(NR_FORMAT_ERROR);
uint16_t addr = Message::readType<uint16_t>(message->mdb.pd, 0);
auto [command, response] = msg;
uint16_t addr = Message::readType<uint16_t>(command->mdb.pd, 0);
if (!setAddress(addr))
return response->nak(NR_DATA_OUT_OF_RANGE);
response->responseType = RESPONSE_TYPE_ACK;
}
/**
* @brief Device::actionSensorDispatch
* @param message
* @param response
* @param msg
*/
void Device::actionSensorDispatch(const MsgPtr message, MsgPtr response)
void Device::actionSensorDispatch(MsgPair msg)
{
if (message->mdb.pdl() != 1)
return response->nak(NR_FORMAT_ERROR);
auto [command, response] = msg;
uint8_t index = command->mdb.pd.front();
uint8_t index = message->mdb.pd.front();
switch (message->mdb.cc) {
switch (command->mdb.cc) {
case GET_COMMAND:
{
if (index == 0xFF || index >= sensors_.size())
@ -740,11 +455,11 @@ void Device::actionSensorDispatch(const MsgPtr message, MsgPtr response)
return;
}
auto sensor = sensors_.at(index);
switch (message->mdb.pid) {
case SENSOR_DEFINITION:
switch (command->mdb.pid) {
case SENSOR_DEFINITION.pid:
sensor->actionGetSensorDefinition(index, response);
break;
case SENSOR_VALUE:
case SENSOR_VALUE.pid:
sensor->actionGetSensorValue(index, response);
break;
}
@ -757,13 +472,14 @@ void Device::actionSensorDispatch(const MsgPtr message, MsgPtr response)
response->nak(NR_DATA_OUT_OF_RANGE);
return;
}
auto setSensor = [index, message, response](Sensor * sensor)
auto setSensor = [index, msg](Sensor * sensor)
{
switch (message->mdb.pid) {
case SENSOR_VALUE:
auto [command, response] = msg;
switch (command->mdb.pid) {
case SENSOR_VALUE.pid:
sensor->actionSetSensorValue(index, response);
break;
case RECORD_SENSORS:
case RECORD_SENSORS.pid:
sensor->actionSetRecordSensors(response);
break;
}
@ -778,60 +494,4 @@ void Device::actionSensorDispatch(const MsgPtr message, MsgPtr response)
}
}
/**
* @brief Device::actionGetIdentifyDevice
* @param message
* @param response
*/
void Device::actionGetIdentifyDevice(const MsgPtr message, MsgPtr response)
{
if (message->mdb.pdl() != 0)
return response->nak(NR_FORMAT_ERROR);
response->responseType = RESPONSE_TYPE_ACK;
response->appendParameterData<uint8_t>(identifying_);
}
/**
* @brief Device::actionSetIdentifyDevice
* @param message
* @param response
*/
void Device::actionSetIdentifyDevice(const MsgPtr message, MsgPtr response)
{
if (message->mdb.pdl() != 1)
return response->nak(NR_FORMAT_ERROR);
identify(message->mdb.pd.front());
response->responseType = RESPONSE_TYPE_ACK;
}
/**
* @brief Device::actionSetResetDevice
* @param message
* @param response
*/
void Device::actionSetResetDevice(const MsgPtr message, MsgPtr response)
{
if (message->mdb.pdl() != 1)
return response->nak(NR_FORMAT_ERROR);
switch (message->mdb.pd.front()) {
case 0x01:
reset(false);
break;
case 0xff:
reset(true);
break;
default:
return response->nak(NR_DATA_OUT_OF_RANGE);
}
response->responseType = RESPONSE_TYPE_ACK;
}
} // namespace RDM

View File

@ -24,10 +24,8 @@
#pragma once
#include "../dmx/device.h"
#include "parameter.h"
#include "rdm.h"
#include "sensor.h"
#include "status.h"
#include "basicdevice.h"
#include "subdevice.h"
#include "uid.h"
#include <list>
@ -41,76 +39,51 @@ namespace RDM {
* @brief The RDM::Device class
*/
class Device
: public DMX::Device
: public RDM::BasicDevice
, public DMX::Device
{
public:
explicit Device(UID id = UID(), Device* parent = nullptr);
explicit Device(UID id = UID());
virtual ~Device();
const UID uid; //!< This devices UID
void addSubDevice(uint16_t number, Device* dev);
Device* subDevice(uint16_t number);
uint16_t subDeviceCount() const;
void addProductDetailId(uint16_t);
std::shared_ptr<SubDevice> subDevice(uint16_t number);
virtual uint16_t subDeviceCount() const;
Parameter* addParameter(const PID id);
void get(const MsgPtr message, MsgPtr response);
void set(const MsgPtr message, MsgPtr response);
virtual void identify(bool state);
virtual void reset(bool hard);
std::string deviceManufacturerLabel; //!< manufacturer label
std::string deviceModelDescription; //!< model description
uint16_t deviceModelID; //!< model ID number
uint16_t deviceProductCategory; //!< device category
void addProductDetail(uint16_t id);
protected:
friend class Responder;
std::unordered_map<uint16_t, Device*> sub_devices_; //!< sub devices
std::unordered_map<PID, Parameter*> parameters_; //!< parameters
std::vector<Sensor*> sensors_; //!< sensors
std::list<uint16_t> product_detail_list_; //!< product detail list
std::deque<MsgPtr> queued_messages_; //!< outbound message queue
virtual void dispatch(MsgPair msg) override;
void addSubDevice(uint16_t number, std::shared_ptr<SubDevice> dev);
void deleteSubDevice(uint16_t number);
void enqueueMessage(MsgPtr message, bool urgent = false);
UID controller_uid_; //!< controller UID
std::unordered_map<uint8_t, std::queue<StatusPtr>> queued_statuses_; //!< outbound status queue
void enqueueStatus(StatusPtr status);
bool actionPrep_(const MsgPtr message, MsgPtr response);
void actionGetSupportedParameters (const MsgPtr message, MsgPtr response);
void actionSetClearStatusId (const MsgPtr message, MsgPtr response);
void actionGetSubdeviceThreshold (const MsgPtr message, MsgPtr response);
void actionSetSubdeviceThreshold (const MsgPtr message, MsgPtr response);
void actionGetDeviceInfo (const MsgPtr message, MsgPtr response);
void actionGetProductDetailIdList (const MsgPtr message, MsgPtr response);
void actionGetDevModelDescription (const MsgPtr message, MsgPtr response);
void actionGetManufacturerLabel (const MsgPtr message, MsgPtr response);
void actionGetLanguage (const MsgPtr message, MsgPtr response);
void actionSetLanguage (const MsgPtr message, MsgPtr response);
void actionGetSoftwareVersionLabel(const MsgPtr message, MsgPtr response);
void actionGetDmxPersonality (const MsgPtr message, MsgPtr response);
void actionSetDmxPersonality (const MsgPtr message, MsgPtr response);
void actionGetDmxPersonalityDesc (const MsgPtr message, MsgPtr response);
void actionGetDmxStartAddress (const MsgPtr message, MsgPtr response);
void actionSetDmxStartAddress (const MsgPtr message, MsgPtr response);
void actionSensorDispatch (const MsgPtr message, MsgPtr response);
void actionGetIdentifyDevice (const MsgPtr message, MsgPtr response);
void actionSetIdentifyDevice (const MsgPtr message, MsgPtr response);
void actionSetResetDevice (const MsgPtr message, MsgPtr response);
virtual void actionSetClearStatusId(MsgPair msg);
virtual void actionGetSubdeviceThreshold(MsgPair msg);
virtual void actionSetSubdeviceThreshold(MsgPair msg);
virtual void actionGetDeviceInfo(MsgPair msg) override;
virtual void actionGetProductDetailIdList(MsgPair msg);
virtual void actionGetDevModelDescription(MsgPair msg);
virtual void actionGetManufacturerLabel(MsgPair msg);
virtual void actionGetLanguage(MsgPair msg);
virtual void actionSetLanguage(MsgPair msg);
virtual void actionGetDmxPersonality(MsgPair msg);
virtual void actionSetDmxPersonality(MsgPair msg);
virtual void actionGetDmxPersonalityDesc(MsgPair msg);
virtual void actionGetDmxStartAddress(MsgPair msg);
virtual void actionSetDmxStartAddress(MsgPair msg);
virtual void actionSensorDispatch(MsgPair msg);
private:
Device* parent_;
PID last_rx_pid_ = 0;
unsigned int ack_overflow_page = 0;
uint8_t status_reporting_threshold_;
bool identifying_ = false;
std::list<uint16_t> product_detail_list_; //!< product detail list
std::unordered_map<uint16_t, std::shared_ptr<SubDevice>> sub_devices_; //!< sub devices
std::deque<MsgPtr> queued_messages_; //!< outbound message queue
UID controller_uid_; //!< controller UID
};
} // namespace RDM

View File

@ -31,62 +31,52 @@ namespace RDM {
/**
* @brief Responder::Responder
* @param id
* @param parent
*/
Responder::Responder(UID id, Device* parent)
: Device(id, parent)
Responder::Responder(UID id)
: Device(id)
, control_field(0)
{
deviceModelID = 1;
deviceModelDescription = "Basic RDM Responder";
subdevice_flag = true;
Parameter *pid;
Parameter *parameter;
/// \cite RDM 7.5 Discovery Unique Branch Message (DISC_UNIQUE_BRANCH)
pid = addParameter(DISC_UNIQUE_BRANCH);
pid->discAction(std::bind(&Responder::actionDiscoverUniqueBranch, this,
std::placeholders::_1, std::placeholders::_2));
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.
pid = addParameter(DISC_MUTE);
pid->discAction(std::bind(&Responder::actionDiscoveryMute, this,
std::placeholders::_1, std::placeholders::_2));
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.
pid = addParameter(DISC_UN_MUTE);
pid->discAction(std::bind(&Responder::actionDiscoveryUnmute, this,
std::placeholders::_1, std::placeholders::_2));
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.
pid = addParameter(COMMS_STATUS);
pid->getAction(std::bind(&Responder::actionGetCommsStatus, this,
std::placeholders::_1, std::placeholders::_2));
pid->setAction(std::bind(&Responder::actionSetCommsStatus, this,
std::placeholders::_1, std::placeholders::_2));
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
/// responders 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.
pid = addParameter(QUEUED_MESSAGE);
pid->getAction(std::bind(&Responder::actionGetQueuedMessage, this,
std::placeholders::_1, std::placeholders::_2));
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.
pid = addParameter(STATUS_MESSAGES);
pid->getAction(std::bind(&Responder::actionGetStatusMessages, this,
std::placeholders::_1, std::placeholders::_2));
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.
pid = addParameter(STATUS_ID_DESCRIPTION);
pid->getAction(std::bind(&Responder::actionGetStatusIdDescription, this,
std::placeholders::_1, std::placeholders::_2));
parameter = addParameter(STATUS_ID_DESCRIPTION);
parameter->onGet([this](MsgPair msg){actionGetStatusIdDescription(msg);});
}
@ -97,12 +87,12 @@ Responder::~Responder()
/**
* @brief Responder::send
* @param data
* @param buffer
*/
void Responder::send(const std::vector<uint8_t> &data)
void Responder::send(const std::vector<uint8_t> &buffer)
{
if (auto sp = sender_.lock())
(*sp)(data); // sender still exists
(*sp)(buffer); // sender still exists
}
@ -159,12 +149,12 @@ std::shared_ptr<void> Responder::setSender(std::function<void(const std::vector<
/**
* @brief Responder::receive
* @param data
* @param buffer
*/
void Responder::receive(const std::vector<uint8_t> &data)
void Responder::receive(const std::vector<uint8_t> &buffer)
{
auto msg = std::make_shared<Message>();
msg->read(data);
msg->read(buffer);
receive(msg);
}
@ -223,6 +213,7 @@ void Responder::receive(const MsgPtr 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
@ -235,21 +226,7 @@ void Responder::receive(const MsgPtr message)
if (message->failure_mode)
response->nak(NR_FORMAT_ERROR); // nak on any failure modes
else
{
switch (message->mdb.cc) { // dispatch valid messages
case DISCOVERY_COMMAND:
rxDiscovery(message, response);
break;
case GET_COMMAND:
rxGet(message, response);
break;
case SET_COMMAND:
rxSet(message, response);
break;
default:
response->nak(NR_UNSUPPORTED_COMMAND_CLASS);
}
}
dispatch({message, response});
send(response); // send the reply
}
@ -261,7 +238,7 @@ void Responder::receive(const MsgPtr message)
*/
void Responder::reset(bool hard)
{
(void)hard;
Device::reset(hard);
/// \cite RDM 10.11.2 Reset Device (RESET_DEVICE)
/// This parameter shall also clear the Discovery Mute flag.
@ -269,106 +246,13 @@ void Responder::reset(bool hard)
}
/**
* @brief Responder::rxDiscovery
* @param message
* @param response
*/
void Responder::rxDiscovery(const MsgPtr message, MsgPtr response)
{
if (message->subDevice != 0)
{
response->nak(NR_SUB_DEVICE_OUT_OF_RANGE);
return;
}
if (!actionPrep_(message, response))
return;
parameters_.at(message->mdb.pid)->disc(message, response);
}
/**
* @brief Responder::rxGet
* @param message
* @param response
*/
void Responder::rxGet(const MsgPtr message, MsgPtr response)
{
/// \cite RDM 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->nak(NR_SUB_DEVICE_OUT_OF_RANGE);
return;
}
if (message->subDevice == 0)
{
get(message, response);
return;
}
if (!sub_devices_.count(message->subDevice))
{
response->nak(NR_SUB_DEVICE_OUT_OF_RANGE);
return;
}
sub_devices_.at(message->subDevice)->get(message, response);
}
/**
* @brief Responder::rxSet
* @param message
* @param response
*/
void Responder::rxSet(const MsgPtr message, MsgPtr response)
{
if (message->subDevice == 0)
{
set(message, response);
return;
}
if (sub_devices_.empty())
{
response->nak(NR_SUB_DEVICE_OUT_OF_RANGE);
return;
}
if (message->subDevice == SUB_DEVICE_ALL_CALL)
{
for (auto& [num, dev] : sub_devices_)
dev->set(message, response);
return;
}
if (!sub_devices_.count(message->subDevice))
{
response->nak(NR_SUB_DEVICE_OUT_OF_RANGE);
return;
}
sub_devices_.at(message->subDevice)->set(message, response);
}
/**
* @brief Responder::actionDiscoverUniqueBranch
* @param message
* @param response
* @param msg
*/
void Responder::actionDiscoverUniqueBranch(const MsgPtr message, MsgPtr response)
void Responder::actionDiscoverUniqueBranch(MsgPair msg)
{
if (message->mdb.pdl() != 12)
return response->nak(NR_FORMAT_ERROR);
auto [command, response] = msg;
if (discovery_mute_flag_)
{
response->do_not_send = true;
@ -376,10 +260,10 @@ void Responder::actionDiscoverUniqueBranch(const MsgPtr message, MsgPtr response
}
UID lower, upper;
lower.manufacturer = Message::readType<uint16_t>(message->mdb.pd, 0);
lower.device = Message::readType<uint32_t>(message->mdb.pd, 2);
upper.manufacturer = Message::readType<uint16_t>(message->mdb.pd, 6);
upper.device = Message::readType<uint32_t>(message->mdb.pd, 8);
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())
{
@ -399,184 +283,149 @@ void Responder::actionDiscoverUniqueBranch(const MsgPtr message, MsgPtr response
/**
* @brief Responder::actionDiscoveryMute
* @param message
* @param response
* @param msg
*/
void Responder::actionDiscoveryMute(const MsgPtr message, MsgPtr response)
void Responder::actionDiscoveryMute(MsgPair msg)
{
if (message->mdb.pdl() != 0)
return response->nak(NR_FORMAT_ERROR);
auto [command, response] = msg;
discovery_mute_flag_ = true;
controller_uid_ = message->source;
controller_uid_ = command->source;
response->appendParameterData(control_field);
response->responseType = RESPONSE_TYPE_ACK;
}
/**
* @brief Responder::actionDiscoveryUnmute
* @param message
* @param response
* @param msg
*/
void Responder::actionDiscoveryUnmute(const MsgPtr message, MsgPtr response)
void Responder::actionDiscoveryUnmute(MsgPair msg)
{
if (message->mdb.pdl() != 0)
return response->nak(NR_FORMAT_ERROR);
auto [_, response] = msg;
discovery_mute_flag_ = false;
response->appendParameterData(control_field);
response->responseType = RESPONSE_TYPE_ACK;
}
/**
* @brief Responder::actionGetCommsStatus
* @param message
* @param response
* @param msg
*/
void Responder::actionGetCommsStatus(const MsgPtr message, MsgPtr response)
void Responder::actionGetCommsStatus(MsgPair msg)
{
if (message->mdb.pdl() != 0)
return response->nak(NR_FORMAT_ERROR);
auto [_, response] = msg;
response->appendParameterData(short_message_counter_);
response->appendParameterData(length_mismatch_counter_);
response->appendParameterData(checksum_fail_counter_);
response->responseType = RESPONSE_TYPE_ACK;
}
/**
* @brief Responder::actionSetCommsStatus
* @param message
* @param response
* @param msg
*/
void Responder::actionSetCommsStatus(const MsgPtr message, MsgPtr response)
void Responder::actionSetCommsStatus(MsgPair msg)
{
if (message->mdb.pdl() != 0)
return response->nak(NR_FORMAT_ERROR);
auto [_, response] = msg;
short_message_counter_ = 0;
length_mismatch_counter_ = 0;
checksum_fail_counter_ = 0;
response->responseType = RESPONSE_TYPE_ACK;
}
/**
* @brief Responder::actionGetQueuedMessage
* @param message
* @param response
* @param msg
*/
void Responder::actionGetQueuedMessage(const MsgPtr message, MsgPtr response)
void Responder::actionGetQueuedMessage(MsgPair msg)
{
if (message->mdb.pdl() != 1)
return response->nak(NR_FORMAT_ERROR);
auto [_, response] = msg;
if (queued_messages_.empty())
{
response->mdb.pid = STATUS_MESSAGES;
actionGetStatusMessages(message, response);
response->mdb.pid = STATUS_MESSAGES.pid;
actionGetStatusMessages(msg);
return;
}
response->do_not_send = true;
auto msg = queued_messages_.front();
response = queued_messages_.front();
queued_messages_.pop_front();
last_status_message_ = msg;
send(msg);
last_status_message_ = response;
}
/**
* @brief Responder::actionGetStatusMessages
* @param message
* @param response
* @param msg
*/
void Responder::actionGetStatusMessages(const MsgPtr message, MsgPtr response)
void Responder::actionGetStatusMessages(MsgPair msg)
{
if (message->mdb.pdl() != 1)
return response->nak(NR_FORMAT_ERROR);
auto [command, response] = msg;
uint8_t queue = command->mdb.pd.front();
uint8_t type = message->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;
if (type != STATUS_GET_LAST_MESSAGE &&
type != STATUS_ERROR &&
type != STATUS_WARNING &&
type != STATUS_ADVISORY)
{
response->nak(NR_DATA_OUT_OF_RANGE);
return;
}
if (type == STATUS_GET_LAST_MESSAGE)
{
response->do_not_send = true;
send(last_status_message_);
return;
}
int counter = 0;
auto reportStatusQueue = [response, counter] (std::queue<StatusPtr> q) mutable
/// \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)
{
while(!q.empty() && counter < 25)
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)
{
for (uint8_t& b : q.front()->bytes)
response->appendParameterData(b);
counter++;
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));
}
};
if (type == STATUS_ERROR ||
type == STATUS_WARNING ||
type == STATUS_ADVISORY)
{
reportStatusQueue(queued_statuses_.at(STATUS_ERROR));
for (auto& [_, dev] : sub_devices_)
reportStatusQueue(dev->queued_statuses_.at(STATUS_ERROR));
}
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 (type == STATUS_WARNING ||
type == STATUS_ADVISORY)
{
reportStatusQueue(queued_statuses_.at(STATUS_WARNING));
for (auto& [_, dev] : sub_devices_)
reportStatusQueue(dev->queued_statuses_.at(STATUS_WARNING));
}
if (type == STATUS_ADVISORY)
{
reportStatusQueue(queued_statuses_.at(STATUS_ADVISORY));
for (auto& [_, dev] : sub_devices_)
reportStatusQueue(dev->queued_statuses_.at(STATUS_ADVISORY));
}
if (counter == 25)
if (count < 0)
response->responseType = RESPONSE_TYPE_ACK_OVERFLOW;
else
response->responseType = RESPONSE_TYPE_ACK;
/// The responder shall maintain reported status information until it has been successfully
/// delivered to the controller.
last_status_message_ = response;
}
/**
* @brief Responder::actionGetStatusIdDescription
* @param message
* @param response
* @param msg
*/
void Responder::actionGetStatusIdDescription(const MsgPtr message, MsgPtr response)
void Responder::actionGetStatusIdDescription(MsgPair msg)
{
if (message->mdb.pdl() != 2)
return response->nak(NR_FORMAT_ERROR);
uint16_t status = message->readType<uint16_t>(message->mdb.pd, 0);
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++)
@ -585,8 +434,6 @@ void Responder::actionGetStatusIdDescription(const MsgPtr message, MsgPtr respon
break;
response->appendParameterData(label.at(i));
}
response->responseType = RESPONSE_TYPE_ACK;
}
} // namespace RDM

View File

@ -37,7 +37,7 @@ class Responder
: public Device
{
public:
explicit Responder(UID id, Device* parent = nullptr);
explicit Responder(UID id);
virtual ~Responder();
std::shared_ptr<void> setSender(std::function<void(const std::vector<uint8_t>&)> cb);
@ -52,25 +52,22 @@ public:
};
};
virtual void send(const std::vector<uint8_t> &data);
virtual void send(const std::vector<uint8_t> &buffer);
virtual void send(const MsgPtr message);
virtual void receive(const std::vector<uint8_t> &data);
virtual void receive(const std::vector<uint8_t> &buffer);
virtual void receive(const MsgPtr message);
virtual void reset(bool hard) override;
protected:
virtual void rxDiscovery(const MsgPtr message, MsgPtr response);
virtual void rxGet(const MsgPtr message, MsgPtr response);
virtual void rxSet(const MsgPtr message, MsgPtr response);
void actionDiscoverUniqueBranch (const MsgPtr message, MsgPtr response);
void actionDiscoveryMute (const MsgPtr message, MsgPtr response);
void actionDiscoveryUnmute (const MsgPtr message, MsgPtr response);
void actionGetCommsStatus (const MsgPtr message, MsgPtr response);
void actionSetCommsStatus (const MsgPtr message, MsgPtr response);
void actionGetQueuedMessage (const MsgPtr message, MsgPtr response);
void actionGetStatusMessages (const MsgPtr message, MsgPtr response);
void actionGetStatusIdDescription (const MsgPtr message, MsgPtr response);
virtual void actionDiscoverUniqueBranch (MsgPair msg);
virtual void actionDiscoveryMute (MsgPair msg);
virtual void actionDiscoveryUnmute (MsgPair msg);
virtual void actionGetCommsStatus (MsgPair msg);
virtual void actionSetCommsStatus (MsgPair msg);
virtual void actionGetQueuedMessage (MsgPair msg);
virtual void actionGetStatusMessages (MsgPair msg);
virtual void actionGetStatusIdDescription (MsgPair msg);
private:
bool discovery_mute_flag_ = false;

View File

@ -0,0 +1,39 @@
/*
rdm/subdevice.cpp
Copyright (c) 2023 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 "subdevice.h"
namespace RDM {
SubDevice::SubDevice()
: BasicDevice()
{
}
SubDevice::~SubDevice()
{
}
} // namespace RDM

View File

@ -0,0 +1,42 @@
/*
rdm/subdevice.h
Copyright (c) 2023 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.
*/
#pragma once
#include "basicdevice.h"
namespace RDM {
/**
* @brief The SubDevice class
*/
class SubDevice
: public BasicDevice
{
public:
explicit SubDevice();
virtual ~SubDevice();
};
} // namespace RDM