AC_CHECK_LIB([Tachyon], [main])
# FIXME: Replace `main' with a function in `-lrt':
AC_CHECK_LIB([rt], [main])
+# FIXME: Replace `main' with a function in `-lpthread':
+AC_CHECK_LIB([pthread], [main])
+
+
+AC_PATH_PROG(XML_CONFIG,xml2-config,no)
+
+if test $XML_CONFIG = "no"
+then
+ echo "XML library not found (see http://xmlsoft.org/)."
+ exit 1;
+fi
+
+XML_CFLAGS=`$XML_CONFIG --cflags`
+XML_LFLAGS=`$XML_CONFIG --libs`
+AC_CHECK_LIB(xml2, xmlParseFile)
+
+
+CXXFLAGS="$CXXFLAGS $XML_CFLAGS -Wall"
+LDFLAGS="$LDFLAGS $XML_LFLAGS"
# Checks for header files.
AC_CHECK_HEADERS([fcntl.h stdlib.h unistd.h])
Elements in an event are:
<itemize>
<item> Label</item>
- <item> Action</item>
<item> Start time</item>
<item> Recurrence pattern</item>
<item> Number of recurrences</item>
<item> End time</item>
+ <item> Action sequence</item>
</itemize>
A recurrence pattern can be specified with a number of days, weeks or months as well as a set of weekdays.
A set of weekdays implies the recurrence will be weekly.
The action for an event can be a light sequence or a curtain control.
</para>
+<para>
+When an event triggers, a sequence of actions is executed.
+Possible actions:
+<itemize>
+ <item>Light control</item>
+ <item>Curtain control</item>
+ <item>Cancel an event</item>
+ <item>Create a sunrise or sunset event</item>
+ <item>Wait a while</item>
+</itemize>
+</para>
<para>
An event is read from an XML element or created dynamically, for example calculated from the time of sunrise.
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="0.86772068"
- inkscape:cx="255.00379"
+ inkscape:cx="140.42641"
inkscape:cy="579.0815"
inkscape:document-units="mm"
inkscape:current-layer="layer1"
inkscape:connector-curvature="0"
inkscape:connection-start="#path4528"
inkscape:connection-end="#rect5098" />
+ <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.517975"
+ y="139.66251"
+ id="text4521"><tspan
+ sodipodi:role="line"
+ id="tspan4519"
+ x="44.517975"
+ y="139.66251"
+ style="stroke-width:0.26458332px">execute</tspan></text>
</g>
</svg>
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="0.98994949"
- inkscape:cx="159.49246"
+ inkscape:cx="281.56296"
inkscape:cy="798.19494"
inkscape:document-units="mm"
inkscape:current-layer="layer1"
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
- <dc:title></dc:title>
+ <dc:title />
</cc:Work>
</rdf:RDF>
</metadata>
id="tspan4567"
x="23.938553"
y="140.03242"
- style="stroke-width:0.26458332px">Fade lights to desired level or open curtains</tspan></text>
+ style="stroke-width:0.26458332px">Execute action sequence</tspan></text>
</g>
</svg>
-bin_PROGRAMS = pwm lightcontrol sunrise read_serial
+bin_PROGRAMS = wakeup pwm lightcontrol sunrise read_serial
+wakeup_SOURCES = wakeup.cpp event.cpp
pwm_SOURCES = pwm.c
-lightcontrol_SOURCES = lightcontrol.cpp
+lightcontrol_SOURCES = lightcontrol.cpp logging.cpp
sunrise_SOURCES = sunrise.cpp
read_serial_SOURCES = read_serial.cpp
-#include <list>
-#include <xml.h>
-#include <date.h>
+#include <String.h>
+
class Action
{
+protected:
+
+ String parameters;
+
public:
-virtual void execute() = 0;
+ void set_parameters(String p)
+ {
+ parameters = p;
+ }
-}
+ virtual String command_line(void)
+ {
+ return String("no command");
+ }
+//virtual void execute() = 0;
+
+};
+
+class Lightstep : public Action
+{
+ virtual String command_line(void)
+ {
+ String cmd("lightcontrol ");
+ cmd += parameters;
+ return cmd;
+ }
+};
-class Lightstep : Action
+class Sleepstep : public Action
{
-}
+ virtual String command_line(void)
+ {
+ String cmd("sleep ");
+ cmd += parameters;
+ return cmd;
+ }
+};
--- /dev/null
+#include "event.h"
+
+void Event::FromXML(xml_element x)
+{
+ std::vector<xml_element> elems;
+
+ elems = x["start"];
+ start_time = elems[0].content();
+
+ elems = x["action"];
+ for (unsigned int i = 0; i < elems.size(); i++)
+ {
+ add_action(elems[i].content());
+ }
+}
+
+String Event::ToXML(void)
+{
+ String xml_text;
+ std::list<Action *>::iterator act;
+
+ xml_text = " <event id='";
+ xml_text += label;
+ xml_text += "'>\n";
+
+ xml_text += " <start>";
+ xml_text += start_time.format();
+ xml_text += "</start>\n";
+
+ for (act = sequence.begin(); act != sequence.end(); act++)
+ {
+ xml_text += " <action>";
+ xml_text += (*act)->command_line();
+ xml_text += "</action>\n";
+ }
+ xml_text += " </event>\n";
+
+ return xml_text;
+}
+
+void Event::add_action(String command)
+{
+ String to_execute, parameters;
+ int separation;
+
+ separation = command.index(' ');
+ to_execute = command(0, separation);
+ parameters = command << separation + 1;
+
+ if (to_execute == "lightcontrol")
+ {
+ Lightstep *act;
+ act = new Lightstep;
+
+ act->set_parameters(parameters);
+ sequence.push_back(act);
+ }
+ else if (to_execute == "sleep")
+ {
+ Sleepstep *act;
+ act = new Sleepstep;
+
+ act->set_parameters(parameters);
+ sequence.push_back(act);
+ }
+}
+
+std::list<Event> read_alarms(const char filename[])
+{
+ std::list<Event> collected_alarms;
+ std::vector<xml_element> alarms;
+
+ xml wakeups;
+ wakeups.ParseFile(filename);
+
+ xml_element wakeup_tree(wakeups);
+
+ alarms = wakeup_tree["event"];
+
+ for (unsigned int i = 0; i < alarms.size(); i++)
+ {
+ Event alarm("name");
+
+ alarm.FromXML(alarms[i]);
+ collected_alarms.push_back(alarm);
+ }
+
+ return collected_alarms;
+}
+
+String alarms_to_XML(std::list<Event> alarms)
+{
+ String xml_text("<?xml version='1.0'?>\n");
+
+ xml_text += "<wakeup>\n";
+
+ std::list<Event>::iterator a;
+
+ for (a = alarms.begin(); a != alarms.end(); a++)
+ {
+ xml_text += a->ToXML();
+ }
+
+ xml_text += "</wakeup>\n";
+ return xml_text;
+}
+#include <list>
+#include <xml.h>
+#include <date.h>
#include "action.h"
class Event
{
- String label;
- date start_time;
- std::list<Action> sequence;
+ String label;
+ UTC start_time;
+ std::list<Action *> sequence;
public:
label = lbl;
}
- void FromXML(xmlnode x);
-}
+ void FromXML(xml_element x);
+ String ToXML(void);
+
+ void add_action(String command);
+};
+
+std::list<Event> read_alarms(const char *filename);
+String alarms_to_XML(std::list<Event> alarms);
#include <errno.h>
#include <stdlib.h>
#include <stdio.h>
+#include <unistd.h>
#include <getopt.h>
#include <iostream>
#include <fstream>
+#include <sstream>
#include <vector>
#include <algorithm>
+#include <configuration.h>
#include <Tachyon.h>
#include "pwm.h"
+#include "logging.h"
//#define DEBUG
signals[N_COLORS].output = -1;
}
+// Create a report of light levels for logging and debugging
+
+std::ostringstream report_lights(std::vector<color_level> lights)
+{
+ std::ostringstream report;
+ lvl_ptr p;
+
+ p = find_level(lights, RED);
+ report << "red=" << p->level << " ";
+ p = find_level(lights, GREEN);
+ report << "green=" << p->level << " ";
+ p = find_level(lights, BLUE);
+ report << "blue=" << p->level << " ";
+ p = find_level(lights, WHITE);
+ report << "white=" << p->level;
+
+ return report;
+}
+
/*
* Fade the lights from the start level to the end level in the specified fade time.
*/
remove("lightcontrol.run");
}
+String parent_command(void)
+{
+ pid_t parent_pid;
+ String parent_comm;
+
+ parent_pid = getppid();
+
+ String parent_proc_filename("/proc/");
+ parent_proc_filename += String(parent_pid) + "/comm";
+
+ std::ifstream parent_proc_file(parent_proc_filename);
+ parent_proc_file >> parent_comm;
+
+ return parent_comm;
+}
+
+
int main(int argc, char *argv[])
{
+ configuration cfg;
+ String logdir;
+
+ cfg.read("lightcontrol");
+ logdir = cfg.find_parameter("logging", "destination");
+ if (logdir == "")
+ {
+ logdir = "."; // default to current dir
+ }
+ logstream log(logdir + "/lightcontrol.log");
+
+ String start_message("lightcontrol started by ");
+
+ log << start_message + parent_command();
int fade_time = 0;
bool change_lights = false;
}
}
+
if (change_lights)
{
+ std::ostringstream report;
+
+ report << "Changing light levels to: ";
+ report << report_lights(desired_levels).str();
+ if (fade_time != 0)
+ {
+ report << ", fading in " << fade_time << " seconds";
+ }
+ log << report.str().c_str();
lightfade(lightlevels, desired_levels, fade_time);
}
}
--- /dev/null
+#include <date.h>
+
+#include "logging.h"
+
+
+logstream & logstream::operator << (const char *msg)
+{
+ UTC timestamp;
+
+ timestamp = Now();
+
+ destination << timestamp << ": " << msg << "\n";
+
+ return *this;
+}
+
+logstream & logstream::operator << (const String &msg)
+{
+ UTC timestamp;
+
+ timestamp = Now();
+
+ destination << timestamp << ": " << msg << "\n";
+
+ return *this;
+}
--- /dev/null
+#include <fstream>
+#include <String.h>
+
+class logstream : public std::ofstream
+{
+ std::ofstream destination;
+
+public:
+
+ logstream(const char * filename)
+ {
+ destination.open(filename, std::ios_base::app);
+ }
+
+ logstream & operator << (const char *msg);
+ logstream & operator << (const String &msg);
+};
--- /dev/null
+#include "event.h"
+
+int main()
+{
+ std::list<Event> set_alarms;
+
+ set_alarms = read_alarms("wakeup_alarms.xml");
+
+ std::cout << alarms_to_XML(set_alarms);
+}
-TESTS = lightctrl lightctrl-oor lightctrl-fade
+TESTS = lightctrl lightctrl-oor lightctrl-fade $(check_PROGRAMS) check_output
+
+AM_CPPFLAGS = -I../src
+LDADD = -lTachyon -lACL
+check_PROGRAMS = wakeup_load_events
+
+wakeup_load_events_SOURCES = wakeup_load_events.cpp ../src/event.cpp
--- /dev/null
+#!/bin/bash
+
+STATUS=0
+
+for output in *.exp
+do
+ logfile=`basename $output .exp`
+ logfile=${logfile}.log
+ echo "Comparing $output with $logfile"
+ diff $output $logfile
+ RESULT=$?
+ if [ $STATUS == 0 ]
+ then
+ STATUS=${RESULT}
+ fi
+done
+exit $STATUS
--- /dev/null
+<?xml version='1.0'?>
+<lightcontrol version='0.0.1'>
+ <logging>
+ <method>file</method>
+ <destination>.</destination>
+ <level>0</level>
+ </logging>
+</lightcontrol>
+
--- /dev/null
+0 0 0 0
+0 0 0 0
+0 0 0 0
+0 1 0 1
+3 4 2 4
+5 7 3 7
+8 11 5 11
+10 14 7 14
+13 18 9 18
+15 21 10 21
+18 24 12 24
+21 28 14 28
+23 31 15 31
+26 35 17 35
+28 38 19 38
+Receiving message.
+The message is A10.000000.
+30 40 20 40
+PASS lightctrl-fade (exit status: 0)
--- /dev/null
+Set light to (R G B W) 0 0 0 50
+Light levels are 0 0 0 50
+Set light to (R G B W) 200 0 0 50
+Light levels are 100 0 0 50
+Set light to (R G B W) 100 0 0 -1
+Light levels are 100 0 0 0
+PASS lightctrl-oor (exit status: 0)
--- /dev/null
+<?xml version='1.0'?>
+<wakeup>
+ <event id='monday_wakeup'>
+ <start>2020-03-22 06:00</start>
+ <action>lightcontrol -r 100 -f 200</action>
+ <action>lightcontrol -g 100 -f 200</action>
+ <action>lightcontrol -w 100 -f 200</action>
+ <action>sleep 600</action>
+ <action>lightcontrol -r 0 -g 0 -w 0 -f 200</action>
+ </event>
+</wakeup>
--- /dev/null
+#include "event.h"
+
+int main()
+{
+ std::list<Event> set_alarms;
+
+ set_alarms = read_alarms("wakeup-01.xml");
+
+ std::cout << alarms_to_XML(set_alarms);
+}
--- /dev/null
+<?xml version='1.0'?>
+<wakeup>
+ <event id='name'>
+ <start>2020-03-22 06:00:00</start>
+ <action>lightcontrol -r 100 -f 200</action>
+ <action>lightcontrol -g 100 -f 200</action>
+ <action>lightcontrol -w 100 -f 200</action>
+ <action>sleep 600</action>
+ <action>lightcontrol -r 0 -g 0 -w 0 -f 200</action>
+ </event>
+</wakeup>
+PASS wakeup_load_events (exit status: 0)