#no-alloc #defmt #dmx512 #heap-allocation #no-std

no-std dmx-rdm

Rust库,通过可互换的驱动器在RS485总线上进行DMX512(ANSI E1.11)和DMX-RDM(ANSI E1.20)通信。

5个版本

0.0.12-alpha2024年5月3日
0.0.11-alpha2024年4月15日
0.0.10-alpha2024年4月15日
0.0.9-alpha2024年4月10日
0.0.8-alpha2024年4月9日

#152嵌入式开发

47 每月下载量
用于 dmx-rdm-ftdi

MIT/Apache

120KB
2.5K SLoC

dmx-rdm-rs

dmx-rdm-rs是一个Rust库,通过可互换的驱动器在RS485总线上进行DMX512(ANSI E1.11)和DMX-RDM(ANSI E1.20)通信。该库支持no-std以及no-alloc(无堆分配)功能,旨在支持嵌入式和操作系统平台。

请参阅ESTA发布的官方规范

此库仍在开发中,尚未经过广泛的测试,API可能不是最终的。

用法

以下示例展示了使用dmx-rdm-ftdi驱动器的基本用法。这些示例是相互关联的。

控制器

use dmx_rdm::dmx_controller::{DmxController, DmxControllerConfig};
use dmx_rdm::unique_identifier::{PackageAddress, UniqueIdentifier};
use dmx_rdm::utils::run_full_discovery;
use dmx_rdm_ftdi::{FtdiDriver, FtdiDriverConfig};

fn main() {
    let dmx_driver = FtdiDriver::new(FtdiDriverConfig::default()).unwrap();
    let mut dmx_controller = DmxController::new(dmx_driver, &DmxControllerConfig::default());

    let mut devices_found = vec![];

    // Unmute all dmx responders.
    dmx_controller
        .rdm_disc_un_mute(PackageAddress::Broadcast)
        .unwrap();

    let mut uid_array = [UniqueIdentifier::new(1, 1).unwrap(); 512];
    loop {
        // Search for devices.
        let amount_devices_found = run_full_discovery(&mut dmx_controller, &mut uid_array).unwrap();

        // Add found devices to vector.
        devices_found.extend_from_slice(&uid_array[..amount_devices_found]);

        // Have all devices been found and muted?
        if amount_devices_found != uid_array.len() {
            break;
        }
    }
  
    for device in devices_found { 
      match dmx_controller.rdm_set_identify(device, true) {
        Ok(_) => println!("Activated identify for device_uid {device}"),
        Err(error) => {
          println!("Activating identify for device_uid {device} failed with {error}")
        },
      }
    }
}

响应器

use dmx_rdm::command_class::RequestCommandClass;
use dmx_rdm::dmx_receiver::{
  DmxReceiverContext, DmxResponderHandler, RdmResponder, RdmResponderConfig, RdmResult,
};
use dmx_rdm::rdm_data::RdmRequestData;
use dmx_rdm::types::{DataPack, NackReason};
use dmx_rdm::unique_identifier::UniqueIdentifier;
use dmx_rdm_ftdi::{FtdiDriver, FtdiDriverConfig};

struct RdmHandler {
  identify: bool,
}

const PID_IDENTIFY_DEVICE: u16 = 0x1000;

impl RdmHandler {
  fn handle_get_identify(&self) -> RdmResult {
    RdmResult::Acknowledged(DataPack::from_slice(&[self.identify as u8]).unwrap())
  }

  fn handle_set_identify(&mut self, parameter_data: &[u8]) -> Result<RdmResult, std::fmt::Error> {
    // Check if the parameter data has the correct size
    if parameter_data.len() != 1 {
      return Ok(RdmResult::NotAcknowledged(
        NackReason::DataOutOfRange as u16,
      ));
    }

    // Convert identify flag to bool and set that in the state.
    self.identify = parameter_data[0] != 0;

    println!("Current identify is {}", self.identify);

    // Acknowledge request with an empty response.
    Ok(RdmResult::Acknowledged(DataPack::new()))
  }
}

impl DmxResponderHandler for RdmHandler {
  type Error = std::fmt::Error;

  fn handle_rdm(
    &mut self,
    request: &RdmRequestData,
    _: &mut DmxReceiverContext,
  ) -> Result<RdmResult, Self::Error> {
    match request.parameter_id {
      PID_IDENTIFY_DEVICE => match request.command_class {
        RequestCommandClass::GetCommand => Ok(self.handle_get_identify()),
        RequestCommandClass::SetCommand => {
          self.handle_set_identify(&request.parameter_data)
        },
        _ => Ok(RdmResult::NotAcknowledged(
          NackReason::UnsupportedCommandClass as u16,
        )),
      },
      _ => Ok(RdmResult::NotAcknowledged(NackReason::UnknownPid as u16)),
    }
  }
}

fn main() {
  let dmx_driver = FtdiDriver::new(FtdiDriverConfig::default()).unwrap();

  // Create rdm_responder with space for 32 queued messages.
  let mut dmx_responder = RdmResponder::<_, 32>::new(
    dmx_driver,
    RdmResponderConfig {
      uid: UniqueIdentifier::new(0x7FF0, 1).unwrap(),
      // Won't add PID_IDENTIFY_DEVICE since this is a required pid.
      supported_pids: &[],
      rdm_receiver_metadata: Default::default(),
    },
  );

  let mut rdm_handler = RdmHandler { identify: false };

  loop {
    // poll for new packages using our handler
    match dmx_responder.poll(&mut rdm_handler) {
      Ok(_) => (),
      Err(error) => println!("'{error}' during polling"),
    }
  }
}

缺点/问题

如果您有任何关于如何改进当前状态的想法,请贡献力量。

  • 控制器目前会阻塞,直到收到请求的响应
    • 将来可能会将其分离
  • 目前必须由个人自己实现发现功能
    • 我目前正在考虑在控制器上实现轮询系统
  • 尚未支持子设备/代理设备
  • SUPPORTED_PARAMETERS难以评估。
  • 缺乏单元测试覆盖率
  • 轮询,很多阻塞功能
  • AckTimer难以处理
    • 如果使用Enttec DMX Pro作为从设备,则必须支持它
    • ola通过使用回调解决了这个问题,但这不是一个选项,因为我们不能使用线程
    • 可能会尝试类似的方法,但我们将使用轮询而不是线程
      • 这可能会使库的使用变得更加困难
  • 由于大量使用heapless::Vec,因此存在大量的内存复制
  • 为了支持no-alloc,存在一些奇特的堆类型
  • 端到端测试很困难,而且大部分尚未进行
    • 目前尚无适当的标准验证
  • 有两种类型的设备,一种是需要计算机重复DMX包的设备(rp2040,ftdi),另一种是具有此功能内置的设备(enttec dmx pro)
    • 目前对此没有任何处理,而是根据驱动器发送dmx函数的行为不同

许可证

根据您的选择,许可协议为Apache License,Version 2.0或MIT许可证。

除非您明确声明,否则根据Apache-2.0许可证定义的,您有意提交以包含在dmx-rdm-rs中的任何贡献,应如上所述双重许可,不附加任何额外条款或条件。

依赖关系

~2.5MB
~50K SLoC