package org.bensteele.jirrigate.controller; import java.io.IOException; import java.net.InetAddress; import java.util.ArrayList; import java.util.HashSet; import java.util.LinkedHashSet; import java.util.List; import java.util.Set; import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.BlockingQueue; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.LinkedBlockingQueue; import jline.internal.Log; import org.apache.http.HttpEntity; import org.apache.http.HttpResponse; import org.apache.http.client.ClientProtocolException; import org.apache.http.client.ResponseHandler; import org.apache.http.client.config.RequestConfig; import org.apache.http.client.methods.HttpGet; import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.impl.client.HttpClients; import org.apache.http.util.EntityUtils; import org.apache.log4j.Logger; import org.bensteele.jirrigate.controller.zone.Zone; /** * An abstract class for an irrigation controller interface. A complete workable implementation of a * class that extends this will <i>successfully</i> implement all the abstract methods presented. * <p> * See {@link EtherRain8Controller} for a reference implementation of this class. * * @author Ben Steele (ben@bensteele.org) */ public abstract class Controller { public enum ControllerType { ETHERRAIN8 } private final Logger log; private final String controllerName; private InetAddress ipAddress; private int port; private String username = ""; private String password = ""; private final Set<Zone> zones = new LinkedHashSet<Zone>(); private boolean isActive; private boolean isIrrigating; private final ExecutorService requestExecutor = Executors.newSingleThreadExecutor(); private final BlockingQueue<IrrigationRequest> irrigationRequests = new ArrayBlockingQueue<IrrigationRequest>( 100); private final BlockingQueue<IrrigationResult> irrigationResults = new LinkedBlockingQueue<IrrigationResult>(); private final ControllerType controllerType; public Controller(final String controllerName, InetAddress ipAddress, int port, String username, String password, ControllerType type, final Logger log) throws IOException { this.controllerName = controllerName; this.ipAddress = ipAddress; this.port = port; this.username = username; this.password = password; this.controllerType = type; this.log = log; this.isActive = true; // Process the incoming IrrigationRequests. requestExecutor.execute(new Runnable() { @Override public void run() { while (true) { IrrigationRequest request; try { request = irrigationRequests.take(); isIrrigating = true; log.info(controllerName + " starting irrigation request:\n" + request.toString()); startIrrigation(request); } catch (InterruptedException e) { e.printStackTrace(); } } } }); } public void addZone(Zone z) { this.zones.add(z); } public IrrigationRequest generateDefaultIrrigationRequest() { IrrigationRequest request = new IrrigationRequest(); for (Zone z : zones) { request.getZones().put(z, z.getDuration()); } return request; } public IrrigationRequest generateDefaultIrrigationRequest(double multiplier) { IrrigationRequest request = new IrrigationRequest(); for (Zone z : zones) { request.getZones().put(z, (long) (z.getDuration() * multiplier)); } return request; } public ControllerType getControllerType() { return this.controllerType; } public InetAddress getIpAddress() { return this.ipAddress; } public BlockingQueue<IrrigationRequest> getIrrigationRequests() { return irrigationRequests; } public BlockingQueue<IrrigationResult> getIrrigationResults() { return irrigationResults; } protected Logger getLog() { return log; } public String getName() { return this.controllerName; } public String getPassword() { return this.password; } public int getPort() { return this.port; } public abstract String getStatus(); public String getUsername() { return this.username; } public Zone getZone(String name) { for (Zone z : zones) { if (z.getName().matches(name)) { return z; } } return null; } public Set<Zone> getZones() { return this.zones; } public void irrigationRequest(IrrigationRequest request) throws IllegalStateException { this.irrigationRequests.add(request); } public boolean isActive() { return isActive; } public boolean isIrrigating() { return isIrrigating; } /** * Generic "helper" method to send a HTTP GET to a specified URL. * * @param url The full URL that you wish to send this request to. * @return The body of the reply line by line in a List. * @throws IOException If something goes wrong with the request. */ protected List<String> sendHttpGet(String url) throws IOException { // Set the timeout to 5 seconds. final int HTTP_TIMEOUT = 5000; CloseableHttpClient httpclient = HttpClients .custom() .setDefaultRequestConfig( RequestConfig.custom().setSocketTimeout(HTTP_TIMEOUT) .setConnectionRequestTimeout(HTTP_TIMEOUT).setConnectTimeout(HTTP_TIMEOUT).build()) .build(); try { HttpGet httpget = new HttpGet(url); ResponseHandler<String> responseHandler = new ResponseHandler<String>() { @Override public String handleResponse(final HttpResponse response) throws ClientProtocolException, IOException { int status = response.getStatusLine().getStatusCode(); if (status >= 200 && status < 300) { HttpEntity entity = response.getEntity(); return entity != null ? EntityUtils.toString(entity) : null; } else { throw new ClientProtocolException("Unexpected response status: " + status); } } }; String responseBody = httpclient.execute(httpget, responseHandler); // Convert the monolithic response body String into a new line delimited List. String bodyArray[] = responseBody.split("\\n"); List<String> response = new ArrayList<String>(); for (String line : bodyArray) { response.add(line); } return response; } catch (Exception e) { Log.error(controllerName + " timed out trying to execute URL: " + url); return new ArrayList<String>(); } finally { httpclient.close(); } } public void setActive(boolean isActive) { this.isActive = isActive; } public void setIpAddress(InetAddress ipAddress) { this.ipAddress = ipAddress; } public void setIsIrrigating(boolean isIrrigating) { this.isIrrigating = isIrrigating; } public void setPassword(String password) { this.password = password; } public void setPort(int port) { this.port = port; } public void setUsername(String username) { this.username = username; } protected abstract void startIrrigation(IrrigationRequest request); public abstract boolean stopIrrigation(); /** * Removes {@link Zone} that have no/zero duration specified in the {@link IrrigationRequest}. * * @param request The {@link IrrigationRequest} to process. * @return A Set of {@link Zone} to be irrigated. */ protected Set<Zone> stripNoIrrigationZones(IrrigationRequest request) { Set<Zone> strippedZones = new HashSet<Zone>(); for (Zone z : request.getZones().keySet()) { if (request.getZones().get(z) > 0) { strippedZones.add(z); } } return strippedZones; } public void testZone(Zone zone, long duration) { IrrigationRequest request = new IrrigationRequest(); request.getZones().put(zone, duration); irrigationRequest(request); } public void testZones(Set<Zone> zones, long duration) { IrrigationRequest request = new IrrigationRequest(); for (Zone zone : zones) { request.getZones().put(zone, duration); } irrigationRequest(request); } }