// Revision history
// 1.2 Update all code to be compatible with Arduino 1.0 2012/1/23
// 1.1 changed sample_rate to 32-bit, max = 4294966 seconds (47 days)
// RAM is also getting tight, so removed some error strings and added RAM gauge to menu 2011/08/01
// 1.0 initial release, 2011/06/27
// firmware version
const char version_major = 1;
const char version_minor = 2;
// external libraries
#include <SHT1x.h> // SHT15 humidity sensor library
#include <SFE_BMP085.h> // BMP085 pressure sensor library
#include <Wire.h> // I2C library (necessary for pressure sensor)
#include <avr/eeprom.h> // extended EEPROM read/write functions
#include <WiFlyHQ.h> //Wifly
//WiFLy
WiFly wifly;
const char mySSID[] = "home";
const char myPassword[] = "crap#123";
void sendIndex();
void sendGreeting(char *name);
void send404();
char buf[80];
// digital I/O pins
// (the following three are predefined)
// const int SCK = 13;
// const int MISO = 12;
// const int MOSI = 11;
const int XCLR = 9;
const int EOC = 8;
const int RF_RTS = 6;
const int RF_CTS = 5;
const int STATUSLED = 4;
const int WSPEED = 3;
const int RAIN = 2;
// analog I/O pins
const int LIGHT = 7;
const int BATT_LVL = 6;
const int WDIR = 0;
// global variables
float SHT15_humidity;
float SHT15_temp;
float SHT15_dewpoint;
double BMP085_pressure;
double BMP085_temp;
float TEMT6000_light;
float WM_wspeed;
float WM_wdirection;
float WM_rainfall = 0.0;
float batt_volts;
int LED = 0; // status LED
unsigned int windRPM, stopped;
// volatiles are subject to modification by IRQs
volatile unsigned long tempwindRPM, windtime, windlast, windinterval;
volatile unsigned char windintcount;
volatile boolean gotwspeed;
volatile unsigned long raintime, rainlast, raininterval, rain;
// constant conversion factors
const int BATT_RATIO = 63.3271; // divide ADC from BATT_LVL by this to get volts
const float WIND_RPM_TO_MPH = 22.686745; // divide RPM by this for velocity
const float WIND_RPM_TO_MPS = 50.748803; // divide RPM by this for meters per second
const float RAIN_BUCKETS_TO_INCHES = 0.0086206896; // multiply bucket tips by this for inches
const float RAIN_BUCKETS_TO_MM = 0.21896551; // multiply bucket tips by this for mm
const unsigned int ZERODELAY = 4000; // ms, zero RPM if no result for this time period (see irq below)
// sensor objects
SHT1x humidity_sensor(A4, A5);
SFE_BMP085 pressure_sensor(BMP_ADDR);
// enumerated options, changed in user menu
// output format
const int CSV = 1; // default NMEA-like comma-separated values
const int ANSI = 2; // ANSI-formatted data with hardware testing
const int LCD = 3; // directly drive a SparkFun serial-enabled 16x2 LCD display
// general units
const int ENGLISH = 1; // wind speed in miles per hour, rain in inches, temperature in degrees Fahrenheit
const int SI = 2; // International System, aka the metric system. Wind speed in meters per second, rain in mm, temperature in degrees Celsius
// pressure units
const int MBAR = 1; // millibars
const int INHG = 2; // inches of mercury (US weather report standard)
const int PSI = 3; // pounds per square inch
// pressure type
const int ABSOLUTE = 1; // absolute (true) pressure, changes with altitude (ignore altitude variable)
const int RELATIVE = 2; // relative (weather) pressure, altitude effects removed (use altitude variable)
// defaults, replaced with EEPROM settings if saved
int data_format = ANSI;
int general_units = SI;
unsigned long sample_rate = 2; // sample rate (seconds per sample, 0 for as fast as possible)
int pressure_type = ABSOLUTE;
long altitude = 1596; // fixed weather station altitude in meters, for relative (sea level) pressure measurement
int pressure_units = MBAR;
boolean weather_meters_attached = false; // true if we've hooked up SparkFun's Weather Meters (SEN-08942) (set to false to remove weather meters data from output)
long baud_rate = 9600; // default baud rate
// hardware memory pointers, used by freeMemory() (see: http://www.arduino.cc/playground/Code/AvailableMemory)
extern unsigned int __bss_end;
extern unsigned int __heap_start;
extern void *__brkval;
int freeMemory()
{
int free_memory;
if((int)__brkval == 0)
free_memory = ((int)&free_memory) - ((int)&__bss_end);
else
free_memory = ((int)&free_memory) - ((int)__brkval);
return free_memory;
}
// interrupt routines (these are called by the hardware interrupts, not by the main code)
void rainIRQ()
// if the Weather Meters are attached, count rain gauge bucket tips as they occur
// activated by the magnet and reed switch in the rain gauge, attached to input D2
{
raintime = micros(); // grab current time
raininterval = raintime - rainlast; // calculate interval between this and last event
if (raininterval > 100) // ignore switch-bounce glitches less than 100uS after initial edge
{
rain++; // increment bucket counter
rainlast = raintime; // set up for next event
}
}
void wspeedIRQ()
// if the Weather Meters are attached, measure anemometer RPM (2 ticks per rotation), set flag if RPM is updated
// activated by the magnet in the anemometer (2 ticks per rotation), attached to input D3
// this routine measures RPM by measuring the time between anemometer pulses
// windintcount is the number of pulses we've measured - we need two to measure one full rotation (eliminates any bias between the position of the two magnets)
// when windintcount is 2, we can calculate the RPM based on the total time from when we got the first pulse
// note that this routine still needs an outside mechanism to zero the RPM if the anemometer is stopped (no pulses occur within a given period of time)
{
windtime = micros(); // grab current time
if ((windintcount == 0) || ((windtime - windlast) > 10000)) // ignore switch-bounce glitches less than 10ms after the reed switch closes
{
if (windintcount == 0) // if we're starting a new measurement, reset the interval
windinterval = 0;
else
windinterval += (windtime - windlast); // otherwise, add current interval to the interval timer
if (windintcount == 2) // we have two measurements (one full rotation), so calculate result and start a new measurement
{
tempwindRPM = (60000000ul / windinterval); // calculate RPM (temporary since it may change unexpectedly)
windintcount = 0;
windinterval = 0;
gotwspeed = true; // set flag for main loop
}
windintcount++;
windlast = windtime; // save the current time so that we can calculate the interval between now and the next interrupt
}
}
void setup()
// this procedure runs once upon startup or reboot
// perform all the settings we need before running the main loop
{
// set up inputs and outputs
pinMode(XCLR,OUTPUT); // output to BMP085 reset (unused)
digitalWrite(XCLR,HIGH); // make pin high to turn off reset
pinMode(EOC,INPUT); // input from BMP085 end of conversion (unused)
digitalWrite(EOC,LOW); // turn off pullup
pinMode(STATUSLED,OUTPUT); // output to status LED
pinMode(WSPEED,INPUT); // input from wind meters windspeed sensor
digitalWrite(WSPEED,HIGH); // turn on pullup
pinMode(RAIN,INPUT); // input from wind meters rain gauge sensor
digitalWrite(RAIN,HIGH); // turn on pullup
// get settings from EEPROM (use defaults if EEPROM has not been used)
retrieveEEPROMsettings();
// initialize serial port
Serial.begin(baud_rate);
Serial.println();
Serial.println("RESET");
// initialize BMP085 pressure sensor
if (pressure_sensor.begin() == 0)
error(1);
// init wind speed interrupt global variables
gotwspeed = false; windRPM = 0; windintcount = 0;
// blink status LED 3 times
digitalWrite(STATUSLED,HIGH);
delay(100);
digitalWrite(STATUSLED,LOW);
delay(250);
digitalWrite(STATUSLED,HIGH);
delay(100);
digitalWrite(STATUSLED,LOW);
delay(250);
digitalWrite(STATUSLED,HIGH);
delay(100);
digitalWrite(STATUSLED,LOW);
delay(250);
if (weather_meters_attached)
{
// attach external interrupt pins to IRQ functions
attachInterrupt(0,rainIRQ,FALLING);
attachInterrupt(1,wspeedIRQ,FALLING);
// turn on interrupts
interrupts();
}
}
void loop()
// loops forever after setup() ends
{
static long templong, windstopped;
static unsigned long loopstart, loopend;
double Tn, m;
static char LCDstate;
char status;
// record current time so we can sample at regular intervals
loopstart = millis();
loopend = loopstart + (sample_rate * 1000ul);
// turn on LED while we're doing measurements
digitalWrite(STATUSLED,HIGH);
// an interrupt occurred, handle it now
if (gotwspeed)
{
gotwspeed = false;
windRPM = word(tempwindRPM); // grab the RPM value calculated by the interrupt routine
windstopped = millis() + ZERODELAY; // save this timestamp
}
// zero wind speed RPM if we don't get a reading in ZERODELAY ms
if (millis() > windstopped)
{
windRPM = 0; windintcount = 0;
}
TWCR &= ~(_BV(TWEN)); // turn off I2C enable bit so we can access the SHT15 humidity sensor
// get humidity and temp (SHT15)
SHT15_temp = humidity_sensor.readTemperatureC();
SHT15_humidity = humidity_sensor.readHumidity();
// compute dewpoint (because we can!)
if (SHT15_temp > 0.0)
{Tn = 243.12; m = 17.62;}
else
{Tn = 272.62; m = 22.46;}
SHT15_dewpoint = (Tn*(log(SHT15_humidity/100)+((m*SHT15_temp)/(Tn+SHT15_temp)))/(m-log(SHT15_humidity/100)-((m*SHT15_temp)/(Tn+SHT15_temp))));
// get temp (SHT15)
switch (general_units)
{
case ENGLISH: // Fahrenheit
SHT15_temp = (SHT15_temp*9.0/5.0)+32.0;
SHT15_dewpoint = (SHT15_dewpoint*9.0/5.0)+32.0;
break;
case SI: // celsius, don't need to do anything
break;
default:
SHT15_temp = -1.0; // error, invalid units
SHT15_dewpoint = -1.0; // error, invalid units
}
TWCR |= _BV(TWEN); // turn on I2C enable bit so we can access the BMP085 pressure sensor
// start BMP085 temperature reading
status = pressure_sensor.startTemperature();
if (status != 0)
delay(status); // if nonzero, status is number of ms to wait for reading to become available
else
error(2);
// retrieve BMP085 temperature reading
status = pressure_sensor.getTemperature(&BMP085_temp); // deg C
if (status == 0)
error(3);
// start BMP085 pressure reading
status = pressure_sensor.startPressure(3);
if (status != 0)
delay(status); // if nonzero, status is number of ms to wait for reading to become available
else
error(4);
// retrieve BMP085 pressure reading
status = pressure_sensor.getPressure(&BMP085_pressure, &BMP085_temp); // mbar, deg C
if (status == 0)
error(5);
// compensate for altitude if needed
if (pressure_type == RELATIVE)
BMP085_pressure = pressure_sensor.sealevel(BMP085_pressure,altitude);
// convert to desired units
switch (general_units)
{
case SI: // celsius
// do nothing, already C
break;
case ENGLISH: // Fahrenheit
BMP085_temp = BMP085_temp * 1.8 + 32.0;
break;
default:
BMP085_temp = -1.0; // error, invalid units
}
switch (pressure_units)
{
case MBAR:
// do nothing, already mbar
break;
case INHG:
BMP085_pressure = BMP085_pressure / 33.8600000;
break;
case PSI:
BMP085_pressure = BMP085_pressure / 68.9475728;
break;
default:
BMP085_pressure = -1.0; // error, invalid units
}
// get light
TEMT6000_light = (1023.0 - float(analogRead(LIGHT))) / 10.23; // 0-100 percent
// windspeed unit conversion
switch (general_units)
{
case SI: // meters per second
WM_wspeed = float(windRPM) / WIND_RPM_TO_MPS;
break;
case ENGLISH: // miles per hour
WM_wspeed = float(windRPM) / WIND_RPM_TO_MPH;
break;
default:
WM_wspeed = -1.0; // error, invalid units
}
// get wind direction
WM_wdirection = get_wind_direction();
// rainfall unit conversion
switch (general_units)
{
case SI: // mm
WM_rainfall = rain * RAIN_BUCKETS_TO_MM;
break;
case ENGLISH: // inches
WM_rainfall = rain * RAIN_BUCKETS_TO_INCHES;
break;
default:
WM_rainfall = -1.0; // error, invalid units
}
// get battery voltage
batt_volts = float(analogRead(BATT_LVL)) / BATT_RATIO;
// below are a bunch of nested switch statements to handle the different output data_formats that are possible
// feel free to modify them or add your own!
switch (data_format)
{
case CSV: // data_format: comma-separated values
{
// number after values is number of digits after decimal point to print
Serial.print("$");
printComma();
Serial.print(SHT15_temp,1);
printComma();
Serial.print(SHT15_humidity,0);
printComma();
Serial.print(SHT15_dewpoint,1);
printComma();
switch (pressure_units) // change decimal point for different units
{
case MBAR:
Serial.print(BMP085_pressure,2);
break;
case INHG:
Serial.print(BMP085_pressure,3);
break;
case PSI:
Serial.print(BMP085_pressure,4);
break;
}
printComma();
// Serial.print(BMP085_temp,1); // for CSV format, we'll only output the SHT15 temperature
// Serial.print(",");
Serial.print(TEMT6000_light,1);
printComma();
if (weather_meters_attached)
{
Serial.print(WM_wspeed,1);
printComma();
Serial.print(WM_wdirection,0);
printComma();
switch (general_units) // change decimal point for different units
{
case ENGLISH:
Serial.print(WM_rainfall,2);
break;
case SI:
Serial.print(WM_rainfall,1);
break;
}
printComma();
}
Serial.print(batt_volts,2);
printComma();
Serial.print("*");
Serial.println();
}
break;
case ANSI: // this data_format neatly formats the data on an ANSI terminal, and has pass/fail values for testing
{
ansiHome();
Serial.println();
Serial.print("SHT15 temperature:");
ansiTab();
ansiTab();
Serial.print(SHT15_temp,1);
Serial.print(" deg ");
switch (general_units)
{
case ENGLISH:
Serial.print("F ");
ansiTab();
if ((SHT15_temp > 60) && (SHT15_temp < 85)) pass(); else fail();
break;
case SI:
Serial.print("C ");
ansiTab();
if ((SHT15_temp > 15) && (SHT15_temp < 30)) pass(); else fail();
break;
}
Serial.print("SHT15 humidity: ");
ansiTab();
ansiTab();
Serial.print(SHT15_humidity,0);
Serial.print("% ");
ansiTab();
ansiTab();
if ((SHT15_humidity > 10) && (SHT15_humidity < 90)) pass(); else fail();
Serial.print("SHT15 dewpoint: ");
ansiTab();
ansiTab();
Serial.print(SHT15_dewpoint,1);
Serial.print(" deg ");
switch (general_units)
{
case ENGLISH:
Serial.println("F ");
break;
case SI:
Serial.println("C ");
break;
}
Serial.print("BMP085 pressure:");
ansiTab();
ansiTab();
switch (pressure_units) // change decimal point for different units
{
case MBAR:
Serial.print(BMP085_pressure,2);
Serial.print(" mbar ");
ansiTab();
if ((BMP085_pressure > 900) && (BMP085_pressure < 1100)) pass(); else fail();
break;
case INHG:
Serial.print(BMP085_pressure,3);
Serial.print(" in Hg ");
ansiTab();
if ((BMP085_pressure > 25) && (BMP085_pressure < 35)) pass(); else fail();
break;
case PSI:
Serial.print(BMP085_pressure,4);
Serial.print(" PSI ");
ansiTab();
if ((BMP085_pressure > 13) && (BMP085_pressure < 15)) pass(); else fail();
break;
}
Serial.print("BMP085 temperature:");
ansiTab();
ansiTab();
Serial.print(BMP085_temp,1);
Serial.print(" deg ");
switch (general_units)
{
case ENGLISH:
Serial.print("F ");
ansiTab();
if ((BMP085_temp > 60) && (BMP085_temp < 90)) pass(); else fail();
break;
case SI:
Serial.print("C ");
ansiTab();
if ((BMP085_temp > 15) && (BMP085_temp < 35)) pass(); else fail();
break;
}
Serial.print("TEMT6000 light: ");
ansiTab();
ansiTab();
Serial.print(TEMT6000_light,1);
Serial.print("% ");
ansiTab();
ansiTab();
if ((TEMT6000_light > 0) && (TEMT6000_light < 100)) pass(); else fail();
if (weather_meters_attached)
{
Serial.print("Weather meters wind speed:");
ansiTab();
Serial.print(WM_wspeed,1);
switch (general_units)
{
case ENGLISH:
Serial.print(" MPH ");
ansiTab();
if (WM_wspeed > 0.0) pass(); else fail();
break;
case SI:
Serial.print(" m/s ");
ansiTab();
ansiTab();
if (WM_wspeed > 0.0) pass(); else fail();
break;
}
Serial.print("Weather meters wind direction:");
ansiTab();
Serial.print(WM_wdirection,0);
Serial.print(" degrees ");
ansiTab();
// direction will read -1 if wind direction sensor is disconnected or faulty
if (WM_wdirection != -1) pass(); else fail();
Serial.print("Weather meters rainfall:");
ansiTab();
switch (general_units)
{
case ENGLISH:
Serial.print(WM_rainfall,2);
Serial.print(" inches ");
ansiTab();
if (WM_rainfall > 0.05) pass(); else fail();
break;
case SI:
Serial.print(WM_rainfall,0);
Serial.print(" mm ");
ansiTab();
ansiTab();
if (WM_rainfall > 0.5) pass(); else fail();
break;
}
}
Serial.print("External power: ");
ansiTab();
ansiTab();
Serial.print(batt_volts,2);
Serial.print(" Volts ");
ansiTab();
if ((batt_volts > 3.5) && (batt_volts < 13.0)) pass(); else fail();
Serial.println();
}
break;
case LCD: // this data_format will directly drive a SparkFun serial-enabled 16x2 LCD
// since you can't show everything at once on a small LCD display, this routine rotates through the values,
// displaying a different set every time we pass through the loop() using LCDstate to keep track of where it is
// to change the display rate, change the sample rate in the menu
{
LCDclear();
switch (LCDstate)
{
case 0:
LCDline1();
Serial.print("temp: ");
Serial.print(SHT15_temp,1);
switch (general_units)
{
case ENGLISH:
Serial.print(" F");
break;
case SI:
Serial.print(" C");
break;
}
LCDline2();
Serial.print("humid: ");
Serial.print(SHT15_humidity,0);
Serial.print("%");
break;
case 1:
LCDline1();
Serial.print("baro: ");
switch (pressure_units) // change decimal point for different units
{
case MBAR:
Serial.print(BMP085_pressure,2);
Serial.print(" mb");
break;
case INHG:
Serial.print(BMP085_pressure,3);
Serial.print(" in");
break;
case PSI:
Serial.print(BMP085_pressure,3);
Serial.print(" PSI");
break;
}
LCDline2();
Serial.print("dewp: ");
Serial.print(SHT15_dewpoint,1);
switch (general_units)
{
case ENGLISH:
Serial.print(" F");
break;
case SI:
Serial.print(" C");
break;
}
break;
case 2:
LCDline1();
Serial.print("wind: ");
Serial.print(WM_wspeed,1);
switch (general_units)
{
case ENGLISH:
Serial.print(" MPH");
break;
case SI:
Serial.print(" m/s");
break;
}
LCDline2();
Serial.print("dir: ");
Serial.print(WM_wdirection,0);
Serial.print(" deg");
break;
case 3:
LCDline1();
Serial.print("rain: ");
switch (general_units)
{
case ENGLISH:
Serial.print(WM_rainfall,2);
Serial.print(" in");
break;
case SI:
Serial.print(WM_rainfall,0);
Serial.print(" mm");
break;
}
LCDline2();
Serial.print("light: ");
Serial.print(TEMT6000_light,1);
Serial.print("%");
break;
}
LCDstate++;
// reset LCDstate to 0 depending on whether we want to show the Weather Meters data or not
if ((weather_meters_attached && (LCDstate > 3)) || (!weather_meters_attached && (LCDstate > 1)))
LCDstate = 0;
break;
}
}
// turn off LED (done with measurements)
digitalWrite(STATUSLED,LOW);
// we're done sampling all the sensors and printing out the results
// now wait in a loop for the next sample time
// while we're waiting, we'll check the serial port to see if the user has pressed CTRL-Z to activate the menu
do // this is a rare instance of do-while - we need to run through this loop at least once to see if CTRL-Z has been pressed
{
while (Serial.available())
{
if (Serial.read() == 0x1A) // CTRL-Z
{
menu(); // display the menu and allow settings to be changed
loopend = millis(); // we're done with the menu, break out of the do-while
}
}
}
while(millis() < loopend);
}
float get_wind_direction()
// read the wind direction sensor, return heading in degrees
{
unsigned int adc;
adc = analogRead(WDIR); // get the current reading from the sensor
// The following table is ADC readings for the wind direction sensor output, sorted from low to high.
// Each threshold is the midpoint between adjacent headings. The output is degrees for that ADC reading.
// Note that these are not in compass degree order! See Weather Meters datasheet for more information.
if (adc < 380) return (112.5);
if (adc < 393) return (67.5);
if (adc < 414) return (90);
if (adc < 456) return (157.5);
if (adc < 508) return (135);
if (adc < 551) return (202.5);
if (adc < 615) return (180);
if (adc < 680) return (22.5);
if (adc < 746) return (45);
if (adc < 801) return (247.5);
if (adc < 833) return (225);
if (adc < 878) return (337.5);
if (adc < 913) return (0);
if (adc < 940) return (292.5);
if (adc < 967) return (315);
if (adc < 990) return (270);
return (-1); // error, disconnected?
}
/* From Weather Meters docs and the Weather Board V3 schematic:
heading resistance volts nominal midpoint (<)
112.5 º 0.69 k 1.2 V 372 counts 380
67.5 º 0.89 k 1.26 V 389 counts 393
90 º 1 k 1.29 V 398 counts 414
157.5 º 1.41 k 1.39 V 430 counts 456
135 º 2.2 k 1.56 V 483 counts 508
202.5 º 3.14 k 1.72 V 534 counts 551
180 º 3.9 k 1.84 V 569 counts 615
22.5 º 6.57 k 2.13 V 661 counts 680
45 º 8.2 k 2.26 V 700 counts 746
247.5 º 14.12 k 2.55 V 792 counts 801
225 º 16 k 2.62 V 811 counts 833
337.5 º 21.88 k 2.76 V 855 counts 878
0 º 33 k 2.91 V 902 counts 913
292.5 º 42.12 k 2.98 V 925 counts 940
315 º 64.9 k 3.08 V 956 counts 967
270 º 98.6 k 3.15 V 978 counts >967
*/
void sendIndex()
{
/* Send the header direclty with print */
wifly.println(F("HTTP/1.1 200 OK"));
wifly.println(F("Content-Type: text/html"));
wifly.println(F("Transfer-Encoding: chunked"));
wifly.println();
/* Send the body using the chunked protocol so the client knows when
* the message is finished.
* Note: we're not simply doing a close() because in version 2.32
* firmware the close() does not work for client TCP streams.
*/
wifly.sendChunkln(F("<html>"));
wifly.sendChunkln(F("<title>WiFly HTTP Server Example</title>"));
wifly.sendChunkln(F("<h1>"));
wifly.sendChunkln(F("<p>Hello</p>"));
wifly.sendChunkln(F("</h1>"));
wifly.sendChunkln(F("<form name=\"input\" action=\"/\" method=\"post\">"));
wifly.sendChunkln(F("Username:"));
wifly.sendChunkln(F("<input type=\"text\" name=\"user\" />"));
wifly.sendChunkln(F("<input type=\"submit\" value=\"Submit\" />"));
wifly.sendChunkln(F("</form>"));
wifly.sendChunkln(F("</html>"));
wifly.sendChunkln();
}
void sendGreeting(char *name)
{
/* Send the header directly with print */
wifly.println(F("HTTP/1.1 200 OK"));
wifly.println(F("Content-Type: text/html"));
wifly.println(F("Transfer-Encoding: chunked"));
wifly.println();
/* Send the body using the chunked protocol so the client knows when
* the message is finished.
*/
wifly.sendChunkln(F("<html>"));
wifly.sendChunkln(F("<title>WiFly HTTP Server Example</title>"));
/* No newlines on the next parts */
wifly.sendChunk(F("<h1><p>Hello "));
wifly.sendChunk(name);
/* Finish the paragraph and heading */
wifly.sendChunkln(F("</p></h1>"));
/* Include a reading from Analog pin 0 */
snprintf_P(buf, sizeof(buf), PSTR("<p>Analog0=%d</p>"), analogRead(A0));
wifly.sendChunkln(buf);
wifly.sendChunkln(F("</html>"));
wifly.sendChunkln();
}
void send404()
{
wifly.println(F("HTTP/1.1 404 Not Found"));
wifly.println(F("Content-Type: text/html"));
wifly.println(F("Transfer-Encoding: chunked"));
wifly.println();
wifly.sendChunkln(F("<html><head>"));
wifly.sendChunkln(F("<title>404 Not Found</title>"));
wifly.sendChunkln(F("</head><body>"));
wifly.sendChunkln(F("<h1>Not Found</h1>"));
wifly.sendChunkln(F("<hr>"));
wifly.sendChunkln(F("</body></html>"));
wifly.sendChunkln();
}
void menu()
// provide the user a way to modify settings, and save those settings to EEPROM to survive reboot / shutdown
{
boolean done = false;
char choice;
long templong;
// print out a menu of choices, with current settings in (parenthesis)
Serial.println();
Serial.print("SparkFun USB Weather Board V3 firmware version ");
Serial.print(version_major,DEC); Serial.print("."); Serial.println(version_minor,DEC);
Serial.print("free RAM: "); Serial.print(freeMemory()); Serial.println(" bytes");
Serial.println();
while (!done)
{
Serial.print("1. Data format (");
switch (data_format)
{
case CSV: Serial.print("CSV"); break;
case ANSI: Serial.print("ANSI"); break;
case LCD: Serial.print("LCD"); break;
}
Serial.println(")");
Serial.print("2. General units (");
switch (general_units)
{
case ENGLISH: Serial.print("English"); break;
case SI: Serial.print("SI"); break;
}
Serial.println(")");
Serial.print("3. Sample rate (");
Serial.print(sample_rate,DEC);
Serial.println(")");
Serial.print("4. Pressure units (");
switch (pressure_units)
{
case MBAR: Serial.print("mbar"); break;
case INHG: Serial.print("inches Hg"); break;
case PSI: Serial.print("PSI"); break;
}
Serial.println(")");
Serial.print("5. Pressure type (");
switch (pressure_type)
{
case RELATIVE: Serial.print("relative"); break;
case ABSOLUTE: Serial.print("absolute"); break;
}
Serial.println(")");
Serial.print("6. Station altitude (");
Serial.print(altitude,DEC);
Serial.println(" meters)");
Serial.print("7. Baud rate (");
Serial.print(baud_rate,DEC);
Serial.println(" baud)");
Serial.println("8. Zero rain counter");
Serial.print("9. Weather Meters attached (");
if (weather_meters_attached)
Serial.println("yes)");
else
Serial.println("no)");
Serial.println("X. Exit (don't save changes to EEPROM)");
Serial.println("S. Save (save changes to EEPROM)");
Serial.println();
// wait for user input from serial port, and act on that input
// many of these are submenus, some require entering a number
choice = getChar(); // note that this will uppercase the character for you
switch (choice)
{
case '1':
Serial.println("1. CSV");
Serial.println("2. ANSI");
Serial.println("3. LCD");
Serial.println();
data_format = getChar() - '0';
break;
case '2':
Serial.println("1. English");
Serial.println("2. SI (metric)");
Serial.println();
general_units = getChar() - '0';
break;
case '3':
Serial.print("Enter sample rate (every x seconds): ");
templong = getLong();
if (templong > 4294966ul)
{
Serial.println();
Serial.println();
Serial.println("SAMPLE RATE TOO LARGE (max = 4294966 seconds)");
Serial.println();
}
else
{
sample_rate = templong;
Serial.println();
Serial.println();
}
break;
case '4':
Serial.println("1. mbar");
Serial.println("2. inches Hg");
Serial.println("3. PSI");
Serial.println();
pressure_units = getChar() - '0';
break;
case '5':
Serial.println("1. absolute");
Serial.println("2. relative");
Serial.println();
pressure_type = getChar() - '0';
reboot();
break;
case '6':
Serial.print("Enter altitude in integer meters: ");
altitude = getLong();
Serial.println();
Serial.println();
reboot();
break;
case '7':
Serial.print("Enter baud rate: ");
templong = getLong();
Serial.println();
Serial.println();
// make SURE value entered is one of the valid baud rates (otherwise it will be impossible to talk to the board!)
if ((templong == 300) || (templong == 1200) || (templong == 2400) || (templong == 4800) || (templong == 9600) || (templong == 19200) || (templong == 38400) || (templong == 57600) || (templong == 115200))
{
baud_rate = templong;
reboot();
}
else
{
Serial.println("INVALID BAUD RATE (use 300, 1200, 2400, 4800, 9600, 19200, 38400, 57600, 115200)");
Serial.println();
}
break;
case '8':
rain = 0;
Serial.println("Rain counter zeroed");
Serial.println();
break;
case '9':
Serial.println("1. yes");
Serial.println("2. no");
Serial.println();
weather_meters_attached = (getChar() == '1');
break;
case 'S':
storeEEPROMsettings();
// no break here! we want to fall through the 'Q' choice and quit
case 'X':
done = true;
break;
}
}
}
char getChar()
// wait for one character to be typed (and convert to uppercase if it's alphabetic)
{
digitalWrite(STATUSLED,HIGH);
while (!Serial.available())
{;} // wait here forever for a character
digitalWrite(STATUSLED,LOW);
return(toupper(Serial.read())); // return the upper case character
}
long getLong()
// wait for a number to be input (end with return), allows backspace and negative
{
char mystring[10];
char mychar;
int x = 0;
boolean done = false;
// input a string of characters from the user
while (!done)
{
mychar = getChar();
if ((mychar == 0x0D) || (mychar == 0x0A)) // carriage return or line feed?
{
// terminate the string with 0x00 and exit
mystring[x] = 0;
done = true;
}
else
{
if ((mychar == 0x08) && (x > 0)) // backspace?
{
// simulate a backspace - back up, print a space to erase character, and backspace again
Serial.write(0x08);
Serial.print(" ");
Serial.write(0x08);
x--;
}
else // a real character?
{
// if ((mychar != 0x08) && (x < 10))
if (x < 10)
{
Serial.print(mychar);
mystring[x] = mychar;
x++;
}
}
}
}
// convert string to long using ASCII-to-long standard function
return(atol(mystring));
}
void storeEEPROMsettings()
// store all the user-changable settings in EEPROM so it survives power cycles
// note that ints are two bytes, longs are four
{
eeprom_write_word((uint16_t*)0,data_format);
eeprom_write_word((uint16_t*)2,general_units);
// eeprom_write_word((uint16_t*)4,sample_rate); // this space available for an int (changed sample rate to long)
eeprom_write_word((uint16_t*)6,pressure_units);
eeprom_write_word((uint16_t*)8,pressure_type);
eeprom_write_dword((uint32_t*)10,altitude);
eeprom_write_dword((uint32_t*)14,baud_rate);
eeprom_write_word((uint16_t*)18,(int)weather_meters_attached);
eeprom_write_dword((uint32_t*)22,sample_rate);
}
void retrieveEEPROMsettings()
// retrieve settings previously stored in EEPROM
// only load into variables if the settings are valid (-1 == 0xFF == erased)
// note that ints are two bytes, longs are four
{
int tempint;
long templong;
// don't initialize variables if EEPROM is unused
// (uninitialized EEPROM values read back as -1)
tempint = eeprom_read_word((uint16_t*)0); if (tempint != -1) data_format = tempint;
tempint = eeprom_read_word((uint16_t*)2); if (tempint != -1) general_units = tempint;
// tempint = eeprom_read_word((uint16_t*)4); if (tempint != -1) sample_rate = tempint; // this space available for an int (changed sample rate to long)
tempint = eeprom_read_word((uint16_t*)6); if (tempint != -1) pressure_units = tempint;
tempint = eeprom_read_word((uint16_t*)8); if (tempint != -1) pressure_type = tempint;
templong = eeprom_read_dword((uint32_t*)10); if (templong != -1) altitude = templong;
templong = eeprom_read_dword((uint32_t*)14); if (templong != -1) baud_rate = templong;
tempint = eeprom_read_word((uint16_t*)18); if (tempint != -1) weather_meters_attached = (boolean)tempint;
templong = eeprom_read_dword((uint32_t*)22); if (templong != -1) sample_rate = templong;
}
void ansiClear()
// send ANSI clear screen command
// used by ANSI output format
{
// send ESC [2J
// which will clear the screen on an ANSI terminal
Serial.write(27);
Serial.write(91);
Serial.write(50);
Serial.write(74);
}
void ansiHome()
// ANSI terminal command to move the cursor to 0,0 without clearing screen
// used by ANSI output format
{
// send ESC [H
// which puts the cursor in the home 0,0 position on an ANSI terminal
Serial.write(27);
Serial.write(91);
Serial.write(72);
}
void ansiTab()
// send ANSI "tab" character
// used by ANSI output format
{
// send tab char
Serial.write(9);
}
void pass()
// space-saver for pass/fail tests in ANSI output format
// used by ANSI output format
{
Serial.println(" "); // print spaces to erase "FAIL" if necessary
}
void fail()
// space-saver for pass/fail test in ANSI output format
// used by ANSI output format
{
Serial.println("FAIL");
}
void reboot()
// space-saver for menu reboot message
{
Serial.println("REBOOT WEATHER BOARD TO PUT NEW SETTINGS INTO EFFECT");
Serial.println();
}
void LCDclear()
// clear the screen of an SparkFun serial-enabled LCD
// used by LCD output format
{
LCDline1();
Serial.write(" ");
LCDline2();
Serial.write(" ");
}
void LCDline1()
// move cursor to start of line 1 on a SparkFun serial-enabled LCD
// used by LCD output format
{
Serial.write(254);
Serial.write(128);
}
void LCDline2()
// move cursor to start of line 2 on a SparkFun serial-enabled LCD
// used by LCD output format
{
Serial.write(254);
Serial.write(192);
}
void error(int errorcode) // save some space by printing out a generic error message
{
Serial.print("ERROR #"); Serial.print(errorcode,DEC); Serial.println(", see sketch for cause");
}
void printComma() // we do this a lot, it saves two bytes each time we call it
{
Serial.print(",");
}