summaryrefslogtreecommitdiffabout
path: root/lib
Side-by-side diff
Diffstat (limited to 'lib') (more/less context) (ignore whitespace changes)
-rw-r--r--lib/.gitignore11
-rw-r--r--lib/Makefile.am11
-rw-r--r--lib/hypnodata.cc92
-rw-r--r--lib/st-decode.cc121
-rw-r--r--lib/st-download.cc70
-rw-r--r--lib/util.cc13
6 files changed, 318 insertions, 0 deletions
diff --git a/lib/.gitignore b/lib/.gitignore
new file mode 100644
index 0000000..b7b1941
--- a/dev/null
+++ b/lib/.gitignore
@@ -0,0 +1,11 @@
+/.deps
+/.libs
+/st-decode.lo
+/st-decode.o
+/libnapkin.la
+/st-download.lo
+/st-download.o
+/hypnodata.lo
+/hypnodata.o
+/util.lo
+/util.o
diff --git a/lib/Makefile.am b/lib/Makefile.am
new file mode 100644
index 0000000..bf55849
--- a/dev/null
+++ b/lib/Makefile.am
@@ -0,0 +1,11 @@
+lib_LTLIBRARIES = libnapkin.la
+
+INCLUDES = -I${top_builddir}/include/ -I${top_srcdir}/include/ \
+ ${MODULES_CFLAGS}
+LIBS = ${MODULES_CFLAGS}
+
+libnapkin_la_SOURCES = \
+ st-decode.cc st-download.cc \
+ hypnodata.cc \
+ util.cc
+libnapkin_la_LDFLAGS = -version-info 0:0:0
diff --git a/lib/hypnodata.cc b/lib/hypnodata.cc
new file mode 100644
index 0000000..977fb76
--- a/dev/null
+++ b/lib/hypnodata.cc
@@ -0,0 +1,92 @@
+#include <napkin/exception.h>
+#include <napkin/util.h>
+#include <napkin/types.h>
+
+namespace napkin {
+
+ void hypnodata_t::clear() {
+ to_bed = alarm = 0;
+ data_a = window = 0;
+ almost_awakes.clear();
+ }
+
+ static time_t from_minute_w3c(const string& w3c) {
+ struct tm t; memset(&t,0,sizeof(t)); t.tm_isdst=-1;
+ if(sscanf(w3c.c_str(),"%04d-%02d-%02dT%02d:%02d",
+ &t.tm_year,&t.tm_mon,&t.tm_mday,&t.tm_hour,&t.tm_min)!=5)
+ throw exception("failed to parse w3c time");
+ --t.tm_mon;t.tm_year-=1900;
+ time_t rv = mktime(&t);
+ if(rv==(time_t)-1)
+ throw exception("failed to mktime()");
+ return rv;
+ }
+
+ void hypnodata_t::set_to_bed(const string& w3c) {
+ to_bed = from_minute_w3c(w3c); }
+ void hypnodata_t::set_alarm(const string& w3c) {
+ alarm = from_minute_w3c(w3c); }
+ void hypnodata_t::set_window(const string& str) {
+ window = strtol(str.c_str(),0,10); /* TODO: check for error */
+ }
+ void hypnodata_t::set_data_a(const string& str) {
+ data_a = strtol(str.c_str(),0,10); /* TODO: check for error */
+ }
+ void hypnodata_t::set_almost_awakes(const string& str) {
+ almost_awakes.clear();
+ static const char *significants = "0123456789-T:Z";
+ string::size_type p = str.find_first_of(significants);
+ struct tm t; memset(&t,0,sizeof(t)); t.tm_isdst=-1;
+ while(p!=string::npos) {
+ string::size_type ns = str.find_first_not_of(significants,p);
+ string w3c;
+ if(ns==string::npos) {
+ w3c = str.substr(p);
+ p = string::npos;
+ }else{
+ w3c = str.substr(p,ns-p);
+ p = str.find_first_of(significants,ns);
+ }
+ if(w3c.empty()) continue;
+ if(sscanf(w3c.c_str(),"%04d-%02d-%02dT%02d:%02d:%02d",
+ &t.tm_year,&t.tm_mon,&t.tm_mday,&t.tm_hour,&t.tm_min,&t.tm_sec)!=6)
+ throw exception("failed to parse w3c time");
+ --t.tm_mon;t.tm_year-=1900;
+ time_t aa = mktime(&t);
+ if(aa==(time_t)-1)
+ throw exception("failed to mktime()");
+ almost_awakes.push_back(aa);
+ }
+ }
+
+ const string hypnodata_t::w3c_to_bed() const {
+ return strftime("%Y-%m-%dT%H:%M",to_bed); }
+ const string hypnodata_t::w3c_alarm() const {
+ return strftime("%Y-%m-%dT%H:%M",alarm); }
+ const string hypnodata_t::w3c_almostawakes() const {
+ string rv;
+ for(vector<time_t>::const_iterator i=almost_awakes.begin();i!=almost_awakes.end();++i) {
+ if(!rv.empty())
+ rv += ',';
+ rv += strftime("%Y-%m-%dT%H:%M:%S",*i);
+ }
+ return rv;
+ }
+
+ const string hypnodata_t::str_to_bed() const {
+ return strftime("%H:%M",to_bed); }
+ const string hypnodata_t::str_alarm() const {
+ return strftime("%H:%M",alarm); }
+ const string hypnodata_t::str_date() const {
+ return strftime("%Y-%m-%d, %a",alarm); }
+ const string hypnodata_t::str_data_a() const {
+ char tmp[16];
+ snprintf(tmp,sizeof(tmp),"%d:%02d:%02d",
+ data_a/3600, (data_a%3600)/60,
+ data_a % 60 );
+ return tmp; }
+
+ time_t hypnodata_t::aligned_start() const {
+ return alarm - (alarm % (24*60*60)) - 24*60*60; }
+
+}
diff --git a/lib/st-decode.cc b/lib/st-decode.cc
new file mode 100644
index 0000000..f8459ac
--- a/dev/null
+++ b/lib/st-decode.cc
@@ -0,0 +1,121 @@
+#include <stdexcept>
+#include <numeric>
+#include <napkin/exception.h>
+#include <napkin/st/decode.h>
+
+namespace napkin {
+ namespace sleeptracker {
+ using std::invalid_argument;
+ using std::runtime_error;
+
+ struct st_time_t {
+ uint8_t hour;
+ uint8_t min;
+ };
+ struct st_date_t {
+ uint8_t month;
+ uint8_t day;
+ };
+ struct st_fulltime_t {
+ uint8_t hour;
+ uint8_t min;
+ uint8_t sec;
+ };
+ struct st_data_header_t {
+ char magic;
+ st_date_t today;
+ uint8_t unknown;
+ uint8_t window;
+ st_time_t to_bed;
+ st_time_t alarm;
+ uint8_t nawakes;
+ };
+ struct st_data_footer_t {
+ uint16_t data_a;
+ uint8_t checksum;
+ uint8_t eof_mark;
+ };
+
+ static void back_a_day(struct tm& t) {
+ time_t ts = mktime(&t);
+ if(ts==(time_t)-1)
+ throw exception_st_data("failed to make up time to step back a day");
+ ts -= 60*60*24;
+ if(!localtime_r(&ts,&t))
+ throw exception_st_data("failed to localtime_r() while stepping back a day");
+ }
+
+ hypnodata_t& decode(hypnodata_t& rv,const void *data,size_t data_length) {
+ if(data_length < (sizeof(st_data_header_t)+sizeof(st_data_footer_t)))
+ throw exception_st_data_envelope("not enough sleeptracker data to decode");
+ st_data_header_t *h = (st_data_header_t*)data;
+ if(h->magic != 'V')
+ throw exception_st_data_envelope("invalid magic in the data");
+ st_data_footer_t *f = (st_data_footer_t*)(static_cast<const char *>(data)+data_length-sizeof(st_data_footer_t));
+ if( (std::accumulate((uint8_t*)&h->today,(uint8_t*)&f->checksum,0)&0xFF) != f->checksum )
+ throw exception_st_data_integrity("checksum mismatch");
+ st_fulltime_t *aawake = (st_fulltime_t*)&h[1];
+ if((void*)&aawake[h->nawakes] != (void*)f)
+ throw exception_st_data_envelope("unbelievably screwed up data");
+ rv.clear();
+ time_t now = time(0);
+ struct tm t;
+ if(!localtime_r(&now,&t))
+ throw exception_st_data("failed to localtime_r()");
+ t.tm_mon = h->today.month-1;
+ t.tm_mday = h->today.day;
+ time_t mkt = mktime(&t);
+ if(mkt == (time_t)-1)
+ throw exception_st_data("failed to mktime() for a timestamp");
+ if(mkt > now) {
+ --t.tm_year;
+ }
+ struct tm ta;
+ memmove(&ta,&t,sizeof(ta));
+ ta.tm_sec = 0;
+ ta.tm_hour = h->alarm.hour; ta.tm_min = h->alarm.min;
+ rv.alarm = mktime(&ta);
+ if(rv.alarm == (time_t)-1)
+ throw exception_st_data("failed to mktime() for alarm");
+ struct tm tb;
+ memmove(&tb,&ta,sizeof(tb));
+ tb.tm_hour = h->to_bed.hour; tb.tm_min = h->to_bed.min;
+ rv.to_bed = mktime(&tb);
+ if(rv.to_bed == (time_t)-1)
+ throw exception_st_data("failed to mktime() for 'to bed'");
+ if(rv.to_bed > rv.alarm) {
+ back_a_day(tb);
+ rv.to_bed -= 24*60*60;
+ }
+ struct tm taaw;
+ memmove(&taaw,&tb,sizeof(taaw));
+ for(int rest=h->nawakes;rest;--rest,++aawake) {
+ if(
+ taaw.tm_mday!=ta.tm_mday
+ && (
+ aawake->hour < tb.tm_hour
+ || (
+ aawake->hour==tb.tm_hour
+ && aawake->min < tb.tm_min )
+ ) )
+ memmove(&taaw,&ta,sizeof(taaw));
+ taaw.tm_hour = aawake->hour;
+ taaw.tm_min = aawake->min;
+ taaw.tm_sec = aawake->sec;
+ rv.almost_awakes.push_back( mktime(&taaw) );
+ if(rv.almost_awakes.back() == (time_t)-1)
+ throw exception_st_data("failed to mktime() for almost awake moment");
+ }
+ rv.window = h->window;
+ rv.data_a = f->data_a;
+ return rv;
+ }
+
+ hypnodata_ptr_t decode(const void *data,size_t data_length) {
+ hypnodata_ptr_t rv( new hypnodata_t );
+ decode(*rv,data,data_length);
+ return rv;
+ }
+
+ }
+}
diff --git a/lib/st-download.cc b/lib/st-download.cc
new file mode 100644
index 0000000..b56e52d
--- a/dev/null
+++ b/lib/st-download.cc
@@ -0,0 +1,70 @@
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <termios.h>
+#include <stdexcept>
+#include <napkin/exception.h>
+#include <napkin/st/download.h>
+#include <napkin/st/decode.h>
+
+namespace napkin {
+ namespace sleeptracker {
+ using std::runtime_error;
+
+ int download_initiate(const char *port) {
+ int fd = open(port?port:"/dev/sleeptracker",
+ O_RDWR|O_NOCTTY|O_NONBLOCK);
+ if(fd<0)
+ throw exception_st_port("failed to open() sleeptracker port");
+
+ if(tcflush(fd,TCIOFLUSH)) {
+ close(fd);
+ throw exception_st_port("failed to tcflush()");
+ }
+ struct termios ts;
+ ts.c_cflag = CS8|CREAD;
+ cfsetispeed(&ts,B2400); cfsetospeed(&ts,B2400);
+ ts.c_iflag = IGNPAR;
+ ts.c_oflag = ts.c_lflag = 0;
+ ts.c_cc[VMIN]=1; ts.c_cc[VTIME]=0;
+ if(tcsetattr(fd,TCSANOW,&ts)) {
+ close(fd);
+ throw exception_st_port("failed to tcsetattr()");
+ }
+
+ if(write(fd,"V",1)!=1) {
+ close(fd);
+ throw exception_st_port("failed to write() to sleeptracker");
+ }
+ return fd;
+ }
+ size_t download_finish(int fd,void *buffer,size_t buffer_size) {
+ size_t rv = read(fd,buffer,buffer_size);
+ close(fd);
+
+ if(rv==(size_t)-1)
+ throw exception_st_port("failed to read() from sleeptracker");
+ return rv;
+ }
+
+ size_t download(
+ void *buffer,size_t buffer_size,
+ const char *port) {
+ int fd = download_initiate(port);
+ /* this is not the best way to wait for data, but
+ * after all it's a sleeptracker! */
+ sleep(1);
+ return download_finish(fd,buffer,buffer_size);
+ }
+
+ hypnodata_ptr_t download(const char *port) {
+ char buffer[2048];
+ size_t rb = download(buffer,sizeof(buffer),port);
+ hypnodata_ptr_t rv( new hypnodata_t );
+ decode(*rv,buffer,rb);
+ return rv;
+ }
+
+ }
+}
diff --git a/lib/util.cc b/lib/util.cc
new file mode 100644
index 0000000..350cbac
--- a/dev/null
+++ b/lib/util.cc
@@ -0,0 +1,13 @@
+#include <napkin/util.h>
+
+namespace napkin {
+
+ string strftime(const char *fmt,time_t t) {
+ struct tm tt;
+ localtime_r(&t,&tt);// TODO: check res
+ char rv[1024];
+ strftime(rv,sizeof(rv),fmt,&tt); // TODO: check res
+ return rv;
+ }
+
+}