Device.java
Go to the documentation of this file.
00001 /*
00002  * Copyright (C) 2011 Google Inc.
00003  *
00004  * Licensed under the Apache License, Version 2.0 (the "License"); you may not
00005  * use this file except in compliance with the License. You may obtain a copy of
00006  * the License at
00007  *
00008  * http://www.apache.org/licenses/LICENSE-2.0
00009  *
00010  * Unless required by applicable law or agreed to in writing, software
00011  * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
00012  * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
00013  * License for the specific language governing permissions and limitations under
00014  * the License.
00015  */
00016 
00017 package com.github.rosjava.rosjava_extras.hokuyo.scip20;
00018 
00019 import com.google.common.base.Preconditions;
00020 
00021 import com.github.rosjava.rosjava_extras.hokuyo.LaserScan;
00022 import com.github.rosjava.rosjava_extras.hokuyo.LaserScanListener;
00023 import com.github.rosjava.rosjava_extras.hokuyo.LaserScannerConfiguration;
00024 import com.github.rosjava.rosjava_extras.hokuyo.LaserScannerDevice;
00025 
00026 
00027 import org.apache.commons.logging.Log;
00028 import org.apache.commons.logging.LogFactory;
00029 import org.ros.exception.RosRuntimeException;
00030 import org.ros.message.Time;
00031 import org.ros.time.RemoteUptimeClock;
00032 import org.ros.time.TimeProvider;
00033 
00034 import java.io.BufferedInputStream;
00035 import java.io.BufferedOutputStream;
00036 import java.io.BufferedReader;
00037 import java.io.BufferedWriter;
00038 import java.io.IOException;
00039 import java.io.InputStream;
00040 import java.io.InputStreamReader;
00041 import java.io.OutputStream;
00042 import java.io.OutputStreamWriter;
00043 import java.nio.charset.Charset;
00044 import java.util.concurrent.Callable;
00045 
00049 public class Device implements LaserScannerDevice {
00050 
00051   private static final boolean DEBUG = false;
00052   private static final Log log = LogFactory.getLog(Device.class);
00053 
00054   private static final int STREAM_BUFFER_SIZE = 8192;
00055   private static final String EXPECTED_SENSOR_DIAGNOSTIC = "Sensor works well.";
00056   private static final double DRIFT_SENSITIVITY = 0.3;
00057   private static final double ERROR_REDUCTION_COEFFICIENT_SENSITIVITY = 0.3;
00058   private static final double LATENCY_FILTER_THRESHOLD = 1.05;
00059   private static final int LATENCY_FILTER_SAMPLE_SIZE = 10;
00060   private static final int CALIBRATION_SAMPLE_SIZE = 20;
00061   private static final int CALIBRATION_SAMPLING_DELAY_MILLIS = 500;
00062 
00063   private final BufferedInputStream bufferedInputStream;
00064   private final BufferedReader reader;
00065   private final BufferedWriter writer;
00066   private final LaserScannerConfiguration configuration;
00067   private final RemoteUptimeClock remoteUptimeClock;
00068 
00079   public Device(InputStream inputStream, OutputStream outputStream, TimeProvider epochTimeProvider) {
00080     bufferedInputStream = new BufferedInputStream(inputStream, STREAM_BUFFER_SIZE);
00081     reader =
00082         new BufferedReader(new InputStreamReader(bufferedInputStream, Charset.forName("US-ASCII")));
00083     writer =
00084         new BufferedWriter(new OutputStreamWriter(new BufferedOutputStream(outputStream,
00085             STREAM_BUFFER_SIZE), Charset.forName("US-ASCII")));
00086     remoteUptimeClock =
00087         RemoteUptimeClock.newDefault(epochTimeProvider, new Callable<Double>() {
00088           @Override
00089           public Double call() throws Exception {
00090             return (double) queryUptime();
00091           }
00092         }, DRIFT_SENSITIVITY, ERROR_REDUCTION_COEFFICIENT_SENSITIVITY, LATENCY_FILTER_SAMPLE_SIZE,
00093             LATENCY_FILTER_THRESHOLD);
00094     init();
00095     configuration = queryConfiguration();
00096   }
00097 
00107   private void init() {
00108     reset();
00109     String sensorDiagnostic = queryState().getSensorDiagnostic();
00110     Preconditions.checkState(sensorDiagnostic.equals(EXPECTED_SENSOR_DIAGNOSTIC),
00111         "Sensor diagnostic check failed: \"" + sensorDiagnostic + "\"");
00112     waitUntilReady();
00113     remoteUptimeClock.calibrate(CALIBRATION_SAMPLE_SIZE, CALIBRATION_SAMPLING_DELAY_MILLIS);
00114   }
00115 
00116   private void waitUntilReady() {
00117     boolean ready = false;
00118     while (!ready) {
00119       ready = true;
00120       write("MD0000076800001");
00121       try {
00122         checkMdmsStatus();
00123       } catch (MdmsException e) {
00124         if (DEBUG) {
00125           log.info("Sensor not ready.", e);
00126         }
00127         ready = false;
00128       }
00129       checkTerminator();
00130     }
00131     Preconditions.checkState(read().equals("MD0000076800000"));
00132     checkMdmsStatus();
00133     while (true) {
00134       String line = read(); // Data and checksum or terminating LF
00135       if (line.length() == 0) {
00136         break;
00137       }
00138       verifyChecksum(line);
00139     }
00140   }
00141 
00142   @Override
00143   public LaserScannerConfiguration getConfiguration() {
00144     return configuration;
00145   }
00146 
00147   private void write(String command) {
00148     Preconditions.checkArgument(!command.endsWith("\n"));
00149     try {
00150       writer.write(command + "\n");
00151       writer.flush();
00152       if (DEBUG) {
00153         log.info("Wrote: " + command);
00154       }
00155     } catch (IOException e) {
00156       throw new RosRuntimeException(e);
00157     }
00158     String echo = read();
00159     Preconditions.checkState(echo.equals(command),
00160         String.format("Echo does not match command: \"%s\" != \"%s\"", echo, command));
00161   }
00162 
00163   private void checkStatus() {
00164     String statusAndChecksum = read();
00165     String status = verifyChecksum(statusAndChecksum);
00166     Preconditions.checkState(status.equals("00"));
00167   }
00168 
00169   private void checkMdmsStatus() {
00170     String statusAndChecksum = read();
00171     String status = verifyChecksum(statusAndChecksum);
00172     // NOTE(damonkohler): It's not clear in the spec that both of these status
00173     // codes are valid.
00174     if (status.equals("00") || status.equals("99")) {
00175       return;
00176     }
00177     throw new MdmsException(status);
00178   }
00179 
00180   private void checkTmStatus() {
00181     String statusAndChecksum = read();
00182     String status = verifyChecksum(statusAndChecksum);
00183     if (!(status.equals("01") || status.equals("04"))) {
00184       return;
00185     }
00186     throw new TmException(status);
00187   }
00188 
00189   private String read() {
00190     String line;
00191     try {
00192       line = reader.readLine();
00193     } catch (IOException e) {
00194       throw new RosRuntimeException(e);
00195     }
00196     if (DEBUG) {
00197       log.info("Read: " + line);
00198     }
00199     return line;
00200   }
00201 
00202   private String verifyChecksum(String buffer) {
00203     Preconditions.checkArgument(buffer.length() > 0, "Empty buffer supplied to verifyChecksum().");
00204     String data = buffer.substring(0, buffer.length() - 1);
00205     char checksum = buffer.charAt(buffer.length() - 1);
00206     int sum = 0;
00207     for (int i = 0; i < data.length(); i++) {
00208       sum += data.charAt(i);
00209     }
00210     if ((sum & 63) + 0x30 == checksum) {
00211       return data;
00212     }
00213     throw new ChecksumException();
00214   }
00215 
00216   private void reset() {
00217     // Exit time adjust mode.
00218     write("TM2");
00219     checkTmStatus();
00220     checkTerminator();
00221 
00222     // Reset
00223     write("RS");
00224     checkStatus();
00225     checkTerminator();
00226 
00227     // Change to SCIP2.0 mode.
00228     write("SCIP2.0");
00229     try {
00230       checkStatus();
00231     } catch (IllegalStateException e) {
00232       // Not all devices support this command.
00233       if (DEBUG) {
00234         log.error("Switch to SCIP 2.0 failed.", e);
00235       }
00236     }
00237     checkTerminator();
00238 
00239     // Reset
00240     write("RS");
00241     checkStatus();
00242     checkTerminator();
00243   }
00244 
00245   private void checkTerminator() {
00246     Preconditions.checkState(read().length() == 0);
00247   }
00248 
00252   private long readTimestamp() {
00253     return Decoder.decodeValue(verifyChecksum(read()));
00254   }
00255 
00256   @Override
00257   public void startScanning(final LaserScanListener listener) {
00258     // TODO(damonkohler): Use NodeMainExecutor ExecutorService.
00259     new Thread() {
00260       @Override
00261       public void run() {
00262         while (true) {
00263           String command = "MD00000768000%02d";
00264           write(String.format(command, 99));
00265           checkMdmsStatus();
00266           checkTerminator();
00267           String scansRemaining = "99";
00268           while (!scansRemaining.equals("00")) {
00269             String commandEcho = read();
00270             scansRemaining = commandEcho.substring(commandEcho.length() - 2);
00271             checkMdmsStatus();
00272             long timestamp = readTimestamp();
00273             StringBuilder data = new StringBuilder();
00274             boolean checksumOk = true;
00275             while (true) {
00276               String line = read(); // Data and checksum or terminating LF
00277               if (line.length() == 0) {
00278                 if (checksumOk) {
00279                   try {
00280                     Time time = new Time(remoteUptimeClock.toLocalUptime(timestamp));
00281                     int[] ranges = Decoder.decodeValues(data.toString(), 3);
00282                     listener.onNewLaserScan(new LaserScan(time, ranges));
00283                   } catch (IllegalArgumentException e) {
00284                     log.error("Failed to decode scan data.", e);
00285                     break;
00286                   }
00287                 }
00288                 break;
00289               }
00290               try {
00291                 data.append(verifyChecksum(line));
00292               } catch (ChecksumException e) {
00293                 // NOTE(damonkohler): Even though this checksum is incorrect, we
00294                 // continue processing the scan data so that we don't lose
00295                 // synchronization. Once the complete laser scan has arrived, we
00296                 // will drop it and continue with the next incoming scan.
00297                 checksumOk = false;
00298                 log.error("Invalid checksum.", e);
00299               }
00300             }
00301           }
00302           remoteUptimeClock.update();
00303         }
00304       }
00305     }.start();
00306   }
00307 
00308   private String readAndStripSemicolon() {
00309     String buffer = read();
00310     Preconditions.checkState(buffer.charAt(buffer.length() - 2) == ';');
00311     return buffer.substring(0, buffer.length() - 2) + buffer.charAt(buffer.length() - 1);
00312   }
00313 
00314   private LaserScannerConfiguration queryConfiguration() {
00315     Configuration.Builder builder = new Configuration.Builder();
00316     write("PP");
00317     checkStatus();
00318     builder.parseModel(verifyChecksum(readAndStripSemicolon()));
00319     builder.parseMinimumMeasurement(verifyChecksum(readAndStripSemicolon()));
00320     builder.parseMaximumMeasurement(verifyChecksum(readAndStripSemicolon()));
00321     builder.parseTotalSteps(verifyChecksum(readAndStripSemicolon()));
00322     builder.parseFirstStep(verifyChecksum(readAndStripSemicolon()));
00323     builder.parseLastStep(verifyChecksum(readAndStripSemicolon()));
00324     builder.parseFrontStep(verifyChecksum(readAndStripSemicolon()));
00325     builder.parseStandardMotorSpeed(verifyChecksum(readAndStripSemicolon()));
00326     checkTerminator();
00327     return builder.build();
00328   }
00329 
00330   private State queryState() {
00331     State.Builder builder = new State.Builder();
00332     write("II");
00333     checkStatus();
00334     builder.parseModel(verifyChecksum(readAndStripSemicolon()));
00335     builder.parseLaserIlluminationState(verifyChecksum(readAndStripSemicolon()));
00336     builder.parseMotorSpeed(verifyChecksum(readAndStripSemicolon()));
00337     builder.parseMeasurementMode(verifyChecksum(readAndStripSemicolon()));
00338     builder.parseBitRate(verifyChecksum(readAndStripSemicolon()));
00339     builder.parseTimeStamp(verifyChecksum(readAndStripSemicolon()));
00340     builder.parseSensorDiagnostic(verifyChecksum(readAndStripSemicolon()));
00341     checkTerminator();
00342     return builder.build();
00343   }
00344 
00345   private long queryUptime() {
00346     // Enter time adjust mode
00347     write("TM0");
00348     checkTmStatus();
00349     checkTerminator();
00350     // Read the current time stamp
00351     write("TM1");
00352     checkTmStatus();
00353     // We assume that the communication lag is symmetrical meaning that the
00354     // sensor's time is exactly in between the start time and the current time.
00355     long timestamp = readTimestamp();
00356     checkTerminator();
00357     // Leave adjust mode
00358     write("TM2");
00359     checkTmStatus();
00360     checkTerminator();
00361     return timestamp;
00362   }
00363 
00364   @Override
00365   public void shutdown() {
00366     try {
00367       reader.close();
00368     } catch (IOException e) {
00369       // Ignore spurious shutdown errors.
00370       e.printStackTrace();
00371     }
00372     try {
00373       writer.close();
00374     } catch (IOException e) {
00375       // Ignore spurious shutdown errors.
00376       e.printStackTrace();
00377     }
00378   }
00379 }


rosjava_extras
Author(s): Damon Kohler
autogenerated on Thu Aug 27 2015 14:53:42