ddc 0.3.0

DDC/CI monitor control
Documentation
#![deny(missing_docs)]
#![doc(html_root_url = "https://docs.rs/ddc/0.3.0/")]

//! Control displays using the DDC/CI protocol.
//!
//! Provides generic traits and utilities for working with DDC. See [downstream
//! crates](https://crates.io/crates/ddc/reverse_dependencies) for usable
//! concrete implementations.

extern crate mccs;

use std::{error, fmt, iter, time::Duration};
pub use {
    self::{
        commands::{Command, CommandResult, TimingMessage},
        delay::Delay,
    },
    mccs::{FeatureCode, Value as VcpValue, ValueType as VcpValueType},
};

/// DDC/CI command request and response types.
pub mod commands;
mod delay;

/// EDID EEPROM I2C address
pub const I2C_ADDRESS_EDID: u16 = 0x50;

/// E-DDC EDID segment register I2C address
pub const I2C_ADDRESS_EDID_SEGMENT: u16 = 0x30;

/// DDC/CI command and control I2C address
pub const I2C_ADDRESS_DDC_CI: u16 = 0x37;

/// DDC sub-address command prefix
pub const SUB_ADDRESS_DDC_CI: u8 = 0x51;

/// DDC delay required before retrying a request
pub const DELAY_COMMAND_FAILED_MS: u64 = 40;

/// A trait that allows retrieving Extended Display Identification Data (EDID)
/// from a device.
pub trait Edid {
    /// An error that can occur when reading the EDID from a device.
    type EdidError;

    /// Read up to 256 bytes of the monitor's EDID.
    fn read_edid(&mut self, offset: u8, data: &mut [u8]) -> Result<usize, Self::EdidError>;
}

/// E-DDC allows reading extensions of Enhanced EDID.
pub trait Eddc: Edid {
    /// Read part of the EDID using the segments added in the Enhanced Display
    /// Data Channel (E-DDC) protocol.
    fn read_eddc_edid(&mut self, segment: u8, offset: u8, data: &mut [u8]) -> Result<usize, Self::EdidError>;
}

/// A DDC host is able to communicate with a DDC device such as a display.
pub trait DdcHost {
    /// An error that can occur when communicating with a DDC device.
    ///
    /// Usually impls `From<ErrorCode>`.
    type Error;

    /// Wait for any previous commands to complete.
    ///
    /// The DDC specification defines delay intervals that must occur between
    /// execution of two subsequent commands, this waits for the amount of time
    /// remaining since the last command was executed. This is normally done
    /// internally and shouldn't need to be called manually unless synchronizing
    /// with an external process or another handle to the same device. It may
    /// however be desireable to run this before program exit.
    fn sleep(&mut self) {}
}

/// Allows the execution of arbitrary low level DDC commands.
pub trait DdcCommandRaw: DdcHost {
    /// Executes a raw DDC/CI command.
    ///
    /// A response should not be read unless `out` is not empty, and the delay
    /// should occur in between any write and read made to the device. A subslice
    /// of `out` excluding DDC packet headers should be returned.
    fn execute_raw<'a>(
        &mut self,
        data: &[u8],
        out: &'a mut [u8],
        response_delay: Duration,
    ) -> Result<&'a mut [u8], Self::Error>;
}

/// Using this marker trait will automatically implement the `DdcCommand` trait.
pub trait DdcCommandRawMarker: DdcCommandRaw
where
    Self::Error: From<ErrorCode>,
{
    /// Sets an internal `Delay` that must expire before the next command is
    /// attempted.
    fn set_sleep_delay(&mut self, delay: Delay);
}

/// A (slightly) higher level interface to `DdcCommandRaw`.
///
/// Some DDC implementations only provide access to the higher level commands
/// exposed in the `Ddc` trait.
pub trait DdcCommand: DdcHost {
    /// Execute a DDC/CI command. See the `commands` module for all available
    /// commands. The return type is dependent on the executed command.
    fn execute<C: Command>(&mut self, command: C) -> Result<C::Ok, Self::Error>;

    /// Computes a DDC/CI packet checksum
    fn checksum<II: IntoIterator<Item = u8>>(iter: II) -> u8 {
        iter.into_iter().fold(0u8, |sum, v| sum ^ v)
    }

    /// Encodes a DDC/CI command into a packet.
    ///
    /// `packet.len()` must be 3 bytes larger than `data.len()`
    fn encode_command<'a>(data: &[u8], packet: &'a mut [u8]) -> &'a [u8] {
        packet[0] = SUB_ADDRESS_DDC_CI;
        packet[1] = 0x80 | data.len() as u8;
        packet[2..2 + data.len()].copy_from_slice(data);
        packet[2 + data.len()] =
            Self::checksum(iter::once((I2C_ADDRESS_DDC_CI as u8) << 1).chain(packet[..2 + data.len()].iter().cloned()));

        &packet[..3 + data.len()]
    }
}

/// Using this marker trait will automatically implement the `Ddc` and `DdcTable`
/// traits.
pub trait DdcCommandMarker: DdcCommand
where
    Self::Error: From<ErrorCode>,
{
}

/// A high level interface to DDC commands.
pub trait Ddc: DdcHost {
    /// Retrieve the capability string from the device.
    ///
    /// This executes multiple `CapabilitiesRequest` commands to construct the entire string.
    fn capabilities_string(&mut self) -> Result<Vec<u8>, Self::Error>;

    /// Gets the current value of an MCCS VCP feature.
    fn get_vcp_feature(&mut self, code: FeatureCode) -> Result<VcpValue, Self::Error>;

    /// Sets a VCP feature to the specified value.
    fn set_vcp_feature(&mut self, code: FeatureCode, value: u16) -> Result<(), Self::Error>;

    /// Instructs the device to save its current settings.
    fn save_current_settings(&mut self) -> Result<(), Self::Error>;

    /// Retrieves a timing report from the device.
    fn get_timing_report(&mut self) -> Result<TimingMessage, Self::Error>;
}

/// Table commands can read and write arbitrary binary data to a VCP feature.
///
/// Tables were introduced in MCCS specification versions 3.0 and 2.2.
pub trait DdcTable: DdcHost {
    /// Read a table value from the device.
    fn table_read(&mut self, code: FeatureCode) -> Result<Vec<u8>, Self::Error>;

    /// Write a table value to the device.
    fn table_write(&mut self, code: FeatureCode, offset: u16, value: &[u8]) -> Result<(), Self::Error>;
}

/// DDC/CI protocol errors
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum ErrorCode {
    /// Expected matching offset from DDC/CI
    InvalidOffset,
    /// DDC/CI invalid packet length
    InvalidLength,
    /// Checksum mismatch
    InvalidChecksum,
    /// Expected opcode mismatch
    InvalidOpcode,
    /// Expected data mismatch
    InvalidData,
    /// Custom unspecified error
    Invalid(String),
}

impl error::Error for ErrorCode {}

impl fmt::Display for ErrorCode {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        f.write_str(match *self {
            ErrorCode::InvalidOffset => "invalid offset returned from DDC/CI",
            ErrorCode::InvalidLength => "invalid DDC/CI length",
            ErrorCode::InvalidChecksum => "DDC/CI checksum mismatch",
            ErrorCode::InvalidOpcode => "DDC/CI VCP opcode mismatch",
            ErrorCode::InvalidData => "invalid DDC/CI data",
            ErrorCode::Invalid(ref s) => s,
        })
    }
}

impl<D: DdcCommandMarker> Ddc for D
where
    D::Error: From<ErrorCode>,
{
    fn capabilities_string(&mut self) -> Result<Vec<u8>, Self::Error> {
        let mut string = Vec::new();
        let mut offset = 0;
        loop {
            let caps = self.execute(commands::CapabilitiesRequest::new(offset))?;
            if caps.offset != offset {
                return Err(ErrorCode::InvalidOffset.into())
            } else if caps.data.is_empty() {
                break
            }

            string.extend(caps.data.iter());

            offset += caps.data.len() as u16;
        }

        Ok(string)
    }

    fn get_vcp_feature(&mut self, code: FeatureCode) -> Result<VcpValue, Self::Error> {
        self.execute(commands::GetVcpFeature::new(code))
    }

    fn set_vcp_feature(&mut self, code: FeatureCode, value: u16) -> Result<(), Self::Error> {
        self.execute(commands::SetVcpFeature::new(code, value))
    }

    fn save_current_settings(&mut self) -> Result<(), Self::Error> {
        self.execute(commands::SaveCurrentSettings)
    }

    fn get_timing_report(&mut self) -> Result<TimingMessage, Self::Error> {
        self.execute(commands::GetTimingReport)
    }
}

impl<D: DdcCommandMarker> DdcTable for D
where
    D::Error: From<ErrorCode>,
{
    fn table_read(&mut self, code: FeatureCode) -> Result<Vec<u8>, Self::Error> {
        let mut value = Vec::new();
        let mut offset = 0;
        loop {
            let table = self.execute(commands::TableRead::new(code, offset))?;
            if table.offset != offset {
                return Err(ErrorCode::InvalidOffset.into())
            } else if table.bytes().is_empty() {
                break
            }

            value.extend(table.bytes().iter());

            offset += table.bytes().len() as u16;
        }

        Ok(value)
    }

    fn table_write(&mut self, code: FeatureCode, mut offset: u16, value: &[u8]) -> Result<(), Self::Error> {
        for chunk in value.chunks(32) {
            self.execute(commands::TableWrite::new(code, offset, chunk))?;
            offset += chunk.len() as u16;
        }

        Ok(())
    }
}

impl<D: DdcCommandRawMarker> DdcCommand for D
where
    D::Error: From<ErrorCode>,
{
    fn execute<C: Command>(&mut self, command: C) -> Result<C::Ok, Self::Error> {
        // TODO: once associated consts work...
        //let mut data = [0u8; C::MAX_LEN];
        let mut data = [0u8; 36];
        command.encode(&mut data)?;

        // TODO: once associated consts work...
        //let mut out = [0u8; C::Ok::MAX_LEN + 3];
        let mut out = [0u8; 36 + 3];
        let out = if C::Ok::MAX_LEN > 0 {
            &mut out[..C::Ok::MAX_LEN + 3]
        } else {
            &mut []
        };
        let res = self.execute_raw(
            &data[..command.len()],
            out,
            Duration::from_millis(C::DELAY_RESPONSE_MS as _),
        );
        let res = match res {
            Ok(res) => {
                self.set_sleep_delay(Delay::new(Duration::from_millis(C::DELAY_COMMAND_MS)));
                res
            },
            Err(e) => {
                self.set_sleep_delay(Delay::new(Duration::from_millis(DELAY_COMMAND_FAILED_MS)));
                return Err(e)
            },
        };

        let res = C::Ok::decode(res);

        if res.is_err() {
            self.set_sleep_delay(Delay::new(Duration::from_millis(DELAY_COMMAND_FAILED_MS)));
        }

        res.map_err(From::from)
    }
}