ste.cameracontrol.CameraController.java Source code

Java tutorial

Introduction

Here is the source code for ste.cameracontrol.CameraController.java

Source

/*
 * 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;
            }
        }
    }

}