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
13pub enum RdmResult {
15 Acknowledged(DataPack),
17 AcknowledgedOverflow(DataPack),
23 NotAcknowledged(u16),
25 AcknowledgedTimer(u16),
28 NoResponse,
30 Custom(RdmResponseData),
32}
33
34pub struct DmxReceiverContext<'a> {
36 pub dmx_start_address: &'a mut DmxStartAddress,
38 pub dmx_footprint: &'a mut u16,
40 pub discovery_muted: &'a mut bool,
42 pub message_count: u8,
44}
45
46pub trait RdmResponderHandlerFunc {
48 type Error;
49
50 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#[allow(clippy::large_enum_variant)]
69#[derive(Debug, Clone)]
70pub enum RdmAnswer {
71 Response(RdmResponseData),
73 DiscoveryResponse(UniqueIdentifier),
75 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 pub uid: UniqueIdentifier,
137 pub supported_pids: &'static [u16],
139 pub rdm_receiver_metadata: RdmReceiverMetadata,
141}
142
143pub struct RdmResponderPackageHandler<const MQ_SIZE: usize> {
151 pub dmx_start_address: DmxStartAddress,
153 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 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 pub fn get_uid(&self) -> UniqueIdentifier {
191 self.uid
192 }
193
194 pub fn get_message_queue(&self) -> &heapless::Deque<RdmResponseData, MQ_SIZE> {
196 &self.message_queue
197 }
198
199 pub fn get_message_queue_mut(&mut self) -> &mut heapless::Deque<RdmResponseData, MQ_SIZE> {
201 &mut self.message_queue
202 }
203
204 pub fn get_message_count(&self) -> u8 {
206 self.message_queue.len() as u8
207 }
208
209 pub fn get_status_vec(&self) -> &heapless::Vec<StatusMessage, MQ_SIZE> {
211 &self.status_vec
212 }
213
214 pub fn get_status_vec_mut(&mut self) -> &mut heapless::Vec<StatusMessage, MQ_SIZE> {
216 &mut self.status_vec
217 }
218
219 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 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 if let Some(response_data) = response {
279 return Ok(RdmAnswer::Response(response_data));
280 }
281
282 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 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}