commit 3290ff7f52662c3a6bd5c7366c5589d125ab72e1
Author: Dominik Schmidt <das1993@hotmail.com>
Date: Wed, 6 Jun 2018 15:51:54 +0000
Initial Commit
Diffstat:
7 files changed, 263 insertions(+), 0 deletions(-)
diff --git a/Makefile b/Makefile
@@ -0,0 +1,22 @@
+DATASOURCES = light off_mean off_variance on_mean on_variance
+
+DESTDIR ?= /usr/local
+WWWDIR ?= /var/www
+
+.PHONY:all database.rrd plot.png install
+
+all:laundrysorcery %.rrd
+
+src/laundrysorcery: src/laundrysorcery.c
+ gcc -lwiringPi -o $@ $^ -lm -lrrd
+
+%.rrd:
+ rrdtool create $@ --step 1s $(foreach ds,$(DATASOURCES),DS:$(ds):GAUGE:10:U:U) RRA:AVERAGE:0.5:900:96 RRA:AVERAGE:0.5:60:60 RRA:AVERAGE:0.5:1:300
+
+plot.png:
+ rrdtool graph -s -300s $@ DEF:mylight=database.rrd:light:AVERAGE:step=1 LINE2:mylight#ff0000
+
+install: src/laundrysorcery $(WWWDIR)/database.rrd
+ install -s -m 755 src/laundrysorcery $(DESTDIR)/bin/laundrysorcery
+ install -m 600 dist/systemd/LaundrySorcery.service /var/lib/systemd/system/LaundrySorcery.service
+ install -m 755 $(wildcard www/index.sh www/plot_*.sh) -t $(WWWDIR)
diff --git a/dist/systemd/LaundrySorcery.service b/dist/systemd/LaundrySorcery.service
@@ -0,0 +1,6 @@
+[Unit]
+Description=Light sensory application for the laundry machine
+
+[Service]
+ExecStart=/usr/local/bin/laundrysorcery
+WorkingDirectory=/var/www
diff --git a/doc/Algorithm.dot b/doc/Algorithm.dot
@@ -0,0 +1,38 @@
+digraph G{
+ node[shape="rectangle"]
+ //splines=ortho;
+ rankdir=LR;
+ Init [label=<<B>Init</B><BR/>X<SUB>on</SUB> ~ N(0,0)<BR/>X<SUB>off</SUB> ~ N(0,0)<BR/>X<SUB>c</SUB>=X<SUB>off</SUB><BR/>on=false <BR/>λ<SUB>on</SUB>=0.75<BR/>λ<SUB>off</SUB>=0.75>];
+ {rank=same;
+ Measure [label="Measure Δt"];
+ OutlierZero [label="outliercnt=0"];
+ Update [label=<<B>Update Gaussian</B><BR/> X<SUB>c</SUB> ~ λ<SUB>c</SUB>*X<SUB>c</SUB>+(1-λ<SUB>c</SUB>)*N(Δt,Δt²)<BR/> λ<SUB>c</SUB>=min(λ<SUB>c</SUB>+1/1000,0.999999)>];
+ }
+ Min_Measures [label=<X<SUB>c</SUB> has more than 50 measurements?>];
+ subgraph cluster0{
+ rank=same;
+ Comp1 [label=<|Δt-µ<SUB>off</SUB>| > 5*σ<SUB>off</SUB>>];
+ Comp2 [label=<|Δt-µ<SUB>off</SUB>| ≷ |Δt-µ<SUB>on</SUB>|>];
+ }
+ Outlier [label="outliercnt++ > 100"];
+ Switch [label=<<B>Toggle State</B><BR/>on=!on<BR/>X<SUB>c</SUB>=X<SUB>on/off</SUB>>];
+
+ Init -> Measure;
+ Measure -> Min_Measures;
+ Update -> Measure[constraint=false];
+ OutlierZero -> Update[constraint=false];
+ Switch -> Measure[constraint=false];
+
+
+ edge[color="green"];
+ Min_Measures -> Comp1[label=<X<SUB>on</SUB> has no measurements>];
+ Min_Measures -> Comp2[label=<X<SUB>on</SUB> has measurements>];
+ Comp1 -> Outlier;
+ Comp2 -> Outlier;
+ Outlier -> Switch;
+
+ edge[color="red",constraint=false]
+ Min_Measures -> Update;
+ Comp1 -> OutlierZero;
+ Comp2 -> OutlierZero;
+}
diff --git a/src/laundrysorcery.c b/src/laundrysorcery.c
@@ -0,0 +1,159 @@
+
+#include <stdio.h>
+#include <wiringPi.h>
+#include <time.h>
+#include <rrd.h>
+#include <math.h>
+
+typedef struct {
+ double mean;
+ double variance;
+ unsigned int measurements;
+} Gaussian;
+
+
+void update_gaussian_lambda(Gaussian *g, double value, double lambda){
+ g->mean=g->mean*lambda+(1-lambda)*value;
+ g->variance=g->variance*lambda+(1-lambda)*value*value;
+ unsigned int tmp=g->measurements++;
+ if(tmp > g->measurements){
+ g->measurements=tmp;
+ }
+}
+
+void update_gaussian(Gaussian *g, double value){
+ const unsigned int max_measures=1000;
+ double lambda=0.75+0.25/max_measures*g->measurements;
+ lambda=fmin(lambda,0.9999999);
+ update_gaussian_lambda(g,value,lambda);
+}
+
+
+double get_sigma(Gaussian *g){
+ return (g->variance - (g->mean * g->mean));
+}
+
+double get_stdev(Gaussian *g){
+ return sqrt(get_sigma(g));
+}
+
+Gaussian g_on,g_off;
+int on=0;
+Gaussian *current=&g_off;
+struct timespec timestamp;
+double running_mean;
+
+char *on_file;
+
+void turn_on(void){
+ FILE *f=fopen(on_file, "w");
+ fprintf(f, "%lu\n", time(NULL));
+ fclose(f);
+}
+
+void turn_off(void){
+ FILE *f=fopen(on_file, "w");
+ fclose(f);
+}
+
+void toggle_on_off(void){
+ if(on){
+ on=0;
+ current=&g_off;
+ turn_off();
+ }
+ else{
+ on=1;
+ current=&g_on;
+ turn_on();
+ }
+}
+
+int guess_is_on(double delta_t){
+ if(g_on.measurements==0){
+ return fabs(delta_t-current->mean)>5*get_stdev(current);
+ }
+ else{
+ //we assume that sigma_on == sigma_off here, to make the calculation simpler
+ if(fabs(delta_t-g_on.mean)>fabs(delta_t-g_off.mean)){ // closer to "off"
+ return 0;
+ }
+ else{
+ return 1;
+ }
+ }
+}
+
+int guess_if_toggle(double delta_t){
+ return guess_is_on(delta_t)^on;
+}
+
+void process_datapoint(double delta_t){
+ static unsigned int consecutive_outliers=0;
+ running_mean=(running_mean+delta_t)/2;
+ if(current->measurements>50){
+ if(guess_if_toggle(delta_t)){
+ if(consecutive_outliers++>100){
+ toggle_on_off();
+ consecutive_outliers=0;
+ }
+ return; //dont update gaussians with outliers
+ }
+ else{
+ consecutive_outliers=0;
+ }
+ }
+ update_gaussian(current,delta_t);
+}
+
+void falling_edge(void) {
+ clock_gettime(CLOCK_MONOTONIC, ×tamp);
+ digitalWrite (0, HIGH);
+}
+
+void rising_edge(void) {
+ struct timespec timetemp;
+ clock_gettime(CLOCK_MONOTONIC, &timetemp);
+ double delta_t = (double)(timetemp.tv_sec - timestamp.tv_sec) * 1.0e6 +
+ (double)(timetemp.tv_nsec - timestamp.tv_nsec)*1e-3;
+ digitalWrite (0, LOW);
+
+ process_datapoint(delta_t);
+}
+
+
+
+void edge(void) {
+ if(digitalRead(1)==0)
+ falling_edge();
+ else
+ rising_edge();
+}
+
+
+int main(int argc, char **argv)
+{
+ wiringPiSetup () ;
+ pinMode (0, OUTPUT) ;
+ //pinMode (1, INPUT) ;
+ if(argc != 2){
+ printf("Usage: %s </path/to/onfile>\n", argv[0]);
+ return 0;
+ }
+ on_file=argv[1];
+
+ wiringPiISR(1, INT_EDGE_BOTH, &edge);
+ digitalWrite (0, LOW);
+ delay(5);
+ digitalWrite (0, HIGH);
+ char buffer[512];
+ const char* arr[]={buffer};
+ while(1) {
+ delay(1000);
+ snprintf(buffer,sizeof(buffer),"%lu:%f:%f:%f:%f:%f",(unsigned long)time(NULL),running_mean,g_off.mean,g_off.variance,g_on.mean,g_on.variance);
+ rrd_update_r("database.rrd", NULL, 1, arr);
+
+ }
+ return 0;
+}
+
diff --git a/www/generate_image.sh b/www/generate_image.sh
@@ -0,0 +1,5 @@
+#!/bin/bash
+
+echo "Content-Type: image/png"
+
+rrdtool rrdtool graph -s "${START}" /dev/stdout DEF:mylight=database.rrd:light:AVERAGE:step=${RESOLUTION} LINE2:mylight#ff0000
diff --git a/www/index.sh b/www/index.sh
@@ -0,0 +1,28 @@
+#!/bin/bash
+
+
+
+cat <<-EOF
+Content-Type: text/html
+
+<!DOCTYPE html>
+<HTML>
+ <head>
+ <title>LaundrySorcery</title>
+ <meta charset="UTF-8"/>
+ <style type="text/CSS">
+
+ </style>
+ </head>
+ <body>
+ <H1>Laundry Status</H1>
+
+ <H1>Light Sensor Data</H1>
+ <img src="light_5min.sh" />
+ <script type="text/JavaScript">
+ <!--
+ -->
+ </script>
+ </body>
+</HTML>
+EOF
diff --git a/www/light_5min.sh b/www/light_5min.sh
@@ -0,0 +1,5 @@
+#!/bin/bash
+
+START="-300"
+RESOLUTION="1"
+source generate_plot.sh