AC_CONFIG_SRCDIR([src/sunrise.cpp])
AC_CONFIG_HEADERS([config.h])
AM_INIT_AUTOMAKE([foreign])
+AC_LANG(C++)
# Checks for programs.
AC_PROG_CXX
</chapter>
+ <chapter>
+ <heading>Hardware</heading>
+<para>
+<picture src='../hardware/ledcontrol-sch.png' eps='../hardware/ledcontrol-sch'/>
+</para>
+<para>
+<picture src='../hardware/ledcontrol-pcb.png' eps='../hardware/ledcontrol-pcb'/>
+</para>
+<para>
+
+IO pin assignments:
+<table cpos='rll'>
+ <thead><col>Connector pin</col><col>RPi IO</col><col>Function</col></thead>
+ <row><col> 3</col><col>GPIO 2</col><col>Light switch</col></row>
+ <row><col> 5</col><col>GPIO 3</col><col>Red PWM</col></row>
+ <row><col> 7</col><col>GPIO 4</col><col>White PWM</col></row>
+ <row><col> 8</col><col>GPIO 14</col><col>Green PWM</col></row>
+ <row><col>10</col><col>GPIO 15</col><col>Blue PWM</col></row>
+ <row><col>35</col><col>GPIO 19</col><col>Curtain open out</col></row>
+ <row><col>36</col><col>GPIO 16</col><col>Curtain open in</col></row>
+ <row><col>37</col><col>GPIO 26</col><col>Curtain close out</col></row>
+ <row><col>38</col><col>GPIO 20</col><col>Curtain close in</col></row>
+</table>
+</para>
+
+ </chapter>
+
<chapter>
<heading>Modules</heading>
<para>
Each action can for example be a change in lights or the opening or closing of curtains.
Event methods:
-FromXML
-ToXML
-add_recurrance
-next_occurance
-add_action
-execute_actions
+<itemize>
+<item>FromXML</item>
+<item>ToXML</item>
+<item>add_recurrance</item>
+<item>next_occurance</item>
+<item>add_action</item>
+<item>execute_actions</item>
+</itemize>
+
+Calculating the next occurance after a certain time is done by adding the number of days or months untill the given time
+is passed or untill the end of the recurrence is reached. A recurrence pattern of weeks is converted to a pattern of n * 7 days.
+The end of the recurrence is specified as an end time or as a maximum number of occurences.
+A recurrence pattern specified as a set of weekdays requires a different calculation.
</para>
</section>
</chapter>
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
- inkscape:zoom="0.86772068"
- inkscape:cx="140.42641"
- inkscape:cy="579.0815"
+ inkscape:zoom="1.2271424"
+ inkscape:cx="233.1688"
+ inkscape:cy="743.6865"
inkscape:document-units="mm"
inkscape:current-layer="layer1"
showgrid="false"
<text
xml:space="preserve"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:3.52777767px;line-height:125%;font-family:sans-serif;-inkscape-font-specification:sans-serif;font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:center;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:middle;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
- x="42.730206"
- y="41.941666"
+ x="52.182652"
+ y="41.636749"
id="text5086"><tspan
sodipodi:role="line"
id="tspan5084"
- x="42.730206"
- y="41.941666"
- style="stroke-width:0.26458332px">action</tspan></text>
+ x="52.182652"
+ y="41.636749"
+ style="stroke-width:0.26458332px">action_sequnce</tspan></text>
<text
xml:space="preserve"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:3.52777767px;line-height:125%;font-family:sans-serif;-inkscape-font-specification:sans-serif;font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:center;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:middle;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
id="tspan5128"
x="132.30797"
y="37.365841"
- style="text-align:start;text-anchor:start;stroke-width:0.26458332px">number</tspan></text>
+ style="text-align:start;text-anchor:start;stroke-width:0.26458332px">maximum_number</tspan></text>
<text
xml:space="preserve"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:3.52777767px;line-height:125%;font-family:sans-serif;-inkscape-font-specification:sans-serif;font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:center;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:middle;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
id="tspan4519"
x="44.517975"
y="139.66251"
- style="stroke-width:0.26458332px">execute</tspan></text>
+ style="stroke-width:0.26458332px">execute()</tspan></text>
+ <text
+ xml:space="preserve"
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:3.52777767px;line-height:125%;font-family:sans-serif;-inkscape-font-specification:sans-serif;font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:center;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:middle;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ x="46.628098"
+ y="57.9599"
+ id="text5313"><tspan
+ sodipodi:role="line"
+ id="tspan5311"
+ x="46.628098"
+ y="57.9599"
+ style="stroke-width:0.26458332px">FromXML()</tspan></text>
+ <text
+ xml:space="preserve"
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:3.52777767px;line-height:125%;font-family:sans-serif;-inkscape-font-specification:sans-serif;font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:center;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:middle;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ x="44.378979"
+ y="62.281059"
+ id="text5317"><tspan
+ sodipodi:role="line"
+ id="tspan5315"
+ x="44.378979"
+ y="62.281059"
+ style="stroke-width:0.26458332px">ToXML()</tspan></text>
+ <text
+ xml:space="preserve"
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:3.52777767px;line-height:125%;font-family:sans-serif;-inkscape-font-specification:sans-serif;font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:center;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:middle;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ x="48.599228"
+ y="66.817833"
+ id="text5321"><tspan
+ sodipodi:role="line"
+ id="tspan5319"
+ x="48.599228"
+ y="66.817833"
+ style="stroke-width:0.26458332px">add_action()</tspan></text>
+ <text
+ xml:space="preserve"
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:3.52777767px;line-height:125%;font-family:sans-serif;-inkscape-font-specification:sans-serif;font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:center;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:middle;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ x="52.584824"
+ y="71.659523"
+ id="text5325"><tspan
+ sodipodi:role="line"
+ id="tspan5323"
+ x="52.584824"
+ y="71.659523"
+ style="stroke-width:0.26458332px">next_occurance()</tspan></text>
+ <text
+ xml:space="preserve"
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:3.52777767px;line-height:125%;font-family:sans-serif;-inkscape-font-specification:sans-serif;font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:center;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:middle;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ x="46.571613"
+ y="108.98867"
+ id="text5329"><tspan
+ sodipodi:role="line"
+ id="tspan5327"
+ x="46.571613"
+ y="108.98867"
+ style="stroke-width:0.26458332px">parameters</tspan></text>
</g>
</svg>
void Event::FromXML(xml_element x)
{
std::vector<xml_element> elems;
+ std::vector<xml_element> recur;
elems = x["start"];
start_time = elems[0].content();
{
add_action(elems[i].content());
}
+
+ recur = x["recurrance"];
+ if (recur.size() != 0)
+ {
+ String number;
+
+ elems = recur[0]["pattern"];
+ pattern = elems[0].content();
+ elems = recur[0]["number"];
+ number = elems[0].content();
+
+ // Initialize the recurrance interval according to pattern
+
+ if (pattern == "weekdays")
+ {
+ // The number element is a comma separated list of day numbers
+
+ SuperString wkdays = number.split(",");
+ for (int i = 0; i < ~wkdays; i++)
+ {
+ weekdays.insert(int(wkdays[i]));
+ }
+ }
+ else if (pattern == "days")
+ {
+ recurrance_interval = UTC(date(long(number), 0, 0), hour(0));
+ }
+ else if (pattern == "months")
+ {
+ recurrance_interval = UTC(date(0, long(number), 0), hour(0));
+ }
+ else if (pattern == "years")
+ {
+ recurrance_interval = UTC(date(0, 0, long(number)), hour(0));
+ }
+ }
}
String Event::ToXML(void)
xml_text += start_time.format();
xml_text += "</start>\n";
+ if (pattern != "none")
+ {
+ xml_text += " <pattern>";
+ xml_text += pattern;
+ xml_text += "</pattern>\n";
+ xml_text += " <number>";
+ if (pattern == "weekdays")
+ {
+ std::set<int>::iterator it;
+ for (it = weekdays.begin(); it != weekdays.end(); it++)
+ {
+ if (it != weekdays.begin())
+ {
+ xml_text += ",";
+ }
+ xml_text += *it;
+ }
+ }
+ else
+ {
+ if (date(recurrance_interval).Year() != 0)
+ {
+ xml_text += int(date(recurrance_interval).Year());
+ }
+ if (date(recurrance_interval).Month() != 0)
+ {
+ xml_text += int(date(recurrance_interval).Month());
+ }
+ if (date(recurrance_interval).Day() != 0)
+ {
+ xml_text += int(date(recurrance_interval).Day());
+ }
+ }
+ xml_text += "</number>\n";
+ }
+
for (act = sequence.begin(); act != sequence.end(); act++)
{
xml_text += " <action>";
}
}
+UTC Event::next_occurance(UTC after)
+{
+ UTC next;
+
+ //std::cout << "Next occ after " << after << ", pattern = " << pattern << "\n";
+ next = start_time;
+
+ if (pattern != "none")
+ {
+ while (next <= after)
+ {
+ if (pattern == "weekdays")
+ {
+ while (next <= after || weekdays.find(date(next).WeekDay()) == weekdays.end())
+ {
+ next += UTC(date(1, 0, 0), hour(0));
+ }
+ }
+ else
+ {
+ next += recurrance_interval;
+ }
+ }
+ }
+
+ if (after >= next)
+ {
+ // The last event has already passed
+ next = UTC(date(0,0,0), hour(0));
+ }
+
+ return next;
+}
+
std::list<Event> read_alarms(const char filename[])
{
std::list<Event> collected_alarms;
#include <list>
+#include <set>
#include <xml.h>
#include <date.h>
{
String label;
UTC start_time;
+
+ String pattern; // for recurrance
+ UTC recurrance_interval;
+ std::set<int> weekdays;
+
std::list<Action *> sequence;
public:
+ Event()
+ {
+ label = "";
+ pattern = "none";
+ }
+
Event(String lbl)
{
label = lbl;
+ pattern = "none";
}
void FromXML(xml_element x);
String ToXML(void);
void add_action(String command);
+
+ UTC next_occurance(UTC after);
};
std::list<Event> read_alarms(const char *filename);
AM_CPPFLAGS = -I../src
LDADD = -lTachyon -lACL
-check_PROGRAMS = wakeup_load_events sunrise_test
+check_PROGRAMS = wakeup_load_events sunrise_test event_recurrance
wakeup_load_events_SOURCES = wakeup_load_events.cpp ../src/event.cpp
+event_recurrance_SOURCES = event_recurrance.cpp ../src/event.cpp
sunrise_test_SOURCES = sunrise_test.cpp ../src/sunrise.cpp
--- /dev/null
+#include "event.h"
+#include "assert.h"
+
+int main()
+{
+ std::list<Event> set_alarms;
+ std::list<Event>::iterator alarm;
+ UTC time_to_check;
+ UTC time_expected;
+ UTC next_time;
+
+ set_alarms = read_alarms("wakeup-02.xml");
+
+ // The first alarm at 2020-03-22 06:00 does not recurr
+
+
+ alarm = set_alarms.begin();
+
+ // Check before the alarm time
+ time_to_check = UTC(date(20, 3, 2020), hour(2, 2, 2));
+ time_expected = UTC(date(22, 3, 2020), hour(6, 0, 0));
+ next_time = alarm->next_occurance(time_to_check);
+ std::cout << "Next alarm after " << time_to_check << " will be " << next_time << "\n";
+ assert(next_time == time_expected);
+
+ // Check after the alarm time
+ time_to_check = UTC(date(22, 3, 2020), hour(6, 2, 2));
+ time_expected = UTC(date(0, 0, 0), hour(0, 0, 0));
+ next_time = alarm->next_occurance(time_to_check);
+ std::cout << "Next alarm after " << time_to_check << " will be " << next_time << "\n";
+ std::cout.flush();
+ assert(next_time == time_expected);
+
+ // The second alarm at 2020-05-24 08:00 recurs weekly
+
+ alarm++;
+
+ // Before the first occurance
+
+ time_to_check = UTC(date(20, 5, 2020), hour(2, 2, 2));
+ time_expected = UTC(date(24, 5, 2020), hour(8, 0, 0));
+ next_time = alarm->next_occurance(time_to_check);
+ std::cout << "Next alarm after " << time_to_check << " will be " << next_time << "\n";
+ std::cout.flush();
+ assert(next_time == time_expected);
+
+ // After the first occurance
+
+ time_to_check = UTC(date(24, 5, 2020), hour(8, 2, 2));
+ time_expected = UTC(date(31, 5, 2020), hour(8, 0, 0));
+ next_time = alarm->next_occurance(time_to_check);
+ std::cout << "Next alarm after " << time_to_check << " will be " << next_time << "\n";
+ std::cout.flush();
+ assert(next_time == time_expected);
+
+ // Much later after the first occurance
+
+ time_to_check = UTC(date(25, 11, 2020), hour(2, 2, 2));
+ time_expected = UTC(date(29, 11, 2020), hour(8, 0, 0));
+ next_time = alarm->next_occurance(time_to_check);
+ std::cout << "Next alarm after " << time_to_check << " will be " << next_time << "\n";
+ std::cout.flush();
+ assert(next_time == time_expected);
+
+ // The third alarm at 2020-04-01 07:00 (Wednesday) recurs every Tuesday, Thursday and Friday
+
+ alarm++;
+
+ // Before the first occurance
+
+ time_to_check = UTC(date(20, 3, 2020), hour(2, 2, 2));
+ time_expected = UTC(date( 2, 4, 2020), hour(7, 0, 0)); // the first Thursday
+ next_time = alarm->next_occurance(time_to_check);
+ std::cout << "Next alarm after " << time_to_check << " will be " << next_time << ". Expected " << time_expected << "\n";
+ std::cout.flush();
+ assert(next_time == time_expected);
+
+ // After the first occurance
+
+ time_to_check = UTC(date( 2, 4, 2020), hour(7, 2, 2));
+ time_expected = UTC(date( 3, 4, 2020), hour(7, 0, 0)); // the next Friday
+ next_time = alarm->next_occurance(time_to_check);
+ std::cout << "Next alarm after " << time_to_check << " will be " << next_time << ". Expected " << time_expected << "\n";
+ std::cout.flush();
+ assert(next_time == time_expected);
+
+ // After the second occurance
+
+ time_to_check = UTC(date( 3, 4, 2020), hour(7, 2, 2));
+ time_expected = UTC(date( 7, 4, 2020), hour(7, 0, 0)); // the next Tuesday
+ next_time = alarm->next_occurance(time_to_check);
+ std::cout << "Next alarm after " << time_to_check << " will be " << next_time << ". Expected " << time_expected << "\n";
+ std::cout.flush();
+ assert(next_time == time_expected);
+
+}
--- /dev/null
+Next alarm after 20-03-2020 02:02:02 will be 22-03-2020 06:00:00
+Next alarm after 22-03-2020 06:02:02 will be 00-00-0 00:00:00
+Next alarm after 20-05-2020 02:02:02 will be 24-05-2020 08:00:00
+Next alarm after 24-05-2020 08:02:02 will be 31-05-2020 08:00:00
+Next alarm after 25-11-2020 02:02:02 will be 29-11-2020 08:00:00
+Next alarm after 20-03-2020 02:02:02 will be 02-04-2020 07:00:00. Expected 02-04-2020 07:00:00
+Next alarm after 02-04-2020 07:02:02 will be 03-04-2020 07:00:00. Expected 03-04-2020 07:00:00
+Next alarm after 03-04-2020 07:02:02 will be 07-04-2020 07:00:00. Expected 07-04-2020 07:00:00
+PASS event_recurrance (exit status: 0)
--- /dev/null
+<?xml version='1.0'?>
+<wakeup>
+ <event id='non-recurring'>
+ <start>2020-03-22 06:00</start>
+ <action>lightcontrol -r 100 -f 200</action>
+ </event>
+ <event id='recurr-weekly'>
+ <start>2020-05-24 08:00</start>
+ <recurrance>
+ <pattern>days</pattern>
+ <number>7</number>
+ </recurrance>
+ <action>lightcontrol -g 100 -f 200</action>
+ </event>
+ <event id='recurr-somedays'>
+ <start>2020-04-02 07:00</start>
+ <recurrance>
+ <pattern>weekdays</pattern>
+ <number>2,4,5</number>
+ </recurrance>
+ <action>lightcontrol -g 100 -f 200</action>
+ </event>
+</wakeup>
set_alarms = read_alarms("wakeup-01.xml");
std::cout << alarms_to_XML(set_alarms);
+
+ set_alarms = read_alarms("wakeup-02.xml");
+
+ std::cout << alarms_to_XML(set_alarms);
}
<action>lightcontrol -r 0 -g 0 -w 0 -f 200</action>
</event>
</wakeup>
+<?xml version='1.0'?>
+<wakeup>
+ <event id='name'>
+ <start>2020-03-22 06:00:00</start>
+ <action>lightcontrol -r 100 -f 200</action>
+ </event>
+ <event id='name'>
+ <start>2020-05-24 08:00:00</start>
+ <pattern>days</pattern>
+ <number>7</number>
+ <action>lightcontrol -g 100 -f 200</action>
+ </event>
+ <event id='name'>
+ <start>2020-04-02 07:00:00</start>
+ <pattern>weekdays</pattern>
+ <number>2,4,5</number>
+ <action>lightcontrol -g 100 -f 200</action>
+ </event>
+</wakeup>
PASS wakeup_load_events (exit status: 0)