etherdream 0.1.1

An EtherDream (laser projector DAC) library.
Documentation
// Copyright (c) 2016 Brandon Thomas <bt@brand.io>, <echelon@gmail.com>

//! This module contains the EtherDream hardware interface.

use byteorder::LittleEndian;
use byteorder::WriteBytesExt;
use error::EtherdreamError;
use point::PipelinePoint;
use point::SimplePoint;
use protocol::Begin;
use protocol::COMMAND_PING;
use protocol::COMMAND_PREPARE;
use protocol::CommandCode;
use protocol::DacResponse;
use protocol::Point;
use std::io::Read;
use std::io::Write;
use std::net::IpAddr;
use std::net::TcpStream;
use std::time::Duration;

/// An EtherDream DAC.
/// Controls what we display on the projector.
pub struct Dac {
  ip_address: IpAddr,
  stream: TcpStream,
}

impl Dac {
  /// CTOR.
  pub fn new(ip_address: IpAddr) -> Dac {
    let stream = TcpStream::connect((ip_address, 7765u16)).unwrap(); // FIXME

    // These should be reasonable timeouts for any arbitrary laser show.
    stream.set_read_timeout(Some(Duration::from_millis(500))).unwrap(); // FIXME
    stream.set_write_timeout(Some(Duration::from_millis(500))).unwrap();

    Dac {
      ip_address: ip_address,
      stream: stream,
    }
  }

  /// IP address the DAC lives at.
  pub fn get_ip_address(&self) -> &IpAddr {
    &self.ip_address
  }

  /// Stream points generated by a function.
  /// The function takes the number of points it needs to generate.
  pub fn play_function<F>(&mut self, mut make_points: F)
      -> Result<(), EtherdreamError> where F: FnMut(u16) -> Vec<Point> {
    // Read initial hello message from DAC.
    let mut response = self.read_response()?;

    self.try_prepare(response);

    let mut started = false;

    loop {
      let num_points = 1799 - response.status.buffer_fullness;
      let points = make_points(num_points);

      let mut cmd : Vec<u8> = Vec::new();
      cmd.push(0x64); // 'data' command.
      cmd.write_u16::<LittleEndian>(num_points)?;

      for point in points {
        cmd.extend(point.serialize());
      }

      response = self.write_serialized_points(&cmd)?;

      if !started {
        response = self.begin()?;
        started = true;
      }
    }
  }

  /// Stream points generated by a function.
  /// The function takes the number of points it needs to generate.
  pub fn stream_pipeline_points<F>(&mut self, mut make_points: F)
      -> Result<(), EtherdreamError> where F: FnMut(u16) -> Vec<PipelinePoint> {
    // Read initial hello message from DAC.
    let mut response = self.read_response()?;

    self.try_prepare(response);

    let mut started = false;

    loop {
      let num_points = 1799 - response.status.buffer_fullness;
      let points = make_points(num_points);

      let mut cmd : Vec<u8> = Vec::new();
      cmd.push(0x64); // 'data' command.
      cmd.write_u16::<LittleEndian>(num_points)?;

      for point in points {
        cmd.write_u16::<LittleEndian>(0)?; // Control
        cmd.write_i16::<LittleEndian>(point.x as i16)?;
        cmd.write_i16::<LittleEndian>(point.y as i16)?;

        if point.is_blank {
          cmd.write_u16::<LittleEndian>(0)?;
          cmd.write_u16::<LittleEndian>(0)?;
          cmd.write_u16::<LittleEndian>(0)?;
        } else {
          // TODO/FIXME:
          //cmd.write_u16::<LittleEndian>(point.r as u16)?;
          //cmd.write_u16::<LittleEndian>(point.g as u16)?;
          //cmd.write_u16::<LittleEndian>(point.b as u16)?;
          cmd.write_u16::<LittleEndian>(65535)?;
          cmd.write_u16::<LittleEndian>(65535)?;
          cmd.write_u16::<LittleEndian>(65535)?;
        }

        cmd.write_u16::<LittleEndian>(0)?; // i
        cmd.write_u16::<LittleEndian>(0)?; // u1
        cmd.write_u16::<LittleEndian>(0)?; // u2
      }

      response = self.write_serialized_points(&cmd)?;

      if !started {
        response = self.begin()?;
        started = true;
      }
    }
  }

  /// Stream points generated by a function.
  /// The function takes the number of points it needs to generate.
  pub fn stream_simple_points<F>(&mut self, mut make_points: F)
      -> Result<(), EtherdreamError> where F: FnMut(u16) -> Vec<SimplePoint> {
    // Read initial hello message from DAC.
    let mut response = self.read_response()?;

    self.try_prepare(response);

    let mut started = false;

    #[inline(always)]
    fn expand(color: u8) -> u16 {
      (color as u16) * 257
    }

    loop {
      let num_points = 1799 - response.status.buffer_fullness;
      let points = make_points(num_points);

      let mut cmd : Vec<u8> = Vec::new();
      cmd.push(0x64); // 'data' command.
      cmd.write_u16::<LittleEndian>(num_points)?;

      for point in points {
        cmd.write_u16::<LittleEndian>(0)?; // Control
        cmd.write_i16::<LittleEndian>(point.x)?;
        cmd.write_i16::<LittleEndian>(point.y)?;
        if point.is_blank {
          cmd.write_u16::<LittleEndian>(0)?;
          cmd.write_u16::<LittleEndian>(0)?;
          cmd.write_u16::<LittleEndian>(0)?;
        } else {
          cmd.write_u16::<LittleEndian>(expand(point.r))?;
          cmd.write_u16::<LittleEndian>(expand(point.g))?;
          cmd.write_u16::<LittleEndian>(expand(point.b))?;
        }
        cmd.write_u16::<LittleEndian>(0)?; // i
        cmd.write_u16::<LittleEndian>(0)?; // u1
        cmd.write_u16::<LittleEndian>(0)?; // u2
      }

      response = self.write_serialized_points(&cmd)?;

      if !started {
        response = self.begin()?;
        started = true;
      }
    }
  }

  fn hello(&mut self) -> Result<DacResponse, EtherdreamError> {
    let _bytes = self.stream.write(&[COMMAND_PING])?;
    self.read_response() // FIXME
  }

  fn prepare(&mut self) -> Result<DacResponse, EtherdreamError> {
    let _bytes = self.stream.write(&[COMMAND_PREPARE])?;
    self.read_expected_response(CommandCode::Prepare)
  }

  fn begin(&mut self) -> Result<DacResponse, EtherdreamError> {
    let cmd = Begin { low_water_mark: 0, point_rate: 30_000 };
    let _bytes = self.stream.write(&cmd.serialize())?;
    self.read_expected_response(CommandCode::Begin)
  }

  /// Clear emergency stop state.
  fn clear_emergency_stop(&mut self) -> Result<DacResponse, EtherdreamError> {
    let cmd = [ 0x63u8 ]; // 'c'
    let _bytes = self.stream.write(&cmd)?;
    self.read_response() // FIXME
  }

  fn try_prepare(&mut self, response: DacResponse) {
    // Documentation for playback_flags:
    // [0]: Emergency stop occurred due to E-Stop packet or invalid command.
    // [1]: Emergency stop occurred due to E-Stop input to projector.
    // [2]: Emergency stop input to projector is currently active.
    // [3]: Emergency stop occurred due to overtemperature condition.
    // [4]: Overtemperature condition is currently active.
    // [5]: Emergency stop occurred due to loss of Ethernet link.
    // [15:5]: Future use.
    let response = match response.status.playback_flags {
      0x1 | 0x2 | 0x4 | 0x6 => {
        // A previous E-Stop state must be cleared.
        self.clear_emergency_stop().unwrap() // FIXME
      },
      _ => response,
    };

    if response.status.playback_flags != 0x0 && response.status.playback_flags != 0x1 {
      println!("\nBad playback flags, must PREPARE: {}", response.status.playback_flags);
      println!("\nSend prepare");
      let resp = self.prepare().unwrap();
      println!("Response: {:?}", resp);
      if !resp.is_ack() {
        println!("Failure!");
        panic!("Non-ACK received");
      }
      return;
    }

    if response.status.playback_state == 0x2 {
      println!("\nBad playback_state, must PREPARE: {}", response.status.playback_state);
      println!("\nSend prepare");
      let resp = self.prepare().unwrap();
      println!("Response: {:?}", resp);
      if !resp.is_ack() {
        println!("Failure!");
        panic!("Non-ACK received");
      }
    }
  }

  // TODO:
  // Sends (3 + 18*n) bytes.
  // fn write_data(&mut self, num_points: u16) -> Result<DacResponse, EtherdreamError>

  /// Write a slice of points to the DAC.
  fn write_serialized_points(&mut self, serialized_points: &[u8])
                                 -> Result<DacResponse, EtherdreamError> {
    self.stream.write(&serialized_points)?;
    self.read_expected_response(CommandCode::Data)
  }

  /// Read a response from the DAC, and parse error conditions.
  /// Called after sending a command.
  fn read_expected_response(&mut self, expected_command: CommandCode)
      -> Result<DacResponse, EtherdreamError> {

    let response = self.read_response()?;

    if !response.acknowledgement.is_ack() {
      return Err(EtherdreamError::ReceivedNack {
        code: response.acknowledgement,
        command: response.command,
      });
    }

    if response.command != expected_command {
      return Err(EtherdreamError::WrongResponse);
    }

    Ok(response)
  }

  fn read_response(&mut self) -> Result<DacResponse, EtherdreamError> {
    let mut buf = [0; 22];
    let _size = self.stream.read(&mut buf)?;
    DacResponse::parse(&buf)
  }
}