Hardware components | ||||||
| × | 1 | ||||
| × | 1 | ||||
| × | 1 | ||||
| × | 1 | ||||
| × | 1 | ||||
Software apps and online services | ||||||
| ||||||
|
My idea for this contest is to create a "Smarter Irrigation Controller". While new irrigation systems are smarter and using more information to control watering of lawns and shrubs, there are millions of existing irrigation systems installed that use lesser technology. For example I own a 15 year old system that is equipped with a rain sensor that closes a switch when sufficient rainfall has occurred and keeps it closed until enough evaporation has occurred to open the switch. My irrigation controller disables watering cycles while the rain sensor switch is closed.
This scheme works reasonably well, however it's based on previous conditions not current and future conditions. Some times in the morning while watering a large weather system might be moving in, the watering cycle may complete before the rain sensor sees enough moisture to shut down the watering, then it may rain a few inches later in the day, or perhaps the next day.
Making a decisionAs with just about any decision, the more information you have the better decision you can make. So to build a smarter irrigation controller I want to wedge an ESP8266 ThingDev between the rainfall sensor and the irrigation controller. This will allow more than just the rainfall sensor to decide on disabling or enabling the watering cycle. The ThingDev will drive a relay posing as the rainfall sensor cutoff switch. This will allow me to insert a smart watering algorithm to control the water cycle cutoff switch.
Additional sensors will include rainfall, temperature, humidity and wind data from my own personal weather station (PWS), predicted future rainfall amounts from Wunderground along with the existing rain sensor switch. My goal is to use all this input data to make a better decision on whether to water at the scheduled time and that the solution works as an upgrade to existing irrigation systems that only have a rainfall sensor kill switch.
Physical DeploymentThe system will be locally distributed inside the firewall across an ESP8266 Thing Dev, Raspberry Pi, as well as an existing PIC based personal weather station. Outside the firewall will be the Wunderground weather service and Mydevices Cayenne as the remote GUI control. While the Thing Dev could connect to all the pieces of the system itself, I want this to be a learning exercise in distributed control systems, so I’m putting the heavy lifting algorithm on the Raspberry Pi.
ESP8266 Thing Dev – This module will be co-located with the Irrigation Controller so it can easily monitor the rain sensor and control the rain sensor cutoff input. The Thing Dev will be both a sensor and actuator by sending rain sensor state every 2 seconds and accepting relay actuator commands.
Raspberry Pi – The Raspberry Pi will listen to sensor messages from both the personal weather station and the Thing Dev. The main irrigation control algorithm will run on the Raspberry Pi and will be coded in Java. Additionally it will retrieve updated predicted rainfall amounts every 6 hours from Wunderground. Also it will interface to Cayenne as its GUI control and status display.
Risk Assessment and Safety ConsiderationsThe following diagram shows the flow of processing to gather the necessary inputs to calculate a state for the Rain Sensor shutoff signal. Notice there is a legend that indicates black items are local network dependency and orange items have an external internet entity dependency. If you're playing around with a circuit and want to show Mom and Dad or your friends that you can blink an LED by pressing a button on your smart phone by all means go for the joy of doing so. However if your controlling real world devices you should take the time to consider what could go wrong if your internet access or the remote service is down. If your device gets separated from the internet will it burn down your house or possibly injure someone, or cause damage that would be a large expense to repair? Even if you protect yourself from internet outages, ask yourself do all features of your system need to be controlled remotely. Remember there are 2 types of servers on the internet, those that will be hacked and those that have been hacked and will be hacked again. Is it worth the risk that some Yahoo Hacker could have a fun time thrashing your control system around while bored on a Friday night?
Now in my case Death or Injury is not a concern because it's just an irrigation system being controlled. However, if the system got stuck disabling the irrigation and I was away for a week it could be a costly repair of new sod for the lawn. So with monetary risk on the table it is prudent to design the system in such a way that it will continue to operate without internet connectivity. For instance if no future weather information is available then it can fall back to using just previous rainfall and the current status of the rain sensor switch.
Raspberry Pi - Main Java Class
Java/*
* 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/*
* 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/*
* 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++/*
* 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++/*
* 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++/*
* 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++/*
* 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);
}
Comments