RDMnet  0.3.0
Implementation of ANSI E1.33 (RDMnet)
View other versions:
Handling RDM Commands

Handling and responding to RDM commands is the core functionality of RDMnet devices and gateways, and an optional part of the functionality of RDMnet controllers.

Interpreting RDM Commands

RDM commands are delivered via a callback that looks like the below:

void rdm_command_received(rdmnet_device_t handle, const RdmnetRdmCommand* cmd, RdmnetSyncRdmResponse* response,
void* context)
{
{
// Handle the command with the default responder.
}
else
{
// Command is addressed to a non-default virtual or physical RDM responder.
// Inspect cmd->dest_endpoint and cmd->rdm_header.dest_uid to determine which one.
}
}
#define RDMNET_COMMAND_IS_TO_DEFAULT_RESPONDER(cmd_ptr)
Whether an RdmnetRdmCommand is addressed to the default responder.
Definition: message.h:69
int rdmnet_device_t
A handle to an RDMnet device.
Definition: device.h:53
An RDMnet RDM command received by this component.
Definition: message.h:53
This structure should not be manipulated directly - use the macros to access it:
Definition: common.h:214

rdmnet::ResponseAction MyRdmnetNotifyHander::HandleRdmCommand(rdmnet::Device::Handle handle,
const rdmnet::RdmCommand& cmd)
{
{
// Handle the command with the default responder.
}
else
{
// Command is addressed to a non-default virtual or physical RDM responder.
// Inspect cmd.dest_endpoint() and cmd.rdm_dest_uid() to determine which one.
}
}
rdmnet_device_t Handle
A handle type used by the RDMnet library to identify device instances.
Definition: device.h:318
An RDM command received over RDMnet and delivered to an RDMnet callback function.
Definition: rdm_command.h:43
constexpr bool IsToDefaultResponder() const noexcept
Whether this command is addressed to the RDMnet default responder.
Definition: rdm_command.h:222

An RDM command in RDMnet can be addressed to the default responder or a sub-responder (see Devices and Gateways for more information on these concepts). A command to the default responder addresses the RDM parameters of the device implementing RDMnet. A command to a sub-responder addresses either a portion of the device's functionality dedicated to handling sACN (a virtual responder) or a physical RDM responder connected to an RDMnet gateway.

Besides this special RDMnet addressing information, the RDM command structure delivered with the RDM command handler callback contains data typical to RDM handling logic; the command class, parameter ID, subdevice ID, and parameter data, if any. The only other RDMnet-specific construct is the sequence number, which is only used internally by the library.

Responding to RDM Commands

This RDMnet library allows two different paradigms for responding to RDM commands: synchronous and asynchronous. The RDMnet Device API will be used as an example, but this also applies to the Controller API if using the RDM responder callbacks.

By the time this callback is called, the library will already have handled some error conditions internally. For example, if the received RDMnet RDM command was malformed, or not addressed to the local RDMnet component, the message will be dropped or an error response will be sent according to the rules of the RDMnet standard.

Responding synchronously

If you can access the data needed to respond to the RDM command relatively quickly from the code path of the notification callback, it's convenient to respond synchronously. The RDM command notification callbacks provide a way to set the response information directly from the callback, which the library will use to send a response after the callback returns.

Important note: Do not do this when accessing response data may block for a significant time. See "Responding asynchronously" below for that scenario.

There are two different ways to respond synchronously to an RDM command received over RDMnet:

ACK (ACKnowledge)

An ACK response indicates that the action in the command was carried out. ACK responses can contain data; for example, the responses to most RDM GET commands contain data that was requested by a controller. When an ACK response contains data, it should be copied into the buffer that was provided when the RDMnet handle was created, before returning from the callback function. All notifications for any given RDMnet handle are delivered from the same thread, so accesses to this buffer from the callback context are thread-safe.

After copying the data, use the API-specific method to indicate that this is an ACK response as shown below.

// This buffer was provided as part of the RdmnetDeviceConfig when the device handle was created.
static uint8_t my_rdmnet_response_buf[MY_MAX_RESPONSE_SIZE];
void rdm_command_received(rdmnet_device_t handle, const RdmnetRdmCommand* cmd, RdmnetSyncRdmResponse* response,
void* context)
{
// Very simplified example...
if (cmd->rdm_header.command_class == kRdmCCGetCommand && cmd->rdm_header.param_id == E120_DEVICE_INFO)
{
memcpy(my_rdmnet_response_buf, kMyDeviceInfoData, DEVICE_INFO_DATA_SIZE);
RDMNET_SYNC_SEND_RDM_ACK(response, DEVICE_INFO_DATA_SIZE);
// ACK will be sent after this function returns.
}
}
#define RDMNET_SYNC_SEND_RDM_ACK(response_ptr, response_data_len_in)
Indicate that an RDM ACK should be sent when this callback returns.
Definition: common.h:240
T memcpy(T... args)
RdmCommandHeader rdm_header
The header information from the encapsulated RDM command.
Definition: message.h:61

// This buffer was provided as part of the Device::Settings when the Device instance was
// initialized.
static uint8_t my_rdmnet_response_buf[kMyMaxResponseSize];
rdmnet::RdmResponseAction MyRdmnetNotifyHander::HandleRdmCommand(rdmnet::Device::Handle handle,
const rdmnet::RdmCommand& cmd)
{
// Very simplified example...
if (cmd.IsGet() && cmd.param_id() == E120_DEVICE_INFO)
{
std::memcpy(my_rdmnet_response_buf, kMyDeviceInfoData, kDeviceInfoDataSize);
return rdmnet::RdmResponseAction::SendAck(kDeviceInfoDataSize);
}
}
constexpr uint16_t param_id() const noexcept
Get the RDM parameter ID (PID) of this command.
Definition: rdm_command.h:179
constexpr bool IsGet() const noexcept
Whether this command is an RDM GET command.
Definition: rdm_command.h:209
A class representing a synchronous action to take in response to a received RDM command.
Definition: common.h:106
static RdmResponseAction SendAck(size_t response_data_len=0)
Send an RDM ACK, optionally including some response data.
Definition: common.h:122

Most SET commands don't require data in their ACK response; the response is just an acknowledgment that the changed parameter was acted upon. In this case, there's no need to copy any data before responding with an ACK.

void rdm_command_received(rdmnet_device_t handle, const RdmnetRdmCommand* cmd, RdmnetSyncRdmResponse* response,
void* context)
{
// Very simplified example...
if (cmd->rdm_header.command_class == kRdmCCSetCommand && cmd->rdm_header.param_id == E120_IDENTIFY_DEVICE)
{
if (cmd->data[0] == 0)
{
// Stop the physical identify operation
}
else
{
// Start the physical identify operation
}
// Indicate an ACK should be sent with no data.
// ACK will be sent after this function returns.
}
}
const uint8_t * data
Pointer to buffer containing any associated RDM parameter data.
Definition: message.h:63

rdmnet::RdmResponseAction MyRdmnetNotifyHander::HandleRdmCommand(rdmnet::Device::Handle handle,
const rdmnet::RdmCommand& cmd)
{
// Very simplified example...
if (cmd.IsSet() && cmd.param_id() == E120_IDENTIFY_DEVICE)
{
if (cmd.data()[0] == 0)
{
// Stop the physical identify operation
}
else
{
// Start the physical identify operation
}
// Indicate an ACK should be sent with no data.
}
}
constexpr bool IsSet() const noexcept
Whether this command is an RDM SET command.
Definition: rdm_command.h:215
constexpr const uint8_t * data() const noexcept
Get a pointer to the RDM parameter data buffer contained within this command.
Definition: rdm_command.h:191

NACK (Negative ACKnowledge)

A NACK response indicates that the RDMnet command was valid, but the RDM transaction cannot be acted upon for some reason. NACK responses contain a reason code that indicates the reason that the command cannot be acted upon. Common reasons to NACK an RDMnet RDM command include:

  • The given RDM Parameter ID (PID) is not supported by this responder (kRdmNRUnknownPid)
  • The PID is supported, but the requested command class is not (kRdmNRUnsupportedCommandClass)
    • Example: a SET was requested on a read-only parameter
  • Some data present in the command contains an index that is out of range (kRdmNRDataOutOfRange)
    • Example: a device has 3 sensors and a GET:SENSOR_VALUE command was received for sensor number 4

To respond with a NACK, use the API-specific method to indicate that this is a NACK response and provide the reason as shown below.

// Called from rdm_command_received()
void handle_get_sensor_value(rdmnet_device_t handle, const uint8_t* data, RdmnetSyncRdmResponse* response)
{
uint8_t sensor_num = data[0];
if (sensor_num >= NUM_SENSORS)
{
RDMNET_SYNC_SEND_RDM_NACK(response, kRdmNRDataOutOfRange);
// NACK will be sent after the RDM command handler callback returns.
}
else
{
// Process data and send ack...
}
}
#define RDMNET_SYNC_SEND_RDM_NACK(response_ptr, nack_reason_in)
Indicate that an RDM NACK should be sent when this callback returns.
Definition: common.h:252

// Called from MyRdmnetNotifyHandler::HandleRdmCommand()
rdmnet::RdmResponseAction MyRdmnetNotifyHander::HandleGetSensorValue(const uint8_t* data)
{
uint8_t sensor_num = data[0];
if (sensor_num >= NUM_SENSORS)
{
return rdmnet::RdmResponseAction::SendNack(kRdmNRDataOutOfRange);
}
}
static RdmResponseAction SendNack(rdm_nack_reason_t nack_reason)
Send an RDM NACK with a reason code.
Definition: common.h:131

Responding asynchronously

If you choose to defer the response to an RDM command to be sent at a later time, you must ensure that every RDM command gets a response eventually (one of ACK, NACK or RPT Status).

RPT Status

An RPT Status response is an RDMnet protocol level response to an RDM command. Most RPT Status messages (except kRptStatusBroadcastComplete) indicate that something has gone wrong at the RDMnet protocol level and the request cannot be handled. RPT Status responses include a code that indicates the reason for the status message.

Most conditions that would require sending an RPT Status are handled at the library level. The ones that must be handled by the application relate exclusively to the operation of RDMnet Gateways. RPT Status codes that need to be handled by a gateway application include:

Saving Commands

The RDM command structures delivered to callbacks by the RDMnet library reference data that is kept in buffers owned by the library, and will be reused for other data when that callback returns. In order to defer responses to RDM commands to be sent later, it's necessary to save the command data to be sent along with its corresponding response.

RDMnet provides APIs for saving the command into different structs and/or classes which contain data buffers to hold the RDM parameter data.

void rdm_command_received(rdmnet_device_t handle, const RdmnetRdmCommand* cmd, RdmnetSyncRdmResponse* response,
void* context)
{
// Note that this type contains a byte array large enough for the largest possible RDM command
// data buffer, e.g. 231 bytes.
rdmnet_save_rdm_command(cmd, &saved_cmd);
// Add saved_cmd to some memory location to be used later...
}
// Later, when the response is available...
rdmnet_device_send_rdm_ack(my_device_handle, &saved_cmd, response_data, response_data_len);
etcpal_error_t rdmnet_save_rdm_command(const RdmnetRdmCommand *command, RdmnetSavedRdmCommand *saved_command)
Save the data in a received RDM command for later use with API functions from a different context.
Definition: message.c:70
#define RDMNET_SYNC_DEFER_RDM_RESPONSE(response_ptr)
Defer the RDM response to be sent later from another context.
Definition: common.h:264
etcpal_error_t rdmnet_device_send_rdm_ack(rdmnet_device_t handle, const RdmnetSavedRdmCommand *received_cmd, const uint8_t *response_data, size_t response_data_len)
Send an RDM ACK response from a device.
Definition: device.c:286
An RDM command received by this component and saved for a later response.
Definition: message.h:73

rdmnet::RdmResponseAction MyRdmnetNotifyHander::HandleRdmCommand(rdmnet::Device::Handle handle,
const rdmnet::RdmCommand& cmd)
{
// Note that this type contains a byte array large enough for the largest possible RDM command
// data buffer, e.g. 231 bytes.
rdmnet::SavedRdmCommand saved_cmd = cmd.Save();
// Add saved_cmd to some memory location to be used later...
}
// Later, when the response is available...
my_device.SendRdmAck(saved_cmd, response_data, response_data_len);
SavedRdmCommand Save() const
Save the data in this command for later use with API functions from a different context.
Definition: rdm_command.h:241
static RdmResponseAction DeferResponse()
Defer the RDM response to be sent later from another context.
Definition: common.h:150
An RDM command received over RDMnet by a local component and saved for a later response.
Definition: rdm_command.h:87

PIDs handled by the library

The RDMnet library has all the information necessary to handle certain RDMnet-related RDM commands internally with no intervention by the application. The list of PIDs fully handled by the RDMnet library are:

  • ANSI E1.37-7
    • ENDPOINT_LIST
    • ENDPOINT_LIST_CHANGE
    • ENDPOINT_RESPONDERS
    • ENDPOINT_RESPONDER_LIST_CHANGE
    • BINDING_CONTROL_FIELDS
  • ANSI E1.33
    • TCP_COMMS_STATUS
    • BROKER_STATUS
      • Will be handled according to the policies given in the broker's settings configuration.

There is no need to include these PID values in your response to the GET:SUPPORTED_PARAMETERS command; they will be automatically added by the library before sending the response.

Note that the RDMnet standard requires support for additional PIDs that are not handled by the library. If you are using the Controller or Device API, you must support these PIDs in your implementation (or configure the Controller API with RDM data; see Using the Controller API).

  • ANSI E1.20
    • IDENTIFY_DEVICE
    • SUPPORTED_PARAMETERS
    • PARAMETER_DESCRIPTION
    • MANUFACTURER_LABEL
    • DEVICE_MODEL_DESCRIPTION
    • SOFTWARE_VERSION_LABEL
    • DEVICE_LABEL
  • ANSI E1.33
    • COMPONENT_SCOPE
    • SEARCH_DOMAIN