Protocol Library (freemdu)

The FreeMDU protocol crate (freemdu) is the core engine of the project. It implements the proprietary Miele diagnostic serial protocol, providing an asynchronous, platform-agnostic, and safe Rust API for communicating with supported appliances.

By handling the complexities of memory unlocking, device identification, and raw byte serialization, the library allows developers to focus entirely on application logic.

Environment Compatibility (no_std)

The crate is designed to be highly portable. It can be used in no_std environments (like embedded microcontrollers) as well as traditional std desktop environments. However, because it manages dynamic lists of properties and actions, an allocator is required (it makes use of alloc::boxed::Box and alloc::vec::Vec).

Feature Flags

When adding freemdu as a dependency to your Cargo.toml, you can specify optional features depending on your target platform:

  • native-serial: Enables a fully asynchronous desktop serial port implementation powered by the serial2-tokio crate. This feature requires a std environment (Linux, macOS, Windows).
  • defmt: Enables deferred formatting for logging in embedded no_std environments. Useful when deploying to microcontrollers.

Serial Port Initialization

The diagnostic protocol operates over a highly specific UART configuration. To communicate with an appliance, your serial port must be strictly configured to the following parameters:

  • Baud rate: 2400 bps (can be negotiated higher later, but must start at 2400)
  • Parity: Even
  • Data bits: 8
  • Stop bits: 1

If you use the native-serial feature on a desktop OS, the freemdu::serial::open helper automatically applies these strict UART parameters for you:

let mut port = freemdu::serial::open("/dev/ttyACM0")?;

High-Level API: The Device Abstraction

For 99% of use cases, you should use the device module. The freemdu::device::connect function handles the entire initial handshake: it queries the appliance's Software ID, looks up the corresponding unlocking keys, negotiates access, and returns a concrete device implementation mapping.

use freemdu::device::Device;

#[tokio::main]
async fn main() -> freemdu::device::Result<(), freemdu::serial::PortError> {
    // Open port and automatically establish protocol handshake
    let mut port = freemdu::serial::open("/dev/ttyACM0")?;
    let mut dev = freemdu::device::connect(&mut port).await?;

    println!("Connected to a {} with Software ID {}", dev.kind(), dev.software_id());

    // Query and print all supported real-time properties
    for prop in dev.properties() {
        let val = dev.query_property(prop).await?;
        println!("Property '{}': {:?}", prop.name, val);
    }

    Ok(())
}

Low-Level API: The Interface Structure

For advanced diagnostics, security research, dumping EEPROM contents, or reverse-engineering unsupported models, you can bypass the Device abstraction and interact directly with the hardware via the Interface struct.

This API executes raw protocol commands without enforcing property mappings.

use freemdu::Interface;

#[tokio::main]
async fn main() -> freemdu::Result<(), freemdu::serial::PortError> {
    let mut port = freemdu::serial::open("/dev/ttyACM0")?;
    let mut intf = Interface::new(port);

    // 1. Obtain the software ID (Mandatory first step)
    let id = intf.query_software_id().await?;
    println!("Target Software ID: {}", id);

    // 2. Unlock the interface using the specific 16-bit keys for this firmware
    intf.unlock_read_access(0x1234).await?;
    intf.unlock_full_access(0x5678).await?;

    // 3. Perform raw memory access
    let mem: [u8; 16] = intf.read_memory(0x0000).await?;
    println!("Memory dump at 0x0000: {:x?}", mem);

    Ok(())
}

Common Pitfalls & Best Practices

The 3-Second Timeout Lock

Crucial Context: Once fully unlocked, the appliance will accept diagnostic commands. However, the appliance's microcontroller enforces a strict security timeout. The interface automatically locks itself after 3 seconds of inactivity.

If your software takes longer than 3 seconds between requests (e.g., waiting for user input), the connection will drop, and subsequent commands will fail with an access error.

Best Practice: If your application needs to maintain a persistent connection, you must implement a background heartbeat task that periodically sends a benign command (like re-querying a basic property or reading a safe memory address) every 1-2 seconds to keep the session alive.