Java tutorial
/* * cameracontrol * Copyright (C) 2010 Stefano Fornari * * This program is free software; you can redistribute it and/or modify it under * the terms of the GNU Affero General Public License version 3 as published by * the Free Software Foundation with the addition of the following permission * added to Section 15 as permitted in Section 7(a): FOR ANY PART OF THE COVERED * WORK IN WHICH THE COPYRIGHT IS OWNED BY Stefano Fornari, Stefano Fornari * DISCLAIMS THE WARRANTY OF NON INFRINGEMENT OF THIRD PARTY RIGHTS. * * This program 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 Affero General Public License * along with this program; if not, see http://www.gnu.org/licenses or write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * MA 02110-1301 USA. */ package ste.cameracontrol; import java.util.ArrayList; import java.util.List; import ch.ntb.usb.Device; import ch.ntb.usb.USB; import ch.ntb.usb.UsbDevice; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.util.HashSet; import java.util.Set; import org.apache.commons.io.FileUtils; import org.apache.commons.lang.StringUtils; import org.apache.commons.io.FilenameUtils; import ste.ptp.DeviceInfo; import ste.ptp.OutputStreamData; import ste.ptp.PTPException; import ste.ptp.eos.EosEvent; import ste.ptp.eos.EosEventConstants; import ste.ptp.eos.EosEventFormat; import ste.ptp.eos.EosInitiator; /** * This class is the centralized controlled of the camera. It controls all * aspects of the interaction with the camera, from connectivity to picture * download and other controlling functions. * * CameraController implements the Singleton design patter, so that you can have * only one instance of the controller in your program. * * @author ste */ public class CameraController implements Runnable { private final long POLLING_PERIOD = 50; private CameraConnection connection; private ArrayList<CameraListener> listeners; private boolean cameraMonitorActive; private Device camera; private boolean cameraConnected; private Configuration configuration; // // I need to find a better name for this... // private EosInitiator device; /** * The singleton */ private static final CameraController instance = new CameraController(); /** * Creates a new CameraController */ protected CameraController() { listeners = new ArrayList<CameraListener>(); cameraMonitorActive = false; } /** * Creates a new CameraController with the given configuration * * @param configuration the configuration object that contains the configuration * * @throws IllegalArgumentException if configuration is null */ protected CameraController(Configuration configuration) { this(); initialize(configuration); } /** * Returns the singleton instance of CameraController * * @return the the singleton instance of CameraControllers */ public static CameraController getInstance() { return instance; } /** * Initialize the controller with the given configuration * * @param configuration */ public void initialize(Configuration configuration) { if (configuration == null) { throw new IllegalArgumentException("configuration cannot be null"); } this.configuration = configuration; cameraCleanup(); checkCamera(); } /** * initialize the controller with default configuration */ public void initialize() { initialize(new Configuration()); } /** * Returns the configuration object used by this CameraController * * @return the configuration object used by this CameraController */ public Configuration getConfiguration() { return configuration; } /** * Checks the connection with the camera. * * @return true if the camera is connected, false otherwise */ public boolean isConnected() { return cameraConnected; } /** * Adds a CameraControllerListener to the list of listeners to be notified * of camera events. * * @param listener the listener to add - NOT NULL * * @trhows IllegalArgumentException if listener is null * */ public synchronized void addCameraListener(CameraListener listener) { if (listener == null) { throw new IllegalArgumentException("listener cannot be null"); } listeners.add(listener); } /** * Starts the camera detecting monitor in a new thread (if not already * started). */ public synchronized void startCameraMonitor() { setConnected(); if (!cameraMonitorActive) { new Thread(this).start(); } } /** * Stops the camera detecting monitor. */ public synchronized void stopCameraMonior() { cameraMonitorActive = false; } /** * Retrieves and prints on the standard output the device capabilities of * the camera. * * @throws PTPException in case of errors */ public void devinfo() throws PTPException { sanityCheck(); // // If the camera is not found we should not be here (an exception is // thrown). // DeviceInfo info = device.getDeviceInfo(); info.dump(System.out); // // no session to close! // } /** * Retrieves camera's events and dispatches them to the listeners. * * @throws PTPException in case of not recoverable errors */ public void dumpEvents() throws PTPException { sanityCheck(); List<EosEvent> events = device.checkEvents(); System.out.println("Events:"); if (events.isEmpty()) { System.out.println("no events found"); } for (EosEvent event : events) { System.out.println(EosEventFormat.format(event)); } } /** * Command the camera to take a picture. If an error occurs, a PTPException * is thrown. * * @throws PTPException in case of errors */ public void shoot() throws PTPException { sanityCheck(); device.initiateCapture(0, 0); device.checkEvents(); } /** * Command the camera to take a picture and download the foto(s) from the * camera. For now fotos are are saved with the name given by the camera * under a configured directory. It may change in the future. * If an error occurs, a PTPException is thrown. * * @return the Photo made available after the shot * * @throws PTPException in case of errors */ public Photo[] shootAndDownload() throws PTPException { sanityCheck(); device.initiateCapture(0, 0); ArrayList<EosEvent> objects = new ArrayList<EosEvent>(); for (int i = 0; i < 5; ++i) { List<EosEvent> events = device.checkEvents(); for (EosEvent e : events) { if (e.getCode() == EosEventConstants.EosEventObjectAddedEx) { objects.add(e); } } // // We wait for a while to give give some break to the camera while // preparing the images // try { Thread.sleep(1000); } catch (Exception e) { // // What to do??? // } } Set<Photo> photos = new HashSet<Photo>(); Photo photo = null; for (EosEvent e : objects) { String name = FilenameUtils.getBaseName(e.getStringParam(6)); for (Photo p : photos) { if (p.getName().equals(name)) { photo = p; break; } } if (photo == null) { photo = new Photo(name); } downloadPhoto(e.getIntParam(1), e.getIntParam(5), photo, !StringUtils.endsWithIgnoreCase(e.getStringParam(6), ".jpg")); photos.add(photo); } return photos.toArray(new Photo[photos.size()]); } /** * Downloads a photo from the camera given its id, size and Photo object. * The photo object is loaded on the jpeg or raw data buffer based on the * value of the raw parameter. * * @param id object id on the camera * @param size size of the object * @param photo Photo object where to load the item * @param raw is the object the raw image (true) or jpeg(false)? * * @return an Photo object with the downloaded photo * * @throws PTPException in case of communication or protocol errors */ public void downloadPhoto(int id, int size, Photo photo, boolean raw) throws PTPException { sanityCheck(); FileOutputStream file = null; try { ByteArrayOutputStream buf = new ByteArrayOutputStream(size); OutputStreamData data = new OutputStreamData(buf, device); device.getPartialObject(id, 0, size, data); device.transferComplete(id); if (raw) { photo.setRawData(buf.toByteArray()); } else { photo.setJpegData(buf.toByteArray()); } } catch (Exception e) { throw new PTPException("Unable to store the object: " + e.getMessage(), e); } finally { if (file != null) { try { file.close(); } catch (Exception e) { } } } } public void startCamera() throws PTPException { startCamera(true); } /** * Starts the connection with the camera. If session is true, a new session * is established. If session is true, but a session has been already * established, a BusyException is thrown. * * @param session true if a new session should be established * * @return the communication endpoint * * @throws PTPException in case of PTP errors * @throws cameraBusyException in case a session is still active * @throws CameraNotAvailableException in case no cameras are connected */ public void startCamera(boolean session) throws PTPException { checkCamera(); if (camera == null) { // // No cameras found // throw new PTPException("Camera not available"); } if (device == null) { device = new EosInitiator(camera); } if (session) { device.openSession(); } } public void releaseCamera() { try { device.closeSession(); } catch (Exception e) { // ignore everything! } cameraCleanup(); } /** * Save the given Photo in the controller's configured directory * * @param photo - NULL * * @throws IOException in case of IO errors */ public void savePhoto(Photo photo) throws IOException { if (photo.getJpegData() != null) { File f = new File(configuration.getImageDir(), photo.getName() + ".JPG"); FileUtils.writeByteArrayToFile(f, photo.getJpegData()); } if (photo.getRawData() != null) { File f = new File(configuration.getImageDir(), photo.getName() + ".CR2"); FileUtils.writeByteArrayToFile(f, photo.getRawData()); } } /** * Runs the camera detecting monitor (required by Runnable) */ @Override public void run() { cameraMonitorActive = true; while (cameraMonitorActive) { boolean cameraConnectedOld = cameraConnected; checkCamera(); if (cameraConnectedOld != cameraConnected) { setConnected(); } try { Thread.sleep(POLLING_PERIOD); } catch (Exception e) { break; } } } // // SPIKE // public void shutdown() { cameraCleanup(); } // --------------------------------------------------------- private methods /** * Used to set the connection status. Setting the connection status is * considered a status change, therefore invokes the registered * CameraListeners (if any). * * @param status the status of the connection: true if the camera is * connected false otherwise. */ private synchronized void setConnected() { if (listeners == null) { return; } for (CameraListener listener : listeners) { if (cameraConnected) { listener.cameraConnected(camera); } else { listener.cameraDisconnected(camera); } } } private synchronized void checkCamera() { UsbDevice dev = connection.findCamera(); cameraConnected = (dev != null); if (dev == null) { cameraCleanup(); } else { camera = USB.getDevice(dev.getDescriptor().getVendorId(), dev.getDescriptor().getProductId()); cameraConnected = true; } } private void sanityCheck() throws PTPException { if (device == null) { throw new CameraNotAvailableException(); } } private void cameraCleanup() { connection = new CameraConnection(); camera = null; cameraConnected = false; if (device != null) { try { device.close(); } catch (Exception ignore) { } finally { device = null; } } } }