commit d4f1f57a01c78e26d5d554b061ee5fed6fd7e7d4 Author: Nathanaël Restori Date: Wed Sep 5 20:01:37 2012 +0200 Initializing TM repository diff --git a/arduino/libraries/sensors/BMP085.cpp b/arduino/libraries/sensors/BMP085.cpp new file mode 100644 index 0000000..be97b6f --- /dev/null +++ b/arduino/libraries/sensors/BMP085.cpp @@ -0,0 +1,290 @@ +/*************************************************** + This is a library for the BMP085 Barometric Pressure & Temp Sensor + + Designed specifically to work with the Adafruit BMP085 Breakout + ----> https://www.adafruit.com/products/391 + + These displays use I2C to communicate, 2 pins are required to + interface + Adafruit invests time and resources providing this open source code, + please support Adafruit and open-source hardware by purchasing + products from Adafruit! + + Written by Limor Fried/Ladyada for Adafruit Industries. + BSD license, all text above must be included in any redistribution + ****************************************************/ + +#include "BMP085.h" +#include + +BMP085::BMP085() { +} + + +void BMP085::begin(uint8_t mode) { + if (mode > BMP085_ULTRAHIGHRES) + mode = BMP085_ULTRAHIGHRES; + oversampling = mode; + + Wire.begin(); + + /* read calibration data */ + ac1 = read16(BMP085_CAL_AC1); + ac2 = read16(BMP085_CAL_AC2); + ac3 = read16(BMP085_CAL_AC3); + ac4 = read16(BMP085_CAL_AC4); + ac5 = read16(BMP085_CAL_AC5); + ac6 = read16(BMP085_CAL_AC6); + + b1 = read16(BMP085_CAL_B1); + b2 = read16(BMP085_CAL_B2); + + mb = read16(BMP085_CAL_MB); + mc = read16(BMP085_CAL_MC); + md = read16(BMP085_CAL_MD); +#if (BMP085_DEBUG == 1) + Serial.print("ac1 = "); Serial.println(ac1, DEC); + Serial.print("ac2 = "); Serial.println(ac2, DEC); + Serial.print("ac3 = "); Serial.println(ac3, DEC); + Serial.print("ac4 = "); Serial.println(ac4, DEC); + Serial.print("ac5 = "); Serial.println(ac5, DEC); + Serial.print("ac6 = "); Serial.println(ac6, DEC); + + Serial.print("b1 = "); Serial.println(b1, DEC); + Serial.print("b2 = "); Serial.println(b2, DEC); + + Serial.print("mb = "); Serial.println(mb, DEC); + Serial.print("mc = "); Serial.println(mc, DEC); + Serial.print("md = "); Serial.println(md, DEC); +#endif +} + +uint16_t BMP085::readRawTemperature(void) { + write8(BMP085_CONTROL, BMP085_READTEMPCMD); + _delay_ms(5); +#if BMP085_DEBUG == 1 + Serial.print("Raw temp: "); Serial.println(read16(BMP085_TEMPDATA)); +#endif + return read16(BMP085_TEMPDATA); +} + +uint32_t BMP085::readRawPressure(void) { + uint32_t raw; + + write8(BMP085_CONTROL, BMP085_READPRESSURECMD + (oversampling << 6)); + + if (oversampling == BMP085_ULTRALOWPOWER) + _delay_ms(5); + else if (oversampling == BMP085_STANDARD) + _delay_ms(8); + else if (oversampling == BMP085_HIGHRES) + _delay_ms(14); + else + _delay_ms(26); + + raw = read16(BMP085_PRESSUREDATA); + + raw <<= 8; + raw |= read8(BMP085_PRESSUREDATA+2); + raw >>= (8 - oversampling); + + /* this pull broke stuff, look at it later? + if (oversampling==0) { + raw <<= 8; + raw |= read8(BMP085_PRESSUREDATA+2); + raw >>= (8 - oversampling); + } + */ + +#if BMP085_DEBUG == 1 + Serial.print("Raw pressure: "); Serial.println(raw); +#endif + return raw; +} + + +int32_t BMP085::readPressure(void) { + int32_t UT, UP, B3, B5, B6, X1, X2, X3, p; + uint32_t B4, B7; + + UT = readRawTemperature(); + UP = readRawPressure(); + +#if BMP085_DEBUG == 1 + // use datasheet numbers! + UT = 27898; + UP = 23843; + ac6 = 23153; + ac5 = 32757; + mc = -8711; + md = 2868; + b1 = 6190; + b2 = 4; + ac3 = -14383; + ac2 = -72; + ac1 = 408; + ac4 = 32741; + oversampling = 0; +#endif + + // do temperature calculations + X1 = ((UT - (int32_t)ac6) * (int32_t)ac5) >> 15; + X2 = ((int32_t)mc << 11) - (X1 + md)/2; // round up + X2 /= (X1 + md); + B5 = X1 + X2; + +#if BMP085_DEBUG == 1 + Serial.print("X1 = "); Serial.println(X1); + Serial.print("X2 = "); Serial.println(X2); + Serial.print("B5 = "); Serial.println(B5); +#endif + + // do pressure calcs + B6 = B5 - 4000; + X1 = ((int32_t)b2 * ( (B6 * B6)>>12 )) >> 11; + X2 = ((int32_t)ac2 * B6) >> 11; + X3 = X1 + X2; + B3 = ((((int32_t)ac1*4 + X3) << oversampling) + 2) / 4; + +#if BMP085_DEBUG == 1 + Serial.print("B6 = "); Serial.println(B6); + Serial.print("X1 = "); Serial.println(X1); + Serial.print("X2 = "); Serial.println(X2); + Serial.print("B3 = "); Serial.println(B3); +#endif + + X1 = ((int32_t)ac3 * B6) >> 13; + X2 = ((int32_t)b1 * ((B6 * B6) >> 12)) >> 16; + X3 = ((X1 + X2) + 2) >> 2; + B4 = ((uint32_t)ac4 * (uint32_t)(X3 + 32768)) >> 15; + B7 = ((uint32_t)UP - B3) * (uint32_t)( 50000UL >> oversampling ); + +#if BMP085_DEBUG == 1 + Serial.print("X1 = "); Serial.println(X1); + Serial.print("X2 = "); Serial.println(X2); + Serial.print("B4 = "); Serial.println(B4); + Serial.print("B7 = "); Serial.println(B7); +#endif + + if (B7 < 0x80000000) { + p = (B7 * 2) / B4; + } else { + p = (B7 / B4) * 2; + } + X1 = (p >> 8) * (p >> 8); + X1 = (X1 * 3038) >> 16; + X2 = (-7357 * p) >> 16; + +#if BMP085_DEBUG == 1 + Serial.print("p = "); Serial.println(p); + Serial.print("X1 = "); Serial.println(X1); + Serial.print("X2 = "); Serial.println(X2); +#endif + + p = p + ((X1 + X2 + (int32_t)3791)>>4); +#if BMP085_DEBUG == 1 + Serial.print("p = "); Serial.println(p); +#endif + return p; +} + + +float BMP085::readTemperature(void) { + int32_t UT, X1, X2, B5; // following ds convention + float temp; + + UT = readRawTemperature(); + +#if BMP085_DEBUG == 1 + // use datasheet numbers! + UT = 27898; + ac6 = 23153; + ac5 = 32757; + mc = -8711; + md = 2868; +#endif + + // step 1 + X1 = ((UT - (int32_t)ac6) * (int32_t)ac5) >> 15; + X2 = ((int32_t)mc << 11) / (X1 + (int32_t)md); + B5 = X1 + X2; + temp = (B5 + 8) >> 4; + temp /= 10; + + return temp; +} + +float BMP085::readAltitude(float sealevelPressure) { + float altitude; + + float pressure = readPressure(); + + altitude = 44330 * (1.0 - pow(pressure /sealevelPressure,0.1903)); + + return altitude; +} + + +/*********************************************************************/ + +uint8_t BMP085::read8(uint8_t a) { + uint8_t ret; + + Wire.beginTransmission(BMP085_I2CADDR); // start transmission to device +#if (ARDUINO >= 100) + Wire.write(a); // sends register address to read from +#else + Wire.send(a); // sends register address to read from +#endif + Wire.endTransmission(); // end transmission + + Wire.beginTransmission(BMP085_I2CADDR); // start transmission to device + Wire.requestFrom(BMP085_I2CADDR, 1);// send data n-bytes read +#if (ARDUINO >= 100) + ret = Wire.read(); // receive DATA +#else + ret = Wire.receive(); // receive DATA +#endif + Wire.endTransmission(); // end transmission + + return ret; +} + +uint16_t BMP085::read16(uint8_t a) { + uint16_t ret; + + Wire.beginTransmission(BMP085_I2CADDR); // start transmission to device +#if (ARDUINO >= 100) + Wire.write(a); // sends register address to read from +#else + Wire.send(a); // sends register address to read from +#endif + Wire.endTransmission(); // end transmission + + Wire.beginTransmission(BMP085_I2CADDR); // start transmission to device + Wire.requestFrom(BMP085_I2CADDR, 2);// send data n-bytes read +#if (ARDUINO >= 100) + ret = Wire.read(); // receive DATA + ret <<= 8; + ret |= Wire.read(); // receive DATA +#else + ret = Wire.receive(); // receive DATA + ret <<= 8; + ret |= Wire.receive(); // receive DATA +#endif + Wire.endTransmission(); // end transmission + + return ret; +} + +void BMP085::write8(uint8_t a, uint8_t d) { + Wire.beginTransmission(BMP085_I2CADDR); // start transmission to device +#if (ARDUINO >= 100) + Wire.write(a); // sends register address to read from + Wire.write(d); // write data +#else + Wire.send(a); // sends register address to read from + Wire.send(d); // write data +#endif + Wire.endTransmission(); // end transmission +} diff --git a/arduino/libraries/sensors/BMP085.h b/arduino/libraries/sensors/BMP085.h new file mode 100644 index 0000000..396f309 --- /dev/null +++ b/arduino/libraries/sensors/BMP085.h @@ -0,0 +1,71 @@ +/*************************************************** + This is a library for the BMP085 Barometric Pressure & Temp Sensor + + Designed specifically to work with the Adafruit BMP085 Breakout + ----> https://www.adafruit.com/products/391 + + These displays use I2C to communicate, 2 pins are required to + interface + Adafruit invests time and resources providing this open source code, + please support Adafruit and open-source hardware by purchasing + products from Adafruit! + + Written by Limor Fried/Ladyada for Adafruit Industries. + BSD license, all text above must be included in any redistribution + ****************************************************/ + +#if (ARDUINO >= 100) + #include "Arduino.h" +#else + #include "WProgram.h" +#endif +#include "Wire.h" + +#define BMP085_DEBUG 0 + +#define BMP085_I2CADDR 0x77 + +#define BMP085_ULTRALOWPOWER 0 +#define BMP085_STANDARD 1 +#define BMP085_HIGHRES 2 +#define BMP085_ULTRAHIGHRES 3 +#define BMP085_CAL_AC1 0xAA // R Calibration data (16 bits) +#define BMP085_CAL_AC2 0xAC // R Calibration data (16 bits) +#define BMP085_CAL_AC3 0xAE // R Calibration data (16 bits) +#define BMP085_CAL_AC4 0xB0 // R Calibration data (16 bits) +#define BMP085_CAL_AC5 0xB2 // R Calibration data (16 bits) +#define BMP085_CAL_AC6 0xB4 // R Calibration data (16 bits) +#define BMP085_CAL_B1 0xB6 // R Calibration data (16 bits) +#define BMP085_CAL_B2 0xB8 // R Calibration data (16 bits) +#define BMP085_CAL_MB 0xBA // R Calibration data (16 bits) +#define BMP085_CAL_MC 0xBC // R Calibration data (16 bits) +#define BMP085_CAL_MD 0xBE // R Calibration data (16 bits) + +#define BMP085_CONTROL 0xF4 +#define BMP085_TEMPDATA 0xF6 +#define BMP085_PRESSUREDATA 0xF6 +#define BMP085_READTEMPCMD 0x2E +#define BMP085_READPRESSURECMD 0x34 + + +class BMP085 { + public: + BMP085(); + void begin(uint8_t mode = BMP085_ULTRAHIGHRES); // by default go highres + float readTemperature(void); + int32_t readPressure(void); + float readAltitude(float sealevelPressure = 101325); // std atmosphere + uint16_t readRawTemperature(void); + uint32_t readRawPressure(void); + + private: + uint8_t read8(uint8_t addr); + uint16_t read16(uint8_t addr); + void write8(uint8_t addr, uint8_t data); + + uint8_t oversampling; + + int16_t ac1, ac2, ac3, b1, b2, mb, mc, md; + uint16_t ac4, ac5, ac6; +}; + diff --git a/arduino/libraries/sensors/Chronodot.cpp b/arduino/libraries/sensors/Chronodot.cpp new file mode 100644 index 0000000..7cc8625 --- /dev/null +++ b/arduino/libraries/sensors/Chronodot.cpp @@ -0,0 +1,263 @@ +// Code by JeeLabs http://news.jeelabs.org/code/ +// Released to the public domain! Enjoy! +// +// Modified for Chronodot / DS3132 with +// temperature data by Stephanie Maks +// http://planetstephanie.net/ + + +#include +#include +#include "Chronodot.h" +#if ARDUINO >= 100 + #include "Arduino.h" +#else + #include "WProgram.h" +#endif + +#define CHRONODOT_ADDRESS 0x68 +#define SECONDS_PER_DAY 86400L + +#define SECONDS_FROM_1970_TO_2000 946684800 + +//////////////////////////////////////////////////////////////////////////////// +// utility code, some of this could be exposed in the DateTime API if needed + +static uint8_t daysInMonth [] PROGMEM = { 31,28,31,30,31,30,31,31,30,31,30,31 }; + +// number of days since 2000/01/01, valid for 2001..2099 +static uint16_t date2days(uint16_t y, uint8_t m, uint8_t d) { + if (y >= 2000) + y -= 2000; + uint16_t days = d; + for (uint8_t i = 1; i < m; ++i) + days += pgm_read_byte(daysInMonth + i - 1); + if (m > 2 && y % 4 == 0) + ++days; + return days + 365 * y + (y + 3) / 4 - 1; +} + +static long time2long(uint16_t days, uint8_t h, uint8_t m, uint8_t s) { + return ((days * 24L + h) * 60 + m) * 60 + s; +} + +//////////////////////////////////////////////////////////////////////////////// +// DateTime implementation - ignores time zones and DST changes +// NOTE: also ignores leap seconds, see http://en.wikipedia.org/wiki/Leap_second + +DateTime::DateTime (uint32_t t) { + t -= SECONDS_FROM_1970_TO_2000; // bring to 2000 timestamp from 1970 + + ss = t % 60; + t /= 60; + mm = t % 60; + t /= 60; + hh = t % 24; + uint16_t days = t / 24; + uint8_t leap; + for (yOff = 0; ; ++yOff) { + leap = yOff % 4 == 0; + if (days < 365 + leap) + break; + days -= 365 + leap; + } + for (m = 1; ; ++m) { + uint8_t daysPerMonth = pgm_read_byte(daysInMonth + m - 1); + if (leap && m == 2) + ++daysPerMonth; + if (days < daysPerMonth) + break; + days -= daysPerMonth; + } + d = days + 1; +} + +DateTime::DateTime (uint16_t year, uint8_t month, uint8_t day, uint8_t hour, uint8_t min, uint8_t sec, int tempF, float tempC) { + if (year >= 2000) + year -= 2000; + yOff = year; + m = month; + d = day; + hh = hour; + mm = min; + ss = sec; + ttf = tempF; + ttc = tempC; +} + +static uint8_t conv2d(const char* p) { + uint8_t v = 0; + if ('0' <= *p && *p <= '9') + v = *p - '0'; + return 10 * v + *++p - '0'; +} + +// A convenient constructor for using "the compiler's time": +// DateTime now (__DATE__, __TIME__); +// NOTE: using PSTR would further reduce the RAM footprint +DateTime::DateTime (const char* date, const char* time) { + // sample input: date = "Dec 26 2009", time = "12:34:56" + yOff = conv2d(date + 9); + // Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec + switch (date[0]) { + case 'J': m = date[1] == 'a' ? 1 : m = date[2] == 'n' ? 6 : 7; break; + case 'F': m = 2; break; + case 'A': m = date[2] == 'r' ? 4 : 8; break; + case 'M': m = date[2] == 'r' ? 3 : 5; break; + case 'S': m = 9; break; + case 'O': m = 10; break; + case 'N': m = 11; break; + case 'D': m = 12; break; + } + d = conv2d(date + 4); + hh = conv2d(time); + mm = conv2d(time + 3); + ss = conv2d(time + 6); +} + +uint8_t DateTime::dayOfWeek() const { + uint16_t day = date2days(yOff, m, d); + return (day + 6) % 7; // Jan 1, 2000 is a Saturday, i.e. returns 6 +} + +long DateTime::secondstime(void) const { + long t; + uint16_t days = date2days(yOff, m, d); + t = time2long(days, hh, mm, ss); + return t; +} + +uint32_t DateTime::unixtime(void) const { + uint32_t t; + uint16_t days = date2days(yOff, m, d); + t = time2long(days, hh, mm, ss); + t += SECONDS_FROM_1970_TO_2000; // seconds from 1970 to 2000 + return t; +} + +//////////////////////////////////////////////////////////////////////////////// +// RTC_DS3231 implementation + +static uint8_t bcd2bin (uint8_t val) { return val - 6 * (val >> 4); } +static uint8_t bin2bcd (uint8_t val) { return val + 6 * (val / 10); } + +uint8_t Chronodot::begin(void) { + return 1; +} + +uint8_t Chronodot::isrunning(void) { + Wire.beginTransmission(CHRONODOT_ADDRESS); +#if ARDUINO >= 100 + Wire.write((byte)0x0F); +#else + Wire.send(0x0F); +#endif + Wire.endTransmission(); + + Wire.requestFrom(CHRONODOT_ADDRESS, 1); +#if ARDUINO >= 100 + uint8_t ss = Wire.read(); +#else + uint8_t ss = Wire.receive(); +#endif + return !(ss>>7); +} + +void Chronodot::adjust(const DateTime& dt) { +// send new date & time to chronodot + Wire.beginTransmission(CHRONODOT_ADDRESS); +#if ARDUINO >= 100 + Wire.write((byte)0); // memory address + Wire.write(bin2bcd(dt.second())); // byte 0 + Wire.write(bin2bcd(dt.minute())); // byte 1 + Wire.write(bin2bcd(dt.hour())); // byte 2 + Wire.write(bin2bcd(0)); // byte 3 + Wire.write(bin2bcd(dt.day())); // byte 4 + Wire.write(bin2bcd(dt.month())); // byte 5 + Wire.write(bin2bcd(dt.year() - 2000)); // byte 6 +#else + Wire.send(0); + Wire.send(bin2bcd(dt.second())); + Wire.send(bin2bcd(dt.minute())); + Wire.send(bin2bcd(dt.hour())); + Wire.send(bin2bcd(0)); + Wire.send(bin2bcd(dt.day())); + Wire.send(bin2bcd(dt.month())); + Wire.send(bin2bcd(dt.year() - 2000)); +#endif + Wire.endTransmission(); +// now get the control byte - we need to set bit 7 to zero + Wire.beginTransmission(CHRONODOT_ADDRESS); +#if ARDUINO >= 100 + Wire.write((byte)0x0F); +#else + Wire.send(0x0F); +#endif + Wire.endTransmission(); + Wire.requestFrom(CHRONODOT_ADDRESS, 1); +#if ARDUINO >= 100 + uint8_t ss = Wire.read(); +#else + uint8_t ss = Wire.receive(); +#endif + ss &= ~(1 << 7); // clear OSF bit + Wire.beginTransmission(CHRONODOT_ADDRESS); +#if ARDUINO >= 100 + Wire.write((byte)0x0F); + Wire.write((byte)ss); +#else + Wire.send(0x0F); + Wire.send(ss); +#endif + Wire.endTransmission(); +} + +DateTime Chronodot::now() { + Wire.beginTransmission(CHRONODOT_ADDRESS); +#if ARDUINO >= 100 + Wire.write((byte)0); +#else + Wire.send(0); +#endif + Wire.endTransmission(); + + Wire.requestFrom(CHRONODOT_ADDRESS, 19); + byte blah[20]; + int i; + for(i=0; i<20; i++) { +#if ARDUINO >= 100 + blah[i] = Wire.read(); +#else + blah[i] = Wire.receive(); +#endif + } + uint8_t ss = bcd2bin(blah[0] & 0x7F); + uint8_t mm = bcd2bin(blah[1]); + uint8_t hh = bcd2bin(blah[2]); + uint8_t d = bcd2bin(blah[4]); + uint8_t m = bcd2bin(blah[5]); + uint16_t y = bcd2bin(blah[6]) + 2000; + float ttc = (float)(int)blah[17]; + byte portion = blah[18]; + if(portion == 0b01000000) ttc += 0.25; + if(portion == 0b10000000) ttc += 0.5; + if(portion == 0b11000000) ttc += 0.75; + float degF = (((ttc * 9.0) / 5.0) + 32.5); + int ttf = (int)degF; + return DateTime (y, m, d, hh, mm, ss, ttf, ttc); +} + +//////////////////////////////////////////////////////////////////////////////// +// RTC_Millis implementation + +long RTC_Millis::offset = 0; + +void RTC_Millis::adjust(const DateTime& dt) { + offset = dt.unixtime() - millis() / 1000; +} + +DateTime RTC_Millis::now() { + return (uint32_t)(offset + millis() / 1000); +} + +//////////////////////////////////////////////////////////////////////////////// diff --git a/arduino/libraries/sensors/Chronodot.h b/arduino/libraries/sensors/Chronodot.h new file mode 100644 index 0000000..9a14f97 --- /dev/null +++ b/arduino/libraries/sensors/Chronodot.h @@ -0,0 +1,60 @@ +// Code by JeeLabs http://news.jeelabs.org/code/ +// Released to the public domain! Enjoy! +// +// Modified for Chronodot / DS3132 with +// temperature data by Stephanie Maks +// http://planetstephanie.net/ + +#ifndef CHRONODOT_H +#define CHRONODOT_H + +// Simple general-purpose date/time class (no TZ / DST / leap second handling!) +class DateTime { +public: + DateTime (uint32_t t =0); + DateTime (uint16_t year, uint8_t month, uint8_t day, + uint8_t hour =0, uint8_t min =0, uint8_t sec =0, int tempF =0, float tempC = 0.0); + DateTime (const char* date, const char* time); + uint16_t year() const { return 2000 + yOff; } + uint8_t month() const { return m; } + uint8_t day() const { return d; } + uint8_t hour() const { return hh; } + uint8_t minute() const { return mm; } + uint8_t second() const { return ss; } + int tempF() const { return ttf; } + float tempC() const { return ttc; } + uint8_t dayOfWeek() const; + + // 32-bit times as seconds since 1/1/2000 + long secondstime() const; + // 32-bit times as seconds since 1/1/1970 + uint32_t unixtime(void) const; + +protected: + uint8_t yOff, m, d, hh, mm, ss; + int ttf; + float ttc; +}; + +// Chronodot based on the DS3231 chip connected via I2C and the Wire library +class Chronodot { +public: + static uint8_t begin(void); + static void adjust(const DateTime& dt); + uint8_t isrunning(void); + static DateTime now(); +}; + +// RTC using the internal millis() clock, has to be initialized before use +// NOTE: this clock won't be correct once the millis() timer rolls over (>49d?) +class RTC_Millis { +public: + static void begin(const DateTime& dt) { adjust(dt); } + static void adjust(const DateTime& dt); + static DateTime now(); + +protected: + static long offset; +}; + +#endif CHRONODOT_H \ No newline at end of file diff --git a/arduino/libraries/sensors/DHT.cpp b/arduino/libraries/sensors/DHT.cpp new file mode 100644 index 0000000..8bba81b --- /dev/null +++ b/arduino/libraries/sensors/DHT.cpp @@ -0,0 +1,160 @@ +/* DHT library + +MIT license +written by Adafruit Industries +*/ + +#include "DHT.h" + +DHT::DHT(uint8_t pin, uint8_t type) { + _pin = pin; + _type = type; + firstreading = true; +} + +void DHT::begin(void) { + // set up the pins! + pinMode(_pin, INPUT); + digitalWrite(_pin, HIGH); + _lastreadtime = 0; +} + +//boolean S == Scale. True == Farenheit; False == Celcius +float DHT::readTemperature(bool S) { + float f; + + if (read()) { + switch (_type) { + case DHT11: + f = data[2]; + if(S) + f = convertCtoF(f); + + return f; + case DHT22: + case DHT21: + f = data[2] & 0x7F; + f *= 256; + f += data[3]; + f /= 10; + if (data[2] & 0x80) + f *= -1; + if(S) + f = convertCtoF(f); + + return f; + } + } + Serial.print("Read fail"); + return NAN; +} + +float DHT::convertCtoF(float c) { + return c * 9 / 5 + 32; +} + +float DHT::readHumidity(void) { + float f; + if (read()) { + switch (_type) { + case DHT11: + f = data[0]; + return f; + case DHT22: + case DHT21: + f = data[0]; + f *= 256; + f += data[1]; + f /= 10; + return f; + } + } + Serial.print("Read fail"); + return NAN; +} + + +boolean DHT::read(void) { + uint8_t laststate = HIGH; + uint8_t counter = 0; + uint8_t j = 0, i; + unsigned long currenttime; + + // pull the pin high and wait 250 milliseconds + digitalWrite(_pin, HIGH); + delay(250); + + currenttime = millis(); + if (currenttime < _lastreadtime) { + // ie there was a rollover + _lastreadtime = 0; + } + if (!firstreading && ((currenttime - _lastreadtime) < 2000)) { + return true; // return last correct measurement + //delay(2000 - (currenttime - _lastreadtime)); + } + firstreading = false; + /* + Serial.print("Currtime: "); Serial.print(currenttime); + Serial.print(" Lasttime: "); Serial.print(_lastreadtime); + */ + _lastreadtime = millis(); + + data[0] = data[1] = data[2] = data[3] = data[4] = 0; + + // now pull it low for ~20 milliseconds + pinMode(_pin, OUTPUT); + digitalWrite(_pin, LOW); + delay(20); + cli(); + digitalWrite(_pin, HIGH); + delayMicroseconds(40); + pinMode(_pin, INPUT); + + // read in timings + for ( i=0; i< MAXTIMINGS; i++) { + counter = 0; + while (digitalRead(_pin) == laststate) { + counter++; + delayMicroseconds(1); + if (counter == 255) { + break; + } + } + laststate = digitalRead(_pin); + + if (counter == 255) break; + + // ignore first 3 transitions + if ((i >= 4) && (i%2 == 0)) { + // shove each bit into the storage bytes + data[j/8] <<= 1; + if (counter > 6) + data[j/8] |= 1; + j++; + } + + } + + sei(); + + /* + Serial.println(j, DEC); + Serial.print(data[0], HEX); Serial.print(", "); + Serial.print(data[1], HEX); Serial.print(", "); + Serial.print(data[2], HEX); Serial.print(", "); + Serial.print(data[3], HEX); Serial.print(", "); + Serial.print(data[4], HEX); Serial.print(" =? "); + Serial.println(data[0] + data[1] + data[2] + data[3], HEX); + */ + + // check we read 40 bits and that the checksum matches + if ((j >= 40) && + (data[4] == ((data[0] + data[1] + data[2] + data[3]) & 0xFF)) ) { + return true; + } + + + return false; + +} diff --git a/arduino/libraries/sensors/DHT.h b/arduino/libraries/sensors/DHT.h new file mode 100644 index 0000000..d6c2412 --- /dev/null +++ b/arduino/libraries/sensors/DHT.h @@ -0,0 +1,36 @@ +#if ARDUINO >= 100 + #include "Arduino.h" +#else + #include "WProgram.h" +#endif + +/* DHT library + +MIT license +written by Adafruit Industries +*/ + +// how many timing transitions we need to keep track of. 2 * number bits + extra +#define MAXTIMINGS 85 + +#define DHT11 11 +#define DHT22 22 +#define DHT21 21 +#define AM2301 21 + +class DHT { + private: + uint8_t data[6]; + uint8_t _pin, _type; + boolean read(void); + unsigned long _lastreadtime; + boolean firstreading; + + public: + DHT(uint8_t pin, uint8_t type); + void begin(void); + float readTemperature(bool S=false); + float convertCtoF(float); + float readHumidity(void); + +}; diff --git a/arduino/libraries/sensors/TSL2561.cpp b/arduino/libraries/sensors/TSL2561.cpp new file mode 100644 index 0000000..848cf67 --- /dev/null +++ b/arduino/libraries/sensors/TSL2561.cpp @@ -0,0 +1,298 @@ + +/**************************************************************************/ +/*! + @file tsl2561.c + @author K. Townsend (microBuilder.eu / adafruit.com) + + @section LICENSE + + Software License Agreement (BSD License) + + Copyright (c) 2010, microBuilder SARL, Adafruit Industries + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + 3. Neither the name of the copyright holders nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ''AS IS'' AND ANY + EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY + DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ +/**************************************************************************/ + +#include +#include +#include + +#include "TSL2561.h" + +TSL2561::TSL2561(uint8_t addr) { + _addr = addr; + _initialized = false; + _integration = TSL2561_INTEGRATIONTIME_13MS; + _gain = TSL2561_GAIN_16X; + + // we cant do wire initialization till later, because we havent loaded Wire yet +} + +boolean TSL2561::begin(void) { + Wire.begin(); + + // Initialise I2C + Wire.beginTransmission(_addr); +#if ARDUINO >= 100 + Wire.write(TSL2561_REGISTER_ID); +#else + Wire.send(TSL2561_REGISTER_ID); +#endif + Wire.endTransmission(); + Wire.requestFrom(_addr, 1); +#if ARDUINO >= 100 + int x = Wire.read(); +#else + int x = Wire.receive(); +#endif + //Serial.print("0x"); Serial.println(x, HEX); + if (x & 0x0A ) { + //Serial.println("Found TSL2561"); + } else { + return false; + } + _initialized = true; + + // Set default integration time and gain + setTiming(_integration); + setGain(_gain); + // Note: by default, the device is in power down mode on bootup + disable(); + + return true; +} + +void TSL2561::enable(void) +{ + if (!_initialized) begin(); + + // Enable the device by setting the control bit to 0x03 + write8(TSL2561_COMMAND_BIT | TSL2561_REGISTER_CONTROL, TSL2561_CONTROL_POWERON); +} + +void TSL2561::disable(void) +{ + if (!_initialized) begin(); + + // Disable the device by setting the control bit to 0x03 + write8(TSL2561_COMMAND_BIT | TSL2561_REGISTER_CONTROL, TSL2561_CONTROL_POWEROFF); +} + + +void TSL2561::setGain(tsl2561Gain_t gain) { + if (!_initialized) begin(); + + enable(); + _gain = gain; + write8(TSL2561_COMMAND_BIT | TSL2561_REGISTER_TIMING, _integration | _gain); + disable(); +} + +void TSL2561::setTiming(tsl2561IntegrationTime_t integration) +{ + if (!_initialized) begin(); + + enable(); + _integration = integration; + write8(TSL2561_COMMAND_BIT | TSL2561_REGISTER_TIMING, _integration | _gain); + disable(); +} + +uint32_t TSL2561::calculateLux(uint16_t ch0, uint16_t ch1) +{ + unsigned long chScale; + unsigned long channel1; + unsigned long channel0; + + switch (_integration) + { + case TSL2561_INTEGRATIONTIME_13MS: + chScale = TSL2561_LUX_CHSCALE_TINT0; + break; + case TSL2561_INTEGRATIONTIME_101MS: + chScale = TSL2561_LUX_CHSCALE_TINT1; + break; + default: // No scaling ... integration time = 402ms + chScale = (1 << TSL2561_LUX_CHSCALE); + break; + } + + // Scale for gain (1x or 16x) + if (!_gain) chScale = chScale << 4; + + // scale the channel values + channel0 = (ch0 * chScale) >> TSL2561_LUX_CHSCALE; + channel1 = (ch1 * chScale) >> TSL2561_LUX_CHSCALE; + + // find the ratio of the channel values (Channel1/Channel0) + unsigned long ratio1 = 0; + if (channel0 != 0) ratio1 = (channel1 << (TSL2561_LUX_RATIOSCALE+1)) / channel0; + + // round the ratio value + unsigned long ratio = (ratio1 + 1) >> 1; + + unsigned int b, m; + +#ifdef TSL2561_PACKAGE_CS + if ((ratio >= 0) && (ratio <= TSL2561_LUX_K1C)) + {b=TSL2561_LUX_B1C; m=TSL2561_LUX_M1C;} + else if (ratio <= TSL2561_LUX_K2C) + {b=TSL2561_LUX_B2C; m=TSL2561_LUX_M2C;} + else if (ratio <= TSL2561_LUX_K3C) + {b=TSL2561_LUX_B3C; m=TSL2561_LUX_M3C;} + else if (ratio <= TSL2561_LUX_K4C) + {b=TSL2561_LUX_B4C; m=TSL2561_LUX_M4C;} + else if (ratio <= TSL2561_LUX_K5C) + {b=TSL2561_LUX_B5C; m=TSL2561_LUX_M5C;} + else if (ratio <= TSL2561_LUX_K6C) + {b=TSL2561_LUX_B6C; m=TSL2561_LUX_M6C;} + else if (ratio <= TSL2561_LUX_K7C) + {b=TSL2561_LUX_B7C; m=TSL2561_LUX_M7C;} + else if (ratio > TSL2561_LUX_K8C) + {b=TSL2561_LUX_B8C; m=TSL2561_LUX_M8C;} +#else + if ((ratio >= 0) && (ratio <= TSL2561_LUX_K1T)) + {b=TSL2561_LUX_B1T; m=TSL2561_LUX_M1T;} + else if (ratio <= TSL2561_LUX_K2T) + {b=TSL2561_LUX_B2T; m=TSL2561_LUX_M2T;} + else if (ratio <= TSL2561_LUX_K3T) + {b=TSL2561_LUX_B3T; m=TSL2561_LUX_M3T;} + else if (ratio <= TSL2561_LUX_K4T) + {b=TSL2561_LUX_B4T; m=TSL2561_LUX_M4T;} + else if (ratio <= TSL2561_LUX_K5T) + {b=TSL2561_LUX_B5T; m=TSL2561_LUX_M5T;} + else if (ratio <= TSL2561_LUX_K6T) + {b=TSL2561_LUX_B6T; m=TSL2561_LUX_M6T;} + else if (ratio <= TSL2561_LUX_K7T) + {b=TSL2561_LUX_B7T; m=TSL2561_LUX_M7T;} + else if (ratio > TSL2561_LUX_K8T) + {b=TSL2561_LUX_B8T; m=TSL2561_LUX_M8T;} +#endif + + unsigned long temp; + temp = ((channel0 * b) - (channel1 * m)); + + // do not allow negative lux value + if (temp < 0) temp = 0; + + // round lsb (2^(LUX_SCALE-1)) + temp += (1 << (TSL2561_LUX_LUXSCALE-1)); + + // strip off fractional portion + uint32_t lux = temp >> TSL2561_LUX_LUXSCALE; + + // Signal I2C had no errors + return lux; +} + +uint32_t TSL2561::getFullLuminosity (void) +{ + if (!_initialized) begin(); + + // Enable the device by setting the control bit to 0x03 + enable(); + + // Wait x ms for ADC to complete + switch (_integration) + { + case TSL2561_INTEGRATIONTIME_13MS: + delay(14); + break; + case TSL2561_INTEGRATIONTIME_101MS: + delay(102); + break; + default: + delay(400); + break; + } + + uint32_t x; + x = read16(TSL2561_COMMAND_BIT | TSL2561_WORD_BIT | TSL2561_REGISTER_CHAN1_LOW); + x <<= 16; + x |= read16(TSL2561_COMMAND_BIT | TSL2561_WORD_BIT | TSL2561_REGISTER_CHAN0_LOW); + + disable(); + + return x; +} +uint16_t TSL2561::getLuminosity (uint8_t channel) { + + uint32_t x = getFullLuminosity(); + + if (channel == 0) { + // Reads two byte value from channel 0 (visible + infrared) + return (x & 0xFFFF); + } else if (channel == 1) { + // Reads two byte value from channel 1 (infrared) + return (x >> 16); + } else if (channel == 2) { + // Reads all and subtracts out just the visible! + return ( (x & 0xFFFF) - (x >> 16)); + } + + // unknown channel! + return 0; +} + + +uint16_t TSL2561::read16(uint8_t reg) +{ + uint16_t x; uint16_t t; + + Wire.beginTransmission(_addr); +#if ARDUINO >= 100 + Wire.write(reg); +#else + Wire.send(reg); +#endif + Wire.endTransmission(); + + Wire.requestFrom(_addr, 2); +#if ARDUINO >= 100 + t = Wire.read(); + x = Wire.read(); +#else + t = Wire.receive(); + x = Wire.receive(); +#endif + x <<= 8; + x |= t; + return x; +} + + + +void TSL2561::write8 (uint8_t reg, uint8_t value) +{ + Wire.beginTransmission(_addr); +#if ARDUINO >= 100 + Wire.write(reg); + Wire.write(value); +#else + Wire.send(reg); + Wire.send(value); +#endif + Wire.endTransmission(); +} diff --git a/arduino/libraries/sensors/TSL2561.h b/arduino/libraries/sensors/TSL2561.h new file mode 100644 index 0000000..2750f2b --- /dev/null +++ b/arduino/libraries/sensors/TSL2561.h @@ -0,0 +1,184 @@ + +/**************************************************************************/ +/*! + @file tsl2561.h + @author K. Townsend (microBuilder.eu) + + @section LICENSE + + Software License Agreement (BSD License) + + Copyright (c) 2010, microBuilder SARL + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + 3. Neither the name of the copyright holders nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ''AS IS'' AND ANY + EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY + DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ +/**************************************************************************/ + +#ifndef _TSL2561_H_ +#define _TSL2561_H_ + +#if ARDUINO >= 100 + #include +#else + #include +#endif +#include + +#define TSL2561_VISIBLE 2 // channel 0 - channel 1 +#define TSL2561_INFRARED 1 // channel 1 +#define TSL2561_FULLSPECTRUM 0 // channel 0 + +// 3 i2c address options! +#define TSL2561_ADDR_LOW 0x29 +#define TSL2561_ADDR_FLOAT 0x39 +#define TSL2561_ADDR_HIGH 0x49 + +// Lux calculations differ slightly for CS package +//#define TSL2561_PACKAGE_CS +#define TSL2561_PACKAGE_T_FN_CL + +#define TSL2561_READBIT (0x01) + +#define TSL2561_COMMAND_BIT (0x80) // Must be 1 +#define TSL2561_CLEAR_BIT (0x40) // Clears any pending interrupt (write 1 to clear) +#define TSL2561_WORD_BIT (0x20) // 1 = read/write word (rather than byte) +#define TSL2561_BLOCK_BIT (0x10) // 1 = using block read/write + +#define TSL2561_CONTROL_POWERON (0x03) +#define TSL2561_CONTROL_POWEROFF (0x00) + +#define TSL2561_LUX_LUXSCALE (14) // Scale by 2^14 +#define TSL2561_LUX_RATIOSCALE (9) // Scale ratio by 2^9 +#define TSL2561_LUX_CHSCALE (10) // Scale channel values by 2^10 +#define TSL2561_LUX_CHSCALE_TINT0 (0x7517) // 322/11 * 2^TSL2561_LUX_CHSCALE +#define TSL2561_LUX_CHSCALE_TINT1 (0x0FE7) // 322/81 * 2^TSL2561_LUX_CHSCALE + +// T, FN and CL package values +#define TSL2561_LUX_K1T (0x0040) // 0.125 * 2^RATIO_SCALE +#define TSL2561_LUX_B1T (0x01f2) // 0.0304 * 2^LUX_SCALE +#define TSL2561_LUX_M1T (0x01be) // 0.0272 * 2^LUX_SCALE +#define TSL2561_LUX_K2T (0x0080) // 0.250 * 2^RATIO_SCALE +#define TSL2561_LUX_B2T (0x0214) // 0.0325 * 2^LUX_SCALE +#define TSL2561_LUX_M2T (0x02d1) // 0.0440 * 2^LUX_SCALE +#define TSL2561_LUX_K3T (0x00c0) // 0.375 * 2^RATIO_SCALE +#define TSL2561_LUX_B3T (0x023f) // 0.0351 * 2^LUX_SCALE +#define TSL2561_LUX_M3T (0x037b) // 0.0544 * 2^LUX_SCALE +#define TSL2561_LUX_K4T (0x0100) // 0.50 * 2^RATIO_SCALE +#define TSL2561_LUX_B4T (0x0270) // 0.0381 * 2^LUX_SCALE +#define TSL2561_LUX_M4T (0x03fe) // 0.0624 * 2^LUX_SCALE +#define TSL2561_LUX_K5T (0x0138) // 0.61 * 2^RATIO_SCALE +#define TSL2561_LUX_B5T (0x016f) // 0.0224 * 2^LUX_SCALE +#define TSL2561_LUX_M5T (0x01fc) // 0.0310 * 2^LUX_SCALE +#define TSL2561_LUX_K6T (0x019a) // 0.80 * 2^RATIO_SCALE +#define TSL2561_LUX_B6T (0x00d2) // 0.0128 * 2^LUX_SCALE +#define TSL2561_LUX_M6T (0x00fb) // 0.0153 * 2^LUX_SCALE +#define TSL2561_LUX_K7T (0x029a) // 1.3 * 2^RATIO_SCALE +#define TSL2561_LUX_B7T (0x0018) // 0.00146 * 2^LUX_SCALE +#define TSL2561_LUX_M7T (0x0012) // 0.00112 * 2^LUX_SCALE +#define TSL2561_LUX_K8T (0x029a) // 1.3 * 2^RATIO_SCALE +#define TSL2561_LUX_B8T (0x0000) // 0.000 * 2^LUX_SCALE +#define TSL2561_LUX_M8T (0x0000) // 0.000 * 2^LUX_SCALE + +// CS package values +#define TSL2561_LUX_K1C (0x0043) // 0.130 * 2^RATIO_SCALE +#define TSL2561_LUX_B1C (0x0204) // 0.0315 * 2^LUX_SCALE +#define TSL2561_LUX_M1C (0x01ad) // 0.0262 * 2^LUX_SCALE +#define TSL2561_LUX_K2C (0x0085) // 0.260 * 2^RATIO_SCALE +#define TSL2561_LUX_B2C (0x0228) // 0.0337 * 2^LUX_SCALE +#define TSL2561_LUX_M2C (0x02c1) // 0.0430 * 2^LUX_SCALE +#define TSL2561_LUX_K3C (0x00c8) // 0.390 * 2^RATIO_SCALE +#define TSL2561_LUX_B3C (0x0253) // 0.0363 * 2^LUX_SCALE +#define TSL2561_LUX_M3C (0x0363) // 0.0529 * 2^LUX_SCALE +#define TSL2561_LUX_K4C (0x010a) // 0.520 * 2^RATIO_SCALE +#define TSL2561_LUX_B4C (0x0282) // 0.0392 * 2^LUX_SCALE +#define TSL2561_LUX_M4C (0x03df) // 0.0605 * 2^LUX_SCALE +#define TSL2561_LUX_K5C (0x014d) // 0.65 * 2^RATIO_SCALE +#define TSL2561_LUX_B5C (0x0177) // 0.0229 * 2^LUX_SCALE +#define TSL2561_LUX_M5C (0x01dd) // 0.0291 * 2^LUX_SCALE +#define TSL2561_LUX_K6C (0x019a) // 0.80 * 2^RATIO_SCALE +#define TSL2561_LUX_B6C (0x0101) // 0.0157 * 2^LUX_SCALE +#define TSL2561_LUX_M6C (0x0127) // 0.0180 * 2^LUX_SCALE +#define TSL2561_LUX_K7C (0x029a) // 1.3 * 2^RATIO_SCALE +#define TSL2561_LUX_B7C (0x0037) // 0.00338 * 2^LUX_SCALE +#define TSL2561_LUX_M7C (0x002b) // 0.00260 * 2^LUX_SCALE +#define TSL2561_LUX_K8C (0x029a) // 1.3 * 2^RATIO_SCALE +#define TSL2561_LUX_B8C (0x0000) // 0.000 * 2^LUX_SCALE +#define TSL2561_LUX_M8C (0x0000) // 0.000 * 2^LUX_SCALE + +enum +{ + TSL2561_REGISTER_CONTROL = 0x00, + TSL2561_REGISTER_TIMING = 0x01, + TSL2561_REGISTER_THRESHHOLDL_LOW = 0x02, + TSL2561_REGISTER_THRESHHOLDL_HIGH = 0x03, + TSL2561_REGISTER_THRESHHOLDH_LOW = 0x04, + TSL2561_REGISTER_THRESHHOLDH_HIGH = 0x05, + TSL2561_REGISTER_INTERRUPT = 0x06, + TSL2561_REGISTER_CRC = 0x08, + TSL2561_REGISTER_ID = 0x0A, + TSL2561_REGISTER_CHAN0_LOW = 0x0C, + TSL2561_REGISTER_CHAN0_HIGH = 0x0D, + TSL2561_REGISTER_CHAN1_LOW = 0x0E, + TSL2561_REGISTER_CHAN1_HIGH = 0x0F +}; + +typedef enum +{ + TSL2561_INTEGRATIONTIME_13MS = 0x00, // 13.7ms + TSL2561_INTEGRATIONTIME_101MS = 0x01, // 101ms + TSL2561_INTEGRATIONTIME_402MS = 0x02 // 402ms +} +tsl2561IntegrationTime_t; + +typedef enum +{ + TSL2561_GAIN_0X = 0x00, // No gain + TSL2561_GAIN_16X = 0x10, // 16x gain +} +tsl2561Gain_t; + + +class TSL2561 { + public: + TSL2561(uint8_t addr); + boolean begin(void); + void enable(void); + void disable(void); + void write8(uint8_t r, uint8_t v); + uint16_t read16(uint8_t reg); + + uint32_t calculateLux(uint16_t ch0, uint16_t ch1); + void setTiming(tsl2561IntegrationTime_t integration); + void setGain(tsl2561Gain_t gain); + uint16_t getLuminosity (uint8_t channel); + uint32_t getFullLuminosity (); + + private: + int8_t _addr; + tsl2561IntegrationTime_t _integration; + tsl2561Gain_t _gain; + + boolean _initialized; +}; +#endif diff --git a/arduino/libraries/webserver/WebServer.h b/arduino/libraries/webserver/WebServer.h new file mode 100644 index 0000000..701027a --- /dev/null +++ b/arduino/libraries/webserver/WebServer.h @@ -0,0 +1,1175 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-file-style: "k&r"; c-basic-offset: 2; -*- + + Webduino, a simple Arduino web server + Copyright 2009-2012 Ben Combee, Ran Talbott, Christopher Lee, Martin Lormes + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +*/ + +#ifndef WEBDUINO_H_ +#define WEBDUINO_H_ + +#include +#include + +#include +#include + +/******************************************************************** + * CONFIGURATION + ********************************************************************/ + +#define WEBDUINO_VERSION 1007 +#define WEBDUINO_VERSION_STRING "1.7" + +#if WEBDUINO_SUPRESS_SERVER_HEADER +#define WEBDUINO_SERVER_HEADER "" +#else +#define WEBDUINO_SERVER_HEADER "Server: Webduino/" WEBDUINO_VERSION_STRING CRLF +#endif + +// standard END-OF-LINE marker in HTTP +#define CRLF "\r\n" + +// If processConnection is called without a buffer, it allocates one +// of 32 bytes +#define WEBDUINO_DEFAULT_REQUEST_LENGTH 32 + +// How long to wait before considering a connection as dead when +// reading the HTTP request. Used to avoid DOS attacks. +#ifndef WEBDUINO_READ_TIMEOUT_IN_MS +#define WEBDUINO_READ_TIMEOUT_IN_MS 1000 +#endif + +#ifndef WEBDUINO_FAIL_MESSAGE +#define WEBDUINO_FAIL_MESSAGE "

EPIC FAIL

" +#endif + +#ifndef WEBDUINO_AUTH_REALM +#define WEBDUINO_AUTH_REALM "Webduino" +#endif // #ifndef WEBDUINO_AUTH_REALM + +#ifndef WEBDUINO_AUTH_MESSAGE +#define WEBDUINO_AUTH_MESSAGE "

401 Unauthorized

" +#endif // #ifndef WEBDUINO_AUTH_MESSAGE + +#ifndef WEBDUINO_SERVER_ERROR_MESSAGE +#define WEBDUINO_SERVER_ERROR_MESSAGE "

500 Internal Server Error

" +#endif // WEBDUINO_SERVER_ERROR_MESSAGE + +// add '#define WEBDUINO_FAVICON_DATA ""' to your application +// before including WebServer.h to send a null file as the favicon.ico file +// otherwise this defaults to a 16x16 px black diode on blue ground +// (or include your own icon if you like) +#ifndef WEBDUINO_FAVICON_DATA +#define WEBDUINO_FAVICON_DATA { 0x00, 0x00, 0x01, 0x00, 0x01, 0x00, 0x10, \ + 0x10, 0x02, 0x00, 0x01, 0x00, 0x01, 0x00, \ + 0xb0, 0x00, 0x00, 0x00, 0x16, 0x00, 0x00, \ + 0x00, 0x28, 0x00, 0x00, 0x00, 0x10, 0x00, \ + 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x01, \ + 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, \ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \ + 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, \ + 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, \ + 0x00, 0xff, 0xff, 0x00, 0x00, 0xcf, 0xbf, \ + 0x00, 0x00, 0xc7, 0xbf, 0x00, 0x00, 0xc3, \ + 0xbf, 0x00, 0x00, 0xc1, 0xbf, 0x00, 0x00, \ + 0xc0, 0xbf, 0x00, 0x00, 0x00, 0x00, 0x00, \ + 0x00, 0xc0, 0xbf, 0x00, 0x00, 0xc1, 0xbf, \ + 0x00, 0x00, 0xc3, 0xbf, 0x00, 0x00, 0xc7, \ + 0xbf, 0x00, 0x00, 0xcf, 0xbf, 0x00, 0x00, \ + 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, \ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \ + 0x00, 0x00 } +#endif // #ifndef WEBDUINO_FAVICON_DATA + +// add "#define WEBDUINO_SERIAL_DEBUGGING 1" to your application +// before including WebServer.h to have incoming requests logged to +// the serial port. +#ifndef WEBDUINO_SERIAL_DEBUGGING +#define WEBDUINO_SERIAL_DEBUGGING 0 +#endif +#if WEBDUINO_SERIAL_DEBUGGING +#include +#endif + +// declared in wiring.h +extern "C" unsigned long millis(void); + +// declare a static string +#define P(name) static const prog_uchar name[] PROGMEM + +// returns the number of elements in the array +#define SIZE(array) (sizeof(array) / sizeof(*array)) + +/******************************************************************** + * DECLARATIONS + ********************************************************************/ + +/* Return codes from nextURLparam. NOTE: URLPARAM_EOS is returned + * when you call nextURLparam AFTER the last parameter is read. The + * last actual parameter gets an "OK" return code. */ + +typedef enum URLPARAM_RESULT { URLPARAM_OK, + URLPARAM_NAME_OFLO, + URLPARAM_VALUE_OFLO, + URLPARAM_BOTH_OFLO, + URLPARAM_EOS // No params left +}; + +class WebServer: public Print +{ +public: + // passed to a command to indicate what kind of request was received + enum ConnectionType { INVALID, GET, HEAD, POST, PUT, DELETE, PATCH }; + + // any commands registered with the web server have to follow + // this prototype. + // url_tail contains the part of the URL that wasn't matched against + // the registered command table. + // tail_complete is true if the complete URL fit in url_tail, false if + // part of it was lost because the buffer was too small. + typedef void Command(WebServer &server, ConnectionType type, + char *url_tail, bool tail_complete); + + // constructor for webserver object + WebServer(const char *urlPrefix = "", int port = 80); + + // start listening for connections + void begin(); + + // check for an incoming connection, and if it exists, process it + // by reading its request and calling the appropriate command + // handler. This version is for compatibility with apps written for + // version 1.1, and allocates the URL "tail" buffer internally. + void processConnection(); + + // check for an incoming connection, and if it exists, process it + // by reading its request and calling the appropriate command + // handler. This version saves the "tail" of the URL in buff. + void processConnection(char *buff, int *bufflen); + + // set command that's run when you access the root of the server + void setDefaultCommand(Command *cmd); + + // set command run for undefined pages + void setFailureCommand(Command *cmd); + + // add a new command to be run at the URL specified by verb + void addCommand(const char *verb, Command *cmd); + + // utility function to output CRLF pair + void printCRLF(); + + // output a string stored in program memory, usually one defined + // with the P macro + void printP(const prog_uchar *str); + + // inline overload for printP to handle signed char strings + void printP(const prog_char *str) { printP((prog_uchar*)str); } + + // output raw data stored in program memory + void writeP(const prog_uchar *data, size_t length); + + // output HTML for a radio button + void radioButton(const char *name, const char *val, + const char *label, bool selected); + + // output HTML for a checkbox + void checkBox(const char *name, const char *val, + const char *label, bool selected); + + // returns next character or -1 if we're at end-of-stream + int read(); + + // put a character that's been read back into the input pool + void push(int ch); + + // returns true if the string is next in the stream. Doesn't + // consume any character if false, so can be used to try out + // different expected values. + bool expect(const char *expectedStr); + + // returns true if a number, with possible whitespace in front, was + // read from the server stream. number will be set with the new + // value or 0 if nothing was read. + bool readInt(int &number); + + // reads a header value, stripped of possible whitespace in front, + // from the server stream + void readHeader(char *value, int valueLen); + + // Read the next keyword parameter from the socket. Assumes that other + // code has already skipped over the headers, and the next thing to + // be read will be the start of a keyword. + // + // returns true if we're not at end-of-stream + bool readPOSTparam(char *name, int nameLen, char *value, int valueLen); + + // Read the next keyword parameter from the buffer filled by getRequest. + // + // returns 0 if everything weent okay, non-zero if not + // (see the typedef for codes) + URLPARAM_RESULT nextURLparam(char **tail, char *name, int nameLen, + char *value, int valueLen); + + // compare string against credentials in current request + // + // authCredentials must be Base64 encoded outside of Webduino + // (I wanted to be easy on the resources) + // + // returns true if strings match, false otherwise + bool checkCredentials(const char authCredentials[45]); + + // output headers and a message indicating a server error + void httpFail(); + + // output headers and a message indicating "401 Unauthorized" + void httpUnauthorized(); + + // output headers and a message indicating "500 Internal Server Error" + void httpServerError(); + + // output standard headers indicating "200 Success". You can change the + // type of the data you're outputting or also add extra headers like + // "Refresh: 1". Extra headers should each be terminated with CRLF. + void httpSuccess(const char *contentType = "text/html; charset=utf-8", + const char *extraHeaders = NULL); + + // used with POST to output a redirect to another URL. This is + // preferable to outputting HTML from a post because you can then + // refresh the page without getting a "resubmit form" dialog. + void httpSeeOther(const char *otherURL); + + // implementation of write used to implement Print interface + virtual size_t write(uint8_t); + virtual size_t write(const char *str); + virtual size_t write(const uint8_t *buffer, size_t size); + size_t write(const char *data, size_t length); + +private: + EthernetServer m_server; + EthernetClient m_client; + const char *m_urlPrefix; + + unsigned char m_pushback[32]; + char m_pushbackDepth; + + int m_contentLength; + char m_authCredentials[51]; + bool m_readingContent; + + Command *m_failureCmd; + Command *m_defaultCmd; + struct CommandMap + { + const char *verb; + Command *cmd; + } m_commands[8]; + char m_cmdCount; + + void reset(); + void getRequest(WebServer::ConnectionType &type, char *request, int *length); + bool dispatchCommand(ConnectionType requestType, char *verb, + bool tail_complete); + void processHeaders(); + void outputCheckboxOrRadio(const char *element, const char *name, + const char *val, const char *label, + bool selected); + + static void defaultFailCmd(WebServer &server, ConnectionType type, + char *url_tail, bool tail_complete); + void noRobots(ConnectionType type); + void favicon(ConnectionType type); +}; + +/* define this macro if you want to include the header in a sketch source + file but not define any of the implementation. This is useful if + multiple source files are using the Webduino class. */ +#ifndef WEBDUINO_NO_IMPLEMENTATION + +/******************************************************************** + * IMPLEMENTATION + ********************************************************************/ + +WebServer::WebServer(const char *urlPrefix, int port) : + m_server(port), + m_client(255), + m_urlPrefix(urlPrefix), + m_pushbackDepth(0), + m_cmdCount(0), + m_contentLength(0), + m_failureCmd(&defaultFailCmd), + m_defaultCmd(&defaultFailCmd) +{ +} + +void WebServer::begin() +{ + m_server.begin(); +} + +void WebServer::setDefaultCommand(Command *cmd) +{ + m_defaultCmd = cmd; +} + +void WebServer::setFailureCommand(Command *cmd) +{ + m_failureCmd = cmd; +} + +void WebServer::addCommand(const char *verb, Command *cmd) +{ + if (m_cmdCount < SIZE(m_commands)) + { + m_commands[m_cmdCount].verb = verb; + m_commands[m_cmdCount++].cmd = cmd; + } +} + +size_t WebServer::write(uint8_t ch) +{ + return m_client.write(ch); +} + +size_t WebServer::write(const char *str) +{ + return m_client.write(str); +} + +size_t WebServer::write(const uint8_t *buffer, size_t size) +{ + return m_client.write(buffer, size); +} + +size_t WebServer::write(const char *buffer, size_t length) +{ + return m_client.write((const uint8_t *)buffer, length); +} + +void WebServer::writeP(const prog_uchar *data, size_t length) +{ + // copy data out of program memory into local storage, write out in + // chunks of 32 bytes to avoid extra short TCP/IP packets + uint8_t buffer[32]; + size_t bufferEnd = 0; + + while (length--) + { + if (bufferEnd == 32) + { + m_client.write(buffer, 32); + bufferEnd = 0; + } + + buffer[bufferEnd++] = pgm_read_byte(data++); + } + + if (bufferEnd > 0) + m_client.write(buffer, bufferEnd); +} + +void WebServer::printP(const prog_uchar *str) +{ + // copy data out of program memory into local storage, write out in + // chunks of 32 bytes to avoid extra short TCP/IP packets + uint8_t buffer[32]; + size_t bufferEnd = 0; + + while (buffer[bufferEnd++] = pgm_read_byte(str++)) + { + if (bufferEnd == 32) + { + m_client.write(buffer, 32); + bufferEnd = 0; + } + } + + // write out everything left but trailing NUL + if (bufferEnd > 1) + m_client.write(buffer, bufferEnd - 1); +} + +void WebServer::printCRLF() +{ + m_client.write((const uint8_t *)"\r\n", 2); +} + +bool WebServer::dispatchCommand(ConnectionType requestType, char *verb, + bool tail_complete) +{ + // if there is no URL, i.e. we have a prefix and it's requested without a + // trailing slash or if the URL is just the slash + if ((verb[0] == 0) || ((verb[0] == '/') && (verb[1] == 0))) + { + m_defaultCmd(*this, requestType, "", tail_complete); + return true; + } + // if the URL is just a slash followed by a question mark + // we're looking at the default command with GET parameters passed + if ((verb[0] == '/') && (verb[1] == '?')) + { + verb+=2; // skip over the "/?" part of the url + m_defaultCmd(*this, requestType, verb, tail_complete); + return true; + } + // We now know that the URL contains at least one character. And, + // if the first character is a slash, there's more after it. + if (verb[0] == '/') + { + char i; + char *qm_loc; + int verb_len; + int qm_offset; + // Skip over the leading "/", because it makes the code more + // efficient and easier to understand. + verb++; + // Look for a "?" separating the filename part of the URL from the + // parameters. If it's not there, compare to the whole URL. + qm_loc = strchr(verb, '?'); + verb_len = (qm_loc == NULL) ? strlen(verb) : (qm_loc - verb); + qm_offset = (qm_loc == NULL) ? 0 : 1; + for (i = 0; i < m_cmdCount; ++i) + { + if ((verb_len == strlen(m_commands[i].verb)) + && (strncmp(verb, m_commands[i].verb, verb_len) == 0)) + { + // Skip over the "verb" part of the URL (and the question + // mark, if present) when passing it to the "action" routine + m_commands[i].cmd(*this, requestType, + verb + verb_len + qm_offset, + tail_complete); + return true; + } + } + } + return false; +} + +// processConnection with a default buffer +void WebServer::processConnection() +{ + char request[WEBDUINO_DEFAULT_REQUEST_LENGTH]; + int request_len = WEBDUINO_DEFAULT_REQUEST_LENGTH; + processConnection(request, &request_len); +} + +void WebServer::processConnection(char *buff, int *bufflen) +{ + int urlPrefixLen = strlen(m_urlPrefix); + + m_client = m_server.available(); + + if (m_client) { + m_readingContent = false; + buff[0] = 0; + ConnectionType requestType = INVALID; +#if WEBDUINO_SERIAL_DEBUGGING > 1 + Serial.println("*** checking request ***"); +#endif + getRequest(requestType, buff, bufflen); +#if WEBDUINO_SERIAL_DEBUGGING > 1 + Serial.print("*** requestType = "); + Serial.print((int)requestType); + Serial.print(", request = \""); + Serial.print(buff); + Serial.println("\" ***"); +#endif + + // don't even look further at invalid requests. + // this is done to prevent Webduino from hanging + // - when there are illegal requests, + // - when someone contacts it through telnet rather than proper HTTP, + // - etc. + if (requestType != INVALID) + { + processHeaders(); +#if WEBDUINO_SERIAL_DEBUGGING > 1 + Serial.println("*** headers complete ***"); +#endif + + if (strcmp(buff, "/robots.txt") == 0) + { + noRobots(requestType); + } + else if (strcmp(buff, "/favicon.ico") == 0) + { + favicon(requestType); + } + } + if (requestType == INVALID || + strncmp(buff, m_urlPrefix, urlPrefixLen) != 0 || + !dispatchCommand(requestType, buff + urlPrefixLen, + (*bufflen) >= 0)) + { + m_failureCmd(*this, requestType, buff, (*bufflen) >= 0); + } + +#if WEBDUINO_SERIAL_DEBUGGING > 1 + Serial.println("*** stopping connection ***"); +#endif + reset(); + } +} + +bool WebServer::checkCredentials(const char authCredentials[45]) +{ + char basic[7] = "Basic "; + if((0 == strncmp(m_authCredentials,basic,6)) && + (0 == strcmp(authCredentials, m_authCredentials + 6))) return true; + return false; +} + +void WebServer::httpFail() +{ + P(failMsg) = + "HTTP/1.0 400 Bad Request" CRLF + WEBDUINO_SERVER_HEADER + "Content-Type: text/html" CRLF + CRLF + WEBDUINO_FAIL_MESSAGE; + + printP(failMsg); +} + +void WebServer::defaultFailCmd(WebServer &server, + WebServer::ConnectionType type, + char *url_tail, + bool tail_complete) +{ + server.httpFail(); +} + +void WebServer::noRobots(ConnectionType type) +{ + httpSuccess("text/plain"); + if (type != HEAD) + { + P(allowNoneMsg) = "User-agent: *" CRLF "Disallow: /" CRLF; + printP(allowNoneMsg); + } +} + +void WebServer::favicon(ConnectionType type) +{ + httpSuccess("image/x-icon","Cache-Control: max-age=31536000\r\n"); + if (type != HEAD) + { + P(faviconIco) = WEBDUINO_FAVICON_DATA; + writeP(faviconIco, sizeof(faviconIco)); + } +} + +void WebServer::httpUnauthorized() +{ + P(failMsg) = + "HTTP/1.0 401 Authorization Required" CRLF + WEBDUINO_SERVER_HEADER + "Content-Type: text/html" CRLF + "WWW-Authenticate: Basic realm=\"" WEBDUINO_AUTH_REALM "\"" CRLF + CRLF + WEBDUINO_AUTH_MESSAGE; + + printP(failMsg); +} + +void WebServer::httpServerError() +{ + P(failMsg) = + "HTTP/1.0 500 Internal Server Error" CRLF + WEBDUINO_SERVER_HEADER + "Content-Type: text/html" CRLF + CRLF + WEBDUINO_SERVER_ERROR_MESSAGE; + + printP(failMsg); +} + +void WebServer::httpSuccess(const char *contentType, + const char *extraHeaders) +{ + P(successMsg1) = + "HTTP/1.0 200 OK" CRLF + WEBDUINO_SERVER_HEADER + "Access-Control-Allow-Origin: *" CRLF + "Content-Type: "; + + printP(successMsg1); + print(contentType); + printCRLF(); + if (extraHeaders) + print(extraHeaders); + printCRLF(); +} + +void WebServer::httpSeeOther(const char *otherURL) +{ + P(seeOtherMsg) = + "HTTP/1.0 303 See Other" CRLF + WEBDUINO_SERVER_HEADER + "Location: "; + + printP(seeOtherMsg); + print(otherURL); + printCRLF(); + printCRLF(); +} + +int WebServer::read() +{ + if (m_client == NULL) + return -1; + + if (m_pushbackDepth == 0) + { + unsigned long timeoutTime = millis() + WEBDUINO_READ_TIMEOUT_IN_MS; + + while (m_client.connected()) + { + // stop reading the socket early if we get to content-length + // characters in the POST. This is because some clients leave + // the socket open because they assume HTTP keep-alive. + if (m_readingContent) + { + if (m_contentLength == 0) + { +#if WEBDUINO_SERIAL_DEBUGGING > 1 + Serial.println("\n*** End of content, terminating connection"); +#endif + return -1; + } + } + + int ch = m_client.read(); + + // if we get a character, return it, otherwise continue in while + // loop, checking connection status + if (ch != -1) + { + // count character against content-length + if (m_readingContent) + { + --m_contentLength; + } + +#if WEBDUINO_SERIAL_DEBUGGING + if (ch == '\r') + Serial.print(""); + else if (ch == '\n') + Serial.println(""); + else + Serial.print((char)ch); +#endif + return ch; + } + else + { + unsigned long now = millis(); + if (now > timeoutTime) + { + // connection timed out, destroy client, return EOF +#if WEBDUINO_SERIAL_DEBUGGING + Serial.println("*** Connection timed out"); +#endif + reset(); + return -1; + } + } + } + + // connection lost, return EOF +#if WEBDUINO_SERIAL_DEBUGGING + Serial.println("*** Connection lost"); +#endif + return -1; + } + else + return m_pushback[--m_pushbackDepth]; +} + +void WebServer::push(int ch) +{ + // don't allow pushing EOF + if (ch == -1) + return; + + m_pushback[m_pushbackDepth++] = ch; + // can't raise error here, so just replace last char over and over + if (m_pushbackDepth == SIZE(m_pushback)) + m_pushbackDepth = SIZE(m_pushback) - 1; +} + +void WebServer::reset() +{ + m_pushbackDepth = 0; + m_client.flush(); + m_client.stop(); +} + +bool WebServer::expect(const char *str) +{ + const char *curr = str; + while (*curr != 0) + { + int ch = read(); + if (ch != *curr++) + { + // push back ch and the characters we accepted + push(ch); + while (--curr != str) + push(curr[-1]); + return false; + } + } + return true; +} + +bool WebServer::readInt(int &number) +{ + bool negate = false; + bool gotNumber = false; + int ch; + number = 0; + + // absorb whitespace + do + { + ch = read(); + } while (ch == ' ' || ch == '\t'); + + // check for leading minus sign + if (ch == '-') + { + negate = true; + ch = read(); + } + + // read digits to update number, exit when we find non-digit + while (ch >= '0' && ch <= '9') + { + gotNumber = true; + number = number * 10 + ch - '0'; + ch = read(); + } + + push(ch); + if (negate) + number = -number; + return gotNumber; +} + +void WebServer::readHeader(char *value, int valueLen) +{ + int ch; + memset(value, 0, valueLen); + --valueLen; + + // absorb whitespace + do + { + ch = read(); + } while (ch == ' ' || ch == '\t'); + + // read rest of line + do + { + if (valueLen > 1) + { + *value++=ch; + --valueLen; + ch = read(); + } + } while (ch != '\r'); + push(ch); +} + +bool WebServer::readPOSTparam(char *name, int nameLen, + char *value, int valueLen) +{ + // assume name is at current place in stream + int ch; + // to not to miss the last parameter + bool foundSomething = false; + + // clear out name and value so they'll be NUL terminated + memset(name, 0, nameLen); + memset(value, 0, valueLen); + + // decrement length so we don't write into NUL terminator + --nameLen; + --valueLen; + + while ((ch = read()) != -1) + { + foundSomething = true; + if (ch == '+') + { + ch = ' '; + } + else if (ch == '=') + { + /* that's end of name, so switch to storing in value */ + nameLen = 0; + continue; + } + else if (ch == '&') + { + /* that's end of pair, go away */ + return true; + } + else if (ch == '%') + { + /* handle URL encoded characters by converting back to original form */ + int ch1 = read(); + int ch2 = read(); + if (ch1 == -1 || ch2 == -1) + return false; + char hex[3] = { ch1, ch2, 0 }; + ch = strtoul(hex, NULL, 16); + } + + // output the new character into the appropriate buffer or drop it if + // there's no room in either one. This code will malfunction in the + // case where the parameter name is too long to fit into the name buffer, + // but in that case, it will just overflow into the value buffer so + // there's no harm. + if (nameLen > 0) + { + *name++ = ch; + --nameLen; + } + else if (valueLen > 0) + { + *value++ = ch; + --valueLen; + } + } + + if (foundSomething) + { + // if we get here, we have one last parameter to serve + return true; + } + else + { + // if we get here, we hit the end-of-file, so POST is over and there + // are no more parameters + return false; + } +} + +/* Retrieve a parameter that was encoded as part of the URL, stored in + * the buffer pointed to by *tail. tail is updated to point just past + * the last character read from the buffer. */ +URLPARAM_RESULT WebServer::nextURLparam(char **tail, char *name, int nameLen, + char *value, int valueLen) +{ + // assume name is at current place in stream + char ch, hex[3]; + URLPARAM_RESULT result = URLPARAM_OK; + char *s = *tail; + bool keep_scanning = true; + bool need_value = true; + + // clear out name and value so they'll be NUL terminated + memset(name, 0, nameLen); + memset(value, 0, valueLen); + + if (*s == 0) + return URLPARAM_EOS; + // Read the keyword name + while (keep_scanning) + { + ch = *s++; + switch (ch) + { + case 0: + s--; // Back up to point to terminating NUL + // Fall through to "stop the scan" code + case '&': + /* that's end of pair, go away */ + keep_scanning = false; + need_value = false; + break; + case '+': + ch = ' '; + break; + case '%': + /* handle URL encoded characters by converting back + * to original form */ + if ((hex[0] = *s++) == 0) + { + s--; // Back up to NUL + keep_scanning = false; + need_value = false; + } + else + { + if ((hex[1] = *s++) == 0) + { + s--; // Back up to NUL + keep_scanning = false; + need_value = false; + } + else + { + hex[2] = 0; + ch = strtoul(hex, NULL, 16); + } + } + break; + case '=': + /* that's end of name, so switch to storing in value */ + keep_scanning = false; + break; + } + + + // check against 1 so we don't overwrite the final NUL + if (keep_scanning && (nameLen > 1)) + { + *name++ = ch; + --nameLen; + } + else + result = URLPARAM_NAME_OFLO; + } + + if (need_value && (*s != 0)) + { + keep_scanning = true; + while (keep_scanning) + { + ch = *s++; + switch (ch) + { + case 0: + s--; // Back up to point to terminating NUL + // Fall through to "stop the scan" code + case '&': + /* that's end of pair, go away */ + keep_scanning = false; + need_value = false; + break; + case '+': + ch = ' '; + break; + case '%': + /* handle URL encoded characters by converting back to original form */ + if ((hex[0] = *s++) == 0) + { + s--; // Back up to NUL + keep_scanning = false; + need_value = false; + } + else + { + if ((hex[1] = *s++) == 0) + { + s--; // Back up to NUL + keep_scanning = false; + need_value = false; + } + else + { + hex[2] = 0; + ch = strtoul(hex, NULL, 16); + } + + } + break; + } + + + // check against 1 so we don't overwrite the final NUL + if (keep_scanning && (valueLen > 1)) + { + *value++ = ch; + --valueLen; + } + else + result = (result == URLPARAM_OK) ? + URLPARAM_VALUE_OFLO : + URLPARAM_BOTH_OFLO; + } + } + *tail = s; + return result; +} + + + +// Read and parse the first line of the request header. +// The "command" (GET/HEAD/POST) is translated into a numeric value in type. +// The URL is stored in request, up to the length passed in length +// NOTE 1: length must include one byte for the terminating NUL. +// NOTE 2: request is NOT checked for NULL, nor length for a value < 1. +// Reading stops when the code encounters a space, CR, or LF. If the HTTP +// version was supplied by the client, it will still be waiting in the input +// stream when we exit. +// +// On return, length contains the amount of space left in request. If it's +// less than 0, the URL was longer than the buffer, and part of it had to +// be discarded. + +void WebServer::getRequest(WebServer::ConnectionType &type, + char *request, int *length) +{ + --*length; // save room for NUL + + type = INVALID; + + // store the HTTP method line of the request + if (expect("GET ")) + type = GET; + else if (expect("HEAD ")) + type = HEAD; + else if (expect("POST ")) + type = POST; + else if (expect("PUT ")) + type = PUT; + else if (expect("DELETE ")) + type = DELETE; + else if (expect("PATCH ")) + type = PATCH; + + // if it doesn't start with any of those, we have an unknown method + // so just get out of here + else + return; + + int ch; + while ((ch = read()) != -1) + { + // stop storing at first space or end of line + if (ch == ' ' || ch == '\n' || ch == '\r') + { + break; + } + if (*length > 0) + { + *request = ch; + ++request; + } + --*length; + } + // NUL terminate + *request = 0; +} + +void WebServer::processHeaders() +{ + // look for three things: the Content-Length header, the Authorization + // header, and the double-CRLF that ends the headers. + + // empty the m_authCredentials before every run of this function. + // otherwise users who don't send an Authorization header would be treated + // like the last user who tried to authenticate (possibly successful) + m_authCredentials[0]=0; + + while (1) + { + if (expect("Content-Length:")) + { + readInt(m_contentLength); +#if WEBDUINO_SERIAL_DEBUGGING > 1 + Serial.print("\n*** got Content-Length of "); + Serial.print(m_contentLength); + Serial.print(" ***"); +#endif + continue; + } + + if (expect("Authorization:")) + { + readHeader(m_authCredentials,51); +#if WEBDUINO_SERIAL_DEBUGGING > 1 + Serial.print("\n*** got Authorization: of "); + Serial.print(m_authCredentials); + Serial.print(" ***"); +#endif + continue; + } + + if (expect(CRLF CRLF)) + { + m_readingContent = true; + return; + } + + // no expect checks hit, so just absorb a character and try again + if (read() == -1) + { + return; + } + } +} + +void WebServer::outputCheckboxOrRadio(const char *element, const char *name, + const char *val, const char *label, + bool selected) +{ + P(cbPart1a) = "