Pastebin launched a little side project called VERYVIRAL.com, check it out ;-) Want more features on Pastebin? Sign Up, it's FREE!
Guest

Untitled

By: a guest on May 20th, 2012  |  syntax: C  |  size: 36.91 KB  |  views: 102  |  expires: Never
download  |  raw  |  embed  |  report abuse  |  print
Text below is selected. Please press Ctrl+C to copy to your clipboard. (⌘+C on Mac)
  1. // Revision history
  2. // 1.2 Update all code to be compatible with Arduino 1.0 2012/1/23
  3. // 1.1 changed sample_rate to 32-bit, max = 4294966 seconds (47 days)
  4. // RAM is also getting tight, so removed some error strings and added RAM gauge to menu 2011/08/01
  5. // 1.0 initial release, 2011/06/27
  6.  
  7. // firmware version
  8. const char version_major = 1;
  9. const char version_minor = 2;
  10.  
  11. // external libraries
  12. #include <SHT1x.h> // SHT15 humidity sensor library
  13. #include <SFE_BMP085.h> // BMP085 pressure sensor library
  14. #include <Wire.h> // I2C library (necessary for pressure sensor)
  15. #include <avr/eeprom.h> // extended EEPROM read/write functions
  16. #include <WiFlyHQ.h> //Wifly
  17.  
  18. //WiFLy
  19. WiFly wifly;
  20.  
  21. const char mySSID[] = "home";
  22. const char myPassword[] = "crap#123";
  23. void sendIndex();
  24. void sendGreeting(char *name);
  25. void send404();
  26. char buf[80];
  27.  
  28. // digital I/O pins
  29. // (the following three are predefined)
  30. // const int SCK = 13;
  31. // const int MISO = 12;
  32. // const int MOSI = 11;
  33. const int XCLR = 9;
  34. const int EOC = 8;
  35. const int RF_RTS = 6;
  36. const int RF_CTS = 5;
  37. const int STATUSLED = 4;
  38. const int WSPEED = 3;
  39. const int RAIN = 2;
  40.  
  41. // analog I/O pins
  42. const int LIGHT = 7;
  43. const int BATT_LVL = 6;
  44. const int WDIR = 0;
  45.  
  46. // global variables
  47. float SHT15_humidity;
  48. float SHT15_temp;
  49. float SHT15_dewpoint;
  50. double BMP085_pressure;
  51. double BMP085_temp;
  52. float TEMT6000_light;
  53. float WM_wspeed;
  54. float WM_wdirection;
  55. float WM_rainfall = 0.0;
  56. float batt_volts;
  57. int LED = 0; // status LED
  58. unsigned int windRPM, stopped;
  59. // volatiles are subject to modification by IRQs
  60. volatile unsigned long tempwindRPM, windtime, windlast, windinterval;
  61. volatile unsigned char windintcount;
  62. volatile boolean gotwspeed;
  63. volatile unsigned long raintime, rainlast, raininterval, rain;
  64.  
  65. // constant conversion factors
  66. const int BATT_RATIO = 63.3271; // divide ADC from BATT_LVL by this to get volts
  67. const float WIND_RPM_TO_MPH = 22.686745; // divide RPM by this for velocity
  68. const float WIND_RPM_TO_MPS = 50.748803; // divide RPM by this for meters per second
  69. const float RAIN_BUCKETS_TO_INCHES = 0.0086206896; // multiply bucket tips by this for inches
  70. const float RAIN_BUCKETS_TO_MM = 0.21896551; // multiply bucket tips by this for mm
  71. const unsigned int ZERODELAY = 4000; // ms, zero RPM if no result for this time period (see irq below)
  72.  
  73. // sensor objects
  74. SHT1x humidity_sensor(A4, A5);
  75. SFE_BMP085 pressure_sensor(BMP_ADDR);
  76.  
  77. // enumerated options, changed in user menu
  78.  
  79. // output format
  80. const int CSV = 1; // default NMEA-like comma-separated values
  81. const int ANSI = 2; // ANSI-formatted data with hardware testing
  82. const int LCD = 3; // directly drive a SparkFun serial-enabled 16x2 LCD display
  83.  
  84. // general units
  85. const int ENGLISH = 1; // wind speed in miles per hour, rain in inches, temperature in degrees Fahrenheit
  86. const int SI = 2; // International System, aka the metric system. Wind speed in meters per second, rain in mm, temperature in degrees Celsius
  87.  
  88. // pressure units
  89. const int MBAR = 1; // millibars
  90. const int INHG = 2; // inches of mercury (US weather report standard)
  91. const int PSI = 3; // pounds per square inch
  92.  
  93. // pressure type
  94. const int ABSOLUTE = 1; // absolute (true) pressure, changes with altitude (ignore altitude variable)
  95. const int RELATIVE = 2; // relative (weather) pressure, altitude effects removed (use altitude variable)
  96.  
  97. // defaults, replaced with EEPROM settings if saved
  98. int data_format = ANSI;
  99. int general_units = SI;
  100. unsigned long sample_rate = 2; // sample rate (seconds per sample, 0 for as fast as possible)
  101. int pressure_type = ABSOLUTE;
  102. long altitude = 1596; // fixed weather station altitude in meters, for relative (sea level) pressure measurement
  103. int pressure_units = MBAR;
  104. 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)
  105. long baud_rate = 9600; // default baud rate
  106.  
  107. // hardware memory pointers, used by freeMemory() (see: http://www.arduino.cc/playground/Code/AvailableMemory)
  108.  
  109. extern unsigned int __bss_end;
  110. extern unsigned int __heap_start;
  111. extern void *__brkval;
  112.  
  113. int freeMemory()
  114. {
  115.   int free_memory;
  116.  
  117.   if((int)__brkval == 0)
  118.      free_memory = ((int)&free_memory) - ((int)&__bss_end);
  119.   else
  120.     free_memory = ((int)&free_memory) - ((int)__brkval);
  121.  
  122.   return free_memory;
  123. }
  124.  
  125. // interrupt routines (these are called by the hardware interrupts, not by the main code)
  126.  
  127. void rainIRQ()
  128. // if the Weather Meters are attached, count rain gauge bucket tips as they occur
  129. // activated by the magnet and reed switch in the rain gauge, attached to input D2
  130. {
  131.   raintime = micros(); // grab current time
  132.   raininterval = raintime - rainlast; // calculate interval between this and last event
  133.  
  134.   if (raininterval > 100) // ignore switch-bounce glitches less than 100uS after initial edge
  135.   {
  136.     rain++; // increment bucket counter
  137.     rainlast = raintime; // set up for next event
  138.   }
  139. }
  140.  
  141. void wspeedIRQ()
  142. // if the Weather Meters are attached, measure anemometer RPM (2 ticks per rotation), set flag if RPM is updated
  143. // activated by the magnet in the anemometer (2 ticks per rotation), attached to input D3
  144.  
  145. // this routine measures RPM by measuring the time between anemometer pulses
  146. // 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)
  147. // when windintcount is 2, we can calculate the RPM based on the total time from when we got the first pulse
  148. // 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)
  149. {
  150.   windtime = micros(); // grab current time
  151.   if ((windintcount == 0) || ((windtime - windlast) > 10000)) // ignore switch-bounce glitches less than 10ms after the reed switch closes
  152.   {
  153.     if (windintcount == 0) // if we're starting a new measurement, reset the interval
  154.       windinterval = 0;  
  155.     else
  156.       windinterval += (windtime - windlast); // otherwise, add current interval to the interval timer
  157.  
  158.     if (windintcount == 2) // we have two measurements (one full rotation), so calculate result and start a new measurement
  159.     {
  160.       tempwindRPM = (60000000ul / windinterval); // calculate RPM (temporary since it may change unexpectedly)
  161.       windintcount = 0;
  162.       windinterval = 0;  
  163.       gotwspeed = true; // set flag for main loop
  164.     }
  165.  
  166.     windintcount++;    
  167.     windlast = windtime; // save the current time so that we can calculate the interval between now and the next interrupt
  168.   }
  169. }
  170.  
  171. void setup()
  172. // this procedure runs once upon startup or reboot
  173. // perform all the settings we need before running the main loop
  174. {
  175.   // set up inputs and outputs
  176.   pinMode(XCLR,OUTPUT); // output to BMP085 reset (unused)
  177.   digitalWrite(XCLR,HIGH); // make pin high to turn off reset
  178.  
  179.   pinMode(EOC,INPUT); // input from BMP085 end of conversion (unused)
  180.   digitalWrite(EOC,LOW); // turn off pullup
  181.  
  182.   pinMode(STATUSLED,OUTPUT); // output to status LED
  183.  
  184.   pinMode(WSPEED,INPUT); // input from wind meters windspeed sensor
  185.   digitalWrite(WSPEED,HIGH); // turn on pullup
  186.  
  187.   pinMode(RAIN,INPUT); // input from wind meters rain gauge sensor
  188.   digitalWrite(RAIN,HIGH); // turn on pullup
  189.  
  190.   // get settings from EEPROM (use defaults if EEPROM has not been used)
  191.   retrieveEEPROMsettings();
  192.  
  193.   // initialize serial port
  194.   Serial.begin(baud_rate);
  195.   Serial.println();
  196.   Serial.println("RESET");
  197.  
  198.   // initialize BMP085 pressure sensor
  199.   if (pressure_sensor.begin() == 0)
  200.     error(1);
  201.  
  202.   // init wind speed interrupt global variables
  203.   gotwspeed = false; windRPM = 0; windintcount = 0;
  204.  
  205.   // blink status LED 3 times
  206.   digitalWrite(STATUSLED,HIGH);
  207.   delay(100);
  208.   digitalWrite(STATUSLED,LOW);
  209.   delay(250);
  210.   digitalWrite(STATUSLED,HIGH);
  211.   delay(100);
  212.   digitalWrite(STATUSLED,LOW);
  213.   delay(250);
  214.   digitalWrite(STATUSLED,HIGH);
  215.   delay(100);
  216.   digitalWrite(STATUSLED,LOW);
  217.   delay(250);
  218.  
  219.   if (weather_meters_attached)
  220.   {
  221.     // attach external interrupt pins to IRQ functions
  222.     attachInterrupt(0,rainIRQ,FALLING);
  223.     attachInterrupt(1,wspeedIRQ,FALLING);
  224.    
  225.     // turn on interrupts
  226.     interrupts();
  227.   }
  228. }
  229.  
  230. void loop()
  231. // loops forever after setup() ends
  232. {
  233.   static long templong, windstopped;
  234.   static unsigned long loopstart, loopend;
  235.   double Tn, m;
  236.   static char LCDstate;
  237.   char status;
  238.  
  239.   // record current time so we can sample at regular intervals
  240.   loopstart = millis();
  241.   loopend = loopstart + (sample_rate * 1000ul);
  242.  
  243.   // turn on LED while we're doing measurements
  244.   digitalWrite(STATUSLED,HIGH);
  245.  
  246.   // an interrupt occurred, handle it now
  247.   if (gotwspeed)
  248.   {
  249.     gotwspeed = false;
  250.     windRPM = word(tempwindRPM); // grab the RPM value calculated by the interrupt routine
  251.     windstopped = millis() + ZERODELAY; // save this timestamp
  252.   }
  253.  
  254.   // zero wind speed RPM if we don't get a reading in ZERODELAY ms
  255.   if (millis() > windstopped)
  256.   {
  257.     windRPM = 0; windintcount = 0;
  258.   }
  259.  
  260.   TWCR &= ~(_BV(TWEN));  // turn off I2C enable bit so we can access the SHT15 humidity sensor
  261.  
  262.   // get humidity and temp (SHT15)
  263.   SHT15_temp = humidity_sensor.readTemperatureC();
  264.   SHT15_humidity = humidity_sensor.readHumidity();
  265.  
  266.   // compute dewpoint (because we can!)
  267.   if (SHT15_temp > 0.0)
  268.     {Tn = 243.12; m = 17.62;}
  269.   else
  270.     {Tn = 272.62; m = 22.46;}
  271.  
  272.   SHT15_dewpoint = (Tn*(log(SHT15_humidity/100)+((m*SHT15_temp)/(Tn+SHT15_temp)))/(m-log(SHT15_humidity/100)-((m*SHT15_temp)/(Tn+SHT15_temp))));
  273.  
  274.   // get temp (SHT15)
  275.   switch (general_units)
  276.   {
  277.     case ENGLISH: // Fahrenheit
  278.       SHT15_temp = (SHT15_temp*9.0/5.0)+32.0;
  279.       SHT15_dewpoint = (SHT15_dewpoint*9.0/5.0)+32.0;
  280.       break;
  281.     case SI: // celsius, don't need to do anything
  282.       break;
  283.     default:
  284.       SHT15_temp = -1.0; // error, invalid units
  285.       SHT15_dewpoint = -1.0; // error, invalid units
  286.   }
  287.  
  288.   TWCR |= _BV(TWEN);  // turn on I2C enable bit so we can access the BMP085 pressure sensor
  289.  
  290.   // start BMP085 temperature reading
  291.   status = pressure_sensor.startTemperature();
  292.   if (status != 0)
  293.     delay(status); // if nonzero, status is number of ms to wait for reading to become available
  294.   else
  295.     error(2);
  296.    
  297.   // retrieve BMP085 temperature reading
  298.   status = pressure_sensor.getTemperature(&BMP085_temp); // deg C
  299.   if (status == 0)
  300.     error(3);
  301.  
  302.   // start BMP085 pressure reading
  303.   status = pressure_sensor.startPressure(3);
  304.   if (status != 0)
  305.     delay(status); // if nonzero, status is number of ms to wait for reading to become available
  306.   else
  307.     error(4);
  308.    
  309.   // retrieve BMP085 pressure reading
  310.   status = pressure_sensor.getPressure(&BMP085_pressure, &BMP085_temp); // mbar, deg C
  311.   if (status == 0)
  312.     error(5);
  313.  
  314.   // compensate for altitude if needed
  315.   if (pressure_type == RELATIVE)
  316.     BMP085_pressure = pressure_sensor.sealevel(BMP085_pressure,altitude);
  317.  
  318.   // convert to desired units
  319.   switch (general_units)
  320.   {
  321.     case SI: // celsius
  322.       // do nothing, already C
  323.       break;
  324.     case ENGLISH: // Fahrenheit
  325.       BMP085_temp = BMP085_temp * 1.8 + 32.0;
  326.       break;
  327.     default:
  328.       BMP085_temp = -1.0; // error, invalid units
  329.   }
  330.  
  331.   switch (pressure_units)
  332.   {
  333.     case MBAR:
  334.       // do nothing, already mbar
  335.       break;
  336.     case INHG:
  337.       BMP085_pressure = BMP085_pressure / 33.8600000;
  338.       break;
  339.     case PSI:
  340.       BMP085_pressure = BMP085_pressure / 68.9475728;
  341.       break;
  342.     default:
  343.       BMP085_pressure = -1.0; // error, invalid units
  344.   }
  345.  
  346.   // get light
  347.   TEMT6000_light = (1023.0 - float(analogRead(LIGHT))) / 10.23; // 0-100 percent
  348.  
  349.   // windspeed unit conversion
  350.   switch (general_units)
  351.   {
  352.     case SI: // meters per second
  353.       WM_wspeed = float(windRPM) / WIND_RPM_TO_MPS;
  354.       break;
  355.     case ENGLISH: // miles per hour
  356.       WM_wspeed = float(windRPM) / WIND_RPM_TO_MPH;
  357.       break;
  358.     default:
  359.       WM_wspeed = -1.0; // error, invalid units
  360.   }
  361.  
  362.   // get wind direction
  363.   WM_wdirection = get_wind_direction();
  364.  
  365.   // rainfall unit conversion
  366.   switch (general_units)
  367.   {
  368.     case SI: // mm
  369.       WM_rainfall = rain * RAIN_BUCKETS_TO_MM;
  370.       break;
  371.     case ENGLISH: // inches
  372.       WM_rainfall = rain * RAIN_BUCKETS_TO_INCHES;
  373.       break;
  374.     default:
  375.       WM_rainfall = -1.0; // error, invalid units
  376.   }
  377.  
  378.   // get battery voltage
  379.   batt_volts = float(analogRead(BATT_LVL)) / BATT_RATIO;
  380.  
  381.   // below are a bunch of nested switch statements to handle the different output data_formats that are possible
  382.   // feel free to modify them or add your own!
  383.  
  384.   switch (data_format)
  385.   {
  386.  
  387.     case CSV: // data_format: comma-separated values
  388.     {
  389.       // number after values is number of digits after decimal point to print
  390.       Serial.print("$");
  391.       printComma();
  392.       Serial.print(SHT15_temp,1);
  393.       printComma();
  394.       Serial.print(SHT15_humidity,0);
  395.       printComma();
  396.       Serial.print(SHT15_dewpoint,1);
  397.       printComma();
  398.       switch (pressure_units) // change decimal point for different units
  399.       {
  400.         case MBAR:
  401.           Serial.print(BMP085_pressure,2);
  402.           break;
  403.         case INHG:
  404.           Serial.print(BMP085_pressure,3);
  405.           break;
  406.         case PSI:
  407.           Serial.print(BMP085_pressure,4);
  408.           break;
  409.       }
  410.       printComma();
  411.       // Serial.print(BMP085_temp,1);  // for CSV format, we'll only output the SHT15 temperature
  412.       // Serial.print(",");
  413.       Serial.print(TEMT6000_light,1);
  414.       printComma();
  415.       if (weather_meters_attached)
  416.       {
  417.         Serial.print(WM_wspeed,1);
  418.         printComma();
  419.         Serial.print(WM_wdirection,0);
  420.         printComma();
  421.         switch (general_units) // change decimal point for different units
  422.         {
  423.           case ENGLISH:
  424.             Serial.print(WM_rainfall,2);
  425.             break;
  426.           case SI:
  427.             Serial.print(WM_rainfall,1);
  428.             break;
  429.         }
  430.         printComma();
  431.       }
  432.       Serial.print(batt_volts,2);
  433.       printComma();
  434.       Serial.print("*");
  435.       Serial.println();
  436.     }
  437.     break;
  438.  
  439.     case ANSI: // this data_format neatly formats the data on an ANSI terminal, and has pass/fail values for testing
  440.     {
  441.       ansiHome();
  442.       Serial.println();
  443.  
  444.       Serial.print("SHT15 temperature:");
  445.       ansiTab();
  446.       ansiTab();
  447.       Serial.print(SHT15_temp,1);
  448.       Serial.print(" deg ");
  449.       switch (general_units)
  450.       {
  451.         case ENGLISH:
  452.           Serial.print("F ");
  453.           ansiTab();
  454.           if ((SHT15_temp > 60) && (SHT15_temp < 85)) pass(); else fail();
  455.           break;
  456.         case SI:
  457.           Serial.print("C ");
  458.           ansiTab();
  459.           if ((SHT15_temp > 15) && (SHT15_temp < 30)) pass(); else fail();
  460.           break;
  461.       }
  462.  
  463.       Serial.print("SHT15 humidity:  ");
  464.       ansiTab();
  465.       ansiTab();
  466.       Serial.print(SHT15_humidity,0);
  467.       Serial.print("% ");
  468.       ansiTab();
  469.       ansiTab();
  470.       if ((SHT15_humidity > 10) && (SHT15_humidity < 90)) pass(); else fail();
  471.  
  472.  
  473.       Serial.print("SHT15 dewpoint:  ");
  474.       ansiTab();
  475.       ansiTab();
  476.       Serial.print(SHT15_dewpoint,1);
  477.       Serial.print(" deg ");
  478.       switch (general_units)
  479.       {
  480.         case ENGLISH:
  481.           Serial.println("F ");
  482.           break;
  483.         case SI:
  484.           Serial.println("C ");
  485.           break;
  486.       }
  487.  
  488.       Serial.print("BMP085 pressure:");
  489.       ansiTab();
  490.       ansiTab();
  491.       switch (pressure_units) // change decimal point for different units
  492.       {
  493.         case MBAR:
  494.           Serial.print(BMP085_pressure,2);
  495.           Serial.print(" mbar ");
  496.           ansiTab();
  497.           if ((BMP085_pressure > 900) && (BMP085_pressure < 1100)) pass(); else fail();
  498.           break;
  499.         case INHG:
  500.           Serial.print(BMP085_pressure,3);
  501.           Serial.print(" in Hg ");
  502.           ansiTab();
  503.           if ((BMP085_pressure > 25) && (BMP085_pressure < 35)) pass(); else fail();
  504.           break;
  505.         case PSI:
  506.           Serial.print(BMP085_pressure,4);
  507.           Serial.print(" PSI ");
  508.           ansiTab();
  509.           if ((BMP085_pressure > 13) && (BMP085_pressure < 15)) pass(); else fail();
  510.           break;
  511.       }
  512.  
  513.       Serial.print("BMP085 temperature:");
  514.       ansiTab();
  515.       ansiTab();
  516.       Serial.print(BMP085_temp,1);
  517.       Serial.print(" deg ");
  518.       switch (general_units)
  519.       {
  520.         case ENGLISH:
  521.           Serial.print("F ");
  522.           ansiTab();
  523.           if ((BMP085_temp > 60) && (BMP085_temp < 90)) pass(); else fail();
  524.           break;
  525.         case SI:
  526.           Serial.print("C ");
  527.           ansiTab();
  528.           if ((BMP085_temp > 15) && (BMP085_temp < 35)) pass(); else fail();
  529.           break;
  530.       }
  531.  
  532.       Serial.print("TEMT6000 light:  ");
  533.       ansiTab();
  534.       ansiTab();
  535.       Serial.print(TEMT6000_light,1);
  536.       Serial.print("% ");
  537.       ansiTab();
  538.       ansiTab();
  539.       if ((TEMT6000_light > 0) && (TEMT6000_light < 100)) pass(); else fail();
  540.  
  541.       if (weather_meters_attached)
  542.       {
  543.         Serial.print("Weather meters wind speed:");
  544.         ansiTab();
  545.         Serial.print(WM_wspeed,1);
  546.         switch (general_units)
  547.         {
  548.           case ENGLISH:
  549.             Serial.print(" MPH ");
  550.             ansiTab();
  551.             if (WM_wspeed > 0.0) pass(); else fail();
  552.             break;
  553.           case SI:
  554.             Serial.print(" m/s ");
  555.             ansiTab();
  556.             ansiTab();
  557.             if (WM_wspeed > 0.0) pass(); else fail();
  558.             break;
  559.         }
  560.    
  561.         Serial.print("Weather meters wind direction:");
  562.         ansiTab();
  563.         Serial.print(WM_wdirection,0);
  564.         Serial.print(" degrees ");
  565.         ansiTab();
  566.         // direction will read -1 if wind direction sensor is disconnected or faulty
  567.         if (WM_wdirection != -1) pass(); else fail();
  568.    
  569.         Serial.print("Weather meters rainfall:");
  570.         ansiTab();
  571.         switch (general_units)
  572.         {
  573.           case ENGLISH:
  574.             Serial.print(WM_rainfall,2);
  575.             Serial.print(" inches ");
  576.             ansiTab();
  577.             if (WM_rainfall > 0.05) pass(); else fail();
  578.             break;
  579.           case SI:
  580.             Serial.print(WM_rainfall,0);
  581.             Serial.print(" mm ");
  582.             ansiTab();
  583.             ansiTab();
  584.             if (WM_rainfall > 0.5) pass(); else fail();
  585.             break;
  586.         }
  587.       }
  588.       Serial.print("External power:  ");
  589.       ansiTab();
  590.       ansiTab();
  591.       Serial.print(batt_volts,2);
  592.       Serial.print(" Volts ");
  593.       ansiTab();
  594.       if ((batt_volts > 3.5) && (batt_volts < 13.0)) pass(); else fail();
  595.       Serial.println();
  596.     }
  597.     break;
  598.  
  599.     case LCD: // this data_format will directly drive a SparkFun serial-enabled 16x2 LCD
  600.     // since you can't show everything at once on a small LCD display, this routine rotates through the values,
  601.     // displaying a different set every time we pass through the loop() using LCDstate to keep track of where it is
  602.     // to change the display rate, change the sample rate in the menu
  603.     {
  604.       LCDclear();
  605.       switch (LCDstate)
  606.       {
  607.         case 0:
  608.           LCDline1();
  609.           Serial.print("temp: ");
  610.           Serial.print(SHT15_temp,1);
  611.           switch (general_units)
  612.           {
  613.             case ENGLISH:
  614.               Serial.print(" F");
  615.               break;
  616.             case SI:
  617.               Serial.print(" C");
  618.               break;
  619.           }
  620.           LCDline2();
  621.           Serial.print("humid: ");
  622.           Serial.print(SHT15_humidity,0);
  623.           Serial.print("%");
  624.           break;
  625.         case 1:
  626.           LCDline1();
  627.           Serial.print("baro: ");
  628.           switch (pressure_units) // change decimal point for different units
  629.           {
  630.             case MBAR:
  631.               Serial.print(BMP085_pressure,2);
  632.               Serial.print(" mb");
  633.               break;
  634.             case INHG:
  635.               Serial.print(BMP085_pressure,3);
  636.               Serial.print(" in");
  637.               break;
  638.             case PSI:
  639.               Serial.print(BMP085_pressure,3);
  640.               Serial.print(" PSI");
  641.               break;
  642.           }
  643.           LCDline2();
  644.           Serial.print("dewp: ");
  645.           Serial.print(SHT15_dewpoint,1);
  646.           switch (general_units)
  647.           {
  648.             case ENGLISH:
  649.               Serial.print(" F");
  650.               break;
  651.             case SI:
  652.               Serial.print(" C");
  653.               break;
  654.           }
  655.           break;
  656.         case 2:
  657.           LCDline1();
  658.           Serial.print("wind: ");
  659.           Serial.print(WM_wspeed,1);
  660.           switch (general_units)
  661.           {
  662.             case ENGLISH:
  663.               Serial.print(" MPH");
  664.               break;
  665.             case SI:
  666.               Serial.print(" m/s");
  667.               break;
  668.           }
  669.           LCDline2();
  670.           Serial.print("dir: ");
  671.           Serial.print(WM_wdirection,0);
  672.           Serial.print(" deg");
  673.           break;
  674.         case 3:
  675.           LCDline1();
  676.           Serial.print("rain: ");
  677.           switch (general_units)
  678.           {
  679.             case ENGLISH:
  680.               Serial.print(WM_rainfall,2);
  681.               Serial.print(" in");
  682.               break;
  683.             case SI:
  684.               Serial.print(WM_rainfall,0);
  685.               Serial.print(" mm");
  686.               break;
  687.           }
  688.           LCDline2();
  689.           Serial.print("light: ");
  690.           Serial.print(TEMT6000_light,1);
  691.           Serial.print("%");
  692.           break;
  693.       }
  694.       LCDstate++;
  695.       // reset LCDstate to 0 depending on whether we want to show the Weather Meters data or not
  696.       if ((weather_meters_attached && (LCDstate > 3)) || (!weather_meters_attached && (LCDstate > 1)))
  697.         LCDstate = 0;
  698.       break;
  699.     }
  700.   }
  701.  
  702.   // turn off LED (done with measurements)
  703.   digitalWrite(STATUSLED,LOW);
  704.  
  705.   // we're done sampling all the sensors and printing out the results
  706.   // now wait in a loop for the next sample time
  707.   // while we're waiting, we'll check the serial port to see if the user has pressed CTRL-Z to activate the menu
  708.   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
  709.   {
  710.     while (Serial.available())
  711.     {
  712.       if (Serial.read() == 0x1A) // CTRL-Z
  713.       {
  714.         menu(); // display the menu and allow settings to be changed
  715.         loopend = millis(); // we're done with the menu, break out of the do-while
  716.       }
  717.     }
  718.   }
  719.   while(millis() < loopend);
  720. }
  721.  
  722. float get_wind_direction()
  723. // read the wind direction sensor, return heading in degrees
  724. {
  725.   unsigned int adc;
  726.  
  727.   adc = analogRead(WDIR); // get the current reading from the sensor
  728.  
  729.   // The following table is ADC readings for the wind direction sensor output, sorted from low to high.
  730.   // Each threshold is the midpoint between adjacent headings. The output is degrees for that ADC reading.
  731.   // Note that these are not in compass degree order!  See Weather Meters datasheet for more information.
  732.  
  733.   if (adc < 380) return (112.5);
  734.   if (adc < 393) return (67.5);
  735.   if (adc < 414) return (90);
  736.   if (adc < 456) return (157.5);
  737.   if (adc < 508) return (135);
  738.   if (adc < 551) return (202.5);
  739.   if (adc < 615) return (180);
  740.   if (adc < 680) return (22.5);
  741.   if (adc < 746) return (45);
  742.   if (adc < 801) return (247.5);
  743.   if (adc < 833) return (225);
  744.   if (adc < 878) return (337.5);
  745.   if (adc < 913) return (0);
  746.   if (adc < 940) return (292.5);
  747.   if (adc < 967) return (315);
  748.   if (adc < 990) return (270);
  749.   return (-1); // error, disconnected?
  750. }
  751.  
  752. /* From Weather Meters docs and the Weather Board V3 schematic:
  753.  
  754. heading         resistance      volts           nominal         midpoint (<)
  755. 112.5   º      0.69    k       1.2     V       372     counts  380
  756. 67.5    º      0.89    k       1.26    V       389     counts  393
  757. 90      º      1       k       1.29    V       398     counts  414
  758. 157.5   º      1.41    k       1.39    V       430     counts  456
  759. 135     º      2.2     k       1.56    V       483     counts  508
  760. 202.5   º      3.14    k       1.72    V       534     counts  551
  761. 180     º      3.9     k       1.84    V       569     counts  615
  762. 22.5    º      6.57    k       2.13    V       661     counts  680
  763. 45      º      8.2     k       2.26    V       700     counts  746
  764. 247.5   º      14.12   k       2.55    V       792     counts  801
  765. 225     º      16      k       2.62    V       811     counts  833
  766. 337.5   º      21.88   k       2.76    V       855     counts  878
  767. 0       º      33      k       2.91    V       902     counts  913
  768. 292.5   º      42.12   k       2.98    V       925     counts  940
  769. 315     º      64.9    k       3.08    V       956     counts  967
  770. 270     º      98.6    k       3.15    V       978     counts  >967
  771. */
  772.  
  773. void sendIndex()
  774. {
  775.     /* Send the header direclty with print */
  776.     wifly.println(F("HTTP/1.1 200 OK"));
  777.     wifly.println(F("Content-Type: text/html"));
  778.     wifly.println(F("Transfer-Encoding: chunked"));
  779.     wifly.println();
  780.  
  781.     /* Send the body using the chunked protocol so the client knows when
  782.     * the message is finished.
  783.     * Note: we're not simply doing a close() because in version 2.32
  784.     * firmware the close() does not work for client TCP streams.
  785.     */
  786.     wifly.sendChunkln(F("<html>"));
  787.     wifly.sendChunkln(F("<title>WiFly HTTP Server Example</title>"));
  788.     wifly.sendChunkln(F("<h1>"));
  789.     wifly.sendChunkln(F("<p>Hello</p>"));
  790.     wifly.sendChunkln(F("</h1>"));
  791.     wifly.sendChunkln(F("<form name=\"input\" action=\"/\" method=\"post\">"));
  792.     wifly.sendChunkln(F("Username:"));
  793.     wifly.sendChunkln(F("<input type=\"text\" name=\"user\" />"));
  794.     wifly.sendChunkln(F("<input type=\"submit\" value=\"Submit\" />"));
  795.     wifly.sendChunkln(F("</form>"));
  796.     wifly.sendChunkln(F("</html>"));
  797.     wifly.sendChunkln();
  798. }
  799.  
  800. void sendGreeting(char *name)
  801. {
  802.     /* Send the header directly with print */
  803.     wifly.println(F("HTTP/1.1 200 OK"));
  804.     wifly.println(F("Content-Type: text/html"));
  805.     wifly.println(F("Transfer-Encoding: chunked"));
  806.     wifly.println();
  807.  
  808.     /* Send the body using the chunked protocol so the client knows when
  809.     * the message is finished.
  810.     */
  811.     wifly.sendChunkln(F("<html>"));
  812.     wifly.sendChunkln(F("<title>WiFly HTTP Server Example</title>"));
  813.     /* No newlines on the next parts */
  814.     wifly.sendChunk(F("<h1><p>Hello "));
  815.     wifly.sendChunk(name);
  816.     /* Finish the paragraph and heading */
  817.     wifly.sendChunkln(F("</p></h1>"));
  818.  
  819.     /* Include a reading from Analog pin 0 */
  820.     snprintf_P(buf, sizeof(buf), PSTR("<p>Analog0=%d</p>"), analogRead(A0));
  821.     wifly.sendChunkln(buf);
  822.  
  823.     wifly.sendChunkln(F("</html>"));
  824.     wifly.sendChunkln();
  825. }
  826.  
  827. void send404()
  828. {
  829.     wifly.println(F("HTTP/1.1 404 Not Found"));
  830.     wifly.println(F("Content-Type: text/html"));
  831.     wifly.println(F("Transfer-Encoding: chunked"));
  832.     wifly.println();
  833.     wifly.sendChunkln(F("<html><head>"));
  834.     wifly.sendChunkln(F("<title>404 Not Found</title>"));
  835.     wifly.sendChunkln(F("</head><body>"));
  836.     wifly.sendChunkln(F("<h1>Not Found</h1>"));
  837.     wifly.sendChunkln(F("<hr>"));
  838.     wifly.sendChunkln(F("</body></html>"));
  839.     wifly.sendChunkln();
  840. }
  841.  
  842. void menu()
  843. // provide the user a way to modify settings, and save those settings to EEPROM to survive reboot / shutdown
  844. {
  845.   boolean done = false;
  846.   char choice;
  847.   long templong;
  848.  
  849.   // print out a menu of choices, with current settings in (parenthesis)
  850.  
  851.   Serial.println();
  852.   Serial.print("SparkFun USB Weather Board V3 firmware version ");
  853.   Serial.print(version_major,DEC); Serial.print(".");  Serial.println(version_minor,DEC);
  854.   Serial.print("free RAM: "); Serial.print(freeMemory()); Serial.println(" bytes");
  855.   Serial.println();
  856.  
  857.   while (!done)
  858.   {
  859.     Serial.print("1. Data format (");
  860.     switch (data_format)
  861.     {
  862.       case CSV: Serial.print("CSV"); break;
  863.       case ANSI: Serial.print("ANSI"); break;
  864.       case LCD: Serial.print("LCD"); break;
  865.     }
  866.     Serial.println(")");
  867.  
  868.     Serial.print("2. General units (");
  869.     switch (general_units)
  870.     {
  871.       case ENGLISH: Serial.print("English"); break;
  872.       case SI: Serial.print("SI"); break;
  873.     }
  874.     Serial.println(")");
  875.  
  876.     Serial.print("3. Sample rate (");
  877.     Serial.print(sample_rate,DEC);
  878.     Serial.println(")");
  879.  
  880.     Serial.print("4. Pressure units (");
  881.     switch (pressure_units)
  882.     {
  883.       case MBAR: Serial.print("mbar"); break;
  884.       case INHG: Serial.print("inches Hg"); break;
  885.       case PSI: Serial.print("PSI"); break;
  886.     }
  887.     Serial.println(")");
  888.  
  889.     Serial.print("5. Pressure type (");
  890.     switch (pressure_type)
  891.     {
  892.       case RELATIVE: Serial.print("relative"); break;
  893.       case ABSOLUTE: Serial.print("absolute"); break;
  894.     }
  895.     Serial.println(")");
  896.  
  897.     Serial.print("6. Station altitude (");
  898.     Serial.print(altitude,DEC);
  899.     Serial.println(" meters)");
  900.  
  901.     Serial.print("7. Baud rate (");
  902.     Serial.print(baud_rate,DEC);
  903.     Serial.println(" baud)");
  904.  
  905.     Serial.println("8. Zero rain counter");
  906.  
  907.     Serial.print("9. Weather Meters attached (");
  908.     if (weather_meters_attached)
  909.       Serial.println("yes)");
  910.     else
  911.       Serial.println("no)");
  912.  
  913.     Serial.println("X. Exit (don't save changes to EEPROM)");
  914.     Serial.println("S. Save (save changes to EEPROM)");
  915.     Serial.println();
  916.  
  917.     // wait for user input from serial port, and act on that input
  918.     // many of these are submenus, some require entering a number
  919.    
  920.     choice = getChar(); // note that this will uppercase the character for you
  921.     switch (choice)
  922.     {
  923.       case '1':
  924.         Serial.println("1. CSV");
  925.         Serial.println("2. ANSI");
  926.         Serial.println("3. LCD");
  927.         Serial.println();
  928.         data_format = getChar() - '0';
  929.         break;
  930.  
  931.       case '2':
  932.         Serial.println("1. English");
  933.         Serial.println("2. SI (metric)");
  934.         Serial.println();
  935.         general_units = getChar() - '0';
  936.         break;
  937.  
  938.       case '3':
  939.         Serial.print("Enter sample rate (every x seconds): ");
  940.         templong = getLong();
  941.         if (templong > 4294966ul)
  942.         {
  943.           Serial.println();
  944.           Serial.println();
  945.           Serial.println("SAMPLE RATE TOO LARGE (max = 4294966 seconds)");
  946.           Serial.println();
  947.         }
  948.         else
  949.         {
  950.           sample_rate = templong;
  951.           Serial.println();
  952.           Serial.println();
  953.         }
  954.         break;
  955.  
  956.       case '4':
  957.         Serial.println("1. mbar");
  958.         Serial.println("2. inches Hg");
  959.         Serial.println("3. PSI");
  960.         Serial.println();
  961.         pressure_units = getChar() - '0';
  962.         break;
  963.  
  964.       case '5':
  965.         Serial.println("1. absolute");
  966.         Serial.println("2. relative");
  967.         Serial.println();
  968.         pressure_type = getChar() - '0';
  969.         reboot();
  970.         break;
  971.  
  972.       case '6':
  973.         Serial.print("Enter altitude in integer meters: ");
  974.         altitude = getLong();
  975.         Serial.println();
  976.         Serial.println();
  977.         reboot();
  978.         break;
  979.  
  980.       case '7':
  981.         Serial.print("Enter baud rate: ");
  982.         templong = getLong();
  983.         Serial.println();
  984.         Serial.println();
  985.         // make SURE value entered is one of the valid baud rates (otherwise it will be impossible to talk to the board!)
  986.         if ((templong == 300) || (templong == 1200) || (templong == 2400) || (templong == 4800) || (templong == 9600) || (templong == 19200) || (templong == 38400) || (templong == 57600) || (templong == 115200))
  987.         {
  988.           baud_rate = templong;
  989.           reboot();
  990.         }
  991.         else
  992.         {
  993.           Serial.println("INVALID BAUD RATE (use 300, 1200, 2400, 4800, 9600, 19200, 38400, 57600, 115200)");
  994.           Serial.println();
  995.         }
  996.         break;
  997.  
  998.       case '8':
  999.         rain = 0;
  1000.         Serial.println("Rain counter zeroed");
  1001.         Serial.println();
  1002.         break;
  1003.  
  1004.       case '9':
  1005.         Serial.println("1. yes");
  1006.         Serial.println("2. no");
  1007.         Serial.println();
  1008.         weather_meters_attached = (getChar() == '1');
  1009.         break;
  1010.  
  1011.       case 'S':
  1012.         storeEEPROMsettings();
  1013.         // no break here! we want to fall through the 'Q' choice and quit
  1014.       case 'X':
  1015.         done = true;
  1016.         break;
  1017.     }
  1018.   }
  1019. }
  1020.  
  1021. char getChar()
  1022. // wait for one character to be typed (and convert to uppercase if it's alphabetic)
  1023. {
  1024.   digitalWrite(STATUSLED,HIGH);
  1025.   while (!Serial.available())
  1026.     {;} // wait here forever for a character
  1027.   digitalWrite(STATUSLED,LOW);
  1028.   return(toupper(Serial.read())); // return the upper case character
  1029. }
  1030.  
  1031. long getLong()
  1032. // wait for a number to be input (end with return), allows backspace and negative
  1033. {
  1034.   char mystring[10];
  1035.   char mychar;
  1036.   int x = 0;
  1037.   boolean done = false;
  1038.  
  1039.   // input a string of characters from the user
  1040.  
  1041.   while (!done)
  1042.   {
  1043.     mychar = getChar();
  1044.     if ((mychar == 0x0D) || (mychar == 0x0A)) // carriage return or line feed?
  1045.     {
  1046.       // terminate the string with 0x00 and exit
  1047.       mystring[x] = 0;
  1048.       done = true;
  1049.     }
  1050.     else
  1051.     {
  1052.       if ((mychar == 0x08) && (x > 0)) // backspace?
  1053.       {
  1054.         // simulate a backspace - back up, print a space to erase character, and backspace again
  1055.         Serial.write(0x08);
  1056.         Serial.print(" ");
  1057.         Serial.write(0x08);
  1058.         x--;
  1059.       }
  1060.       else // a real character?
  1061.       {
  1062. //        if ((mychar != 0x08) && (x < 10))
  1063.         if (x < 10)
  1064.         {
  1065.           Serial.print(mychar);
  1066.           mystring[x] = mychar;
  1067.           x++;
  1068.         }
  1069.       }
  1070.     }
  1071.   }
  1072.   // convert string to long using ASCII-to-long standard function
  1073.   return(atol(mystring));
  1074. }
  1075.  
  1076. void storeEEPROMsettings()
  1077. // store all the user-changable settings in EEPROM so it survives power cycles
  1078. // note that ints are two bytes, longs are four
  1079. {
  1080.   eeprom_write_word((uint16_t*)0,data_format);
  1081.   eeprom_write_word((uint16_t*)2,general_units);
  1082. //  eeprom_write_word((uint16_t*)4,sample_rate); // this space available for an int (changed sample rate to long)
  1083.   eeprom_write_word((uint16_t*)6,pressure_units);
  1084.   eeprom_write_word((uint16_t*)8,pressure_type);
  1085.   eeprom_write_dword((uint32_t*)10,altitude);
  1086.   eeprom_write_dword((uint32_t*)14,baud_rate);
  1087.   eeprom_write_word((uint16_t*)18,(int)weather_meters_attached);
  1088.   eeprom_write_dword((uint32_t*)22,sample_rate);
  1089. }
  1090.  
  1091. void retrieveEEPROMsettings()
  1092. // retrieve settings previously stored in EEPROM
  1093. // only load into variables if the settings are valid (-1 == 0xFF == erased)
  1094. // note that ints are two bytes, longs are four
  1095. {
  1096.   int tempint;
  1097.   long templong;
  1098.  
  1099.   // don't initialize variables if EEPROM is unused
  1100.   // (uninitialized EEPROM values read back as -1)
  1101.   tempint = eeprom_read_word((uint16_t*)0); if (tempint != -1) data_format = tempint;
  1102.   tempint = eeprom_read_word((uint16_t*)2); if (tempint != -1) general_units = tempint;
  1103. //  tempint = eeprom_read_word((uint16_t*)4); if (tempint != -1) sample_rate = tempint; // this space available for an int (changed sample rate to long)
  1104.   tempint = eeprom_read_word((uint16_t*)6); if (tempint != -1) pressure_units = tempint;
  1105.   tempint = eeprom_read_word((uint16_t*)8); if (tempint != -1) pressure_type = tempint;
  1106.   templong = eeprom_read_dword((uint32_t*)10); if (templong != -1) altitude = templong;
  1107.   templong = eeprom_read_dword((uint32_t*)14); if (templong != -1) baud_rate = templong;
  1108.   tempint = eeprom_read_word((uint16_t*)18); if (tempint != -1) weather_meters_attached = (boolean)tempint;
  1109.   templong = eeprom_read_dword((uint32_t*)22); if (templong != -1) sample_rate = templong;
  1110. }
  1111.  
  1112. void ansiClear()
  1113. // send ANSI clear screen command
  1114. // used by ANSI output format
  1115. {
  1116.   // send ESC [2J
  1117.   // which will clear the screen on an ANSI terminal
  1118.   Serial.write(27);
  1119.   Serial.write(91);
  1120.   Serial.write(50);
  1121.   Serial.write(74);
  1122. }
  1123.  
  1124. void ansiHome()
  1125. // ANSI terminal command to move the cursor to 0,0 without clearing screen
  1126. // used by ANSI output format
  1127. {
  1128.   // send ESC [H
  1129.   // which puts the cursor in the home 0,0 position on an ANSI terminal
  1130.   Serial.write(27);
  1131.   Serial.write(91);
  1132.   Serial.write(72);
  1133. }
  1134.  
  1135. void ansiTab()
  1136. // send ANSI "tab" character
  1137. // used by ANSI output format
  1138. {
  1139.   // send tab char
  1140.   Serial.write(9);
  1141. }
  1142.  
  1143. void pass()
  1144. // space-saver for pass/fail tests in ANSI output format
  1145. // used by ANSI output format
  1146. {
  1147.   Serial.println("    "); // print spaces to erase "FAIL" if necessary
  1148. }
  1149.  
  1150. void fail()
  1151. // space-saver for pass/fail test in ANSI output format
  1152. // used by ANSI output format
  1153. {
  1154.   Serial.println("FAIL");
  1155. }
  1156.  
  1157. void reboot()
  1158. // space-saver for menu reboot message
  1159. {
  1160.   Serial.println("REBOOT WEATHER BOARD TO PUT NEW SETTINGS INTO EFFECT");
  1161.   Serial.println();
  1162. }
  1163.  
  1164. void LCDclear()
  1165. // clear the screen of an SparkFun serial-enabled LCD
  1166. // used by LCD output format
  1167. {
  1168.   LCDline1();
  1169.   Serial.write("                ");
  1170.   LCDline2();
  1171.   Serial.write("                ");
  1172. }
  1173.  
  1174. void LCDline1()
  1175. // move cursor to start of line 1 on a SparkFun serial-enabled LCD
  1176. // used by LCD output format
  1177. {
  1178.   Serial.write(254);
  1179.   Serial.write(128);
  1180. }
  1181.  
  1182. void LCDline2()
  1183. // move cursor to start of line 2 on a SparkFun serial-enabled LCD
  1184. // used by LCD output format
  1185. {
  1186.   Serial.write(254);
  1187.   Serial.write(192);
  1188. }
  1189.  
  1190. void error(int errorcode) // save some space by printing out a generic error message
  1191. {
  1192.   Serial.print("ERROR #"); Serial.print(errorcode,DEC); Serial.println(", see sketch for cause");
  1193. }
  1194.  
  1195. void printComma() // we do this a lot, it saves two bytes each time we call it
  1196. {
  1197.   Serial.print(",");
  1198. }