Project - TeensyClimate

From My Notepad
Jump to: navigation, search

Project: TeensyClimate

The primary purpose of this project was to give me a reason to learn how to develop solutions using the Atmel AVR 8-bit microcontrollers.

The secondary purpose of this project is to develop a climate control system for the home.

Current Project Status

4 Dec 2014:

Really haven't done anything with this project since the last update primarily because I wasn't finding a decent relay module for the fan attic fan controls easily enough. Earlier this week someone showed me the SainSmart relay modules. They just came in today. Hopefully I'll get to play with soon and get them integrated into the project. The ultimate goal of this project was to have a device automatically control the attic fan during the sprint, summer and autumn months. With these boards I now see a light at the end of the tunnel.


30 July 2011:

The code has been cleaned up a little bit (you should have seen versions 0.1-0.4!!!).

While I am using the uthash library, I know I'm not using it properly, but it works. While I wanted to implement a hash table, it's pretty much just creating a linked list for me and I'm iterating the list in various places. I've tried implementing a standard linked list using pointers (in version 0.6) but it's not working properly. The sensor objects are being created with the proper hardware addresses in each location, but the temperature readings are not being stored right, which is just strange. So for now I'm continuing to eat up some extra memory while I use the uthash library.

I'm trying to determine the best relay setup to use to control the attic fan. I guess I just need to get over it and by a $15-20 120-240V relay and be done with it.

To Date min's and max's

  • 7/31/2011 14:11:16 - Max 131.67 @ Attic
  • 8/3/2011 14:58:15 - Max: 135.16 @ Attic

Development Photos

Breadboarded project
  1. Teensy 2.0 Development Board
  2. LCD Contrast Potentiometer (10k)
  3. DS18B20 Programmable Resolution 1-Wire Digital Thermometer: two on board and one in attic
  4. CAT3 extending 1-Wire bus to sensor in attic
  5. HD44780 compatible LCD display
Serial console extended stats
Serial console CLI options
Graphed data from 8 Aug 2011 (dark green is attic temperature, others are inside temperature)

The CLI was updated to export data using a CSV format (millis,now(),each sensors last conversion...). A Python script was written to read the text from the serial interface and write it to a file. LiveGraph was used to read the file in real time and display the graphed data. The graph above begins at about 11:30 PM 7 Aug 2011 and ends about 11:30 PM 8 Aug 2011. One sample is output to the CLI every second. After roughly a 24 hour period the CSV file was 2.69MB in size.

Version

The current version is 0.5c.

Features

As of 0.5c (1 Aug 2011):

  • Global min/max with sensor name and timestamps

As of 0.5b (30 July 2011):

  • Dynamic sensor discovery on device power on
  • Per sensor last/min/max temperatures with timestamps
  • Non-blocking temperature conversions
  • Sensor data display on LCD (currently limited to the first two sensors)
  • Sensor data display on serial console (basic and extended)
  • Serial console control
  • Time update via serial console

To Do

  • Air Conditioner Filter Change Reminder
  • Attic fan relay control
  • EEPROM storage of configuration information
  • Ethernet TCP/IP Interface using WizNet WIZ812MJ module
  • Historical statistics other than just current/min/max
    • avg/min/max per day
    • long term five minute interval graphing (via external means, SNMP possible)
  • Keypad for complete device control
  • PCB design
  • Final migration from breadboard to PCB

Source Code

   1 /*
   2 Starting point:
   3  http://tushev.org/articles/electronics/42-how-it-works-ds18b20-and-arduino
   4  20150822 Forked from temp_05b_hash_realtime_metrof
   5 
   6  -= TODO =-
   7 
   8  A/C Filter Change Reminder
   9 
  10  -= EEPROM STORAGE MAP =-
  11  1k bytes EEPROM available
  12  Page size is 8 bytes
  13  128 Pages
  14 
  15  Reserve the first page for empty (due to slot 0 possiblity of being corrupted)
  16 
  17  Reserve pages 2-9 for configuration data
  18 
  19  TIMEZONE_OFFSET_HOURS - char
  20  DST - daylight savings time - byte
  21  NUM_STORED_SENSORS - byte
  22  AIRFILTER REPLACEMENT DATE
  23  AIRFILTER LIFESPAN
  24 
  25  Pages 10 and up are reserved for stored sensors
  26 
  27  STORED SENSORS:
  28  ADDR - byte[8] (1 page)
  29  NAME - char[10] (1 page plus 2 bytes)
  30  CALIBRATION_OFFSET - float (2 bytes)
  31 
  32  */
  33 
  34 /*
  35  * Teensy 2.0 pinout details for this project
  36  * ==========================================
  37  * 0  - 
  38  * 1  - 
  39  * 2  - 
  40  * 3  - 
  41  * 4  - 
  42  * 5  - * i2c SCL
  43  * 6  - * i2c SDA
  44  * 7  - 
  45  * 8  - 
  46  * 9  - 
  47  * 10 - * OneWire bus
  48  * 11 - * LED_PIN Used for onboard signalling LED
  49  * 12 - * LCD Display
  50  * 13 - * LCD Display
  51  * 14 - * LCD Display
  52  * 15 - * LCD Display
  53  * 16 - * LCD Display
  54  * 17 - * LCD Display
  55  * 18 - 
  56  * 19 - 
  57  * 20 - 
  58  * 21 - A0 - Connected to Adafruit GA1A12S202 Log-scale Analog Light Sensor (response from 3 to 55,000 lux)
  59  * 
  60  * i2c Device Addresses
  61  * ====================
  62  * 0x29 - Adafruit TSL2591 High Dynamic Range Digital Light Sensor
  63  * 
  64  */
  65 
  66 // Uncomment the line below to enable DEBUG information. This will cause the compiled code to increase.
  67 //#define DEBUG 0
  68 
  69 #define SKETCHVERSION 7
  70 
  71 #define ACTIVITY_CHAR_INTERVAL 250
  72 #define BLINK_INTERVAL 250
  73 #define CONSOLE_UPDATE_INTERVAL 1000
  74 #define DEFAULT_MIN_TEMP 999
  75 #define DEFAULT_MAX_TEMP -99
  76 #define DEFAULT_TIME_ZONE_OFFSET -5  // CST during DST
  77 #define DS18B20_ID 0x28
  78 #define DS18B20_CONVERSION_WAIT_TIME 750
  79 #define HELP_PAUSE_METRO_INTERVAL 10000
  80 #define LCD_CLEAR_INTERVAL 10000
  81 #define LCD_UPDATE_INTERVAL 1000
  82 #define LCD_STRING_BUFFER_LENGTH 21
  83 #define LED_PIN 11 // physical pin 11
  84 
  85 #define ANALOG_LUX_SENSOR_PIN A0 // physical pin 21
  86 #define LIGHTANALOGSENSOR 250
  87 #define LIGHTDIGITALSENSOR 1000
  88 
  89 #include <avr/pgmspace.h>
  90 #include <MemoryFree.h>
  91 #include <LiquidCrystal.h>
  92 #include <Metro.h>
  93 #include <OneWire.h>
  94 #include <String.h>
  95 #include "C:\ArduinoProjects\lib\Streaming.h"
  96 #include <Time.h>
  97 #include "C:\ArduinoProjects\lib\uthash-master\uthash.h"
  98 
  99 // 20150822 MSHARP - Adding TSL2591 light sensor support
 100 #include <Wire.h>
 101 #include <Adafruit_Sensor.h>
 102 #include "Adafruit_TSL2591.h"
 103 
 104 enum console_mode_t {
 105   standard,
 106   extended,
 107   streaming
 108 };
 109 
 110 console_mode_t consoleMode = standard;
 111 
 112 // ===== ===== ===== ===== ===== ===== ===== ===== ===== ===== ===== =====
 113 // Using PROGMEM ROM to store strings
 114 // Use printPROGMEMString() to print these strings to the serial port
 115 // ===== ===== ===== ===== ===== ===== ===== ===== ===== ===== ===== =====
 116 
 117 const char version_line_1[] PROGMEM = "Sketch version: ";
 118 
 119 const char message_settingup [] PROGMEM = "Setting up...";
 120 
 121 const char message_timesync_waiting [] PROGMEM = "Waiting for time data in @time_t format...";
 122 const char message_timesync_updated [] PROGMEM = "Sync message received and time updated.";
 123 const char message_timesync_invalid [] PROGMEM = "Invalid sync message received.";
 124 
 125 const char message_bootloader_jump_1 [] PROGMEM = "Jumping to bootloader in ";
 126 const char message_bootloader_jump_2 [] PROGMEM = "Make sure you close your serial console!!!";
 127 const char message_bootloader_jump_jumping [] PROGMEM = "Jumping!";
 128 
 129 const char message_pressanykeytoabort [] PROGMEM = "PRESS ANY KEY TO ABORT!!!";
 130 const char message_aborted [] PROGMEM = "Aborted!";
 131 
 132 // ===== ===== ===== ===== ===== ===== ===== ===== ===== ===== ===== =====
 133 
 134 #define CLI_STRING_BUFFER_LENGTH 50
 135 #define NUM_CLI_LINES 13
 136 
 137 const char cli_line_1[] PROGMEM  = "CLI Options:";
 138 const char cli_line_2[] PROGMEM  = "B:     jump to bootloader";
 139 const char cli_line_3[] PROGMEM  = "c:     cycle console mode";
 140 const char cli_line_4[] PROGMEM  = "C:     clear sensors stats";
 141 const char cli_line_5[] PROGMEM  = "d/D:   toggle lcd display contents";
 142 const char cli_line_6[] PROGMEM  = "f/F:   show current free memory";
 143 const char cli_line_7[] PROGMEM  = "G:     reset global sensor stats";
 144 const char cli_line_8[] PROGMEM  = "h:     display this help";
 145 const char cli_line_9[] PROGMEM  = "l:     query analog light sensor and display result";
 146 const char cli_line_10[] PROGMEM = "L:     query digital light sensor and display result";
 147 const char cli_line_11[] PROGMEM = "s/S:   set console mode to streaming";
 148 const char cli_line_12[] PROGMEM = "T:     time sync via console";
 149 const char cli_line_13[] PROGMEM = "v/V:   show version";
 150 
 151 const char* const cli_string_table[] PROGMEM =
 152 {
 153   cli_line_1,
 154   cli_line_2,
 155   cli_line_3,
 156   cli_line_4,
 157   cli_line_5,
 158   cli_line_6,
 159   cli_line_7,
 160   cli_line_8,
 161   cli_line_9,
 162   cli_line_10,
 163   cli_line_11,
 164   cli_line_12,
 165   cli_line_13
 166 };
 167 
 168 // ===== ===== ===== ===== ===== ===== ===== ===== ===== ===== ===== =====
 169 
 170 // These are sensors that I know I have. Eventually we'll store this in EEPROM programatically
 171 byte tempSensorAttic[8] = {
 172   0x28, 0xdb, 0x32, 0x5b, 0x03, 0x00, 0x00, 0x8d
 173 };
 174 byte tempSensorInside1[8] = {
 175   0x28, 0xb7, 0x4b, 0x5b, 0x03, 0x00, 0x00, 0xad
 176 };
 177 byte tempSensorInside2[8] = {
 178   0x28, 0x39, 0x44, 0x5b, 0x03, 0x00, 0x00, 0x3b
 179 };
 180 
 181 boolean consolePaused = false;
 182 boolean lcdDisplayAddresses = false;
 183 boolean showExtendedStats = false;
 184 
 185 unsigned int activityCharIndex = 0;
 186 unsigned int ledStatus = 0;
 187 
 188 unsigned long loopIteration = 0;
 189 unsigned long loopStartMillis;
 190 unsigned long lastLoopDuration = 0;
 191 
 192 // LCD Display using 6 pins (12-17)
 193 LiquidCrystal lcd(16, 17, 12, 13, 14, 15);
 194 
 195 // Adafruit TSL2591 High Dynamic Range Digital Light Sensor
 196 boolean tslFound = false;
 197 Adafruit_TSL2591 tsl = Adafruit_TSL2591(2591);
 198 
 199 // Global variables for templerature sensors
 200 const int luxAnalogNumReadings = 10;
 201 float luxAnalogReadings[luxAnalogNumReadings];
 202 float luxAnalogRaw = 0.0;
 203 int luxAnalogRawIndex = 0;
 204 float luxAnalogRawTotal = 0.0;
 205 float luxAnalogRawAverage = 0.0;
 206 float luxAnalogLog = 0.0;
 207 float luxAnalogLogSmoothed = 0.0;
 208 
 209 uint32_t luxDigitalFullLuminosity;
 210 uint16_t luxDigitalIR, luxDigitalFull, luxDigitalCalculated;
 211 
 212 // Manually update this number when you add a Metro below
 213 #define NUM_METROS 7
 214 
 215 Metro activityCharMetro(ACTIVITY_CHAR_INTERVAL, false);
 216 Metro consoleUpdateMetro(CONSOLE_UPDATE_INTERVAL);
 217 Metro helpPauseMetro(HELP_PAUSE_METRO_INTERVAL, false);
 218 Metro ledMetro(BLINK_INTERVAL, false);
 219 Metro lcdUpdateMetro(LCD_UPDATE_INTERVAL);
 220 Metro lcdClearMetro(LCD_CLEAR_INTERVAL);  // counter to clear the lcd every 10 seconds for housekeeping
 221 Metro owBusSearchMetro(600000, false);  //  counter to search the bus every 10 minutes for new devices
 222 Metro lightAnalogSensor(LIGHTANALOGSENSOR, false);
 223 Metro lightDigitalSensor(LIGHTDIGITALSENSOR, false);
 224   
 225 // OneWire bus on pin 10
 226 OneWire ds(10);
 227 
 228 float globalMin = DEFAULT_MIN_TEMP;
 229 char * globalMinName = NULL;
 230 time_t globalMinTimeStamp = (time_t) 0;
 231 float globalMax = DEFAULT_MAX_TEMP;
 232 char * globalMaxName = NULL;
 233 time_t globalMaxTimeStamp = (time_t) 0;
 234 
 235 struct DS18B20 {
 236   byte addr[8];              /* key */
 237   char name[10];
 238   boolean active;
 239   boolean converting;  /* true if a conversion has been requested */
 240   unsigned int crcerrors;
 241   unsigned long startConversionLI;  // loop iteration of the start conversion
 242   unsigned long liLastConversion;    // number of loop iterations for the last conversion
 243   Metro conversionTimer;
 244   float lastTemp;
 245   time_t lastTimeStamp;
 246   float minTemp;
 247   time_t minTimeStamp;
 248   float maxTemp;
 249   time_t maxTimeStamp;
 250   UT_hash_handle hh;         /* makes this structure hashable */
 251 };
 252 
 253 struct DS18B20 *tempSensors = NULL;
 254 
 255 boolean compareByteArray(byte a1[], byte a2[]) {
 256   int arraySize = sizeof(a1) / sizeof(byte);
 257   if (sizeof(a1) != sizeof(a2)) {
 258     return false;
 259   }
 260   for (int i = 0; i < arraySize; i++) {
 261     if (a1[i] != a2[i]) {
 262       return false;
 263     }
 264   }
 265   return true;
 266 }
 267 
 268 void add_sensor(byte addr[]) {
 269   struct DS18B20 *s;
 270 
 271   s = (DS18B20 *) malloc(sizeof(struct DS18B20));
 272   for (int i = 0; i < 8; i++) {
 273     s->addr[i] = addr[i];
 274   }
 275   s->active = false;
 276   s->converting = false;
 277   s->crcerrors = 0;
 278   s->conversionTimer = Metro(DS18B20_CONVERSION_WAIT_TIME, false);
 279   s->liLastConversion = 0;
 280   s->lastTemp = 0.0;
 281   s->lastTimeStamp = now();
 282   s->minTemp = DEFAULT_MIN_TEMP;
 283   s->minTimeStamp = s->lastTimeStamp;
 284   s->maxTemp = DEFAULT_MAX_TEMP;
 285   s->maxTimeStamp = s->lastTimeStamp;
 286   if (compareByteArray(s->addr, tempSensorAttic)) {
 287     strcpy(s->name, "Attic");
 288   }
 289   else if (compareByteArray(s->addr, tempSensorInside1)) {
 290     strcpy(s->name, "Inside1");
 291   }
 292   else if (compareByteArray(s->addr, tempSensorInside2)) {
 293     strcpy(s->name, "Inside2");
 294   }
 295   else {
 296     strcpy(s->name, "Unknown");
 297   }
 298   HASH_ADD(hh, tempSensors, addr, sizeof(byte) * 8, s);
 299 }
 300 
 301 struct DS18B20 *find_sensor(byte addr[]) {
 302   struct DS18B20 *s;
 303 
 304 #ifdef DEBUG
 305   Serial.print("find_sensor(");
 306   Serial.print(OneWireaddrtostring(addr, false));
 307   Serial.println(")");
 308 #endif
 309 
 310   for (s = tempSensors; s != NULL; s = (DS18B20 *) s->hh.next) {
 311 #ifdef DEBUG
 312     Serial.println("Comparing:");
 313     Serial.print(OneWireaddrtostring(s->addr, false));
 314     Serial.print(" to ");
 315     Serial.println(OneWireaddrtostring(addr, false));
 316 #endif
 317 
 318     if (compareByteArray(addr, s->addr)) {
 319 #ifdef DEBUG
 320       Serial.println("  match found... sensor already detected");
 321 #endif
 322       return s;
 323     }
 324   }
 325 
 326 #ifdef DEBUG
 327   Serial.println("no match found... returning NULL");
 328 #endif
 329   return NULL;
 330 }
 331 
 332 void update_console() {
 333   struct DS18B20 *s;
 334 
 335   // Do a digital lux reading before we attempt displaying anything... this will make the display update more pleasing
 336   doLuxReadingDigital();
 337 
 338   if ((consoleMode == standard) || (consoleMode == extended)) {
 339     // Now lets start putting stuff on the console
 340     serialPrintDateTime(now());
 341     Serial << " (Uptime: " << millis() / 1000 << " s) (li: " << loopIteration << ") (lld: " << lastLoopDuration << " ms)" << endl;
 342     Serial << "Global Min: " << globalMin << " @ ";
 343     serialPrintDateTime(globalMinTimeStamp);
 344     Serial << " (" << globalMinName << ")" << endl;
 345     Serial << "Global Max: " << globalMax << " @ ";
 346     serialPrintDateTime(globalMaxTimeStamp);
 347     Serial << " (" << globalMaxName << ")" << endl << endl;
 348     Serial.println("Current list of sensors:");
 349 
 350     for (s = tempSensors; s != NULL; s = (DS18B20 *) s->hh.next) {
 351       Serial.print(OneWireaddrtostring(s->addr, false));
 352       if (s->active) {
 353         if (consoleMode == standard) {
 354           // show standard information
 355           Serial.print(" ");
 356           Serial.print(s->lastTemp);
 357           Serial.print("/");
 358           Serial.print(s->minTemp);
 359           Serial.print("/");
 360           Serial.print(s->maxTemp);
 361           Serial.print(" F, Location: ");
 362           Serial.println(s->name);
 363         }
 364         else {
 365           // show extended stats
 366           Serial << " Location: " << s->name << " (crcerrors: " << s->crcerrors << " ) (lis: " << s->liLastConversion << ")" << endl;
 367           Serial << "    Last: " << s->lastTemp << " @ ";
 368           serialPrintDateTime(s->lastTimeStamp);
 369           Serial << endl;
 370           Serial << "    Min: " << s->minTemp << " @ ";
 371           serialPrintDateTime(s->minTimeStamp);
 372           Serial << endl;
 373           Serial << "    Max: " << s->maxTemp << " @ ";
 374           serialPrintDateTime(s->maxTimeStamp);
 375           Serial << endl;
 376         }
 377       }
 378       else {
 379         Serial.println(" is pending first read.");
 380       }
 381     }
 382     // Display light sensor data now
 383     Serial.println();
 384     displayAnalogLightSensorData();
 385     displayDigitalLightSensorData();
 386   }
 387   else {
 388     // streaming
 389     //        Serial << "@" << endl;
 390     //     serialPrintDateTime(now());
 391     Serial << millis() << "," << now();
 392     Serial.print(",");
 393     /*     Serial << "," << millis() / 1000 << endl;
 394          Serial << globalMin << ",";
 395          serialPrintDateTime(globalMinTimeStamp);
 396          Serial << "," << globalMinName << endl;
 397          Serial << globalMax << ",";
 398          serialPrintDateTime(globalMaxTimeStamp);
 399          Serial << "," << globalMaxName << endl;
 400          */
 401     for (s = tempSensors; s != NULL; s = (DS18B20 *) s->hh.next) {
 402       //      Serial.print(OneWireaddrtostring(s->addr, false));
 403       //      Serial.print(",");
 404       Serial.print(s->lastTemp);
 405       if (s->hh.next != NULL) {
 406         Serial.print(",");
 407       }
 408       /*      serialPrintDateTime(s->lastTimeStamp);
 409        Serial.print(",");
 410        Serial.print(s->minTemp);
 411        Serial.print(",");
 412        serialPrintDateTime(s->minTimeStamp);
 413        Serial.print(",");
 414        Serial.print(s->maxTemp);
 415        Serial.print(",");
 416        serialPrintDateTime(s->maxTimeStamp);
 417        Serial.print(",");
 418        Serial.println(s->name); */
 419     }
 420 
 421     Serial.print(",LA,");
 422     Serial.print(luxAnalogReadings[luxAnalogRawIndex]);
 423     Serial.print(",");
 424     Serial.print(luxAnalogRawAverage);
 425     Serial.print(",");
 426     Serial.print(pow(10, luxAnalogLog));
 427     Serial.print(",");
 428     Serial.print(pow(10, luxAnalogLogSmoothed));
 429 
 430     if (tslFound) {
 431       Serial.print(",LD,");
 432       Serial.print("IR,"); Serial.print(luxDigitalIR);  Serial.print(",");
 433       Serial.print("F,"); Serial.print(luxDigitalFull); Serial.print(",");
 434       Serial.print("V,"); Serial.print(luxDigitalFull - luxDigitalIR); Serial.print(",");
 435       Serial.print("L,"); Serial.print(luxDigitalCalculated);
 436     }
 437   }
 438   Serial.println();
 439 }
 440 
 441 boolean update_sensor(byte addr[], float temp) {
 442   struct DS18B20 *s;
 443 
 444   s = find_sensor(addr);
 445   if (s != NULL) {
 446     if (!s->active) {
 447       s->active = true;
 448     }
 449 
 450     s->lastTemp = temp;
 451     s->lastTimeStamp = now();
 452 
 453     if (temp < s->minTemp) {
 454       s->minTemp = temp;
 455       s->minTimeStamp = now();
 456     }
 457 
 458     if (temp < globalMin) {
 459       globalMin = temp;
 460       globalMinName = s->name;
 461       globalMinTimeStamp = now();
 462     }
 463 
 464     if (temp > s->maxTemp) {
 465       s->maxTemp = temp;
 466       s->maxTimeStamp = now();
 467     }
 468 
 469     if (temp > globalMax) {
 470       globalMax = temp;
 471       globalMaxName = s->name;
 472       globalMaxTimeStamp = now();
 473     }
 474 
 475     return true;
 476   }
 477   else {
 478 #ifdef DEBUG
 479     Serial.print("update_sensor() failed for addr ");
 480     Serial.print(OneWireaddrtostring(addr, false));
 481     Serial.print(" ");
 482     Serial.print(temp);
 483     Serial.println(" F");
 484 #endif
 485     return false;
 486   }
 487 }
 488 
 489 int count_sensors() {
 490   struct DS18B20 *s;
 491   int count = 0;
 492   for (s = tempSensors; s != NULL; s = (DS18B20 *) s->hh.next, count++) {
 493   }
 494   return count;
 495 }
 496 
 497 void clear_sensor_stats() {
 498   struct DS18B20 *s;
 499 
 500   for (s = tempSensors; s != NULL; s = (DS18B20 *) s->hh.next) {
 501     s->minTemp = DEFAULT_MIN_TEMP;
 502     s->minTimeStamp = (time_t) 0;
 503     s->maxTemp = DEFAULT_MAX_TEMP;
 504     s->maxTimeStamp = (time_t) 0;
 505   }
 506 }
 507 
 508 void clear_global_sensor_stats() {
 509   globalMin = DEFAULT_MIN_TEMP;
 510   globalMinName = NULL;
 511   globalMinTimeStamp = (time_t) 0;
 512   globalMax = DEFAULT_MAX_TEMP;
 513   globalMaxName = NULL;
 514   globalMaxTimeStamp = (time_t) 0;
 515 }
 516 
 517 void doLuxReadingAnalog() {
 518   float rawRange = 1024; // 3.3v
 519   float logRange = 5.0;  // 3.3 = 10^5 lux
 520 
 521   luxAnalogRawTotal = luxAnalogRawTotal - luxAnalogReadings[luxAnalogRawIndex];
 522   luxAnalogReadings[luxAnalogRawIndex] = analogRead(ANALOG_LUX_SENSOR_PIN);
 523   luxAnalogRawTotal = luxAnalogRawTotal + luxAnalogReadings[luxAnalogRawIndex];
 524   luxAnalogRawAverage = luxAnalogRawTotal / luxAnalogNumReadings;
 525   luxAnalogLog = luxAnalogRawAverage * logRange / rawRange;
 526   luxAnalogLogSmoothed = luxAnalogRawAverage * logRange / rawRange;
 527   
 528   luxAnalogRawIndex = luxAnalogRawIndex + 1;
 529   if (luxAnalogRawIndex >= luxAnalogNumReadings){
 530     luxAnalogRawIndex = 0;
 531   }
 532   
 533 }
 534 
 535 void displayAnalogLightSensorData() {
 536   Serial.print("Light [analog] : ");
 537   Serial.print("Raw: ");
 538   Serial.print(luxAnalogReadings[luxAnalogRawIndex]);
 539   Serial.print("  Smoothed: ");
 540   Serial.print(luxAnalogRawAverage);
 541   Serial.print("  Log: ");
 542   Serial.print(pow(10, luxAnalogLog));
 543   Serial.print("  Smoothed: ");
 544   Serial.println(pow(10, luxAnalogLogSmoothed));
 545 }
 546 
 547 // Updates global variables to be used in displayDigitalLightSensorDataData()
 548 void doLuxReadingDigital() {
 549   if (!tslFound) {
 550     Serial.println("No TSL2591 device has been found.");
 551     return;
 552   }
 553 
 554   // You can change the gain on the fly, to adapt to brighter/dimmer light situations
 555   //tsl.setGain(TSL2591_GAIN_LOW);    // 1x gain (bright light)
 556   tsl.setGain(TSL2591_GAIN_MED);      // 25x gain
 557   //tsl.setGain(TSL2591_GAIN_HIGH);   // 428x gain
 558 
 559   // Changing the integration time gives you a longer time over which to sense light
 560   // longer timelines are slower, but are good in very low light situtations!
 561   tsl.setTiming(TSL2591_INTEGRATIONTIME_100MS);  // shortest integration time (bright light)
 562   //tsl.setTiming(TSL2591_INTEGRATIONTIME_200MS);
 563   //tsl.setTiming(TSL2591_INTEGRATIONTIME_300MS);
 564   //tsl.setTiming(TSL2591_INTEGRATIONTIME_400MS);
 565   //tsl.setTiming(TSL2591_INTEGRATIONTIME_500MS);
 566   //tsl.setTiming(TSL2591_INTEGRATIONTIME_600MS);  // longest integration time (dim light)
 567 
 568   luxDigitalFullLuminosity = tsl.getFullLuminosity();
 569   luxDigitalIR = luxDigitalFullLuminosity >> 16;
 570   luxDigitalFull = luxDigitalFullLuminosity & 0xFFFF;
 571   luxDigitalCalculated = tsl.calculateLux(luxDigitalFull, luxDigitalIR);
 572 }
 573 
 574 void displayDigitalLightSensorData() {
 575   if (!tslFound) {
 576     Serial.println("No TSL2591 device has been found.");
 577     return;
 578   }
 579   
 580   Serial.print("Light [digital]: ");
 581   Serial.print("IR: "); Serial.print(luxDigitalIR);  Serial.print("  ");
 582   Serial.print("Full: "); Serial.print(luxDigitalFull); Serial.print("  ");
 583   Serial.print("Visible: "); Serial.print(luxDigitalFull - luxDigitalIR); Serial.print("  ");
 584   Serial.print("Lux: "); Serial.print(luxDigitalCalculated);
 585   Serial.println();
 586 }
 587 
 588 boolean findDS18B20Devices(OneWire & ow) {
 589   byte addr[8];
 590   unsigned int deviceCount = 0;
 591 
 592 #ifdef DEBUG
 593   Serial.println("Searching bus...");
 594 #endif
 595 
 596   //find a device
 597   while (ow.search(addr)) {
 598     if (OneWire::crc8( addr, 7) != addr[7]) {
 599       Serial.println("Bad crc!!!");
 600       continue;
 601     }
 602 
 603     if (addr[0] != DS18B20_ID) {
 604 #ifdef DEBUG
 605       Serial.print("Unknown device: ");
 606 #endif
 607       Serial.println(OneWireaddrtostring(addr, false));
 608       continue;
 609     }
 610 
 611 #ifdef DEBUG
 612     Serial.print("Found a device:");
 613     Serial.println(OneWireaddrtostring(addr, false));
 614 #endif
 615 
 616     deviceCount++;
 617     if (find_sensor(addr) == NULL) {
 618 #ifdef DEBUG
 619       Serial.print("Adding new sensor to list: ");
 620 #endif
 621       add_sensor(addr);
 622     }
 623 #ifdef DEBUG
 624     else {
 625       Serial.print("We already know about this sensor: ");
 626     }
 627 #endif
 628     Serial.println(OneWireaddrtostring(addr, false));
 629     Serial.println();
 630   }
 631 
 632 #ifdef DEBUG
 633   Serial.println("No more devices found... resetting search.");
 634   Serial.println();
 635 #endif
 636   ow.reset_search();
 637 }
 638 
 639 boolean requestTemperatureConversion(OneWire ow, DS18B20 *sensor) {
 640   ow.reset();
 641   ow.select(sensor->addr);
 642   ow.write(0x44, 1);
 643 
 644   return true;
 645 }
 646 
 647 float retrieveTemperature(OneWire ow, DS18B20 *sensor) {
 648   byte data[12];
 649   float temp;
 650 
 651   ow.reset();
 652   ow.select(sensor->addr);
 653   ow.write(0xBE);
 654   for (int i = 0; i < 9; i++) {
 655     data[i] = ow.read();
 656   }
 657 
 658   temp = ( (data[1] << 8) + data[0] ) * 0.0625; // tempc
 659   temp = (temp * 1.8) + 32;  // tempf
 660 
 661   if (OneWire::crc8( data, 8) != data[8]) {
 662     temp = -9999;
 663   }
 664 
 665   return temp;
 666 }
 667 
 668 void toggleLED() {
 669   switch (ledStatus) {
 670     case 1:
 671       digitalWrite(LED_PIN, LOW);
 672       ledStatus = 0;
 673       break;
 674     default:
 675       digitalWrite(LED_PIN, HIGH);
 676       ledStatus = 1;
 677       break;
 678   }
 679 }
 680 
 681 void showActivityChar() {
 682   lcd.setCursor(18, 0);
 683   switch (activityCharIndex) {
 684     case 1:
 685       activityCharIndex--;
 686       lcd.print(count_sensors());
 687       lcd.print(" ");
 688       break;
 689     default:
 690       activityCharIndex++;
 691       lcd.print(count_sensors());
 692       lcd.print("*");
 693       break;
 694   }
 695 }
 696 
 697 String OneWireaddrtostring(byte addr[], boolean lcd) {
 698   String toReturn;
 699 
 700   for ( int i = 0; i < 8; i++) {
 701     if (addr[i] < 16) {
 702       toReturn += "0";
 703     }
 704     toReturn += String(addr[i], HEX);
 705     if (i < 7) {
 706       if (!lcd) {
 707         // don't print semicolons on the lcd
 708         // we don't have enough room
 709         toReturn += ":";
 710       }
 711     }
 712   }
 713 
 714   return toReturn;
 715 }
 716 
 717 void update_lcd() {
 718   struct DS18B20 *s;
 719   unsigned int count;
 720   unsigned int maxCount = 2;
 721 
 722   s = tempSensors;
 723   if (lcdDisplayAddresses) {
 724     maxCount = 3;
 725   }
 726 
 727   if (lcdClearMetro.check() == 1) {
 728     lcd.clear();
 729     lcdClearMetro.reset();
 730   }
 731 
 732   if (lcdDisplayAddresses) {
 733     lcd.setCursor(0, 0);
 734     lcd.print("Uptime: ");
 735     lcd.print(millis() / 1000);
 736     lcd.print("s ");
 737   }
 738 
 739   for (count = 0; count < maxCount; count++, s = (DS18B20 *) s->hh.next) {
 740     if (s == NULL) {
 741       break;
 742     }
 743 
 744     if (!lcdDisplayAddresses) {
 745       if (!s->active) {
 746         continue;
 747       }
 748       lcd.setCursor(count * 10, 0);
 749       lcd.print(s->name);
 750       lcd.setCursor(count * 10, 1);
 751       lcd.print(s->lastTemp);
 752       lcd.print((char)223);
 753       lcd.print("F");
 754       lcd.setCursor(count * 10, 2);
 755       lcd.print(s->minTemp);
 756       lcd.print((char)223);
 757       lcd.print("F");
 758       lcd.setCursor(count * 10, 3);
 759       lcd.print(s->maxTemp);
 760       lcd.print((char)223);
 761       lcd.print("F");
 762     }
 763     else {
 764       // display addresses instead of temps
 765       lcd.setCursor(0, count + 1);
 766       lcd.print("    ");
 767       lcd.print(OneWireaddrtostring(s->addr, true));
 768       lcd.setCursor(0, count + 1);
 769       lcd.print(s->name);
 770       lcd.print(":");
 771     }
 772   }
 773 }
 774 
 775 void serialPrintDateTime(time_t timeStamp) {
 776   time_t timeStampAdjusted = timeStamp + (DEFAULT_TIME_ZONE_OFFSET * 60 * 60);
 777   Serial << month(timeStampAdjusted) << "/" << day(timeStampAdjusted) << "/" << year(timeStampAdjusted) << " " << hour(timeStampAdjusted) << ":";
 778   if (minute(timeStampAdjusted) < 10) Serial << "0";
 779   Serial << minute(timeStampAdjusted) << ":";
 780   if (second(timeStampAdjusted) < 10) Serial << "0";
 781   Serial << second(timeStampAdjusted);
 782 }
 783 
 784 void processTimeSyncMessage() {
 785   int count = 0;
 786   char buf[11];
 787   boolean status = false;  // did we get good data
 788   printPROGMEMString(message_timesync_waiting); // "Waiting for time data in @time_t format..."
 789   Serial.println();
 790   Serial.flush();
 791   while (count < 11) {
 792     if (Serial.available()) {  // receive all 11 bytes into "buf"
 793       buf[count++] = Serial.read();
 794     }
 795   }
 796   if (buf[0] == '@') {
 797     time_t pctime = 0;
 798     for (int i = 1; i < 11; i++) {
 799       char c = buf[i];
 800       if (c >= '0' && c <= '9') {
 801         pctime = (10 * pctime) + (c - '0') ; // convert digits to a number
 802       }
 803     }
 804     pctime += 10;
 805     setTime(pctime);   // Sync clock to the time received
 806     status = true;
 807   }
 808   if (status) {
 809     // "Sync message received and time updated."
 810     printPROGMEMString(message_timesync_updated);
 811     Serial.println();
 812   }
 813   else {
 814     // "Invalid sync message received."
 815     printPROGMEMString(message_timesync_invalid);
 816     Serial.println();
 817   }
 818 }
 819 
 820 void jumpToBootloader() {
 821   unsigned int counter = 10;
 822   printPROGMEMString(message_bootloader_jump_1); // "Jumping to bootloader in "
 823   Serial << counter << " seconds...";
 824   Serial.println();
 825   printPROGMEMString(message_bootloader_jump_2); // "Make sure you close your serial console!!!"
 826   Serial.println(); Serial.println();
 827   printPROGMEMString(message_pressanykeytoabort); // "PRESS ANY KEY TO ABORT!!!"
 828   Serial.println(); Serial.println();
 829   Serial.flush();
 830   lcd.clear();
 831   while (counter > 0) {
 832     lcd.setCursor(0, 0);
 833     lcd.print(counter);
 834     Serial << counter << " ";
 835     if (Serial.available()) {
 836       Serial.flush();
 837       Serial.println(); Serial.println();
 838       printPROGMEMString(message_aborted); // "Aborted!"
 839       Serial.println(); Serial.println(); Serial.println();
 840       return;
 841     }
 842     delay(1000);
 843     counter--;
 844   }
 845   printPROGMEMString(message_bootloader_jump_jumping); // "Jumping!"
 846   Serial.println();
 847   cli();
 848   // disable watchdog, if enabled
 849   // disable all peripherals
 850   UDCON = 1;
 851   USBCON = (1 << FRZCLK); // disable USB
 852   UCSR1B = 0;
 853   delay(5);
 854 #if defined(__AVR_AT90USB162__)                // Teensy 1.0
 855   EIMSK = 0; PCICR = 0; SPCR = 0; ACSR = 0; EECR = 0;
 856   TIMSK0 = 0; TIMSK1 = 0; UCSR1B = 0;
 857   DDRB = 0; DDRC = 0; DDRD = 0;
 858   PORTB = 0; PORTC = 0; PORTD = 0;
 859   asm volatile("jmp 0x3E00");
 860 #elif defined(__AVR_ATmega32U4__)              // Teensy 2.0
 861   EIMSK = 0; PCICR = 0; SPCR = 0; ACSR = 0; EECR = 0; ADCSRA = 0;
 862   TIMSK0 = 0; TIMSK1 = 0; TIMSK3 = 0; TIMSK4 = 0; UCSR1B = 0; TWCR = 0;
 863   DDRB = 0; DDRC = 0; DDRD = 0; DDRE = 0; DDRF = 0; TWCR = 0;
 864   PORTB = 0; PORTC = 0; PORTD = 0; PORTE = 0; PORTF = 0;
 865   asm volatile("jmp 0x7E00");
 866 #elif defined(__AVR_AT90USB646__)              // Teensy++ 1.0
 867   EIMSK = 0; PCICR = 0; SPCR = 0; ACSR = 0; EECR = 0; ADCSRA = 0;
 868   TIMSK0 = 0; TIMSK1 = 0; TIMSK2 = 0; TIMSK3 = 0; UCSR1B = 0; TWCR = 0;
 869   DDRA = 0; DDRB = 0; DDRC = 0; DDRD = 0; DDRE = 0; DDRF = 0;
 870   PORTA = 0; PORTB = 0; PORTC = 0; PORTD = 0; PORTE = 0; PORTF = 0;
 871   asm volatile("jmp 0xFC00");
 872 #elif defined(__AVR_AT90USB1286__)             // Teensy++ 2.0
 873   EIMSK = 0; PCICR = 0; SPCR = 0; ACSR = 0; EECR = 0; ADCSRA = 0;
 874   TIMSK0 = 0; TIMSK1 = 0; TIMSK2 = 0; TIMSK3 = 0; UCSR1B = 0; TWCR = 0;
 875   DDRA = 0; DDRB = 0; DDRC = 0; DDRD = 0; DDRE = 0; DDRF = 0;
 876   PORTA = 0; PORTB = 0; PORTC = 0; PORTD = 0; PORTE = 0; PORTF = 0;
 877   asm volatile("jmp 0x1FC00");
 878 #endif
 879 }
 880 
 881 void printCLIOptions() {
 882 //  char buf[CLI_STRING_BUFFER_LENGTH];
 883 
 884   for (int i = 0; i < NUM_CLI_LINES; i++) {
 885 //    strcpy_P(buf, (char*)pgm_read_word(&(cli_string_table[i])));
 886 //    Serial.println(buf);
 887     printPROGMEMString((char*) pgm_read_word(&(cli_string_table[i])));
 888     Serial.println();
 889   }
 890 
 891   Serial.println();
 892 }
 893 
 894 // 20150822 MSHARP - Added function
 895 void printPROGMEMString(const char* PMSTRING) {
 896   int i;
 897   int len = strlen_P(PMSTRING);
 898   char nextCharacter;
 899   for (i = 0; i < len; i++) {
 900     nextCharacter = pgm_read_byte_near(PMSTRING + i);
 901     Serial.print(nextCharacter);
 902   }
 903 
 904 }
 905 
 906 void setup() {
 907   // Initialize the display and tell the world we're starting to work
 908   lcd.begin(20, 4);
 909   lcd.setCursor(0, 0);
 910   lcd.print("Setting up...");
 911 
 912   pinMode(LED_PIN, OUTPUT);
 913   ledMetro.reset();
 914   Serial.begin(9600);
 915 
 916   // displaying activity char so we know we've started the initial 1-wire search
 917   showActivityChar();
 918   
 919   // Give human 10 seconds to connect via serial to watch for debug information
 920 #ifdef DEBUG
 921   for (int i = 0; i < 10; i++) {
 922     Serial << i << " ";
 923     delay(1000);
 924   }
 925   Serial << endl;
 926 #endif
 927 
 928   findDS18B20Devices(ds);
 929 
 930   // Setup Adafruit i2c TSL sensor
 931   if (tsl.begin()) {
 932     Serial.println("Found a TSL2591 sensor!!!");
 933     tslFound = true;
 934   }
 935 
 936   // Give human 10 seconds to view debug information from device scan
 937 #ifdef DEBUG
 938   for (int i = 0; i < 10; i++) {
 939     Serial << i << " ";
 940     delay(1000);
 941   }
 942   Serial << endl;
 943 #endif
 944 
 945   // Initialize analog lux reading smoothing array
 946   for (int thisReading = 0; thisReading < luxAnalogNumReadings; thisReading++) {
 947     luxAnalogReadings[thisReading] = 0.0;
 948   }
 949 } // end setup
 950 
 951 void loop() {
 952   loopIteration++;
 953   loopStartMillis = millis();
 954 
 955   float tmpTemp = 0;
 956   struct DS18B20 *s;
 957 
 958   if (activityCharMetro.check() == 1) {
 959     showActivityChar();
 960     activityCharMetro.reset();
 961   }
 962 
 963   // manage the sensors
 964   for (s = tempSensors; s != NULL; s = (DS18B20 *) s->hh.next) {
 965     if (s->converting) {
 966       if (s->conversionTimer.check() == 1) {
 967         tmpTemp = retrieveTemperature(ds, s);
 968         if (tmpTemp != -9999) {
 969           update_sensor(s->addr, tmpTemp);
 970         }
 971         else {
 972           s->crcerrors++;
 973         }
 974         s->liLastConversion = loopIteration - s->startConversionLI;
 975         s->converting = false;
 976         tmpTemp = 0;
 977       }
 978     }
 979     else {
 980       requestTemperatureConversion(ds, s);
 981       s->startConversionLI = loopIteration;
 982       s->conversionTimer.reset();
 983       s->converting = true;
 984     }
 985 
 986   } // for tempSensors
 987 
 988   // manage analog lux sensor
 989   if (lightAnalogSensor.check() == 1) {
 990     doLuxReadingAnalog();
 991     lightAnalogSensor.reset();
 992   }
 993 
 994 
 995   // process command line input
 996   if (Serial.available() > 0) {
 997     char c = Serial.read();
 998     switch (c) {
 999       case 'B':
1000         jumpToBootloader();
1001         break;
1002       case 'c':
1003         if (consoleMode == streaming) {
1004 #ifdef DEBUG
1005           Serial.println("Setting console mode to standard.");
1006 #endif
1007           consoleMode = standard;
1008         }
1009         else if (consoleMode == standard) {
1010 #ifdef DEBUG
1011           Serial.println("Setting console mode to extended.");
1012 #endif
1013           consoleMode = extended;
1014         }
1015         else if (consoleMode == extended) {
1016 #ifdef DEBUG
1017           Serial.println("Setting console mode to streaming.");
1018 #endif
1019           consoleMode = streaming;
1020         }
1021         break;
1022       case 'C':
1023         clear_sensor_stats();
1024         break;
1025       case 'd':
1026       case 'D':
1027         if (lcdDisplayAddresses) {
1028 #ifdef DEBUG
1029           Serial.println("Switching LCD to display sensor values");
1030 #endif
1031           lcdDisplayAddresses = false;
1032         }
1033         else {
1034 #ifdef DEBUG
1035           Serial.println("Switching LCD to display sensor addresses");
1036 #endif
1037           lcdDisplayAddresses = true;
1038         }
1039         Serial.println();
1040         Serial.flush();
1041         lcd.clear();
1042         break;
1043       case 'f':
1044       case 'F':
1045         Serial << "Free memory: " << freeMemory() << " bytes." << endl;
1046 #ifdef DEBUG
1047         Serial << sizeof(DS18B20) * count_sensors() << " bytes for " << count_sensors() << " DS18B20 sensors" << endl;
1048         Serial << sizeof(LiquidCrystal) << " bytes for LiquidCrystal object" << endl;
1049         Serial << sizeof(Metro) * NUM_METROS << " bytes for " << NUM_METROS << " Metro objects" << endl;
1050         Serial << sizeof(OneWire) << " bytes for OneWire object" << endl;
1051 #endif
1052         Serial << endl;
1053         break;
1054       case 'G':
1055         clear_global_sensor_stats();
1056         break;
1057       case 'l':
1058         displayAnalogLightSensorData();
1059         break;
1060       case 'L':
1061         displayDigitalLightSensorData();
1062         break;
1063       case 's':
1064       case 'S':
1065 #ifdef DEBUG
1066         Serial.println("Setting console mode to streaming.");
1067 #endif
1068         consoleMode = streaming;
1069         break;
1070       case 'T':
1071 #ifdef DEBUG
1072         Serial.println("Processing time sync request...");
1073 #endif
1074         processTimeSyncMessage();
1075 #ifdef DEBUG
1076         Serial.println("Done!");
1077 #endif
1078         Serial.println();
1079         break;
1080       case 'h':
1081       case 'H':
1082       case '?':
1083         printCLIOptions();
1084         // consoleUpdateMetro.autoreset(false); // 20150822 MSHARP commented out because autoreset is now private method
1085         consoleUpdateMetro.reset();
1086         helpPauseMetro.reset();
1087         consolePaused = true;
1088         break;
1089       case 'v':
1090       case 'V':
1091         printPROGMEMString(version_line_1);
1092         Serial.print(" ");
1093         Serial.println(SKETCHVERSION);
1094         Serial.println();
1095         break;
1096       default:
1097         Serial.print("Key: 0x");
1098         Serial.println(c, HEX);
1099         printCLIOptions();
1100     } // testing c
1101   }
1102 
1103   if (helpPauseMetro.check() == 1) {
1104     consolePaused = false;
1105     // consoleUpdateMetro.autoreset(true); // 20150822 MSHARP commented out because autoreset is now private method
1106     consoleUpdateMetro.reset();
1107   }
1108 
1109   if ((!consolePaused) && (consoleUpdateMetro.check() == 1)) {
1110     update_console();
1111   }
1112 
1113   if (lcdUpdateMetro.check() == 1) {
1114     update_lcd();
1115   }
1116 
1117   if (ledMetro.check() == 1) {
1118     toggleLED();
1119     ledMetro.reset();
1120   }
1121 
1122   lastLoopDuration = millis() - loopStartMillis;
1123 } // end loop