/**
*
* Copyright (c) 2009-2016 Freedomotic team http://freedomotic.com
*
* This file is part of Freedomotic
*
* This Program is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License as published by the Free Software
* Foundation; either version 2, or (at your option) any later version.
*
* 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 General Public License along with
* Freedomotic; see the file COPYING. If not, see
* <http://www.gnu.org/licenses/>.
*/
package com.freedomotic.plugins.devices.ipcameramotion;
import com.freedomotic.api.EventTemplate;
import com.freedomotic.api.Protocol;
import com.freedomotic.events.GenericEvent;
import com.freedomotic.events.ProtocolRead;
import com.freedomotic.exceptions.PluginStartupException;
import com.freedomotic.exceptions.UnableToExecuteException;
import com.freedomotic.reactions.Command;
import com.freedomotic.settings.Info;
import com.github.sarxos.webcam.*;
import com.github.sarxos.webcam.ds.ipcam.*;
import java.awt.Dimension;
import java.awt.GridLayout;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.net.MalformedURLException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import javax.imageio.ImageIO;
import javax.swing.BorderFactory;
import javax.swing.JFrame;
import javax.swing.border.Border;
import java.net.URL;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class IpCameraMotion
extends Protocol {
private static final Logger LOG = LoggerFactory.getLogger(IpCameraMotion.class.getName());
private final int PANEL_WIDTH = configuration.getIntProperty("panel-width", 256);
private final int PANEL_HEIGHT = configuration.getIntProperty("panel-height", 144);
private final int DETECTOR_INTERVAL = configuration.getIntProperty("detector-interval", 2000);
private final String FPS = configuration.getStringProperty("fps", "0.5");
private List<WebcamPanel> panels;
private List<WebcamMotionDetector> detectors;
private ProtocolRead event;
static {
Webcam.setDriver(new IpCamDriver(new IpCamStorage(Info.PATHS.PATH_DEVICES_FOLDER + "/ipcamera-motion/" + "cameras.xml")));
}
JFrame gui = null;
public IpCameraMotion() {
super("IpCamera Motion", "/ipcamera-motion/ipcamera-motion-manifest.xml");
setPollingWait(-1);
}
@Override
protected void onShowGui() {
bindGuiToPlugin(gui);
}
@Override
protected void onHideGui() {
setDescription("IpCamera Motion");
}
@Override
protected void onRun() {
new DetectMotion();
}
@Override
protected void onStart() throws PluginStartupException {
gui = new JFrame("IpCamera Motion");
panels = new ArrayList<WebcamPanel>();
detectors = new ArrayList<WebcamMotionDetector>();
try {
loadCameras();
} catch (MalformedURLException ex) {
// TODO if there are no cameras available stop plugin
throw new PluginStartupException("Error loading cameras " + ex.getMessage(), ex);
}
LOG.info("IpCamera Motion plugin started");
}
@Override
protected void onStop() {
for (WebcamMotionDetector detector : detectors) {
detector.stop();
}
for (WebcamPanel panel : panels) {
panel.stop();
}
for (Webcam webcam : Webcam.getWebcams()) {
webcam.close();
}
LOG.info("IpCamera Motion plugin stopped");
}
@Override
protected void onCommand(Command c)
throws IOException, UnableToExecuteException {
switch (c.getProperty("command")) {
case "CAPTURE-IMAGE":
LOG.debug("Command capture image " + c.getProperty("camera-name"));
String capturedImagePath = captureImage(c.getProperty("camera-name"));
break;
case "CAPTURE-IMAGE-NOTIFY-MAIL":
LOG.debug("Command capture image and notify by mail " + c.getProperty("camera-name"));
capturedImagePath = captureImage(c.getProperty("camera-name"));
// send a command to Mailer plugin
Command mailerCommand = new Command();
mailerCommand.setName("Mailer notification");
mailerCommand.setReceiver("app.actuators.messaging.mail.in");
mailerCommand.setProperty("to", c.getProperty("to-address"));
mailerCommand.setProperty("message", c.getProperty("message"));
mailerCommand.setProperty("subject", "Notification from IpCamera Motion plugin");
mailerCommand.setProperty("attachment", capturedImagePath);
notifyCommand(mailerCommand);
break;
case "CAPTURE-IMAGE-NOTIFY-TELEGRAM":
LOG.debug("Command capture image and notify by Telegram " + c.getProperty("camera-name"));
capturedImagePath = captureImage(c.getProperty("camera-name"));
// send a command to Telegram Bot plugin
Command telegramCommand = new Command();
telegramCommand.setName("Telegram notification");
telegramCommand.setReceiver("app.actuators.social.telegram-bot.in");
telegramCommand.setProperty("message", c.getProperty("message"));
telegramCommand.setProperty("attachment", capturedImagePath);
notifyCommand(telegramCommand);
break;
}
LOG.info("IpCamera Motion plugin receives a command called {} with parameters {}", c.getName(), c.getProperties().toString());
}
@Override
protected boolean canExecute(Command c) {
//don't mind this method for now
throw new UnsupportedOperationException("Not supported yet.");
}
@Override
protected void onEvent(EventTemplate event) {
//don't mind this method for now
throw new UnsupportedOperationException("Not supported yet.");
}
/**
* Loads the list of cameras from camera.xml file.
*
* @throws MalformedURLException
*/
private void loadCameras() throws MalformedURLException {
gui.setLayout(new GridLayout(0, 3, 1, 1));
for (Webcam webcam : Webcam.getWebcams()) {
// TODO set panel dimensions as config parameters
WebcamPanel panel = new WebcamPanel(webcam, new Dimension(PANEL_WIDTH, PANEL_HEIGHT), false);
panel.setFitArea(true); // best fit into panel area (do not break image proportions)
panel.setFPSLimited(true);
panel.setFPSLimit(Double.valueOf(FPS)); // 0.5 FPS = 1 frame per 2 seconds
Border title = BorderFactory.createTitledBorder(webcam.getName());
panel.setBorder(title);
gui.add(panel);
panels.add(panel);
WebcamDevice device = webcam.getDevice();
URL url = ((IpCamDevice) device).getURL();
final Webcam refWebcam = webcam;
final WebcamPanel refPanel = panel;
// open webcam and start panel in parallel, by doing this in new thread GUI will
// not be blocked for the time when webcam is being initialized
// webcam will be open in asynchronouns mode:
// webcam.open() = synchronouse mode, getImage() is blocking
// webcam.open(true) = asynchronous mode, getImage() is non-blocking (return immediately, but may return old image)
Thread t = new Thread() {
@Override
public void run() {
refWebcam.open(true); // open in asynchronous mode, do nothing if already open
refPanel.start(); // start motion detector
}
};
t.setDaemon(true);
t.start();
}
gui.pack();
}
/**
* This class implements a listener for Webcam motion
*
*/
private class DetectMotion implements WebcamMotionListener {
public DetectMotion() {
for (Webcam webcam : Webcam.getWebcams()) {
WebcamMotionDetector detector = new WebcamMotionDetector(webcam);
// 2000 = 1 check per 2 seconds, 0.5 FPS which is the same value as in panel
detector.setInterval(DETECTOR_INTERVAL);
detector.addMotionListener(this);
detectors.add(detector);
final Webcam refWebcam = webcam;
final WebcamMotionDetector refDetector = detector;
// open webcam and start motion detector in parallel, by doing this in new thread GUI will
// not be blocked for the time when webcam is being initialized
Thread t = new Thread() {
@Override
public void run() {
refWebcam.open(true); // asynchronous mode, do nothing if already open
refDetector.start(); // start motion detector
}
};
t.setDaemon(true);
t.start();
}
}
@Override
public void motionDetected(WebcamMotionEvent wme) {
Webcam webcam = ((WebcamMotionDetector) wme.getSource()).getWebcam();
IpCamDevice device = (IpCamDevice) webcam.getDevice(); // in case whe IP camera driver is used
//here create and send the event related to motion detection
GenericEvent event = new GenericEvent(this);
event.setDestination("app.event.sensor.video.motion");
event.getPayload().addStatement("camera-name", webcam.getName());
event.getPayload().addStatement("motion-area", String.valueOf(wme.getArea())); //percentage of complete image pixels area that has been changed between two consecutive images
event.getPayload().addStatement("center-of-gravity", wme.getCog().toString()); //center-of-gravity (the center of motion area)
notifyEvent(event);
LOG.info("IpCamera {} detected motion!", webcam.getName());
}
}
/**
* Captures an image when motion is detected.
*
* @param cameraName camera name
* @return the absolute path of the captured image
*/
private String captureImage(String cameraName) {
Webcam webcam = getCameraByName(cameraName);
File capturedImage = null;
if (webcam != null) {
webcam.open(true);
BufferedImage image = webcam.getImage();
SimpleDateFormat dateFormat = new SimpleDateFormat("dd-MM-yy_HH-mm-ss");
String dateStr = dateFormat.format(new Date());
capturedImage = new File(Info.PATHS.PATH_DEVICES_FOLDER + "/ipcamera-motion/data/captured-images/" + webcam.getName() + "_" + dateStr + ".jpg");
// JPG is circa 10x smaller than the same image in PNG
try {
ImageIO.write(image, "JPG", capturedImage);
} catch (IOException ex) {
LOG.error("Error during image capture from camera " + cameraName + " for: " + ex.getMessage());
return null;
}
}
return capturedImage.getAbsolutePath();
}
/**
* This class returns a webcam object from its name
*
* @param name
* @return a Webcam object
*/
public Webcam getCameraByName(String name) {
for (Webcam webcam : Webcam.getWebcams()) {
if (webcam.getName().equalsIgnoreCase(name)) {
return webcam;
}
}
return null;
}
}