Java tutorial
/*- * Copyright 2009 Diamond Light Source Ltd. * * This file is part of GDA. * * GDA is free software: you can redistribute it and/or modify it under the * terms of the GNU General Public License version 3 as published by the Free * Software Foundation. * * GDA is distributed in the hope that it will be useful, but WITHOUT ANY * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more * details. * * You should have received a copy of the GNU General Public License along * with GDA. If not, see <http://www.gnu.org/licenses/>. */ package gda.device.scannable.keyence; import gda.data.PathConstructor; import gda.device.DeviceException; import gda.device.scannable.ScannableBase; import gda.factory.Configurable; import gda.factory.FactoryException; import gda.factory.Findable; import java.awt.image.BufferedImage; import java.io.ByteArrayInputStream; import java.io.File; import java.io.IOException; import java.net.ConnectException; import java.net.InetSocketAddress; import java.net.SocketTimeoutException; import java.net.UnknownHostException; import java.nio.ByteBuffer; import java.nio.CharBuffer; import java.nio.channels.SocketChannel; import java.nio.charset.Charset; import java.nio.charset.CharsetDecoder; import java.nio.charset.CharsetEncoder; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Date; import javax.imageio.ImageIO; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.util.StringUtils; /** * Provides Ethernet communications a Keyence Camera Controller * * Tested with a CV-3001P but should work with others as well. * Triggering is done in software. If you require hardware (external) triggering, * that would not be very hard to implement. */ public class Keyence extends ScannableBase implements Configurable, Findable, Runnable { private static final Logger logger = LoggerFactory.getLogger(Keyence.class); private String host = "localhost"; private int commandPort = 8500; private int socketTimeOut = 2000; private ArrayList<String> startupCommands = new ArrayList<String>(); private static Charset charset = Charset.forName("US-ASCII"); private static CharsetEncoder encoder = charset.newEncoder(); private static CharsetDecoder decoder = charset.newDecoder(); private SimpleDateFormat dateFormat = new SimpleDateFormat("yyyyMMdd-HH:mm:ss"); private String imageFormat = "png"; //Object used to grant a thread exclusive access to the socket, bb and connected private final Object socketAccessLock = new Object(); private ByteBuffer bb = ByteBuffer.allocate(4096); private SocketChannel socketChannel; private boolean connected = false; /** * */ public Keyence() { inputNames = new String[] {}; } @Override public void configure() throws FactoryException { if (isConfigured()) { return; } try { connect(); } catch (DeviceException e) { throw new FactoryException("Error connecting to Keyence device", e); } uk.ac.gda.util.ThreadManager.getThread(this, getName() + " buffer emptier").start(); setConfigured(true); } /** * Set up initial connections to socket and wrap the stream in buffered reader and writer. * @throws DeviceException */ public void connect() throws DeviceException { try { synchronized (socketAccessLock) { if (!isConnected()) { InetSocketAddress inetAddr = new InetSocketAddress(host, commandPort); socketChannel = SocketChannel.open(); socketChannel.connect(inetAddr); socketChannel.socket().setSoTimeout(socketTimeOut); socketChannel.configureBlocking(true); socketChannel.finishConnect(); cleanPipe(); doStartupScript(); connected = true; } } } catch (UnknownHostException ex) { // this could be fatal as reconnect attempts are futile. logger.error(getName() + ": connect: " + ex.getMessage()); } catch (ConnectException ex) { logger.debug(getName() + ": connect: " + ex.getMessage()); } catch (IOException ix) { logger.error(getName() + ": connect: " + ix.getMessage()); } } @Override public void reconfigure() throws FactoryException { try { reconnect(); } catch (DeviceException e) { throw new FactoryException("Error reconfiguring keyence device", e); } } /** * Tidy existing socket streams and try to connect them again within the thread. This method is synchronized as both * the main thread and run thread use this method. * @throws DeviceException */ public synchronized void reconnect() throws DeviceException { try { close(); } catch (DeviceException e) { // do nothing for now } connect(); } @Override public void close() throws DeviceException { synchronized (socketAccessLock) { connected = false; if (socketChannel != null) { try { socketChannel.close(); socketChannel = null; } catch (IOException ex) { throw new DeviceException(ex.getMessage(), ex); } } } } /** * Returns the state of the socket connection * * @return true if connected */ public boolean isConnected() { return connected; } /** * Send command to the server. * * @param msg * an unterminated command * @return the reply string. * @throws DeviceException */ public String processCommand(String msg) throws DeviceException { String command = msg + '\r'; String reply = null; logger.debug(getName() + ": sent command: |" + msg + "|"); synchronized (socketAccessLock) { if (!isConnected()) { throw new DeviceException("not connected"); } try { cleanPipe(); socketChannel.write(encoder.encode(CharBuffer.wrap(command))); bb.clear(); socketChannel.read(bb); bb.flip(); reply = decoder.decode(bb).toString(); logger.debug(getName() + ": got reply: |" + reply.trim() + "|"); } catch (SocketTimeoutException ex) { throw new DeviceException("sendCommand read timeout " + ex.getMessage(), ex); } catch (IOException ex) { connected = false; throw new DeviceException("sendCommand: " + ex.getMessage(), ex); } } return reply; } private String writeImage(BufferedImage image) throws IOException { String fileName = PathConstructor.createFromDefaultProperty() + "/" + getName() + "-" + dateFormat.format(new Date()) + "." + imageFormat; File imageFile = new File(fileName); ImageIO.write(image, imageFormat, imageFile); return fileName; } /** * * @return the filename * @throws IOException * @throws DeviceException */ public String saveLastMeasurementImage() throws IOException, DeviceException { return writeImage(getLastMeasurementImage()); } /** * * @return the camera shot of the last measurement * @throws DeviceException * @throws IOException */ public BufferedImage getLastMeasurementImage() throws DeviceException, IOException { byte[] image = (byte[]) processImageRequest("BR,CM,1,0,NW", 5)[5]; return ImageIO.read(new ByteArrayInputStream(image)); } /** * * @throws IOException * @throws DeviceException */ public void saveScreenShot(String fileName) throws IOException, DeviceException { File imageFile = new File(fileName); ImageIO.write(getScreenShot(), imageFormat, imageFile); } /** * * @return the filename * @throws IOException * @throws DeviceException */ public String saveScreenShot() throws IOException, DeviceException { return writeImage(getScreenShot()); } /** * * @return a screenshot * @throws DeviceException * @throws IOException */ public BufferedImage getScreenShot() throws DeviceException, IOException { byte[] image = (byte[]) processImageRequest("BC,CM", 2)[2]; return ImageIO.read(new ByteArrayInputStream(image)); } /** * Send command to the server. * * @param msg * an unterminated command * @param expectedReplyItems * @return the reply string. * @throws DeviceException */ public Object[] processImageRequest(String msg, int expectedReplyItems) throws DeviceException { if (expectedReplyItems < 2) throw new IllegalArgumentException("need at least two values for images (length definition and data)"); String command = msg + '\r'; Object reply[] = new Object[expectedReplyItems + 1]; logger.debug(getName() + ": sent command: " + msg); synchronized (socketAccessLock) { try { if (!isConnected()) { throw new DeviceException("not connected"); } cleanPipe(); socketChannel.write(encoder.encode(CharBuffer.wrap(command))); ByteBuffer singleByte = ByteBuffer.allocate(1); StringBuilder sb = new StringBuilder(); int argCounter = 0; while (argCounter < expectedReplyItems) { singleByte.clear(); socketChannel.socket().setSoTimeout(socketTimeOut); socketChannel.configureBlocking(true); while (singleByte.position() == 0) socketChannel.read(singleByte); singleByte.flip(); String c = decoder.decode(singleByte).toString(); logger.debug(c.toString()); if (c.equals(",")) { reply[argCounter] = sb.toString(); sb = new StringBuilder(); argCounter++; } else if (c.equals("\r")) { throw new DeviceException( "sendCommand: not enough data for image received - suspect an error"); } else { sb.append(c); } } int imageLength = Integer.parseInt(reply[expectedReplyItems - 1].toString()); byte[] imageData = new byte[imageLength]; ByteBuffer bybu = ByteBuffer.wrap(imageData); while (bybu.remaining() != 0) { socketChannel.read(bybu); } reply[expectedReplyItems] = imageData; } catch (SocketTimeoutException ex) { throw new DeviceException("sendCommand read timeout " + ex.getMessage(), ex); } catch (IOException ex) { // treat as fatal connected = false; throw new DeviceException("sendCommand: " + ex.getMessage(), ex); } } return reply; } private void doStartupScript() throws DeviceException { for (String command : startupCommands) { String reply = processCommand(command); if (reply.startsWith("ER")) { logger.error("error sending command " + command + " received error reply from device: " + reply); } } } /** * Set the keyence host * * @param host */ public void setHost(String host) { this.host = host; } /** * @return the keyence host name */ public String getHost() { return host; } /** * Set the command port * * @param port */ public void setPort(int port) { this.commandPort = port; } /** * @return the command port */ public int getPort() { return commandPort; } /** * @param startupCommands */ public void setStartupCommands(ArrayList<String> startupCommands) { this.startupCommands = startupCommands; } /** * @return an array list of startup commands to be processed by da.server on startup */ public ArrayList<String> getStartupCommands() { return startupCommands; } @Override public double[] getPosition() throws DeviceException { String reply = processCommand("T1"); String[] posStrings = reply.split(","); if (!"T1".equals(posStrings[0])) { throw new DeviceException( "communication or measurement error (device not in run mode?): " + StringUtils.quote(reply)); } int positionsRead = posStrings.length - 1; if (extraNames.length > 0 && extraNames.length != positionsRead) throw new DeviceException( "unexpected number of measurements, are we running the right program? expected <" + extraNames.length + "> got <" + positionsRead + ">. Reply was " + StringUtils.quote(reply.replace("\n", "\\n").replace("\r", "\\r"))); double[] positions = new double[positionsRead]; for (int i = 1; i < posStrings.length; i++) { positions[i - 1] = Double.parseDouble(posStrings[i]); } return positions; } @Override public void asynchronousMoveTo(Object position) throws DeviceException { } @Override public boolean isBusy() throws DeviceException { return false; } @Override public void setInputNames(String[] names) { } private void cleanPipe() throws IOException { synchronized (socketAccessLock) { if (isConnected()) { try { socketChannel.configureBlocking(false); while (socketChannel.read(bb) > 0) { bb.clear(); } socketChannel.configureBlocking(true); } catch (IOException ex) { try { close(); } catch (DeviceException e) { // we know that already } throw ex; } } } } @Override public void run() { try { runKeyence(); } catch (InterruptedException e) { logger.error("Thread interrupted while running Keyence {}", getName(), e); Thread.currentThread().interrupt(); } } private void runKeyence() throws InterruptedException { while (true) { Thread.sleep(21987); try { cleanPipe(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } if (isConfigured() && !isConnected()) { try { reconnect(); } catch (DeviceException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } } }