1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
pub mod mode;
mod device_grammar {
    include!(concat!(env!("OUT_DIR"), "/device_grammar.rs"));
}

use self::mode::connected::Connected;
use ea1::mode::power::{Power, Error as PowerError};
use ea1::mode::beam_track::{BeamTrack, Error as TrackError};

use std::net::{SocketAddrV4};
use std::io;
use std::io::{Write, BufRead, BufReader};

use std::net;
use std::result;
use encoding::{Encoding, DecoderTrap, EncoderTrap};
use encoding::all::ASCII;

/*
 * Instances of the EA1 struct are parameterized on an internal 'Mode' which represents the
 * state of the sensor e.g. Power or Beam Tracking mode.  It is essentially a state machine
 * with a trivial diagram where any state can transition to any other state.  State transitions
 * are performed by methods that consume the EA1 and return a new instance.
 */
pub struct EA1<M> {
    mode: M,
    socket: net::TcpStream
}

/*
 * A new instance of a connection to an EA1 meter starts in the Connected state.
 */
impl EA1<Connected> {
    pub fn new(device_address: SocketAddrV4) -> Result<EA1<Connected>> {
        net::TcpStream::connect(device_address)
            .map(move |socket| {
                EA1 {
                    mode: Connected::new(),
                    socket: socket
                }
            })
            .map_err(Error::from)
    }
}

/*
 * Generic behavior for any mode M.
 */
impl<M> EA1<M> {
    /*
     * Send a command to the device and block waiting for a reply.
     * TODO: timeout on socket
     * TODO: chain these method calls
     * TODO: appropriate error messages
     */
    fn send_sync(&mut self, cmd: &str) -> Result<DeviceResponse> {
        let prepended = format!("${}", cmd);
        let mut cmd_bytes = ASCII.encode(&prepended,
                                         EncoderTrap::Strict).unwrap();
        cmd_bytes.push('\r' as u8);
        cmd_bytes.push('\n' as u8);

        self.socket.write_all(&cmd_bytes);

        let mut resp_bytes: Vec<u8> = Vec::new();
        let mut response_buf = BufReader::new(&self.socket);
        let result = response_buf.read_until('\n' as u8, &mut resp_bytes);
        result.map(|_| {
            ASCII.decode(&resp_bytes, DecoderTrap::Strict).unwrap()
        })
        .map_err(Error::from)
        .and_then(|reply| {
            device_grammar::parse(&reply).map_err(Error::from)
        }) // TODO: this will throw a _very_ misleading error
    }

    /*
    * Set the state of echo to be enabled or disabled.
    */
    pub fn set_echo_enabled(&mut self, state: bool) -> Result<()> {
        let cmd = format!("EE {}", state as u8);
        self.send_sync(&cmd).map(|_| ())
    }

    /*
     * Set the meter to Power measurement mode
    */
    pub fn to_power_mode(self) -> EA1<Power> {
        EA1::<Power> {
            mode: Power::new(),
            socket: self.socket
        }
    }

    /*
     * Set the meter to beam track mode.
    */
    pub fn to_beam_track_mode(self) -> EA1<BeamTrack> {
        EA1::<BeamTrack> {
            mode: BeamTrack::new(),
            socket: self.socket
        }
    }
}

/*
 * Modes that implement the Measure trait define an interface for querying a device and returning
 * a result of some kind e.g. a floating point number in the case of Power.
 */
pub trait Measure {
    type OutputType;
    type ErrorType: Into<Error>;

    fn command(&self) -> &'static str;
    fn parse_reply(&self, &str) -> result::Result<Self::OutputType, Self::ErrorType>;
}

/*
 * When the type parameter M implements Measure, we delegate the implementation of
 * what to send to the device and how to parse the reply to mode, which is a concrete
 * implementation of M.
 */
impl <M> EA1<M> where M: Measure {
    pub fn measure(&mut self) -> result::Result<M::OutputType, Error> {
        let cmd = self.mode.command();

        self.send_sync(cmd)
            .and_then(|reply| {
                reply.to_result()
            })
            .and_then(|result| {
                self.mode.parse_reply(&result).map_err(|err| err.into())
            })
    }
}

/*
 * Used by the device grammar to wrap responses from the EA1.
 * Requests to the device can succeed or fail, but this is distinct from a failure that is caused
 * by e.g. i/o failure.  Additionally the response of the device may be simply an echo if the
 * echo is enabled.
 * TODO: this should not be public
 */
#[derive(Debug, PartialEq)]
pub enum DeviceResponse {
    Echo(String),
    Success(String),
    Failure(String),
}

impl DeviceResponse {
    /*
     * Simplifies control flow by converting DeviceResponse to a result.
     * TODO: Do we need this?  I'm not convinced this is the way to handle this.
     * TODO: If we do need this, it should be done by implementing
     *       From<DeviceResponse> for Result<String>
     */
    fn to_result(self) -> Result<String> {
        match self {
            DeviceResponse::Success(succ) => {
                Ok(succ)
            },
            DeviceResponse::Failure(fail) => {
                let msg = if fail.is_empty() {
                    String::from("unknown device error")
                } else {
                    fail
                };
                Err(Error::DeviceError(msg))
            },
            DeviceResponse::Echo(_) => {
                let msg = String::from("communication failure: received echo.");
                Err(Error::DeviceError(msg))
            }
        }
    }
}

/*
 * Error encodes what can go wrong in this module.
 */
#[derive(Debug)]
pub enum Error {
    ConnectionError(io::Error),
    PowerModeError(PowerError),
    DeviceError(String),
    ParserError(device_grammar::ParseError),
    BeamTrackError(TrackError)
}
type Result<T> = result::Result<T, Error>;

/*
 * Implementation of conversion traits for Error
 */
impl From<io::Error> for Error {
    fn from(err: io::Error) -> Error {
        Error::ConnectionError(err)
    }
}

impl From<PowerError> for Error {
    fn from(err: PowerError) -> Error {
        Error::PowerModeError(err)
    }
}

impl From<device_grammar::ParseError> for Error {
    fn from(err: device_grammar::ParseError) -> Error {
        Error::ParserError(err)
    }
}

impl From<TrackError> for Error {
    fn from(err: TrackError) -> Error {
        Error::BeamTrackError(err)
    }
}

#[cfg(test)]
mod tests {
    use std::thread;
    use std::env;
    use std::net;
    use std::str::FromStr;
    use super::EA1;

    const ea1_address_key: &'static str = "OPHIR_EA1_TEST_ADDR";

    #[test]
    #[ignore]
    fn test_connect() {
        let env_addr = env::var(ea1_address_key).unwrap();
        let address = net::SocketAddrV4::from_str(&env_addr).unwrap();
        let ea1 = EA1::new(address);
        assert!(ea1.is_ok(), true);
    }
}