However, the design has two main problems: first, that the clock must be set at least twice a year due to DST (and the only way to set the clock is by plugging in a laptop, as there are no controls to set or display the time); second, that the triac subcircut sucks and generally behaves just a bit awkwardly, with the biggest problem being an occasional flash in the CCFL tube when a large load like the heat pump switches on. (the initial concept also included dimming an incandescent bulb with the triac, but that never worked reliably either). All in all this works reliably enough, but I've wanted to replace it with a better solution.
Problem 2: Setting the time and conforming to DST rules. Solution: Use GPS and code future DST changes in the program flash. GPS reports (e.g., $GPZDA) include UTC time, and PPS output is a reliable timebase.
I've started on the code to do this. So far, I've invented a new time epoch (starting 00:00:00 UTC Jan 1 2000 but counting once per second like the Unix epoch)—a 32-bit "utime" allows counting 2³²-1 seconds (~136 years) into the epoch, well beyond the lifetime I can imagine for this device. I could have used the Unix epoch instead. In retrospect, using the Unix epoch but with unsigned time_t would give an epoch that extends to 2106, also beyond the lifetime I can imagine for the device.
I've created a time structure 'stm', which is like C 'struct tm' but with minimum-sized fields (e.g., stm_sec is a uint8_t); other fields that are not required (like tm_yday) are not in the structure at all.
A pair of functions, utime2stm and stm2utime, convert between the representations. The implementation of stm2utime is quite straightforward, while the implementation of utime2stm is less so. The weekday, hours, minutes, and seconds are easy; years, months and days are the tough part. The latter I implemented while studying Python's datetime module; it provides a function which uses the first day of year 1 as its epoch; because leap years are on a 400-year cycle, the year 2001 works just as well. So after accounting for the fact that I use year 2000 for the epoch, it's fairly easy to write the code for a micro. (Of course, it occurs to me after the fact that I never need to find the year, month, or mday from a utime, unless I add automatic handling of future holidays, in order to skip turning on the light on for days that I don't go to work—actually, this would be a nice feature!)
The most clever bit of stm2utime is how it determines the month for a given yday. It performs the calculation mon = ((yday + 50) >> 5) which a note in datetimemodule.c says is either the correct month or one month after the correct month. A simple comparison suffices to perform the correction. A different approach would be a search (binary or linear) through the table, but this is probably more efficient.
Having all these functions, I can read a UTC time from the GPS and convert it to seconds within the epoch. Every second, I increment the time. At at the top of a minute, I will add the timezone offset to the UTC time and convert it back into (local) time. If it's on a day that the alarm should go off, and the hour and minute match the alarm time, then turn on the light.
The standard unix program 'zdump' knows how to determine all the DST changes and dumps information about when they occur. This can be transmuted into a program that prints a table of changes:
{UINT32_C(0), -21600}, // Sat Jan 1 00:00:00 2000 UTC = Fri Dec 31 18:00:00 1999 CST isdst=0 {UINT32_C(353318400), -18000}, // Sun Mar 13 08:00:00 2011 UTC = Sun Mar 13 03:00:00 2011 CDT isdst=1 {UINT32_C(373878000), -21600}, // Sun Nov 6 07:00:00 2011 UTC = Sun Nov 6 01:00:00 2011 CST isdst=0 …
Since time always progresses forward, it should be very simple to stay on the right table entry: whenever the next table entry's time is not in the future, advance to the next entry. At power-on only it might be necessary to step through multiple entries. If all the data is in seconds, then each entry is 8 bytes (a 16-bit type isn't big enough to store all world UTC offsets), and there are typically 2 entries per year, so 2kiB of flash is enough to store 128 years of rules, or from now to the end of the epoch. Plenty of space for that in flash on, but it's too much to store in RAM, meaning the code will be complicated by pgm_read_xxx calls.
So now I have the "algorithmic" parts of the software designed. I have the parts (arduino, gps, led, pwm led driver). Now all I need is the motivation to work on putting the parts together into a whole.
Files currently attached to this page:
stmtime.c | 6.2kB |
z2a.c | 15.9kB |