Browse Source

record times of filling and watering of single plants in db, to calibrate flowrates.

Thomas Buck 2 years ago
parent
commit
4e003e26f7
6 changed files with 213 additions and 46 deletions
  1. 2
    0
      include/BoolField.h
  2. 4
    0
      include/Statemachine.h
  3. 116
    46
      influx/influx-graph.py
  4. 19
    0
      src/BoolField.cpp
  5. 65
    0
      src/Statemachine.cpp
  6. 7
    0
      src/WifiStuff.cpp

+ 2
- 0
include/BoolField.h View File

@@ -28,6 +28,8 @@ public:
28 28
     void clear(void);
29 29
     void set(int n);
30 30
     bool isSet(int n);
31
+    int countSet(void);
32
+    int getFirstSet(void);
31 33
     
32 34
 private:
33 35
     int size;

+ 4
- 0
include/Statemachine.h View File

@@ -111,6 +111,10 @@ private:
111 111
     unsigned long start_time, stop_time, last_animation_time;
112 112
     String error_condition;
113 113
     unsigned long into_state_time;
114
+
115
+    // used for calibrating, in fill'n'water mode
116
+    bool filling_started_empty;
117
+    bool watering_started_full;
114 118
 };
115 119
 
116 120
 #endif // _STATEMACHINE_H_

+ 116
- 46
influx/influx-graph.py View File

@@ -59,43 +59,77 @@ plt.ioff()
59 59
 fig, ax = plt.subplots()
60 60
 
61 61
 for i in range(len(ids)):
62
+    cumulative = []
63
+    for j in range(len(durations[i])):
64
+        if j == 0:
65
+            cumulative.append(durations[i][j])
66
+        else:
67
+            cumulative.append(cumulative[j - 1] + durations[i][j])
68
+
62 69
     dates = matplotlib.dates.date2num(times[i])
63
-    ax.plot_date(dates, durations[i], '-', label='id ' + ids[i])
70
+    ax.plot_date(dates, cumulative, '-', label='id ' + ids[i])
64 71
 
65 72
     ax.set_xlabel('Time')
66 73
     ax.set_ylabel('Duration')
67
-    ax.set_title('Watering Durations')
74
+    ax.set_title('Cumulative Watering Durations')
68 75
     ax.legend()
69 76
 
70 77
 # ---------------------------
71 78
 
72
-fig, ax = plt.subplots()
73
-
74
-for i in range(len(ids)):
75
-    values = []
76
-    for j in range(len(times[i])):
77
-        if j == 0:
78
-            continue
79
-        delta = times[i][j] - times[i][j - 1]
80
-        values.append(delta.days)
81
-
82
-    ax.plot(range(len(values)), values, '-', label='id ' + ids[i])
83
-
84
-    ax.set_xlabel('Watering No.')
85
-    ax.set_ylabel('Time Difference')
86
-    ax.set_title('Time between Waterings')
87
-    ax.legend()
88
-
89
-# ---------------------------
79
+smoothing_value = 3.0
90 80
 
91 81
 fig, ax = plt.subplots()
92 82
 
93 83
 for i in range(len(ids)):
94
-    ax.plot(range(len(durations[i])), durations[i], '-', label='id ' + ids[i])
84
+    cumulative = []
85
+    for j in range(len(durations[i])):
86
+        if j == 0:
87
+            cumulative.append(durations[i][j])
88
+        else:
89
+            cumulative.append(cumulative[j - 1] + durations[i][j])
90
+
91
+    cumulative_clean = []
92
+    time_clean = []
93
+    xr = iter(range(len(durations[i]) - 1))
94
+    try:
95
+        for j in xr:
96
+            dx_dt = (times[i][j + 1] - times[i][j])
97
+            dx = dx_dt.seconds / 24 / 60 / 60 + dx_dt.days
98
+            if dx <= smoothing_value:
99
+                #print("combining diff=" + str(dx))
100
+                time_clean.append(times[i][j + 1])
101
+                cumulative_clean.append(cumulative[j + 1])
102
+                next(xr)
103
+            else:
104
+                time_clean.append(times[i][j])
105
+                cumulative_clean.append(cumulative[j])
106
+    except:
107
+        pass
108
+
109
+    cumulative_clean_2 = []
110
+    time_clean_2 = []
111
+    xr = iter(range(len(cumulative_clean) - 1))
112
+    try:
113
+        for j in xr:
114
+            dx_dt = (time_clean[j + 1] - time_clean[j])
115
+            dx = dx_dt.seconds / 24 / 60 / 60 + dx_dt.days
116
+            if dx <= smoothing_value:
117
+                #print("combining diff=" + str(dx))
118
+                time_clean_2.append(time_clean[j + 1])
119
+                cumulative_clean_2.append(cumulative_clean[j + 1])
120
+                next(xr)
121
+            else:
122
+                time_clean_2.append(time_clean[j])
123
+                cumulative_clean_2.append(cumulative_clean[j])
124
+    except:
125
+        pass
126
+
127
+    dates = matplotlib.dates.date2num(time_clean_2)
128
+    ax.plot_date(dates, cumulative_clean_2, '-', label='id ' + ids[i])
95 129
 
96
-    ax.set_xlabel('Watering No.')
130
+    ax.set_xlabel('Time')
97 131
     ax.set_ylabel('Duration')
98
-    ax.set_title('Duration per Watering')
132
+    ax.set_title('Smoothed Cumulative Watering Durations')
99 133
     ax.legend()
100 134
 
101 135
 # ---------------------------
@@ -103,29 +137,65 @@ for i in range(len(ids)):
103 137
 fig, ax = plt.subplots()
104 138
 
105 139
 for i in range(len(ids)):
106
-    values = []
107
-    s = 0
108
-    for j in range(len(times[i]) - 1):
109
-        t_delta = times[i][j + 1] - times[i][j]
110
-        dur = (durations[i][j] + durations[i][j + 1]) / 2.0
111
-        #dur = durations[i][j + 1]
112
-        #dur = durations[i][j]
113
-        avg_per_sec = dur / t_delta.total_seconds()
114
-        #if i == 2:
115
-        #    print()
116
-        #    print(dur)
117
-        #    print(t_delta.total_seconds())
118
-        #    print(avg_per_sec)
119
-        avg_per_day = avg_per_sec * 60.0 * 60.0 * 24.0
120
-        values.append(avg_per_day)
121
-        s += avg_per_sec
122
-    #print(s / (len(times[i]) - 1))
123
-
124
-    ax.plot(range(len(values)), values, '-', label='id ' + ids[i])
125
-
126
-    ax.set_xlabel('Watering No.')
127
-    ax.set_ylabel('Duration per Day')
128
-    ax.set_title('Watering Duration per Day')
140
+    cumulative = []
141
+    for j in range(len(durations[i])):
142
+        if j == 0:
143
+            cumulative.append(durations[i][j])
144
+        else:
145
+            cumulative.append(cumulative[j - 1] + durations[i][j])
146
+
147
+    cumulative_clean = []
148
+    time_clean = []
149
+    xr = iter(range(len(durations[i]) - 1))
150
+    try:
151
+        for j in xr:
152
+            dx_dt = (times[i][j + 1] - times[i][j])
153
+            dx = dx_dt.seconds / 24 / 60 / 60 + dx_dt.days
154
+            if dx <= smoothing_value:
155
+                #print("combining diff=" + str(dx))
156
+                time_clean.append(times[i][j + 1])
157
+                cumulative_clean.append(cumulative[j + 1])
158
+                next(xr)
159
+            else:
160
+                time_clean.append(times[i][j])
161
+                cumulative_clean.append(cumulative[j])
162
+    except:
163
+        pass
164
+
165
+    cumulative_clean_2 = []
166
+    time_clean_2 = []
167
+    xr = iter(range(len(cumulative_clean) - 1))
168
+    try:
169
+        for j in xr:
170
+            dx_dt = (time_clean[j + 1] - time_clean[j])
171
+            dx = dx_dt.seconds / 24 / 60 / 60 + dx_dt.days
172
+            if dx <= smoothing_value:
173
+                #print("combining diff=" + str(dx))
174
+                time_clean_2.append(time_clean[j + 1])
175
+                cumulative_clean_2.append(cumulative_clean[j + 1])
176
+                next(xr)
177
+            else:
178
+                time_clean_2.append(time_clean[j])
179
+                cumulative_clean_2.append(cumulative_clean[j])
180
+    except:
181
+        pass
182
+
183
+    rate_of_change = []
184
+    for j in range(len(cumulative_clean_2)):
185
+        if j < len(cumulative_clean_2) - 1:
186
+            dy = cumulative_clean_2[j + 1] - cumulative_clean_2[j]
187
+            dx_dt = (time_clean_2[j + 1] - time_clean_2[j])
188
+            dx = dx_dt.seconds / 24 / 60 / 60 + dx_dt.days
189
+            roc = dy / dx
190
+            #print(str(time_clean_2[j]) + " " + str(dy) + " / " + str(dx) + " = " + str(roc))
191
+            rate_of_change.append(roc)
192
+
193
+    dates = matplotlib.dates.date2num(time_clean_2)
194
+    ax.plot_date(dates[:-1], rate_of_change, '-', label='id ' + ids[i])
195
+
196
+    ax.set_xlabel('Time')
197
+    ax.set_ylabel('Rate of Change')
198
+    ax.set_title('RoC of Cumulative Watering Durations')
129 199
     ax.legend()
130 200
 
131 201
 # ---------------------------

+ 19
- 0
src/BoolField.cpp View File

@@ -41,3 +41,22 @@ void BoolField::set(int n) {
41 41
 bool BoolField::isSet(int n) {
42 42
     return field[n];
43 43
 }
44
+
45
+int BoolField::countSet(void) {
46
+    int c = 0;
47
+    for (int i = 0; i < size; i++) {
48
+        if (field[i]) {
49
+            c++;
50
+        }
51
+    }
52
+    return c;
53
+}
54
+
55
+int BoolField::getFirstSet(void) {
56
+    for (int i = 0; i < size; i++) {
57
+        if (field[i]) {
58
+            return i;
59
+        }
60
+    }
61
+    return -1;
62
+}

+ 65
- 0
src/Statemachine.cpp View File

@@ -123,6 +123,8 @@ Statemachine::Statemachine(print_fn _print, backspace_fn _backspace)
123 123
     last_animation_time = 0;
124 124
     error_condition = "";
125 125
     into_state_time = 0;
126
+    filling_started_empty = false;
127
+    watering_started_full = false;
126 128
 }
127 129
 
128 130
 void Statemachine::begin(void) {
@@ -307,6 +309,10 @@ void Statemachine::input(int n) {
307 309
                 if (found != 0) {
308 310
                     auto wl = plants.getWaterlevel();
309 311
                     if ((wl != Plants::full) && (wl != Plants::invalid)) {
312
+                        // if the waterlevel is currently empty, we
313
+                        // set a flag to record the time to fill
314
+                        filling_started_empty = (wl == Plants::empty);
315
+
310 316
                         plants.openWaterInlet();
311 317
                         selected_id = plants.countPlants() + 1;
312 318
                         selected_time = MAX_TANK_FILL_TIME;
@@ -322,6 +328,9 @@ void Statemachine::input(int n) {
322 328
                                 }
323 329
                             }
324 330
 
331
+                            // for recording the flowrate
332
+                            watering_started_full = (wl == Plants::full);
333
+
325 334
                             selected_time = MAX_AUTO_PLANT_RUNTIME;
326 335
                             start_time = millis();
327 336
                             switch_to(fillnwater_plant_run);
@@ -367,6 +376,8 @@ void Statemachine::input(int n) {
367 376
                 }
368 377
             }
369 378
 
379
+            watering_started_full = (wl == Plants::full);
380
+
370 381
             selected_time = MAX_AUTO_PLANT_RUNTIME;
371 382
             start_time = millis();
372 383
             switch_to(fillnwater_plant_run);
@@ -609,6 +620,8 @@ void Statemachine::act(void) {
609 620
                         }
610 621
                     }
611 622
 
623
+                    watering_started_full = (wl == Plants::full);
624
+
612 625
                     selected_time = MAX_AUTO_PLANT_RUNTIME;
613 626
                     start_time = millis();
614 627
                     switch_to(fillnwater_plant_run);
@@ -635,6 +648,28 @@ void Statemachine::act(void) {
635 648
             plants.abort();
636 649
             stop_time = millis();
637 650
             if (state == fillnwater_tank_run) {
651
+                // record time to fill here, if we started with
652
+                // an empty tank at the start of filling
653
+                if (filling_started_empty) {
654
+                    unsigned long time_to_fill = stop_time - start_time;
655
+                    debug.print("Filling tank took ");
656
+                    debug.print(String(time_to_fill));
657
+                    debug.println("ms");
658
+
659
+#if defined(PLATFORM_ESP)
660
+                    bool success = wifi_write_database(time_to_fill, "calibrated_filling", -1);
661
+                    if (!success) {
662
+                        debug.print("Error writing to InfluxDB ");
663
+                        debug.print(INFLUXDB_HOST);
664
+                        debug.print(":");
665
+                        debug.print(INFLUXDB_PORT);
666
+                        debug.print("/");
667
+                        debug.print(INFLUXDB_DATABASE);
668
+                        debug.println("/calibrated_filling");
669
+                    }
670
+#endif // PLATFORM_ESP
671
+                }
672
+
638 673
                 auto wl = plants.getWaterlevel();
639 674
                 if ((wl != Plants::empty) && (wl != Plants::invalid)) {
640 675
                     for (int i = 0; i < plants.countPlants(); i++) {
@@ -643,6 +678,8 @@ void Statemachine::act(void) {
643 678
                         }
644 679
                     }
645 680
 
681
+                    watering_started_full = (wl == Plants::full);
682
+
646 683
                     selected_time = MAX_AUTO_PLANT_RUNTIME;
647 684
                     start_time = millis();
648 685
                     switch_to(fillnwater_plant_run);
@@ -683,6 +720,34 @@ void Statemachine::act(void) {
683 720
         if (wl == Plants::empty) {
684 721
             plants.abort();
685 722
             stop_time = millis();
723
+
724
+            // if we started watering with a full tank
725
+            // and then finished watering when it was empty
726
+            // and we were only watering a single plant
727
+            // look at this as a "calibration run" and record
728
+            // the time it took to empty the tank
729
+            if ((state == fillnwater_plant_run) && watering_started_full && (selected_plants.countSet() == 1)) {
730
+                unsigned long time_to_water = stop_time - start_time;
731
+                debug.print("Watering plant ");
732
+                debug.print(selected_plants.getFirstSet() + 1);
733
+                debug.print(" with the complete tank took ");
734
+                debug.print(String(time_to_water));
735
+                debug.println("ms");
736
+
737
+#if defined(PLATFORM_ESP)
738
+                bool success = wifi_write_database(time_to_water, "calibrated_watering", selected_plants.getFirstSet() + 1);
739
+                if (!success) {
740
+                    debug.print("Error writing to InfluxDB ");
741
+                    debug.print(INFLUXDB_HOST);
742
+                    debug.print(":");
743
+                    debug.print(INFLUXDB_PORT);
744
+                    debug.print("/");
745
+                    debug.print(INFLUXDB_DATABASE);
746
+                    debug.println("/calibrated_watering");
747
+                }
748
+#endif // PLATFORM_ESP
749
+            }
750
+
686 751
             switch_to(auto_done);
687 752
         } else if (wl == Plants::invalid) {
688 753
             plants.abort();

+ 7
- 0
src/WifiStuff.cpp View File

@@ -103,6 +103,13 @@ bool wifi_write_database(int duration, const char *type, int id) {
103 103
     bool success = false;
104 104
 
105 105
 #ifdef ENABLE_INFLUXDB_LOGGING
106
+    // we still want to be locally usable / have a snappy ui
107
+    // even when the wifi connection is broken.
108
+    if (WiFi.status() != WL_CONNECTED) {
109
+        debug.println("Won't attempt db write, no WiFi connection.");
110
+        return success;
111
+    }
112
+
106 113
     InfluxData measurement(type);
107 114
     measurement.addTag("version", FIRMWARE_VERSION);
108 115
     measurement.addTag("device", WiFi.macAddress());

Loading…
Cancel
Save