use super::{PROBE_TIMEOUT, Session, malformed};
use crate::{
error::Error,
protocol::{
caps::other::DataTypes,
cdbs::{Execute, GetParameter, Read, Send, SendDiagnostic, SetParameter},
curves::Curves,
data::{self, BoundaryType2, FrameTable, PerfInformation},
sense::{self, Failure, Fault, Refusal},
window::Channel,
},
transport::{Data, Sense, Status},
};
use std::sync::Arc;
use std::time::Duration;
use tracing::*;
impl Session {
pub fn read_data(
&mut self,
kind: data::DataType,
color: u8,
) -> Result<(data::Header, data::Values), Error> {
let (header, valid) = self.read_record(kind, color)?;
Ok((header, data::Values::decode(kind.scalar(), &valid)))
}
pub fn read_record(
&mut self,
kind: data::DataType,
color: u8,
) -> Result<(data::Header, Vec<u8>), Error> {
let row = kind.row();
if !row.header {
return Err(Error::Unsupported {
op: "read data type",
reason: format!("{kind:?} carries no data header to size a read by"),
});
}
let (width, qualifier, color) = self.addressing(kind, row.read, color, "read data type")?;
let code = row.code;
let mut fetch = |len: u32| -> Result<Vec<u8>, Error> {
let cmd = Read::new(code, color, qualifier, len);
let mut buf = vec![0u8; cmd.allocation_length()];
debug!("cdb for read {:02x} {:02x?}", code, &cmd.cdb());
let completion = self.run(&cmd.cdb(), Data::In(&mut buf), PROBE_TIMEOUT)?;
buf.truncate(completion.transferred);
debug!("recv {:02x?}", buf);
Ok(buf)
};
let probe = fetch(data::HEADER as u32)?;
let (probe, _) = data::Header::from_bytes(&probe)
.ok_or_else(|| malformed(format!("{kind:?} header was {} bytes", probe.len())))?;
let raw = fetch(data::HEADER as u32 + probe.length)?;
let (header, payload) = data::Header::from_bytes(&raw)
.ok_or_else(|| malformed(format!("{kind:?} header was {} bytes", raw.len())))?;
let valid: &[u8] = match row.count {
Some(n) => payload
.get(..n as usize * width as usize)
.unwrap_or(payload),
None => payload,
};
debug!(?header, bytes = valid.len(), "read data");
Ok((header, valid.to_vec()))
}
fn addressing(
&self,
kind: data::DataType,
offered: Option<DataTypes>,
color: u8,
op: &'static str,
) -> Result<(u8, u8, u8), Error> {
let refuse = |reason| Error::Unsupported { op, reason };
match offered {
Some(bit) if self.caps.features.data_types.contains(bit) => {}
_ => return Err(refuse(format!("this unit does not offer {kind:?}"))),
}
let Some((width, qualifier)) = kind.qualifier() else {
return Err(refuse(format!("{kind:?} has no addressing qualifier")));
};
Ok((width, qualifier, if kind.per_color() { color } else { 0 }))
}
pub fn send_data(&mut self, kind: data::DataType, color: u8, body: &[u8]) -> Result<(), Error> {
let (_, qualifier, color) =
self.addressing(kind, kind.row().write, color, "send data type")?;
let cmd = Send::new(kind.row().code, color, qualifier, body.len() as u32);
debug!(
"cdb for send {:02x} {:02x?} data {:02x?}",
kind.row().code,
&cmd.cdb(),
&body
);
self.run(&cmd.cdb(), Data::Out(body), PROBE_TIMEOUT)?;
Ok(())
}
pub fn boundaries(&mut self) -> Result<data::Boundary, Error> {
let (_, record) = self.read_record(data::DataType::Boundary, 0)?;
let boundary = data::Boundary::from_bytes(&record)
.ok_or_else(|| malformed(format!("Boundary was {} bytes", record.len())))?;
self.frames = Some(FrameTable::Boundary(boundary.clone()));
Ok(boundary)
}
pub fn frames(&self) -> Option<&data::Boundary> {
match self.frames.as_ref() {
Some(FrameTable::Boundary(boundary)) => Some(boundary),
_ => None,
}
}
pub fn boundaries_type2(&mut self) -> Result<data::BoundaryType2, Error> {
let (_, record) = self.read_record(data::DataType::Boundary2, 0)?;
let boundary = data::BoundaryType2::from_bytes(&record)
.ok_or_else(|| malformed(format!("BoundaryType2 was {} bytes", record.len())))?;
self.frames = Some(FrameTable::BoundaryType2(boundary.clone()));
Ok(boundary)
}
pub fn frames_type2(&self) -> Option<&data::BoundaryType2> {
match self.frames.as_ref() {
Some(FrameTable::BoundaryType2(boundary)) => Some(boundary),
_ => None,
}
}
pub fn set_boundaries(&mut self, boundary: &data::Boundary) -> Result<(), Error> {
let bytes = boundary.to_bytes()?;
self.send_data(data::DataType::Boundary, 0, &bytes)
.map_err(|e| match e {
Error::Device(fault)
if matches!(*fault, Fault::Rejected(Refusal::BadCdbField, _)) =>
{
Error::Unsupported {
op: "frame table",
reason: format!(
"this unit would not take a {}-frame table of {} bytes",
boundary.frames.len(),
bytes.len()
),
}
}
e => e,
})?;
self.frames = Some(FrameTable::Boundary(boundary.clone()));
Ok(())
}
pub fn set_boundaries_type2(&mut self, boundary: &data::BoundaryType2) -> Result<(), Error> {
let bytes = boundary.to_bytes()?;
self.send_data(data::DataType::Boundary2, 0, &bytes)?;
self.frames = Some(FrameTable::BoundaryType2(boundary.clone()));
Ok(())
}
pub fn read_perforations(&mut self) -> Result<data::PerfInformation, Error> {
let (_, record) = self.read_record(data::DataType::Perforation, 0)?;
self.test_unit_ready(Duration::from_millis(500))?;
let perfs = PerfInformation::from_bytes(&record)
.ok_or_else(|| malformed(format!("PerfInfo was {} bytes", record.len())))?;
Ok(perfs)
}
pub fn read_boundaries_type2(&mut self) -> Result<data::BoundaryType2, Error> {
let (_, record) = self.read_record(data::DataType::Boundary2, 0)?;
let bounds = BoundaryType2::from_bytes(&record)
.ok_or_else(|| malformed(format!("BoundaryType2 was {} bytes", record.len())))?;
Ok(bounds)
}
pub fn white_balance(&mut self, channel: Channel) -> Result<u32, Error> {
let color = channel.id();
let (_, values) = self.read_data(data::DataType::WhiteBalanceExposure, color)?;
let data::Values::Longs(v) = values else {
return Err(malformed(format!(
"WhiteBalanceExposure color {color} did not come back as longs"
)));
};
let exposure = *v
.first()
.ok_or_else(|| malformed(format!("WhiteBalanceExposure color {color} was empty")))?;
debug!(color, exposure, "start-up exposure");
Ok(exposure)
}
pub fn setup(&mut self, color: u8) -> Result<data::Setup, Error> {
let (_, values) = self.read_data(data::DataType::Setup, color)?;
let data::Values::Bytes(record) = values else {
return Err(malformed("Setup did not come back as bytes".into()));
};
data::Setup::from_bytes(&record)
.ok_or_else(|| malformed(format!("Setup was {} bytes", record.len())))
}
pub fn fetch_curves(&mut self) -> bool {
let Some(ccd) = self.caps.ccd.clone() else {
return false;
};
let rows = usize::from(self.caps.address.lines).max(1);
let (_, values) = match self
.read_data(data::DataType::CcdData, 0)
.inspect_err(|e| debug!(%e, "no CCD curves to correct with"))
{
Ok(v) => v,
Err(_) => return false,
};
let data::Values::Words(words) = values else {
debug!("CcdData did not come back as words");
return false;
};
let curves = Curves::parse(&ccd, &words, rows, 0);
if curves.is_none() {
warn!(
curves = ccd.curves(),
points = ccd.points.len(),
got = words.len(),
"the CCD curves do not match the page describing them, scanning uncorrected"
);
}
self.curves = curves.map(Arc::new);
self.curves.is_some()
}
pub fn curves(&self) -> Option<Arc<Curves>> {
self.curves.clone()
}
pub fn cooperation(&mut self) -> Result<data::CooperativeAction, Error> {
let (_, values) = self.read_data(data::DataType::Cooperation, 0)?;
let data::Values::Bytes(record) = values else {
return Err(malformed("Cooperation did not come back as bytes".into()));
};
data::CooperativeAction::from_bytes(&record)
.ok_or_else(|| malformed(format!("Cooperation was {} bytes", record.len())))
}
pub fn execute(
&mut self,
operation: data::Op,
params: data::Operation,
timeout: Duration,
) -> Result<(), Error> {
if !self.caps.features.execute.supports(operation) {
return Err(Error::Unsupported {
op: "execute operation",
reason: format!("this unit does not offer {operation:?}"),
});
}
let block = params.to_bytes();
let cmd = SetParameter::new(operation.code(), block.len() as u32);
self.run(&cmd.cdb(), Data::Out(&block), PROBE_TIMEOUT)?;
debug!(?operation, ?params, "executing");
self.run(&Execute.cdb(), Data::None, PROBE_TIMEOUT)?;
match self.test_unit_ready(timeout) {
Err(Error::Device(fault))
if matches!(*fault, Fault::Reported(Failure::Mechanism, _)) =>
{
match self.diagnose() {
Ok(Some(sense)) => Err(Error::Device(Box::new(Fault::Reported(
sense::diagnosed(&sense),
Some(sense),
)))),
_ => Err(Error::Device(fault)),
}
}
other => other,
}
}
pub fn get_parameter(&mut self, operation: data::Op) -> Result<data::Operation, Error> {
if !self.caps.features.execute.supports(operation) {
return Err(Error::Unsupported {
op: "get parameter",
reason: format!("this unit does not offer {operation:?}"),
});
}
let cmd = GetParameter::new(operation.code(), data::Operation::LENGTH as u32);
let mut buf = vec![0u8; cmd.allocation_length()];
let completion = self.run(&cmd.cdb(), Data::In(&mut buf), PROBE_TIMEOUT)?;
buf.truncate(completion.transferred);
let params = data::Operation::from_bytes(&buf)
.ok_or_else(|| malformed(format!("{operation:?} was {} bytes", buf.len())))?;
debug!(?operation, ?params, "read parameters");
Ok(params)
}
pub fn diagnose(&mut self) -> Result<Option<Sense>, Error> {
let completion =
self.transport
.execute(&SendDiagnostic.cdb(), Data::None, PROBE_TIMEOUT)?;
debug!(status = ?completion.status, sense = ?completion.sense, "diagnostic");
Ok(completion
.sense
.filter(|_| completion.status == Status::CheckCondition))
}
}