Browse Source

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

Thomas Buck 3 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
     void clear(void);
28
     void clear(void);
29
     void set(int n);
29
     void set(int n);
30
     bool isSet(int n);
30
     bool isSet(int n);
31
+    int countSet(void);
32
+    int getFirstSet(void);
31
     
33
     
32
 private:
34
 private:
33
     int size;
35
     int size;

+ 4
- 0
include/Statemachine.h View File

111
     unsigned long start_time, stop_time, last_animation_time;
111
     unsigned long start_time, stop_time, last_animation_time;
112
     String error_condition;
112
     String error_condition;
113
     unsigned long into_state_time;
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
 #endif // _STATEMACHINE_H_
120
 #endif // _STATEMACHINE_H_

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

59
 fig, ax = plt.subplots()
59
 fig, ax = plt.subplots()
60
 
60
 
61
 for i in range(len(ids)):
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
     dates = matplotlib.dates.date2num(times[i])
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
     ax.set_xlabel('Time')
72
     ax.set_xlabel('Time')
66
     ax.set_ylabel('Duration')
73
     ax.set_ylabel('Duration')
67
-    ax.set_title('Watering Durations')
74
+    ax.set_title('Cumulative Watering Durations')
68
     ax.legend()
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
 fig, ax = plt.subplots()
81
 fig, ax = plt.subplots()
92
 
82
 
93
 for i in range(len(ids)):
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
     ax.set_ylabel('Duration')
131
     ax.set_ylabel('Duration')
98
-    ax.set_title('Duration per Watering')
132
+    ax.set_title('Smoothed Cumulative Watering Durations')
99
     ax.legend()
133
     ax.legend()
100
 
134
 
101
 # ---------------------------
135
 # ---------------------------
103
 fig, ax = plt.subplots()
137
 fig, ax = plt.subplots()
104
 
138
 
105
 for i in range(len(ids)):
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
     ax.legend()
199
     ax.legend()
130
 
200
 
131
 # ---------------------------
201
 # ---------------------------

+ 19
- 0
src/BoolField.cpp View File

41
 bool BoolField::isSet(int n) {
41
 bool BoolField::isSet(int n) {
42
     return field[n];
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
     last_animation_time = 0;
123
     last_animation_time = 0;
124
     error_condition = "";
124
     error_condition = "";
125
     into_state_time = 0;
125
     into_state_time = 0;
126
+    filling_started_empty = false;
127
+    watering_started_full = false;
126
 }
128
 }
127
 
129
 
128
 void Statemachine::begin(void) {
130
 void Statemachine::begin(void) {
307
                 if (found != 0) {
309
                 if (found != 0) {
308
                     auto wl = plants.getWaterlevel();
310
                     auto wl = plants.getWaterlevel();
309
                     if ((wl != Plants::full) && (wl != Plants::invalid)) {
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
                         plants.openWaterInlet();
316
                         plants.openWaterInlet();
311
                         selected_id = plants.countPlants() + 1;
317
                         selected_id = plants.countPlants() + 1;
312
                         selected_time = MAX_TANK_FILL_TIME;
318
                         selected_time = MAX_TANK_FILL_TIME;
322
                                 }
328
                                 }
323
                             }
329
                             }
324
 
330
 
331
+                            // for recording the flowrate
332
+                            watering_started_full = (wl == Plants::full);
333
+
325
                             selected_time = MAX_AUTO_PLANT_RUNTIME;
334
                             selected_time = MAX_AUTO_PLANT_RUNTIME;
326
                             start_time = millis();
335
                             start_time = millis();
327
                             switch_to(fillnwater_plant_run);
336
                             switch_to(fillnwater_plant_run);
367
                 }
376
                 }
368
             }
377
             }
369
 
378
 
379
+            watering_started_full = (wl == Plants::full);
380
+
370
             selected_time = MAX_AUTO_PLANT_RUNTIME;
381
             selected_time = MAX_AUTO_PLANT_RUNTIME;
371
             start_time = millis();
382
             start_time = millis();
372
             switch_to(fillnwater_plant_run);
383
             switch_to(fillnwater_plant_run);
609
                         }
620
                         }
610
                     }
621
                     }
611
 
622
 
623
+                    watering_started_full = (wl == Plants::full);
624
+
612
                     selected_time = MAX_AUTO_PLANT_RUNTIME;
625
                     selected_time = MAX_AUTO_PLANT_RUNTIME;
613
                     start_time = millis();
626
                     start_time = millis();
614
                     switch_to(fillnwater_plant_run);
627
                     switch_to(fillnwater_plant_run);
635
             plants.abort();
648
             plants.abort();
636
             stop_time = millis();
649
             stop_time = millis();
637
             if (state == fillnwater_tank_run) {
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
                 auto wl = plants.getWaterlevel();
673
                 auto wl = plants.getWaterlevel();
639
                 if ((wl != Plants::empty) && (wl != Plants::invalid)) {
674
                 if ((wl != Plants::empty) && (wl != Plants::invalid)) {
640
                     for (int i = 0; i < plants.countPlants(); i++) {
675
                     for (int i = 0; i < plants.countPlants(); i++) {
643
                         }
678
                         }
644
                     }
679
                     }
645
 
680
 
681
+                    watering_started_full = (wl == Plants::full);
682
+
646
                     selected_time = MAX_AUTO_PLANT_RUNTIME;
683
                     selected_time = MAX_AUTO_PLANT_RUNTIME;
647
                     start_time = millis();
684
                     start_time = millis();
648
                     switch_to(fillnwater_plant_run);
685
                     switch_to(fillnwater_plant_run);
683
         if (wl == Plants::empty) {
720
         if (wl == Plants::empty) {
684
             plants.abort();
721
             plants.abort();
685
             stop_time = millis();
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
             switch_to(auto_done);
751
             switch_to(auto_done);
687
         } else if (wl == Plants::invalid) {
752
         } else if (wl == Plants::invalid) {
688
             plants.abort();
753
             plants.abort();

+ 7
- 0
src/WifiStuff.cpp View File

103
     bool success = false;
103
     bool success = false;
104
 
104
 
105
 #ifdef ENABLE_INFLUXDB_LOGGING
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
     InfluxData measurement(type);
113
     InfluxData measurement(type);
107
     measurement.addTag("version", FIRMWARE_VERSION);
114
     measurement.addTag("version", FIRMWARE_VERSION);
108
     measurement.addTag("device", WiFi.macAddress());
115
     measurement.addTag("device", WiFi.macAddress());

Loading…
Cancel
Save