Barry Hannigan
Published © GPL3+

Smarter Irrigation Controller

Enhanced algorithm to control the disabling of an irrigation system based on multiple sensor input and future rain fall amounts.

IntermediateFull instructions provided1,495

Things used in this project

Story

Read more

Schematics

Dashboard

This is the Cayenne Dashboard for the Smarter Irrigation Control

Code

Raspberry Pi - Main Java Class

Java
This is the main class that does initial setup and contains the main processing loop. It checks for messages from the ESP8266 Thing Dev that monitors the Rain Guage cutoff switch. Gets current weather from the local weather station, gets forecast from weather underground and updates Cayenne controls.
/* 
 * Copyright (C) 2017 Barry Hannigan <support@miser-tech.com>
 *
 * 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 3 of the License, 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 this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package smartirrigation;

import MiserSensor.Discovery.DiscoveryDataHandler;
import MiserSensor.Discovery.MiserDeviceInfo;
import MiserSensor.Discovery.SensorDiscovery;
import MiserSensor.decoders.MiserWeatherStation;
import MiserSensor.listeners.MiserSensorListener;
import MiserSensor.messages.ActuatorMsg;
import MiserSensor.messages.DataOutputConfigResponse;
import MiserSensor.messages.DataOutputMsg3;
import MiserSensor.utilities.DataOutputConfigManager;
import MiserSensor.utilities.SensorData;
import java.util.Timer;
import java.util.TimerTask;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 *
 * @author Barry Hannigan <support@miser-tech.com>
 */
public class SmartIrrigation implements Runnable, DiscoveryDataHandler
{
    private SensorDiscovery sensorDisc;
    private final DataOutputConfigManager configObj = new DataOutputConfigManager();
    private final MiserSensorListener weatherListener = new MiserSensorListener();
    private final MiserSensorListener irrigationListener = new MiserSensorListener();
    private final WeatherUG wug = new WeatherUG();
    
    private long lastForecastUpdate = 0;
    private final CayenneIrrigation cayenne = new CayenneIrrigation();
    
    private final IrrigationController algorithm = new IrrigationController();
    
    private final String weatherStationMAC = "18-FE-34-A6-42-30";
    private final String irrigationDevMAC = "5C-CF-7F-E2-3C-DA";
    private String weatherStationIP = null;
    private String irrigationDevIP = null;
    public final Object discRetry = new Object();
    private final Object readyToRun = new Object();
    private final Timer discTimer = new Timer();
    private final Timer loopTimer = new Timer();
    private final ActuatorMsg actMsg = new ActuatorMsg();
    private AvatarActuatorSender actSender;
    
    /**
     * @param args the command line arguments
     */
    public static void main(String[] args)
    {
        SmartIrrigation app = new SmartIrrigation();
        //app.init();
        java.awt.EventQueue.invokeLater(app);
        System.out.println("After the invokeLater");
    }

    @Override
    public void run()
    {
        System.out.println("Running");
        // Create the Sensory Discovery Singleton
        sensorDisc = SensorDiscovery.getInstance();
        
        // Subscribe to discovery events
        sensorDisc.subscribe(this);
        
        boolean error = sensorDisc.startRx();
        if (error)
            System.out.println("Error: "+sensorDisc.getStartError());
        
        // Set a discTimer task to notify every 2 seconds if we need to retry
        discTimer.scheduleAtFixedRate(new TimerTask() {
                @Override
                public void run() {
                    synchronized(discRetry)
                    {
                        discRetry.notifyAll();
                    }
            }
        }, 2000, 2000);
        // Wait until both stations are discovered
        while((weatherStationIP == null) || (irrigationDevIP == null))
        {
            error = sensorDisc.pollDevices();
            if (error)
                System.out.println("Error: "+sensorDisc.getPollError());

            try
            {
                synchronized(discRetry)
                {
                    // Wait for discTimer to go off
                    discRetry.wait();
                }
            } catch (InterruptedException ex)
            {
                Logger.getLogger(SmartIrrigation.class.getName()).log(Level.SEVERE, null, ex);
            }            
        }
        // Stop Timer
        discTimer.cancel();
        
        // Setup Avatar Actuator Sender for irrigation controller IP address
        actSender = new AvatarActuatorSender(irrigationDevIP);
        
        // Init Cayenne
        cayenne.init();
        
        // Set a discTimer task to notify every 2 seconds if we need to retry
        loopTimer.scheduleAtFixedRate(new TimerTask() {
                @Override
                public void run() {
                    synchronized(readyToRun)
                    {
                        readyToRun.notifyAll();
                    }
            }
        }, 1000, 1000);
        // Start Main Loop here
        while(true)
        {
            // Check for new Local Weather Station Data
            if (weatherListener.hasData())
            {
                updateLocalWeatherData();
            }
            
            // Check for new Irrigation Device Data
            if (irrigationListener.hasData())
            {
                updateIrrigationData();
            }

            // Check if time to update forecast
            long currMills = System.currentTimeMillis();
            if((currMills - lastForecastUpdate) > (60000*60*6))
            {
                lastForecastUpdate = currMills;
                updateForecast();
            }

            // Update current Enable Switch State
            algorithm.updateEnableSwitchState(cayenne.getEnableSwitchState());
            
            // Update the current Irrigation State
            algorithm.calculateIrrigationState();
            if(algorithm.getIrrigationState() == true)
                actMsg.setDigital(1);
            else
                actMsg.setDigital(0);
            actSender.sendMsg(actMsg);
            
            // Update Cayenne GUI items
            cayenne.updateRainSensorState(algorithm.getRainSensorState());
            cayenne.updateIrrigationState(algorithm.getIrrigationState());
            cayenne.updateRainLast7(algorithm.getRainLast7Days());
            cayenne.updateCurrentTemp(algorithm.getTemperature());
            cayenne.updateCurrentHumidity(algorithm.getHumidity());
            cayenne.updateCurrentWind(algorithm.getWindSpeed());                
            
            //System.out.println("Rain last 7 days = "+algorithm.getRainLast7Days());

            // Block until timer wakes us up again
            try
            {
                synchronized(readyToRun)
                {
                    // Wait for discTimer to go off
                    readyToRun.wait();
                }
            } catch (InterruptedException ex)
            {
                Logger.getLogger(SmartIrrigation.class.getName()).log(Level.SEVERE, null, ex);
            }
        }
    }

    private void updateIrrigationData()
    {
        // We know data is available so get it
        SensorData<DataOutputMsg3> queueData = irrigationListener.waitForSensorData();
        if(queueData != null)
        {
            DataOutputMsg3 theMsg = queueData.getTheMsg();
            if (theMsg != null)
            {
                //System.out.println("Got irrigation msg, Seconds = "+theMsg.getSeconds());
                IrrigationSensor irrCtrl = new IrrigationSensor();
                irrCtrl.decodeMessage(theMsg, queueData.getTimeStamp());
                algorithm.updateRainSensorState(irrCtrl.getRainSensorState());
            }
        }            
    }
    
    private void updateLocalWeatherData()
    {
        SensorData<DataOutputMsg3> queueData = weatherListener.waitForSensorData();
        if(queueData != null)
        {
            DataOutputMsg3 theMsg = queueData.getTheMsg();
            if (theMsg != null)
            {
                //System.out.println("Got weather msg, Seconds = "+theMsg.getSeconds());
                MiserWeatherStation theData = new MiserWeatherStation();
                theData.decodeMessage(theMsg, queueData.getTimeStamp());

                algorithm.updateTodaysRain(theData.getRainFall());
                algorithm.updateCurrentTemperature(theData.getTemperature());
                algorithm.updateCurrentHumidity(theData.getHumidity());
                algorithm.updateCurrentWindSpeed(theData.getWindSpeed(theMsg.getDataInterval()));
            }
        }
    }
    
    private void updateForecast()
    {
        System.out.println("Time to update Forecast");
        wug.sendGet();
        WeatherUG.ForecastData[] forecast = wug.parseXml();
        System.out.println("Got Forecast for "+forecast.length+" days");
        float forecastRain = 0;
        for (int i = 0; i < forecast.length; i++)
        {
            float factor = 1.0f - (i*0.25f);
            //forecastRain += forecast[i].rain;
            //forecastRain += forecast[i].rain * factor;
            forecastRain += ((forecast[i].rain * (forecast[i].pop / 100.0)) * factor);
        }
        
        algorithm.updateForecast(forecastRain);
        cayenne.updateFutureRain(forecastRain);
        System.out.println("Forecast Rain = "+forecastRain);
    }
    
    private boolean startListener(MiserSensorListener theListener, String ipAddress)
    {
        // Get the DataOutput Configuration Message from the Sensor
        DataOutputConfigResponse theResponse = null;
        
        // Try to get devices configuration
        for(int retries = 3; retries > 0;retries--)
        {
            theResponse = configObj.getConfigMsg(ipAddress);
            if (theResponse != null)
                break;
            else
            {
                //System.out.println("Error: No Config response!");
                return false;
            }
        }

        // If message is invalid return 
        if ((null == theResponse) || (false == theResponse.isValid()))
        {
            System.out.println("Error: Could not get configuration for sesnor: "+ipAddress);
            return false;
        }

        // Start the Multicast Address Listener
        theListener.startMultiCast(theResponse.getIpAddressValue(), ipAddress);

        return true;
    }                                        


    @Override
    public void notifyDiscoveryData(DiscEventBase event, MiserDeviceInfo discData)
    {
        //System.out.println("Found Device MAC:"+discData.getMacAddr()+", IP:"+discData.getIpAddr());
        if (discData.getMacAddr().contentEquals(weatherStationMAC) && (weatherStationIP == null))
        {
            System.out.println("Found Weather Station at IP: "+discData.getIpAddr());
            if(startListener(weatherListener, discData.getIpAddr()))
                weatherStationIP = discData.getIpAddr();
        }
        if (discData.getMacAddr().contentEquals(irrigationDevMAC) && (irrigationDevIP == null))
        {  
            System.out.println("Found Irrigation Device at IP: "+discData.getIpAddr());
            if(startListener(irrigationListener, discData.getIpAddr()))
                irrigationDevIP = discData.getIpAddr();
        }
    }
}

Raspberry Pi - Cayenne Java MQTT class

Java
This class uses the Eclipse PAHO MQTT version 3 library to communicate to Cayenne.
/*
 * Copyright (C) 2017 Barry Hannigan <support@miser-tech.com>
 *
 * 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 3 of the License, 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 this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package smartirrigation;

import java.util.logging.Level;
import java.util.logging.Logger;
import org.eclipse.paho.client.mqttv3.IMqttDeliveryToken;
import org.eclipse.paho.client.mqttv3.MqttCallback;
import org.eclipse.paho.client.mqttv3.MqttClient;
import org.eclipse.paho.client.mqttv3.MqttConnectOptions;
import org.eclipse.paho.client.mqttv3.MqttDeliveryToken;
import org.eclipse.paho.client.mqttv3.MqttException;
import org.eclipse.paho.client.mqttv3.MqttMessage;
import org.eclipse.paho.client.mqttv3.MqttTopic;
import org.eclipse.paho.client.mqttv3.persist.MemoryPersistence;

/**
 *
 * @author Barry Hannigan <support@miser-tech.com>
 * This module uses the Eclipse PAHO MQTT Client Java library. You must have
 * the Eclipse PAHO MQTT V3 jar file in your class path to use it.
 */
public class CayenneIrrigation implements MqttCallback
{
    MqttClient myClient = null; 
    MqttConnectOptions connOpt = null; 
    MemoryPersistence persistence = new MemoryPersistence();

    static final String BROKER_URL = "tcp://mqtt.mydevices.com:1883"; 
    static final String CAYENNE_DOMAIN = "v1"; 
    static final String CAYENNE_STUFF = "things"; 
    static final String CAYENNE_THING = "<Your Thing ID here>"; 
    static final String CAYENNE_USERNAME = "<Your Username Here>"; 
    static final String CAYENNE_PASSWORD = "<Your Password Here>"; 

    static final String cayenneTopic = CAYENNE_DOMAIN + "/" + CAYENNE_USERNAME + "/" + CAYENNE_STUFF + "/" + CAYENNE_THING; 

    private final String currTempTopic = cayenneTopic + "/data/0"; 
    private final String enableSwitchPubTopic = cayenneTopic + "/data/1"; 
    private final String currWindPubTopic = cayenneTopic + "/data/2"; 
    private final String irrigationSwitchTopic = cayenneTopic + "/data/3"; 
    private final String rainSensorTopic = cayenneTopic + "/data/4"; 
    private final String currHumidityPubTopic = cayenneTopic + "/data/5"; 
    private final String currFutureRainPubTopic = cayenneTopic + "/data/6"; 
    private final String currRainLast7PubTopic = cayenneTopic + "/data/7"; 
    
    // private String rainSensorTopic = cayenneTopic + "/sys/model"; 
    private final String enableSwitchSubTopic = cayenneTopic + "/cmd/1"; 
    private final String myRspTopic = cayenneTopic + "/response"; 
    
    private boolean enableSwitchState = true;
    private boolean irrigationState = false;
    private float prevTemp = 10.0f;
    private float prevHumidity = 10.0f;
    private float prevWind = 10.0f;
    private float prevFutureRain = 10.0f;
    private float prevRainLast7 = 10.0f;
    private boolean prevRainSensorState = true;
    
    public void init()
    {
        String clientID = CAYENNE_THING; 
        connOpt = new MqttConnectOptions(); 

        connOpt.setCleanSession(true); 
        connOpt.setKeepAliveInterval(30); 
        connOpt.setUserName(CAYENNE_USERNAME); 
        connOpt.setPassword(CAYENNE_PASSWORD.toCharArray()); 
        connOpt.setMqttVersion(MqttConnectOptions.MQTT_VERSION_3_1_1);
        
        // Connect to Broker 
        try { 
            myClient = new MqttClient(BROKER_URL, clientID, persistence); 
            myClient.setCallback(this); 
            myClient.connect(connOpt); 
        } catch (MqttException e) { 
            e.printStackTrace(); 
            System.exit(-1); 
        } 

        System.out.println("Connected to " + BROKER_URL); 

        // setup topic 

        // Create Subscribe
        try { 
            int subQoS = 0; 
            myClient.subscribe(enableSwitchSubTopic, subQoS); 
        } catch (Exception e) { 
            e.printStackTrace(); 
        }
        
        // Set Initial Values
        updateTopic(enableSwitchPubTopic, "1");
        updateIrrigationState(true);
        //updateKillSwitchState(this.enableSwitchState);
        updateRainSensorState(false);
        updateCurrentTemp(0.0f);
        updateCurrentHumidity(0.0f);
        updateCurrentWind(0.0f);
        updateFutureRain(0.0f);
        updateRainLast7(0.0f);
    }
    
    public boolean getEnableSwitchState()
    {
        return enableSwitchState;
    }
    
    public void updateIrrigationState(boolean state)
    {
        if (state == irrigationState)
        {
            return;
        }
        int stateVal = 0;
        if(state)
            stateVal = 1;
        irrigationState = state;
        String pubMsg = "digital_sensor,d="+stateVal;
        updateTopic(irrigationSwitchTopic, pubMsg);
    }
    
    public void updateCurrentTemp(float temp)
    {
        // Only update new values
        if(temp == prevTemp)
            return;
        prevTemp = temp;
        
        String pubMsg = "temp,f="+temp;
        updateTopic(currTempTopic, pubMsg);
    }
    
    public void updateRainSensorState(boolean state)
    {
        if(prevRainSensorState == state)
            return;
        prevRainSensorState = state;
        int stateVal = 0;
        if(state)
            stateVal = 1;
        String pubMsg = "digital_sensor,d="+stateVal;
        updateTopic(rainSensorTopic, pubMsg);
    }
    
    public void updateCurrentHumidity(float humidity)
    {
        // Only update new values
        if(humidity == prevHumidity)
            return;
        prevHumidity = humidity;
        
        String pubMsg = "rel_hum,p="+humidity;
        updateTopic(currHumidityPubTopic, pubMsg);
    }
    
    public void updateCurrentWind(float wind)
    {
        // Only update new values
        if(wind == prevWind)
            return;
        prevWind = wind;
        
        String pubMsg = "wind_speed,kmh="+wind;
        updateTopic(currWindPubTopic, pubMsg);
    }
    
    public void updateFutureRain(float rain)
    {
        // Only update new values
        if(rain == prevFutureRain)
            return;
        prevFutureRain = rain;
        
        String pubMsg = "rain_level,in="+rain;
        //String pubMsg = "soil_moist,p="+rain ;
        updateTopic(currFutureRainPubTopic, pubMsg);
    }
    
    public void updateRainLast7(float rain)
    {
        // Only update new values
        if(rain == prevRainLast7)
            return;
        prevRainLast7 = rain;
        
        String pubMsg = "rain_level,in="+rain;
        //String pubMsg = "soil_moist,p="+rain;
        updateTopic(currRainLast7PubTopic, pubMsg);
    }
    
    private void updateTopic(String topicString, String pubMsg)
    {
        MqttTopic topic = myClient.getTopic(topicString);
        MqttMessage message = new MqttMessage(pubMsg.getBytes()); 
        int pubQoS = 0;
        message.setQos(pubQoS); 
        message.setRetained(false); 

        // Publish the message 
        System.out.println("Publishing new value: "+pubMsg);
        //System.out.println("Publishing to topic \"" + topic + "\" qos " + pubQoS); 
        MqttDeliveryToken token = null; 
        try { 
            // publish message to broker 
            token = topic.publish(message); 
            // Wait until the message has been delivered to the broker 
            token.waitForCompletion(); 
            Thread.sleep(100); 
        } catch (Exception e) { 
            e.printStackTrace(); 
        } 
    }
    
    private void updateTopicNoWait(String topicString, String pubMsg)
    {
        MqttTopic topic = myClient.getTopic(topicString);
        MqttMessage message = new MqttMessage(pubMsg.getBytes()); 
        int pubQoS = 0;
        message.setQos(pubQoS); 
        message.setRetained(false); 

        // Publish the message 
        System.out.println("Publishing new value: "+pubMsg);
        System.out.println("Publishing to topic \"" + topic + "\" qos " + pubQoS); 
        MqttDeliveryToken token = null; 
        try { 
            // publish message to broker 
            token = topic.publish(message); 
            // Wait until the message has been delivered to the broker 
            //token.waitForCompletion(); 
            Thread.sleep(100); 
        } catch (Exception e) { 
            e.printStackTrace(); 
        } 
    }
    
    @Override
    public void connectionLost(Throwable thrwbl)
    {
        System.out.println("Connection lost!");
        System.exit(1);
    }

    @Override
    public void messageArrived(String string, MqttMessage mm) throws Exception
    {
        String msg = new String(mm.getPayload());
        System.out.println("-------------------------------------------------"); 
        System.out.println("| Topic:" + string); 
        System.out.println("| Message: " + msg); 
        System.out.println("-------------------------------------------------");  


        String[] vals = msg.split(",");
        String[] strings = string.split("cmd/");
        String chanNum = strings[1];
        //System.out.println("Channel Num = "+chanNum);
        
        if (chanNum.contentEquals("1"))
        {
            // Update new kill switch State
            String pubMsg = vals[1];
            enableSwitchState = vals[1].contentEquals("1");
            System.out.println("Kill Switch State = "+enableSwitchState);
            updateTopicNoWait(enableSwitchPubTopic, pubMsg);

            // Send Response
            pubMsg = "ok,"+vals[0];
            updateTopicNoWait(myRspTopic, pubMsg);
        }
    }

    @Override
    public void deliveryComplete(IMqttDeliveryToken imdt)
    {
        byte[] data = new byte[1];
        try
        {
            data = imdt.getMessage().getPayload();
        } catch (MqttException ex)
        {
            Logger.getLogger(CayenneIrrigation.class.getName()).log(Level.SEVERE, null, ex);
        }
	//System.out.println("Pub complete: " + new String(data)); 
    }
}

Raspberry Pi - Weather Underground Java Class

Java
This class retrieves a forecast from Weather underground and parses the information needed by the Irrigation Algorithm
/*
 * Copyright (C) 2017 Barry Hannigan <support@miser-tech.com>
 *
 * 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 3 of the License, 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 this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package smartirrigation;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.StringReader;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.InputSource;

/**
 *
 * @author Barry Hannigan <support@miser-tech.com>
 */
public class WeatherUG
{
    private String key = "<Your Wunderground Key Here>";
    private final String USER_AGENT = "Mozilla/5.0";
    private String xmlStr = "";
    
    public class ForecastData
    {
        public int high;
        public int low;
        public int pop;
        public float rain;
        public int avgWind;
        public int avgHumidity;
    };
    
    /**
     * @param args the command line arguments
     */
    public static void main(String[] args)
    {
        // TODO code application logic here
        WeatherUG myTest = new WeatherUG();
        myTest.sendGet();
    }

    // HTTP GET request
    public void sendGet()
    {

        String url = "http://api.wunderground.com/api/"+key+"/forecast/q/08094.xml";
        URL obj;
        try
        {
            obj = new URL(url);
            HttpURLConnection con = (HttpURLConnection) obj.openConnection();

            // optional default is GET
            con.setRequestMethod("GET");

            //add request header
            con.setRequestProperty("User-Agent", USER_AGENT);

            //int responseCode = con.getResponseCode();
            System.out.println("\nSending 'GET' request to URL : " + url);
            //System.out.println("Response Code : " + responseCode);

            BufferedReader in = new BufferedReader( new InputStreamReader(con.getInputStream()));
            String inputLine;
            StringBuffer response = new StringBuffer();

            while ((inputLine = in.readLine()) != null) {
                response.append(inputLine);
            }
            in.close();
            xmlStr = response.toString();
            //print result
            //System.out.println(response.toString());
        } catch (MalformedURLException ex)
        {
            Logger.getLogger(WeatherUG.class.getName()).log(Level.SEVERE, null, ex);
        } catch (IOException ex)
        {
            Logger.getLogger(WeatherUG.class.getName()).log(Level.SEVERE, null, ex);
        }
    }

    public ForecastData[] parseXml()
    {
        // Create storage for the forecast data
        ForecastData[] fcast = null;
        
        try
        {
            DocumentBuilder db = DocumentBuilderFactory.newInstance().newDocumentBuilder();
            Document doc = db.parse(new InputSource(new StringReader(xmlStr)));
            doc.getDocumentElement().normalize();
            NodeList forecast = doc.getElementsByTagName("forecast");
 
            for (int a = 0; a < forecast.getLength(); a++)
            {
                Node forecastNode = forecast.item(a);
                  
                if (forecastNode.getNodeType() == Node.ELEMENT_NODE)
                {
                    Element forecastElement = (Element) forecastNode;

                    NodeList simpleforecast = forecastElement.getElementsByTagName("simpleforecast");

                    for (int b = 0; b < simpleforecast.getLength(); b++)
                    {
                        Node simpleforecastNode = simpleforecast.item(b);

                        if (simpleforecastNode.getNodeType() == Node.ELEMENT_NODE)
                        {
                            Element simpleforecastElement = (Element) simpleforecastNode;

                            NodeList forecastdays = simpleforecastElement.getElementsByTagName("forecastdays");
                            
                            for (int c = 0; c < forecastdays.getLength(); c++)
                            {
                                Node forecastdaysNode = forecastdays.item(c);

                                if (forecastdaysNode.getNodeType() == Node.ELEMENT_NODE)
                                {
                                    Element forecastdaysElement = (Element) forecastdaysNode;

                                    NodeList forecastday = forecastdaysElement.getElementsByTagName("forecastday");
                                    
                                    // Allocate enough storage for number of days
                                    int numDays = forecastday.getLength();
                                    //System.out.println("Number of Days: "+numDays );
                                    fcast = new ForecastData[numDays];

                                    for (int d = 0; d < forecastday.getLength(); d++)
                                    {
                                        // Allocate this days forecast object
                                        fcast[d] = new ForecastData();

                                        Node forecastdayNode = forecastday.item(d);
                                        if (forecastdayNode.getNodeType() == Node.ELEMENT_NODE)
                                        {
                                            Element forecastdayElement = (Element) forecastdayNode;
                                            NodeList date = forecastdayElement.getElementsByTagName("date");
                                            for (int e = 0; e < date.getLength(); e++)
                                            {
                                                Node dateNode = date.item(e); 

                                                if (dateNode.getNodeType() == Node.ELEMENT_NODE)
                                                {
                                                    Element dateElement = (Element) dateNode;

                                                    NodeList weekday = dateElement.getElementsByTagName("weekday");
                                                    Element day = (Element) weekday.item(0);
                                                    //System.out.println("Day of weekday: " + day.getTextContent());
                                                }
                                            }

                                            NodeList qpf = forecastdayElement.getElementsByTagName("qpf_allday");

                                            for (int f = 0; f < qpf.getLength(); f++)
                                            {
                                                Node qpfNode = qpf.item(f); 

                                                if (qpfNode.getNodeType() == Node.ELEMENT_NODE)
                                                {
                                                    Element qpfElement = (Element) qpfNode;
                                                    NodeList rainNode = qpfElement.getElementsByTagName("in");
                                                    Element val = (Element) rainNode.item(0);
                                                    //System.out.println("Rain: " + val.getTextContent());
                                                    fcast[d].rain = Float.parseFloat(val.getTextContent());
                                                }
                                            }


                                            NodeList pop = forecastdayElement.getElementsByTagName("pop");
                                            Node popNode = pop.item(0); 
                                            //System.out.println("POP: "+popNode.getTextContent());
                                            fcast[d].pop = Integer.parseInt(popNode.getTextContent());
                                            NodeList high = forecastdayElement.getElementsByTagName("high");

                                            for (int f = 0; f < high.getLength(); f++)
                                            {
                                                Node highNode = high.item(f); 

                                                if (highNode.getNodeType() == Node.ELEMENT_NODE)
                                                {
                                                    Element highElement = (Element) highNode;
                                                    //NodeList ftemp = highElement.getElementsByTagName("ftemp");
                                                    NodeList ftemp = highElement.getElementsByTagName("fahrenheit");
                                                    Element highVal = (Element) ftemp.item(0);
                                                    //System.out.println("High F: " + highVal.getTextContent());
                                                    fcast[d].high = Integer.parseInt(highVal.getTextContent());
                                                }
                                            }

                                            NodeList low = forecastdayElement.getElementsByTagName("low");

                                            for (int g = 0; g < low.getLength(); g++)
                                            {
                                                Node lowNode = low.item(g); 

                                                if (lowNode.getNodeType() == Node.ELEMENT_NODE)
                                                {
                                                    Element lowElement = (Element) lowNode;
                                                    NodeList ftemp = lowElement.getElementsByTagName("fahrenheit");
                                                    Element lowVal = (Element) ftemp.item(0);
                                                    //System.out.println("Low F: " + lowVal.getTextContent());
                                                    fcast[d].high = Integer.parseInt(lowVal.getTextContent());
                                                }
                                            }

                                            NodeList avewind = forecastdayElement.getElementsByTagName("avewind");

                                            for (int h = 0; h < avewind.getLength(); h++)
                                            {
                                                Node avewindNode = avewind.item(h); 

                                                if (avewindNode.getNodeType() == Node.ELEMENT_NODE)
                                                {
                                                    Element avewindElement = (Element) avewindNode;
                                                    NodeList mph = avewindElement.getElementsByTagName("mph");
                                                    Element mp = (Element) mph.item(0);
                                                    //System.out.println("mph: " + mp.getTextContent());
                                                    fcast[d].avgWind = Integer.parseInt(mp.getTextContent());

                                                    NodeList kph = avewindElement.getElementsByTagName("kph");
                                                    Element kp = (Element) kph.item(0);
                                                    //System.out.println("kph: " + kp.getTextContent());

                                                    NodeList dir = avewindElement.getElementsByTagName("dir");
                                                    Element dr = (Element) dir.item(0);
                                                    //System.out.println("Dir: " + dr.getTextContent());

                                                    NodeList degrees = avewindElement.getElementsByTagName("degrees");
                                                    Element deg = (Element) degrees.item(0);
                                                    //System.out.println("Degree: " + deg.getTextContent());
                                                }
                                            }

                                            NodeList conditions = forecastdayElement.getElementsByTagName("conditions");
                                            Element con = (Element) conditions.item(0);
                                            //System.out.println("conditions: " + con.getTextContent());

                                            NodeList avehumidity = forecastdayElement.getElementsByTagName("avehumidity");
                                            Element ave = (Element) avehumidity.item(0);
                                            //System.out.println("avehumidity: " + ave.getTextContent());
                                            fcast[d].avgHumidity = Integer.parseInt(ave.getTextContent());
                                        }
                                    }
                                }
                            }
                        }
                    }                       
                }                 
            }
        }catch (Exception ex)
        {
            System.out.println(ex.getMessage());
            
        }
        // Return Forecast Data
        return fcast;
    }
    
}

Raspberry Pi - Irrigation Controller Algorithm

C/C++
This class contains the Irrigation Control algorithm. It stores values that are collected by other parts of the program and has a function that will analyze the stored values and suggest whether the Irrigation System should be enabled or disabled. The algorithm currently uses the system enable button from the Cayenne dashboard, the existing Irrigation Rain Sensor state, previous 7 days of rain and forecast rain to make its decision. In the future I will probably add high wind cutoff, low temperature cutoff and possibly long periods of high humidity cutoff.
/*
 * Copyright (C) 2017 Barry Hannigan <support@miser-tech.com>
 *
 * 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 3 of the License, 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 this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package smartirrigation;

import MiserSensor.utilities.SlidingWindow;
import java.util.Calendar;
import java.util.Date;
import java.util.TimeZone;

/**
 *
 * @author Barry Hannigan <support@miser-tech.com>
 */
public class IrrigationController
{
    private final float SECONDS_PER_DAY = 86400.0f;
    private int currentDay = 0;
    private Date today = new Date(System.currentTimeMillis());
    private TimeZone tz = Calendar.getInstance().getTimeZone();
    private long millOffset = tz.getOffset(today.getTime());
    private float rainLast24 = 0;
    private float futureRain;
    private final SlidingWindow rainLast6 = new SlidingWindow(6);
    private final SlidingWindow temperature = new SlidingWindow(60);
    private final SlidingWindow humidity = new SlidingWindow(60);
    private final SlidingWindow windSpeed = new SlidingWindow(60);
    private boolean rainSensorState;
    private boolean enableSwitchState;
    private boolean irrigationState;
    
    public class IrrigationData
    {
        int currentDay;
        float rainLast24;
        float[] rainlast6 = new float[6];
    }
    

    public IrrigationController()
    {
        enableSwitchState = false;
        irrigationState = true;
    }
    
    public void loadStoredData(IrrigationData data)
    {
        for (int i = 0; i < data.rainlast6.length; i++)
        {
            rainLast6.addDataPoint(data.rainlast6[i]);
        }
        if (data.currentDay == currentDay)
        {
            rainLast24 = data.rainLast24;
        }
        else
        {
            rainLast6.addDataPoint(rainLast24);
            rainLast24 = 0;
        }
    }
    
    public void updateTodaysRain(float newRainAmount)
    {
        // Reset Rain if new day
        Double seconds = (double) (System.currentTimeMillis() + millOffset) / 1000.0f;
        int dayCalc = (int)(seconds / SECONDS_PER_DAY);
        if( currentDay != dayCalc )
        {
            // Put previous day into 6 day rain total and zero out 24 hour value
            rainLast6.addDataPoint(rainLast24);
            rainLast24 = 0;
            currentDay = dayCalc;
            // Calculate today's offset
            today = new Date(System.currentTimeMillis());
            tz = Calendar.getInstance().getTimeZone();
            millOffset = tz.getOffset(today.getTime());
        }
        
        // Add new rain amount to 24 hour total
        rainLast24 += newRainAmount;
    }
    
    public void updateCurrentTemperature(float temperatureVal)
    {
        temperature.addDataPoint(temperatureVal);
    }
    
    public void updateCurrentHumidity(float humidityVal)
    {
        humidity.addDataPoint(humidityVal);
    }
    
    public void updateCurrentWindSpeed(float windVal)
    {
        windSpeed.addDataPoint(windVal);
    }
    
    public void updateForecast(float futureRainVal)
    {
        futureRain = futureRainVal;
    }
    
    public void updateRainSensorState(boolean sensorState)
    {
        rainSensorState = sensorState;
    }

    public void updateEnableSwitchState(boolean state)
    {
        enableSwitchState = state;
    }
    
    // Algorithm to calculate the current state of the irrigation system
    public boolean calculateIrrigationState()
    {
        irrigationState = true;
        
        // If system disabled set state false and exit
        if (!enableSwitchState)
        {
            irrigationState = false;
            return irrigationState;
        }

        // Calculate previous 7 days of rain
        float prevRain = rainLast24 + (float)rainLast6.getTotal();
        
        // Check the State of the Rain Sensor (true means enough rain)
        if (rainSensorState)
        {
            // If rain sensor is activated, check we've had enough rain and or expected rain
            if ((prevRain < 0.5f) && (futureRain < 0.25f))
            {
                // if we haven't had too much rain override irrigation
                irrigationState = true;
            }
            else
            {
                irrigationState = false;
            }
        }
        else
        {
            // If rain sensor is off, check if we should disable anyway
            if ((prevRain > 0.9f) || (futureRain > 0.49f))
            {
                irrigationState = false;                
            }
        }
        return irrigationState;
    }
    
    public boolean getIrrigationState()
    {
        return irrigationState;
    }
    
    public float getRainLast7Days()
    {
        return rainLast24 + (float)rainLast6.getTotal();
    }
    
    public boolean getRainSensorState()
    {
        return rainSensorState;
    }
    
    public float getFutureRain()
    {
        return futureRain;
    }

    public float getTemperature()
    {
        return (float)temperature.getAverage();
    }

    public float getHumidity()
    {
        return (float)humidity.getAverage();
    }

    public float getWindSpeed()
    {
        return (float)windSpeed.getAverage();
    }
}

ESP8266 ThingDev - Main Arduino code

C/C++
This is the main Arduino file that contains the setup() and loop() functions. These call through to the Avatar Framework functions which in turn call Miser Application functions.
/* 
 * Copyright (C) 2017 Barry Hannigan <support@miser-tech.com>
 *
 * 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 3 of the License, 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 this program.  If not, see <http://www.gnu.org/licenses/>.
 */
 
 #include <dummy.h>

#include "ESP8266WiFi.h"
#include "user_interface.h"
#include "avatar_config.h"
#include "miser_adc.h"
extern "C" {
#include "avatar_time.h"
#include "miser_utils.h"
#include "avatar_framework.h"
#include "module_app.h"
}

#define BLINK_LED     5
#define WDT_TIMEOUT   15000

// Setup to read VCC voltage
ADC_MODE(ADC_VCC)

// Storage for Avatar Configuration
AvatarConfig appConfig;

uint32_t lastSecond = 0;
uint32_t lastInterval = 0;
uint8_t lastReset;
AvatarTime startTime;
AvatarTime startMeasTime;
uint32_t* reportInterval;
uint32_t* sampleSpacing;
uint8_t ledState = 0;
bool    loggedDelta;


void setup()
{
  Serial.begin(115200);
  //Serial.setDebugOutput(true);
  while(!Serial) { ; }
  Serial.flush();
  Serial.println("");
  delay(200);

  // Initialize Avatar Time 
  AT_init( 65535 - 1000, millis );
  
  // Save reason we reset
  lastReset = getResetReason();
  
  // Initialize Avatar Framework
  AFW_init(&appConfig, lastReset);

  // Get the pointers to Interval data so if the framework
  // updates the values, the updated values will be used
  reportInterval = AFW_GetReportInterval();
  sampleSpacing = AFW_GetSampleInterval();

  // Record current time to prime loop
  AT_getTime(&startTime);
  startMeasTime = startTime;

  // Configure BLINK_LED GPIO for output mode
  pinMode(BLINK_LED, OUTPUT);
  
  Serial.println("Setup done");

  // Enable Watchdog for 15 second timeout
  ESP.wdtEnable(WDT_TIMEOUT); 
}

void loop()
{
  // put your main code here, to run repeatedly:
  uint32_t curSecond = AT_getSecond(); 

  // Clear Watch Dog
  ESP.wdtFeed();
  
  // Framework Task
  AFW_task();
  MAPP_task();

  // Check for new clock Tick
  if(curSecond != lastSecond)
  {
      // Check if its time to send data
      if ((*reportInterval != 0u) && (curSecond - lastInterval) >= *reportInterval)
      {
          printf("\r\n\nThe timer = %u, tickCount = %ld\r\n\r\n",
                                  AT_getTick(),
                                  curSecond);
          printf("sampleTime = %ld, reportInterval = %ld\r\n",
                                  *sampleSpacing,
                                  *reportInterval);
          Serial.print("reset reason = ");
          Serial.println(lastReset);
          AFW_UDPDataOutTask3();
          MAPP_startCycle();
          loggedDelta = false;
          AT_getTime(&startMeasTime);
          startTime = startMeasTime;
          lastInterval = curSecond;
      }

      // Toggle state of LED
      ledState = ledState ^ 0x01;
      digitalWrite(BLINK_LED, ledState);
      
      // Record this second
      lastSecond = curSecond;
  }
}

ESP8266 ThingDev - Avatar Framework file

C/C++
This code configures and manages the Avatar Framework. This framework is responsible for implementing the Avatar Framework requirements. More at http://www.miser-tech.com website. Besides for managing all the communications the framework will call the Miser App code to get the required information to report.
/* 
 * Copyright (C) 2017 Barry Hannigan <support@miser-tech.com>
 *
 * 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 3 of the License, 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 this program.  If not, see <http://www.gnu.org/licenses/>.
 */
#include <stdio.h>
#include <string.h>
#include <stdint.h>
#include <stdbool.h>

#include "miser_utils.h"
#include "avatar_config.h"
#include "ESP8266Iface.h"

#include "avatar_framework.h"
#include "avatar_messages.h"
#include "avatar_time.h"
#include "module_app.h"
//#include "miser_adc.h"

static AvatarConfig* appConfig;
uint8_t     capFlags = 0;

uint16_t    discoveryHandle = UDP_INVALID;
uint8_t     cmdHandle =  UDP_INVALID;
uint8_t     dataHandle = UDP_INVALID;
uint8_t     actHandle = UDP_INVALID;


void AFW_init(AvatarConfig* theConfig, uint8_t resetReason)
{
    appConfig = theConfig;

    // Fill in Capability Flags
    if(MAPP_isSensor())
        capFlags = capFlags | CF_SENSOR;
    if(MAPP_isActuator())
        capFlags = capFlags | CF_ACTUATOR;
    if(MAPP_isBatteryPowered())
        capFlags = capFlags | CF_BATT_POWER;

    // Initialize Avatar Configuration
    AC_init(appConfig);

    // Loop until ESP8266 lib initialized
    while(!ESP_init(&appConfig->netConfig, &appConfig->wifiConfig))
    {
        printf("ESP8266 init failed, retrying...\n\r");
        AT_waitMills(1000);
    }

    // Initialization is OK
    printf("ESP8266 is Alive!\r\n");
        
    // Setup announce UDP socket
    discoveryHandle = udpOpen("255.255.255.255", MT_BROADCAST_RX_PORT, MT_BROADCAST_TX_PORT);
    
    // Set up Config socket
    printf("Opening cmdHandle\r\n");
    cmdHandle = udpOpen("0.0.0.0", MT_CONFIG_PORT, MT_CONFIG_PORT);
    printf(" cmdHandle = 0x%02X\r\n", cmdHandle);

    if(MAPP_isSensor())
    {
      // Set up Data socket
      printf("Opening dataHandle\r\n");
      uint8_t strDataAddr[16];
      uint8_t* ipBytes = (uint8_t*)&appConfig->dataIpAddr;
      sprintf(strDataAddr, "%u.%u.%u.%u", ipBytes[0], ipBytes[1], ipBytes[2], ipBytes[3]);
      dataHandle = udpOpen(strDataAddr, MT_DATA_PORT, 0);
      printf(" dataHandle = 0x%02X\r\n", dataHandle);    
    }

    if(MAPP_isActuator())
    {
      // Set up Actuator socket
      printf("Opening actuatorHandle\r\n");
      actHandle = udpOpen("0.0.0.0", MT_ACTUATOR_PORT, MT_ACTUATOR_PORT);
      printf(" actHandle = 0x%02X\r\n", actHandle);    
    }
    
    // Connect to WiFi
    ESP_wifiConnect();

    // Announce this module is Alive
    AFW_announce(resetReason);    
}

uint32_t* AFW_GetReportInterval()
{
    return &appConfig->dataInterval;
}

uint32_t* AFW_GetSampleInterval()
{
    //appConfig->sampleInterval = 50; // Override for testing
    return &appConfig->sampleInterval;
}

void AFW_announce(uint8_t status)
{
    DiscoveryAnnounce discAnnMsg;
    // Send command 0x0202
    discAnnMsg.command = swap16(ANNOUNCE);
    // Hostname
    memcpy(discAnnMsg.hostName, appConfig->netConfig.HostName, sizeof(discAnnMsg.hostName));
    // MAC
    memcpy(discAnnMsg.macAddr, appConfig->netConfig.MyMACAddr, sizeof(discAnnMsg.macAddr));
    // Vendor ID code
    discAnnMsg.vendorID = swap16(VENDOR_ID);
    // Device ID code
    discAnnMsg.deviceID = swap16(appConfig->deviceID);
    // Zone Info 
    discAnnMsg.zone = appConfig->locZone;
    discAnnMsg.locA = appConfig->locA;
    discAnnMsg.locB = appConfig->locB;
    discAnnMsg.locC = appConfig->locC;
    // Capabilities
    discAnnMsg.capFlags = capFlags;
    // Reset Status
    discAnnMsg.status = status;
    printf("Sending announce message, status = 0x%02X\r\n", status);
    udpSendArray(discoveryHandle, (uint8_t*)&discAnnMsg, sizeof(discAnnMsg));
}

void AFW_task()
{
    while(!ESP_isWifiConnected())
    {
        ESP_wifiConnect();
        AT_waitMills(500);
        continue;
    }
    ESP_task();
    discoveryTask();
    actuatorTask();
    udpCmdTask();
}

void discoveryTask()
{
    int bytes = udpIsGetReady(discoveryHandle);
    if (bytes > 0)
    {
        printf("udpIsGetReady(%d) = %d\r\n", discoveryHandle, bytes);
        uint8_t* theBuffer;
        udpGetArray(discoveryHandle, &theBuffer);
        // Looking for 0x02 0x01
        if ((theBuffer[0] == 0x02) && theBuffer[1] == 0x01)
            AFW_announce(DISC_RESPONSE);
        else
            printf("Received bad discovery message %02X, %02X\r\n", theBuffer[0], theBuffer[1]);
    }
}

void udpCmdTask(void)
{
    uint16_t wTemp = 0;
    uint8_t*  udpData;
    wTemp = udpIsGetReady(cmdHandle);
    if(wTemp > 0u)
    {
        uint16_t cmdWord;
        udpGetArray(cmdHandle, &udpData);
        cmdWord = udpData[0];
        cmdWord *= 256;
        cmdWord += udpData[1];
        switch(cmdWord)
        {
            // Configure Wifi Connection
            case PUT_WIFI_CONFIG:
            {
                WIFI_CONNECTION* wifiCon = (WIFI_CONNECTION*)udpData;
                printf("Received Connection Config Cmd\n");
                printf(" data: [%s], [%s]\n", wifiCon->ssid, wifiCon->passPhrase);
                appConfig->wifiConfig.SsidLength = strlen(wifiCon->ssid);
                strncpy( (char*)appConfig->wifiConfig.MySSID, wifiCon->ssid, appConfig->wifiConfig.SsidLength );
                appConfig->wifiConfig.SecurityMode = (uint8_t)wifiCon->secMode; // swaps(wifiCon->secMode);
                //AppConfig.SecurityKeyLength = strlen(wifiCon->passPhrase);
                printf("Saving AppConfig\n");
                strncpy( appConfig->wifiConfig.ssidPassword,
                         wifiCon->passPhrase,
                         sizeof(wifiCon->passPhrase));
                appConfig->wifiConfig.networkType = WF_INFRASTRUCTURE;
                // We are going to connect now, so clear AP Mode flag
                appConfig->wifiConfig.useApMode = false;
                AC_saveAvatarConfig(appConfig);
                printf("Reset in 10 seconds\n");
                AT_waitMills(10000);
                SwReset();

                break;
            }
            // Configure Data Send Options
            case PUT_DATA_CONFIG:
            {
                bool ipChanged = false;
                DATA_CONFIG* dataConfig = (DATA_CONFIG*)udpData;
                uint8_t* ipBytes = (uint8_t*)&dataConfig->ipAddr;
                printf("Received IP Address %d.%d.%d.%d\n\r", ipBytes[0], ipBytes[1], ipBytes[2], ipBytes[3]);
                // If address is changing from previous save it and mark change flag
                 if (appConfig->dataIpAddr != dataConfig->ipAddr)
                 {
                    appConfig->dataIpAddr = dataConfig->ipAddr; // Endian is stored network order
                    ipChanged = true;
                 }
                appConfig->dataInterval = swap16(dataConfig->interval);
                AC_saveAvatarConfig(appConfig);
                // Send Response
                ACK_MESSAGE ack;
                ack.command = swap16(ACK_COMMAND);
                ack.putCmd = dataConfig->command;
                ack.commandStatus = UPDATE_OK;
                ack.errorCode = RESP_SUCCESS;
                printf("Sending Response now\r\n");
                // Send the packet
                udpSendArray(cmdHandle, (uint8_t*)&ack, sizeof(ACK_MESSAGE));

                // If we have a new IP address close and open new data send socket
                if(ipChanged)
                {
                    uint8_t outIpStr[17];
                    sprintf(outIpStr, "%d.%d.%d.%d", ipBytes[0], ipBytes[1], ipBytes[2], ipBytes[3]);
                    udpClose(dataHandle);
                    // Set up Data socket
                    dataHandle = udpOpen(outIpStr, MT_DATA_PORT, 0);
                }
                break;
            }
            case GET_DATA_CONFIG:
            {
                DATA_CONFIG resp;
                printf("Sending Configuration\r\n");
                resp.command = swap16(RESP_DATA_CONFIG);
                resp.interval = swap16(appConfig->dataInterval);
                resp.ipAddr = appConfig->dataIpAddr;
                udpSendArray(cmdHandle, (uint8_t*)&resp, sizeof(DATA_CONFIG));
                break;
            }
            case PUT_MODULE_CONFIG:
            {
                MODULE_CONFIG* modCon = (MODULE_CONFIG*)udpData;
                // Save new configuration
                appConfig->locZone = modCon->locZone;
                appConfig->locA = modCon->locA;
                appConfig->locB = modCon->locB;
                appConfig->locC = modCon->locC;
                AC_saveAvatarConfig(appConfig);
                // Now send response
                ACK_MESSAGE ack;
                ack.command = swap16(ACK_COMMAND);
                ack.putCmd = modCon->command;
                // Return message is just a short with 1 = good, 0 = bad
                ack.commandStatus = UPDATE_OK;
                ack.errorCode = RESP_SUCCESS;
                // Send the packet
                udpSendArray(cmdHandle, (uint8_t*)&ack, sizeof(ACK_MESSAGE));
                break;
            }
            case GET_MODULE_CONFIG:
            {
                MODULE_CONFIG modCon;
                modCon.command = swap16(RESP_MODULE_CONFIG);
                modCon.locZone = appConfig->locZone;
                modCon.locA = appConfig->locA;
                modCon.locB = appConfig->locB;
                modCon.locC = appConfig->locC;
                udpSendArray(cmdHandle, (uint8_t*)&modCon, sizeof(MODULE_CONFIG));
                break;
            }
            case PUT_DEVICE_CONFIG:
            {
                DEVICE_CONFIG* devCon = (DEVICE_CONFIG*)udpData;
                // Save new configuration
                appConfig->deviceID = devCon->deviceID;
                AC_saveAvatarConfig(appConfig);
                // Now send response
                ACK_MESSAGE ack;
                ack.command = swap16(ACK_COMMAND);
                ack.putCmd = devCon->command;
                // Return message is just a short with 1 = good, 0 = bad
                ack.commandStatus = UPDATE_OK;
                ack.errorCode = RESP_SUCCESS;
                // Send the packet
                udpSendArray(cmdHandle, (uint8_t*)&ack, sizeof(ACK_MESSAGE));
                break;
            }
            case GET_DEVICE_CONFIG:
            {
                DEVICE_CONFIG devCon;
                devCon.command = swap16(RESP_DEVICE_CONFIG);
                devCon.deviceID = appConfig->deviceID;
                udpSendArray(cmdHandle, (uint8_t*)&devCon, sizeof(DEVICE_CONFIG));
                break;
            }
            case PUT_SECOND_OF_DAY:
            {
                SECOND_OF_DAY* sod = (SECOND_OF_DAY*)udpData;
                // Save new configuration
                AT_setSecondOfTheDay(sod->secondOfDay);
                // Now send response
                ACK_MESSAGE ack;
                ack.command = swap16(ACK_COMMAND);
                ack.putCmd = sod->command;
                // Return message is just a short with 1 = good, 0 = bad
                ack.commandStatus = UPDATE_OK;
                ack.errorCode = RESP_SUCCESS;
                // Send the packet
                udpSendArray(cmdHandle, (uint8_t*)&ack, sizeof(ACK_MESSAGE));
                break;
            }
            case GET_SECOND_OF_DAY:
            {
                SECOND_OF_DAY sod;
                sod.command = swap16(RESP_SECOND_OF_DAY);
                sod.secondOfDay = AT_getSecondOfTheDay();
                udpSendArray(cmdHandle, (uint8_t*)&sod, sizeof(SECOND_OF_DAY));
                break;
            }
            default:
                printf("Invalid Command Received - 0x%04X\n\r", cmdWord);
        }
        return;
    }
}

void actuatorTask(void)
{
    uint16_t wTemp = 0;
    uint8_t*  udpData;
    wTemp = udpIsGetReady(actHandle);
    if(wTemp > 0u)
    {
        uint16_t cmdWord;
        udpGetArray(actHandle, &udpData);
        cmdWord = udpData[0];
        cmdWord *= 256;
        cmdWord += udpData[1];
        //printf("Actuator Command Received - 0x%04X\n\r", cmdWord);
        switch(cmdWord)
        {
            // Configure Wifi Connection
            case ACTUATOR_CMD:
            {
              ActuatorCmd* actCmd = (ActuatorCmd*)udpData;
              MAPP_handleActuatorCmd(actCmd);
              break;
            }
            default:
                printf("Invalid Actuator Command Received - 0x%04X\n\r", cmdWord);
        }
    }
}

void AFW_UDPDataOutTask3(void)
{
    // Don't send data in AP Mode
    if(ESP_isApMode())
    {
        return;
    }

    // Message Buffer
    NEW_DATA_OUTPUT3 msg3;

    // Make certain the socket can be written to
    uint32_t dwChecksum = 0;

    // Report Vendor and Device IDs
    msg3.vendorID = swap16(VENDOR_ID);
    msg3.deviceID = swap16(appConfig->deviceID);
    // Report Location Information
    // Need to get this from AppConfig
    msg3.locationID[0] = appConfig->locZone;
    msg3.locationID[1] = appConfig->locA;
    msg3.locationID[2] = appConfig->locB;
    msg3.locationID[3] = appConfig->locC;
    // Report Current Second and Tick
    msg3.upSecs = swap32(AT_getSecond());
    //msg3.upUsecs = swap16(AT_getTick());
    msg3.dataInterval = swap16(appConfig->dataInterval);
    
    // Report Battery and VCC Voltage
    msg3.battVolts = swap16(MAPP_getBattVoltage());
    msg3.vccVolts = swap16(MAPP_getVccVoltage());
    
    // Start of Application Data
    uint16_t msgSize = MAPP_reportData3(&msg3);
    
    dwChecksum = 0;
    uint8_t* crcData = (uint8_t*)&msg3;
    uint16_t crcSize = (uint8_t*)&msg3.checksum - (uint8_t*)&msg3;
    
    for(int i = 0; i < crcSize; i++)
    {
        dwChecksum += crcData[i]; // ADC x
    }
    msg3.checksum = swap32(dwChecksum);
    
    printf("Output Checksum = %lu\r\n", dwChecksum);
    udpSendArray(dataHandle, (uint8_t*)&msg3, msgSize);
}

ESP8266 ThingDev - Avatar Module Application file

C/C++
This file implements the Avatar Module Application. This application is both a sensor and actuator. The sensor is to sense the state of the Irrigation System's rain gauge, and the actuator is to activate a relay to simulate the rain sensor to the existing Irrigation Control unit.
/* 
 * Copyright (C) 2017 Barry Hannigan <support@miser-tech.com>
 *
 * 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 3 of the License, 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 this program.  If not, see <http://www.gnu.org/licenses/>.
 */
 
 #include <stdio.h>
#include "module_app.h"
//#include "miser_adc.h"
#include "avatar_time.h"
#include "miser_utils.h"
#include <Arduino.h>

#define SENSOR_PIN  12
#define SWITCH_PIN  13

int accum = 0;
int samples = 0;
uint32_t digital[20];

// Initialize App, Similar to Arduino setup()
void MAPP_init()
{
    // Init GPIO Pins
    pinMode(SWITCH_PIN, OUTPUT);
    pinMode(SENSOR_PIN, INPUT);
    
    // Initialize Analog To Digital
    MADC_init();
}

// Main task routines - like Arduino loop()
void MAPP_task()
{
    MADC_task();
}

// Start of a new Sample cycle
void MAPP_startCycle()
{
    MADC_start();
    accum = 0;
    samples = 0;
}

// Called when time to take next sample
void MAPP_nextSample()
{
    MADC_nextSample();
    if(samples < 20)
    {
      digital[samples] = 0;
#if 1
      // read just the Rain Sensor Pin
      digital[samples] = digitalRead(SENSOR_PIN);
#else 
      // Scan all GPIO into one 32 bit value     
      for(int i = 0; i < 32; i++)
      {
        digital[samples] |= (digitalRead(i) << i);
      }
#endif
      samples += 1;
    }
}

// Return true when sampling is done
bool MAPP_isSampleDone()
{
    //return MADC_isSampleDone();
    return !(samples < 20);
}

// Return true if app is a sensor
bool MAPP_isSensor()
{
    return true;
}

// Return true if app is an actuator
bool MAPP_isActuator()
{
    return true;
}

// Return true if app is battery powered
bool MAPP_isBatteryPowered()
{
    return false;
}

// Return Battery voltage in milliVolts
uint16_t MAPP_getBattVoltage()
{
    //return MADC_getVoltage(BATT);
    return 0;
}

// Return Current VCC Voltage
uint16_t MAPP_getVccVoltage()
{
    return MADC_getVccVoltage();
}

// Report Sensor data
uint16_t MAPP_reportData3(NEW_DATA_OUTPUT3* msg3)
{
    // Get Application specific Data
    uint16_t appSize = sizeof(digital);
    uint32_t* payload = &msg3->appData[0];
    for(int i = 0; i < (appSize/sizeof(digital[0])); i++)
    {
      payload[i] = swap32(digital[i]);
      //printf("appData[%d] = %d\n", i, payload[i]);
    }
    msg3->appDataSize = swap16(appSize);
    // Return the size of this message
    return sizeof(NEW_DATA_OUTPUT3) - (MAX_APP_DATA_SIZE - appSize);
}

// Handle a new actuator command
void MAPP_handleActuatorCmd(ActuatorCmd* msg)
{
  printf("Actuator Digital = %d\n",swap32(msg->digital));
  digitalWrite(SWITCH_PIN, msg->digital);
}

Credits

Barry Hannigan

Barry Hannigan

0 projects • 3 followers
Embedded C/C++ programming for 30+ years. Java programming 5 years.

Comments