dmx_rdm/
dmx_controller.rs

1use crate::command_class::RequestCommandClass;
2use crate::dmx_driver::{
3    ControllerDriverErrorDef, CustomStartCodeControllerDriver, DiscoveryOption,
4    DmxControllerDriver, DmxError, RdmControllerDriver,
5};
6use crate::rdm_data::{RdmData, RdmRequestData};
7use crate::rdm_packages::{
8    deserialize_identify, deserialize_status_messages, deserialize_supported_parameters,
9    RdmResponseInfo, RdmResponsePackage,
10};
11use crate::rdm_types::{
12    DeviceInfo, DiscoveryMuteResponse, DmxStartAddress, OverflowMessageResp, StatusMessages,
13    StatusType, SupportedParameters,
14};
15use crate::types::{DataPack, NackReason, ResponseType};
16use crate::unique_identifier::{PackageAddress, UniqueIdentifier};
17use crate::{pids, rdm_packages, rdm_types};
18
19#[derive(Debug)]
20pub struct DmxControllerConfig {
21    pub rdm_uid: UniqueIdentifier,
22}
23
24impl Default for DmxControllerConfig {
25    fn default() -> Self {
26        Self {
27            rdm_uid: UniqueIdentifier::new(0x7FF0, 0).unwrap(), // prototyping id
28        }
29    }
30}
31
32#[derive(Debug)]
33pub struct RdmRequest {
34    /// The unique id of the recipient of the request.
35    pub destination_uid: PackageAddress,
36    /// The id that specifies the type of the package.
37    pub parameter_id: u16,
38    /// The parameter data.
39    pub data: DataPack,
40}
41
42impl RdmRequest {
43    /// Creates an RdmRequest with empty parameter data.
44    pub fn empty(uid: PackageAddress, pid: u16) -> Self {
45        Self {
46            destination_uid: uid,
47            parameter_id: pid,
48            data: heapless::Vec::new(),
49        }
50    }
51}
52
53#[derive(Debug)]
54pub enum RdmResponse {
55    /// The message data of the response.
56    Response(RdmResponseInfo),
57    /// The request has been excepted but the message data is too big to fit into one response.
58    /// Use the get command on the same pid to receive the rest of it until you just receive a Response.
59    IncompleteResponse(RdmResponseInfo),
60    /// No response was received since the request was a broadcast.
61    RequestWasBroadcast,
62}
63
64/// An RDM controller
65pub struct DmxController<C: ControllerDriverErrorDef> {
66    driver: C,
67    uid: UniqueIdentifier,
68    current_transaction_id: u8,
69    last_message_count: u8,
70}
71
72#[derive(Debug)]
73#[cfg_attr(feature = "defmt", derive(defmt::Format))]
74pub enum RdmResponseError<E> {
75    /// The received package doesn't match the request.
76    NotMatching,
77    /// The parameter data couldn't be deserialized.
78    ParameterDataNotDeserializable,
79    /// The response has an error status but the contents aren't deserializable.
80    ErrorNotDeserializable,
81    /// The response isn't ready yet. The value is the estimated time in 100ms steps.
82    NotReady(u16),
83    /// The responder didn't acknowledge the request.
84    NotAcknowledged(NackReason),
85    /// The underlying dmx controller raised an error.
86    DmxError(DmxError<E>),
87}
88
89impl<E: core::fmt::Debug> core::fmt::Display for RdmResponseError<E> {
90    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
91        core::fmt::Debug::fmt(self, f)
92    }
93}
94
95impl<E> From<DmxError<E>> for RdmResponseError<E> {
96    fn from(value: DmxError<E>) -> Self {
97        Self::DmxError(value)
98    }
99}
100
101#[cfg(feature = "std")]
102impl<E: core::fmt::Debug + core::fmt::Display> std::error::Error for RdmResponseError<E> {}
103
104impl<E> From<rdm_types::DeserializationError> for RdmResponseError<E> {
105    fn from(_: rdm_types::DeserializationError) -> Self {
106        Self::ParameterDataNotDeserializable
107    }
108}
109
110impl<D: ControllerDriverErrorDef> DmxController<D> {
111    /// Creates a new DmxManager instance.
112    pub fn new(driver: D, config: &DmxControllerConfig) -> Self {
113        Self {
114            driver,
115            uid: config.rdm_uid,
116            current_transaction_id: 0,
117            last_message_count: 0,
118        }
119    }
120
121    /// Get a reference to the underlying driver.
122    pub fn get_driver(&mut self) -> &mut D {
123        &mut self.driver
124    }
125}
126
127impl<D: CustomStartCodeControllerDriver> DmxController<D> {
128    /// Sends a package with a custom start code.
129    pub fn send_custom_package(
130        &mut self,
131        start_code: u8,
132        package: &[u8],
133    ) -> Result<(), RdmResponseError<D::DriverError>> {
134        self.driver
135            .send_custom_package(start_code, package)
136            .map_err(RdmResponseError::DmxError)
137    }
138}
139
140impl<D: DmxControllerDriver> DmxController<D> {
141    /// Sends a dmx package. Package can't be bigger than 512 bytes.
142    pub fn send_dmx_package(
143        &mut self,
144        package: &[u8],
145    ) -> Result<(), RdmResponseError<D::DriverError>> {
146        self.driver
147            .send_dmx_package(package)
148            .map_err(RdmResponseError::DmxError)
149    }
150}
151
152impl<D: RdmControllerDriver> DmxController<D> {
153    fn rdm_request(
154        &mut self,
155        command_class: RequestCommandClass,
156        request: RdmRequest,
157    ) -> Result<RdmResponse, RdmResponseError<D::DriverError>> {
158        self.current_transaction_id = self.current_transaction_id.wrapping_add(1);
159
160        self.driver.send_rdm(RdmData::Request(RdmRequestData {
161            destination_uid: request.destination_uid,
162            source_uid: self.uid,
163            transaction_number: self.current_transaction_id,
164            port_id: 0,
165            message_count: 0,
166            sub_device: 0,
167            command_class,
168            parameter_id: request.parameter_id,
169            parameter_data: request.data,
170        }))?;
171
172        if request.destination_uid.is_broadcast() {
173            return Ok(RdmResponse::RequestWasBroadcast);
174        }
175
176        let response = loop {
177            let response = match self.driver.receive_rdm()? {
178                RdmData::Request(_) => {
179                    return Err(RdmResponseError::NotMatching);
180                },
181                RdmData::Response(response) => response,
182            };
183
184            if self.current_transaction_id == response.transaction_number {
185                break response;
186            }
187        };
188
189        if response.destination_uid != PackageAddress::Device(self.uid) {
190            return Err(RdmResponseError::NotMatching);
191        }
192
193        let response_info = RdmResponseInfo {
194            parameter_id: response.parameter_id,
195            message_count: response.message_count,
196            data: response.parameter_data,
197        };
198
199        self.last_message_count = response.message_count;
200
201        match response.response_type {
202            ResponseType::ResponseTypeAck => Ok(RdmResponse::Response(response_info)),
203            ResponseType::ResponseTypeAckTimer => {
204                if response_info.data.len() != 2 {
205                    return Err(RdmResponseError::ErrorNotDeserializable);
206                }
207
208                Err(RdmResponseError::NotReady(u16::from_be_bytes(
209                    response_info.data[..2].try_into().unwrap(),
210                )))
211            },
212            ResponseType::ResponseTypeNackReason => {
213                if response_info.data.len() != 2 {
214                    return Err(RdmResponseError::ErrorNotDeserializable);
215                }
216
217                let nack_reason = u16::from_be_bytes(response_info.data[..2].try_into().unwrap())
218                    .try_into()
219                    .or(Err(RdmResponseError::ErrorNotDeserializable))?;
220
221                Err(RdmResponseError::NotAcknowledged(nack_reason))
222            },
223            ResponseType::ResponseTypeAckOverflow => {
224                Ok(RdmResponse::IncompleteResponse(response_info))
225            },
226        }
227    }
228
229    /// Sends a get request.
230    pub fn rdm_get(
231        &mut self,
232        request: RdmRequest,
233    ) -> Result<RdmResponse, RdmResponseError<D::DriverError>> {
234        self.rdm_request(RequestCommandClass::GetCommand, request)
235    }
236
237    /// Sends a set request.
238    pub fn rdm_set(
239        &mut self,
240        request: RdmRequest,
241    ) -> Result<RdmResponse, RdmResponseError<D::DriverError>> {
242        self.rdm_request(RequestCommandClass::SetCommand, request)
243    }
244
245    /// Sends a discovery request to a range of device ids and returns the found uid
246    /// if there is no collision and the device does not have its discovery muted.
247    pub fn rdm_discover(
248        &mut self,
249        first_uid: u64,
250        last_uid: u64,
251    ) -> Result<DiscoveryOption, RdmResponseError<D::DriverError>> {
252        let mut parameter_data = heapless::Vec::new();
253
254        parameter_data
255            .extend_from_slice(&first_uid.to_be_bytes()[2..8])
256            .unwrap();
257        parameter_data
258            .extend_from_slice(&last_uid.to_be_bytes()[2..8])
259            .unwrap();
260
261        self.driver.send_rdm(RdmData::Request(RdmRequestData {
262            destination_uid: PackageAddress::Broadcast,
263            source_uid: self.uid,
264            transaction_number: self.current_transaction_id,
265            port_id: 0,
266            message_count: 0,
267            sub_device: 0,
268            command_class: RequestCommandClass::DiscoveryCommand,
269            parameter_id: pids::DISC_UNIQUE_BRANCH,
270            parameter_data,
271        }))?;
272
273        Ok(self.driver.receive_rdm_discovery_response()?)
274    }
275
276    /// Mute device from discovery. It will not respond to discovery requests anymore.
277    /// Returns None if the request was a broadcast.
278    pub fn rdm_disc_mute(
279        &mut self,
280        uid: PackageAddress,
281    ) -> Result<Option<DiscoveryMuteResponse>, RdmResponseError<D::DriverError>> {
282        let response = self.rdm_request(
283            RequestCommandClass::DiscoveryCommand,
284            RdmRequest::empty(uid, pids::DISC_MUTE),
285        )?;
286
287        deserialize_discovery_mute_response::<D>(&response)
288    }
289
290    /// Unmute device from discovery. It will respond to discovery requests again.
291    /// Returns None if the request was a broadcast.
292    pub fn rdm_disc_un_mute(
293        &mut self,
294        uid: PackageAddress,
295    ) -> Result<Option<DiscoveryMuteResponse>, RdmResponseError<D::DriverError>> {
296        let response = self.rdm_request(
297            RequestCommandClass::DiscoveryCommand,
298            RdmRequest::empty(uid, pids::DISC_UN_MUTE),
299        )?;
300
301        deserialize_discovery_mute_response::<D>(&response)
302    }
303
304    /// Get the identify state in the rdm device (led for searching)
305    pub fn rdm_get_identify(
306        &mut self,
307        uid: UniqueIdentifier,
308    ) -> Result<bool, RdmResponseError<D::DriverError>> {
309        let response = self.rdm_get(RdmRequest::empty(
310            PackageAddress::Device(uid),
311            pids::IDENTIFY_DEVICE,
312        ))?;
313
314        match response {
315            RdmResponse::Response(response_info) => Ok(deserialize_identify(&response_info.data)?),
316            _ => Err(RdmResponseError::ParameterDataNotDeserializable),
317        }
318    }
319
320    /// Set the identify state in the rdm device (led for searching)
321    pub fn rdm_set_identify(
322        &mut self,
323        uid: PackageAddress,
324        enabled: bool,
325    ) -> Result<(), RdmResponseError<D::DriverError>> {
326        self.rdm_set(RdmRequest {
327            destination_uid: uid,
328            parameter_id: pids::IDENTIFY_DEVICE,
329            data: heapless::Vec::from_slice(&[enabled as u8]).unwrap(),
330        })?;
331
332        Ok(())
333    }
334
335    /// Get the software version label.
336    pub fn rdm_get_software_version_label(
337        &mut self,
338        uid: UniqueIdentifier,
339    ) -> Result<heapless::String<32>, RdmResponseError<D::DriverError>> {
340        let response_info = match self.rdm_get(RdmRequest::empty(
341            PackageAddress::Device(uid),
342            pids::SOFTWARE_VERSION_LABEL,
343        ))? {
344            RdmResponse::Response(response_info) => response_info,
345            _ => return Err(RdmResponseError::ParameterDataNotDeserializable),
346        };
347
348        Ok(rdm_packages::deserialize_software_version_label(
349            &response_info.data,
350        )?)
351    }
352
353    /// Get the current start address of the dmx slave.
354    pub fn rdm_get_dmx_start_address(
355        &mut self,
356        uid: UniqueIdentifier,
357    ) -> Result<DmxStartAddress, RdmResponseError<D::DriverError>> {
358        let response = match self.rdm_get(RdmRequest::empty(
359            PackageAddress::Device(uid),
360            pids::DMX_START_ADDRESS,
361        ))? {
362            RdmResponse::Response(response) => response,
363            _ => return Err(RdmResponseError::ParameterDataNotDeserializable),
364        };
365
366        Ok(DmxStartAddress::deserialize(&response.data)?)
367    }
368
369    /// Set the current start address of the dmx slave. The address has to be between 1 and 512.
370    pub fn rdm_set_dmx_start_address(
371        &mut self,
372        uid: PackageAddress,
373        start_address: u16,
374    ) -> Result<(), RdmResponseError<D::DriverError>> {
375        assert!(
376            (1..=512).contains(&start_address),
377            "The requested start address is not valid."
378        );
379
380        self.rdm_set(RdmRequest {
381            destination_uid: uid,
382            parameter_id: pids::DMX_START_ADDRESS,
383            data: DataPack::from_slice(&start_address.to_be_bytes()).unwrap(),
384        })?;
385
386        Ok(())
387    }
388
389    /// Get the last queued message.
390    ///
391    /// Use [DmxController::rdm_get_last_message_count]
392    /// to receive the message count from the last request.
393    ///
394    /// If no messages are queued this will return the current [StatusMessages].
395    /// You can use the [StatusType] to filter these [StatusMessages].
396    ///
397    /// Note that you can only use [StatusType::StatusAdvisory], [StatusType::StatusWarning],
398    /// [StatusType::StatusError] and [StatusType::StatusGetLastMessage].
399    ///
400    /// If you want to receive the previous response use [StatusType::StatusGetLastMessage].
401    pub fn rdm_get_queued_message(
402        &mut self,
403        uid: UniqueIdentifier,
404        status_requested: StatusType,
405    ) -> Result<RdmResponsePackage, RdmResponseError<D::DriverError>> {
406        let response = self.rdm_get(RdmRequest {
407            destination_uid: PackageAddress::Device(uid),
408            parameter_id: pids::QUEUED_MESSAGE,
409            data: DataPack::from_slice(&[status_requested as u8]).unwrap(),
410        })?;
411
412        match response {
413            RdmResponse::Response(response_info) => {
414                Ok(RdmResponsePackage::from_response_info(response_info)?)
415            },
416            _ => Err(RdmResponseError::ParameterDataNotDeserializable),
417        }
418    }
419
420    /// Get status messages. Filter severity by using the [StatusType::StatusAdvisory], [StatusType::StatusWarning]
421    /// and [StatusType::StatusError].
422    ///
423    /// If you want to receive the previously set of status messages again use [StatusType::StatusGetLastMessage].
424    /// To perform an availability test use [StatusType::StatusNone].
425    ///
426    /// If this parameter message is properly implemented on the slave you
427    /// should never get a [OverflowMessageResp::Incomplete] back, since STATUS_MESSAGE uses
428    /// its own queuing.
429    pub fn rdm_get_status_messages(
430        &mut self,
431        uid: UniqueIdentifier,
432        status_requested: StatusType,
433    ) -> Result<OverflowMessageResp<StatusMessages>, RdmResponseError<D::DriverError>> {
434        let response = self.rdm_get(RdmRequest {
435            destination_uid: PackageAddress::Device(uid),
436            parameter_id: pids::STATUS_MESSAGES,
437            data: DataPack::from_slice(&[status_requested as u8]).unwrap(),
438        })?;
439
440        match response {
441            RdmResponse::Response(response_info) => Ok(OverflowMessageResp::Complete(
442                deserialize_status_messages(&response_info.data)?,
443            )),
444            RdmResponse::IncompleteResponse(response_info) => Ok(OverflowMessageResp::Incomplete(
445                deserialize_status_messages(&response_info.data)?,
446            )),
447            _ => Err(RdmResponseError::ParameterDataNotDeserializable),
448        }
449    }
450
451    /// Get the parameter ids that are supported by the responder.
452    ///
453    /// <div class="warning">Note that this only includes optional parameter ids that are not
454    /// required to be compliant with ANSI E1.20.</div>
455    pub fn rdm_get_supported_parameters(
456        &mut self,
457        uid: UniqueIdentifier,
458    ) -> Result<OverflowMessageResp<SupportedParameters>, RdmResponseError<D::DriverError>> {
459        let response = self.rdm_get(RdmRequest::empty(
460            PackageAddress::Device(uid),
461            pids::SUPPORTED_PARAMETERS,
462        ))?;
463
464        match response {
465            RdmResponse::Response(response_info) => Ok(OverflowMessageResp::Complete(
466                deserialize_supported_parameters(&response_info.data)?,
467            )),
468            RdmResponse::IncompleteResponse(response_info) => Ok(OverflowMessageResp::Incomplete(
469                deserialize_supported_parameters(&response_info.data)?,
470            )),
471            _ => Err(RdmResponseError::ParameterDataNotDeserializable),
472        }
473    }
474
475    /// Get the device info from the rdm device.
476    pub fn rdm_get_device_info(
477        &mut self,
478        uid: UniqueIdentifier,
479    ) -> Result<DeviceInfo, RdmResponseError<D::DriverError>> {
480        let response = self.rdm_get(RdmRequest::empty(
481            PackageAddress::Device(uid),
482            pids::DEVICE_INFO,
483        ))?;
484        match response {
485            RdmResponse::Response(response_info) => {
486                Ok(DeviceInfo::deserialize(&response_info.data)?)
487            },
488            _ => Err(RdmResponseError::ParameterDataNotDeserializable),
489        }
490    }
491
492    /// Returns the message count that was received on the last request using this instance.
493    pub fn rdm_get_last_message_count(&self) -> u8 {
494        self.last_message_count
495    }
496}
497
498fn deserialize_discovery_mute_response<D: RdmControllerDriver>(
499    response: &RdmResponse,
500) -> Result<Option<DiscoveryMuteResponse>, RdmResponseError<D::DriverError>> {
501    Ok(match response {
502        RdmResponse::Response(response_info) => Some(
503            DiscoveryMuteResponse::deserialize(&response_info.data)
504                .map_err(|_| RdmResponseError::ParameterDataNotDeserializable)?,
505        ),
506        RdmResponse::RequestWasBroadcast => None,
507        RdmResponse::IncompleteResponse(_) => {
508            return Err(RdmResponseError::ParameterDataNotDeserializable)
509        },
510    })
511}