LaundrySorcery

Log | Files | Refs

commit 3290ff7f52662c3a6bd5c7366c5589d125ab72e1
Author: Dominik Schmidt <das1993@hotmail.com>
Date:   Wed,  6 Jun 2018 15:51:54 +0000

Initial Commit

Diffstat:
Makefile | 22++++++++++++++++++++++
dist/systemd/LaundrySorcery.service | 6++++++
doc/Algorithm.dot | 38++++++++++++++++++++++++++++++++++++++
src/laundrysorcery.c | 159+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
www/generate_image.sh | 5+++++
www/index.sh | 28++++++++++++++++++++++++++++
www/light_5min.sh | 5+++++
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>| &gt; 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, &timestamp); + 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