To create a controller instance, use the rdmnet_controller_create() function in C, or instantiate an rdmnet::Controller and call its Startup() function in C++. Most apps will only need a single controller instance. One controller can monitor an arbitrary number of RDMnet scopes at once.
The RDMnet controller API is an asynchronous, callback-oriented API. Part of the initial configuration for a controller 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.
A set of configuration settings that a controller needs to initialize.
Definition: controller.h:171
Deinitialization
The controller should be shut down and destroyed gracefully before program termination. This will send a graceful disconnect message to any connected brokers and deallocate the controller's resources.
Shut down this controller and deallocate resources.
Definition: controller.h:624
Managing Scopes
A controller instance is initially created without any configured scopes. If the app has not been reconfigured by a user, the E1.33 RDMnet standard requires that the RDMnet default scope be configured automatically. There is a shortcut function for this. Otherwise, you can add a custom scope.
Per the requirements of RDMnet, a scope string is always UTF-8 and is thus represented by a char[] in C and a std::string in C++.
See the "Scopes" section in How RDMnet Works for more information on scopes.
A handle to a scope that an RDMnet client participates in.
Definition: client.h:447
Dynamic vs Static Scopes
Adding a scope 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 an RdmnetScopeConfig, or calling rdmnet::Controller::AddScope() with only one argument) the first action will be to attempt to discover brokers for this scope using 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 a static configuration:
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; in fact, this is the recommended way of handling many callbacks.
For example, a very common controller behavior will be to fetch a client list from the broker after a successful connection:
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.
Discovering Devices
To discover devices in RDMnet, you need to request a Client List from the broker you're connected to. In our API, this is very easy - as we saw in the callbacks section earlier, we can just call rdmnet_controller_request_client_list() or rdmnet::Controller::RequestClientList(). This sends the appropriate request to the broker, and the reply will come back in the "Client List Update" callback:
Brokers also send Client List Update messages asynchronously when things change; these messages contain only the differences from the last client list sent to a given controller. This means you should only have to request a full client list when a new connection completes; after that, expect periodic callbacks notifying you of changes, with the client_list_action_t set appropriately as shown above.
Clients include both devices and other controllers; to differentiate the two, check the type field in each RPT client entry structure.
Sending RDM Commands
Sending RDM commands requires a destination address structure to indicate the RDMnet component and RDM responder to which the command is addressed. See Devices and Gateways for more information on the fields of the destination address structure.
After sending a command, the library will provide a sequence number that will be echoed in the corresponding RDM response. This sequence number should be saved.
Get a DestinationAddr representing a message addressed to a component's default responder.
Definition: client.h:94
Handling RDM Responses
Responses to commands you send will be delivered asynchronously through the "RDM response" callback. You may also receive "unsolicited responses" - asynchronous state updates from devices that don't correspond to changes you requested.
Responses are always delivered atomically in RDMnet (contrast this with RDM, which uses the ACK_OVERFLOW mechanism to deliver responses with oversized data). This means that RDM response data in RDMnet can be larger than the 231-byte limit that is customary in RDM. The only time a response can be fragmented is if the RDMnet library was compiled with dynamic memory allocation disabled (see the note on the more_coming flag below for more information on this).
Note that response callbacks reference data buffers owned by the library, which will be invalid when the callback returns. See Data Ownership Paradigms in the RDMnet Library for more information.
Append more data to this response's parameter data.
Definition: rdm_response.h:672
Handling RPT Status Messages
If something went wrong while either a broker or device was processing your message, you will get a response called an "RPT Status". There is a separate callback for handling these messages.
When you send an RDM command, you start a transaction that is identified by a 32-bit sequence number. That transaction is considered completed when you get either an RDM Response or an RPT Status containing that same sequence number.
Convert the status message's code to a string representation.
Definition: rpt_status.h:152
Getting Responder IDs
Controllers may encounter RDMnet responders which have dynamic UIDs. Base RDMnet components such as controllers and devices can have dynamic UIDs, as can virtual responders present on devices. See Devices and Gateways for more information on virtual responders, and Roles and Addressing for more information on dynamic UIDs. As a reminder, a dynamic UID is identified by the top bit of the Manufacturer ID portion being set and the UID not being a broadcast UID; this can be tested using the convenience methods in the RDM library: RDMNET_UID_IS_DYNAMIC() and/or rdm::Uid::IsDynamic().
A responder using a dynamic UID may get a different UID at any time. For this reason, it is useful for a controller to be able to get a more permanent identifier for an RDMnet responder.
For controllers, devices and brokers, the permanent identifier is the CID, which is present alongside the UID in an RPT client list entry. If you want to track these components beyond a single RDMnet session, be sure to store the CID.
Virtual responders present on a device have a similar identifier called a Responder ID (RID) which has the same function as a CID for that virtual responder. The UIDs of a device's virtual responders are obtained using the ENDPOINT_RESPONDERS RDM command; however, this command's data does not include the RID. To obtain RIDs for a set of virtual responders, it is necessary to query the connected broker.
The broker will respond with a list of mappings between dynamic UIDs and RIDs known as a Dynamic UID Assignment List. Each entry in the list will contain a status code indicating whether the RID was looked up successfully, followed by the UID and RID.
A mapping from a dynamic UID to a responder ID (RID).
Definition: dynamic_uid.h:41
Handling RDM Commands
In addition to getting information about responders, RDMnet controllers are required to respond to a basic set of RDM commands, which allows them to be identified by other controllers on the network. By default, this behavior is implemented completely within the library.
The default implementation provides read-only access to the standard set of data that is required to be readable from a controller. This includes the current scope(s) (COMPONENT_SCOPE), search domain (SEARCH_DOMAIN), and RDMnet communication diagnostic data (TCP_COMMS_STATUS), as well as some basic RDM data like the manufacturer (MANUFACTURER_LABEL), a description of the product (DEVICE_MODEL_DESCRIPTION), the software version in string form (SOFTWARE_VERSION_LABEL) and a user-settable label for the controller (DEVICE_LABEL). The library has access to all the information necessary for the first three items, since that information is necessary for RDMnet communication. Initial values for the last four items are provided to the library when creating a new controller instance, through the rdmnet_controller_set_rdm_data() function or the rdmnet::Controller::RdmData structure.
If you want to provide richer RDM responder functionality from your controller implementation, you can provide a set of callbacks to handle RDM commands. In this case, the library only handle TCP_COMMS_STATUS, and you must handle the rest of the above PIDs, as well as SUPPORTED_PARAMETERS and any additional ones you choose to support.