Added configuration and logging for lightcontrol
authorArjen Baart <arjen@andromeda.nl>
Tue, 31 Mar 2020 14:17:24 +0000 (16:17 +0200)
committerArjen Baart <arjen@andromeda.nl>
Tue, 31 Mar 2020 14:17:24 +0000 (16:17 +0200)
20 files changed:
configure.ac
doc/design.xml
doc/wakeup-classes.svg
doc/wakeup.svg
src/Makefile.am
src/action.h
src/event.cpp [new file with mode: 0644]
src/event.h
src/lightcontrol.cpp
src/logging.cpp [new file with mode: 0644]
src/logging.h [new file with mode: 0644]
src/wakeup.cpp [new file with mode: 0644]
test/Makefile.am
test/check_output [new file with mode: 0755]
test/lightcontrol.conf [new file with mode: 0644]
test/lightctrl-fade.exp [new file with mode: 0644]
test/lightctrl-oor.exp [new file with mode: 0644]
test/wakeup-01.xml [new file with mode: 0644]
test/wakeup_load_events.cpp [new file with mode: 0644]
test/wakeup_load_events.exp [new file with mode: 0644]

index 3dabdf3..081ba58 100644 (file)
@@ -18,6 +18,25 @@ AC_CHECK_LIB([ACL], [Now])
 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])
index 9aef045..e531d81 100644 (file)
@@ -344,16 +344,27 @@ The wakeup times are specified like calendar events, possibly with a recurrence
 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.
index 950d04b..054b46d 100644 (file)
@@ -26,7 +26,7 @@
      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>
index 49e786f..7c1be91 100644 (file)
@@ -26,7 +26,7 @@
      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"
@@ -44,7 +44,7 @@
         <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>
index 59f9787..0507dc3 100644 (file)
@@ -1,7 +1,8 @@
-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
index 8f29894..797ab80 100644 (file)
@@ -1,16 +1,44 @@
-#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;
+   }
+};
 
diff --git a/src/event.cpp b/src/event.cpp
new file mode 100644 (file)
index 0000000..a2f10d8
--- /dev/null
@@ -0,0 +1,106 @@
+#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;
+}
index ab1214e..b4cdd27 100644 (file)
@@ -1,12 +1,15 @@
 
+#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:
 
@@ -15,5 +18,11 @@ 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);
index a1b13a2..f5c0100 100644 (file)
@@ -8,13 +8,17 @@
 #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
 
@@ -189,6 +193,25 @@ void light_to_pwm(std::vector<color_level> &lightlevels)
    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.
  */
@@ -247,8 +270,39 @@ void lightfade(std::vector<color_level> start_lvl, std::vector<color_level> end_
    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;
@@ -345,8 +399,18 @@ int main(int argc, char *argv[])
       }
    }
 
+
    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);
    }
 }
diff --git a/src/logging.cpp b/src/logging.cpp
new file mode 100644 (file)
index 0000000..65241dc
--- /dev/null
@@ -0,0 +1,26 @@
+#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;
+}
diff --git a/src/logging.h b/src/logging.h
new file mode 100644 (file)
index 0000000..8998cf9
--- /dev/null
@@ -0,0 +1,17 @@
+#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);
+};
diff --git a/src/wakeup.cpp b/src/wakeup.cpp
new file mode 100644 (file)
index 0000000..7dd048f
--- /dev/null
@@ -0,0 +1,10 @@
+#include "event.h"
+
+int main()
+{
+   std::list<Event>   set_alarms;
+
+   set_alarms = read_alarms("wakeup_alarms.xml");
+
+   std::cout << alarms_to_XML(set_alarms);
+}
index 017067c..f1a54f3 100644 (file)
@@ -1 +1,7 @@
-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
diff --git a/test/check_output b/test/check_output
new file mode 100755 (executable)
index 0000000..62f828f
--- /dev/null
@@ -0,0 +1,17 @@
+#!/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
diff --git a/test/lightcontrol.conf b/test/lightcontrol.conf
new file mode 100644 (file)
index 0000000..cf01efd
--- /dev/null
@@ -0,0 +1,9 @@
+<?xml version='1.0'?>
+<lightcontrol version='0.0.1'>
+   <logging>
+      <method>file</method>
+      <destination>.</destination>
+      <level>0</level>
+   </logging>
+</lightcontrol>
+
diff --git a/test/lightctrl-fade.exp b/test/lightctrl-fade.exp
new file mode 100644 (file)
index 0000000..525364a
--- /dev/null
@@ -0,0 +1,19 @@
+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)
diff --git a/test/lightctrl-oor.exp b/test/lightctrl-oor.exp
new file mode 100644 (file)
index 0000000..228ffb1
--- /dev/null
@@ -0,0 +1,7 @@
+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)
diff --git a/test/wakeup-01.xml b/test/wakeup-01.xml
new file mode 100644 (file)
index 0000000..eb4e8f8
--- /dev/null
@@ -0,0 +1,11 @@
+<?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>
diff --git a/test/wakeup_load_events.cpp b/test/wakeup_load_events.cpp
new file mode 100644 (file)
index 0000000..219bd1b
--- /dev/null
@@ -0,0 +1,10 @@
+#include "event.h"
+
+int main()
+{
+   std::list<Event>   set_alarms;
+
+   set_alarms = read_alarms("wakeup-01.xml");
+
+   std::cout << alarms_to_XML(set_alarms);
+}
diff --git a/test/wakeup_load_events.exp b/test/wakeup_load_events.exp
new file mode 100644 (file)
index 0000000..1acff55
--- /dev/null
@@ -0,0 +1,12 @@
+<?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)