jobs.WarningMonitorJob.java Source code

Java tutorial

Introduction

Here is the source code for jobs.WarningMonitorJob.java

Source

/**
 * The GardenDroid, a self monitoring and reporting mini-greenhouse.
 *
 * Copyright (c) 2010-2011 Lee Clarke
 *
 * LICENSE:
 *
 * This file is part of TheGardenDroid (https://github.com/leeclarke/TheGardenDroid).
 *
 * TheGardenDroid 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 of the License, or (at your option) any
 * later version.
 *
 * TheGardenDroid 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 TheGardenDroid.  If not, see
 * <http://www.gnu.org/licenses/>.
 *
 */
package jobs;

import java.util.Calendar;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.concurrent.Future;

import org.apache.commons.mail.EmailException;
import org.apache.commons.mail.SimpleEmail;
import org.apache.log4j.Logger;

import models.AlertType;
import models.LogData;
import models.ObservationData;
import models.Options;
import models.Plant;
import models.PlantData;
import models.SensorData;
import models.SensorType;
import models.TempSensorData;
import models.UserDataType;
import models.Warning;
import play.Play;
import play.jobs.Every;
import play.jobs.Job;
import play.libs.Mail;

@Every("1h")
public class WarningMonitorJob extends Job {
    static Logger logger = Logger.getLogger(WarningMonitorJob.class);

    /** 
     * Job responsible for monitoring status of the GardenDroid and logging and sending out notifications if expected.
     * @see play.jobs.Job#doJob()
     */
    @Override
    public void doJob() throws Exception {
        logger.info("Starting WarningMonitorJob");
        super.doJob();
        Options options = Options.find("order by id").first();
        //See if should be active 

        boolean isOperational = verifyDroidIsOperational(options.remoteAliveCheckMins);
        if (!isOperational) {
            processAlert(options, AlertType.DROID_DOWN);
        } else { //Ensure that no Warnings are active.
            Warning.deactivateType(AlertType.DROID_DOWN);
        }

        int tempThresholdCheck = checkForTempThresholdsAlert(options);
        if (tempThresholdCheck == 1) {
            processAlert(options, AlertType.HIGH_TEMP_THRESHOLD);
        } else if (tempThresholdCheck == -1) {
            processAlert(options, AlertType.LOW_TEMP_THRESHOLD);
        } else { // OK returned, deactivate any previous Warnings.
            Warning.deactivateType(AlertType.HIGH_TEMP_THRESHOLD);
            Warning.deactivateType(AlertType.LOW_TEMP_THRESHOLD);
        }

        checkActivePlantings(options);
    }

    /**
     * Sends Alert assuming rule checks pass, Might want to consider customizing the message in the future to contain more info.
     * @param options
     * @param aType
     */
    protected boolean processAlert(Options options, AlertType aType) {
        boolean emailSent = false;
        if (isAlertTypeActive(aType, options)) {
            try {
                if (options.enableWarningNotification) {
                    sendNotification(options, aType.subject, aType.message);
                    logger.debug("Email Notification Sent");
                    emailSent = true;
                } else {
                    logger.warn("Email Notifications currently disabled.");
                }
                new Warning(aType.message, true, aType).save();
            } catch (EmailException e) {
                logger.error("Email Alert failed.", e);
                new LogData(new Date(), "Failed to send email address to email address: " + options.email).save();
            }
        }
        return emailSent;
    }

    /**
     * If the GardenDroid hasn't logged any data in over an hour then something is wrong!
     * @return
     */
    public boolean verifyDroidIsOperational(Integer aliveMins) {
        List<SensorData> latestData = SensorData.getSensorData(0, 1);
        if (latestData.size() > 0) {
            SensorData lastData = latestData.get(0);
            Calendar now = Calendar.getInstance();
            now.add(Calendar.MINUTE, -(aliveMins));
            if (now.getTimeInMillis() < lastData.dateTime.getTime()) {
                return true;
            }
        }
        return false;
    }

    /**
     * Just sends the notifications, all business process and error checking should happen elsewhere.
     * @param options
     * @param alertMessage
     * @throws EmailException
     */
    public void sendNotification(Options options, String subject, String alertMessage) throws EmailException {
        SimpleEmail email = new SimpleEmail();
        email.setFrom(Play.configuration.getProperty("mail.smtp.user"));
        email.addTo(options.email);
        email.setSubject(subject);
        email.setMsg(alertMessage);
        Mail.send(email);
        logger.info("Email Alert sent to address:" + options.email + " subject:" + subject);
    }

    /**
     * Using defaults or values set in options; check most recent temp readings for over/under threshold state.
     * @param options
     * @return - true value indicated Alert status.
     */
    public int checkForTempThresholdsAlert(Options options) {
        int status = 0;
        TempSensorData tempReading = TempSensorData.getCurrentReading();
        logger.warn("current==" + tempReading);
        logger.warn("options==" + options);
        if (options.enableHighTempWarning && tempReading.tempF >= options.highTempThreshold) {
            status = 1;
        } else if (options.enableLowTempWarning && tempReading.tempF <= options.lowTempThreshold) {
            status = -1;
        }
        return status;
    }

    /**
     * Using defaults or values set in options; check most recent temp readings for over/under threshold state.
     * @param options
     * @return - true value indicated Alert status.
     */
    public int checkForTempThresholdsAlert(PlantData plant) {
        int status = 0;
        TempSensorData tempReading = TempSensorData.getCurrentReading();
        logger.warn("current==" + tempReading);
        logger.warn("planting==" + plant);
        if (tempReading.tempF >= plant.highTemp) {
            status = 1;
        } else if (tempReading.tempF <= plant.lowTemp) {
            status = -1;
        }
        return status;
    }

    /**
     * Check alert table to see if there is an active Warning of the given Alert type and that they are not older then the stale hours limit. 
     * Active warnings are considered stale if they are older then the configured Stale hours options which defaults to 12.
     * @param aType
     * @return true if no active warnings or the active warning is over ignore limit time.
     */
    public boolean isAlertTypeActive(AlertType aType, Options options) {
        List<Warning> resp = Warning.getActive(aType);
        if (resp.size() > 0) {
            //See if warning has gone stale.
            Warning mostRecent = resp.get(0);
            Calendar now = Calendar.getInstance();
            now.add(Calendar.HOUR, -1 * options.snoozeActiveWarnings_hours);
            Calendar lastDate = Calendar.getInstance();
            lastDate.setTime(mostRecent.dateTime);
            if (now.after(lastDate))
                return true;
        }
        return false;
    }

    /**
     * All checks done on specific plants under the GardenDroid's care will be managed from here.
     * @param options
     * @throws EmailException 
     */
    public boolean checkActivePlantings(Options options) {
        boolean alertDetected = false;
        if (options.enablePlantedWarnings) {
            StringBuilder sb = new StringBuilder();

            List<Plant> plantings = Plant.getActivePlantings();
            for (Plant plant : plantings) {
                //Check Temp Thresholds
                logger.warn("### Checking on plant: " + plant);
                int tempWarn = checkForTempThresholdsAlert(plant.plantData);
                if (tempWarn == 1) {
                    sb.append("\n ").append(plant.name)
                            .append(": Tempratures have exceeded the Plants indicated tolerance range.");
                } else if (tempWarn == -1) {
                    sb.append("\n ").append(plant.name)
                            .append(": Tempratures have dropped below the Plants indicated tolerance range.");
                }
                //check watering
                HashMap<SensorType, SensorData> latest = SensorData.retrieveLatestSensorData();
                boolean sensorState = false;
                boolean observationState = false;
                int waterDays = plant.plantData.waterFreqDays;
                if (latest.containsKey(SensorType.WATER_IRRIGATION)
                        && latest.get(SensorType.WATER_IRRIGATION) != null) {

                    SensorData water = latest.get(SensorType.WATER_IRRIGATION);
                    logger.warn("### latest water data== " + water);
                    Calendar waterDate = Calendar.getInstance();
                    waterDate.setTime(water.dateTime);

                    Calendar nextWaterDate = Calendar.getInstance();
                    nextWaterDate.add(Calendar.DATE, waterDays);

                    if (waterDate.before(nextWaterDate)) {
                        sensorState = true;
                    }
                }

                if (!sensorState) { //check observation entries.
                    ObservationData mostRecent = ObservationData
                            .find("plant = ? AND dataType = ? order by dateCreated desc",
                                    new Object[] { plant, UserDataType.DEFAULT_PLANT_IRRIGATION })
                            .first();
                    if (mostRecent != null) {
                        Calendar obsWaterDate = Calendar.getInstance();
                        obsWaterDate.setTime(mostRecent.dateCreated);

                        Calendar nextWaterDate = Calendar.getInstance();
                        nextWaterDate.add(Calendar.DATE, waterDays);
                        if (obsWaterDate.before(nextWaterDate)) {
                            observationState = true;
                        }
                    }
                }
                logger.warn(" sensorState=" + sensorState + "  ObsState=" + observationState);
                if (!sensorState & !observationState) {
                    sb.append("\n ").append(plant.name).append(": is due for irrigation.");
                }

            }

            if (sb.length() > 0) {
                try {
                    alertDetected = true;
                    if (options.enableWarningNotification) {
                        sendNotification(options, "", sb.toString());
                    }
                    logger.info("Plant Alert Message Sent: " + sb.toString());
                } catch (EmailException e) {
                    logger.error("Email Alert failed.", e);
                    new LogData(new Date(), "Failed to send email address to email address: " + options.email)
                            .save();
                }
            }

        }
        return alertDetected;
    }
}