dmx_rdm/
rdm_responder.rs

1use crate::command_class::RequestCommandClass;
2use crate::consts::{RDM_MAX_PARAMETER_DATA_LENGTH, RDM_MAX_STATUS_PACKAGES_PER_REQUEST};
3use crate::pids;
4use crate::rdm_data::{IsBroadcastError, RdmRequestData, RdmResponseData};
5use crate::rdm_types::{
6    DeviceInfo, DiscoveryMuteResponse, DmxStartAddress, StatusMessage, StatusType,
7};
8use crate::types::{DataPack, NackReason, ResponseType};
9use crate::unique_identifier::{PackageAddress, UniqueIdentifier};
10
11const INTERNALLY_SUPPORTED_PIDS: [u16; 2] = [pids::QUEUED_MESSAGE, pids::STATUS_MESSAGES];
12
13/// The result object of an RDM handler.
14pub enum RdmResult {
15    /// The package was acknowledged. The [DataPack] contains the response data.
16    Acknowledged(DataPack),
17    /// The package was acknowledged, but it does not fit into one [DataPack].
18    /// The [DataPack] contains part of the response.
19    /// If the RDM-controller requests the same pid and the rest of the message still doesn't fit
20    /// doesn't fit into one [DataPack], send the next part as an [RdmResult::AcknowledgedOverflow].
21    /// If the rest finally does fit into one [DataPack] send the rest as an [RdmResult::Acknowledged].
22    AcknowledgedOverflow(DataPack),
23    /// The message was not acknowledged. The [u16] is the [NackReason].
24    NotAcknowledged(u16),
25    /// The message was acknowledged but a result can not be delivered immediately. The [u16]
26    /// contains the amount of time the controller has to wait in 100ms steps.
27    AcknowledgedTimer(u16),
28    /// The receiver does not respond with anything.
29    NoResponse,
30    /// A custom response.
31    Custom(RdmResponseData),
32}
33
34/// A context object for accessing the state of a [RdmResponder] from a [crate::dmx_receiver::DmxResponderHandler].
35pub struct DmxReceiverContext<'a> {
36    /// The start address of the dmx space.
37    pub dmx_start_address: &'a mut DmxStartAddress,
38    /// The amount of dmx address allocated.
39    pub dmx_footprint: &'a mut u16,
40    /// true if the device won't respond to discovery requests.
41    pub discovery_muted: &'a mut bool,
42    /// The amount of messages in the message queue.
43    pub message_count: u8,
44}
45
46/// A handler for dmx and custom rdm packages.
47pub trait RdmResponderHandlerFunc {
48    type Error;
49
50    /// Handle rdm requests that aren't handled by the [RdmResponder] itself.
51    fn handle_rdm(
52        &mut self,
53        _request: &RdmRequestData,
54        _context: &mut DmxReceiverContext,
55    ) -> Result<RdmResult, Self::Error> {
56        Ok(RdmResult::NotAcknowledged(
57            NackReason::UnsupportedCommandClass as u16,
58        ))
59    }
60}
61
62struct UnfinishedRequest {
63    pid: u16,
64    iteration: u16,
65}
66
67/// The RDM answer from the [RdmResponderPackageHandler]
68#[allow(clippy::large_enum_variant)]
69#[derive(Debug, Clone)]
70pub enum RdmAnswer {
71    /// Has to be sent with an uart break
72    Response(RdmResponseData),
73    /// Has to be sent without an uart break
74    DiscoveryResponse(UniqueIdentifier),
75    /// No response to send
76    NoResponse,
77}
78
79macro_rules! build_nack {
80    ($request:path, $nack_reason:path, $message_count:path) => {
81        $request.build_response(
82            ResponseType::ResponseTypeNackReason,
83            $nack_reason.serialize(),
84            $message_count,
85        )
86    };
87}
88
89macro_rules! verify_get_request {
90    ($request:path, $responder:path) => {
91        if $request.destination_uid.is_broadcast() {
92            return None;
93        }
94
95        let message_count = $responder.get_message_count();
96
97        if $request.command_class != RequestCommandClass::GetCommand {
98            return build_nack!($request, NackReason::UnsupportedCommandClass, message_count).ok();
99        }
100
101        if $request.sub_device != 0 {
102            return build_nack!($request, NackReason::SubDeviceOutOfRange, message_count).ok();
103        }
104    };
105}
106
107macro_rules! verify_disc_request {
108    ($request:path, $responder:path) => {
109        if $request.command_class != RequestCommandClass::DiscoveryCommand {
110            let message_count = $responder.get_message_count();
111            return build_nack!($request, NackReason::UnsupportedCommandClass, message_count).ok();
112        }
113    };
114}
115
116pub struct RdmReceiverMetadata {
117    pub device_model_id: u16,
118    pub product_category: u16,
119    pub software_version_id: u32,
120    pub software_version_label: &'static str,
121}
122
123impl Default for RdmReceiverMetadata {
124    fn default() -> Self {
125        Self {
126            device_model_id: 0,
127            product_category: 0,
128            software_version_id: 0,
129            software_version_label: "dmx-rdm-rs device",
130        }
131    }
132}
133
134pub struct RdmResponderConfig {
135    /// The unique id that is used as a source id in the packages.
136    pub uid: UniqueIdentifier,
137    /// An array that contains all the supported pids excluding once that are required by the standard.
138    pub supported_pids: &'static [u16],
139    /// Additional metadata of the RDM-receiver.
140    pub rdm_receiver_metadata: RdmReceiverMetadata,
141}
142
143/// A structure to handle RDM requests and generate the responses.
144///
145/// This struct is used by the [crate::dmx_receiver::RdmResponder], but can be used
146/// without it in order to realize custom dmx setups that can't rely on the
147/// dmx_driver pattern.
148/// MQ_SIZE specifies the size of the message queue and the status vector. MQ_SIZE cannot be greater
149/// than 255.
150pub struct RdmResponderPackageHandler<const MQ_SIZE: usize> {
151    /// The start of the dmx address space.
152    pub dmx_start_address: DmxStartAddress,
153    /// The amount of addresses the dmx device allocates.
154    pub dmx_footprint: u16,
155    supported_pids: &'static [u16],
156    rdm_receiver_metadata: RdmReceiverMetadata,
157    uid: UniqueIdentifier,
158    discovery_muted: bool,
159    unfinished_request: Option<UnfinishedRequest>,
160    message_queue: heapless::Deque<RdmResponseData, MQ_SIZE>,
161    status_vec: heapless::Vec<StatusMessage, MQ_SIZE>,
162    last_queued_message: Option<RdmResponseData>,
163    last_status_vec_message: DataPack,
164}
165
166impl<const MQ_SIZE: usize> RdmResponderPackageHandler<MQ_SIZE> {
167    /// Creates a new [RdmResponderPackageHandler].
168    pub fn new(config: RdmResponderConfig) -> Self {
169        assert!(
170            MQ_SIZE <= u8::MAX as usize,
171            "Message queue size cannot be greater than 255."
172        );
173
174        Self {
175            supported_pids: config.supported_pids,
176            dmx_start_address: DmxStartAddress::NoAddress,
177            dmx_footprint: 1,
178            rdm_receiver_metadata: config.rdm_receiver_metadata,
179            uid: config.uid,
180            discovery_muted: false,
181            unfinished_request: None,
182            message_queue: heapless::Deque::new(),
183            status_vec: heapless::Vec::new(),
184            last_queued_message: None,
185            last_status_vec_message: DataPack::new(),
186        }
187    }
188
189    /// Get the uid of the rdm responder.
190    pub fn get_uid(&self) -> UniqueIdentifier {
191        self.uid
192    }
193
194    /// Get the message queue that contains the results of [RdmResult::AcknowledgedTimer] packages.
195    pub fn get_message_queue(&self) -> &heapless::Deque<RdmResponseData, MQ_SIZE> {
196        &self.message_queue
197    }
198
199    /// Get the message queue to add the results of [RdmResult::AcknowledgedTimer] packages to.
200    pub fn get_message_queue_mut(&mut self) -> &mut heapless::Deque<RdmResponseData, MQ_SIZE> {
201        &mut self.message_queue
202    }
203
204    /// Get the amount of queued messages.
205    pub fn get_message_count(&self) -> u8 {
206        self.message_queue.len() as u8
207    }
208
209    /// Get the status queue that contains the current status messages.
210    pub fn get_status_vec(&self) -> &heapless::Vec<StatusMessage, MQ_SIZE> {
211        &self.status_vec
212    }
213
214    /// Get the status queue to add or remove status messages.
215    pub fn get_status_vec_mut(&mut self) -> &mut heapless::Vec<StatusMessage, MQ_SIZE> {
216        &mut self.status_vec
217    }
218
219    /// Gets a context object that contains references to the current internal state
220    /// of some of the parameters.
221    pub fn get_context(&mut self) -> DmxReceiverContext {
222        let message_count = self.get_message_count();
223
224        DmxReceiverContext {
225            dmx_start_address: &mut self.dmx_start_address,
226            dmx_footprint: &mut self.dmx_footprint,
227            discovery_muted: &mut self.discovery_muted,
228            message_count,
229        }
230    }
231
232    /// Method to handle a received and deserialized RdmPackage from the RDM-Controller.
233    /// This method will return the response package that has to be sent back to the RDM-Controller.
234    pub fn handle_rdm_request<HandlerError>(
235        &mut self,
236        request: RdmRequestData,
237        handler: &mut dyn RdmResponderHandlerFunc<Error = HandlerError>,
238    ) -> Result<RdmAnswer, HandlerError> {
239        match request.destination_uid {
240            PackageAddress::ManufacturerBroadcast(manufacturer_uid) => {
241                if manufacturer_uid != self.uid.manufacturer_uid() {
242                    return Ok(RdmAnswer::NoResponse);
243                }
244            },
245            PackageAddress::Device(device_uid) => {
246                if self.uid != device_uid {
247                    return Ok(RdmAnswer::NoResponse);
248                }
249            },
250            _ => {},
251        }
252
253        if request.command_class == RequestCommandClass::DiscoveryCommand
254            && ![
255                pids::DISC_UNIQUE_BRANCH,
256                pids::DISC_MUTE,
257                pids::DISC_UN_MUTE,
258            ]
259            .contains(&request.parameter_id)
260        {
261            return Ok(RdmAnswer::NoResponse);
262        }
263
264        let response = match request.parameter_id {
265            pids::DISC_UNIQUE_BRANCH => return Ok(self.handle_disc_unique_branch(&request)),
266            pids::DISC_MUTE => self.handle_disc_mute(&request),
267            pids::DISC_UN_MUTE => self.handle_disc_unmute(&request),
268            pids::SUPPORTED_PARAMETERS => self.handle_supported_parameters(&request),
269            pids::DEVICE_INFO => self.handle_device_info(&request),
270            pids::SOFTWARE_VERSION_LABEL => self.handle_get_software_version_label(&request),
271            pids::DMX_START_ADDRESS => self.handle_dmx_start_address(&request),
272            pids::QUEUED_MESSAGE => self.handle_queued_message(&request),
273            pids::STATUS_MESSAGES => self.handle_status_messages(&request),
274            _ => self.handle_other_request(&request, handler)?,
275        };
276
277        // Was this a broadcast?
278        if let Some(response_data) = response {
279            return Ok(RdmAnswer::Response(response_data));
280        }
281
282        // No response since the request is a broadcast
283        Ok(RdmAnswer::NoResponse)
284    }
285
286    fn handle_other_request<HandlerError>(
287        &mut self,
288        request: &RdmRequestData,
289        handler: &mut dyn RdmResponderHandlerFunc<Error = HandlerError>,
290    ) -> Result<Option<RdmResponseData>, HandlerError> {
291        let response = match handler.handle_rdm(request, &mut self.get_context())? {
292            RdmResult::Acknowledged(response_data) => request.build_response(
293                ResponseType::ResponseTypeAck,
294                response_data,
295                self.get_message_count(),
296            ),
297            RdmResult::AcknowledgedOverflow(response_data) => request.build_response(
298                ResponseType::ResponseTypeAckOverflow,
299                response_data,
300                self.get_message_count(),
301            ),
302            RdmResult::NotAcknowledged(nack_reason) => request.build_response(
303                ResponseType::ResponseTypeNackReason,
304                DataPack::from_slice(&nack_reason.to_be_bytes()).unwrap(),
305                self.get_message_count(),
306            ),
307            RdmResult::AcknowledgedTimer(timer) => request.build_response(
308                ResponseType::ResponseTypeAckTimer,
309                DataPack::from_slice(&timer.to_be_bytes()).unwrap(),
310                self.get_message_count(),
311            ),
312            RdmResult::NoResponse => {
313                return Ok(None);
314            },
315            RdmResult::Custom(response_data) => Ok(response_data),
316        };
317
318        Ok(response.ok())
319    }
320
321    fn handle_disc_unique_branch(&self, request: &RdmRequestData) -> RdmAnswer {
322        if request.command_class != RequestCommandClass::DiscoveryCommand {
323            let message_count = self.get_message_count();
324            return match build_nack!(request, NackReason::UnsupportedCommandClass, message_count) {
325                Ok(response) => RdmAnswer::Response(response),
326                Err(_) => RdmAnswer::NoResponse,
327            };
328        }
329
330        if request.parameter_data.len() != 12 {
331            return RdmAnswer::NoResponse;
332        }
333
334        let lower_bound: u64 =
335            PackageAddress::from_bytes(&request.parameter_data[..6].try_into().unwrap()).into();
336        let upper_bound: u64 =
337            PackageAddress::from_bytes(&request.parameter_data[6..].try_into().unwrap()).into();
338        let own_uid: u64 = self.uid.into();
339
340        if !self.discovery_muted && own_uid >= lower_bound && own_uid <= upper_bound {
341            return RdmAnswer::DiscoveryResponse(self.uid);
342        }
343
344        RdmAnswer::NoResponse
345    }
346
347    fn handle_disc_mute(&mut self, request: &RdmRequestData) -> Option<RdmResponseData> {
348        verify_disc_request!(request, self);
349
350        if !request.parameter_data.is_empty() {
351            return None;
352        }
353
354        self.discovery_muted = true;
355        self.build_disc_mute_response(request).ok()
356    }
357
358    fn handle_disc_unmute(&mut self, request: &RdmRequestData) -> Option<RdmResponseData> {
359        verify_disc_request!(request, self);
360
361        if !request.parameter_data.is_empty() {
362            return None;
363        }
364
365        self.discovery_muted = false;
366        self.build_disc_mute_response(request).ok()
367    }
368
369    fn handle_get_software_version_label(
370        &self,
371        request: &RdmRequestData,
372    ) -> Option<RdmResponseData> {
373        verify_get_request!(request, self);
374
375        let software_version_label = self.rdm_receiver_metadata.software_version_label;
376
377        request
378            .build_response(
379                ResponseType::ResponseTypeAck,
380                DataPack::from_slice(
381                    &software_version_label.as_bytes()[..software_version_label.len().min(32)],
382                )
383                .unwrap(),
384                self.get_message_count(),
385            )
386            .ok()
387    }
388
389    fn handle_supported_parameters(&mut self, request: &RdmRequestData) -> Option<RdmResponseData> {
390        verify_get_request!(request, self);
391
392        let current_iteration = match &self.unfinished_request {
393            Some(UnfinishedRequest {
394                pid: pids::SUPPORTED_PARAMETERS,
395                iteration,
396            }) => *iteration,
397            _ => 0,
398        };
399
400        // one pid is u16
401        const MAX_PIDS_PER_RESPONSE: usize = RDM_MAX_PARAMETER_DATA_LENGTH / 2;
402        let current_parameter_index = MAX_PIDS_PER_RESPONSE * (current_iteration as usize);
403
404        let amount_pids = self.supported_pids.len() + INTERNALLY_SUPPORTED_PIDS.len();
405        let end_parameter_index = amount_pids.min(current_parameter_index + MAX_PIDS_PER_RESPONSE);
406
407        let mut response_package = DataPack::new();
408
409        for supported_pid in INTERNALLY_SUPPORTED_PIDS
410            .iter()
411            .chain(self.supported_pids.iter())
412        {
413            response_package
414                .extend_from_slice(&supported_pid.to_be_bytes())
415                .unwrap();
416        }
417
418        if end_parameter_index != amount_pids {
419            self.unfinished_request = Some(UnfinishedRequest {
420                pid: pids::SUPPORTED_PARAMETERS,
421                iteration: current_iteration + 1,
422            });
423
424            request
425                .build_response(
426                    ResponseType::ResponseTypeAckOverflow,
427                    response_package,
428                    self.get_message_count(),
429                )
430                .ok()
431        } else {
432            self.unfinished_request = None;
433
434            request
435                .build_response(
436                    ResponseType::ResponseTypeAck,
437                    response_package,
438                    self.get_message_count(),
439                )
440                .ok()
441        }
442    }
443
444    fn handle_dmx_start_address(&mut self, request: &RdmRequestData) -> Option<RdmResponseData> {
445        let message_count = self.get_message_count();
446
447        match request.command_class {
448            RequestCommandClass::GetCommand => request.build_response(
449                ResponseType::ResponseTypeAck,
450                self.dmx_start_address.serialize(),
451                self.message_queue.len() as u8,
452            ),
453            RequestCommandClass::SetCommand => 'set_command: {
454                if request.parameter_data.len() != 2 {
455                    break 'set_command build_nack!(
456                        request,
457                        NackReason::FormatError,
458                        message_count
459                    );
460                }
461
462                let dmx_start_address = match DmxStartAddress::deserialize(&request.parameter_data)
463                {
464                    Ok(start_address) => start_address,
465                    Err(_) => {
466                        break 'set_command build_nack!(
467                            request,
468                            NackReason::DataOutOfRange,
469                            message_count
470                        );
471                    },
472                };
473
474                self.dmx_start_address = dmx_start_address;
475
476                request.build_response(
477                    ResponseType::ResponseTypeAck,
478                    DataPack::new(),
479                    self.message_queue.len() as u8,
480                )
481            },
482            RequestCommandClass::DiscoveryCommand => {
483                build_nack!(request, NackReason::UnsupportedCommandClass, message_count)
484            },
485        }
486        .ok()
487    }
488
489    fn handle_device_info(&self, request: &RdmRequestData) -> Option<RdmResponseData> {
490        verify_get_request!(request, self);
491
492        request
493            .build_response(
494                ResponseType::ResponseTypeAck,
495                DeviceInfo {
496                    device_model_id: self.rdm_receiver_metadata.device_model_id,
497                    product_category: self.rdm_receiver_metadata.product_category,
498                    software_version: self.rdm_receiver_metadata.software_version_id,
499                    dmx_footprint: self.dmx_footprint,
500                    dmx_personality: 1,
501                    dmx_start_address: self.dmx_start_address.clone(),
502                    sub_device_count: 0,
503                    sensor_count: 0,
504                }
505                .serialize(),
506                self.get_message_count(),
507            )
508            .ok()
509    }
510
511    fn build_disc_mute_response(
512        &self,
513        request: &RdmRequestData,
514    ) -> Result<RdmResponseData, IsBroadcastError> {
515        request.build_response(
516            ResponseType::ResponseTypeAck,
517            DiscoveryMuteResponse {
518                managed_proxy: false,
519                sub_device: false,
520                boot_loader: false,
521                proxy_device: false,
522                binding_uid: None,
523            }
524            .serialize(),
525            self.get_message_count(),
526        )
527    }
528
529    fn handle_queued_message(&mut self, request: &RdmRequestData) -> Option<RdmResponseData> {
530        verify_get_request!(request, self);
531
532        let message_count = self.get_message_count();
533
534        let status_type_requested = match StatusType::deserialize(&request.parameter_data) {
535            Ok(status) => status,
536            Err(_) => return build_nack!(request, NackReason::DataOutOfRange, message_count).ok(),
537        };
538
539        if status_type_requested == StatusType::StatusNone {
540            return build_nack!(request, NackReason::DataOutOfRange, message_count).ok();
541        }
542
543        if status_type_requested == StatusType::StatusGetLastMessage {
544            return match self.last_queued_message {
545                None => request
546                    .build_response(
547                        ResponseType::ResponseTypeAck,
548                        DataPack::new(),
549                        message_count,
550                    )
551                    .ok(),
552                Some(ref mut response) => {
553                    response.message_count = message_count;
554                    response.transaction_number = request.transaction_number;
555                    Some(response.clone())
556                },
557            };
558        }
559
560        match status_type_requested {
561            StatusType::StatusWarning | StatusType::StatusError | StatusType::StatusAdvisory => {},
562            _ => return build_nack!(request, NackReason::DataOutOfRange, message_count).ok(),
563        }
564
565        let response = match self.message_queue.pop_back() {
566            None => {
567                let response_data = self.pop_filtered_statuses(status_type_requested);
568
569                let status_message_response = RdmResponseData {
570                    destination_uid: PackageAddress::Device(request.source_uid),
571                    source_uid: self.uid,
572                    transaction_number: request.transaction_number,
573                    response_type: ResponseType::ResponseTypeAck,
574                    message_count: 0,
575                    sub_device: 0,
576                    command_class: request.command_class.get_response_class(),
577                    parameter_id: pids::STATUS_MESSAGES,
578                    parameter_data: response_data,
579                };
580                self.last_status_vec_message = status_message_response.parameter_data.clone();
581
582                status_message_response
583            },
584            Some(mut response_data) => {
585                response_data.message_count = self.get_message_count();
586                response_data.transaction_number = request.transaction_number;
587                response_data
588            },
589        };
590
591        self.last_queued_message = Some(response.clone());
592        Some(response)
593    }
594
595    fn handle_status_messages(&mut self, request: &RdmRequestData) -> Option<RdmResponseData> {
596        verify_get_request!(request, self);
597
598        let message_count = self.get_message_count();
599
600        let status_type_requested = match StatusType::deserialize(&request.parameter_data) {
601            Ok(status) => status,
602            Err(_) => return build_nack!(request, NackReason::FormatError, message_count).ok(),
603        };
604
605        match status_type_requested {
606            StatusType::StatusNone => request.build_response(
607                ResponseType::ResponseTypeAck,
608                DataPack::new(),
609                message_count,
610            ),
611            StatusType::StatusGetLastMessage => request.build_response(
612                ResponseType::ResponseTypeAck,
613                self.last_status_vec_message.clone(),
614                message_count,
615            ),
616            StatusType::StatusWarning | StatusType::StatusError | StatusType::StatusAdvisory => {
617                let response_vec = self.pop_filtered_statuses(status_type_requested);
618
619                self.last_status_vec_message = response_vec.clone();
620                request.build_response(ResponseType::ResponseTypeAck, response_vec, message_count)
621            },
622            _ => build_nack!(request, NackReason::DataOutOfRange, message_count),
623        }
624        .ok()
625    }
626
627    fn pop_filtered_statuses(&mut self, status_filter: StatusType) -> DataPack {
628        let mut indexes_to_remove =
629            heapless::Vec::<usize, RDM_MAX_STATUS_PACKAGES_PER_REQUEST>::new();
630        let mut parameter_data = DataPack::new();
631
632        self.status_vec
633            .iter()
634            .take(RDM_MAX_STATUS_PACKAGES_PER_REQUEST)
635            .filter(|item| ((item.status_type as u8) & 0x0F) >= status_filter as u8)
636            .map(|item| item.serialize())
637            .enumerate()
638            .for_each(|(index, data_pack)| {
639                parameter_data.extend_from_slice(&data_pack).unwrap();
640                indexes_to_remove.push(index).unwrap();
641            });
642
643        for index_to_remove in indexes_to_remove {
644            self.status_vec.remove(index_to_remove);
645        }
646
647        parameter_data
648    }
649}