RDMnet  0.3.0
Implementation of ANSI E1.33 (RDMnet)
View other versions:
Using the Device API

The RDMnet Device API exposes both a C and C++ language interface. The C++ interface is a header-only wrapper around the C interface.

Initialization

The RDMnet library must be globally initialized before using the RDMnet Device API. See Global Initialization and Destruction.

To create a device instance, use the rdmnet_device_create() function in C, or instantiate an rdmnet::Device and call its Startup() function in C++.

The RDMnet device API is an asynchronous, callback-oriented API. Part of the initial configuration for a device instance is a set of function pointers (or abstract interface) for the library to use as callbacks. Callbacks are dispatched from the background thread that is started when the RDMnet library is initialized.

#include "rdmnet/device.h"
// Sets optional values to defaults. Must pass your ESTA manufacturer ID. If you have not yet
// requested an ESTA manufacturer ID, the range 0x7ff0 to 0x7fff can be used for prototyping.
RdmnetDeviceConfig config = RDMNET_DEVICE_CONFIG_DEFAULT_INIT(MY_ESTA_MANUFACTURER_ID_VAL);
// Each device is a component that must have a Component ID (CID), which is simply a UUID. Pure
// redistributable software apps may generate a new CID on each run, but hardware-locked devices
// should use a consistent CID locked to a MAC address (a V3 or V5 UUID).
etcpal_generate_device_uuid("My Device Name", &device_mac_addr, 0, &config.cid);
// Set the callback functions - defined elsewhere
// p_some_opaque_data is an opaque data pointer that will be passed back to each callback function
rdmnet_device_set_callbacks(&config, my_device_connected_cb, my_device_connect_failed_cb,
my_device_disconnected_cb, my_device_rdm_command_received_cb,
my_device_llrp_rdm_command_received_cb, p_some_opaque_data);
rdmnet_device_t my_device_handle;
etcpal_error_t result = rdmnet_device_create(&config, &my_device_handle);
if (result == kEtcPalErrOk)
{
// Handle is valid and may be referenced in later calls to API functions.
}
else
{
// Some error occurred, handle is not valid.
}
Definitions for the RDMnet Device API.
etcpal_error_t
kEtcPalErrOk
etcpal_error_t etcpal_generate_device_uuid(const char *dev_str, const uint8_t *mac_addr, uint32_t uuid_num, EtcPalUuid *uuid)
void rdmnet_device_set_callbacks(RdmnetDeviceConfig *config, RdmnetDeviceConnectedCallback connected, RdmnetDeviceConnectFailedCallback connect_failed, RdmnetDeviceDisconnectedCallback disconnected, RdmnetDeviceRdmCommandReceivedCallback rdm_command_received, RdmnetDeviceLlrpRdmCommandReceivedCallback llrp_rdm_command_received, RdmnetDeviceDynamicUidStatusCallback dynamic_uid_status_received, void *context)
Set the main callbacks in an RDMnet device configuration structure.
Definition: device.c:182
#define RDMNET_DEVICE_CONFIG_DEFAULT_INIT(manu_id)
A default-value initializer for an RdmnetDeviceConfig struct.
Definition: device.h:297
int rdmnet_device_t
A handle to an RDMnet device.
Definition: device.h:53
etcpal_error_t rdmnet_device_create(const RdmnetDeviceConfig *config, rdmnet_device_t *handle)
Create a new instance of RDMnet device functionality.
Definition: device.c:219
A set of information that defines the startup parameters of an RDMnet Device.
Definition: device.h:227
EtcPalUuid cid
The device's CID.
Definition: device.h:233

class MyDeviceNotifyHandler : public rdmnet::Device::NotifyHandler
{
// Implement the NotifyHandler callbacks...
};
MyDeviceNotifyHandler my_device_notify_handler;
// Example method for generating a CID for a hardware-locked device:
etcpal::Uuid my_cid = etcpal::Uuid::Device("My Device Name", device_mac_addr, 0);
rdmnet::Device::Settings settings(my_cid, MY_ESTA_MANUFACTURER_ID_VAL);
// In this example we are using the convenience method to startup with the default scope. The
// Startup() overloads can be used to specify a scope to start on.
etcpal::Error result = device.StartupWithDefaultScope(my_device_notify_handler, settings);
if (result)
{
// Device is valid and running.
}
else
{
std::cout << "RDMnet device startup failed with error: " << result.ToString() << '\n';
}
std::string ToString() const
static Uuid Device(const std::string &device_str, const uint8_t *mac_addr, uint32_t uuid_num) noexcept
A base class for a class that receives notification callbacks from a device.
Definition: device.h:327
An instance of RDMnet device functionality.
Definition: device.h:315
etcpal::Error StartupWithDefaultScope(NotifyHandler &notify_handler, const Settings &settings, const etcpal::SockAddr &static_broker_addr=etcpal::SockAddr{})
Allocate resources and start up this device with the given configuration on the default RDMnet scope.
Definition: device.h:606
C++ wrapper for the RDMnet Device API.
A set of configuration settings that a device needs to initialize.
Definition: device.h:378

Deinitialization

The device should be shut down and destroyed gracefully before program termination. This will send a graceful disconnect message to any connected broker and deallocate the device's resources.

// At this point, the device is no longer running and its handle is no longer valid.
@ kRdmnetDisconnectShutdown
The remote component is shutting down.
Definition: common.h:87
etcpal_error_t rdmnet_device_destroy(rdmnet_device_t handle, rdmnet_disconnect_reason_t disconnect_reason)
Destroy a device instance.
Definition: device.c:257

device.Shutdown();
// At this point, the device is no longer running and its handle is no longer valid. It can be
// started again (with a new handle) by calling Startup() again.
void Shutdown(rdmnet_disconnect_reason_t disconnect_reason=kRdmnetDisconnectShutdown)
Shut down this device and deallocate resources.
Definition: device.h:664

Managing Scopes

Devices operate on a single RDMnet scope at a time, which is set at initial configuration time when the device instance is created, and can be changed using rdmnet_device_change_scope(). See the "Scopes" section in How RDMnet Works for more information on scopes.

On startup, a device will immediately begin the scope connection state machine from the RDMnet tick thread. If a static configuration is not provided (using the RDMNET_CLIENT_SET_SCOPE() macro to initialize the device's RdmnetScopeConfig, or calling rdmnet::Device::Startup() without a static_broker_addr argument), the first action will be to attempt to discover brokers for this scope via DNS-SD. Once a broker is found, connection will be initiated automatically, and the result will be delivered via either the connected or connect_failed callback.

If a broker for a given scope has been configured with a static IP address and port, you can skip DNS-SD discovery by providing this address to the relevant functions/macros.

You can change the initial scope configuration before the device is started:

RdmnetDeviceConfig config = RDMNET_DEVICE_CONFIG_DEFAULT_INIT(MY_ESTA_MANUFACTURER_ID_VAL);
// Edit the scope information in the configuration struct
RDMNET_CLIENT_SET_SCOPE(&config.scope_config, "My initial scope");
// Or, if configured with a static broker address...
RDMNET_CLIENT_SET_STATIC_SCOPE(&config.scope_config, "My initial scope", static_broker_addr);
#define RDMNET_CLIENT_SET_SCOPE(configptr, scope_str)
Initialize an RdmnetScopeConfig struct with a scope string.
Definition: client.h:262
#define RDMNET_CLIENT_SET_STATIC_SCOPE(configptr, scope_str, broker_addr)
Initialize an RdmnetScopeConfig struct with a scope string and static broker address.
Definition: client.h:295
RdmnetScopeConfig scope_config
(optional) The device's configured RDMnet scope.
Definition: device.h:251

etcpal::Error result = device.Startup(my_device_notify_handler, settings, "My initial scope");
// Or, if configured with a static broker address...
etcpal::Error result = device.Startup(my_device_notify_handler, settings, "My initial scope", my_static_broker_addr);
etcpal::Error Startup(NotifyHandler &notify_handler, const Settings &settings, const char *scope_id_str, const etcpal::SockAddr &static_broker_addr=etcpal::SockAddr{})
Allocate resources and start up this device with the given configuration on the given RDMnet scope.
Definition: device.h:629

Or change the scope while the device is running. This will cause the device to disconnect from the old scope and connect to the new one.

RdmnetScopeConfig new_scope_config;
RDMNET_CLIENT_SET_SCOPE(&new_scope_config, "My new scope");
// The disconnect_reason argument provides the RDMnet disconnect reason code to send on the current
// connected scope. Select the most appropriate value for why the scope is changing.
etcpal_error_t result = rdmnet_device_change_scope(my_device_handle, &new_scope_config, kRdmnetDisconnectUserReconfigure);
@ kRdmnetDisconnectUserReconfigure
The component was reconfigured via some other means, and the new configuration requires connection te...
Definition: common.h:112
etcpal_error_t rdmnet_device_change_scope(rdmnet_device_t handle, const RdmnetScopeConfig *new_scope_config, rdmnet_disconnect_reason_t disconnect_reason)
Change the device's scope.
Definition: device.c:1154
A set of configuration information for a single scope in which an RDMnet client is participating.
Definition: client.h:224

// The disconnect_reason argument provides the RDMnet disconnect reason code to send on the current
// connected scope. Select the most appropriate value for why the scope is changing.
etcpal::Error ChangeScope(const char *new_scope_id_str, rdmnet_disconnect_reason_t disconnect_reason, const etcpal::SockAddr &static_broker_addr=etcpal::SockAddr{})
Change the device's RDMnet scope.
Definition: device.h:683

Handling Callbacks

The library will dispatch callback notifications from the background thread which is started when rdmnet_init() is called. It is safe to call any RDMnet API function from any callback.

Generally the first callback a device gets will be when it has connected to a broker:

void device_connected_callback(rdmnet_device_t handle, const RdmnetClientConnectedInfo* info, void* context)
{
char addr_str[ETCPAL_IP_STRING_BYTES];
etcpal_ip_to_string(&info->broker_addr.ip, addr_str);
printf("Connected to broker '%s' at address %s:%d\n", info->broker_name, addr_str, info->broker_addr.port);
}
T printf(T... args)
#define ETCPAL_IP_STRING_BYTES
etcpal_error_t etcpal_ip_to_string(const EtcPalIpAddr *src, char *dest)
EtcPalIpAddr ip
Information provided by the library about a successful RDMnet client connection.
Definition: client.h:151
const char * broker_name
The DNS name of the broker, if it was discovered using DNS-SD; otherwise an empty string.
Definition: client.h:155
EtcPalSockAddr broker_addr
The IP address and port of the remote broker to which we have connected.
Definition: client.h:153

void MyDeviceNotifyHandler::HandleConnectedToBroker(rdmnet::Device::Handle handle,
{
std::cout << "Connected to broker '" << info.broker_name()
<< "' at IP address " << info.broker_addr().ToString() << '\n';
}
std::string ToString() const
Information about a successful connection to a broker delivered to an RDMnet callback function.
Definition: client.h:149
std::string broker_name() const
Get the DNS name of the broker (if it was discovered via DNS-SD; otherwise this will be an empty stri...
Definition: client.h:184
constexpr etcpal::SockAddr broker_addr() const noexcept
Get the IP address and port of the remote broker to which we have connected.
Definition: client.h:178
rdmnet_device_t Handle
A handle type used by the RDMnet library to identify device instances.
Definition: device.h:318

Connection Failure

It's worth noting connection failure as a special case here. RDMnet connections can fail for many reasons, including user misconfiguration, network misconfiguration, components starting up or shutting down, programming errors, and more.

The ClientConnectFailedInfo and ClientDisconnectedInfo structs passed back with the "connect failed" and "disconnected" callbacks respectively have comprehensive information about the failure, including enum values containing the library's categorization of the failure, protocol reason codes and socket errors where applicable. This information is typically used mostly for logging and debugging. Each of these codes has a respective to_string() function to aid in logging.

For programmatic use, the structs also contain a will_retry member which indicates whether the library plans to retry this connection in the background. The only circumstances under which the library will not retry is when a connection failure is determined to be either a programming error or a user misconfiguration. Some examples of these circumstances are:

  • The broker explicitly rejected a connection with a reason code indicating a configuration error, such as CONNECT_SCOPE_MISMATCH or CONNECT_DUPLICATE_UID.
  • The library failed to create a network socket before the connection was initiated.

Adding Endpoints and Responders

As explained in Devices and Gateways, RDMnet devices can contain endpoints which serve as grouping mechanisms for virtual or physical RDM responders. The RDMnet device API has functionality for managing endpoints and responders on a local device.

Adding Physical Endpoints and Responders

Physical endpoints are added using the physical endpoint configuration structure. This structure defines the endpoint number (an integer between 1 and 63,999) and any responders which are present on the endpoint initially.

// Create a physical endpoint numbered 1, with no responders on it initially.
etcpal_error_t result = rdmnet_device_add_physical_endpoint(my_device_handle, &endpoint_config);
if (result == kEtcPalErrOk)
{
// The endpoint has been added. If currently connected, the library will send the proper
// notification which indicates to connected controllers that there is a new endpoint present.
}
etcpal_error_t rdmnet_device_add_physical_endpoint(rdmnet_device_t handle, const RdmnetPhysicalEndpointConfig *endpoint_config)
Add a physical endpoint to a device.
Definition: device.c:512
#define RDMNET_PHYSICAL_ENDPOINT_INIT(endpoint_num)
An initializer for an RdmnetPhysicalEndpointConfig instance.
Definition: device.h:220
Configuration information for a physical endpoint on a device.
Definition: device.h:198

// Endpoint configs are constructed implicitly from endpoint numbers, so when creating an endpoint
// with no responders on it initially, it's as simple as:
etcpal::Error result = device.AddPhysicalEndpoint(1); // Create a physical endpoint numbered 1
if (result)
{
// The endpoint has been added. If currently connected, the library will send the proper
// notification which indicates to connected controllers that there is a new endpoint present.
}
etcpal::Error AddPhysicalEndpoint(const PhysicalEndpointConfig &physical_config)
Add a physical endpoint to a device.
Definition: device.h:909

Adding and removing physical responders from an endpoint will typically be done when an RDMnet gateway discovers or loses RDM responders on its RDM ports. For example:

void rdm_responder_discovered(uint16_t port_endpoint_number, const RdmUid* responder_uid, uint16_t control_field,
const RdmUid* binding_uid)
{
new_responder.uid = *responder_uid;
new_responder.control_field = control_field;
new_responder.binding_uid = *binding_uid;
etcpal_error_t result = rdmnet_device_add_physical_responders(my_device_handle, port_endpoint_number,
&new_responder, 1);
if (result == kEtcPalErrOk)
{
// The responder has been added. If currently connected, the library will send the proper
// notification which indicates to connected controllers that there is a new responder present.
}
}
etcpal_error_t rdmnet_device_add_physical_responders(rdmnet_device_t handle, uint16_t endpoint_id, const RdmnetPhysicalEndpointResponder *responders, size_t num_responders)
Add one or more responders to a physical endpoint.
Definition: device.c:856
Identifying information for a physical RDM responder connected to an RDMnet gateway.
Definition: device.h:181
RdmUid uid
The responder's UID.
Definition: device.h:183
RdmUid binding_uid
The binding UID received in the DISC_MUTE message from this responder.
Definition: device.h:190
uint16_t control_field
The control field received in the DISC_MUTE message from this responder.
Definition: device.h:185

void RdmResponderDiscovered(uint16_t port_endpoint_number, const rdm::Uid& responder_uid, uint16_t control_field,
const rdm::Uid& binding_uid)
{
etcpal::Error result = device.AddPhysicalResponder(port_endpoint_number, responder_uid, control_field, binding_uid);
if (result)
{
// The responder has been added. If currently connected, the library will send the proper
// notification which indicates to connected controllers that there is a new responder present.
}
}
etcpal::Error AddPhysicalResponder(uint16_t endpoint_id, const rdm::Uid &responder_uid, uint16_t control_field, const rdm::Uid &binding_uid=rdm::Uid{})
Add a responder to a physical endpoint.
Definition: device.h:1070

Note that the RDMnet library requires the control field (and optional binding UID) received in the DISC_MUTE message from a physical responder, so that controllers can fetch this information.

You can also add responders initially present on an endpoint at the same time as the endpoint is added. This should be done anytime the initial responders on an endpoint are known, as it reduces the volume of RDMnet messages that need to be sent.

// Create a physical endpoint numbered 2
// UIDs for a group of responders that have already been discovered on this endpoint are contained
// in the responders array below.
// Assume the above array is filled in with the correct responder information...
endpoint_config.responders = responders;
endpoint_config.num_responders = 3;
// Add the endpoint
etcpal_error_t result = rdmnet_device_add_physical_endpoint(my_device_handle, &endpoint_config);
if (result == kEtcPalErrOk)
{
// The endpoint has been added. If currently connected, the library will send the proper
// notification which indicates to connected controllers that there is a new endpoint and new
// responders present.
}
size_t num_responders
Size of the responders array.
Definition: device.h:204
const RdmnetPhysicalEndpointResponder * responders
An array of initial physical RDM responders on this endpoint, identified by static UID.
Definition: device.h:202

// Create a physical endpoint numbered 2 with the UIDs and control fields of 3
// previously-discovered physical responders.
rdmnet::PhysicalEndpointResponder({0x6574, 0x00000001}, 0),
rdmnet::PhysicalEndpointResponder({0x6574, 0x00000002}, 0),
rdmnet::PhysicalEndpointResponder({0x6574, 0x00000003}, 0),
};
rdmnet::PhysicalEndpointConfig endpoint_config(2, responders);
// Add the endpoint
etcpal::Error result = device.AddPhysicalEndpoint(endpoint_config);
if (result)
{
// The endpoint has been added. If currently connected, the library will send the proper
// notification which indicates to connected controllers that there is a new endpoint and new
// responders present.
}
Configuration information for a physical endpoint on a device.
Definition: device.h:251
Identifying information for a physical RDM responder connected to an RDMnet gateway.
Definition: device.h:203

Adding Virtual Endpoints and Responders

The process for adding virtual endpoints is similar to that for physical endpoints, except that virtual responders can have Dynamic UIDs (see Roles and Addressing for more information about this).

// Create a virtual endpoint numbered 3
// The virtual endpoint has a single responder that needs a dynamic UID. The responder needs a
// permanent Responder ID (RID), which is a UUID that should not change over the lifetime of the
// responder.
etcpal_string_to_uuid("f7c58fe2-d380-4367-91d1-b148eade448d", &rid);
endpoint_config.dynamic_responders = &rid;
endpoint_config.num_dynamic_responders = 1;
// Add the endpoint
etcpal_error_t result = rdmnet_device_add_virtual_endpoint(my_device_handle, &endpoint_config);
if (result == kEtcPalErrOk)
{
// The endpoint has been added. If currently connected, the library will send the proper
// notification which indicates to connected controllers that there is a new endpoint present,
// and request a dynamic UID for the responder which will be delivered to the
// RdmnetDeviceDynamicUidStatusCallback.
}
bool etcpal_string_to_uuid(const char *str, EtcPalUuid *uuid)
#define RDMNET_VIRTUAL_ENDPOINT_INIT(endpoint_num)
An initializer for an RdmnetVirtualEndpointConfig instance.
Definition: device.h:174
etcpal_error_t rdmnet_device_add_virtual_endpoint(rdmnet_device_t handle, const RdmnetVirtualEndpointConfig *endpoint_config)
Add a virtual endpoint to a device.
Definition: device.c:584
Configuration information for a virtual endpoint on a device.
Definition: device.h:145
const EtcPalUuid * dynamic_responders
An array of initial virtual RDM responders on this endpoint, identified by RID.
Definition: device.h:152
size_t num_dynamic_responders
Size of the dynamic_responders array.
Definition: device.h:154

// Create a virtual endpoint numbered 3, which has a single responder that needs a dynamic UID. The
// responder needs a permanent Responder ID (RID), which is a UUID that should not change over the
// lifetime of the responder.
std::vector<etcpal::Uuid> responders = { etcpal::Uuid::FromString("f7c58fe2-d380-4367-91d1-b148eade448d") };
rdmnet::VirtualEndpointConfig endpoint_config(3, responders);
// Add the endpoint
etcpal::Error result = device.AddVirtualEndpoint(endpoint_config);
if (result)
{
// The endpoint has been added. If currently connected, the library will send the proper
// notification which indicates to connected controllers that there is a new endpoint present,
// and request a dynamic UID for the responder which will be delivered to the
// MyDeviceNotifyHandler::HandleDynamicUidStatus() callback.
}
static Uuid FromString(const char *uuid_str) noexcept
etcpal::Error AddVirtualEndpoint(const VirtualEndpointConfig &endpoint_config)
Add a virtual endpoint to a device.
Definition: device.h:875
Configuration information for a virtual endpoint on a device.
Definition: device.h:71

Before dynamic virtual responders can be used, they must be assigned a dynamic UID. The status of dynamic UID assignment is communicated through the dynamic UID status callback.

void handle_dynamic_uid_status(rdmnet_device_t handle, const RdmnetDynamicUidAssignmentList* list, void* context)
{
// Check handles and/or context as necessary...
for (const RdmnetDynamicUidMapping* mapping = list->mappings; mapping < list->mappings + list->num_mappings;
++mapping)
{
if (mapping->status_code == kRdmnetDynamicUidStatusOk)
{
// This function is assumed to add the dynamic UID to the virtual responder's runtime data.
// This UID should be used to determine whether an RDM command received later is addressed to
// the virtual responder.
set_responder_dynamic_uid(&mapping->rid, &mapping->uid);
}
else
{
char rid_str[ETCPAL_UUID_STRING_BYTES];
etcpal_uuid_to_string(&mapping->rid, rid_str);
printf("Error obtaining dynamic UID for responder %s: '%s'\n", rid_str,
rdmnet_dynamic_uid_status_to_string(mapping->status_code));
}
}
}
bool etcpal_uuid_to_string(const EtcPalUuid *uuid, char *buf)
#define ETCPAL_UUID_STRING_BYTES
const char * rdmnet_dynamic_uid_status_to_string(rdmnet_dynamic_uid_status_t code)
Get a string description of an RDMnet Dynamic UID status code.
Definition: common.c:392
@ kRdmnetDynamicUidStatusOk
The Dynamic UID Mapping was fetched or assigned successfully.
Definition: common.h:136
A list of mappings from dynamic UIDs to responder IDs received from an RDMnet broker.
Definition: message.h:245
size_t num_mappings
The size of the mappings array.
Definition: message.h:249
RdmnetDynamicUidMapping * mappings
An array of dynamic UID mappings.
Definition: message.h:247
A mapping from a dynamic UID to a responder ID (RID).
Definition: message.h:234

void MyDeviceNotifyHandler::HandleDynamicUidStatus(rdmnet::Device::Handle handle,
{
// Check handles as necessary...
for (const rdmnet::DynamicUidMapping& mapping : list.GetMappings())
{
if (mapping.IsOk())
{
// This function is assumed to add the dynamic UID to the virtual responder's runtime data.
// This UID should be used to determine whether an RDM command received later is addressed to
// the virtual responder.
SetResponderDynamicUid(mapping.rid, mapping.uid);
}
else
{
std::cout << "Error obtaining dynamic UID for responder " << mapping.rid.ToString() << ": '"
<< mapping.CodeToString() << "'\n";
}
}
}
A list of mappings from dynamic UIDs to responder IDs received from an RDMnet broker.
Definition: dynamic_uid.h:98
std::vector< DynamicUidMapping > GetMappings() const
Copy out the list of dynamic UID mappings.
Definition: dynamic_uid.h:129
A mapping from a dynamic UID to a responder ID (RID).
Definition: dynamic_uid.h:41

One last thing about endpoints: if the set of physical and virtual endpoints that a device has is known at initialization time, they can be added to the initial configuration structures for a device instance, so that they don't need to be added later.

Handling RDM Commands

Handling RDM commands is the main functionality of RDMnet devices. See Handling RDM Commands for information on how to do this.