ddc/
lib.rs

1#![deny(missing_docs)]
2#![doc(html_root_url = "https://docs.rs/ddc/0.3.0/")]
3
4//! Control displays using the DDC/CI protocol.
5//!
6//! Provides generic traits and utilities for working with DDC. See [downstream
7//! crates](https://crates.io/crates/ddc/reverse_dependencies) for usable
8//! concrete implementations.
9
10extern crate mccs;
11
12use std::{error, fmt, iter, time::Duration};
13pub use {
14    self::{
15        commands::{Command, CommandResult, TimingMessage},
16        delay::Delay,
17    },
18    mccs::{FeatureCode, Value as VcpValue, ValueType as VcpValueType},
19};
20
21/// DDC/CI command request and response types.
22pub mod commands;
23mod delay;
24
25/// EDID EEPROM I2C address
26pub const I2C_ADDRESS_EDID: u16 = 0x50;
27
28/// E-DDC EDID segment register I2C address
29pub const I2C_ADDRESS_EDID_SEGMENT: u16 = 0x30;
30
31/// DDC/CI command and control I2C address
32pub const I2C_ADDRESS_DDC_CI: u16 = 0x37;
33
34/// DDC sub-address command prefix
35pub const SUB_ADDRESS_DDC_CI: u8 = 0x51;
36
37/// DDC delay required before retrying a request
38pub const DELAY_COMMAND_FAILED_MS: u64 = 40;
39
40/// A trait that allows retrieving Extended Display Identification Data (EDID)
41/// from a device.
42pub trait Edid {
43    /// An error that can occur when reading the EDID from a device.
44    type EdidError;
45
46    /// Read up to 256 bytes of the monitor's EDID.
47    fn read_edid(&mut self, offset: u8, data: &mut [u8]) -> Result<usize, Self::EdidError>;
48}
49
50/// E-DDC allows reading extensions of Enhanced EDID.
51pub trait Eddc: Edid {
52    /// Read part of the EDID using the segments added in the Enhanced Display
53    /// Data Channel (E-DDC) protocol.
54    fn read_eddc_edid(&mut self, segment: u8, offset: u8, data: &mut [u8]) -> Result<usize, Self::EdidError>;
55}
56
57/// A DDC host is able to communicate with a DDC device such as a display.
58pub trait DdcHost {
59    /// An error that can occur when communicating with a DDC device.
60    ///
61    /// Usually impls `From<ErrorCode>`.
62    type Error;
63
64    /// Wait for any previous commands to complete.
65    ///
66    /// The DDC specification defines delay intervals that must occur between
67    /// execution of two subsequent commands, this waits for the amount of time
68    /// remaining since the last command was executed. This is normally done
69    /// internally and shouldn't need to be called manually unless synchronizing
70    /// with an external process or another handle to the same device. It may
71    /// however be desireable to run this before program exit.
72    fn sleep(&mut self) {}
73}
74
75/// Allows the execution of arbitrary low level DDC commands.
76pub trait DdcCommandRaw: DdcHost {
77    /// Executes a raw DDC/CI command.
78    ///
79    /// A response should not be read unless `out` is not empty, and the delay
80    /// should occur in between any write and read made to the device. A subslice
81    /// of `out` excluding DDC packet headers should be returned.
82    fn execute_raw<'a>(
83        &mut self,
84        data: &[u8],
85        out: &'a mut [u8],
86        response_delay: Duration,
87    ) -> Result<&'a mut [u8], Self::Error>;
88}
89
90/// Using this marker trait will automatically implement the `DdcCommand` trait.
91pub trait DdcCommandRawMarker: DdcCommandRaw
92where
93    Self::Error: From<ErrorCode>,
94{
95    /// Sets an internal `Delay` that must expire before the next command is
96    /// attempted.
97    fn set_sleep_delay(&mut self, delay: Delay);
98}
99
100/// A (slightly) higher level interface to `DdcCommandRaw`.
101///
102/// Some DDC implementations only provide access to the higher level commands
103/// exposed in the `Ddc` trait.
104pub trait DdcCommand: DdcHost {
105    /// Execute a DDC/CI command. See the `commands` module for all available
106    /// commands. The return type is dependent on the executed command.
107    fn execute<C: Command>(&mut self, command: C) -> Result<C::Ok, Self::Error>;
108
109    /// Computes a DDC/CI packet checksum
110    fn checksum<II: IntoIterator<Item = u8>>(iter: II) -> u8 {
111        iter.into_iter().fold(0u8, |sum, v| sum ^ v)
112    }
113
114    /// Encodes a DDC/CI command into a packet.
115    ///
116    /// `packet.len()` must be 3 bytes larger than `data.len()`
117    fn encode_command<'a>(data: &[u8], packet: &'a mut [u8]) -> &'a [u8] {
118        packet[0] = SUB_ADDRESS_DDC_CI;
119        packet[1] = 0x80 | data.len() as u8;
120        packet[2..2 + data.len()].copy_from_slice(data);
121        packet[2 + data.len()] =
122            Self::checksum(iter::once((I2C_ADDRESS_DDC_CI as u8) << 1).chain(packet[..2 + data.len()].iter().cloned()));
123
124        &packet[..3 + data.len()]
125    }
126}
127
128/// Using this marker trait will automatically implement the `Ddc` and `DdcTable`
129/// traits.
130pub trait DdcCommandMarker: DdcCommand
131where
132    Self::Error: From<ErrorCode>,
133{
134}
135
136/// A high level interface to DDC commands.
137pub trait Ddc: DdcHost {
138    /// Retrieve the capability string from the device.
139    ///
140    /// This executes multiple `CapabilitiesRequest` commands to construct the entire string.
141    fn capabilities_string(&mut self) -> Result<Vec<u8>, Self::Error>;
142
143    /// Gets the current value of an MCCS VCP feature.
144    fn get_vcp_feature(&mut self, code: FeatureCode) -> Result<VcpValue, Self::Error>;
145
146    /// Sets a VCP feature to the specified value.
147    fn set_vcp_feature(&mut self, code: FeatureCode, value: u16) -> Result<(), Self::Error>;
148
149    /// Instructs the device to save its current settings.
150    fn save_current_settings(&mut self) -> Result<(), Self::Error>;
151
152    /// Retrieves a timing report from the device.
153    fn get_timing_report(&mut self) -> Result<TimingMessage, Self::Error>;
154}
155
156/// Table commands can read and write arbitrary binary data to a VCP feature.
157///
158/// Tables were introduced in MCCS specification versions 3.0 and 2.2.
159pub trait DdcTable: DdcHost {
160    /// Read a table value from the device.
161    fn table_read(&mut self, code: FeatureCode) -> Result<Vec<u8>, Self::Error>;
162
163    /// Write a table value to the device.
164    fn table_write(&mut self, code: FeatureCode, offset: u16, value: &[u8]) -> Result<(), Self::Error>;
165}
166
167/// DDC/CI protocol errors
168#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
169pub enum ErrorCode {
170    /// Expected matching offset from DDC/CI
171    InvalidOffset,
172    /// DDC/CI invalid packet length
173    InvalidLength,
174    /// Checksum mismatch
175    InvalidChecksum,
176    /// Expected opcode mismatch
177    InvalidOpcode,
178    /// Expected data mismatch
179    InvalidData,
180    /// Custom unspecified error
181    Invalid(String),
182}
183
184impl error::Error for ErrorCode {}
185
186impl fmt::Display for ErrorCode {
187    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
188        f.write_str(match *self {
189            ErrorCode::InvalidOffset => "invalid offset returned from DDC/CI",
190            ErrorCode::InvalidLength => "invalid DDC/CI length",
191            ErrorCode::InvalidChecksum => "DDC/CI checksum mismatch",
192            ErrorCode::InvalidOpcode => "DDC/CI VCP opcode mismatch",
193            ErrorCode::InvalidData => "invalid DDC/CI data",
194            ErrorCode::Invalid(ref s) => s,
195        })
196    }
197}
198
199impl<D: DdcCommandMarker> Ddc for D
200where
201    D::Error: From<ErrorCode>,
202{
203    fn capabilities_string(&mut self) -> Result<Vec<u8>, Self::Error> {
204        let mut string = Vec::new();
205        let mut offset = 0;
206        loop {
207            let caps = self.execute(commands::CapabilitiesRequest::new(offset))?;
208            if caps.offset != offset {
209                return Err(ErrorCode::InvalidOffset.into())
210            } else if caps.data.is_empty() {
211                break
212            }
213
214            string.extend(caps.data.iter());
215
216            offset += caps.data.len() as u16;
217        }
218
219        Ok(string)
220    }
221
222    fn get_vcp_feature(&mut self, code: FeatureCode) -> Result<VcpValue, Self::Error> {
223        self.execute(commands::GetVcpFeature::new(code))
224    }
225
226    fn set_vcp_feature(&mut self, code: FeatureCode, value: u16) -> Result<(), Self::Error> {
227        self.execute(commands::SetVcpFeature::new(code, value))
228    }
229
230    fn save_current_settings(&mut self) -> Result<(), Self::Error> {
231        self.execute(commands::SaveCurrentSettings)
232    }
233
234    fn get_timing_report(&mut self) -> Result<TimingMessage, Self::Error> {
235        self.execute(commands::GetTimingReport)
236    }
237}
238
239impl<D: DdcCommandMarker> DdcTable for D
240where
241    D::Error: From<ErrorCode>,
242{
243    fn table_read(&mut self, code: FeatureCode) -> Result<Vec<u8>, Self::Error> {
244        let mut value = Vec::new();
245        let mut offset = 0;
246        loop {
247            let table = self.execute(commands::TableRead::new(code, offset))?;
248            if table.offset != offset {
249                return Err(ErrorCode::InvalidOffset.into())
250            } else if table.bytes().is_empty() {
251                break
252            }
253
254            value.extend(table.bytes().iter());
255
256            offset += table.bytes().len() as u16;
257        }
258
259        Ok(value)
260    }
261
262    fn table_write(&mut self, code: FeatureCode, mut offset: u16, value: &[u8]) -> Result<(), Self::Error> {
263        for chunk in value.chunks(32) {
264            self.execute(commands::TableWrite::new(code, offset, chunk))?;
265            offset += chunk.len() as u16;
266        }
267
268        Ok(())
269    }
270}
271
272impl<D: DdcCommandRawMarker> DdcCommand for D
273where
274    D::Error: From<ErrorCode>,
275{
276    fn execute<C: Command>(&mut self, command: C) -> Result<C::Ok, Self::Error> {
277        // TODO: once associated consts work...
278        //let mut data = [0u8; C::MAX_LEN];
279        let mut data = [0u8; 36];
280        command.encode(&mut data)?;
281
282        // TODO: once associated consts work...
283        //let mut out = [0u8; C::Ok::MAX_LEN + 3];
284        let mut out = [0u8; 36 + 3];
285        let out = if C::Ok::MAX_LEN > 0 {
286            &mut out[..C::Ok::MAX_LEN + 3]
287        } else {
288            &mut []
289        };
290        let res = self.execute_raw(
291            &data[..command.len()],
292            out,
293            Duration::from_millis(C::DELAY_RESPONSE_MS as _),
294        );
295        let res = match res {
296            Ok(res) => {
297                self.set_sleep_delay(Delay::new(Duration::from_millis(C::DELAY_COMMAND_MS)));
298                res
299            },
300            Err(e) => {
301                self.set_sleep_delay(Delay::new(Duration::from_millis(DELAY_COMMAND_FAILED_MS)));
302                return Err(e)
303            },
304        };
305
306        let res = C::Ok::decode(res);
307
308        if res.is_err() {
309            self.set_sleep_delay(Delay::new(Duration::from_millis(DELAY_COMMAND_FAILED_MS)));
310        }
311
312        res.map_err(From::from)
313    }
314}