diff --git a/MS5803/ms5803.cpp b/MS5803/ms5803.cpp new file mode 100644 index 0000000..0895de0 --- /dev/null +++ b/MS5803/ms5803.cpp @@ -0,0 +1,163 @@ +#include "ms5803.h" +#include + + +MS5803::MS5803(uint8_t address) +{ + this->address = address; +} + +uint8_t MS5803::begin() +{ + + Wire.begin(); + + Wire.beginTransmission( address ); + Wire.write( MS_RESET ); + Wire.endTransmission(); + delay(100); + + Wire.beginTransmission( address ); + + for( uint8_t i = 0; i<8; i++ ) + { + + Wire.beginTransmission( address ); + Wire.write( MS_PROMREAD0 + i*2 ); + Wire.endTransmission(); + + delay(10); + + Wire.requestFrom( address, 2 ); + + uint32_t timeout = millis(); + + while( !Wire.available() ) + { + + if( ( millis() - timeout ) > MS5803_TIMEOUT ) + { + return 1; // failed to initialize device + } + } + + PROM[i] = Wire.read() << 8; + + if( Wire.available() ) + { + PROM[i] += Wire.read(); + } + else + { + return 2; + } + Serial.print("PROM");Serial.print(i);Serial.print(": "); + Serial.print(PROM[i]); + Serial.println(); + + } + + return 0; + +} + +uint8_t MS5803::measure() +{ + Wire.beginTransmission( address ); + Wire.write( MS_CONVERTD1_4096 ); + Wire.endTransmission(); + + delay( 10 ); + Wire.beginTransmission( address ); + Wire.write( MS_ADCREAD ); + Wire.endTransmission(); + + Wire.requestFrom( address , 3 ); + + uint32_t timeout = millis(); + while( Wire.available() != 3 ) + { + if( ( millis() - timeout ) > MS5803_TIMEOUT ) + { + return 1; // failed to initialize device + } + } + + pressure_nc = Wire.read() << 16; + pressure_nc += Wire.read() << 8; + pressure_nc += Wire.read(); + + Wire.endTransmission(); + + + Wire.beginTransmission( address ); + Wire.write( MS_CONVERTD2_4096 ); + Wire.endTransmission(); + delay( 10 ); + + Wire.beginTransmission( address ); + Wire.write( MS_ADCREAD ); + Wire.endTransmission(); + + Wire.requestFrom( address , 3); + + timeout = millis(); + while( Wire.available() != 3 ) + { + if( ( millis() - timeout ) > MS5803_TIMEOUT ) + { + return 1; // failed to initialize device + } + } + + temp_nc = Wire.read() << 16; + temp_nc += Wire.read() << 8; + temp_nc += Wire.read(); + + Wire.endTransmission(); + + compensate(); + return 0; +} + +void MS5803::compensate() +{ + + Serial.print("Temp_nc:\t");Serial.println(temp_nc); + int64_t dT = 0; + int64_t TEMP = 0; + int64_t OFF = 0; + int64_t SENS = 0; + int64_t PRESSURE = 0; + + // Temperature compensation + dT = int64_t( temp_nc ) - int64_t( int64_t( PROM[5] ) * int32_t( pow(2,8) ) ); + TEMP = int64_t(2000) + int64_t( dT ) * int64_t( PROM[6] ) / int32_t( pow(2,23) ); + + + // Pressure Compensation + OFF = int64_t( PROM[2] ) * int32_t( pow(2,17) ) + int64_t( PROM[4] ) * dT / int32_t( pow(2,6) ); + + SENS = int64_t( PROM[1] ) * int32_t( pow(2,16) ) + int64_t( PROM[3] ) * dT / int32_t( pow(2,7) ); + + PRESSURE = int64_t( pressure_nc * ( SENS / int32_t( pow(2,21) ) ) ) - OFF; + + PRESSURE = PRESSURE / int32_t( pow(2,15) ); + + // convert to float + pressure_c = float( PRESSURE ) / 1000.00; // kPa + temp_c = float( TEMP ) / 100.00; // degrees C + +} + +float MS5803::getPressure() +{ + return pressure_c; +} + +float MS5803::getTemperature() +{ + return temp_c; +} + + diff --git a/MS5803/ms5803.h b/MS5803/ms5803.h new file mode 100644 index 0000000..b82cc4c --- /dev/null +++ b/MS5803/ms5803.h @@ -0,0 +1,39 @@ +#pragma once + +#include + +#define MS5803_TIMEOUT 5000 +#define MS_RESET 0x1E +#define MS_CONVERTD1_256 0x40 +#define MS_CONVERTD1_512 0x42 +#define MS_CONVERTD1_1024 0x44 +#define MS_CONVERTD1_2048 0x46 +#define MS_CONVERTD1_4096 0x48 +#define MS_CONVERTD2_256 0x50 +#define MS_CONVERTD2_512 0x52 +#define MS_CONVERTD2_1024 0x54 +#define MS_CONVERTD2_2048 0x56 +#define MS_CONVERTD2_4096 0x58 +#define MS_ADCREAD 0x00 +#define MS_PROMREAD0 0xA0 + +#define MS_ADDRESS 0x77 + +class MS5803 { +private: + uint16_t PROM[8]; + float temp_c = 0; + float pressure_c = 0; + int32_t temp_nc = 0; + int32_t pressure_nc = 0; + uint8_t address; + + void compensate(); + +public: + MS5803(uint8_t); + uint8_t begin(); + uint8_t measure(); + float getPressure(); + float getTemperature(); +}; \ No newline at end of file diff --git a/OpensPower/OpensPower.cpp b/OpensPower/OpensPower.cpp new file mode 100644 index 0000000..d5c0306 --- /dev/null +++ b/OpensPower/OpensPower.cpp @@ -0,0 +1,634 @@ + +#include "OpensPower.h" + + +OpensPower::OpensPower(int offPin) +{ + shutoffPin = offPin; + + interruptPin = -1; +} + +/* +Constructor +Parameters: +- offPin: the output pin to use to shut down the device +- intPin: the input pin to use for alarm interrupts when the device + is already on. +*/ +OpensPower::OpensPower(int offPin, int intPin) +{ + // shutoffPin is used EXCLUSIVELY for turning the device off. + // do not repurpose it. + shutoffPin = offPin; + + + // input pin for alarm interrupts while the device is on + interruptPin = intPin; + +} + +void OpensPower::begin() +{ + Wire.begin(); + pinMode(shutoffPin, OUTPUT); + digitalWrite(shutoffPin, LOW); + getTime(); + Serial.print("Time:\t"); Serial.print(date);Serial.print(':'); + Serial.print(hour);Serial.print(':'); + Serial.print(minute);Serial.print(':'); + Serial.println(second); + // initialize and write the status byte + getStatus(); + status &= 0B10000100; + setStatus(); + + // initialize and write the control byte + getControl(); + control = 0B01000100; + setControl(); + + // update real time, alarm times + + alarm1_getTime(); + alarm2_getTime(); + + getTime(); + Serial.print("Time:\t"); Serial.print(date);Serial.print(':'); + Serial.print(hour);Serial.print(':'); + Serial.print(minute);Serial.print(':'); + Serial.println(second); +} + +/* + +PowerDown +Power down the device by writing shutoffPin HIGH. If the shutoffpin is +not defined, do nothing. If no alarms are activated, turn on alarm1 +using the current alarm1 times and set it to trigger when everything +matches. + +*/ + +void OpensPower::powerDown() +{ + if ( shutoffPin < 1 ) // if the shutoff pin is not defined + { Serial.println( "powerdown failed" ); + return; } + + getControl(); // get the current control byte + + // if no alarm is activated, activate alarm1 + if( ( alarm1_state | alarm2_state ) == 0 ) + { + alarm1_enable(MATCH_DATE); // match date, hour, minute, second + } + alarm1_getTime(); + Serial.print("Alarm1:\t"); Serial.print(alarm1_date);Serial.print(':'); + Serial.print(alarm1_hour);Serial.print(':'); + Serial.print(alarm1_minute);Serial.print(':'); + Serial.println(alarm1_second); + + clearAlarmFlags(); + Serial.print("Control:\t"); Serial.println(control); + Serial.print("Status: \t"); Serial.println(status); + Serial.println("writing shutoffPin HIGH\n\n"); + digitalWrite(shutoffPin, HIGH); // turn the device off + +} + + +/* +getTime +Retrieves the current time from the RTC. Century is marked on the MSB +of the month byte. + +*/ + +void OpensPower::getTime() +{ + uint8_t temp_byte = 0; + + Wire.beginTransmission(address); + Wire.write(0x00); + Wire.endTransmission(); + Wire.requestFrom( address, 7 ); + second = Wire.read(); + minute = Wire.read(); + hour = Wire.read(); + day = Wire.read(); + date = Wire.read(); + month = Wire.read(); + year = Wire.read(); + Wire.endTransmission(); + + century = month & 0B10000000; // marked as the MSB of the month byte + month = month & 0B01111111; // get rid of the century bit + + second = (second & 15 ) + ( second >> 4 ) * 10; + minute = (minute & 15 ) + ( minute >> 4 ) * 10; + hour = (hour & 15 ) + ( hour >> 4 ) * 10; + date = (date & 15 ) + ( date >> 4 ) * 10; + month = (month & 15 ) + ( month >> 4 ) * 10; + year = (year & 15 ) + ( year >> 4 ) * 10; + + + +} + +/* +getStatus +gets the status byte from the RTC. Status stores conditions such as +the lostPower flag, the busy flag, the enable32kHz bit, and the alarm +flags. +*/ +void OpensPower::getStatus() +{ + + //get the raw status byte + Wire.beginTransmission(address); + Wire.write(0x0F); + Wire.endTransmission(); + Wire.requestFrom( address, 1 ); + status = Wire.read(); + Wire.endTransmission(); + + // we only use these conditions + alarm1_flag = ( status>>1 ) & 1; + alarm2_flag = ( status ) & 1; + + // and this setting + lostPower = (status >> 7 ) & 1; +} + +void OpensPower::setStatus() +{ + Wire.beginTransmission( address ); + Wire.write( 0x0F ); + Wire.write( status ); + Wire.endTransmission(); +} + +/* +getControl +Same as getStatus but the control byte stores settings. +*/ + +void OpensPower::getControl() +{ + // get the raw control byte + Wire.beginTransmission(address); + Wire.write(0x0E); + Wire.endTransmission(); + Wire.requestFrom( address, 1 ); + control = Wire.read(); + Wire.endTransmission(); + + // only these need to be updated + alarm1_state = ( control ) & 1; + alarm2_state = ( control >> 1 ) & 1; +} + +void OpensPower::setControl() +{ + Wire.beginTransmission( address ); + Wire.write( 0x0E ); + Wire.write( control ); + Wire.endTransmission(); +} + + +/* +Alarm1 getTime +Gets the date, hour, minute, second that alarm1 will trigger. Updates +the variables that hold this information. +*/ +void OpensPower::alarm1_getTime() +{ + Wire.beginTransmission(address); + Wire.write(0x07); + Wire.endTransmission(); + + Wire.requestFrom( address, 4 ); + alarm1_second = Wire.read(); + alarm1_minute = Wire.read(); + alarm1_hour = Wire.read(); + alarm1_date = Wire.read(); + Wire.endTransmission(); + + alarm1_second &= 0B01111111; + alarm1_minute &= 0B01111111; + alarm1_hour &= 0B01111111; + alarm1_date &= 0B01111111; + + alarm1_second = (alarm1_second & 15 ) + ( alarm1_second >> 4 ) * 10; + alarm1_minute = (alarm1_minute & 15 ) + ( alarm1_minute >> 4 ) * 10; + alarm1_hour = (alarm1_hour & 15 ) + ( alarm1_hour >> 4 ) * 10; + alarm1_date = (alarm1_date & 15 ) + ( alarm1_date >> 4 ) * 10; + +} + + +/* +Alarm1 setTime +Sets the Alarm1 time using provided values. +*/ + + +// Use new minute and second times, but use the current date and hour. +void OpensPower::alarm1_setTime(uint8_t m, + uint8_t s) +{ + alarm1_setTime(alarm1_date, alarm1_hour, m, s); +} + +// Use new hour, minute, second times, but current date +void OpensPower::alarm1_setTime(uint8_t h, + uint8_t m, + uint8_t s) +{ + alarm1_setTime(alarm1_date,h,m,s); +} + +// Use new date, hour, minute, second +void OpensPower::alarm1_setTime(uint8_t d, + uint8_t h, + uint8_t m, + uint8_t s) +{ + + // update the values + alarm1_date = d; + alarm1_hour = h; + alarm1_minute = m; + alarm1_second = s; + + uint8_t temp_byte = 0; + // seconds is split like: + // bit 4,5,6 = 10 seconds + // bit 0,1,2,3 = seconds + temp_byte = s % 10; // get the first three bits + // the remaining amount of seconds are divisible by 10 if we subtract temp_byte from them + // so do that, divide by 10 for the 10's place, and shift over to bit 4. + temp_byte += (( s - temp_byte ) / 10 ) << 4; + s = temp_byte; + + // now repeat for minutes, hours, date + temp_byte = m % 10; + temp_byte += (( m - temp_byte ) / 10 ) << 4; + m = temp_byte; + + temp_byte = h % 10; + temp_byte += (( h - temp_byte ) / 10 ) << 4; + h = temp_byte; + + temp_byte = d % 10; + temp_byte += (( d - temp_byte ) / 10 ) << 4; + d = temp_byte; + + + // send the values to the RTC + Wire.beginTransmission(address); + Wire.write(0x07); + Wire.write(s); + Wire.write(m); + Wire.write(h); + Wire.write(d); + Wire.endTransmission(); + + +} + + +/* +Alarm1 Enable +Parameters: +- alarmType: choose what needs to match in order to trigger the alarm. + valid types are: + EVERY_SECOND + MATCH_SECONDS + MATCH_MINUTES + MATCH_HOURS + MATCH_DATE + +Enables Alarm1 and sets the mask that determines how often to trigger +the alarm. +*/ +void OpensPower::alarm1_enable(uint8_t alarmType) +{ + // get the current alarm time just to be sure. The alarm type + // is masked over the alarm times, so we are careful not to + // overwrite the desired alarm times. + alarm1_getTime(); + + // set the correct alarm masks based on the alarm type + switch( alarmType ) + { + case EVERY_SECOND: // 1,1,1,1 + alarm1_second |= 0B10000000; + alarm1_minute |= 0B10000000; + alarm1_hour |= 0B10000000; + alarm1_date |= 0B10000000; + break; + + case MATCH_SECONDS: // 0,1,1,1 + alarm1_second &= 0B01111111; + alarm1_minute |= 0B10000000; + alarm1_hour |= 0B10000000; + alarm1_date |= 0b10000000; + break; + + case MATCH_MINUTES: // 0,0,1,1 + alarm1_second &= 0B01111111; + alarm1_minute &= 0B01111111; + alarm1_hour |= 0B10000000; + alarm1_date |= 0B10000000; + break; + + case MATCH_HOURS: // 0,0,0,1 + alarm1_second &= 0B01111111; + alarm1_minute &= 0B01111111; + alarm1_hour &= 0B01111111; + alarm1_date |= 0B10000000; + break; + + case MATCH_DATE: // 0,0,0,0 + alarm1_second &= 0B01111111; + alarm1_minute &= 0B01111111; + alarm1_hour &= 0B01111111; + alarm1_date &= 0B01111111; + break; + + default: + break; + } + + // write the masks to the RTC + alarm1_setTime( alarm1_date, alarm1_hour, alarm1_minute, alarm1_second ); + + // get the control byte and make sure only alarm1 is enabled + // and the alarm flags are transparent to the INT/SQW pin. + getControl(); + control |= 1; + control &= (~2); + control |= 4; + + // write the correct settings + setControl(); +} + +// same as alarm1_getTime but without seconds. +void OpensPower::alarm2_getTime() +{ + Wire.beginTransmission(address); + Wire.write(0x0B); + alarm2_minute = Wire.read(); + alarm2_hour = Wire.read(); + alarm2_date = Wire.read(); + Wire.endTransmission(); +} + +void OpensPower::alarm2_setTime(uint8_t h, + uint8_t m) +{ + alarm2_setTime(alarm2_date, h, m); +} + +void OpensPower::alarm2_setTime(uint8_t m) +{ + alarm2_setTime(alarm2_date, alarm2_hour, m); +} + +void OpensPower::alarm2_setTime(uint8_t d, + uint8_t h, + uint8_t m) +{ + Wire.beginTransmission(address); + Wire.write(0x0B); + Wire.write(m); + Wire.write(h); + Wire.write(d); + Wire.endTransmission(); + + alarm2_minute = m; + alarm2_hour = h; + alarm2_date = d; +} + +void OpensPower::alarm2_enable(uint8_t alarmType) +{ + alarm1_getTime(); + + switch( alarmType ) + { + case EVERY_MINUTE: + alarm2_minute |= 0B10000000; + alarm2_hour |= 0B10000000; + alarm2_date |= 0B10000000; + break; + + case MATCH_MINUTES: + alarm2_minute &= 0B01111111; + alarm2_hour |= 0B10000000; + alarm2_date |= 0b10000000; + break; + + case MATCH_HOURS: + alarm2_minute &= 0B01111111; + alarm2_hour &= 0B01111111; + alarm2_date |= 0B10000000; + break; + + case MATCH_DATE: + alarm2_minute &= 0B01111111; + alarm2_hour &= 0B01111111; + alarm2_date &= 0B01111111; + break; + + default: + break; + } + alarm2_setTime( alarm2_date, alarm2_hour, alarm2_minute ); + + getStatus(); + status |= 2; + status &= (!1); + status |= 4; + setStatus(); +} + + +/* +Squarewave +Sets the INT/SQW pin to output a squarewave with the designated frequency. +Alarms are not transparent to the INT pin until they are set again. +*/ + +void OpensPower::squarewave(uint8_t frequency = SQW_1HZ) +{ + control = 0B01000000; + + switch(frequency) + { + case SQW_1KHZ: + control ^= 0B00001000; + break; + + case SQW_4KHZ: + control ^= 0B00010000; + break; + + case SQW_8KHZ: + control ^= 0B00011000; + break; + + default: + break; + } + + setControl(); +} + +// Attaches an intterupt to the input pin. The user can link a custom ISR +// by passing a function pointer to the ISR to setInterrupt. The interrupt will +// trigger when interruptPin falls LOW, so don't mess with the interruptPin until +// detachInterrupt() is called. +void OpensPower::setInterrupt(void (*userFunction)(void) ) +{ + interrupted = false; + pinMode(interruptPin,INPUT_PULLUP); + attachInterrupt(interruptPin, *userFunction, FALLING ); +} + +// Just a simple ISR that sets a member variable flag to 'true' after detaching +// interrupts, then exits. +void OpensPower::defaultISR(void) +{ + detachInterrupt( interruptPin ); + interrupted = true; +} + + +// disable both alarms. +void OpensPower::clearAlarms() +{ + control &= 0B11111100; + setControl(); +} + +// check which alarms have been triggered. Useful if you aren't using interrupts. +// Returns: +// 0: no alarms have triggered +// 1: alarm 1 triggered +// 2: alarm 2 triggered +// 3: both alarms triggered +uint8_t OpensPower::checkAlarmFlags() +{ + getStatus(); + return ( status & 3 ); +} + +// clear the alarm flag bits in the status register +void OpensPower::clearAlarmFlags() +{ + getStatus(); + status &= 0B11111100; + setStatus(); +} + +void OpensPower::setTime( uint8_t yr, + uint8_t mo, + uint8_t dy, + uint8_t hr, + uint8_t mi, + uint8_t sc) +{ + + uint8_t temp_byte = 0; + // seconds is split like: + // bit 4,5,6 = 10 seconds + // bit 0,1,2,3 = seconds + temp_byte = sc % 10; // get the first three bits + // the remaining amount of seconds are divisible by 10 if we subtract temp_byte from them + // so do that, divide by 10 for the 10's place, and shift over to bit 4. + temp_byte += (( sc - temp_byte ) / 10 ) << 4; + sc = temp_byte; + + // now repeat for minutes, hours, date + temp_byte = mi % 10; + temp_byte += (( mi - temp_byte ) / 10 ) << 4; + mi = temp_byte; + + temp_byte = hr % 10; + temp_byte += (( hr - temp_byte ) / 10 ) << 4; + hr = temp_byte; + + temp_byte = dy % 10; + temp_byte += (( dy - temp_byte ) / 10 ) << 4; + dy = temp_byte; + + temp_byte = mo % 10; + temp_byte += (( mo - temp_byte ) / 10 ) << 4; + mo = temp_byte; + + temp_byte = yr % 10; + temp_byte += (( yr - temp_byte ) / 10 ) << 4; + yr = temp_byte; + + + Wire.beginTransmission( address ); + Wire.write( 0x00 ); // seconds address + Wire.write( sc ); // seconds + Wire.write( mi ); // minutes + Wire.write( hr ); // hours + Wire.write( 0 ); // weekday + Wire.write( dy ); // day of month + Wire.write( mo | ( century << 7 ) ); // month with century mask + Wire.write( yr ); // year + Wire.endTransmission(); + + getTime(); +} + +void OpensPower::alarm1_SetTime_Offset( uint32_t alm_offset ) +{ + getTime(); + + alm_offset = alm_offset + + second + + minute * 60 + + hour * 3600 + + date * 86400; + + alarm1_second = alm_offset % 60; + alm_offset -= alarm1_second; + + alarm1_minute = ( alm_offset % 3600 ) / 60; + alm_offset -= alarm1_minute * 60; + + alarm1_hour = ( alm_offset % 86400 ) / 3600; + alm_offset -= alarm1_hour * 3600; + + alarm1_date = alm_offset / 86400; + + + + alarm1_setTime( alarm1_date, alarm1_hour, alarm1_minute, alarm1_second ); + +} + + + + + + + + + + + + + + + + diff --git a/OpensPower/OpensPower.h b/OpensPower/OpensPower.h new file mode 100644 index 0000000..3df3737 --- /dev/null +++ b/OpensPower/OpensPower.h @@ -0,0 +1,180 @@ +#pragma once + +/* + +OPEnS Power Header File +Author: mitch nelke +Date: August 24, 2018 +Version: 1.0 + +This is the first draft of the OpensPower library written for the OPEnS Lab +at OSU. The library is essentially just an RTC library, meant to interface with +the DS3231 real time clock (most often on the Adafruit breakout board). The class +also allows the use of powerDown(), which shuts off the device when interfaced with +the Opens Power board. The board also allows the user to enable interrupts based on +either alarm. Either alarm can be used to wake the device up, but only the shutoff pin +can be used to turn the device off. It's therefore important to NOT use the shutoff pin +for any other function. + +*/ + +#include +#include + + +// definitions for alarm masks +#define EVERY_SECOND 0 // once per second +#define EVERY_MINUTE 0 // first second of every minute +#define MATCH_SECONDS 1 // only second needs to match +#define MATCH_MINUTES 2 // minute and second match +#define MATCH_HOURS 3 // hour, minute, second match +#define MATCH_DATE 4 // date, hour, minute, second match + + +// squarewave frequencies +#define SQW_1HZ 0 +#define SQW_1KHZ 1 +#define SQW_4KHZ 2 +#define SQW_8KHZ 3 + + +class OpensPower +{ +private: + // input pin for reading alarm interrupt when device is on. + // can be reused for other purposes. + int interruptPin; + + // output pin for shutting device off. This pin will shut off + // the power supply until the device is awoken by the RTC alarm. + // Don't reuse for any other functionality, only use through + // OpensPower object (checks for alarms being set). + int shutoffPin; + + uint8_t control; // control byte for RTC + uint8_t status; // status byte for RTC + const uint8_t address = 0x68; // RTC's I2C address + + + // write the currently saved status byte to the RTC + void setStatus(); + + // write the currently saved control byte to the RTC + void setControl(); + +public: + bool lostPower; + + uint8_t century; // current century (0,1) + uint8_t year; // two-digit year (0-99) + uint8_t month; + uint8_t date; // day of the month (1-31) + uint8_t day; // day of the week (1-7) + uint8_t hour; + uint8_t minute; + uint8_t second; + + + OpensPower(int offPin); + OpensPower(int offPin, int intPin); + + void begin(); + + void powerDown(); + + void getTime(); + void setTime( uint8_t yr, + uint8_t mo, + uint8_t dy, + uint8_t hr, + uint8_t mt, + uint8_t sc); + + void getStatus(); + void getControl(); + + +/********** Alarm 1 Functions *********/ + + bool alarm1_state; // enabled or disabled + bool alarm1_flag; // flag indicating alarm has triggered + uint8_t alarm1_date; // day of the month + uint8_t alarm1_hour; + uint8_t alarm1_minute; + uint8_t alarm1_second; + void alarm1_getTime(); // gets the time from the RTC and stores in alarm1 variable + + // the following set alarm1's time to trigger. + + // set based on an offset of the current time in seconds + void alarm1_SetTime_Offset(uint32_t); + + // set just the minute and second, using the current date and hour + void alarm1_setTime(uint8_t m, + uint8_t s); + + // set just the hour, minute, second, using the current date + void alarm1_setTime(uint8_t h, + uint8_t m, + uint8_t s); + + // set everything + void alarm1_setTime(uint8_t d, // day of the month (date) + uint8_t h, // hour + uint8_t m, // minute + uint8_t s); // second + + // enables alarm1. Types of alarms: + // EVERY_SECOND + // MATCH_SECONDS + // MATCH_MINUTES + // MATCH_HOURS + // MATCH_DATE + void alarm1_enable(uint8_t alarmType); + + +/********** Alarm 2 Functions *********/ + +// same as alarm1 except it doesn't count seconds + + bool alarm2_state; // enabled or disabled + bool alarm2_flag; // flag indicating alarm has triggered + uint8_t alarm2_date; // day of the month + uint8_t alarm2_hour; + uint8_t alarm2_minute; + + void alarm2_getTime(); + + void alarm2_setTime(uint8_t m); + + void alarm2_setTime(uint8_t h, + uint8_t m); + + void alarm2_setTime(uint8_t d, + uint8_t h, + uint8_t m); + + // same as alarm1_enable but uses EVERY_MINUTE in place of + // EVERY_SECOND + void alarm2_enable(uint8_t alarmType); + + void alarm2_disable(); + + + // enables the squarewave on INT_SQW at a specified frequency + void squarewave(uint8_t); + + bool interrupted = false; + + // straightforward functions. + void setInterrupt(void (*userFunction)(void)); + void defaultISR(void); + void clearAlarms(); + uint8_t checkAlarmFlags(); + void clearAlarmFlags(); + +}; + + + + diff --git a/OpensSD/OpensSD.cpp b/OpensSD/OpensSD.cpp new file mode 100644 index 0000000..def4d1c --- /dev/null +++ b/OpensSD/OpensSD.cpp @@ -0,0 +1,1313 @@ +#include "OpensSD.h" + +OpensSD::OpensSD( int CS ) +{ + chipSelect = CS; + for(uint8_t i = 0; i < bufferSize; i++ ) + { + buffer[i] = 0; + } + for( uint8_t i = 0; i < bufferBSize; i++) + { + bufferB[i] = 0; + } + firstCycle = true; +} + +int8_t OpensSD::begin() +{ + // Hardware Chip Select needs to be output and high for SD chip select to work + pinMode( 8, OUTPUT ); + digitalWrite( 8, HIGH ); + SD.begin( chipSelect ); + + // check for "Data.txt" + // clear the buffer and then copy over the settings filename. + memset(buffer, 0, sizeof(buffer)); + strncpy_P(buffer, STRING_TABLE[0], sizeof( buffer ) - 1); + // if the file doesn't exist, make it + if( !SD.exists( buffer ) ) + { + File f = SD.open( buffer, FILE_WRITE ); + Serial.print("isDir: ");Serial.println( f.isDirectory() ); + Serial.print(buffer);Serial.print(" Opened: "); Serial.println( f ); + f.close(); + } + + // check for "Settings.txt" + // clear the buffer and then copy over the settings filename. + memset(buffer, 0, sizeof(buffer)); + strcpy_P(buffer, STRING_TABLE[1] ); + + // if the file doesn't exist, make it + if( !SD.exists( buffer ) ) + { + File f = SD.open( buffer, FILE_WRITE ); + Serial.print(buffer);Serial.print(" Opened: "); Serial.println( f ); + f.close(); + } + + return checkSettingsFile(); +} + + +/* +Mark New Date + +Create a date marker on the data file. This function should +be called every time the date changes. The order of day, month, and year +are determined by DATE_MARKER, defined in the header. + +Parameters: +- year: two digit year +- month: two digit month +- date: two digit date representing the day of the month (1-31) +*/ +int8_t OpensSD::markNewDate(uint8_t year, uint8_t month, uint8_t date) +{ + // clear the buffer and then copy over the settings filename. + memset(buffer, 0, sizeof(buffer)); + strcpy_P(buffer, STRING_TABLE[0] ); + + File f = SD.open( buffer, FILE_WRITE); // close requires the file object f + + if( f == false ) + { // file not opened + return 1; + } + + // clear the buffer and prepare the date string based on the format + // defined by DATE_MARKER + memset(buffer, 0, sizeof(buffer)); + switch( DATE_MARKER ) + { + case DAY_MONTH_YEAR: + // delimiters are used because this will be formatted like any other keyvalue. + // to find certain dates in the file, the DATE key can be searched for. + sprintf_P( buffer, + STRING_TABLE[5], + delimiter1, + date, month, year, + delimiter2 ); + break; + + case YEAR_MONTH_DAY: + sprintf_P( buffer, + STRING_TABLE[5], + delimiter1, + year, month, date, + delimiter2 ); + break; + + case MONTH_DAY_YEAR: + sprintf_P( buffer, + STRING_TABLE[5], + delimiter1, + month, date, year, + delimiter2 ); + break; + } + + // write the string to the file, then close it. + f.print(buffer); + f.close(); + + return 0; +} + + +/* +Check Settings File + +Scans the settings file on the SD card for keyvalue settings. Every setting +that is found is added to the local settings list. If no file exists, this function +creates one. + +Returns: +0: no settings found, file was empty or was created. +Positive Integer: number of settings added +-1: timeout reached + +*/ +int8_t OpensSD::checkSettingsFile() +{ + // only call in the main program's setup. + // copy over "Settings.txt" to the buffer + memset( buffer, 0, sizeof( buffer ) ); + strncpy_P( buffer, STRING_TABLE[1], sizeof(buffer) ); + Serial.print("[CSF] Settings.txt: ");Serial.println( buffer ); + + // if the settings file doesn't exist, create it and return 0 + // since there aren't any settings to be found. + if( !SD.exists( buffer ) ) + { + Serial.print("[CSF] buffer: "); Serial.println( buffer ); + Serial.println( "[CSF] Settings not found." ); + File f = SD.open( buffer, FILE_READ); + Serial.print("[CSF] Open settings file: "); Serial.println( f ); + f.close(); + firstCycle = true; + + #if SD_DEBUG == 1 + memset(error_buffer, 0, sizeof(error_buffer) ); + strcpy_P( error_buffer, ERROR_TBL[0] ); + Serial.println( error_buffer ); + + #endif + return 0; + } + Serial.println( "[CSF] Settings file found."); + File f = SD.open( buffer, FILE_READ ); + Serial.print( "[CSF] File Open: "); Serial.println( f ); + Serial.print( "[CSF] Size: "); Serial.println( f.size() ); + Serial.println( "[CSF] SETTINGS FILE: "); + Serial.println( "***********************************************************" ); + while( f.available() ) + { + Serial.print( uint8_t ( f.read() ) ); + Serial.print( ' ' ); + } + + Serial.println(); + Serial.println( "***********************************************************" ); + + f.seek(0); + + // create a new blank setting at the end of the list + // and keep track of the current setting with kv_ptr + OpensSD::KeyValue* kv_ptr = settings.add(); + + uint8_t maxSize = (settings.head)->max_size; + uint8_t numSettings = 0; + uint8_t index = 0; + uint32_t timeout = millis(); + while( f.available() ) // read the whole file + { + if( ( millis() - timeout ) > SD_TIMEOUT ) + { + + #if SD_DEBUG == 1 + memset(error_buffer, 0, sizeof(error_buffer) ); + strcpy_P( error_buffer, ERROR_TBL[1] ); + Serial.println( error_buffer ); + #endif + + return -1; // timeout error + } + + // read the next character + char c = f.read(); + + switch( c ) + { + case delimiter1: + + // the read char is separating a key and its value + (kv_ptr->key)[index] = 0; // terminate the string + Serial.print("[CSF] Key: ");Serial.println( kv_ptr->key ); + index = maxSize; + break; + + case delimiter2: + // the read char is marking the end of a keyvalue pair. + // Add a blank keyvalue object to the settings array, reset the + // current setting object and index, and continue. + (kv_ptr->val)[index - maxSize] = 0; + Serial.print("[CSF] Val: ");Serial.println( kv_ptr->val ); + Serial.print("Key first char: "); Serial.println((uint8_t)( kv_ptr->key[0] ) ); + if( kv_ptr->key[0] != 0 && kv_ptr->key[0] != 13 ) + { + Serial.println("[CSF] added."); + kv_ptr = settings.add(); // this will move 'last' as well + numSettings++; + } + index = 0; + break; + + default: + if( c == '\n' ) + { + break; + } + // the character is part of a setting member and the index + // will not overflow the alotted size of keyval members. + if( index < maxSize-1 ) // space for a terminator + { // key + kv_ptr->key[index] = c; + + // the next char is always a terminator + kv_ptr->key[index+1] = 0; + } + else if( maxSize < index < 2*maxSize - 1 ) // space for a terminator + { // value + kv_ptr->val[index - maxSize] = c; + + // the next char is always a terminator + kv_ptr->val[index - maxSize + 1] = 0; + } + index++; + break; + } + } + + f.close(); // close the file + + if( numSettings > 0 ) + { + firstCycle = false; + } + + #if SD_DEBUG == 1 + memset(error_buffer, 0, sizeof(error_buffer) ); + sprintf_P( error_buffer, ERROR_TBL[2], numSettings ); + Serial.println( error_buffer ); + #endif + Serial.println("Local Settings: "); + printLocalSettings(); + printSavedSettings(); + + return numSettings; // return the number of settings that were added + +} + + +/* +Add A KeyValue to a List +Possible parameters: +- void to add a blank keyvalue +- key to add a keyvalue with a key but no value +- key and val to add a keyvalue with both + +Returns: +A pointer to the last KeyValue object in the list, even if a keyvalue +matching the user defined key was found elsewhere in the list. +*/ + +OpensSD::KeyValue* OpensSD::KeyValueList::add( char* key, char* value) +{ + Serial.print("[KV ADD] Key: "); Serial.print( key ); Serial.print("\t Val: "); Serial.println( value ); + + if( head == nullptr ) + { + Serial.println("[KV ADD] Head is nullptr, creating new keyvalue.\n"); + head = new KeyValue( key, value ); + last = head; + Serial.print("[KV ADD] Head's next key: "); Serial.println( (head->next)->key ); + return last; + } + else + { + KeyValue* kv_ptr = get( key ); + if( kv_ptr == nullptr ) + { + kv_ptr = get( "" ); + if( kv_ptr == head ) + { + Serial.println("[KV ADD] Head is empty, inserting keyvalue."); + // head is empty and no matching key was found, add kv to head + memset( head->key, 0, head->max_size ); + memset( head->val, 0, head->max_size ); + strncpy( head->key, key, head->max_size ); + strncpy( head->val, value, head->max_size ); + return last; + } + else if( kv_ptr == nullptr ) + { // no blank kvs + Serial.println("[KV ADD] appending new keyvalue."); + last->next = new KeyValue( key, value ); + Serial.print("[KV Add] Previous Last Key: ");Serial.println( last->key ); + last = last->next; + if( last == nullptr ) + { + Serial.println( "last is nullptr!!"); + } + Serial.print("[KV Add] New Last Key: "); Serial.println( last->key );Serial.println(); + return last; + } + else if( kv_ptr != nullptr ) + { + Serial.println("[KV ADD] Found blank key. Overwriting."); + memset( kv_ptr->key, 0, kv_ptr->max_size ); + memset( kv_ptr->val, 0, kv_ptr->max_size ); + strncpy( kv_ptr->key, key, kv_ptr->max_size ); + strncpy( kv_ptr->val, value, kv_ptr->max_size ); + return last; + + } + } + else + { + Serial.println("[KV ADD] key already exists. Overwriting value.\n"); + memset( kv_ptr->val, 0, kv_ptr->max_size ); + strncpy( kv_ptr->val, value, kv_ptr->max_size ); + return last; + } + + } +} + +/* +Get a KeyValue from a List +Parameters: +- key string to look for +Returns: +- KeyValue pointer to object that was found +- Nullptr if not found +*/ +OpensSD::KeyValue* OpensSD::KeyValueList::get( char* key ) +{ + OpensSD::KeyValue* kv_ptr = head; + while( kv_ptr != nullptr ) + { + if( strlen( key ) == strlen(kv_ptr->key) ) + { + uint8_t minLen = min( strlen(key), kv_ptr->max_size ); + if( strncmp( key, kv_ptr->key, minLen ) == 0 ) + { + return kv_ptr; + } + } + kv_ptr = kv_ptr->next; + } + return nullptr; +} + +/* +Destructor +Usually not used since the OpensSD object should be initialized outside +the main loop and when the device turns off it clears its SRAM. This is +just here in case someone wants to modify the library to use KeyValueLists +elsewhere. +*/ + +OpensSD::KeyValueList::~KeyValueList() +{ + OpensSD::KeyValue* ptr = head; + while( ptr != nullptr ) + { + OpensSD::KeyValue* trash = ptr; + ptr = ptr->next; + delete trash; + } +} + +/* +Save Local Settings to the SD Card + +Stores the objects from the settings KeyValueList in Settings.txt +on the SD card. + +*/ + +uint8_t OpensSD::saveSettings() +{ + // clear the buffer and then copy over the settings filename. + memset(buffer, 0, sizeof(buffer)); + strcpy_P(buffer, STRING_TABLE[1] ); + + + // if the file has already been made, delete it so we can write a new one + if( SD.exists( buffer ) ) + { + SD.remove( buffer ); + } + Serial.print("[SS] Buffer: "); Serial.println( buffer ); + // make the new file and open it + File f = SD.open( buffer, FILE_WRITE ); + if( f == false ) + { + #if SD_DEBUG == 1 + memset(error_buffer, 0, sizeof(error_buffer) ); + strcpy_P( error_buffer, ERROR_TBL[6], value ); + Serial.println( error_buffer ); + #endif + + return 1; // file failed to open + } + + // cycle through all the keyvalues in the settings list and print them + // to the file, separated by delimiter1 and delimiter2. + OpensSD::KeyValue* kv_ptr = settings.head; + while( kv_ptr != nullptr ) + { + Serial.print("[SS] Adding key: "); Serial.println( kv_ptr->key ); + // write the key + f.write(kv_ptr->key, strlen( kv_ptr->key ) ); + // write the key and value separator + f.write(delimiter1); + + Serial.print("[SS] Adding Val: "); Serial.println( kv_ptr->val ); + // write the value + f.write(kv_ptr->val, strlen( kv_ptr->val ) ); + // write the KeyValue separator + f.write(delimiter2); + // get the next KeyValue object + kv_ptr = kv_ptr->next; + } + #if SD_DEBUG == 1 + memset(error_buffer, 0, sizeof(error_buffer) ); + strcpy_P( error_buffer, ERROR_TBL[7], value ); + Serial.println( error_buffer ); + #endif + f.println(); + + f.close(); // close the file + + Serial.println("[SS] FILE CONTENTS: "); + f = SD.open( buffer, FILE_READ ); + while( f.available() ) + { + Serial.print( (uint8_t) f.read() ); + Serial.print(' '); + } + Serial.println(); + + + + return 0; +} + + +OpensSD::KeyValue* OpensSD::getKVListValue( char* key, char** value, KeyValueList* kvList) +{ + // iterator + OpensSD::KeyValue* kv_ptr = kvList->head; + + // cycle through the settings list, checking the given key string + while(kv_ptr != nullptr ) + { + // in case the setting key is not terminated, check for the minimum between + // the string size and the max member size + uint8_t min_size = min( kv_ptr->max_size , strlen(key) ); + int cmp = strncmp( kv_ptr->key, key, min_size ); + + // if the setting was found, store its value as a string in the passed + // character pointer 'value' + if( cmp == 0 ) + { + + #if SD_DEBUG == 1 + memset(error_buffer, 0, sizeof(error_buffer) ); + snprintf_P( error_buffer, ERROR_TBL[8], + kv_ptr->key, + delimiter1, + kv_ptr->val, + delimiter2 ); + Serial.println( error_buffer ); + #endif + + *value = kv_ptr->val; + + return kv_ptr; + } + kv_ptr = kv_ptr->next; + } + + #if SD_DEBUG == 1 + memset( error_buffer, 0, sizeof( error_buffer ) ); + strcpy_P( error_buffer, ERROR_TBL[9] ); + Serial.println( error_buffer ); + #endif + + return nullptr; // failed +} + + +void OpensSD::addSetting( char* k) +{ + addSetting( k, "" ); +} + +void OpensSD::addSetting( char* key, char* value ) +{ + settings.add( key, value ); +} + +void OpensSD::addDataPoint( char* key, char* value ) +{ + variables.add( key, value ); +} + +uint8_t OpensSD::getSetting(char* key, char* value) +{ + char* val; + if( getKVListValue( key, &val, &settings ) == nullptr ) + { + #if SD_DEBUG == 1 + memset( error_buffer, 0, sizeof( error_buffer ) ); + strcpy_P( error_buffer, ERROR_TBL[9] ); + Serial.println( error_buffer ); + #endif + return 1; + } + strcpy( value, val ); + Serial.print("[GS] TEMPVAL: "); Serial.println( val ); + Serial.print("[GS] USERVAL: "); Serial.println( value ); + return 0; + +} + +void OpensSD::printLocalSettings() +{ + settings.print(); +} + +void OpensSD::printSavedSettings() +{ + // clear the buffer and then copy over the settings filename. + memset(buffer, 0, sizeof(buffer)); + strcpy_P(buffer, STRING_TABLE[1] ); + Serial.print("[save] buffer: "); Serial.println( buffer ); + + // make the new file and open it + File f = SD.open( buffer, FILE_READ ); + Serial.println("FILE CONTENTS:"); + + while( f.available() > 0 ) + { + Serial.print( char( f.read() ) ); + } + Serial.println(); + f.close(); + +} + +uint8_t OpensSD::saveData() +{ + // clear the buffer and then copy over the settings filename. + memset(buffer, 0, sizeof(buffer)); + strcpy_P(buffer, STRING_TABLE[0] ); + + Serial.print("[SAVEDATA] BUFFER: "); Serial.println( buffer ); + File f = SD.open( buffer, FILE_WRITE ); // close requires the file object f + + if( f == false ) + { // file not opened + #if SD_DEBUG == 1 + memset( error_buffer, 0, sizeof( error_buffer ) ); + strcpy_P( error_buffer, ERROR_TBL[6] ); + Serial.println( error_buffer ); + #endif + return 1; + } + // cycle through all the keyvalues in the settings list and print them + // to the file, separated by delimiter1 and delimiter2. + OpensSD::KeyValue* kv_ptr = variables.head; + while( kv_ptr != nullptr ) + { + f.write(kv_ptr->key, min( strlen(kv_ptr->key), kv_ptr->max_size)); + f.write(delimiter1); + f.write(kv_ptr->val, min( strlen(kv_ptr->val), kv_ptr->max_size)); + f.write(delimiter2); + + kv_ptr = kv_ptr->next; + } + f.write('\n'); + f.close(); // close the file + + Serial.println("DATA CONTENTS: "); + Serial.println("**********************************"); + f = SD.open( buffer, FILE_READ ); + while( f.available() ) + { + Serial.print( (char) f.read() ); + } + Serial.println(); + Serial.println("**********************************"); + return 0; +} + +uint8_t OpensSD::saveData( char* dataString) +{ + // clear the buffer and then copy over the settings filename. + memset(buffer, 0, sizeof(buffer)); + strcpy_P(buffer, STRING_TABLE[0] ); + + File f = SD.open( buffer, FILE_WRITE ); + + if( f == false ) + { + #if SD_DEBUG == 1 + memset( error_buffer, 0, sizeof( error_buffer ) ); + strcpy_P( error_buffer, ERROR_TBL[6] ); + Serial.println( error_buffer ); + #endif + return 1; + } + + f.print( dataString ); + f.print('\n'); + + f.close(); + return 0; +} + +/* +Get the position in the sd file of the start of the line AFTER a matching key +AND value are found. This is most useful for getting the data after a specific +timestamp, such as the OpensSD's date stamp. For getting more granular data, +getValueAfter() could be called using the index of getPosition. +*/ + + +int32_t OpensSD::getPosition( char* key, char* value , uint32_t startPosition) +{ + + Serial.print("[GP] KEY: "); Serial.println( key ); + Serial.print("[GP] VAL: "); Serial.println( value ); + // clear the buffer and then copy over the settings filename. + memset(buffer, 0, sizeof(buffer)); + strcpy_P(buffer, STRING_TABLE[0] ); + + // if the file doesn't exist, return -1 + if( !SD.exists( buffer ) ) + { + #if SD_DEBUG == 1 + memset( error_buffer, 0, sizeof( error_buffer ) ); + strcpy_P( error_buffer, ERROR_TBL[11] ); + Serial.println( error_buffer ); + #endif + return -1; + } + + File f = SD.open( buffer, FILE_READ ); + + if( f == false ) + { // if the file wasn't opened, return -2 + #if SD_DEBUG == 1 + memset( error_buffer, 0, sizeof( error_buffer ) ); + strcpy_P( error_buffer, ERROR_TBL[6] ); + Serial.println( error_buffer ); + #endif + return -2; + } + + f.seek(startPosition); + uint32_t position = startPosition; + uint32_t timeout = millis(); + Serial.println("[GP] PARSING FILE..."); + + while( f.available() ) + { + // first timeout check so we don't get stuck in while loop + if( (millis() - timeout ) > SD_TIMEOUT ) + { + #if SD_DEBUG == 1 + memset( error_buffer, 0, sizeof( error_buffer ) ); + strcpy_P( error_buffer, ERROR_TBL[1] ); + Serial.println( error_buffer ); + #endif + return 1; + } + + // read in the first character + char c = f.read(); + + + if( c == 10 || f.position() == 0 ) + { // we're only interested in the start of a key + + // comparison is used to compare the desired key and the key that is read in + bool comparison = true; + position = f.position(); + + + // since we've started a key, read until they don't compare or we've + // reached the end of the desired key + for( int i=0; i < strlen(key); i++ ) + { + // read in the next character + c = f.read(); + while( c == 10 ) + { + c = f.read(); + } + Serial.print("[GP] File Char: ");Serial.print( (uint8_t) c ); + Serial.print("\t[GP] Key Char: "); Serial.println( (uint8_t) key[i] ); + + Serial.print("[GP] pos: ");Serial.println( f.position() ); + + // if the key character doesn't compare true, set comparison to false + // and go back to checking for another KeyValue separator. + if ( ((char) key[i]) != c ) + { + Serial.println("[GP] false"); + comparison = false; + break; + } + } + Serial.println( f.read() ); // skip the delimiter between the key and value + + // if comparison is still true then continue. + if( comparison == true ) + { + if( value[0] == 0 || value[0] == 255 ) + { + return position; + } + Serial.println("[GP] true!"); + + // check if the values match. They won't match if we had querried, for example, + // "temp" but both "temp" and "temp2" exist, because we'll read in a character from + // the name or a delimiter here rather than the value string. + for( int i=0; i < strlen( value ); i++ ) + { + c = f.read(); + + Serial.print("[GP] File Char: ");Serial.print( (uint8_t) c ); + Serial.print("\t[GP] Val Char: "); Serial.println( (uint8_t) value[i] ); + + // if the values don't match, go back to checking for a KeyValue delimiter. + if( (char)(value[i]) != c ) + { + Serial.println("[GP] false"); + comparison = false; + break; + } + } + } + + if( comparison == true ) + { + Serial.println("[GP] TRUE!"); + // skip to the newline separating the datestamp from the + // start of the day's data + while( c != '\n' && + f.available() > 0) + { + c = f.read(); + } + if( c == '\n' ) + { + return position; //return the position of the day's data + } + } + } + } + + return 0; +} + +int32_t OpensSD::getAfterDate( uint8_t year, uint8_t month, uint8_t date, + bool (*userFunction)(char) ) +{ + return getBetweenDates(year, month, date, 0,0,0, userFunction); +} + +/* +Get Between Dates + +Parameters: +- year1/month1/date1: year, month, and day of the month to start +- year2/...: year, month, day of the month to end +- *userFunction: pointer to function that accepts a character and returns a boolean + +This function takes a date to start and end, then passes each individual byte from the data +file between these dates to a user defined function, userFunction. This allows each project +using this library to pass however much data they want using whatever protocol they desire. The +Smart Rock, for example, will use bluetooth to send data but also can pass data through +Serial Comms to debug. + +Example userFunction using +getBetweenDates(y1,m1,d1,y2,m2,d2, &myFunction ) + +bool myFunction(char c) +{ + if(Serial) + { + print( c ); + return true; + } + return false; +}' + +Returns: +positive integer: number of bytes that were passed to userFunction +-1: data file doesn't exist +-2: data file couldn't be opened + +negative integer < -10: -10 minus number of bytes that were passed to userfunction before it + stopped accepting bytes. + +*/ +int32_t OpensSD::getBetweenDates( uint8_t year1, uint8_t month1, uint8_t date1, + uint8_t year2, uint8_t month2, uint8_t date2, + bool (*userFunction)(char) ) +{ + memset(bufferB, 0, sizeof(bufferB)); // clear the buffer + + + // based on DATE_MARKER, we'll change the format of the date stamp we are looking for + // it's formatted as "var1:var2:var3" + switch( DATE_MARKER ) + { + default: //DAY_MONTH_YEAR + snprintf_P( bufferB, + sizeof(bufferB), + STRING_TABLE[3], + date1, month1, year1 ); + break; + + case YEAR_MONTH_DAY: + snprintf_P( bufferB, + sizeof(bufferB), + STRING_TABLE[3], + year1, month1, date1 ); + break; + + case MONTH_DAY_YEAR: + snprintf_P( bufferB, + sizeof(bufferB), + STRING_TABLE[3], + month1, date1, year1 ); + break; + } + + // get the starting index where a match between the DATE flag's value + // and the provided date is found + int32_t indexStart = getPosition( "DATE", bufferB ); + + memset(bufferB, 0, sizeof(bufferB)); // clear the buffer + + int32_t indexEnd = 0; + if( month2 != 0 && date2 != 0 ) + { + // do the same thing but for the end date, if it is provided. + // Year can be equal to 0 so we don't check that looking for + // if the user specified an end date. + switch( DATE_MARKER ) + { + default: //DAY_MONTH_YEAR + snprintf_P( bufferB, + sizeof(bufferB), + STRING_TABLE[3], + date2, month2, year2 ); + break; + + case YEAR_MONTH_DAY: + snprintf_P( bufferB, + sizeof(bufferB), + STRING_TABLE[3], + year2, month2, date2 ); + break; + + case MONTH_DAY_YEAR: + snprintf_P( bufferB, + sizeof(bufferB), + STRING_TABLE[3], + month2, date2, year2 ); + break; + } + + indexEnd = getPosition( "DATE", bufferB ); + } + + // get the data file name + memset( buffer, 0, sizeof(buffer) ); + strncpy_P( buffer, STRING_TABLE[0], sizeof(buffer) ); + + // if the file doesn't exist, return -1 + if( !SD.exists( buffer ) ) + { + return -1; + } + + File f = SD.open( buffer ); + + if( f == false ) + { // if the file wasn't opened, return -2 + return -2; + } + if( indexEnd == 0 ) + { + indexEnd = f.size(); + } + + // skip to the start date + f.seek( indexStart ); + + uint32_t timeout = millis(); + + + // if userfunction returns false, that means there is no point in passing + // more data because it doesn't want any more. + bool stopSending = false; + + // counter for number of characters we've read + uint32_t numChars = 0; + + while( f.available() && // bytes are available to read from the file + stopSending == false && // stopSending hasn't been flagged + indexEnd - f.position() > 0) // the end index hasn't been reached + { + // first timeout check so we don't get stuck in while loop + if( (millis() - timeout ) > SD_TIMEOUT ) + { + return 1; + } + + // to speed things up, only check f.position() once every hundred + // reads until we are closer to the end index + if( indexEnd - f.position() > 100 ) + { + for( int i=0; i < 100; i++ ) + { + // read a character 100 times or until userFunction returns false + if( (*userFunction)( f.read() ) == false ) + { + stopSending = true; + numChars++; // we've read a character regardless of userFunction's return + break; + } + } + } + else + { + // close to the end index. Read one byte at a time and check the return. + if( (*userFunction)( f.read() ) == false ) + { + stopSending = true; + + } + numChars++; // we've read a character regardless of userFunction's return + } + } + + + // if the userfunction ever returned false, return -numChars. + // this will return a useless value (possibly a different error code) + // if in32t overflows but would require passing 2GB of data within SD_TIMEOUT's duration. + // the -10 offset is there to allow room for more error codes. + if( stopSending == true ) + { + return ( int32_t(numChars) * -1 ) - 10; + } + + return numChars; // + +} + + +int16_t OpensSD::getBlockAfter( char* storage, uint16_t size , uint32_t index ) +{ + memset( storage, 0, size); + + if( !SD.exists( buffer ) ) + { + return -1; + } + + File f = SD.open( buffer ); + + if( f == false ) + { // if the file wasn't opened, return -2 + return -2; + } + + f.seek(index); + + uint16_t i = 0; + while( f.available() == true && + i < size ) + { + storage[i] = f.read(); + i++; + } + + f.close(); + + return i; + +} + +int16_t OpensSD::get16_After( char storage[16], uint32_t index ) +{ + return getBlockAfter( storage, 16, index ); +} + +int16_t OpensSD::get64_After( char storage[64], uint32_t index ) +{ + return getBlockAfter( storage, 64, index ); +} + +int16_t OpensSD::get128_After( char storage[128], uint32_t index ) +{ + return getBlockAfter( storage, 128, index ); +} + +int16_t OpensSD::get256_After( char storage[256], uint32_t index ) +{ + return getBlockAfter( storage, 256, index ); +} + +int16_t OpensSD::get512_After( char storage[512], uint32_t index ) +{ + return getBlockAfter( storage, 512, index ); +} + +void OpensSD::printLocalData() +{ + variables.print(); +} + +int32_t OpensSD::streamLocalData( bool (*userFunction) (char) ) +{ + return variables.streamList( userFunction ); +} + +int16_t OpensSD::streamLocalSettings( bool (*userFunction) (char) ) +{ + return settings.streamList( userFunction ); +} + + +/* +Stream A KeyValue List +Passes each character in a keyvalue list to a user defined function, checking that the +function is still accepting characters. + +Parameters: +- userFunction Pointer: pointer to user defined function that accepts a character and returns + a boolean representing its ability to accept another character. + +Returns: +Positive Integer: number of characters (numChars) that were passed to userFunction. +Negative Integer < -10: number of characters that were passed to userFunction before it returned + false, offset by -10. +*/ + +int16_t OpensSD::KeyValueList::streamList( bool (*userFunction) (char) ) +{ + OpensSD::KeyValue* kv_ptr = head; + + int32_t numChars = 0; + while( kv_ptr != nullptr ) // while there are more keyvalue objects + { + + // for each character in the keyvalue object's key string, + // pass it to userFunction. + for(uint8_t i = 0; + i < strnlen( kv_ptr->key, kv_ptr->max_size ); + i++ ) + { + + // if userFunction returns false, it has stopped accepting + // characters. Return -numCharacters offset by -10 to allow + // for error codes. + if( (*userFunction)( kv_ptr->key[i] ) == false ) + { + return -10 - numChars; + } + numChars++; + } + + // pass it the delimiter so it can understand a transition from + // key to value + if( (*userFunction)( delimiter1 ) == false ) + { + return -10 - numChars; + } + numChars++; + + // for each character in the keyvalue object's value string, + // pass it to userFunction and check that it is still accepting + // characters. + for( uint8_t i = 0; + i < strnlen( kv_ptr->val, kv_ptr->max_size ); + i++ ) + { + if( (*userFunction)( kv_ptr->val[i] ) == false ) + { + return -10 - numChars; + } + numChars++; + } + + // pass it the keyvalue delimiter + if( (*userFunction)( delimiter2 ) == false ) + { + return -10 - numChars; + } + + // increment number of characters again, then + // get the next keyvalue in the list + numChars ++; + kv_ptr = kv_ptr->next; + } + + // return the number of characters we successfully passed to the + // userFunction as a positive integer. + return numChars; +} + +uint8_t OpensSD::getSingleDate( uint8_t year, uint8_t month, uint8_t date, + bool (*userFunction) (char) ) +{ + + int32_t start = getPositionFromDate(year, month, date); + Serial.print("[GSD] START POSITION: ");Serial.println(start); + int32_t stop = getPosition( "DATE", "", start + 5 ); + + Serial.print("[GSD] STOP POSITION: ");Serial.println(stop); + + streamDataBetween( start, stop, userFunction ); + + return 0; +} + + +uint32_t OpensSD::getPositionFromDate( uint8_t year, uint8_t month, uint8_t date ) +{ + + char userDate [16] = {0}; + + switch( DATE_MARKER ) + { + case DAY_MONTH_YEAR: + // delimiters are used because this will be formatted like any other keyvalue. + // to find certain dates in the file, the DATE key can be searched for. + sprintf_P( userDate, + STRING_TABLE[2], + date, month, year); + break; + + case YEAR_MONTH_DAY: + sprintf_P( userDate, + STRING_TABLE[2], + year, month, date); + break; + + case MONTH_DAY_YEAR: + sprintf_P( userDate, + STRING_TABLE[2], + month, date, year); + break; + } + Serial.print("[GPD] Date: "); Serial.println( userDate ); + return getPosition("DATE",userDate); +} + +int32_t OpensSD::streamDataBetween( uint32_t start, uint32_t stop, + bool (*userFunction)(char) ) +{ + // get the data file name + memset( buffer, 0, sizeof(buffer) ); + strncpy_P( buffer, STRING_TABLE[0], sizeof(buffer) ); + + // if the file doesn't exist, return -1 + + File f = SD.open( buffer, FILE_READ ); + if( f == false ) + { // if the file wasn't opened, return -2 + return -2; + } + + // skip to the start date + f.seek( start ); + if( stop == 0 ) + { + stop = f.size(); + } + + uint32_t timeout = millis(); + int32_t numBytes = 0; + + while( f.available() && + f.position() < stop ) + { + if( millis() - timeout > SD_TIMEOUT ) + { + return -3; + } + + char c = f.read(); + + if( (*userFunction)( c ) == false ) + { + return -10 - numBytes; + } + numBytes ++; + } + return numBytes; +} + +void OpensSD::KeyValueList::print() +{ + OpensSD::KeyValue* kv_ptr = head; + + while( kv_ptr != nullptr && Serial) + { + Serial.print(kv_ptr->key); + Serial.print(delimiter1); + Serial.print(kv_ptr->val); + Serial.print(delimiter2); + kv_ptr = kv_ptr->next; + } + Serial.println(); +} + +void OpensSD::clearLocalSettings() +{ + + OpensSD::KeyValue* ptr = settings.head; + + while( ptr != nullptr ) + { + memset( ptr->key, 0, ptr->max_size ); + memset( ptr->val, 0, ptr->max_size ); + ptr = ptr->next; + } + if( settings.head == nullptr ) + { + settings.add(); + } + +} + +void OpensSD::clearSavedSettings() +{ + // check for "Settings.txt" + // clear the buffer and then copy over the settings filename. + memset(buffer, 0, sizeof(buffer)); + strcpy_P(buffer, STRING_TABLE[1] ); + + if( SD.exists( buffer ) ) + { + SD.remove( buffer ); + } + File f = SD.open( buffer, FILE_WRITE ); + f.close(); +} + +void OpensSD::printSavedData() +{ + // get the data file name + memset( buffer, 0, sizeof(buffer) ); + strncpy_P( buffer, STRING_TABLE[0], sizeof(buffer) ); + + File f = SD.open( buffer, FILE_READ ); + f.seek( 0 ); + + if( f == false ) + { + return; + } + + Serial.println("DATA FILE: "); + Serial.println("****************************"); + + while( f.available() ) + { + Serial.print( (char) f.read() ); + } + Serial.println(); + + f.close(); + +} + + + + + + diff --git a/OpensSD/OpensSD.h b/OpensSD/OpensSD.h new file mode 100644 index 0000000..9b84a22 --- /dev/null +++ b/OpensSD/OpensSD.h @@ -0,0 +1,197 @@ +#pragma once +#include +/* +NOT RECOMMENDED FOR USE WITH ARDUINO UNO +This library is large and it can very quickly fill up the SRAM of +the microcontroller. +*/ +#include +#include + +#define DATE_MARKER DAY_MONTH_YEAR +#define DAY_MONTH_YEAR 0 +#define YEAR_MONTH_DAY 1 +#define MONTH_DAY_YEAR 2 + +#define SD_TIMEOUT 30000 +// filename stuff +// prefix goes before the date of the file. +const char FILENAME_DATA [] PROGMEM = "Data.txt"; +const char FILENAME_SETTINGS [] PROGMEM = "Settings.txt"; +const char DATE_STAMP [] PROGMEM = "%.2u:%.2u:%.2u"; +const char DATE_FORMAT [] PROGMEM = "%.2u:%.2u:%.2u"; +const char KV_FORMAT [] PROGMEM = "%S%c%S%c"; +const char DATE_PRINT [] PROGMEM = "\nDATE%c%.2u:%.2u:%.2u%c\n"; + +// storing strings that should never change. These include filenames, +// error messages, and command strings. +const char* const STRING_TABLE [] PROGMEM = +{ + FILENAME_DATA, // 0 + FILENAME_SETTINGS, // 1 + DATE_STAMP, // 2 + DATE_FORMAT, // 3 + KV_FORMAT, // 4 + DATE_PRINT // 5 +}; + +/******************************************** + * ERROR MESSAGE TABLE + ********************************************/ +// OpensSD error messages +const char ERROR_MSG_0 [] PROGMEM = "FIRST CYCLE -- SETTINGS FILE NOT FOUND."; +const char ERROR_MSG_1 [] PROGMEM = "SD TIMEOUT."; +const char ERROR_MSG_2 [] PROGMEM = "%u SETTINGS FOUND."; +const char ERROR_MSG_3 [] PROGMEM = "KEY FOUND. REPLACING VALUE WITH %S."; +const char ERROR_MSG_4 [] PROGMEM = "EMPTY LIST. CREATING NEW KEYVALUE."; +const char ERROR_MSG_5 [] PROGMEM = "KEYVALUE NOT FOUND. CREATING NEW KEYVALUE."; +const char ERROR_MSG_6 [] PROGMEM = "FAILED TO OPEN FILE."; +const char ERROR_MSG_7 [] PROGMEM = "OK"; +const char ERROR_MSG_8 [] PROGMEM = "FOUND:\t%S%c%S%c"; +const char ERROR_MSG_9 [] PROGMEM = "KEY NOT FOUND."; +const char ERROR_MSG_10 [] PROGMEM = "KEY ALREADY EXISTS."; +const char ERROR_MSG_11 [] PROGMEM = "FILE DOESN'T EXIST."; + + + + + + +class OpensSD +{ +private: + + /* + + KEYVALUE STRUCTURE + It is a separate member from the keyvaluelist class so that + other functions can use keyvalue objects. + + */ + + struct KeyValue + { + const static uint8_t max_size = 16; + char key [max_size] = {0}; + char val [max_size] = {0}; + KeyValue* next; + + KeyValue( char* k, char* v, KeyValue* nextKV = nullptr ) + { + memset(key, 0, max_size); + memset(val, 0, max_size); + strncpy( key, k, max_size -1 ); + strncpy( val, v, max_size -1 ); + next = nextKV; + } + }; + + + /* + KEYVALUELIST CLASS + + Allows simple manipulation and storage of keyvalues in a linked list. + Keyvalue lists use dynamic memory allocation. While this can be dangerous + for small amounts of memory, most of the microcontrollers we use now have + plenty of space for this. + */ + + class KeyValueList + { + public: + KeyValue* head; + KeyValue* last; + KeyValueList(){ head = nullptr; last = head;} + ~KeyValueList(); + + void clear(); + KeyValue* add( char* key = "", char* value = "" ); + KeyValue* get( char* ); + int16_t streamList( bool (*userFunction)(char)); + void print(); + }; + + KeyValueList variables; // place to store current data + KeyValueList settings; // place to store current settings + + int chipSelect; // chip select pin for the SD card + static const char delimiter1 = '='; // char that separates keys and values + static const char delimiter2 = ';'; // char that separates keyvalues + + + // Functions only to be used internally + uint8_t addSetting(KeyValue*); // add a setting by passing a keyvalue object + int32_t getPosition(char*, char*, uint32_t startPosition = 0);// get the position of a key and matching value + uint32_t getPositionFromDate( uint8_t, uint8_t, uint8_t); + int16_t getBlockAfter( char*, uint16_t, uint32_t ); // get a number of bytes after a point in the file + + // Finds a key in the given list, stores its value, and returns a pointer to the whole keyvalue + KeyValue* getKVListValue( char*, char**, KeyValueList* ); + + // to keep it clear that super functions are called from the base class that + // OpensSD inherits from. + + static const uint8_t bufferSize = 64; + static const uint8_t bufferBSize = 32; + + char error_buffer [bufferSize]; + +public: + + // marks whether this is the first cycle or not based on a check. + // this is public so that user functions can make use of this + // fact. + bool firstCycle; + + // a character array to store whatever the current function needs to. + // This is usually used for messages, filenames, and frequently used formats. + char buffer [bufferSize]; + + // anonther, smaller buffer. + char bufferB [bufferBSize]; + + OpensSD( int chipSelect ); // constructor + + // initialize the sd, check for settings, make setting/datafiles if necessary, + // and update the firstCycle flag + int8_t begin(); + + int8_t markNewDate(uint8_t, uint8_t, uint8_t); // marks a new date if one isn't found. + + void addDataPoint(char*, char*); + uint8_t saveData(char*); // all one string + uint8_t saveData(); + int32_t streamLocalData( bool(*userFunction)(char)); + uint8_t getAllAfterDate(uint8_t year, uint8_t month, uint8_t date); + int32_t getBetweenDates(uint8_t year1, uint8_t month1, uint8_t date1, + uint8_t year2, uint8_t month2, uint8_t date2, + bool (*userFunction) (char) ); + uint8_t getSingleDate(uint8_t year, uint8_t month, uint8_t date, + bool (*userFunction) (char) ); + int32_t streamDataBetween( uint32_t start, uint32_t stop, bool (*userFunction)(char)); + int32_t getAfterDate(uint8_t, uint8_t, uint8_t, bool (*userFunction)(char)); + void printLocalData(); + void printSavedData(); + + void clearLocalData(); + + int16_t get16_After( char [16], uint32_t); + int16_t get64_After( char [64], uint32_t); + int16_t get128_After( char [128], uint32_t); + int16_t get256_After( char [256], uint32_t); + int16_t get512_After( char [512], uint32_t); + + uint8_t saveSettings(); + int8_t checkSettingsFile(); + uint8_t getSetting(char*, char*); + void addSetting(char*); + void addSetting(char*, char*); + int16_t streamLocalSettings( bool(*userFunction)(char)); + void clearLocalSettings(); + void printLocalSettings(); + void printSavedSettings(); + void clearSavedSettings(); + +}; + + diff --git a/SmartRock3.ino b/SmartRock3.ino new file mode 100644 index 0000000..18b4347 --- /dev/null +++ b/SmartRock3.ino @@ -0,0 +1,234 @@ +#include "header.h" + +MS5803 sensor( 0x77 ); +OpensSD mySD( 10 ); +OpensPower myPower( 6 ); + +uint8_t ble_broadcast_start = 0; +uint8_t ble_broadcast_end = 24; +uint8_t ble_broadcast_time = 10; +uint8_t lastDate = 0; +uint16_t intervalTime = 60; +bool ble_broadcast = false; +char tempSettingKey [16] = {0}; + +void setup() { + + // begin serial at 57600 BAUD rate + Serial.begin(57600); + + // delay to give time to open serial window and to program + delay(4000); + + // turn on the onboard LED + pinMode(13, OUTPUT); + digitalWrite(13, HIGH); + + // GET SETTINGS from SD File, initialing the SD class + int8_t numSettings = mySD.begin(); // checks for settings + + // print out the number of settings followed by a list of the settings + Serial.print("numSettings: ");Serial.println( numSettings ); + mySD.printLocalSettings(); + + // if mySD setup found valid settings in the file, transfer the settings to their global + // variables so that they are usable + if( numSettings > 0 ) + { + // get the broadcasting starting hour + memset( tempSettingKey, 0, sizeof( tempSettingKey ) ); + mySD.getSetting( "BC_START", tempSettingKey ); + Serial.print("BC_START: ");Serial.print(tempSettingKey);Serial.println(); + // even if the string is invalid (corrupt data?), the start time + // can be 0 so we won't check if it is a valid number. + ble_broadcast_start = String( tempSettingKey ).toInt(); + + + // get the broadcasting ending hour + memset( tempSettingKey, 0, sizeof( tempSettingKey ) ); + mySD.getSetting( "BC_END", tempSettingKey ); + Serial.print("BC_END: ");Serial.print(tempSettingKey);Serial.println(); + // this one we have to check if it isn't 0 + if( String( tempSettingKey ).toInt() != 0 ) + { + ble_broadcast_end = String( tempSettingKey ).toInt(); + } + + + // get the broadcasting duration + memset( tempSettingKey, 0, sizeof( tempSettingKey ) ); + mySD.getSetting( "BC_TIME", tempSettingKey ); + if( String( tempSettingKey ).toInt() != 0 ) + { + ble_broadcast_time = String( tempSettingKey ).toInt(); + } + + + // get the sleep time duration + memset( tempSettingKey, 0, sizeof( tempSettingKey ) ); + mySD.getSetting( "SLEEP_TIME", tempSettingKey ); + if( String( tempSettingKey ).toInt() != 0 ) + { + intervalTime = String( tempSettingKey ).toInt(); + } + + // get the previously recorded day of the month + memset( tempSettingKey, 0, sizeof( tempSettingKey ) ); + mySD.getSetting( "LASTDATE", tempSettingKey ); + + Serial.println("----------------------------------------------------------------"); + if( String( tempSettingKey ).toInt() != 0 ) + { + lastDate = String( tempSettingKey ).toInt(); + Serial.print("[MAIN] LAST DATE: ");Serial.println( lastDate ); + } + else + { + Serial.println("[MAIN] Last Date String: "); + for( int i=0; i < sizeof(tempSettingKey); i++) + { + Serial.print( (uint8_t) tempSettingKey[i] ); + Serial.print(' '); + } + Serial.println(); + } + + } + mySD.printLocalSettings(); + + myPower.begin(); // inits RTC, gets RTC settings + if( lastDate != myPower.date || mySD.firstCycle ) + { + Serial.println("[MAIN] MARKING NEW DATE"); + mySD.markNewDate( myPower.year, myPower.month, myPower.date ); + lastDate = myPower.date; + } + Serial.println("&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&"); + Serial.print("[MAIN] HOUR: ");Serial.println( myPower.hour ); + if( myPower.hour >= ble_broadcast_start && myPower.hour < ble_broadcast_end ) + { + Serial.println( ble_broadcast_start); Serial.println(ble_broadcast_end ); + ble_Setup(); + ble_broadcast = true; + } + sensor.begin(); +} + +void loop() { + char sensorBuffer [16] = {0}; + + // measure sensor + sensor.measure(); + + // update sensor data on mySD Data list + String( sensor.getTemperature() ).toCharArray( sensorBuffer, sizeof( sensorBuffer ) ); + mySD.addDataPoint( "Temperature", sensorBuffer ); + memset( sensorBuffer, 0, sizeof( sensorBuffer ) ); + String( sensor.getPressure() ).toCharArray( sensorBuffer, sizeof( sensorBuffer ) ); + mySD.addDataPoint( "Pressure", sensorBuffer ); + + uint16_t timestamp_seconds = myPower.hour*3600 + myPower.minute*60 + myPower.second; + memset( sensorBuffer, 0, sizeof( sensorBuffer ) ); + String( timestamp_seconds ).toCharArray( sensorBuffer, sizeof( sensorBuffer ) ); + mySD.addDataPoint( "Time", sensorBuffer ); + + // save data to the SD + mySD.saveData(); + + // print data to serial + mySD.printLocalData(); + mySD.clearLocalSettings(); + mySD.printLocalSettings(); + + memset(tempSettingKey, 0, sizeof(tempSettingKey) ); + String( ble_broadcast_start ).toCharArray( tempSettingKey, sizeof(tempSettingKey) ); + mySD.addSetting( "BC_START", tempSettingKey ); + + memset(tempSettingKey, 0, sizeof(tempSettingKey) ); + String( ble_broadcast_end ).toCharArray( tempSettingKey, sizeof(tempSettingKey) ); + mySD.addSetting( "BC_END", tempSettingKey ); + + memset(tempSettingKey, 0, sizeof(tempSettingKey) ); + String( ble_broadcast_time ).toCharArray( tempSettingKey, sizeof(tempSettingKey) ); + mySD.addSetting( "BC_TIME", tempSettingKey ); + + memset(tempSettingKey, 0, sizeof(tempSettingKey) ); + String( intervalTime ).toCharArray( tempSettingKey, sizeof(tempSettingKey) ); + mySD.addSetting( "SLEEP_TIME", tempSettingKey ); + + memset( tempSettingKey, 0, sizeof(tempSettingKey) ); + String( lastDate ).toCharArray( tempSettingKey, sizeof(tempSettingKey) ); + mySD.addSetting( "LASTDATE", tempSettingKey ); + + // if bluetooth is not connected, try to connect for 10 seconds. + if( !ble.isConnected() && ble_broadcast == true ) + { + Serial.println("Connecting ble..."); + Serial.print("broadcast time: ");Serial.println( ble_broadcast_time ); + ble_Connect( ble_broadcast_time * 1000 ); + + if( ble.isConnected() ) + { + delay(5000); + ble.println("Connected!"); + } + } + + while( ble.isConnected() && ble_broadcast == true ) + { + Serial.println("getting command..."); + int8_t cmd = ble_GetCommand( 30000 ); + if( cmd == -1 ) + { + ble_broadcast = false; + } + else + { + Serial.println(cmd); + ble_ParseCommand( cmd ); + } + } + + // shut down + myPower.alarm1_SetTime_Offset( intervalTime ); + myPower.alarm1_getTime(); + + if( ble.isConnected() && ble_broadcast == true ) + { + ble.println("Alarm Time:"); + ble.print(myPower.alarm1_date);ble.print('-'); + ble.print(myPower.alarm1_hour);ble.print(':'); + ble.print(myPower.alarm1_minute);ble.print(':'); + ble.print(myPower.alarm1_second); + delay(5000); + } + + Serial.println(F("Going to sleep.\n\n")); + + + mySD.saveSettings(); + mySD.printLocalSettings(); + mySD.printSavedSettings(); + mySD.printSavedData(); + + myPower.powerDown(); +} + +bool printData( char c ) +{ + if( Serial ) + { + Serial.print( c ); + return true; + } + return false; +} + +void debug_msg ( char* msg ) +{ + #if DEBUG_MODE == 1 || DEBUG_MODE == 3 + Serial.println( msg ); + #elif DEBUG_MODE == 2 || DEBUG_MODE == 3 + ble.println( msg ); + #endif +} diff --git a/bluetooth2.ino b/bluetooth2.ino new file mode 100644 index 0000000..771f039 --- /dev/null +++ b/bluetooth2.ino @@ -0,0 +1,519 @@ + +#include "header.h" + +Adafruit_BluefruitLE_SPI ble(BLUEFRUIT_SPI_CS, BLUEFRUIT_SPI_IRQ, BLUEFRUIT_SPI_RST); + +/*------------------------------------------------------------------------------------- + +FUNCTIONS + +-------------------------------------------------------------------------------------*/ + +/* +SETUP + +Begins ble communications. +*/ + +void ble_Setup(){ + + ble.begin(); + ble.setMode(BLUEFRUIT_MODE_DATA); // set to simple UART back and forth comms +} + + +/* + +Connect + +Checks connection to a ble device, if not connected then tries to connect until a timeout +is reached. + +*/ + +void ble_Connect(uint16_t connect_timeout) +{ + uint32_t timeout = millis(); + + Serial.print("[BLE_C] timeout: "); Serial.println( timeout ); + + while( !ble.isConnected() ) + { + if( ( millis() - timeout ) > connect_timeout ) + { + return; + } + } +} + + +/* +Stream Data + +Write data between dates to the ble window. + +*/ + +uint8_t ble_StreamData( uint8_t year1 = 0, uint8_t month1 = 0, uint8_t day1 = 0, + uint8_t year2 = 0, uint8_t month2 = 0, uint8_t day2 = 0) +{ + + if( !ble.isConnected()) + { + ble_Connect(BLE_TIMEOUT); + } + ble.setMode(BLUEFRUIT_MODE_DATA); + + + + mySD.getBetweenDates( year1, month1, day1, + year2, month2, day2, + &ble_sendByte ); +} + + +/* +Send Byte + +Sends a byte to the ble window. This is used for any SD function that requires a function to stream +data to. + +*/ + +bool ble_sendByte( char c ) +{ + if( ble.isConnected() ) + { + ble.write( c ); + return true; + } + return false; + +} + +/* +Stream Data From Day + +Sends all data from just one day, specified by the user, to the ble window. + +*/ + +int16_t ble_StreamDataFromDay( uint8_t year1, uint8_t month1, uint8_t day1 ) +{ + int16_t retVal = mySD.getSingleDate( year1, month1, day1, + &ble_sendByte ); + return retVal; +} + + +/* +Get Command + +Gets a command from the ble interface, or times out. This is used in combination with +parseCommand if the return value is valid. + +Returns: +-1: user never wrote a command, timed out +-2: user entered an invalid command +0 or positive integer: index in COMMAND_TABLE that matches the user-entered command + +*/ + +int8_t ble_GetCommand( uint16_t max_timeout ) +{ + ble_PrintCommands(); + ble.println("Enter Command."); + char cmd_buffer [32] = {0}; + uint32_t timeout = millis(); + + while( !ble.available()) + { + if( millis() - timeout > max_timeout ) + { + ble.println("timed out"); + return -1; + } + } + + uint8_t index = 0; + + timeout = millis(); + while( ble.available() && index < sizeof(cmd_buffer) - 1) + { + cmd_buffer[index] = ble.read(); + index++; + } + cmd_buffer[index] = 0; + ble.println( cmd_buffer ); + + for( int i = 0; i< NUMCOMMANDS; i++) + { + int cmp = 0; + cmp = strncmp_P( cmd_buffer, (char*)pgm_read_word( &(COMMAND_TABLE[i]) ), + strlen(cmd_buffer) - 1 ); + if( cmp == 0 ) + { + if( i == CMD_QUIT2 ) + { + i = CMD_QUIT; + } + return i; + } + + } + delay(5000); + return -2; +} + + +/* +Parse Command + +Takes an integer command and performs an action based on which index it matches in +the COMMAND_TABLE array. + +*/ + +void ble_ParseCommand( uint8_t commandIndex) +{ + char msg_buffer [32]; + + ble.println( commandIndex ); + uint8_t userDate [3] = {0}; + uint32_t fileIndex = 0; + + uint8_t buff_idx = 0; + char userKey_buffer[16] = {0}; + char userVal_buffer[16] = {0}; + char lgBuffer[32] = {0}; + uint8_t userTime [6] = {0}; + char shortBuffer [3] = {0}; + bool failed = false; + + switch( commandIndex ) + { + case CMD_GETDAY: + + if( ble_GetUserDate(userDate) == false) + { + ble.println("invalid date."); + delay(5000); + break; + } + mySD.getSingleDate( userDate[0], userDate[1], userDate[2], &ble_sendByte ); + break; + + case CMD_GETBETWEEN: + ble.print("Enter Start Date."); + if( ble_GetUserDate(userDate) == false) + { + ble.println("invalid date."); + break; + } + else + { + ble.println("Start Date (y-m-d):"); + ble.print(userDate[0]); ble.print(" - "); + ble.print(userDate[1]); ble.print(" - "); + ble.print(userDate[2]); + } + + memset(userDate, 0, sizeof(userDate)); + ble.println("Enter End Date."); + if( ble_GetUserDate(userDate) == false) + { + ble.println("invalid date."); + break; + } + else + { + ble.println("End Date (y-m-d): "); + ble.print(userDate[0]); ble.print(" - "); + ble.print(userDate[1]); ble.print(" - "); + ble.print(userDate[2]); + } + break; + + case CMD_MEASURE: + ble.print("Measuring pressure and temperature..."); + sensor.measure(); + memset( userVal_buffer, 0, sizeof( userVal_buffer ) ); + String( sensor.getTemperature() ).toCharArray( userVal_buffer, 16 ); + mySD.addDataPoint( "Temperature", userVal_buffer ); + + memset( userVal_buffer, 0, sizeof( userVal_buffer ) ); + String( sensor.getPressure() ).toCharArray( userVal_buffer, 16 ); + mySD.addDataPoint( "Pressure", userVal_buffer ); + + mySD.streamLocalData( &ble_sendByte ); + break; + + case CMD_SETTINGS: + mySD.streamLocalSettings( &ble_sendByte ); + ble.println("Enter Mode (0=nothing, 1=write)."); + if ( ble_WaitForBytes( 1, BLE_TIMEOUT) < 1 ) + { + ble.println("Invalid command or timeout."); + break; + } + switch( ble.read() ) + { + case '0': + ble.flush(); + break; + + case '1': + ble.flush(); + + ble.println("Enter Key:"); + ble.print("Key:\t"); + if( ble_WaitForBytes(1, BLE_TIMEOUT) >= 1 ) + { + buff_idx = 0; + + char userChar = ble.read(); + + while( ble.available() && buff_idx < 15 && userChar != 13 ) + { + userKey_buffer[buff_idx] = userChar; + buff_idx++; + userChar = ble.read(); + } + for( int i=0; i< buff_idx; i++ ) + { + ble.print( uint8_t (userKey_buffer[i] ) ); + } + ble.println("----"); + userKey_buffer[buff_idx] = 0; + } + + ble.println("Enter Value:"); + if( ble_WaitForBytes(1, BLE_TIMEOUT) >= 1 ) + { + buff_idx = 0; + char userChar = ble.read(); + while( ble.available() && buff_idx < 15 && userChar != 13) + { + userVal_buffer[buff_idx] = userChar; + buff_idx++; + userChar = ble.read(); + } + userVal_buffer[buff_idx] = 0; + } + + mySD.addSetting(userKey_buffer, userVal_buffer); + mySD.streamLocalSettings( &ble_sendByte ); + break; + } + break; + + case CMD_TIME: + ble.print("Current Time (yy:mm:dd:hh:mm:ss): "); + snprintf(lgBuffer, 32, "%.2u:%.2u:%.2u:%.2u:%.2u:%.2u", + myPower.year, + myPower.month, + myPower.date, + myPower.hour, + myPower.minute, + myPower.second ); + + ble.println(lgBuffer); + ble.print("Change time? (Y/N):"); + ble.flush(); + if( ble_WaitForBytes(1, BLE_TIMEOUT) >= 1 ) + { + if( toupper( (char) ble.read() ) == 'Y' ) + { + memset( msg_buffer, 0, sizeof(msg_buffer) ); + strcpy_P( msg_buffer, (char*) pgm_read_word( &( MSG_TABLE[6] ) ) ); + ble.print( msg_buffer ); + + for(uint8_t i = 0; i < 6; i++ ) + { + memset( msg_buffer, 0, sizeof(msg_buffer) ); + strcpy( msg_buffer, (char*) pgm_read_word( &( MSG_TABLE[i] ) ) ); + ble.print( msg_buffer ); + + if( ble_WaitForBytes(3, BLE_TIMEOUT) >= 3 ) + { + shortBuffer[0] = ble.read(); + shortBuffer[1] = ble.read(); + shortBuffer[2] = 0; + if( isDigit(shortBuffer[0]) && isDigit(shortBuffer[1]) ) + { + userTime[i] = String( shortBuffer ).toInt(); + ble.println( userTime[i] ); + } + else + { + failed = true; + ble.println("failed."); + delay(5000); + break; + } + } + + } + + if( failed == false ) + { + myPower.setTime( userTime[0], userTime[1], userTime[2], userTime[3], userTime[4], userTime[5] ); + ble.println("New Time Set."); + memset( lgBuffer, 0, sizeof(lgBuffer) ); + snprintf(lgBuffer, 32, "%.2u:%.2u:%.2u:%.2u:%.2u:%.2u", + myPower.year, + myPower.month, + myPower.date, + myPower.hour, + myPower.minute, + myPower.second ); + ble.println(lgBuffer); + delay(5000); + } + } + else + { + ble.print("No more actions."); + delay(5000); + } + } + else + { + ble.print("invalid."); + delay(5000); + } + break; + + case -2: + ble.println("invalid command."); + break; + + case CMD_QUIT: + ble.println("quitting..."); + delay(2000); + ble_broadcast = false; + break; + + case CMD_DEFAULT: + mySD.clearSavedSettings(); + break; + + case CMD_ALL_DATA: + mySD.streamDataBetween( 0, 10000, &ble_sendByte ); + ble.println(); + break; + } +} + +/* +Wait For Bytes + +Simple macro that waits until the user enters the correct number of bytes, or it times out. + +Returns: +0: timeout or user entered too few bytes +Available bytes: number of bytes user entered if greater than or equal to the requested + amount. + +*/ + +uint8_t ble_WaitForBytes( uint8_t numBytes, uint16_t max_timeout ) +{ + uint32_t timeout = millis(); + + while( !ble.available() ) + { + if( millis() - timeout > max_timeout ) + { + return 0; + } + } + if( ble.available() < numBytes ) + { + return 0; + } + return ble.available(); +} + +/* +GetUserDate + +Requests a date from the user and stores it in a size-3 uint8_t array "date", in the +form of year, month, date. + +Returns: +true: a valid date was stored in the array +false: an invalid date was entered or the request timed out + +*/ + +bool ble_GetUserDate( uint8_t date [3]) +{ + char year1 [3] = {0}; + char month1 [3] = {0}; + char day1 [3] = {0}; + + ble.print("Enter two-digit year: "); + + if( !ble_WaitForBytes( 2, BLE_TIMEOUT ) ) + { + return false; + } + + year1[0] = ble.read(); + year1[1] = ble.read(); + year1[2] = 0; + + ble.print("Enter two-digit month: "); + + if( !ble_WaitForBytes( 2, BLE_TIMEOUT ) ) + { + return false; + } + + month1[0] = ble.read(); + month1[1] = ble.read(); + month1[2] = 0; + + ble.println(month1); + + ble.print("Enter two-digit day: "); + + if( !ble_WaitForBytes( 2, BLE_TIMEOUT ) ) + { + return false; + } + + day1[0] = ble.read(); + day1[1] = ble.read(); + day1[2] = 0; + + date[0] = String(year1).toInt(); + date[1] = String(month1).toInt(); + date[2] = String(day1).toInt(); + + if( date[1] == 0 || date[2] == 0 ) + { + return false; + } + + return true; +} + +void ble_PrintLocalData() +{ + mySD.streamLocalData( &ble_sendByte ); +} + +void ble_PrintCommands() +{ + ble.println("POSSIBLE COMMANDS:"); + char tempMessage [32] = {0}; + for( uint8_t i = 0; i < NUMCOMMANDS; i++ ) + { + memset( tempMessage, 0, sizeof( tempMessage ) ); + strcpy_P( tempMessage, COMMAND_TABLE[i] ); + ble.print(i);ble.print(": ");ble.println( tempMessage ); + } + ble.println(); +} diff --git a/header.h b/header.h new file mode 100644 index 0000000..43d201b --- /dev/null +++ b/header.h @@ -0,0 +1,123 @@ +#pragma once + +#include +#include +#include +#include +#include + +void ble_Setup(); +void ble_Connect(uint16_t connect_timeout); +uint8_t ble_ReceiveCommand(); +void ble_PrintHelp(); +void ble_streamData( uint8_t, uint8_t, uint8_t, uint8_t, uint8_t ); +bool ble_sendByte( char ); +void ble_PrintLocalData(); +bool ble_GetUserDate(uint8_t []); +int8_t ble_GetCommand( uint16_t ); +void ble_ParseCommand( uint8_t ); +void ble_PrintCommands(); + +bool printData( char ); +void debug_msg( char* ); + +extern Adafruit_BluefruitLE_SPI ble; +extern MS5803 sensor; +extern OpensSD mySD; +extern OpensPower myPower; + + +/*------------------------------------------------------------------------------------- + +Constant Settings and Definitions + +-------------------------------------------------------------------------------------*/ + +// Debug Mode: +// 0 = no printout +// 1 = Serial printout +// 2 = BLE printout +// 3 = both +#define DEBUG_MODE 1 + +const char COMPILE_DATE [] PROGMEM = __DATE__ " " __TIME__; + +// command strings + +const char COMMAND0 [] PROGMEM = "GETDAY"; +const char COMMAND1 [] PROGMEM = "GETBETWEEN"; +const char COMMAND2 [] PROGMEM = "MEASURE"; +const char COMMAND3 [] PROGMEM = "SETTINGS"; +const char COMMAND4 [] PROGMEM = "TIME"; +const char COMMAND5 [] PROGMEM = "ALL_DATA"; +const char COMMAND6 [] PROGMEM = "DEFAULT"; +const char COMMAND7 [] PROGMEM = "Q"; +const char COMMAND8 [] PROGMEM = "q"; + +#define NUMCOMMANDS 9 + +// command indices +#define CMD_GETDAY 0 +#define CMD_GETBETWEEN 1 +#define CMD_MEASURE 2 +#define CMD_SETTINGS 3 +#define CMD_TIME 4 +#define CMD_ALL_DATA 5 +#define CMD_DEFAULT 6 +#define CMD_QUIT 7 +#define CMD_QUIT2 8 + +// command table +const char* const COMMAND_TABLE [] PROGMEM = +{ + COMMAND0, + COMMAND1, + COMMAND2, + COMMAND3, + COMMAND4, + COMMAND5, + COMMAND6, + COMMAND7, + COMMAND8 +}; + +/******************************************** + * MESSAGE TABLE + ********************************************/ + +const char MESSAGE0 [] PROGMEM = "ENTER YEAR: "; +const char MESSAGE1 [] PROGMEM = "ENTER MONTH: "; +const char MESSAGE2 [] PROGMEM = "ENTER DAY OF MONTH: "; +const char MESSAGE3 [] PROGMEM = "ENTER HOUR: "; +const char MESSAGE4 [] PROGMEM = "ENTER MINUTE: "; +const char MESSAGE5 [] PROGMEM = "ENTER SECOND: "; +const char MESSAGE6 [] PROGMEM = "ENTERED VALUES SHOULD BE TWO DIGITS ('01', '11','99').\n"; + +const char* const MSG_TABLE [] PROGMEM = +{ + MESSAGE0, + MESSAGE1, + MESSAGE2, + MESSAGE3, + MESSAGE4, + MESSAGE5, + MESSAGE6 +}; + +// SETTINGS: + +extern uint16_t intervalTime; // time between wakeups in seconds +extern uint8_t ble_waitConnect_time; // time to wait for ble to connect before shutting off during broadcasting hours +extern uint8_t ble_broadcast_start; // hour of the day the ble device starts broadcasting every sample for waitConnect_time +extern uint8_t ble_broadcast_end; // hour of the day the ble device ends broadcasting every sample for waitConnect_time +extern bool ble_broadcast; + + +// BLE pins +#define BLE_TIMEOUT 30000 +#define BLUEFRUIT_SPI_CS 8 +#define BLUEFRUIT_SPI_IRQ 7 +#define BLUEFRUIT_SPI_RST 4 + +// SD CONSTANTS +#define SD_CHIPSELECT 10