My entry for the Internet of Holiday Lights is an electro-mechanical wreath.
My first blog post was a brain dump of possibilities.
In my second post I made a paper prototype.
My third post was about getting the Arduino Yun up and running.
In the fourth post I used the Linux part of the Yun to get at the current date and time.
In the fifth post I scavenged a stepper motor from a flatbed scanner.
In post six that motor was running.
Post seven is covering the Infineon RGB LED shield.
My post number eight is an aside on programming the advent calendar logic.
In post nine I'm covering the creative 'Arts & Crafts" part.
This time I'll cover Time and Event handling.
Why Time management
My wreath will be doing several things. A few of them event handled, other repetitive.
- There are some animations happening (LEDs dimming and flashing)
- A connection with the Eclipse MQTT broker needs to be kept alive.
- I'm sending MQTT messages to something for surprise functionality on a regular base.
- Others.
I can handle all of this in the loop() function without real issues (it's not that complex after all). But a timer can handle all of this without me having to worry about it too much.
The ATMega has spare timers, so why not.
In my design I need three granularities:
- things to be done approx each second
- actions that should be done each minute
- hourly activities
So I've set up a counter that ticks every second and manages the cadence.
void timerInit() { // initialize timer1 for 1 second ticks; ISR(TIMER1_COMPA_vect) will be called as interrupt handler noInterrupts(); // disable all interrupts TCCR1A = 0; TCCR1B = 0; TCNT1 = 0; OCR1A = 62500; // compare match register 16MHz/256/1Hz TCCR1B |= (1 << WGM12); // CTC mode TCCR1B |= (1 << CS12); // 256 prescaler TIMSK1 |= (1 << OCIE1A); // enable timer compare interrupt interrupts(); // enable all interrupts }
My interrupt handler does nothing more than marking what jobs need to execute:
ISR(TIMER1_COMPA_vect) // timer compare interrupt service routine { static unsigned int uSeconds = 0; uSeconds++; // every call is a second bRunSeconds = true; // so yes, flag that the seconds handler should be called bRunMinutes = ! (uSeconds % 60); // each 60th second, flag that the minutes handler should be called if (uSeconds > 3599) { // every hour bRunHours = true; // flag that the hours handler should be called uSeconds = 0; // and start over } }
This is a very short routine that is guaranteed to give back control to non-interrupt code fast
I have a function that calls the right schedule at the right times. That function is the first call in my 'arduino sketch' loop():
void loop() { timerTasks(); // non timer related functionality comes here // ...
void timerTasks() { if (bRunSeconds && ! bIsRunningSeconds) { // timer interrupt flagged that seconds handler should be called runSeconds(); // but we only run it if it's not active } if (bRunMinutes && ! bIsRunningMinutes) { // timer interrupt flagged that minutes handler should be called runMinutes(); // but we only run it if it's not active } if (bRunHours && ! bIsRunningHours) { // timer interrupt flagged that hours handler should be called runHours(); // but we only run it if it's not active } }
As an example, here's some household variables, runSeconds() and the beginning of runMinutes():
boolean bRunSeconds = false; boolean bIsRunningSeconds = false; boolean bRunMinutes = false; boolean bIsRunningMinutes = false; boolean bRunHours = false; boolean bIsRunningHours = false; void runSeconds() { bIsRunningSeconds = true; Serial.print("s"); // remove when confident // task: keep MQTT alive client.yield(30); // this takes 30 ms. May reduce the parameter // if you get duplicates, increase // surprise functionality removed bRunSeconds = false; bIsRunningSeconds = false; } void runMinutes() { bIsRunningMinutes = true; Serial.println("m"); // remove when confident // ..
Why Event management
MQTT is event driven for the receiving side. You subscribe to a message, and provide the callback function that will be invoked upon incoming messages.
That is currently the only (non-interrupt) event driven scenario in my design.
Here's my handler. Some functionality that I'll reveal later is removed.
void messageArrived(MQTT::MessageData& md) // this handler is called when a subscribed MQTT message arrives { MQTT::Message &message = md.message; // debug code sprintf(printbuf, "Message arrived: qos %d, retained %d, dup %d, packetid %d\n", message.qos, message.retained, message.dup, message.id); Serial.print(printbuf); sprintf(printbuf, "Payload %s\n", (char*)message.payload); Serial.print(printbuf); // end debug code // in the real world, I should validate if this is the right topic -- I've only subscribed to one so I'll not bother // here be secret codes for surprise functionality }
Other candidates?
I may add an interrupt handler for a button that I plan to use, but since that is only to be used in calibration scenario's, I may just handle that in my loop(). I'm not in the mood to pull out my debounce lib.
I would go full-fledged if I had a keyboard or an end-user button interface. But that's not the case, so a simple check of the pin state in my loop will do.
update: execution log of one hour operation
invoking linux date linux date invoked 20141231 20141231 day: 20141231 checking against: 20141130 checking against: 20141201 checking against: 20141202 checking against: 20141203 checking against: 20141204 checking against: 20141205 checking against: 20141206 checking against: 20141207 checking against: 20141208 checking against: 20141209 checking against: 20141210 checking against: 20141211 checking against: 20141212 checking against: 20141213 checking against: 20141214 checking against: 20141215 checking against: 20141216 checking against: 20141217 checking against: 20141218 checking against: 20141219 checking against: 20141220 checking against: 20141221 checking against: 20141222 checking against: 20141223 checking against: 20141224 checking against: 20141225 checking against: 20141226 checking against: 20141227 checking against: 20141228 checking against: 20141229 checking against: 20141230 checking against: 20141231 found state : 22 0 ... 0 Payload secret 119 ... 116 motor adjust in seconds: 9 115 motor adjust in seconds: 9 114 motor adjust in seconds: 9 113 motor adjust in seconds: 9 112 motor adjust in seconds: 8 111 motor adjust in seconds: 7 110 motor adjust in seconds: 6 109 motor adjust in seconds: 5 108 motor adjust in seconds: 4 107 motor adjust in seconds: 3 106 motor adjust in seconds: 2 105 motor adjust in seconds: 1 104 motor adjust in seconds: 0 103 motor adjust to advent state 102 ... 65 m 64 ... 6 5 m 4 3 2 1 0 0 ... 0 Payload secret 119 ... 106 motor adjust in seconds: 9 105 motor adjust in seconds: 8 104 motor adjust in seconds: 7 103 motor adjust in seconds: 6 102 motor adjust in seconds: 5 101 motor adjust in seconds: 4 100 motor adjust in seconds: 3 99 motor adjust in seconds: 2 98 motor adjust in seconds: 1 97 motor adjust in seconds: 0 96 motor adjust to advent state 95 m 94 93 ... 36 35 m 34 33 ... 4 3 2 1 0 0 ... 0 0 m 0 ... 0 m h invoking linux date linux date invoked 20141231 20141231 day: 20141231 checking against: 20141130 checking against: 20141201 checking against: 20141202 checking against: 20141203 checking against: 20141204 checking against: 20141205 checking against: 20141206 checking against: 20141207 checking against: 20141208 checking against: 20141209 checking against: 20141210 checking against: 20141211 checking against: 20141212 checking against: 20141213 checking against: 20141214 checking against: 20141215 checking against: 20141216 checking against: 20141217 checking against: 20141218 checking against: 20141219 checking against: 20141220 checking against: 20141221 checking against: 20141222 checking against: 20141223 checking against: 20141224 checking against: 20141225 checking against: 20141226 checking against: 20141227 checking against: 20141228 checking against: 20141229 checking against: 20141230 checking against: 20141231 found state : 22 0 0 ...