diff --git a/README.md b/README.md index 2f61785..ec7bee9 100644 --- a/README.md +++ b/README.md @@ -24,6 +24,17 @@ At this point, you should be ready to compile and upload Clyde's firmware. Conne If you want to make modifications to the bootloader, please refer to the information found here: https://github.com/fabule/Clyde/tree/master/software/arduino/bootloaders/caterina +Configurations +-------------- + +### Using `TimeAlarms` library to enable alarms + +The `Time` and `TimeAlarms` libraries provide functionality to set/get the time on an arduino and enable repeated or single alarms. An example configuration to use Clyde as an *alarm clock* is given in `/software/arduino/libraries/Clyde/Examples/ClydeTimeAlarm.ino`. This firmware can be uploaded instead of the `ClydeOriginal` firmware above by loading it under "File > Examples > Clyde > ClideTimeAlarms" and following the steps described above to compile and upload the firmware. The essential configuration steps for this alternative firmware are to set the time and to define an alarm. Without any additional hardware, the time has to be set manually in and the firmware has to be uploaded to Clyde each time Clyde is detached from the power source. In principle, the `Time` and `TimeAlarms` would allow to synchronize the arduino time e.g. with internet time over a Wifi shield. + +### Using Clyde without the eye + +The eye of some Clydes can become quite buggy after some time. Thus, the preprocessor variable `ENABLE_EYE` has been included, that, if not defined allows to switch the lights by touching one of the legs of Clyde (requires the TouchyFeely module). The `ENABLE_EYE` can be set in the `Clyde.h` file, the leg that is to be used as a switch can be defined with the `LEG_SWITCH` variable in `ClydeTouchyFeely.cpp`. + Credits ------- Clyde is an open source project manage by Fabule Fabrications in Montréal, Québec, Canada. diff --git a/software/arduino/libraries/Clyde/Clyde.cpp b/software/arduino/libraries/Clyde/Clyde.cpp index 563399e..290e17a 100644 --- a/software/arduino/libraries/Clyde/Clyde.cpp +++ b/software/arduino/libraries/Clyde/Clyde.cpp @@ -34,7 +34,9 @@ CClyde Clyde; const float CClyde::CAmbientLight::SCALE_CONSTRAINT = 225.0f / 255.0f; +#ifdef ENABLE_MOUTH SoftwareSerial CClyde::CMouth::mp3(CClyde::CMouth::RX_PIN, CClyde::CMouth::TX_PIN); +#endif CClyde::CClyde() { //init modules @@ -63,7 +65,8 @@ CClyde::CClyde() { //TODO look for a better solution m_white.pin = 11; setWhite(254); - + +#ifdef ENABLE_EYE //init eye m_eye.pin = 0; m_eye.onceCalibrated = false; @@ -84,7 +87,7 @@ CClyde::CClyde() { #ifdef CLYDE_DEBUG m_eye.restartCount = 0; #endif - +#endif //init ambient cycle m_cycle.type = OFF; m_cycle.numSteps = 0; @@ -96,9 +99,11 @@ CClyde::CClyde() { memset((void*)&m_cycle.intervals[0], 0, sizeof(uint32_t)*CAmbientCycle::MAX_CYCLE_LENGTH); m_cycle.loop = NO_LOOP; +#ifdef ENABLE_MOUTH m_mouth.detected = false; m_mouth.waitingOpCode = OP_NONE; m_mouth.lastCmdTime = 0; +#endif } void CClyde::begin() { @@ -124,6 +129,7 @@ void CClyde::begin() { pinMode(m_white.pin, OUTPUT); analogWrite(m_white.pin, m_white.brightness); +#ifdef ENABLE_MOUTH //setup mouth / mp3 shield m_mouth.mp3.begin(9600); @@ -133,6 +139,7 @@ void CClyde::begin() { pinMode(CMouth::TX_PIN, OUTPUT); digitalWrite(CMouth::SELECT_PIN, HIGH); +#endif //load parameters from eeprom m_eeprom.readAmbientColor(&m_ambient.savedColor); @@ -140,8 +147,10 @@ void CClyde::begin() { //detect the personality modules detectPersonalities(); +#ifdef ENABLE_MOUTH //detect the loudmouth shield detectMouth(); +#endif //set default lights off setAmbient(RGB(0,0,0)); @@ -212,9 +221,12 @@ void CClyde::detectMouth() { #ifdef CLYDE_DEBUG Serial.println("Clyde: Trying to detect Loudmouth. Request set play mode: Single Loop Mode"); #endif - m_mouth.waitingOpCode = Clyde.setPlayMode(PLAYMODE_SINGLE_CYCLE); +#ifdef ENABLE_MOUTH + m_mouth.waitingOpCode = Clyde.setPlayMode(PLAYMODE_SINGLE_CYCLE); +#endif } +#ifdef ENABLE_EYE void CClyde::updateEye() { //read IR value uint16_t irValue = analogRead(m_eye.pin); @@ -293,7 +305,8 @@ void CClyde::calibrateEye(uint16_t irValue) { if (irAvg < (uint16_t)((CEye::CALIB_FORMULA_B - CEye::CALIB_MIN_THRESHOLD_DIFF) / CEye::CALIB_FORMULA_A)) { //if the eye was not calibrated, turn on ambient light to show feedback if (!m_eye.calibrated) - fadeAmbient(m_ambient.savedColor, 0.1f); + //fadeAmbient(m_ambient.savedColor, 0.1f); + fadeAmbient(m_ambient.savedColor, 10); if (!m_eye.onceCalibrated) setWhite(255); @@ -371,10 +384,10 @@ bool CClyde::wasEyePressed(uint16_t irValue) { //blink(RGB(255,0,0), 200, 200, 3); setAmbient(RGB(0, 0, 0)); setWhite(255); - +#ifdef ENABLE_MOUTH setPlayMode(PLAYMODE_SINGLE); play(SND_ERROR); - +#endif #ifdef CLYDE_DEBUG Serial.println("Clyde: eye long press detected. auto release."); #endif @@ -401,7 +414,10 @@ bool CClyde::wasEyePressed(uint16_t irValue) { return false; } +#endif // the endif for #ifdef ENABLE_EYE + +#ifdef ENABLE_MOUTH void CClyde::updateMouth() { //detect the loudmouth shield at startup by waiting for mp3 player response if (!m_mouth.detected) { @@ -514,6 +530,7 @@ EOpCode CClyde::stop(void) return OP_PAUSE; } +#endif void CClyde::updateAmbientLight() { //update ambient cycle @@ -581,14 +598,31 @@ void CClyde::updatePersonalities() { } } -void CClyde::fadeAmbient(const RGB &c, float spd) { +// void CClyde::fadeAmbient(const RGB &c, float spd) { +// m_ambient.targetColor = c; + +// //calculate fade speed for each color +// m_ambient.fadeSpeed = RGBf( +// (m_ambient.targetColor.r - m_ambient.color.r) / 255.0f * spd, +// (m_ambient.targetColor.g - m_ambient.color.g) / 255.0f * spd, +// (m_ambient.targetColor.b - m_ambient.color.b) / 255.0f * spd +// ); + +// //make sure that fade speeds are positive +// if (m_ambient.fadeSpeed.r < 0) m_ambient.fadeSpeed.r *= -1; +// if (m_ambient.fadeSpeed.g < 0) m_ambient.fadeSpeed.g *= -1; +// if (m_ambient.fadeSpeed.b < 0) m_ambient.fadeSpeed.b *= -1; +// } + +// the fadeAmbient version without floats. +void CClyde::fadeAmbient(const RGB &c, uint8_t tm) { m_ambient.targetColor = c; //calculate fade speed for each color m_ambient.fadeSpeed = RGBf( - (m_ambient.targetColor.r - m_ambient.color.r) / 255.0f * spd, - (m_ambient.targetColor.g - m_ambient.color.g) / 255.0f * spd, - (m_ambient.targetColor.b - m_ambient.color.b) / 255.0f * spd + (m_ambient.targetColor.r - m_ambient.color.r) / 255 / tm, + (m_ambient.targetColor.g - m_ambient.color.g) / 255 / tm, + (m_ambient.targetColor.b - m_ambient.color.b) / 255 / tm ); //make sure that fade speeds are positive @@ -602,32 +636,56 @@ void CClyde::setWhite(uint8_t b) { showWhiteLight(); } -void CClyde::fadeWhite(uint8_t b, float spd) { +// void CClyde::fadeWhite(uint8_t b, float spd) { +// m_white.targetBrightness = b; + +// m_white.fadeSpeed = (m_white.targetBrightness - m_white.brightness) / 255.0 * spd; +// if (m_white.fadeSpeed < 0) m_white.fadeSpeed *= -1; +// } + +// fadeWhite without a float... +void CClyde::fadeWhite(uint8_t b, uint16_t tm) { m_white.targetBrightness = b; - m_white.fadeSpeed = (m_white.targetBrightness - m_white.brightness) / 255.0 * spd; + m_white.fadeSpeed = (m_white.targetBrightness - m_white.brightness) / 255 / tm; if (m_white.fadeSpeed < 0) m_white.fadeSpeed *= -1; } void CClyde::switchLights() -{ - if (!m_white.isOn() && m_ambient.isOn()) { - fadeWhite(0, 0.1f); - } - else if (m_white.isOn() && m_ambient.isOn()) { - fadeAmbient(RGB(0,0,0), 0.5f); - } - else if (m_white.isOn() && !m_ambient.isOn()) { - fadeWhite(255, 0.3f); - setPlayMode(PLAYMODE_SINGLE); - play(SND_OFF); - } - else if (!m_white.isOn() && !m_ambient.isOn()) { - fadeAmbient(m_ambient.savedColor, 0.1f); - setPlayMode(PLAYMODE_SINGLE); - play(SND_ON); - } - +{ +#ifdef CLYDE_DEBUG + Serial.println( "Clyde: switchLights" ); +#endif + // just making sure to switch off any cycle... + m_cycle.off(); + + if( m_white.isOn() ){ + if( m_ambient.isOn() ){ + // save the current ambient light before switching off. + m_ambient.save(); + //fadeAmbient(RGB(0,0,0), 0.5f); + fadeAmbient(RGB(0,0,0), 2 ); + }else{ + // fadeWhite(255, 0.3f); + fadeWhite(255, 3); +#ifdef ENABLE_MOUTH + setPlayMode(PLAYMODE_SINGLE); + play(SND_OFF); +#endif + } + }else{ + if( m_ambient.isOn() ){ + // fadeWhite(0, 0.1f); + fadeWhite(0, 10); + }else{ + //fadeAmbient(m_ambient.savedColor, 0.1f); + fadeAmbient(m_ambient.savedColor, 10 ); +#ifdef ENABLE_MOUTH + setPlayMode(PLAYMODE_SINGLE); + play(SND_ON); +#endif + } + } #ifdef CLYDE_DEBUG Serial.print("Switched lights: white is "); Serial.print(m_white.isOn() ? "ON" : "OFF"); @@ -685,6 +743,7 @@ void CClyde::stopCycle() { //TODO should this be a function pointer set when st } } +// just commented this out to save space... void CClyde::blink(const RGB& rgb, uint32_t onDuration, uint32_t offDuration, uint8_t numBlinks) { //calculate number of steps needed in the cycle uint8_t steps = numBlinks*2 + 1; @@ -754,7 +813,9 @@ void CClyde::updateCycleNextStep(uint32_t now) { else { m_cycle.type = OFF; setAmbient(m_cycle.colors[m_cycle.numSteps-1]); +#ifdef ENABLE_MOUTH stop(); +#endif return; } } @@ -786,4 +847,4 @@ void CClyde::speedUpCycle(uint32_t factor) { //jump cycle to next color cycleNextStep(millis()); -} \ No newline at end of file +} diff --git a/software/arduino/libraries/Clyde/Clyde.h b/software/arduino/libraries/Clyde/Clyde.h index 16c9d40..7846ed0 100644 --- a/software/arduino/libraries/Clyde/Clyde.h +++ b/software/arduino/libraries/Clyde/Clyde.h @@ -22,6 +22,8 @@ #define ENABLE_AFRAID_OF_THE_DARK #define ENABLE_TOUCHY_FEELY +//#define ENABLE_MOUTH +//#define ENABLE_EYE // enable/disable the eye... can be buggy on some devices. Note: if not ENABLE_EYE then we have to rely on the touchyfeely module! #include "colortypes.h" #include "ClydeEEPROM.h" @@ -39,6 +41,7 @@ enum ECycleType { OFF, BLINK, SUNSET, + SUNRISE, SELECT, LAUGH, UNKNOWN @@ -53,6 +56,7 @@ enum ECycleLoop { NO_LOOP }; +#ifdef ENABLE_MOUTH /** * Enum types for mp3 op codes. */ @@ -97,7 +101,7 @@ enum EAudioIndex { SND_AU_CLAIR_DE_LA_LUNE = 12, SND_DAISY_BELL = 13 }; - +#endif /** * Main Clyde class that provides the interface to the device. */ @@ -145,7 +149,7 @@ class CClyde { struct CWhiteLight { uint8_t pin; /**< Digital pin to control the brightness. */ float brightness; /**< Current brightness. */ - float targetBrightness; /**< Target brightness, used for fading. */ + uint8_t targetBrightness; /**< Target brightness, used for fading. */ float fadeSpeed; /**< Speed, used for fading. */ /** @@ -153,7 +157,8 @@ class CClyde { */ bool isOn() { return targetBrightness < 255; } }; - + +#ifdef ENABLE_EYE /** * The squishy eye. */ @@ -190,6 +195,7 @@ class CClyde { uint16_t restartCount; /**< Number of time calibration restarted because of noise since last calibration. */ #endif }; +#endif /** * The ambient light cycle. @@ -219,7 +225,8 @@ class CClyde { /** Turn off the cycle. */ void off() { type = OFF; } }; - + +#ifdef ENABLE_MOUTH /** * The mouth / speaker / mp3 player */ @@ -237,31 +244,39 @@ class CClyde { static SoftwareSerial mp3; }; +#endif private: CModulePosition m_modules[CModulePosition::NUM_MODULES]; CAmbientLight m_ambient; CWhiteLight m_white; CClydeEEPROM m_eeprom; +#ifdef ENABLE_EYE CEye m_eye; +#endif CAmbientCycle m_cycle; +#ifdef ENABLE_MOUTH CMouth m_mouth; - +#endif public: /** Contructor. */ CClyde(); /** Initialize Clyde. */ void begin(); - + +#ifdef ENABLE_EYE /** Check if eye was calibrated once. */ bool wasEyeCalibratedOnce() { return m_eye.onceCalibrated; } /** Update the eye / infrared switch. */ void updateEye(); - + +#endif +#ifdef ENABLE_MOUTH /** Update the mouth / sound shield. */ void updateMouth(); +#endif /** Update the ambient light. */ void updateAmbientLight(); @@ -292,11 +307,16 @@ class CClyde { */ void setAmbient(const RGB &c); + /* /\** */ + /* * Fade the ambient color to a given color. */ + /* *\/ */ + /* void fadeAmbient(const RGB &c, float spd); */ + /** - * Fade the ambient color to a given color. + * Fade the ambient color to a given color. Higher values of tm will cause slower fading */ - void fadeAmbient(const RGB &c, float spd); - + void fadeAmbient(const RGB &c, uint8_t tm); + /** * Get the white light object. */ @@ -308,9 +328,10 @@ class CClyde { void setWhite(uint8_t b); /** - * Fade the white light to a given brightness. + * Fade the white light to a given brightness. Slower fading for higher tm values */ - void fadeWhite(uint8_t b, float spd); + // void fadeWhite(uint8_t b, float spd); + void fadeWhite(uint8_t b, uint16_t tm); /** Switch to the next of the four lights on/off states. */ void switchLights(); @@ -354,8 +375,9 @@ class CClyde { /** * Make the ambient light blink. */ - void blink(const RGB& rgb, uint32_t onDuration, uint32_t offDuration, uint8_t numBlinks); - + void blink(const RGB& rgb, uint32_t onDuration, uint32_t offDuration, uint8_t numBlinks); + +#ifdef ENABLE_MOUTH /** * Set the loudmouth mp3 player play mode. */ @@ -386,6 +408,7 @@ class CClyde { * Stop the audio of the Loudmouth shield. */ EOpCode stop(void); +#endif private: /** Detect the personality modules. */ @@ -393,7 +416,8 @@ class CClyde { /** Detect the loudmouth shield. */ void detectMouth(); - + +#ifdef ENABLE_EYE /** * Calibrate the eye. */ @@ -403,6 +427,7 @@ class CClyde { * Check if the eye was pressed given a read sensor value. */ bool wasEyePressed(uint16_t irValue); +#endif /** Update a color channel of the ambient light. */ void updateAmbientLight(float *value, uint8_t target, float speed); @@ -429,4 +454,4 @@ class CClyde { extern CClyde Clyde; -#endif \ No newline at end of file +#endif diff --git a/software/arduino/libraries/Clyde/ClydeAfraidOfTheDark.cpp b/software/arduino/libraries/Clyde/ClydeAfraidOfTheDark.cpp index 6fdece1..39bff96 100644 --- a/software/arduino/libraries/Clyde/ClydeAfraidOfTheDark.cpp +++ b/software/arduino/libraries/Clyde/ClydeAfraidOfTheDark.cpp @@ -104,8 +104,10 @@ void CClydeAfraidOfTheDark::startSunset() { return; Clyde.setCycle(SUNSET, m_sunsetSteps, m_sunsetColors, m_sunsetIntervals, NO_LOOP); +#ifdef ENABLE_MOUTH Clyde.setPlayMode(PLAYMODE_SINGLE); Clyde.play(SND_AU_CLAIR_DE_LA_LUNE); +#endif } -#endif \ No newline at end of file +#endif diff --git a/software/arduino/libraries/Clyde/ClydeEEPROM.cpp b/software/arduino/libraries/Clyde/ClydeEEPROM.cpp index b092d55..800d05b 100644 --- a/software/arduino/libraries/Clyde/ClydeEEPROM.cpp +++ b/software/arduino/libraries/Clyde/ClydeEEPROM.cpp @@ -35,7 +35,7 @@ void CClydeEEPROM::writeSerial(char *serial) { if (*(serial + i) < 48 || *(serial + i) > 70 || (*(serial + i) > 59 && *(serial + i) < 65)) return; - + for(int i = 0; i < CLYDE_SERIAL_LENGTH; i++) EEPROM.write(i, *(serial+i)); } @@ -48,7 +48,7 @@ void CClydeEEPROM::readSerial(char* serial) { void CClydeEEPROM::writeQC(bool qc) { EEPROM.write(QC_ADDR, qc ? 1 : 0); } - + void CClydeEEPROM::readQC(bool* qc) { *(qc) = EEPROM.read(QC_ADDR) == 1; } @@ -70,7 +70,7 @@ void CClydeEEPROM::readAmbientColor(RGB* color) { bool CClydeEEPROM::clearSunsetCycle() { RGB sunsetColors[CClyde::CAmbientCycle::MAX_CYCLE_LENGTH]; memset( (void*)&sunsetColors[0], 0, sizeof(RGB)*CClyde::CAmbientCycle::MAX_CYCLE_LENGTH); - + uint32_t sunsetIntervals[CClyde::CAmbientCycle::MAX_CYCLE_LENGTH]; memset( (void*)&sunsetIntervals[0], 0, sizeof(uint32_t)*CClyde::CAmbientCycle::MAX_CYCLE_LENGTH); @@ -82,19 +82,19 @@ bool CClydeEEPROM::writeSunsetCycle(uint8_t steps, RGB *colors, uint32_t *interv //make sure that we don't write a cycle with more steps than we have space for if (steps > CClyde::CAmbientCycle::MAX_CYCLE_LENGTH) return false; - + uint16_t addr = SUNSET_ADDR; - + //write sunset cycle EEPROM.write(addr, steps); - + addr++; for(int i = 0; i < steps; i++) { EEPROM.write(addr + i*3 + 0, (*(colors + i)).r); EEPROM.write(addr + i*3 + 1, (*(colors + i)).g); EEPROM.write(addr + i*3 + 2, (*(colors + i)).b); } - + addr += steps*3; for(int i = 0; i < steps; i++) { EEPROM.write(addr + i*4, (*(intervals + i) >> 24) & 0xFF); @@ -102,7 +102,7 @@ bool CClydeEEPROM::writeSunsetCycle(uint8_t steps, RGB *colors, uint32_t *interv EEPROM.write(addr + i*4 + 2, (*(intervals + i) >> 8) & 0xFF); EEPROM.write(addr + i*4 + 3, *(intervals + i) & 0xFF); } - + return true; } @@ -110,24 +110,24 @@ bool CClydeEEPROM::readSunsetCycle(uint8_t* steps, RGB *colors, unsigned long *i { //read sunset cycle eeprom address unsigned short sunsetAddr = SUNSET_ADDR; - + //read sunset cycle uint8_t s = EEPROM.read(sunsetAddr); - + //make sure that we don't read a cycle with more steps than we have space for if (s > CClyde::CAmbientCycle::MAX_CYCLE_LENGTH) return false; - + //read cycle *(steps) = s; - + sunsetAddr++; for(int i = 0; i < *(steps); i++) { (*(colors + i)).r = EEPROM.read(sunsetAddr + i*3 + 0); (*(colors + i)).g = EEPROM.read(sunsetAddr + i*3 + 1); (*(colors + i)).b = EEPROM.read(sunsetAddr + i*3 + 2); } - + sunsetAddr += *(steps)*3; for(int i = 0; i < *(steps); i++) { *(intervals + i) = EEPROM.read(sunsetAddr + i*4); @@ -135,7 +135,7 @@ bool CClydeEEPROM::readSunsetCycle(uint8_t* steps, RGB *colors, unsigned long *i *(intervals + i) = (*(intervals + i) << 8) | EEPROM.read(sunsetAddr + i*4 + 2); *(intervals + i) = (*(intervals + i) << 8) | EEPROM.read(sunsetAddr + i*4 + 3); } - + return true; } @@ -143,18 +143,18 @@ void CClydeEEPROM::reset() { //char serial[7] = "012345"; uint16_t ver = 1; RGB ambient(20,255,54); - + //writeSerial(&serial[0]); //writeQC(false); writeAmbientColor(&ambient); - + //write the default sunset cycle //TODO: move values to somewhere more obvious, like top of ClydeAfraidOfTheDark.h const uint8_t sunsetSteps = 9; - RGB sunsetColors[sunsetSteps] = {RGB(255, 150, 0), RGB(255, 0, 0), RGB(100, 0, 200), RGB(0, 0, 255), RGB(0, 0, 0), RGB(0, 0, 180), RGB(0, 0, 0), RGB(0, 0, 125), RGB(0, 0, 0)}; - uint32_t sunsetIntervals[sunsetSteps] = {3000, 400000, 300000, 200000, 180000, 120000, 3000, 2000, 1000}; //real - //uint32_t sunsetIntervals[sunsetSteps] = {500, 10000, 5000, 5000, 3000, 2000, 1500, 1000, 1000}; //demo - + RGB sunsetColors[sunsetSteps] = {RGB(242, 103, 31), RGB(201, 27, 38), RGB(156, 15, 95), RGB(96, 4, 122), RGB(22, 10, 71), RGB( 12, 5, 35 ), RGB( 5, 0, 15 ), RGB( 0, 0, 5 ), RGB( 0, 0, 0 ) }; + uint32_t sunsetIntervals[sunsetSteps] = {30000, 30000, 40000, 60000, 60000, 60000, 60000, 60000, 60000}; //real + //uint32_t sunsetIntervals[sunsetSteps] = {500, 10000, 10000, 10000, 10000, 10000, 10000, 10000, 10000}; //demo + clearSunsetCycle(); writeSunsetCycle(sunsetSteps, &sunsetColors[0], &sunsetIntervals[0]); -} \ No newline at end of file +} diff --git a/software/arduino/libraries/Clyde/ClydeEEPROM.h b/software/arduino/libraries/Clyde/ClydeEEPROM.h index 2c8b7ab..245b0ae 100644 --- a/software/arduino/libraries/Clyde/ClydeEEPROM.h +++ b/software/arduino/libraries/Clyde/ClydeEEPROM.h @@ -56,4 +56,4 @@ class CClydeEEPROM { void reset(); }; -#endif \ No newline at end of file +#endif diff --git a/software/arduino/libraries/Clyde/ClydeTouchyFeely.cpp b/software/arduino/libraries/Clyde/ClydeTouchyFeely.cpp index 1d08213..3c4a8f3 100644 --- a/software/arduino/libraries/Clyde/ClydeTouchyFeely.cpp +++ b/software/arduino/libraries/Clyde/ClydeTouchyFeely.cpp @@ -24,7 +24,8 @@ CClydeTouchyFeely TouchyFeely; const RGB CClydeTouchyFeely::SELECT_COLORS[] = {RGB(255,0,0), RGB(255,255,0), RGB(0,255,0), RGB(0,255,255), RGB(0,0,255), RGB(255,0,255), RGB(255,255,255)}; const uint16_t CClydeTouchyFeely::SELECT_INTERVALS[] = {1000, 1000, 1000, 1000, 1000, 1000, 1000}; const uint8_t CClydeTouchyFeely::SELECT_STEPS = 7; - + + CClydeTouchyFeely::CClydeTouchyFeely() : CClydeModule(ID_LOW, ID_HIGH), m_mpr121(DEVICE_ADDR, TOUCH_LEVEL, RELEASE_LEVEL) { @@ -37,6 +38,15 @@ CClydeTouchyFeely::CClydeTouchyFeely() m_firstTickle = 0; m_lastAmbientOn = false; m_lastWhiteOn = false; + COLOR_LEG_1 = RGB( 228, 26, 28 ); // red + COLOR_LEG_2 = RGB( 55, 126, 255 ); // blue + COLOR_LEG_4 = RGB( 77, 255, 74 ); // green + COLOR_LEG_8 = RGB( 200, 78, 200 ); // purple + COLOR_LEG_16 = RGB( 255, 127, 0 ); // orange + COLOR_LEG_32 = RGB( 240, 2, 127 ); // pink +#ifndef ENABLE_EYE + LEG_SWITCH = 4; // which leg should be used as the light switch, if we're not using the eye? +#endif } bool CClydeTouchyFeely::init(uint8_t apin, uint8_t dpin) { @@ -69,18 +79,25 @@ void CClydeTouchyFeely::update(uint8_t apin, uint8_t dpin) { m_lastAmbientOn = Clyde.ambient()->isOn(); m_lastWhiteOn = Clyde.white()->isOn(); } - + + +// if we use a leg as a switch we cannot return here! +#ifdef ENABLE_EYE //only active when the ambient light is on if (!Clyde.ambient()->isOn()/* || Clyde.white()->isOn()*/) return; - + //return if clyde is in any cycle other than SELECT or OFF. + // this avoids triggering an accidental color change by touching a leg. + if( !( Clyde.cycle()->is(SELECT) | Clyde.cycle()->is(OFF) | Clyde.cycle()->is(UNKNOWN) ) ) return; +#endif + //trigger touch event after a few millis to protect from false positive - if ((m_touchStatus & 0x0FFF) && (millis()-m_touchStart > 250)) { + if ((m_touchStatus & 0x0FFF) && (millis()-m_touchStart > 500)) { #ifdef CLYDE_DEBUG Serial.println("Clyde: Touchy-Feely triggered touch event."); #endif - //start color selection only if current cycle isn't laugh or select - if (!Clyde.cycle()->is(SELECT) && !Clyde.cycle()->is(LAUGH)) + //start color selection only if the current cycle isn't select + if (!Clyde.cycle()->is(SELECT) ) startColorSelect(); //call touched handler if any @@ -89,7 +106,7 @@ void CClydeTouchyFeely::update(uint8_t apin, uint8_t dpin) { //reset status to only call this once m_touchStatus = 0x1000; } - + //check for mpr121 interrupt if (digitalRead(dpin)) return; @@ -100,9 +117,9 @@ void CClydeTouchyFeely::update(uint8_t apin, uint8_t dpin) { //keep track of when touch started if (m_touchStatus & 0x0FFF) { #ifdef CLYDE_DEBUG - Serial.println("Clyde: Touchy-Feely detected a touch."); + Serial.println("Clyde: Touchy-Feely detected a touch (not triggering the color select)."); #endif - + m_lastStatus = m_touchStatus; m_touchStart = millis(); } else { @@ -110,12 +127,12 @@ void CClydeTouchyFeely::update(uint8_t apin, uint8_t dpin) { Serial.print("Clyde: Touchy-Feely detected a release. Touch lasted: "); Serial.println(millis() - m_touchStart); #endif - - if (!Clyde.cycle()->is(LAUGH)) - stopColorSelect(); - - if (!Clyde.white()->isOn()) + + if (!Clyde.cycle()->is(SELECT)) tickleCheck(); + + if (!Clyde.cycle()->is(LAUGH)) + stopColorSelect(); } //call released handler if it is set and no legs are touched @@ -124,6 +141,14 @@ void CClydeTouchyFeely::update(uint8_t apin, uint8_t dpin) { } void CClydeTouchyFeely::tickleCheck() { + // if we're not using the eye to switch the light we use one of the legs. Touching this + // leg has priority over all other stuff in here. +#ifndef ENABLE_EYE + if( m_lastStatus==LEG_SWITCH ){ + Clyde.switchLights(); + return; + } +#endif //touch detected, increase the tickle count m_tickleCount++; if (m_tickleCount == 1) { @@ -135,12 +160,14 @@ void CClydeTouchyFeely::tickleCheck() { if (m_tickleCount >= TICKLE_REPEAT) { laugh(); m_firstTickle = 0; + m_tickleCount = 0; } #ifdef CLYDE_DEBUG else { Serial.print("Clyde: touchy-feely module detected "); Serial.print(m_tickleCount); Serial.println(" tickle(s)"); + Serial.println( m_touchStatus ); } #endif } @@ -148,6 +175,29 @@ void CClydeTouchyFeely::tickleCheck() { else { m_tickleCount = 0; } + // if Clyde is not laughing, we change the color based on the touched leg. + if( !( Clyde.cycle()->is(LAUGH) ) ){ + switch (m_lastStatus){ + case 1: + Clyde.fadeAmbient( COLOR_LEG_1, 2 ); + break; + case 2: + Clyde.fadeAmbient( COLOR_LEG_2, 2 ); + break; + case 4: + Clyde.fadeAmbient( COLOR_LEG_4, 2 ); + break; + case 8: + Clyde.fadeAmbient( COLOR_LEG_8, 2 ); + break; + case 16: + Clyde.fadeAmbient( COLOR_LEG_16, 2 ); + break; + case 32: + Clyde.fadeAmbient( COLOR_LEG_32, 2 ); + break; + } + } } void CClydeTouchyFeely::laugh() { @@ -187,8 +237,10 @@ void CClydeTouchyFeely::laugh() { m_laughIntervals[laughSteps-1] = random(150, 200); Clyde.setCycle(LAUGH, laughSteps, &m_laughColors[0], m_laughIntervals, NO_LOOP); +#ifdef ENABLE_MOUTH Clyde.setPlayMode(PLAYMODE_SINGLE_CYCLE); Clyde.play(SND_LAUGH); +#endif } void CClydeTouchyFeely::startColorSelect() { @@ -200,9 +252,10 @@ void CClydeTouchyFeely::startColorSelect() { Clyde.setCycle(SELECT, SELECT_STEPS, SELECT_COLORS, SELECT_INTERVALS, LOOP); Clyde.setCycleStep(m_lastStopStep); - +#ifdef ENABLE_MOUTH Clyde.setPlayMode(PLAYMODE_SINGLE_CYCLE); Clyde.play(SND_HAPPY); +#endif } void CClydeTouchyFeely::stopColorSelect() { @@ -217,9 +270,11 @@ void CClydeTouchyFeely::stopColorSelect() { Clyde.cycle()->off(); Clyde.ambient()->save(); - + +#ifdef ENABLE_MOUTH //stop audio Clyde.stop(); +#endif } /* @@ -263,4 +318,4 @@ void CClydeTouchyFeely::debugAutoConfig() { } */ -#endif \ No newline at end of file +#endif diff --git a/software/arduino/libraries/Clyde/ClydeTouchyFeely.h b/software/arduino/libraries/Clyde/ClydeTouchyFeely.h index 3048b3d..7b6cb75 100644 --- a/software/arduino/libraries/Clyde/ClydeTouchyFeely.h +++ b/software/arduino/libraries/Clyde/ClydeTouchyFeely.h @@ -44,6 +44,17 @@ class CClydeTouchyFeely : public CClydeModule { static const RGB SELECT_COLORS[]; /**< colors of the color select cycle */ static const uint16_t SELECT_INTERVALS[]; /**< intervals of the color select cycle */ static const uint8_t SELECT_STEPS; /**< steps in the color select cycle */ + + /* leg colors*/ + RGB COLOR_LEG_1; + RGB COLOR_LEG_2; + RGB COLOR_LEG_4; + RGB COLOR_LEG_8; + RGB COLOR_LEG_16; + RGB COLOR_LEG_32; +#ifndef ENABLE_EYE + uint8_t LEG_SWITCH; +#endif MPR121 m_mpr121; /**< interface to the mpr121 */ bool m_colorSelectEnabled; /**< color selection enabled flag */ @@ -51,6 +62,7 @@ class CClydeTouchyFeely : public CClydeModule { uint32_t m_firstTickle; /**< time in millis we detected the first tickle tap */ uint8_t m_lastStopStep; /**< step index when the color select cycle was stopped. */ uint16_t m_touchStatus; /**< current status of touch electrodes. */ + uint16_t m_lastStatus; /**< last status of touch electrodes. */ uint32_t m_touchStart; /**< time in millis when the active touch started. */ bool m_lastAmbientOn; /**< status of the ambient light on last update. */ bool m_lastWhiteOn; /**< status of the white light on last update. */ diff --git a/software/arduino/libraries/Clyde/Examples/ClydeOriginal/ClydeOriginal.ino b/software/arduino/libraries/Clyde/Examples/ClydeOriginal/ClydeOriginal.ino index dd00d7a..914dc69 100644 --- a/software/arduino/libraries/Clyde/Examples/ClydeOriginal/ClydeOriginal.ino +++ b/software/arduino/libraries/Clyde/Examples/ClydeOriginal/ClydeOriginal.ino @@ -11,18 +11,19 @@ SerialCommand sCmd; void setup() { Wire.begin(); - + Serial.begin(9600); // Uncomment this line to talk to Clyde over the Serial Monitor // while (!Serial) ; sCmd.addCommand("SERIAL", cmdSerial); sCmd.addCommand("VERSION", cmdVersion); sCmd.addCommand("RESET", cmdReset); + sCmd.addCommand("TIME", digitalClockDisplay); sCmd.addCommand("SET_AMBIENT", cmdSetAmbient); sCmd.addCommand("SET_WHITE", cmdSetWhite); sCmd.addCommand("WRITE_EEPROM", cmdWriteEEPROM); sCmd.addCommand("READ_EEPROM", cmdReadEEPROM); - + //Clyde.eeprom()->reset(); Clyde.begin(); Serial.println("Clyde is Ready!"); @@ -31,20 +32,26 @@ void setup() { void loop() { //read the serial communication if any sCmd.readSerial(); - + +#ifdef ENABLE_EYE //calibrate the eye and check for press Clyde.updateEye(); - +#endif +#ifdef ENABLE_MOUTH //update the mouth to play sounds Clyde.updateMouth(); - +#endif //update the lights Clyde.updateAmbientLight(); Clyde.updateWhiteLight(); - +#ifdef ENABLE_EYE //make Clyde behave after the eye was calibrated once if (Clyde.wasEyeCalibratedOnce()) Clyde.updatePersonalities(); +#else + Clyde.updatePersonalities(); +#endif + } // @@ -53,26 +60,26 @@ void loop() { void cmdSerial() { char serial[7] = {0}; - Clyde.eeprom()->readSerial(&serial[0]); - Serial.print("OK "); + Clyde.eeprom()->readSerial(&serial[0]); + Serial.print("OK "); Serial.println(serial); } void cmdVersion() { uint16_t vers = FIRMWARE_VERSION; - Serial.print("OK "); + Serial.print("OK "); Serial.println(vers); } void cmdReset() { Clyde.eeprom()->reset(); - Serial.println("OK"); + Serial.println("OK"); } void cmdSetAmbient() { char *param1, *param2, *param3; int r, g, b; - + //Get arguments param1 = sCmd.next(); // Red param2 = sCmd.next(); // Green @@ -80,7 +87,7 @@ void cmdSetAmbient() { r = atoi(param1); g = atoi(param2); b = atoi(param3); - + Clyde.setAmbient(RGB(r, g, b)); Serial.println("OK"); } @@ -88,14 +95,14 @@ void cmdSetAmbient() { void cmdSetWhite() { char *param1; int w; - + //Get arguments param1 = sCmd.next(); // Brightness w = atoi(param1); - + if (w > 255) w = 255; w = 255 - w; - + Clyde.setWhite(w); Serial.println("OK"); } @@ -104,13 +111,13 @@ void cmdWriteEEPROM() { char *param1, *param2; int addr; byte value; - + //Get arguments param1 = sCmd.next(); // Address param2 = sCmd.next(); // Value addr = atoi(param1); value = atoi(param2); - + EEPROM.write(addr, value); Serial.println("OK"); } @@ -118,11 +125,12 @@ void cmdWriteEEPROM() { void cmdReadEEPROM() { char *param1; int addr; - + //Get arguments param1 = sCmd.next(); // Address - addr = atoi(param1); - + addr = atoi(param1); + Serial.print("OK "); Serial.println(EEPROM.read(addr)); } + diff --git a/software/arduino/libraries/Clyde/Examples/ClydeTimeAlarm/ClydeTimeAlarm.ino b/software/arduino/libraries/Clyde/Examples/ClydeTimeAlarm/ClydeTimeAlarm.ino new file mode 100644 index 0000000..abb03ba --- /dev/null +++ b/software/arduino/libraries/Clyde/Examples/ClydeTimeAlarm/ClydeTimeAlarm.ino @@ -0,0 +1,100 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +/** + This is an alternative firmware that enables an alarm that will cause Clyde to + switch on the light at the specified time. + */ + +#define FIRMWARE_VERSION 1 + +SerialCommand sCmd; + +void setup() { + Wire.begin(); + + Serial.begin(9600); + // Uncomment this line to talk to Clyde over the Serial Monitor + //while (!Serial) ; + sCmd.addCommand("TIME", digitalClockDisplay); + sCmd.addCommand("LIGHT", switchLights); + // set the time below to the current time when compiling and uploading + setTime(20,15,0,2,9,14); + + // example alarms. + Alarm.alarmRepeat(6,15,0, startSunrise); + Alarm.alarmRepeat(6,18,0, startSunriseWhiteLight); + Clyde.eeprom()->reset(); + Clyde.begin(); +} + +void loop() { + //read the serial communication if any + sCmd.readSerial(); + +#ifdef ENABLE_EYE + //calibrate the eye and check for press + Clyde.updateEye(); +#endif + +#ifdef ENABLE_MOUTH + //update the mouth to play sounds + Clyde.updateMouth(); +#endif + Clyde.updatePersonalities(); + //update the lights + Clyde.updateAmbientLight(); + Clyde.updateWhiteLight(); + +//#ifdef ENABLE_EYE +// //make Clyde behave after the eye was calibrated once +// if (Clyde.wasEyeCalibratedOnce()) +// Clyde.updatePersonalities(); +//#else +// Clyde.updatePersonalities(); +//#endif + + // check for alarm. + // Note: the serviceAlarms function is private in the original TimeAlarms library and the + // only way to check for an alarm was the delay function. + Alarm.serviceAlarms(); + //Alarm.delay( 1 ); // delay for 1ms and during that time period check for alarms. +} + +void startSunriseWhiteLight(){ + Clyde.fadeWhite( 0, 1000 ); +} + +void startSunrise(){ + RGB sunriseColors[7] = {RGB(19, 17, 28), RGB(39, 34, 57), RGB(78, 69, 114), RGB(46, 108, 181), RGB(168, 142, 127), RGB(255, 166, 48), RGB(255, 210, 66) }; + uint32_t sunriseIntervals[7] = {30000, 60000, 60000, 60000, 60000, 60000, 60000}; //real + Clyde.setCycle( SUNRISE, 7, sunriseColors, sunriseIntervals, NO_LOOP ); +} + +void switchLights(){ + Clyde.switchLights(); +} + +void digitalClockDisplay() +{ + // digital clock display of the time + Serial.print(hour()); + printDigits(minute()); + printDigits(second()); + Serial.println(); +} + +void printDigits(int digits) +{ + Serial.print(":"); + if(digits < 10) + Serial.print('0'); + Serial.print(digits); +} + diff --git a/software/arduino/libraries/SerialCommand/README.txt b/software/arduino/libraries/SerialCommand/README.txt deleted file mode 100644 index 2aed999..0000000 --- a/software/arduino/libraries/SerialCommand/README.txt +++ /dev/null @@ -1,42 +0,0 @@ -An Arduino library to tokenize and parse commands received over a serial port. - -Initially documented here: http://awtfy.com/2011/05/23/a-minimal-arduino-library-for-processing-serial-commands/ - -An alternate version of this library is available as https://github.com/kroimon/Arduino-SerialCommand - -This version is the one on Github. - -/******************************************************************************* -SerialCommand - An Arduino library to tokenize and parse commands received over -a serial port. -Copyright (C) 2011-2013 Steven Cogswell -http://awtfy.com - -Version 20131021A. - -Version History: -May 11 2011 - Initial version -May 13 2011 - Prevent overwriting bounds of SerialCommandCallback[] array in addCommand() - defaultHandler() for non-matching commands -Mar 2012 - Some const char * changes to make compiler happier about deprecated warnings. - Arduino 1.0 compatibility (Arduino.h header) -Oct 2013 - SerialCommand object can be created using a SoftwareSerial object, for SoftwareSerial - support. Requires #include in your sketch even if you don't use - a SoftwareSerial port in the project. sigh. See Example Sketch for usage. -Oct 2013 - Conditional compilation for the SoftwareSerial support, in case you really, really - hate it and want it removed. - -This library is free software; you can redistribute it and/or -modify it under the terms of the GNU Lesser General Public -License as published by the Free Software Foundation; either -version 2.1 of the License, or (at your option) any later version. - -This library is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -Lesser General Public License for more details. - -You should have received a copy of the GNU Lesser General Public -License along with this library; if not, write to the Free Software -Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA -***********************************************************************************/ \ No newline at end of file diff --git a/software/arduino/libraries/SerialCommand/SerialCommand.cpp b/software/arduino/libraries/SerialCommand/SerialCommand.cpp index 5277ad6..bbea5ba 100644 --- a/software/arduino/libraries/SerialCommand/SerialCommand.cpp +++ b/software/arduino/libraries/SerialCommand/SerialCommand.cpp @@ -1,190 +1,144 @@ -/******************************************************************************* -SerialCommand - An Arduino library to tokenize and parse commands received over -a serial port. -Copyright (C) 2011-2013 Steven Cogswell -http://awtfy.com - -See SerialCommand.h for version history. - -This library is free software; you can redistribute it and/or -modify it under the terms of the GNU Lesser General Public -License as published by the Free Software Foundation; either -version 2.1 of the License, or (at your option) any later version. - -This library is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -Lesser General Public License for more details. - -You should have received a copy of the GNU Lesser General Public -License along with this library; if not, write to the Free Software -Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA -***********************************************************************************/ - -#if defined(ARDUINO) && ARDUINO >= 100 -#include "Arduino.h" -#else -#include "WProgram.h" -#endif - +/** + * SerialCommand - A Wiring/Arduino library to tokenize and parse commands + * received over a serial port. + * + * Copyright (C) 2012 Stefan Rado + * Copyright (C) 2011 Steven Cogswell + * http://husks.wordpress.com + * + * Version 20120522 + * + * This library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this library. If not, see . + */ #include "SerialCommand.h" - -#include -#ifndef SERIALCOMMAND_HARDWAREONLY -#include -#endif - -// Constructor makes sure some things are set. +/** + * Constructor makes sure some things are set. + */ SerialCommand::SerialCommand() + : commandList(NULL), + commandCount(0), + defaultHandler(NULL), + term('\n'), // default terminator for commands, newline character + last(NULL) { - usingSoftwareSerial=0; - strncpy(delim," ",MAXDELIMETER); // strtok_r needs a null-terminated string - term='\r'; // return character, default terminator for commands - numCommand=0; // Number of callback handlers installed - clearBuffer(); -} - -#ifndef SERIALCOMMAND_HARDWAREONLY -// Constructor to use a SoftwareSerial object -SerialCommand::SerialCommand(SoftwareSerial &_SoftSer) -{ - usingSoftwareSerial=1; - SoftSerial = &_SoftSer; - strncpy(delim," ",MAXDELIMETER); // strtok_r needs a null-terminated string - term='\r'; // return character, default terminator for commands - numCommand=0; // Number of callback handlers installed - clearBuffer(); + strcpy(delim, " "); // strtok_r needs a null-terminated string + clearBuffer(); } -#endif - -// -// Initialize the command buffer being processed to all null characters -// -void SerialCommand::clearBuffer() -{ - for (int i=0; i 0) - #else - //Serial.print("not SERIALCOMMAND_HARDWAREONLY"); - while ((usingSoftwareSerial==0 && Serial.available() > 0) || (usingSoftwareSerial==1 && SoftSerial->available() > 0) ) - #endif - { - //Serial.print("while"); - int i; - boolean matched; - if (usingSoftwareSerial==0) { - // Hardware serial port - inChar=Serial.read(); // Read single available character, there may be more waiting - } else { - #ifndef SERIALCOMMAND_HARDWAREONLY - // SoftwareSerial port - inChar = SoftSerial->read(); // Read single available character, there may be more waiting - #endif - } - #ifdef SERIALCOMMANDDEBUG - Serial.print(inChar); // Echo back to serial stream - #endif - if (inChar==term) { // Check for the terminator (default '\r') meaning end of command - #ifdef SERIALCOMMANDDEBUG - Serial.print("Received: "); - Serial.println(buffer); - #endif - bufPos=0; // Reset to start of buffer - token = strtok_r(buffer,delim,&last); // Search for command at start of buffer - if (token == NULL) return; - matched=false; - for (i=0; i SERIALCOMMANDBUFFER-1) bufPos=0; // wrap buffer around if full - } - } +/** + * This checks the Serial stream for characters, and assembles them into a buffer. + * When the terminator character (default '\n') is seen, it starts parsing the + * buffer for a prefix command, and calls handlers setup by addCommand() member + */ +void SerialCommand::readSerial() { + while (Serial.available() > 0) { + char inChar = Serial.read(); // Read single available character, there may be more waiting + #ifdef SERIALCOMMAND_DEBUG + Serial.print(inChar); // Echo back to serial stream + #endif + + if (inChar == term) { // Check for the terminator (default '\r') meaning end of command + #ifdef SERIALCOMMAND_DEBUG + Serial.print("Received: "); + Serial.println(buffer); + #endif + + char *command = strtok_r(buffer, delim, &last); // Search for command at start of buffer + if (command != NULL) { + boolean matched = false; + for (int i = 0; i < commandCount; i++) { + #ifdef SERIALCOMMAND_DEBUG + Serial.print("Comparing ["); + Serial.print(command); + Serial.print("] to ["); + Serial.print(commandList[i].command); + Serial.println("]"); + #endif + + // Compare the found command against the list of known commands for a match + if (strncmp(command, commandList[i].command, SERIALCOMMAND_MAXCOMMANDLENGTH) == 0) { + #ifdef SERIALCOMMAND_DEBUG + Serial.print("Matched Command: "); + Serial.println(command); + #endif + + // Execute the stored handler function for the command + (*commandList[i].function)(); + matched = true; + break; + } + } + if (!matched && (defaultHandler != NULL)) { + (*defaultHandler)(command); + } + } + clearBuffer(); + } + else if (isprint(inChar)) { // Only printable characters into the buffer + if (bufPos < SERIALCOMMAND_BUFFER) { + buffer[bufPos++] = inChar; // Put character into buffer + buffer[bufPos] = '\0'; // Null terminate + } else { + #ifdef SERIALCOMMAND_DEBUG + Serial.println("Line buffer is full - increase SERIALCOMMAND_BUFFER"); + #endif + } + } + } } -// Adds a "command" and a handler function to the list of available commands. -// This is used for matching a found token in the buffer, and gives the pointer -// to the handler function to deal with it. -void SerialCommand::addCommand(const char *command, void (*function)()) -{ - if (numCommand < MAXSERIALCOMMANDS) { - #ifdef SERIALCOMMANDDEBUG - Serial.print(numCommand); - Serial.print("-"); - Serial.print("Adding command for "); - Serial.println(command); - #endif - - strncpy(CommandList[numCommand].command,command,SERIALCOMMANDBUFFER); - CommandList[numCommand].function = function; - numCommand++; - } else { - // In this case, you tried to push more commands into the buffer than it is compiled to hold. - // Not much we can do since there is no real visible error assertion, we just ignore adding - // the command - #ifdef SERIALCOMMANDDEBUG - Serial.println("Too many handlers - recompile changing MAXSERIALCOMMANDS"); - #endif - } +/* + * Clear the input buffer. + */ +void SerialCommand::clearBuffer() { + buffer[0] = '\0'; + bufPos = 0; } -// This sets up a handler to be called in the event that the receveived command string -// isn't in the list of things with handlers. -void SerialCommand::addDefaultHandler(void (*function)()) -{ - defaultHandler = function; -} \ No newline at end of file +/** + * Retrieve the next token ("word" or "argument") from the command buffer. + * Returns NULL if no more tokens exist. + */ +char *SerialCommand::next() { + return strtok_r(NULL, delim, &last); +} diff --git a/software/arduino/libraries/SerialCommand/SerialCommand.h b/software/arduino/libraries/SerialCommand/SerialCommand.h index bcfdc9f..e00dd29 100644 --- a/software/arduino/libraries/SerialCommand/SerialCommand.h +++ b/software/arduino/libraries/SerialCommand/SerialCommand.h @@ -1,108 +1,75 @@ -/******************************************************************************* -SerialCommand - An Arduino library to tokenize and parse commands received over -a serial port. -Copyright (C) 2011-2013 Steven Cogswell -http://awtfy.com - -Version 20131021A. - -Version History: -May 11 2011 - Initial version -May 13 2011 - Prevent overwriting bounds of SerialCommandCallback[] array in addCommand() - defaultHandler() for non-matching commands -Mar 2012 - Some const char * changes to make compiler happier about deprecated warnings. - Arduino 1.0 compatibility (Arduino.h header) -Oct 2013 - SerialCommand object can be created using a SoftwareSerial object, for SoftwareSerial - support. Requires #include in your sketch even if you don't use - a SoftwareSerial port in the project. sigh. See Example Sketch for usage. -Oct 2013 - Conditional compilation for the SoftwareSerial support, in case you really, really - hate it and want it removed. - -This library is free software; you can redistribute it and/or -modify it under the terms of the GNU Lesser General Public -License as published by the Free Software Foundation; either -version 2.1 of the License, or (at your option) any later version. - -This library is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -Lesser General Public License for more details. - -You should have received a copy of the GNU Lesser General Public -License along with this library; if not, write to the Free Software -Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA -***********************************************************************************/ +/** + * SerialCommand - A Wiring/Arduino library to tokenize and parse commands + * received over a serial port. + * + * Copyright (C) 2012 Stefan Rado + * Copyright (C) 2011 Steven Cogswell + * http://husks.wordpress.com + * + * Version 20120522 + * + * This library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this library. If not, see . + */ #ifndef SerialCommand_h #define SerialCommand_h -#if defined(ARDUINO) && ARDUINO >= 100 -#include "Arduino.h" +#if defined(WIRING) && WIRING >= 100 + #include +#elif defined(ARDUINO) && ARDUINO >= 100 + #include #else -#include "WProgram.h" + #include #endif +#include -// If you want to use SerialCommand with the hardware serial port only, and want to disable -// SoftwareSerial support, and thus don't have to use "#include " in your -// sketches, then uncomment this define for SERIALCOMMAND_HARDWAREONLY, and comment out the -// corresponding #undef line. -// -// You don't have to use SoftwareSerial features if this is not defined, you can still only use -// the Hardware serial port, just that this way lets you get out of having to include -// the SoftwareSerial.h header. -//#define SERIALCOMMAND_HARDWAREONLY 1 -#undef SERIALCOMMAND_HARDWAREONLY +// Size of the input buffer in bytes (maximum length of one command plus arguments) +#define SERIALCOMMAND_BUFFER 32 +// Maximum length of a command excluding the terminating null +#define SERIALCOMMAND_MAXCOMMANDLENGTH 8 -#ifdef SERIALCOMMAND_HARDWAREONLY -#warning "Warning: Building SerialCommand without SoftwareSerial Support" -#endif +// Uncomment the next line to run the library in debug mode (verbose messages) +//#define SERIALCOMMAND_DEBUG -#ifndef SERIALCOMMAND_HARDWAREONLY -#include -#endif -#include +class SerialCommand { + public: + SerialCommand(); // Constructor + void addCommand(const char *command, void(*function)()); // Add a command to the processing dictionary. + void setDefaultHandler(void (*function)(const char *)); // A handler to call when no valid command received. + void readSerial(); // Main entry point. + void clearBuffer(); // Clears the input buffer. + char *next(); // Returns pointer to next token found in command buffer (for getting arguments to commands). -#define SERIALCOMMANDBUFFER 16 -#define MAXSERIALCOMMANDS 10 -#define MAXDELIMETER 2 + private: + // Command/handler dictionary + struct SerialCommandCallback { + char command[SERIALCOMMAND_MAXCOMMANDLENGTH + 1]; + void (*function)(); + }; // Data structure to hold Command/Handler function key-value pairs + SerialCommandCallback *commandList; // Actual definition for command/handler array + byte commandCount; -#define SERIALCOMMANDDEBUG 1 -#undef SERIALCOMMANDDEBUG // Comment this out to run the library in debug mode (verbose messages) + // Pointer to the default handler function + void (*defaultHandler)(const char *); -class SerialCommand -{ - public: - SerialCommand(); // Constructor - #ifndef SERIALCOMMAND_HARDWAREONLY - SerialCommand(SoftwareSerial &SoftSer); // Constructor for using SoftwareSerial objects - #endif + char delim[2]; // null-terminated list of character to be used as delimeters for tokenizing (default " ") + char term; // Character that signals end of command (default '\n') - void clearBuffer(); // Sets the command buffer to all '\0' (nulls) - char *next(); // returns pointer to next token found in command buffer (for getting arguments to commands) - void readSerial(); // Main entry point. - void addCommand(const char *, void(*)()); // Add commands to processing dictionary - void addDefaultHandler(void (*function)()); // A handler to call when no valid command received. - - private: - char inChar; // A character read from the serial stream - char buffer[SERIALCOMMANDBUFFER]; // Buffer of stored characters while waiting for terminator character - int bufPos; // Current position in the buffer - char delim[MAXDELIMETER]; // null-terminated list of character to be used as delimeters for tokenizing (default " ") - char term; // Character that signals end of command (default '\r') - char *token; // Returned token from the command buffer as returned by strtok_r - char *last; // State variable used by strtok_r during processing - typedef struct _callback { - char command[SERIALCOMMANDBUFFER]; - void (*function)(); - } SerialCommandCallback; // Data structure to hold Command/Handler function key-value pairs - int numCommand; - SerialCommandCallback CommandList[MAXSERIALCOMMANDS]; // Actual definition for command/handler array - void (*defaultHandler)(); // Pointer to the default handler function - int usingSoftwareSerial; // Used as boolean to see if we're using SoftwareSerial object or not - #ifndef SERIALCOMMAND_HARDWAREONLY - SoftwareSerial *SoftSerial; // Pointer to a user-created SoftwareSerial object - #endif + char buffer[SERIALCOMMAND_BUFFER + 1]; // Buffer of stored characters while waiting for terminator character + byte bufPos; // Current position in the buffer + char *last; // State variable used by strtok_r during processing }; -#endif //SerialCommand_h \ No newline at end of file +#endif //SerialCommand_h diff --git a/software/arduino/libraries/SerialCommand/examples/SerialCommandExample/SerialCommandExample.pde b/software/arduino/libraries/SerialCommand/examples/SerialCommandExample/SerialCommandExample.pde index 9f5ef16..8d0bf4a 100644 --- a/software/arduino/libraries/SerialCommand/examples/SerialCommandExample/SerialCommandExample.pde +++ b/software/arduino/libraries/SerialCommand/examples/SerialCommandExample/SerialCommandExample.pde @@ -2,97 +2,82 @@ // Steven Cogswell // May 2011 -#include // We need this even if we're not using a SoftwareSerial object - // Due to the way the Arduino IDE compiles #include #define arduinoLED 13 // Arduino LED on board -SerialCommand SCmd; // The demo SerialCommand object +SerialCommand sCmd; // The demo SerialCommand object -void setup() -{ - pinMode(arduinoLED,OUTPUT); // Configure the onboard LED for output - digitalWrite(arduinoLED,LOW); // default to LED off +void setup() { + pinMode(arduinoLED, OUTPUT); // Configure the onboard LED for output + digitalWrite(arduinoLED, LOW); // default to LED off - Serial.begin(9600); - - // Setup callbacks for SerialCommand commands - SCmd.addCommand("ON",LED_on); // Turns LED on - SCmd.addCommand("OFF",LED_off); // Turns LED off - SCmd.addCommand("HELLO",SayHello); // Echos the string argument back - SCmd.addCommand("P",process_command); // Converts two arguments to integers and echos them back - SCmd.addDefaultHandler(unrecognized); // Handler for command that isn't matched (says "What?") - Serial.println("Ready"); + Serial.begin(9600); + // Setup callbacks for SerialCommand commands + sCmd.addCommand("ON", LED_on); // Turns LED on + sCmd.addCommand("OFF", LED_off); // Turns LED off + sCmd.addCommand("HELLO", sayHello); // Echos the string argument back + sCmd.addCommand("P", processCommand); // Converts two arguments to integers and echos them back + sCmd.setDefaultHandler(unrecognized); // Handler for command that isn't matched (says "What?") + Serial.println("Ready"); } -void loop() -{ - SCmd.readSerial(); // We don't do much, just process serial commands +void loop() { + sCmd.readSerial(); // We don't do much, just process serial commands } -void LED_on() -{ - Serial.println("LED on"); - digitalWrite(arduinoLED,HIGH); +void LED_on() { + Serial.println("LED on"); + digitalWrite(arduinoLED, HIGH); } -void LED_off() -{ - Serial.println("LED off"); - digitalWrite(arduinoLED,LOW); +void LED_off() { + Serial.println("LED off"); + digitalWrite(arduinoLED, LOW); } -void SayHello() -{ - char *arg; - arg = SCmd.next(); // Get the next argument from the SerialCommand object buffer - if (arg != NULL) // As long as it existed, take it - { - Serial.print("Hello "); - Serial.println(arg); - } +void sayHello() { + char *arg; + arg = sCmd.next(); // Get the next argument from the SerialCommand object buffer + if (arg != NULL) { // As long as it existed, take it + Serial.print("Hello "); + Serial.println(arg); + } else { - Serial.println("Hello, whoever you are"); + Serial.println("Hello, whoever you are"); } } -void process_command() -{ - int aNumber; - char *arg; +void processCommand() { + int aNumber; + char *arg; - Serial.println("We're in process_command"); - arg = SCmd.next(); - if (arg != NULL) - { - aNumber=atoi(arg); // Converts a char string to an integer - Serial.print("First argument was: "); - Serial.println(aNumber); - } + Serial.println("We're in processCommand"); + arg = sCmd.next(); + if (arg != NULL) { + aNumber = atoi(arg); // Converts a char string to an integer + Serial.print("First argument was: "); + Serial.println(aNumber); + } else { - Serial.println("No arguments"); + Serial.println("No arguments"); } - arg = SCmd.next(); - if (arg != NULL) - { - aNumber=atol(arg); - Serial.print("Second argument was: "); - Serial.println(aNumber); - } + arg = sCmd.next(); + if (arg != NULL) { + aNumber = atol(arg); + Serial.print("Second argument was: "); + Serial.println(aNumber); + } else { - Serial.println("No second argument"); + Serial.println("No second argument"); } - } -// This gets set as the default handler, and gets called when no other command matches. -void unrecognized() -{ - Serial.println("What?"); +// This gets set as the default handler, and gets called when no other command matches. +void unrecognized(const char *command) { + Serial.println("What?"); } - diff --git a/software/arduino/libraries/SerialCommand/examples/SerialCommandExampleSoftwareSerial/SerialCommandExampleSoftwareSerial.ino b/software/arduino/libraries/SerialCommand/examples/SerialCommandExampleSoftwareSerial/SerialCommandExampleSoftwareSerial.ino deleted file mode 100644 index 13941b0..0000000 --- a/software/arduino/libraries/SerialCommand/examples/SerialCommandExampleSoftwareSerial/SerialCommandExampleSoftwareSerial.ino +++ /dev/null @@ -1,136 +0,0 @@ -// Demo Code for SerialCommand Library. -// This example uses a SoftwareSerial object instead of the Hardware Serial port. -// -// Make sure your SoftwareSerial port is actually functioning correctly before -// you use this. See sketch for SoftwareSerialCheck included with this library. -// -// For this demo, all the command output is also echoed to the Hardware Serial port -// which you might find useful for debugging that you have it working correctly. -// -// Steven Cogswell -// October 2013 - -#include -#include - -#define arduinoLED 13 // Arduino LED on board - -// Parameters for SoftwareSerial port. For this example a Sparkfun CP2103 Breakout Module -// was used. Change defines as appopriate for your use. -// CP2103 -- Arduino -// 3.3V 3.3V -// TX-O pin 7 -// RX-I pin 6 -// GND GND -#define TX 6 -#define RX 7 -SoftwareSerial testSoftSerial = SoftwareSerial(RX,TX); // The SoftwareSerial Object - -SerialCommand SCmd(testSoftSerial); // The demo SerialCommand object, using the SoftwareSerial Constructor - -void setup() -{ - pinMode(arduinoLED,OUTPUT); // Configure the onboard LED for output - digitalWrite(arduinoLED,LOW); // default to LED off - - Serial.begin(9600); - testSoftSerial.begin(9600); - - // Setup callbacks for SerialCommand commands - SCmd.addCommand("ON",LED_on); // Turns LED on - SCmd.addCommand("OFF",LED_off); // Turns LED off - SCmd.addCommand("HELLO",SayHello); // Echos the string argument back - SCmd.addCommand("P",process_command); // Converts two arguments to integers and echos them back - SCmd.addDefaultHandler(unrecognized); // Handler for command that isn't matched (says "What?") - Serial.println("Ready"); - testSoftSerial.println("SoftSerial Port Ready"); - testSoftSerial.println("Command Output will also echo to Hardware Serial Port"); -} - -void loop() -{ - SCmd.readSerial(); // We don't do much, just process serial commands -} - - -void LED_on() -{ - Serial.println("LED on"); - testSoftSerial.println("(softserial) LED on"); - digitalWrite(arduinoLED,HIGH); -} - -void LED_off() -{ - Serial.println("LED off"); - testSoftSerial.println("(softserial) LED off"); - - digitalWrite(arduinoLED,LOW); -} - -void SayHello() -{ - char *arg; - arg = SCmd.next(); // Get the next argument from the SerialCommand object buffer - if (arg != NULL) // As long as it existed, take it - { - Serial.print("Hello "); - Serial.println(arg); - - testSoftSerial.print("(softserial) Hello "); - testSoftSerial.println(arg); - } - else { - Serial.println("Hello, whoever you are"); - testSoftSerial.println("(softserial) Hello, whoever you are"); - } -} - - -void process_command() -{ - int aNumber; - char *arg; - - Serial.println("We're in process_command"); - testSoftSerial.println("(softserial) We're in process_command"); - arg = SCmd.next(); - if (arg != NULL) - { - aNumber=atoi(arg); // Converts a char string to an integer - Serial.print("First argument was: "); - Serial.println(aNumber); - - testSoftSerial.print("(softserial) First argument was: "); - testSoftSerial.println(aNumber); - } - else { - Serial.println("No arguments"); - testSoftSerial.println("(softserial) No Arguments"); - } - - arg = SCmd.next(); - if (arg != NULL) - { - aNumber=atol(arg); - Serial.print("Second argument was: "); - Serial.println(aNumber); - - testSoftSerial.print("(softserial) Second argument was: "); - testSoftSerial.println(aNumber); - } - else { - Serial.println("No second argument"); - testSoftSerial.println("No second argument"); - - } - -} - -// This gets set as the default handler, and gets called when no other command matches. -void unrecognized() -{ - Serial.println("What?"); - testSoftSerial.println("(softserial) What?"); -} - diff --git a/software/arduino/libraries/SerialCommand/examples/SerialCommandHardwareOnlyExample/SerialCommandHardwareOnlyExample.ino b/software/arduino/libraries/SerialCommand/examples/SerialCommandHardwareOnlyExample/SerialCommandHardwareOnlyExample.ino deleted file mode 100644 index 40fbc77..0000000 --- a/software/arduino/libraries/SerialCommand/examples/SerialCommandHardwareOnlyExample/SerialCommandHardwareOnlyExample.ino +++ /dev/null @@ -1,102 +0,0 @@ -// Demo Code for SerialCommand Library -// Steven Cogswell -// May 2011 - -// If you want to use HardwareSerial only, and not have to include SoftwareSerial support, you -// can define SERIALCOMMAND_HARDWAREONLY in SerialCommand.h, which will cause it to build without -// SoftwareSerial support. This makes the library act as it used to before SoftwareSerial -// support was added, and you don't need this next include: -//#include - -#include - -#define arduinoLED 13 // Arduino LED on board - -SerialCommand SCmd; // The demo SerialCommand object - -void setup() -{ - pinMode(arduinoLED,OUTPUT); // Configure the onboard LED for output - digitalWrite(arduinoLED,LOW); // default to LED off - - Serial.begin(9600); - - // Setup callbacks for SerialCommand commands - SCmd.addCommand("ON",LED_on); // Turns LED on - SCmd.addCommand("OFF",LED_off); // Turns LED off - SCmd.addCommand("HELLO",SayHello); // Echos the string argument back - SCmd.addCommand("P",process_command); // Converts two arguments to integers and echos them back - SCmd.addDefaultHandler(unrecognized); // Handler for command that isn't matched (says "What?") - Serial.println("Ready"); - -} - -void loop() -{ - SCmd.readSerial(); // We don't do much, just process serial commands -} - - -void LED_on() -{ - Serial.println("LED on"); - digitalWrite(arduinoLED,HIGH); -} - -void LED_off() -{ - Serial.println("LED off"); - digitalWrite(arduinoLED,LOW); -} - -void SayHello() -{ - char *arg; - arg = SCmd.next(); // Get the next argument from the SerialCommand object buffer - if (arg != NULL) // As long as it existed, take it - { - Serial.print("Hello "); - Serial.println(arg); - } - else { - Serial.println("Hello, whoever you are"); - } -} - - -void process_command() -{ - int aNumber; - char *arg; - - Serial.println("We're in process_command"); - arg = SCmd.next(); - if (arg != NULL) - { - aNumber=atoi(arg); // Converts a char string to an integer - Serial.print("First argument was: "); - Serial.println(aNumber); - } - else { - Serial.println("No arguments"); - } - - arg = SCmd.next(); - if (arg != NULL) - { - aNumber=atol(arg); - Serial.print("Second argument was: "); - Serial.println(aNumber); - } - else { - Serial.println("No second argument"); - } - -} - -// This gets set as the default handler, and gets called when no other command matches. -void unrecognized() -{ - Serial.println("What?"); -} - diff --git a/software/arduino/libraries/SerialCommand/examples/SoftwareSerialCheck/SoftwareSerialCheck.ino b/software/arduino/libraries/SerialCommand/examples/SoftwareSerialCheck/SoftwareSerialCheck.ino deleted file mode 100644 index 451320e..0000000 --- a/software/arduino/libraries/SerialCommand/examples/SoftwareSerialCheck/SoftwareSerialCheck.ino +++ /dev/null @@ -1,26 +0,0 @@ -#include - -// Do you actually have your SoftwareSerial hooked up correctly? Find out with this. -// Tested with Sparkfun CPD2103 module. - -#define TX 6 -#define RX 7 -SoftwareSerial SoS = SoftwareSerial(RX,TX); - - -void setup() { - Serial.begin(9600); - SoS.begin(9600); - Serial.println("Starting up"); - SoS.println("(Software) Starting up"); -} - -void loop() { - Serial.print("(Hardware) I'm sending something... "); - SoS.println("(Software) Hello out the Software Serial Port"); - SoS.println("(Software) This one is the Software Serial Port"); - Serial.println("I sent two lines"); - delay(1000); -} - - diff --git a/software/arduino/libraries/SerialCommand/keywords.txt b/software/arduino/libraries/SerialCommand/keywords.txt index 9e19284..45ada90 100644 --- a/software/arduino/libraries/SerialCommand/keywords.txt +++ b/software/arduino/libraries/SerialCommand/keywords.txt @@ -1,5 +1,23 @@ -SerialCommand KEYWORD1 -clearBuffer KEYWORD2 -next KEYWORD2 -readSerial KEYWORD2 -addCommand KEYWORd2 \ No newline at end of file +####################################### +# Datatypes (KEYWORD1) +####################################### + +SerialCommand KEYWORD1 + +####################################### +# Methods and Functions (KEYWORD2) +####################################### + +addCommand KEYWORD2 +setDefaultHandler KEYWORD2 +readSerial KEYWORD2 +clearBuffer KEYWORD2 +next KEYWORD2 + +####################################### +# Instances (KEYWORD2) +####################################### + +####################################### +# Constants (LITERAL1) +####################################### diff --git a/software/arduino/libraries/SerialCommand/readme.md b/software/arduino/libraries/SerialCommand/readme.md new file mode 100644 index 0000000..c439eca --- /dev/null +++ b/software/arduino/libraries/SerialCommand/readme.md @@ -0,0 +1,7 @@ +SerialCommand +============= +A Wiring/Arduino library to tokenize and parse commands received over a serial port. + +The original version of this library was written by [Steven Cogswell](http://husks.wordpress.com) (published May 23, 2011 in his blog post ["A Minimal Arduino Library for Processing Serial Commands"](http://husks.wordpress.com/2011/05/23/a-minimal-arduino-library-for-processing-serial-commands/)). + +This is a heavily modified version with smaller footprint and a cleaned up code by Stefan Rado. diff --git a/software/arduino/libraries/Time/DateStrings.cpp b/software/arduino/libraries/Time/DateStrings.cpp new file mode 100644 index 0000000..4facb73 --- /dev/null +++ b/software/arduino/libraries/Time/DateStrings.cpp @@ -0,0 +1,90 @@ +/* DateStrings.cpp + * Definitions for date strings for use with the Time library + * + * No memory is consumed in the sketch if your code does not call any of the string methods + * You can change the text of the strings, make sure the short strings are each exactly 3 characters + * the long strings can be any length up to the constant dt_MAX_STRING_LEN defined in Time.h + * + */ + +#if defined(__AVR__) +#include +#else +// for compatiblity with Arduino Due and Teensy 3.0 and maybe others? +#define PROGMEM +#define PGM_P const char * +#define pgm_read_byte(addr) (*(const unsigned char *)(addr)) +#define pgm_read_word(addr) (*(const unsigned char **)(addr)) +#define strcpy_P(dest, src) strcpy((dest), (src)) +#endif +#include // for strcpy_P or strcpy +#include "Time.h" + +// the short strings for each day or month must be exactly dt_SHORT_STR_LEN +#define dt_SHORT_STR_LEN 3 // the length of short strings + +static char buffer[dt_MAX_STRING_LEN+1]; // must be big enough for longest string and the terminating null + +const char monthStr1[] PROGMEM = "January"; +const char monthStr2[] PROGMEM = "February"; +const char monthStr3[] PROGMEM = "March"; +const char monthStr4[] PROGMEM = "April"; +const char monthStr5[] PROGMEM = "May"; +const char monthStr6[] PROGMEM = "June"; +const char monthStr7[] PROGMEM = "July"; +const char monthStr8[] PROGMEM = "August"; +const char monthStr9[] PROGMEM = "September"; +const char monthStr10[] PROGMEM = "October"; +const char monthStr11[] PROGMEM = "November"; +const char monthStr12[] PROGMEM = "December"; + +PGM_P monthNames_P[] PROGMEM = +{ + "",monthStr1,monthStr2,monthStr3,monthStr4,monthStr5,monthStr6, + monthStr7,monthStr8,monthStr9,monthStr10,monthStr11,monthStr12 +}; + +const char monthShortNames_P[] PROGMEM = "ErrJanFebMarAprMayJunJulAugSepOctNovDec"; + +const char dayStr0[] PROGMEM = "Err"; +const char dayStr1[] PROGMEM = "Sunday"; +const char dayStr2[] PROGMEM = "Monday"; +const char dayStr3[] PROGMEM = "Tuesday"; +const char dayStr4[] PROGMEM = "Wednesday"; +const char dayStr5[] PROGMEM = "Thursday"; +const char dayStr6[] PROGMEM = "Friday"; +const char dayStr7[] PROGMEM = "Saturday"; + +PGM_P dayNames_P[] PROGMEM = { dayStr0,dayStr1,dayStr2,dayStr3,dayStr4,dayStr5,dayStr6,dayStr7}; +char dayShortNames_P[] PROGMEM = "ErrSunMonTueWedThrFriSat"; + +/* functions to return date strings */ + +char* monthStr(uint8_t month) +{ + strcpy_P(buffer, (PGM_P)pgm_read_word(&(monthNames_P[month]))); + return buffer; +} + +char* monthShortStr(uint8_t month) +{ + for (int i=0; i < dt_SHORT_STR_LEN; i++) + buffer[i] = pgm_read_byte(&(monthShortNames_P[i+ (month*dt_SHORT_STR_LEN)])); + buffer[dt_SHORT_STR_LEN] = 0; + return buffer; +} + +char* dayStr(uint8_t day) +{ + strcpy_P(buffer, (PGM_P)pgm_read_word(&(dayNames_P[day]))); + return buffer; +} + +char* dayShortStr(uint8_t day) +{ + uint8_t index = day*dt_SHORT_STR_LEN; + for (int i=0; i < dt_SHORT_STR_LEN; i++) + buffer[i] = pgm_read_byte(&(dayShortNames_P[index + i])); + buffer[dt_SHORT_STR_LEN] = 0; + return buffer; +} diff --git a/software/arduino/libraries/Time/Readme.txt b/software/arduino/libraries/Time/Readme.txt new file mode 100644 index 0000000..67b148e --- /dev/null +++ b/software/arduino/libraries/Time/Readme.txt @@ -0,0 +1,131 @@ +Readme file for Arduino Time Library + +Time is a library that provides timekeeping functionality for Arduino. + +The code is derived from the Playground DateTime library but is updated +to provide an API that is more flexable and easier to use. + +A primary goal was to enable date and time functionality that can be used with +a variety of external time sources with minimum differences required in sketch logic. + +Example sketches illustrate how similar sketch code can be used with: a Real Time Clock, +internet NTP time service, GPS time data, and Serial time messages from a computer +for time synchronization. + +The functions available in the library include: + +hour(); // the hour now (0-23) +minute(); // the minute now (0-59) +second(); // the second now (0-59) +day(); // the day now (1-31) +weekday(); // day of the week, Sunday is day 0 +month(); // the month now (1-12) +year(); // the full four digit year: (2009, 2010 etc) + +there are also functions to return the hour in 12 hour format +hourFormat12(); // the hour now in 12 hour format +isAM(); // returns true if time now is AM +isPM(); // returns true if time now is PM + +now(); // returns the current time as seconds since Jan 1 1970 + +The time and date functions can take an optional parameter for the time. This prevents +errors if the time rolls over between elements. For example, if a new minute begins +between getting the minute and second, the values will be inconsistent. Using the +following functions eliminates this probglem + time_t t = now(); // store the current time in time variable t + hour(t); // returns the hour for the given time t + minute(t); // returns the minute for the given time t + second(t); // returns the second for the given time t + day(t); // the day for the given time t + weekday(t); // day of the week for the given time t + month(t); // the month for the given time t + year(t); // the year for the given time t + + +Functions for managing the timer services are: +setTime(t); // set the system time to the give time t +setTime(hr,min,sec,day,mnth,yr); // alternative to above, yr is 2 or 4 digit yr (2010 or 10 sets year to 2010) +adjustTime(adjustment); // adjust system time by adding the adjustment value + +timeStatus(); // indicates if time has been set and recently synchronized + // returns one of the following enumerations: + timeNotSet // the time has never been set, the clock started at Jan 1 1970 + timeNeedsSync // the time had been set but a sync attempt did not succeed + timeSet // the time is set and is synced +Time and Date values are not valid if the status is timeNotSet. Otherwise values can be used but +the returned time may have drifted if the status is timeNeedsSync. + +setSyncProvider(getTimeFunction); // set the external time provider +setSyncInterval(interval); // set the number of seconds between re-sync + + +There are many convenience macros in the time.h file for time constants and conversion of time units. + +To use the library, copy the download to the Library directory. + +The Time directory contains the Time library and some example sketches +illustrating how the library can be used with various time sources: + +- TimeSerial.pde shows Arduino as a clock without external hardware. + It is synchronized by time messages sent over the serial port. + A companion Processing sketch will automatically provide these messages + if it is running and connected to the Arduino serial port. + +- TimeSerialDateStrings.pde adds day and month name strings to the sketch above + Short (3 character) and long strings are available to print the days of + the week and names of the months. + +- TimeRTC uses a DS1307 real time clock to provide time synchronization. + A basic RTC library named DS1307RTC is included in the download. + To run this sketch the DS1307RTC library must be installed. + +- TimeRTCSet is similar to the above and adds the ability to set the Real Time Clock + +- TimeRTCLog demonstrates how to calculate the difference between times. + It is a vary simple logger application that monitors events on digtial pins + and prints (to the serial port) the time of an event and the time period since the previous event. + +- TimeNTP uses the Arduino Ethernet shield to access time using the internet NTP time service. + The NTP protocol uses UDP and the UdpBytewise library is required, see: + http://bitbucket.org/bjoern/arduino_osc/src/14667490521f/libraries/Ethernet/ + +- TimeGPS gets time from a GPS + This requires the TinyGPS library from Mikal Hart: + http://arduiniana.org/libraries/TinyGPS + +Differences between this code and the playground DateTime library +although the Time library is based on the DateTime codebase, the API has changed. +Changes in the Time library API: +- time elements are functions returning int (they are variables in DateTime) +- Years start from 1970 +- days of the week and months start from 1 (they start from 0 in DateTime) +- DateStrings do not require a seperate library +- time elements can be accessed non-atomically (in DateTime they are always atomic) +- function added to automatically sync time with extrnal source +- localTime and maketime parameters changed, localTime renamed to breakTime + +Technical notes: + +Internal system time is based on the standard Unix time_t. +The value is the number of seconds since Jan 1 1970. +System time begins at zero when the sketch starts. + +The internal time can be automatically synchronized at regular intervals to an external time source. +This is enabled by calling the setSyncProvider(provider) function - the provider argument is +the address of a function that returns the current time as a time_t. +See the sketches in the examples directory for usage. + +The default interval for re-syncing the time is 5 minutes but can be changed by calling the +setSyncInterval( interval) method to set the number of seconds between re-sync attempts. + +The Time library defines a structure for holding time elements that is a compact version of the C tm structure. +All the members of the Arduino tm structure are bytes and the year is offset from 1970. +Convenience macros provide conversion to and from the Arduino format. + +Low level functions to convert between system time and individual time elements are provided: + breakTime( time, &tm); // break time_t into elements stored in tm struct + makeTime( &tm); // return time_t from elements stored in tm struct + +The DS1307RTC library included in the download provides an example of how a time provider +can use the low level functions to interface with the Time library. diff --git a/software/arduino/libraries/Time/Time.cpp b/software/arduino/libraries/Time/Time.cpp new file mode 100644 index 0000000..9d53cd7 --- /dev/null +++ b/software/arduino/libraries/Time/Time.cpp @@ -0,0 +1,318 @@ +/* + time.c - low level time and date functions + Copyright (c) Michael Margolis 2009 + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + + 6 Jan 2010 - initial release + 12 Feb 2010 - fixed leap year calculation error + 1 Nov 2010 - fixed setTime bug (thanks to Korman for this) + 24 Mar 2012 - many edits by Paul Stoffregen: fixed timeStatus() to update + status, updated examples for Arduino 1.0, fixed ARM + compatibility issues, added TimeArduinoDue and TimeTeensy3 + examples, add error checking and messages to RTC examples, + add examples to DS1307RTC library. +*/ + +#if ARDUINO >= 100 +#include +#else +#include +#endif + +#include "Time.h" + +static tmElements_t tm; // a cache of time elements +static time_t cacheTime; // the time the cache was updated +static uint32_t syncInterval = 300; // time sync will be attempted after this many seconds + +void refreshCache(time_t t) { + if (t != cacheTime) { + breakTime(t, tm); + cacheTime = t; + } +} + +int hour() { // the hour now + return hour(now()); +} + +int hour(time_t t) { // the hour for the given time + refreshCache(t); + return tm.Hour; +} + +int hourFormat12() { // the hour now in 12 hour format + return hourFormat12(now()); +} + +int hourFormat12(time_t t) { // the hour for the given time in 12 hour format + refreshCache(t); + if( tm.Hour == 0 ) + return 12; // 12 midnight + else if( tm.Hour > 12) + return tm.Hour - 12 ; + else + return tm.Hour ; +} + +uint8_t isAM() { // returns true if time now is AM + return !isPM(now()); +} + +uint8_t isAM(time_t t) { // returns true if given time is AM + return !isPM(t); +} + +uint8_t isPM() { // returns true if PM + return isPM(now()); +} + +uint8_t isPM(time_t t) { // returns true if PM + return (hour(t) >= 12); +} + +int minute() { + return minute(now()); +} + +int minute(time_t t) { // the minute for the given time + refreshCache(t); + return tm.Minute; +} + +int second() { + return second(now()); +} + +int second(time_t t) { // the second for the given time + refreshCache(t); + return tm.Second; +} + +int day(){ + return(day(now())); +} + +int day(time_t t) { // the day for the given time (0-6) + refreshCache(t); + return tm.Day; +} + +int weekday() { // Sunday is day 1 + return weekday(now()); +} + +int weekday(time_t t) { + refreshCache(t); + return tm.Wday; +} + +int month(){ + return month(now()); +} + +int month(time_t t) { // the month for the given time + refreshCache(t); + return tm.Month; +} + +int year() { // as in Processing, the full four digit year: (2009, 2010 etc) + return year(now()); +} + +int year(time_t t) { // the year for the given time + refreshCache(t); + return tmYearToCalendar(tm.Year); +} + +/*============================================================================*/ +/* functions to convert to and from system time */ +/* These are for interfacing with time serivces and are not normally needed in a sketch */ + +// leap year calulator expects year argument as years offset from 1970 +#define LEAP_YEAR(Y) ( ((1970+Y)>0) && !((1970+Y)%4) && ( ((1970+Y)%100) || !((1970+Y)%400) ) ) + +static const uint8_t monthDays[]={31,28,31,30,31,30,31,31,30,31,30,31}; // API starts months from 1, this array starts from 0 + +void breakTime(time_t timeInput, tmElements_t &tm){ +// break the given time_t into time components +// this is a more compact version of the C library localtime function +// note that year is offset from 1970 !!! + + uint8_t year; + uint8_t month, monthLength; + uint32_t time; + unsigned long days; + + time = (uint32_t)timeInput; + tm.Second = time % 60; + time /= 60; // now it is minutes + tm.Minute = time % 60; + time /= 60; // now it is hours + tm.Hour = time % 24; + time /= 24; // now it is days + tm.Wday = ((time + 4) % 7) + 1; // Sunday is day 1 + + year = 0; + days = 0; + while((unsigned)(days += (LEAP_YEAR(year) ? 366 : 365)) <= time) { + year++; + } + tm.Year = year; // year is offset from 1970 + + days -= LEAP_YEAR(year) ? 366 : 365; + time -= days; // now it is days in this year, starting at 0 + + days=0; + month=0; + monthLength=0; + for (month=0; month<12; month++) { + if (month==1) { // february + if (LEAP_YEAR(year)) { + monthLength=29; + } else { + monthLength=28; + } + } else { + monthLength = monthDays[month]; + } + + if (time >= monthLength) { + time -= monthLength; + } else { + break; + } + } + tm.Month = month + 1; // jan is month 1 + tm.Day = time + 1; // day of month +} + +time_t makeTime(tmElements_t &tm){ +// assemble time elements into time_t +// note year argument is offset from 1970 (see macros in time.h to convert to other formats) +// previous version used full four digit year (or digits since 2000),i.e. 2009 was 2009 or 9 + + int i; + uint32_t seconds; + + // seconds from 1970 till 1 jan 00:00:00 of the given year + seconds= tm.Year*(SECS_PER_DAY * 365); + for (i = 0; i < tm.Year; i++) { + if (LEAP_YEAR(i)) { + seconds += SECS_PER_DAY; // add extra days for leap years + } + } + + // add days for this year, months start from 1 + for (i = 1; i < tm.Month; i++) { + if ( (i == 2) && LEAP_YEAR(tm.Year)) { + seconds += SECS_PER_DAY * 29; + } else { + seconds += SECS_PER_DAY * monthDays[i-1]; //monthDay array starts from 0 + } + } + seconds+= (tm.Day-1) * SECS_PER_DAY; + seconds+= tm.Hour * SECS_PER_HOUR; + seconds+= tm.Minute * SECS_PER_MIN; + seconds+= tm.Second; + return (time_t)seconds; +} +/*=====================================================*/ +/* Low level system time functions */ + +static uint32_t sysTime = 0; +static uint32_t prevMillis = 0; +static uint32_t nextSyncTime = 0; +static timeStatus_t Status = timeNotSet; + +getExternalTime getTimePtr; // pointer to external sync function +//setExternalTime setTimePtr; // not used in this version + +#ifdef TIME_DRIFT_INFO // define this to get drift data +time_t sysUnsyncedTime = 0; // the time sysTime unadjusted by sync +#endif + + +time_t now() { + while (millis() - prevMillis >= 1000){ + sysTime++; + prevMillis += 1000; +#ifdef TIME_DRIFT_INFO + sysUnsyncedTime++; // this can be compared to the synced time to measure long term drift +#endif + } + if (nextSyncTime <= sysTime) { + if (getTimePtr != 0) { + time_t t = getTimePtr(); + if (t != 0) { + setTime(t); + } else { + nextSyncTime = sysTime + syncInterval; + Status = (Status == timeNotSet) ? timeNotSet : timeNeedsSync; + } + } + } + return (time_t)sysTime; +} + +void setTime(time_t t) { +#ifdef TIME_DRIFT_INFO + if(sysUnsyncedTime == 0) + sysUnsyncedTime = t; // store the time of the first call to set a valid Time +#endif + + sysTime = (uint32_t)t; + nextSyncTime = (uint32_t)t + syncInterval; + Status = timeSet; + prevMillis = millis(); // restart counting from now (thanks to Korman for this fix) +} + +void setTime(int hr,int min,int sec,int dy, int mnth, int yr){ + // year can be given as full four digit year or two digts (2010 or 10 for 2010); + //it is converted to years since 1970 + if( yr > 99) + yr = yr - 1970; + else + yr += 30; + tm.Year = yr; + tm.Month = mnth; + tm.Day = dy; + tm.Hour = hr; + tm.Minute = min; + tm.Second = sec; + setTime(makeTime(tm)); +} + +void adjustTime(long adjustment) { + sysTime += adjustment; +} + +// indicates if time has been set and recently synchronized +timeStatus_t timeStatus() { + now(); // required to actually update the status + return Status; +} + +void setSyncProvider( getExternalTime getTimeFunction){ + getTimePtr = getTimeFunction; + nextSyncTime = sysTime; + now(); // this will sync the clock +} + +void setSyncInterval(time_t interval){ // set the number of seconds between re-sync + syncInterval = (uint32_t)interval; + nextSyncTime = sysTime + syncInterval; +} diff --git a/software/arduino/libraries/Time/Time.h b/software/arduino/libraries/Time/Time.h new file mode 100644 index 0000000..61519f7 --- /dev/null +++ b/software/arduino/libraries/Time/Time.h @@ -0,0 +1,144 @@ +/* + time.h - low level time and date functions +*/ + +/* + July 3 2011 - fixed elapsedSecsThisWeek macro (thanks Vincent Valdy for this) + - fixed daysToTime_t macro (thanks maniacbug) +*/ + +#ifndef _Time_h +#ifdef __cplusplus +#define _Time_h + +#include +#ifndef __AVR__ +#include // for __time_t_defined, but avr libc lacks sys/types.h +#endif + + +#if !defined(__time_t_defined) // avoid conflict with newlib or other posix libc +typedef unsigned long time_t; +#endif + + +// This ugly hack allows us to define C++ overloaded functions, when included +// from within an extern "C", as newlib's sys/stat.h does. Actually it is +// intended to include "time.h" from the C library (on ARM, but AVR does not +// have that file at all). On Mac and Windows, the compiler will find this +// "Time.h" instead of the C library "time.h", so we may cause other weird +// and unpredictable effects by conflicting with the C library header "time.h", +// but at least this hack lets us define C++ functions as intended. Hopefully +// nothing too terrible will result from overriding the C library header?! +extern "C++" { +typedef enum {timeNotSet, timeNeedsSync, timeSet +} timeStatus_t ; + +typedef enum { + dowInvalid, dowSunday, dowMonday, dowTuesday, dowWednesday, dowThursday, dowFriday, dowSaturday +} timeDayOfWeek_t; + +typedef enum { + tmSecond, tmMinute, tmHour, tmWday, tmDay,tmMonth, tmYear, tmNbrFields +} tmByteFields; + +typedef struct { + uint8_t Second; + uint8_t Minute; + uint8_t Hour; + uint8_t Wday; // day of week, sunday is day 1 + uint8_t Day; + uint8_t Month; + uint8_t Year; // offset from 1970; +} tmElements_t, TimeElements, *tmElementsPtr_t; + +//convenience macros to convert to and from tm years +#define tmYearToCalendar(Y) ((Y) + 1970) // full four digit year +#define CalendarYrToTm(Y) ((Y) - 1970) +#define tmYearToY2k(Y) ((Y) - 30) // offset is from 2000 +#define y2kYearToTm(Y) ((Y) + 30) + +typedef time_t(*getExternalTime)(); +//typedef void (*setExternalTime)(const time_t); // not used in this version + + +/*==============================================================================*/ +/* Useful Constants */ +#define SECS_PER_MIN (60UL) +#define SECS_PER_HOUR (3600UL) +#define SECS_PER_DAY (SECS_PER_HOUR * 24UL) +#define DAYS_PER_WEEK (7UL) +#define SECS_PER_WEEK (SECS_PER_DAY * DAYS_PER_WEEK) +#define SECS_PER_YEAR (SECS_PER_WEEK * 52UL) +#define SECS_YR_2000 (946684800UL) // the time at the start of y2k + +/* Useful Macros for getting elapsed time */ +#define numberOfSeconds(_time_) (_time_ % SECS_PER_MIN) +#define numberOfMinutes(_time_) ((_time_ / SECS_PER_MIN) % SECS_PER_MIN) +#define numberOfHours(_time_) (( _time_% SECS_PER_DAY) / SECS_PER_HOUR) +#define dayOfWeek(_time_) ((( _time_ / SECS_PER_DAY + 4) % DAYS_PER_WEEK)+1) // 1 = Sunday +#define elapsedDays(_time_) ( _time_ / SECS_PER_DAY) // this is number of days since Jan 1 1970 +#define elapsedSecsToday(_time_) (_time_ % SECS_PER_DAY) // the number of seconds since last midnight +// The following macros are used in calculating alarms and assume the clock is set to a date later than Jan 1 1971 +// Always set the correct time before settting alarms +#define previousMidnight(_time_) (( _time_ / SECS_PER_DAY) * SECS_PER_DAY) // time at the start of the given day +#define nextMidnight(_time_) ( previousMidnight(_time_) + SECS_PER_DAY ) // time at the end of the given day +#define elapsedSecsThisWeek(_time_) (elapsedSecsToday(_time_) + ((dayOfWeek(_time_)-1) * SECS_PER_DAY) ) // note that week starts on day 1 +#define previousSunday(_time_) (_time_ - elapsedSecsThisWeek(_time_)) // time at the start of the week for the given time +#define nextSunday(_time_) ( previousSunday(_time_)+SECS_PER_WEEK) // time at the end of the week for the given time + + +/* Useful Macros for converting elapsed time to a time_t */ +#define minutesToTime_t ((M)) ( (M) * SECS_PER_MIN) +#define hoursToTime_t ((H)) ( (H) * SECS_PER_HOUR) +#define daysToTime_t ((D)) ( (D) * SECS_PER_DAY) // fixed on Jul 22 2011 +#define weeksToTime_t ((W)) ( (W) * SECS_PER_WEEK) + +/*============================================================================*/ +/* time and date functions */ +int hour(); // the hour now +int hour(time_t t); // the hour for the given time +int hourFormat12(); // the hour now in 12 hour format +int hourFormat12(time_t t); // the hour for the given time in 12 hour format +uint8_t isAM(); // returns true if time now is AM +uint8_t isAM(time_t t); // returns true the given time is AM +uint8_t isPM(); // returns true if time now is PM +uint8_t isPM(time_t t); // returns true the given time is PM +int minute(); // the minute now +int minute(time_t t); // the minute for the given time +int second(); // the second now +int second(time_t t); // the second for the given time +int day(); // the day now +int day(time_t t); // the day for the given time +int weekday(); // the weekday now (Sunday is day 1) +int weekday(time_t t); // the weekday for the given time +int month(); // the month now (Jan is month 1) +int month(time_t t); // the month for the given time +int year(); // the full four digit year: (2009, 2010 etc) +int year(time_t t); // the year for the given time + +time_t now(); // return the current time as seconds since Jan 1 1970 +void setTime(time_t t); +void setTime(int hr,int min,int sec,int day, int month, int yr); +void adjustTime(long adjustment); + +/* date strings */ +#define dt_MAX_STRING_LEN 9 // length of longest date string (excluding terminating null) +char* monthStr(uint8_t month); +char* dayStr(uint8_t day); +char* monthShortStr(uint8_t month); +char* dayShortStr(uint8_t day); + +/* time sync functions */ +timeStatus_t timeStatus(); // indicates if time has been set and recently synchronized +void setSyncProvider( getExternalTime getTimeFunction); // identify the external time provider +void setSyncInterval(time_t interval); // set the number of seconds between re-sync + +/* low level functions to convert to and from system time */ +void breakTime(time_t time, tmElements_t &tm); // break time_t into elements +time_t makeTime(tmElements_t &tm); // convert time elements into time_t + +} // extern "C++" +#endif // __cplusplus +#endif /* _Time_h */ + diff --git a/software/arduino/libraries/Time/examples/Processing/SyncArduinoClock/SyncArduinoClock.pde b/software/arduino/libraries/Time/examples/Processing/SyncArduinoClock/SyncArduinoClock.pde new file mode 100644 index 0000000..4313be3 --- /dev/null +++ b/software/arduino/libraries/Time/examples/Processing/SyncArduinoClock/SyncArduinoClock.pde @@ -0,0 +1,78 @@ +/** + * SyncArduinoClock. + * + * portIndex must be set to the port connected to the Arduino + * + * The current time is sent in response to request message from Arduino + * or by clicking the display window + * + * The time message is 11 ASCII text characters; a header (the letter 'T') + * followed by the ten digit system time (unix time) + */ + + +import processing.serial.*; +import java.util.Date; +import java.util.Calendar; +import java.util.GregorianCalendar; + +public static final short portIndex = 0; // select the com port, 0 is the first port +public static final String TIME_HEADER = "T"; //header for arduino serial time message +public static final char TIME_REQUEST = 7; // ASCII bell character +public static final char LF = 10; // ASCII linefeed +public static final char CR = 13; // ASCII linefeed +Serial myPort; // Create object from Serial class + +void setup() { + size(200, 200); + println(Serial.list()); + println(" Connecting to -> " + Serial.list()[portIndex]); + myPort = new Serial(this,Serial.list()[portIndex], 9600); + println(getTimeNow()); +} + +void draw() +{ + textSize(20); + textAlign(CENTER); + fill(0); + text("Click to send\nTime Sync", 0, 75, 200, 175); + if ( myPort.available() > 0) { // If data is available, + char val = char(myPort.read()); // read it and store it in val + if(val == TIME_REQUEST){ + long t = getTimeNow(); + sendTimeMessage(TIME_HEADER, t); + } + else + { + if(val == LF) + ; //igonore + else if(val == CR) + println(); + else + print(val); // echo everying but time request + } + } +} + +void mousePressed() { + sendTimeMessage( TIME_HEADER, getTimeNow()); +} + + +void sendTimeMessage(String header, long time) { + String timeStr = String.valueOf(time); + myPort.write(header); // send header and time to arduino + myPort.write(timeStr); + myPort.write('\n'); +} + +long getTimeNow(){ + // java time is in ms, we want secs + Date d = new Date(); + Calendar cal = new GregorianCalendar(); + long current = d.getTime()/1000; + long timezone = cal.get(cal.ZONE_OFFSET)/1000; + long daylight = cal.get(cal.DST_OFFSET)/1000; + return current + timezone + daylight; +} diff --git a/software/arduino/libraries/Time/examples/Processing/SyncArduinoClock/readme.txt b/software/arduino/libraries/Time/examples/Processing/SyncArduinoClock/readme.txt new file mode 100644 index 0000000..da9721d --- /dev/null +++ b/software/arduino/libraries/Time/examples/Processing/SyncArduinoClock/readme.txt @@ -0,0 +1,9 @@ +SyncArduinoClock is a Processing sketch that responds to Arduino requests for +time synchronization messages. + +The portIndex must be set the Serial port connected to Arduino. + +Download TimeSerial.pde onto Arduino and you should see the time +message displayed when you run SyncArduinoClock in Processing. +The Arduino time is set from the time on your computer through the +Processing sketch. diff --git a/software/arduino/libraries/Time/examples/TimeArduinoDue/TimeArduinoDue.ino b/software/arduino/libraries/Time/examples/TimeArduinoDue/TimeArduinoDue.ino new file mode 100644 index 0000000..acd80d2 --- /dev/null +++ b/software/arduino/libraries/Time/examples/TimeArduinoDue/TimeArduinoDue.ino @@ -0,0 +1,69 @@ +/* + * TimeRTC.pde + * example code illustrating Time library with Real Time Clock. + * + * This example requires Markus Lange's Arduino Due RTC Library + * https://github.com/MarkusLange/Arduino-Due-RTC-Library + */ + +#include +#include + +// Select the Slowclock source +//RTC_clock rtc_clock(RC); +RTC_clock rtc_clock(XTAL); + +void setup() { + Serial.begin(9600); + rtc_clock.init(); + if (rtc_clock.date_already_set() == 0) { + // Unfortunately, the Arduino Due hardware does not seem to + // be designed to maintain the RTC clock state when the + // board resets. Markus described it thusly: "Uhh the Due + // does reset with the NRSTB pin. This resets the full chip + // with all backup regions including RTC, RTT and SC. Only + // if the reset is done with the NRST pin will these regions + // stay with their old values." + rtc_clock.set_time(__TIME__); + rtc_clock.set_date(__DATE__); + } + setSyncProvider(getArduinoDueTime); + if(timeStatus()!= timeSet) + Serial.println("Unable to sync with the RTC"); + else + Serial.println("RTC has set the system time"); +} + +time_t getArduinoDueTime() +{ + return rtc_clock.unixtime(); +} + +void loop() +{ + digitalClockDisplay(); + delay(1000); +} + +void digitalClockDisplay(){ + // digital clock display of the time + Serial.print(hour()); + printDigits(minute()); + printDigits(second()); + Serial.print(" "); + Serial.print(day()); + Serial.print(" "); + Serial.print(month()); + Serial.print(" "); + Serial.print(year()); + Serial.println(); +} + +void printDigits(int digits){ + // utility function for digital clock display: prints preceding colon and leading 0 + Serial.print(":"); + if(digits < 10) + Serial.print('0'); + Serial.print(digits); +} + diff --git a/software/arduino/libraries/Time/examples/TimeGPS/TimeGPS.ino b/software/arduino/libraries/Time/examples/TimeGPS/TimeGPS.ino new file mode 100644 index 0000000..7a0213b --- /dev/null +++ b/software/arduino/libraries/Time/examples/TimeGPS/TimeGPS.ino @@ -0,0 +1,87 @@ +/* + * TimeGPS.pde + * example code illustrating time synced from a GPS + * + */ + +#include +#include // http://arduiniana.org/libraries/TinyGPS/ +#include +// TinyGPS and SoftwareSerial libraries are the work of Mikal Hart + +SoftwareSerial SerialGPS = SoftwareSerial(10, 11); // receive on pin 10 +TinyGPS gps; + +// To use a hardware serial port, which is far more efficient than +// SoftwareSerial, uncomment this line and remove SoftwareSerial +//#define SerialGPS Serial1 + +// Offset hours from gps time (UTC) +const int offset = 1; // Central European Time +//const int offset = -5; // Eastern Standard Time (USA) +//const int offset = -4; // Eastern Daylight Time (USA) +//const int offset = -8; // Pacific Standard Time (USA) +//const int offset = -7; // Pacific Daylight Time (USA) + +// Ideally, it should be possible to learn the time zone +// based on the GPS position data. However, that would +// require a complex library, probably incorporating some +// sort of database using Eric Muller's time zone shape +// maps, at http://efele.net/maps/tz/ + +time_t prevDisplay = 0; // when the digital clock was displayed + +void setup() +{ + Serial.begin(9600); + while (!Serial) ; // Needed for Leonardo only + SerialGPS.begin(4800); + Serial.println("Waiting for GPS time ... "); +} + +void loop() +{ + while (SerialGPS.available()) { + if (gps.encode(SerialGPS.read())) { // process gps messages + // when TinyGPS reports new data... + unsigned long age; + int Year; + byte Month, Day, Hour, Minute, Second; + gps.crack_datetime(&Year, &Month, &Day, &Hour, &Minute, &Second, NULL, &age); + if (age < 500) { + // set the Time to the latest GPS reading + setTime(Hour, Minute, Second, Day, Month, Year); + adjustTime(offset * SECS_PER_HOUR); + } + } + } + if (timeStatus()!= timeNotSet) { + if (now() != prevDisplay) { //update the display only if the time has changed + prevDisplay = now(); + digitalClockDisplay(); + } + } +} + +void digitalClockDisplay(){ + // digital clock display of the time + Serial.print(hour()); + printDigits(minute()); + printDigits(second()); + Serial.print(" "); + Serial.print(day()); + Serial.print(" "); + Serial.print(month()); + Serial.print(" "); + Serial.print(year()); + Serial.println(); +} + +void printDigits(int digits) { + // utility function for digital clock display: prints preceding colon and leading 0 + Serial.print(":"); + if(digits < 10) + Serial.print('0'); + Serial.print(digits); +} + diff --git a/software/arduino/libraries/Time/examples/TimeNTP/TimeNTP.ino b/software/arduino/libraries/Time/examples/TimeNTP/TimeNTP.ino new file mode 100644 index 0000000..0d3820e --- /dev/null +++ b/software/arduino/libraries/Time/examples/TimeNTP/TimeNTP.ino @@ -0,0 +1,135 @@ +/* + * Time_NTP.pde + * Example showing time sync to NTP time source + * + * This sketch uses the Ethernet library + */ + +#include +#include +#include +#include + +byte mac[] = { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED }; +// NTP Servers: +IPAddress timeServer(132, 163, 4, 101); // time-a.timefreq.bldrdoc.gov +// IPAddress timeServer(132, 163, 4, 102); // time-b.timefreq.bldrdoc.gov +// IPAddress timeServer(132, 163, 4, 103); // time-c.timefreq.bldrdoc.gov + + +const int timeZone = 1; // Central European Time +//const int timeZone = -5; // Eastern Standard Time (USA) +//const int timeZone = -4; // Eastern Daylight Time (USA) +//const int timeZone = -8; // Pacific Standard Time (USA) +//const int timeZone = -7; // Pacific Daylight Time (USA) + + +EthernetUDP Udp; +unsigned int localPort = 8888; // local port to listen for UDP packets + +void setup() +{ + Serial.begin(9600); + while (!Serial) ; // Needed for Leonardo only + delay(250); + Serial.println("TimeNTP Example"); + if (Ethernet.begin(mac) == 0) { + // no point in carrying on, so do nothing forevermore: + while (1) { + Serial.println("Failed to configure Ethernet using DHCP"); + delay(10000); + } + } + Serial.print("IP number assigned by DHCP is "); + Serial.println(Ethernet.localIP()); + Udp.begin(localPort); + Serial.println("waiting for sync"); + setSyncProvider(getNtpTime); +} + +time_t prevDisplay = 0; // when the digital clock was displayed + +void loop() +{ + if (timeStatus() != timeNotSet) { + if (now() != prevDisplay) { //update the display only if time has changed + prevDisplay = now(); + digitalClockDisplay(); + } + } +} + +void digitalClockDisplay(){ + // digital clock display of the time + Serial.print(hour()); + printDigits(minute()); + printDigits(second()); + Serial.print(" "); + Serial.print(day()); + Serial.print(" "); + Serial.print(month()); + Serial.print(" "); + Serial.print(year()); + Serial.println(); +} + +void printDigits(int digits){ + // utility for digital clock display: prints preceding colon and leading 0 + Serial.print(":"); + if(digits < 10) + Serial.print('0'); + Serial.print(digits); +} + +/*-------- NTP code ----------*/ + +const int NTP_PACKET_SIZE = 48; // NTP time is in the first 48 bytes of message +byte packetBuffer[NTP_PACKET_SIZE]; //buffer to hold incoming & outgoing packets + +time_t getNtpTime() +{ + while (Udp.parsePacket() > 0) ; // discard any previously received packets + Serial.println("Transmit NTP Request"); + sendNTPpacket(timeServer); + uint32_t beginWait = millis(); + while (millis() - beginWait < 1500) { + int size = Udp.parsePacket(); + if (size >= NTP_PACKET_SIZE) { + Serial.println("Receive NTP Response"); + Udp.read(packetBuffer, NTP_PACKET_SIZE); // read packet into the buffer + unsigned long secsSince1900; + // convert four bytes starting at location 40 to a long integer + secsSince1900 = (unsigned long)packetBuffer[40] << 24; + secsSince1900 |= (unsigned long)packetBuffer[41] << 16; + secsSince1900 |= (unsigned long)packetBuffer[42] << 8; + secsSince1900 |= (unsigned long)packetBuffer[43]; + return secsSince1900 - 2208988800UL + timeZone * SECS_PER_HOUR; + } + } + Serial.println("No NTP Response :-("); + return 0; // return 0 if unable to get the time +} + +// send an NTP request to the time server at the given address +void sendNTPpacket(IPAddress &address) +{ + // set all bytes in the buffer to 0 + memset(packetBuffer, 0, NTP_PACKET_SIZE); + // Initialize values needed to form NTP request + // (see URL above for details on the packets) + packetBuffer[0] = 0b11100011; // LI, Version, Mode + packetBuffer[1] = 0; // Stratum, or type of clock + packetBuffer[2] = 6; // Polling Interval + packetBuffer[3] = 0xEC; // Peer Clock Precision + // 8 bytes of zero for Root Delay & Root Dispersion + packetBuffer[12] = 49; + packetBuffer[13] = 0x4E; + packetBuffer[14] = 49; + packetBuffer[15] = 52; + // all NTP fields have been given values, now + // you can send a packet requesting a timestamp: + Udp.beginPacket(address, 123); //NTP requests are to port 123 + Udp.write(packetBuffer, NTP_PACKET_SIZE); + Udp.endPacket(); +} + diff --git a/software/arduino/libraries/Time/examples/TimeRTC/TimeRTC.pde b/software/arduino/libraries/Time/examples/TimeRTC/TimeRTC.pde new file mode 100644 index 0000000..42e7e7f --- /dev/null +++ b/software/arduino/libraries/Time/examples/TimeRTC/TimeRTC.pde @@ -0,0 +1,55 @@ +/* + * TimeRTC.pde + * example code illustrating Time library with Real Time Clock. + * + */ + +#include +#include +#include // a basic DS1307 library that returns time as a time_t + +void setup() { + Serial.begin(9600); + while (!Serial) ; // wait until Arduino Serial Monitor opens + setSyncProvider(RTC.get); // the function to get the time from the RTC + if(timeStatus()!= timeSet) + Serial.println("Unable to sync with the RTC"); + else + Serial.println("RTC has set the system time"); +} + +void loop() +{ + if (timeStatus() == timeSet) { + digitalClockDisplay(); + } else { + Serial.println("The time has not been set. Please run the Time"); + Serial.println("TimeRTCSet example, or DS1307RTC SetTime example."); + Serial.println(); + delay(4000); + } + delay(1000); +} + +void digitalClockDisplay(){ + // digital clock display of the time + Serial.print(hour()); + printDigits(minute()); + printDigits(second()); + Serial.print(" "); + Serial.print(day()); + Serial.print(" "); + Serial.print(month()); + Serial.print(" "); + Serial.print(year()); + Serial.println(); +} + +void printDigits(int digits){ + // utility function for digital clock display: prints preceding colon and leading 0 + Serial.print(":"); + if(digits < 10) + Serial.print('0'); + Serial.print(digits); +} + diff --git a/software/arduino/libraries/Time/examples/TimeRTCLog/TimeRTCLog.pde b/software/arduino/libraries/Time/examples/TimeRTCLog/TimeRTCLog.pde new file mode 100644 index 0000000..5715eb0 --- /dev/null +++ b/software/arduino/libraries/Time/examples/TimeRTCLog/TimeRTCLog.pde @@ -0,0 +1,107 @@ +/* + * TimeRTCLogger.pde + * example code illustrating adding and subtracting Time. + * + * this sketch logs pin state change events + * the time of the event and time since the previous event is calculated and sent to the serial port. + */ + +#include +#include +#include // a basic DS1307 library that returns time as a time_t + +const int nbrInputPins = 6; // monitor 6 digital pins +const int inputPins[nbrInputPins] = {2,3,4,5,6,7}; // pins to monitor +boolean state[nbrInputPins] ; // the state of the monitored pins +time_t prevEventTime[nbrInputPins] ; // the time of the previous event + +void setup() { + Serial.begin(9600); + setSyncProvider(RTC.get); // the function to sync the time from the RTC + for(int i=0; i < nbrInputPins; i++){ + pinMode( inputPins[i], INPUT); + // uncomment these lines if pull-up resistors are wanted + // pinMode( inputPins[i], INPUT_PULLUP); + // state[i] = HIGH; + } +} + +void loop() +{ + for(int i=0; i < nbrInputPins; i++) + { + boolean val = digitalRead(inputPins[i]); + if(val != state[i]) + { + time_t duration = 0; // the time since the previous event + state[i] = val; + time_t timeNow = now(); + if(prevEventTime[i] > 0) + // if this was not the first state change, calculate the time from the previous change + duration = duration = timeNow - prevEventTime[i]; + logEvent(inputPins[i], val, timeNow, duration ); // log the event + prevEventTime[i] = timeNow; // store the time for this event + } + } +} + +void logEvent( int pin, boolean state, time_t timeNow, time_t duration) +{ + Serial.print("Pin "); + Serial.print(pin); + if( state == HIGH) + Serial.print(" went High at "); + else + Serial.print(" went Low at "); + showTime(timeNow); + if(duration > 0){ + // only display duration if greater than 0 + Serial.print(", Duration was "); + showDuration(duration); + } + Serial.println(); +} + + +void showTime(time_t t){ + // display the given time + Serial.print(hour(t)); + printDigits(minute(t)); + printDigits(second(t)); + Serial.print(" "); + Serial.print(day(t)); + Serial.print(" "); + Serial.print(month(t)); + Serial.print(" "); + Serial.print(year(t)); +} + +void printDigits(int digits){ + // utility function for digital clock display: prints preceding colon and leading 0 + Serial.print(":"); + if(digits < 10) + Serial.print('0'); + Serial.print(digits); +} + +void showDuration(time_t duration){ +// prints the duration in days, hours, minutes and seconds + if(duration >= SECS_PER_DAY){ + Serial.print(duration / SECS_PER_DAY); + Serial.print(" day(s) "); + duration = duration % SECS_PER_DAY; + } + if(duration >= SECS_PER_HOUR){ + Serial.print(duration / SECS_PER_HOUR); + Serial.print(" hour(s) "); + duration = duration % SECS_PER_HOUR; + } + if(duration >= SECS_PER_MIN){ + Serial.print(duration / SECS_PER_MIN); + Serial.print(" minute(s) "); + duration = duration % SECS_PER_MIN; + } + Serial.print(duration); + Serial.print(" second(s) "); +} + diff --git a/software/arduino/libraries/Time/examples/TimeRTCSet/TimeRTCSet.ino b/software/arduino/libraries/Time/examples/TimeRTCSet/TimeRTCSet.ino new file mode 100644 index 0000000..b4e4854 --- /dev/null +++ b/software/arduino/libraries/Time/examples/TimeRTCSet/TimeRTCSet.ino @@ -0,0 +1,80 @@ +/* + * TimeRTCSet.pde + * example code illustrating Time library with Real Time Clock. + * + * RTC clock is set in response to serial port time message + * A Processing example sketch to set the time is included in the download + * On Linux, you can use "date +T%s > /dev/ttyACM0" (UTC time zone) + */ + +#include +#include +#include // a basic DS1307 library that returns time as a time_t + + +void setup() { + Serial.begin(9600); + while (!Serial) ; // Needed for Leonardo only + setSyncProvider(RTC.get); // the function to get the time from the RTC + if (timeStatus() != timeSet) + Serial.println("Unable to sync with the RTC"); + else + Serial.println("RTC has set the system time"); +} + +void loop() +{ + if (Serial.available()) { + time_t t = processSyncMessage(); + if (t != 0) { + RTC.set(t); // set the RTC and the system time to the received value + setTime(t); + } + } + digitalClockDisplay(); + delay(1000); +} + +void digitalClockDisplay(){ + // digital clock display of the time + Serial.print(hour()); + printDigits(minute()); + printDigits(second()); + Serial.print(" "); + Serial.print(day()); + Serial.print(" "); + Serial.print(month()); + Serial.print(" "); + Serial.print(year()); + Serial.println(); +} + +void printDigits(int digits){ + // utility function for digital clock display: prints preceding colon and leading 0 + Serial.print(":"); + if(digits < 10) + Serial.print('0'); + Serial.print(digits); +} + +/* code to process time sync messages from the serial port */ +#define TIME_HEADER "T" // Header tag for serial time sync message + +unsigned long processSyncMessage() { + unsigned long pctime = 0L; + const unsigned long DEFAULT_TIME = 1357041600; // Jan 1 2013 + + if(Serial.find(TIME_HEADER)) { + pctime = Serial.parseInt(); + return pctime; + if( pctime < DEFAULT_TIME) { // check the value is a valid time (greater than Jan 1 2013) + pctime = 0L; // return 0 to indicate that the time is not valid + } + } + return pctime; +} + + + + + diff --git a/software/arduino/libraries/Time/examples/TimeSerial/TimeSerial.ino b/software/arduino/libraries/Time/examples/TimeSerial/TimeSerial.ino new file mode 100644 index 0000000..15aee0a --- /dev/null +++ b/software/arduino/libraries/Time/examples/TimeSerial/TimeSerial.ino @@ -0,0 +1,81 @@ +/* + * TimeSerial.pde + * example code illustrating Time library set through serial port messages. + * + * Messages consist of the letter T followed by ten digit time (as seconds since Jan 1 1970) + * you can send the text on the next line using Serial Monitor to set the clock to noon Jan 1 2013 + T1357041600 + * + * A Processing example sketch to automatically send the messages is inclided in the download + * On Linux, you can use "date +T%s > /dev/ttyACM0" (UTC time zone) + */ + +#include + +#define TIME_HEADER "T" // Header tag for serial time sync message +#define TIME_REQUEST 7 // ASCII bell character requests a time sync message + +void setup() { + Serial.begin(9600); + while (!Serial) ; // Needed for Leonardo only + pinMode(13, OUTPUT); + setSyncProvider( requestSync); //set function to call when sync required + Serial.println("Waiting for sync message"); +} + +void loop(){ + if (Serial.available()) { + processSyncMessage(); + } + if (timeStatus()!= timeNotSet) { + digitalClockDisplay(); + } + if (timeStatus() == timeSet) { + digitalWrite(13, HIGH); // LED on if synced + } else { + digitalWrite(13, LOW); // LED off if needs refresh + } + delay(1000); +} + +void digitalClockDisplay(){ + // digital clock display of the time + Serial.print(hour()); + printDigits(minute()); + printDigits(second()); + Serial.print(" "); + Serial.print(day()); + Serial.print(" "); + Serial.print(month()); + Serial.print(" "); + Serial.print(year()); + Serial.println(); +} + +void printDigits(int digits){ + // utility function for digital clock display: prints preceding colon and leading 0 + Serial.print(":"); + if(digits < 10) + Serial.print('0'); + Serial.print(digits); +} + + +void processSyncMessage() { + unsigned long pctime; + const unsigned long DEFAULT_TIME = 1357041600; // Jan 1 2013 + + if(Serial.find(TIME_HEADER)) { + pctime = Serial.parseInt(); + if( pctime >= DEFAULT_TIME) { // check the integer is a valid time (greater than Jan 1 2013) + setTime(pctime); // Sync Arduino clock to the time received on the serial port + } + } +} + +time_t requestSync() +{ + Serial.write(TIME_REQUEST); + return 0; // the time will be sent later in response to serial mesg +} + diff --git a/software/arduino/libraries/Time/examples/TimeSerialDateStrings/TimeSerialDateStrings.ino b/software/arduino/libraries/Time/examples/TimeSerialDateStrings/TimeSerialDateStrings.ino new file mode 100644 index 0000000..f181c3d --- /dev/null +++ b/software/arduino/libraries/Time/examples/TimeSerialDateStrings/TimeSerialDateStrings.ino @@ -0,0 +1,110 @@ +/* + * TimeSerialDateStrings.pde + * example code illustrating Time library date strings + * + * This sketch adds date string functionality to TimeSerial sketch + * Also shows how to handle different messages + * + * A message starting with a time header sets the time + * A Processing example sketch to automatically send the messages is inclided in the download + * On Linux, you can use "date +T%s > /dev/ttyACM0" (UTC time zone) + * + * A message starting with a format header sets the date format + * + * send: Fs\n for short date format + * send: Fl\n for long date format + */ + +#include + +// single character message tags +#define TIME_HEADER 'T' // Header tag for serial time sync message +#define FORMAT_HEADER 'F' // Header tag indicating a date format message +#define FORMAT_SHORT 's' // short month and day strings +#define FORMAT_LONG 'l' // (lower case l) long month and day strings + +#define TIME_REQUEST 7 // ASCII bell character requests a time sync message + +static boolean isLongFormat = true; + +void setup() { + Serial.begin(9600); + while (!Serial) ; // Needed for Leonardo only + setSyncProvider( requestSync); //set function to call when sync required + Serial.println("Waiting for sync message"); +} + +void loop(){ + if (Serial.available()) { + char c = Serial.read(); + if( c == TIME_HEADER) { + processSyncMessage(); + } + else if( c== FORMAT_HEADER) { + processFormatMessage(); + } + } + if (timeStatus()!= timeNotSet) { + digitalClockDisplay(); + } + delay(1000); +} + +void digitalClockDisplay(){ + // digital clock display of the time + Serial.print(hour()); + printDigits(minute()); + printDigits(second()); + Serial.print(" "); + if(isLongFormat) + Serial.print(dayStr(weekday())); + else + Serial.print(dayShortStr(weekday())); + Serial.print(" "); + Serial.print(day()); + Serial.print(" "); + if(isLongFormat) + Serial.print(monthStr(month())); + else + Serial.print(monthShortStr(month())); + Serial.print(" "); + Serial.print(year()); + Serial.println(); +} + +void printDigits(int digits){ + // utility function for digital clock display: prints preceding colon and leading 0 + Serial.print(":"); + if(digits < 10) + Serial.print('0'); + Serial.print(digits); +} + +void processFormatMessage() { + char c = Serial.read(); + if( c == FORMAT_LONG){ + isLongFormat = true; + Serial.println("Setting long format"); + } + else if( c == FORMAT_SHORT){ + isLongFormat = false; + Serial.println("Setting short format"); + } +} + +void processSyncMessage() { + unsigned long pctime; + const unsigned long DEFAULT_TIME = 1357041600; // Jan 1 2013 - paul, perhaps we define in time.h? + + pctime = Serial.parseInt(); + if( pctime >= DEFAULT_TIME) { // check the integer is a valid time (greater than Jan 1 2013) + setTime(pctime); // Sync Arduino clock to the time received on the serial port + } +} + +time_t requestSync() +{ + Serial.write(TIME_REQUEST); + return 0; // the time will be sent later in response to serial mesg +} + diff --git a/software/arduino/libraries/Time/examples/TimeTeensy3/TimeTeensy3.ino b/software/arduino/libraries/Time/examples/TimeTeensy3/TimeTeensy3.ino new file mode 100644 index 0000000..a06a7f9 --- /dev/null +++ b/software/arduino/libraries/Time/examples/TimeTeensy3/TimeTeensy3.ino @@ -0,0 +1,78 @@ +/* + * TimeRTC.pde + * example code illustrating Time library with Real Time Clock. + * + */ + +#include + +void setup() { + // set the Time library to use Teensy 3.0's RTC to keep time + setSyncProvider(getTeensy3Time); + + Serial.begin(115200); + while (!Serial); // Wait for Arduino Serial Monitor to open + delay(100); + if (timeStatus()!= timeSet) { + Serial.println("Unable to sync with the RTC"); + } else { + Serial.println("RTC has set the system time"); + } +} + +void loop() { + if (Serial.available()) { + time_t t = processSyncMessage(); + if (t != 0) { + Teensy3Clock.set(t); // set the RTC + setTime(t); + } + } + digitalClockDisplay(); + delay(1000); +} + +void digitalClockDisplay() { + // digital clock display of the time + Serial.print(hour()); + printDigits(minute()); + printDigits(second()); + Serial.print(" "); + Serial.print(day()); + Serial.print(" "); + Serial.print(month()); + Serial.print(" "); + Serial.print(year()); + Serial.println(); +} + +time_t getTeensy3Time() +{ + return Teensy3Clock.get(); +} + +/* code to process time sync messages from the serial port */ +#define TIME_HEADER "T" // Header tag for serial time sync message + +unsigned long processSyncMessage() { + unsigned long pctime = 0L; + const unsigned long DEFAULT_TIME = 1357041600; // Jan 1 2013 + + if(Serial.find(TIME_HEADER)) { + pctime = Serial.parseInt(); + return pctime; + if( pctime < DEFAULT_TIME) { // check the value is a valid time (greater than Jan 1 2013) + pctime = 0L; // return 0 to indicate that the time is not valid + } + } + return pctime; +} + +void printDigits(int digits){ + // utility function for digital clock display: prints preceding colon and leading 0 + Serial.print(":"); + if(digits < 10) + Serial.print('0'); + Serial.print(digits); +} + diff --git a/software/arduino/libraries/Time/keywords.txt b/software/arduino/libraries/Time/keywords.txt new file mode 100644 index 0000000..85b357e --- /dev/null +++ b/software/arduino/libraries/Time/keywords.txt @@ -0,0 +1,33 @@ +####################################### +# Syntax Coloring Map For Time +####################################### + +####################################### +# Datatypes (KEYWORD1) +####################################### +time_t KEYWORD1 +####################################### +# Methods and Functions (KEYWORD2) +####################################### +now KEYWORD2 +second KEYWORD2 +minute KEYWORD2 +hour KEYWORD2 +day KEYWORD2 +month KEYWORD2 +year KEYWORD2 +isAM KEYWORD2 +isPM KEYWORD2 +weekday KEYWORD2 +setTime KEYWORD2 +adjustTime KEYWORD2 +setSyncProvider KEYWORD2 +setSyncInterval KEYWORD2 +timeStatus KEYWORD2 +####################################### +# Instances (KEYWORD2) +####################################### + +####################################### +# Constants (LITERAL1) +####################################### diff --git a/software/arduino/libraries/TimeAlarms/TimeAlarms.cpp b/software/arduino/libraries/TimeAlarms/TimeAlarms.cpp new file mode 100644 index 0000000..f85df9a --- /dev/null +++ b/software/arduino/libraries/TimeAlarms/TimeAlarms.cpp @@ -0,0 +1,357 @@ +/* + TimeAlarms.cpp - Arduino Time alarms for use with Time library + Copyright (c) 208-2011 Michael Margolis. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + */ + + /* + 2 July 2011 - replaced alarm types implied from alarm value with enums to make trigger logic more robust + - this fixes bug in repeating weekly alarms - thanks to Vincent Valdy and draythomp for testing +*/ + +extern "C" { +#include // for memset +} + +#if ARDUINO > 22 +#include +#else +#include +#endif + +#include "TimeAlarms.h" +#include "Time.h" + +#define IS_ONESHOT true // constants used in arguments to create method +#define IS_REPEAT false + + +//************************************************************** +//* Alarm Class Constructor + +AlarmClass::AlarmClass() +{ + Mode.isEnabled = Mode.isOneShot = 0; + Mode.alarmType = dtNotAllocated; + value = nextTrigger = 0; + onTickHandler = NULL; // prevent a callback until this pointer is explicitly set +} + +//************************************************************** +//* Private Methods + + +void AlarmClass::updateNextTrigger() +{ + if( (value != 0) && Mode.isEnabled ) + { + time_t time = now(); + if( dtIsAlarm(Mode.alarmType) && nextTrigger <= time ) // update alarm if next trigger is not yet in the future + { + if(Mode.alarmType == dtExplicitAlarm ) // is the value a specific date and time in the future + { + nextTrigger = value; // yes, trigger on this value + } + else if(Mode.alarmType == dtDailyAlarm) //if this is a daily alarm + { + if( value + previousMidnight(now()) <= time) + { + nextTrigger = value + nextMidnight(time); // if time has passed then set for tomorrow + } + else + { + nextTrigger = value + previousMidnight(time); // set the date to today and add the time given in value + } + } + else if(Mode.alarmType == dtWeeklyAlarm) // if this is a weekly alarm + { + if( (value + previousSunday(now())) <= time) + { + nextTrigger = value + nextSunday(time); // if day has passed then set for the next week. + } + else + { + nextTrigger = value + previousSunday(time); // set the date to this week today and add the time given in value + } + } + else // its not a recognized alarm type - this should not happen + { + Mode.isEnabled = 0; // Disable the alarm + } + } + if( Mode.alarmType == dtTimer) + { + // its a timer + nextTrigger = time + value; // add the value to previous time (this ensures delay always at least Value seconds) + } + } + else + { + Mode.isEnabled = 0; // Disable if the value is 0 + } +} + +//************************************************************** +//* Time Alarms Public Methods + +TimeAlarmsClass::TimeAlarmsClass() +{ + isServicing = false; + for(uint8_t id = 0; id < dtNBR_ALARMS; id++) + free(id); // ensure all Alarms are cleared and available for allocation +} + +// this method creates a trigger at the given absolute time_t +// it replaces the call to alarmOnce with values greater than a week +AlarmID_t TimeAlarmsClass::triggerOnce(time_t value, OnTick_t onTickHandler){ // trigger once at the given time_t + if( value > 0) + return create( value, onTickHandler, IS_ONESHOT, dtExplicitAlarm ); + else + return dtINVALID_ALARM_ID; // dont't allocate if the time is greater than one day +} + +// this method will now return an error if the value is greater than one day - use DOW methods for weekly alarms +AlarmID_t TimeAlarmsClass::alarmOnce(time_t value, OnTick_t onTickHandler){ // trigger once at the given time of day + if( value <= SECS_PER_DAY) + return create( value, onTickHandler, IS_ONESHOT, dtDailyAlarm ); + else + return dtINVALID_ALARM_ID; // dont't allocate if the time is greater than one day +} + +AlarmID_t TimeAlarmsClass::alarmOnce(const int H, const int M, const int S,OnTick_t onTickHandler){ // as above with HMS arguments + return create( AlarmHMS(H,M,S), onTickHandler, IS_ONESHOT, dtDailyAlarm ); +} + +AlarmID_t TimeAlarmsClass::alarmOnce(const timeDayOfWeek_t DOW, const int H, const int M, const int S, OnTick_t onTickHandler){ // as above, with day of week + return create( (DOW-1) * SECS_PER_DAY + AlarmHMS(H,M,S), onTickHandler, IS_ONESHOT, dtWeeklyAlarm ); +} + +// this method will now return an error if the value is greater than one day - use DOW methods for weekly alarms +AlarmID_t TimeAlarmsClass::alarmRepeat(time_t value, OnTick_t onTickHandler){ // trigger daily at the given time + if( value <= SECS_PER_DAY) + return create( value, onTickHandler, IS_REPEAT, dtDailyAlarm ); + else + return dtINVALID_ALARM_ID; // dont't allocate if the time is greater than one day + } + + AlarmID_t TimeAlarmsClass::alarmRepeat(const int H, const int M, const int S, OnTick_t onTickHandler){ // as above with HMS arguments + return create( AlarmHMS(H,M,S), onTickHandler, IS_REPEAT, dtDailyAlarm ); + } + + AlarmID_t TimeAlarmsClass::alarmRepeat(const timeDayOfWeek_t DOW, const int H, const int M, const int S, OnTick_t onTickHandler){ // as above, with day of week + return create( (DOW-1) * SECS_PER_DAY + AlarmHMS(H,M,S), onTickHandler, IS_REPEAT, dtWeeklyAlarm ); + } + + AlarmID_t TimeAlarmsClass::timerOnce(time_t value, OnTick_t onTickHandler){ // trigger once after the given number of seconds + return create( value, onTickHandler, IS_ONESHOT, dtTimer ); + } + + AlarmID_t TimeAlarmsClass::timerOnce(const int H, const int M, const int S, OnTick_t onTickHandler){ // As above with HMS arguments + return create( AlarmHMS(H,M,S), onTickHandler, IS_ONESHOT, dtTimer ); + } + + AlarmID_t TimeAlarmsClass::timerRepeat(time_t value, OnTick_t onTickHandler){ // trigger after the given number of seconds continuously + return create( value, onTickHandler, IS_REPEAT, dtTimer); + } + + AlarmID_t TimeAlarmsClass::timerRepeat(const int H, const int M, const int S, OnTick_t onTickHandler){ // trigger after the given number of seconds continuously + return create( AlarmHMS(H,M,S), onTickHandler, IS_REPEAT, dtTimer); + } + + void TimeAlarmsClass::enable(AlarmID_t ID) + { + if(isAllocated(ID)) { + Alarm[ID].Mode.isEnabled = (Alarm[ID].value != 0) && (Alarm[ID].onTickHandler != 0) ; // only enable if value is non zero and a tick handler has been set + Alarm[ID].updateNextTrigger(); // trigger is updated whenever this is called, even if already enabled + } + } + + void TimeAlarmsClass::disable(AlarmID_t ID) + { + if(isAllocated(ID)) + Alarm[ID].Mode.isEnabled = false; + } + + // write the given value to the given alarm + void TimeAlarmsClass::write(AlarmID_t ID, time_t value) + { + if(isAllocated(ID)) + { + Alarm[ID].value = value; + enable(ID); // update trigger time + } + } + + // return the value for the given alarm ID + time_t TimeAlarmsClass::read(AlarmID_t ID) + { + if(isAllocated(ID)) + return Alarm[ID].value ; + else + return dtINVALID_TIME; + } + + // return the alarm type for the given alarm ID + dtAlarmPeriod_t TimeAlarmsClass::readType(AlarmID_t ID) + { + if(isAllocated(ID)) + return (dtAlarmPeriod_t)Alarm[ID].Mode.alarmType ; + else + return dtNotAllocated; + } + + void TimeAlarmsClass::free(AlarmID_t ID) + { + if(isAllocated(ID)) + { + Alarm[ID].Mode.isEnabled = false; + Alarm[ID].Mode.alarmType = dtNotAllocated; + Alarm[ID].onTickHandler = 0; + Alarm[ID].value = 0; + Alarm[ID].nextTrigger = 0; + } + } + + // returns the number of allocated timers + uint8_t TimeAlarmsClass::count() + { + uint8_t c = 0; + for(uint8_t id = 0; id < dtNBR_ALARMS; id++) + { + if(isAllocated(id)) + c++; + } + return c; + } + + // returns true only if id is allocated and the type is a time based alarm, returns false if not allocated or if its a timer + bool TimeAlarmsClass::isAlarm(AlarmID_t ID) + { + return( isAllocated(ID) && dtIsAlarm(Alarm[ID].Mode.alarmType) ); + } + + // returns true if this id is allocated + bool TimeAlarmsClass::isAllocated(AlarmID_t ID) + { + return( ID < dtNBR_ALARMS && Alarm[ID].Mode.alarmType != dtNotAllocated ); + } + + + AlarmID_t TimeAlarmsClass::getTriggeredAlarmId() //returns the currently triggered alarm id + // returns dtINVALID_ALARM_ID if not invoked from within an alarm handler + { + if(isServicing) + return servicedAlarmId; // new private data member used instead of local loop variable i in serviceAlarms(); + else + return dtINVALID_ALARM_ID; // valid ids only available when servicing a callback + } + + // following functions are not Alarm ID specific. + void TimeAlarmsClass::delay(unsigned long ms) + { + unsigned long start = millis(); + while( millis() - start <= ms) + serviceAlarms(); + } + + void TimeAlarmsClass::waitForDigits( uint8_t Digits, dtUnits_t Units) + { + while(Digits != getDigitsNow(Units) ) + { + serviceAlarms(); + } + } + + void TimeAlarmsClass::waitForRollover( dtUnits_t Units) + { + while(getDigitsNow(Units) == 0 ) // if its just rolled over than wait for another rollover + serviceAlarms(); + waitForDigits(0, Units); + } + + uint8_t TimeAlarmsClass::getDigitsNow( dtUnits_t Units) + { + time_t time = now(); + if(Units == dtSecond) return numberOfSeconds(time); + if(Units == dtMinute) return numberOfMinutes(time); + if(Units == dtHour) return numberOfHours(time); + if(Units == dtDay) return dayOfWeek(time); + return 255; // This should never happen + } + + //*********************************************************** + //* Private Methods + + void TimeAlarmsClass::serviceAlarms() + { + if(! isServicing) + { + isServicing = true; + for( servicedAlarmId = 0; servicedAlarmId < dtNBR_ALARMS; servicedAlarmId++) + { + if( Alarm[servicedAlarmId].Mode.isEnabled && (now() >= Alarm[servicedAlarmId].nextTrigger) ) + { + OnTick_t TickHandler = Alarm[servicedAlarmId].onTickHandler; + if(Alarm[servicedAlarmId].Mode.isOneShot) + free(servicedAlarmId); // free the ID if mode is OnShot + else + Alarm[servicedAlarmId].updateNextTrigger(); + if( TickHandler != NULL) { + (*TickHandler)(); // call the handler + } + } + } + isServicing = false; + } + } + + // returns the absolute time of the next scheduled alarm, or 0 if none + time_t TimeAlarmsClass::getNextTrigger() + { + time_t nextTrigger = 0xffffffff; // the max time value + + for(uint8_t id = 0; id < dtNBR_ALARMS; id++) + { + if(isAllocated(id) ) + { + if(Alarm[id].nextTrigger < nextTrigger) + nextTrigger = Alarm[id].nextTrigger; + } + } + return nextTrigger == 0xffffffff ? 0 : nextTrigger; + } + + // attempt to create an alarm and return true if successful + AlarmID_t TimeAlarmsClass::create( time_t value, OnTick_t onTickHandler, uint8_t isOneShot, dtAlarmPeriod_t alarmType, uint8_t isEnabled) + { + if( ! (dtIsAlarm(alarmType) && now() < SECS_PER_YEAR)) // only create alarm ids if the time is at least Jan 1 1971 + { + for(uint8_t id = 0; id < dtNBR_ALARMS; id++) + { + if( Alarm[id].Mode.alarmType == dtNotAllocated ) + { + // here if there is an Alarm id that is not allocated + Alarm[id].onTickHandler = onTickHandler; + Alarm[id].Mode.isOneShot = isOneShot; + Alarm[id].Mode.alarmType = alarmType; + Alarm[id].value = value; + isEnabled ? enable(id) : disable(id); + return id; // alarm created ok + } + } + } + return dtINVALID_ALARM_ID; // no IDs available or time is invalid + } + + // make one instance for the user to use + TimeAlarmsClass Alarm = TimeAlarmsClass() ; + diff --git a/software/arduino/libraries/TimeAlarms/TimeAlarms.h b/software/arduino/libraries/TimeAlarms/TimeAlarms.h new file mode 100644 index 0000000..ba8a59c --- /dev/null +++ b/software/arduino/libraries/TimeAlarms/TimeAlarms.h @@ -0,0 +1,128 @@ +// TimeAlarms.h - Arduino Time alarms header for use with Time library + +#ifndef TimeAlarms_h +#define TimeAlarms_h + +#include + +#include "Time.h" + +#define dtNBR_ALARMS 6 // max is 255 + +#define USE_SPECIALIST_METHODS // define this for testing + +typedef enum { dtMillisecond, dtSecond, dtMinute, dtHour, dtDay } dtUnits_t; + +typedef struct { + uint8_t alarmType :4 ; // enumeration of daily/weekly (in future: biweekly/semimonthly/monthly/annual) + // note that the current API only supports daily or weekly alarm periods + uint8_t isEnabled :1 ; // the timer is only actioned if isEnabled is true + uint8_t isOneShot :1 ; // the timer will be de-allocated after trigger is processed + } + AlarmMode_t ; + +// new time based alarms should be added just before dtLastAlarmType +typedef enum {dtNotAllocated, dtTimer, dtExplicitAlarm, dtDailyAlarm, dtWeeklyAlarm, dtLastAlarmType } dtAlarmPeriod_t ; // in future: dtBiweekly, dtMonthly, dtAnnual + +// macro to return true if the given type is a time based alarm, false if timer or not allocated +#define dtIsAlarm(_type_) (_type_ >= dtExplicitAlarm && _type_ < dtLastAlarmType) + +typedef uint8_t AlarmID_t; +typedef AlarmID_t AlarmId; // Arduino friendly name + +#define dtINVALID_ALARM_ID 255 +#define dtINVALID_TIME 0L + +class AlarmClass; // forward reference +typedef void (*OnTick_t)(); // alarm callback function typedef + +// class defining an alarm instance, only used by dtAlarmsClass +class AlarmClass +{ +private: + +public: + AlarmClass(); + OnTick_t onTickHandler; + void updateNextTrigger(); + time_t value; + time_t nextTrigger; + AlarmMode_t Mode; +}; + +// class containing the collection of alarms +class TimeAlarmsClass +{ +private: + AlarmClass Alarm[dtNBR_ALARMS]; + uint8_t isServicing; + uint8_t servicedAlarmId; // the alarm currently being serviced + AlarmID_t create( time_t value, OnTick_t onTickHandler, uint8_t isOneShot, dtAlarmPeriod_t alarmType, uint8_t isEnabled=true); + +public: + TimeAlarmsClass(); + // functions to create alarms and timers + + AlarmID_t triggerOnce(time_t value, OnTick_t onTickHandler); // trigger once at the given time_t + + AlarmID_t alarmRepeat(time_t value, OnTick_t onTickHandler); // trigger daily at given time of day + AlarmID_t alarmRepeat(const int H, const int M, const int S, OnTick_t onTickHandler); // as above, with hms arguments + AlarmID_t alarmRepeat(const timeDayOfWeek_t DOW, const int H, const int M, const int S, OnTick_t onTickHandler); // as above, with day of week + + AlarmID_t alarmOnce(time_t value, OnTick_t onTickHandler); // trigger once at given time of day + AlarmID_t alarmOnce( const int H, const int M, const int S, OnTick_t onTickHandler); // as above, with hms arguments + AlarmID_t alarmOnce(const timeDayOfWeek_t DOW, const int H, const int M, const int S, OnTick_t onTickHandler); // as above, with day of week + + AlarmID_t timerOnce(time_t value, OnTick_t onTickHandler); // trigger once after the given number of seconds + AlarmID_t timerOnce(const int H, const int M, const int S, OnTick_t onTickHandler); // As above with HMS arguments + + AlarmID_t timerRepeat(time_t value, OnTick_t onTickHandler); // trigger after the given number of seconds continuously + AlarmID_t timerRepeat(const int H, const int M, const int S, OnTick_t onTickHandler); // As above with HMS arguments + + void delay(unsigned long ms); + // moved that from private methods. + void serviceAlarms(); + + // utility methods + uint8_t getDigitsNow( dtUnits_t Units); // returns the current digit value for the given time unit + void waitForDigits( uint8_t Digits, dtUnits_t Units); + void waitForRollover(dtUnits_t Units); + + // low level methods + void enable(AlarmID_t ID); // enable the alarm to trigger + void disable(AlarmID_t ID); // prevent the alarm from triggering + AlarmID_t getTriggeredAlarmId(); // returns the currently triggered alarm id + void write(AlarmID_t ID, time_t value); // write the value (and enable) the alarm with the given ID + time_t read(AlarmID_t ID); // return the value for the given timer + dtAlarmPeriod_t readType(AlarmID_t ID); // return the alarm type for the given alarm ID + +#ifndef USE_SPECIALIST_METHODS +private: // the following methods are for testing and are not documented as part of the standard library +#endif + void free(AlarmID_t ID); // free the id to allow its reuse + uint8_t count(); // returns the number of allocated timers + time_t getNextTrigger(); // returns the time of the next scheduled alarm + bool isAllocated(AlarmID_t ID); // returns true if this id is allocated + bool isAlarm(AlarmID_t ID); // returns true if id is for a time based alarm, false if its a timer or not allocated +}; + +extern TimeAlarmsClass Alarm; // make an instance for the user + +/*============================================================================== + * MACROS + *============================================================================*/ + +/* public */ +#define waitUntilThisSecond(_val_) waitForDigits( _val_, dtSecond) +#define waitUntilThisMinute(_val_) waitForDigits( _val_, dtMinute) +#define waitUntilThisHour(_val_) waitForDigits( _val_, dtHour) +#define waitUntilThisDay(_val_) waitForDigits( _val_, dtDay) +#define waitMinuteRollover() waitForRollover(dtSecond) +#define waitHourRollover() waitForRollover(dtMinute) +#define waitDayRollover() waitForRollover(dtHour) + +#define AlarmHMS(_hr_, _min_, _sec_) (_hr_ * SECS_PER_HOUR + _min_ * SECS_PER_MIN + _sec_) + + +#endif /* TimeAlarms_h */ + diff --git a/software/arduino/libraries/TimeAlarms/examples/TimeAlarmExample/TimeAlarmExample.pde b/software/arduino/libraries/TimeAlarms/examples/TimeAlarmExample/TimeAlarmExample.pde new file mode 100644 index 0000000..cb5729b --- /dev/null +++ b/software/arduino/libraries/TimeAlarms/examples/TimeAlarmExample/TimeAlarmExample.pde @@ -0,0 +1,77 @@ +/* + * TimeAlarmExample.pde + * + * This example calls alarm functions at 8:30 am and at 5:45 pm (17:45) + * and simulates turning lights on at night and off in the morning + * A weekly timer is set for Saturdays at 8:30:30 + * + * A timer is called every 15 seconds + * Another timer is called once only after 10 seconds + * + * At startup the time is set to Jan 1 2011 8:29 am + */ + +#include +#include + +void setup() +{ + Serial.begin(9600); + setTime(8,29,0,1,1,11); // set time to Saturday 8:29:00am Jan 1 2011 + // create the alarms + Alarm.alarmRepeat(8,30,0, MorningAlarm); // 8:30am every day + Alarm.alarmRepeat(17,45,0,EveningAlarm); // 5:45pm every day + Alarm.alarmRepeat(dowSaturday,8,30,30,WeeklyAlarm); // 8:30:30 every Saturday + + + Alarm.timerRepeat(15, Repeats); // timer for every 15 seconds + Alarm.timerOnce(10, OnceOnly); // called once after 10 seconds +} + +void loop(){ + digitalClockDisplay(); + Alarm.delay(1000); // wait one second between clock display +} + +// functions to be called when an alarm triggers: +void MorningAlarm(){ + Serial.println("Alarm: - turn lights off"); +} + +void EveningAlarm(){ + Serial.println("Alarm: - turn lights on"); +} + +void WeeklyAlarm(){ + Serial.println("Alarm: - its Monday Morning"); +} + +void ExplicitAlarm(){ + Serial.println("Alarm: - this triggers only at the given date and time"); +} + +void Repeats(){ + Serial.println("15 second timer"); +} + +void OnceOnly(){ + Serial.println("This timer only triggers once"); +} + +void digitalClockDisplay() +{ + // digital clock display of the time + Serial.print(hour()); + printDigits(minute()); + printDigits(second()); + Serial.println(); +} + +void printDigits(int digits) +{ + Serial.print(":"); + if(digits < 10) + Serial.print('0'); + Serial.print(digits); +} + diff --git a/software/arduino/libraries/TimeAlarms/keywords.txt b/software/arduino/libraries/TimeAlarms/keywords.txt new file mode 100644 index 0000000..8bc9f79 --- /dev/null +++ b/software/arduino/libraries/TimeAlarms/keywords.txt @@ -0,0 +1,25 @@ +####################################### +# Syntax Coloring Map For TimeAlarms +####################################### + +####################################### +# Datatypes (KEYWORD1) +####################################### + +####################################### +# Methods and Functions (KEYWORD2) +####################################### +alarmRepeat KEYWORD2 +alarmOnce KEYWORD2 +timerRepeat KEYWORD2 +timerOnce KEYWORD2 +delay KEYWORD2 +####################################### +# Instances (KEYWORD2) +####################################### +Alarm KEYWORD2 +####################################### +# Constants (LITERAL1) +####################################### +dtINVALID_ALARM_ID LITERAL1 +dtINVALID_TIME LITERAL1 diff --git a/software/arduino/libraries/TimeAlarms/readme.txt b/software/arduino/libraries/TimeAlarms/readme.txt new file mode 100644 index 0000000..4cdaa5b --- /dev/null +++ b/software/arduino/libraries/TimeAlarms/readme.txt @@ -0,0 +1,220 @@ +Alarms + +The Alarm library is a companion to the Time library that makes it easy to +perform tasks at specific times or after specific intervals. + +Tasks scheduled at a particular time of day are called Alarms, +tasks scheduled after an interval of time has elapsed are called Timers. +These tasks can be created to continuously repeat or to occur once only. + +Here is how you create an alarm to trigger a task repeatedly at a particular time of day: + Alarm.alarmRepeat(8,30,0, MorningAlarm); +This would call the function MorningAlarm() at 8:30 am every day. + +If you want the alarm to trigger only once you can use the alarmOnce method: + Alarm.alarmOnce(8,30,0, MorningAlarm); +This calls a MorningAlarm() function in a sketch once only (when the time is next 8:30am) + +Alarms can be specified to trigger a task repeatedly at a particular day of week and time of day: + Alarm.alarmRepeat(dowMonday, 9,15,0, MondayMorningAlarm); +This would call the function WeeklyAlarm() at 9:15am every Monday. + +If you want the alarm to trigger once only on a particular day and time you can do this: + Alarm.alarmOnce(dowMonday, 9,15,0, MondayMorningAlarm); +This would call the function MondayMorning() Alarm on the next Monday at 9:15am. + +Timers trigger tasks that occur after a specified interval of time has passed. +The timer interval can be specified in seconds, or in hour, minutes and seconds. + Alarm.timerRepeat(15, Repeats); // timer task every 15 seconds +This calls the Repeats() function in your sketch every 15 seconds. + +If you want a timer to trigger once only, you can use the timerOnce method: + Alarm.timerOnce(10, OnceOnly); // called once after 10 seconds +This calls the onceOnly() function in a sketch 10 seconds after the timer is created. + +If you want to trigger once at a specified date and time you can use the trigger Once() method: + Alarm. triggerOnce(time_t value, explicitAlarm); // value specifies a date and time +(See the makeTime() method in the Time library to convert dates and times into time_t) + +Your sketch should call the Alarm.delay() function instead of the Arduino delay() function when +using the Alarms library. The timeliness of triggers depends on sketch delays using this function. + Alarm.delay( period); // Similar to Arduino delay - pauses the program for the period (in milliseconds). + + + +Here is an example sketch: + +This sketch triggers daily alarms at 8:30 am and 17:45 pm. +A Timer is triggered every 15 seconds, another timer triggers once only after 10 seconds. +A weekly alarm is triggered every Sunday at 8:30:30 + +#include +#include + +void setup() +{ + Serial.begin(9600); + setTime(8,29,0,1,1,11); // set time to Saturday 8:29:00am Jan 1 2011 + // create the alarms + Alarm.alarmRepeat(8,30,0, MorningAlarm); // 8:30am every day + Alarm.alarmRepeat(17,45,0,EveningAlarm); // 5:45pm every day + Alarm.alarmRepeat(dowSaturday,8,30,30,WeeklyAlarm); // 8:30:30 every Saturday + + + Alarm.timerRepeat(15, Repeats); // timer for every 15 seconds + Alarm.timerOnce(10, OnceOnly); // called once after 10 seconds +} + +void loop(){ + digitalClockDisplay(); + Alarm.delay(1000); // wait one second between clock display +} + +// functions to be called when an alarm triggers: +void MorningAlarm(){ + Serial.println("Alarm: - turn lights off"); +} + +void EveningAlarm(){ + Serial.println("Alarm: - turn lights on"); +} + +void WeeklyAlarm(){ + Serial.println("Alarm: - its Monday Morning"); +} + +void ExplicitAlarm(){ + Serial.println("Alarm: - this triggers only at the given date and time"); +} + +void Repeats(){ + Serial.println("15 second timer"); +} + +void OnceOnly(){ + Serial.println("This timer only triggers once"); +} + +void digitalClockDisplay() +{ + // digital clock display of the time + Serial.print(hour()); + printDigits(minute()); + printDigits(second()); + Serial.println(); +} + +void printDigits(int digits) +{ + Serial.print(":"); + if(digits < 10) + Serial.print('0'); + Serial.print(digits); +} +Note that the loop code calls Alarm.delay(1000) - Alarm.delay must be used +instead of the usual arduino delay function because the alarms are serviced in the Alarm.delay method. +Failing to regularly call Alarm.delay will result in the alarms not being triggered +so always use Alarm.delay instead of delay in sketches that use the Alarms library. + +Functional reference: + +// functions to create alarms and timers + +Alarm.triggerOnce(value, AlarmFunction); + Description: Call user provided AlarmFunction once at the date and time of the given value + See the Ttime library for more on time_t values + +Alarm.alarmRepeat(Hour, Minute, Second, AlarmFunction); + Description: Calls user provided AlarmFunction every day at the given Hour, Minute and Second. + +Alarm.alarmRepeat(value, AlarmFunction); + Description: Calls user provided AlarmFunction every day at the time indicated by the given value + +Alarm.alarmRepeat(DayOfWeek, Hour, Minute, Second, AlarmFunction); + Description: Calls user provided AlarmFunction every week on the given DayOfWeek, Hour, Minute and Second. + +Alarm.alarmOnce(Hour, Minute, Second, AlarmFunction); + Description: Calls user provided AlarmFunction once when the Arduino time next reaches the given Hour, Minute and Second. + +Alarm.alarmOnce(value, AlarmFunction); + Description: Calls user provided AlarmFunction once at the next time indicated by the given value + +Alarm.alarmOnce(DayOfWeek, Hour, Minute, Second, AlarmFunction); + Description: Calls user provided AlarmFunction once only on the next DayOfWeek, Hour, Minute and Second. + +Alarm.timerRepeat(Period, TimerFunction); + Description: Continuously calls user provided TimerFunction after the given period in seconds has elapsed. + +Alarm.timerRepeat(Hour, Minute, Second, TimerFunction); + Description: As timerRepeat above, but period is the number of seconds in the given Hour, Minute and Second parameters + +Alarm.timerOnce(Period, TimerFunction); + Description: Calls user provided TimerFunction once only after the given period in seconds has elapsed. + +Alarm.timerOnce(Hour, Minute, Second, TimerFunction); + Description: As timerOnce above, but period is the number of seconds in the given Hour, Minute and Second parameters + +Alarm.delay( period) + Description: Similar to Arduino delay - pauses the program for the period (in miliseconds) specified. + Call this function rather than the Arduino delay function when using the Alarms library. + The timeliness of the triggers depends on sketch delays using this function. + +Low level functions not usually required for typical applications: + disable( ID); - prevent the alarm associated with the given ID from triggering + enable(ID); - enable the alarm + write(ID, value); - write the value (and enable) the alarm for the given ID + read(ID); - return the value for the given ID + readType(ID); - return the alarm type for the given alarm ID + getTriggeredAlarmId(); - returns the currently triggered alarm id, only valid in an alarm callback + +FAQ + +Q: What hardware and software is needed to use this library? +A: This library requires the Time library. No internal or external hardware is used by the Alarm library. + +Q: Why must I use Alarm.delay() instead of delay()? +A: Task scheduling is handled in the Alarm.delay function. +Tasks are monitored and triggered from within the Alarm.delay call so Alarm.delay should be called +whenever a delay is required in your sketch. +If your sketch waits on an external event (for example, a sensor change), +make sure you repeatedly call Alarm.delay while checking the sensor. +You can call Alarm.delay(0) if you need to service the scheduler without a delay. + +Q: Are there any restrictions on the code in a task handler function? +A: No. The scheduler does not use interrupts so your task handling function is no +different from other functions you create in your sketch. + +Q: What are the shortest and longest intervals that can be scheduled? +A: Time intervals can range from 1 second to years. +(If you need timer intervals shorter than 1 second then the TimedAction library +by Alexander Brevig may be more suitable, see: http://www.arduino.cc/playground/Code/TimedAction) + +Q: How are scheduled tasks affected if the system time is changed? +A: Tasks are scheduled for specific times designated by the system clock. +If the system time is reset to a later time (for example one hour ahead) then all +alarms and timers will occur one hour later. +If the system time is set backwards (for example one hour back) then the alarms and timers will occur an hour earlier. +If the time is reset before the time a task was scheduled, then the task will be triggered on the next service (the next call to Alarm.delay). +This is the expected behaviour for Alarms – tasks scheduled for a specific time of day will trigger at that time, but the affect on timers may not be intuitive. If a timer is scheduled to trigger in 5 minutes time and the clock is set ahead by one hour, that timer will not trigger until one hour and 5 minutes has elapsed. + +Q: What is the valid range of times supported by these libraries? +A: The time library is intended to handle times from Jan 1 1970 through Jan 19 2038. + The Alarms library expects dates to be on or after Jan1 1971 so clocks should no be set earlier than this if using Alarms. +(The functions to create alarms will return an error if an earlier date is given). + +Q: How many alarms can be created? +A: Up to six alarms can be scheduled. +The number of alarms can be changed in the TimeAlarms header file (set by the constant dtNBR_ALARMS, +note that the RAM used equals dtNBR_ALARMS * 11) + +onceOnly Alarms and Timers are freed when they are triggered so another onceOnly alarm can be set to trigger again. +There is no limit to the number of times a onceOnly alarm can be reset. + +The following fragment gives one example of how a timerOnce task can be rescheduled: +Alarm.timerOnce(random(10), randomTimer); // trigger after random number of seconds + +void randomTimer(){ + int period = random(2,10); // get a new random period + Alarm.timerOnce(period, randomTimer); // trigger for another random period +} +