Browse Source

various fixes. return to init screen on idle. missing fillnwater case. added some influx export scripts for testing. prepared for automation menu.

Thomas Buck 2 years ago
parent
commit
54b445edb9

+ 1
- 0
.gitignore View File

1
 .pio
1
 .pio
2
 include/wifi.h
2
 include/wifi.h
3
+.directory

+ 2
- 0
include/Functionality.h View File

51
                   const char *c, const char *d, int num_input);
51
                   const char *c, const char *d, int num_input);
52
 void backspace(void);
52
 void backspace(void);
53
 
53
 
54
+bool sm_is_idle(void);
55
+
54
 #endif // _FUNCTIONALITY_H_
56
 #endif // _FUNCTIONALITY_H_

+ 6
- 1
include/Statemachine.h View File

29
 public:
29
 public:
30
     enum States {
30
     enum States {
31
         init = 0,
31
         init = 0,
32
-        menu, // auto, pumps, valves
32
+        menu_a, // manual, auto
33
+        menu_b, // pumps, valves
33
         
34
         
34
         auto_mode_a, // select mode 1
35
         auto_mode_a, // select mode 1
35
         auto_mode_b, // select mode 2
36
         auto_mode_b, // select mode 2
43
         fillnwater_plant, // select plants
44
         fillnwater_plant, // select plants
44
         fillnwater_tank_run,
45
         fillnwater_tank_run,
45
         fillnwater_plant_run,
46
         fillnwater_plant_run,
47
+
48
+        automation_mode,
46
         
49
         
47
         menu_pumps, // selet pump
50
         menu_pumps, // selet pump
48
         menu_pumps_time, // set runtime
51
         menu_pumps_time, // set runtime
91
     void act(void);
94
     void act(void);
92
     
95
     
93
     const char *getStateName(void);
96
     const char *getStateName(void);
97
+    bool isIdle(void);
94
     
98
     
95
 private:
99
 private:
96
     void switch_to(States s);
100
     void switch_to(States s);
106
     uint32_t selected_time; // runtime
110
     uint32_t selected_time; // runtime
107
     unsigned long start_time, stop_time, last_animation_time;
111
     unsigned long start_time, stop_time, last_animation_time;
108
     String error_condition;
112
     String error_condition;
113
+    unsigned long into_state_time;
109
 };
114
 };
110
 
115
 
111
 #endif // _STATEMACHINE_H_
116
 #endif // _STATEMACHINE_H_

+ 2
- 1
include/config.h View File

27
 
27
 
28
 // in milliseconds
28
 // in milliseconds
29
 #define DISPLAY_BACKLIGHT_TIMEOUT (5UL * 60UL * 1000UL)
29
 #define DISPLAY_BACKLIGHT_TIMEOUT (5UL * 60UL * 1000UL)
30
+#define BACK_TO_IDLE_TIMEOUT (5UL * 60UL * 1000UL)
30
 
31
 
31
 // in seconds
32
 // in seconds
32
 #define MAX_TANK_FILL_TIME (67 + 3)
33
 #define MAX_TANK_FILL_TIME (67 + 3)
33
 #define AUTO_PUMP_RUNTIME 5
34
 #define AUTO_PUMP_RUNTIME 5
34
-#define MAX_AUTO_PLANT_RUNTIME (30 * 60)
35
+#define MAX_AUTO_PLANT_RUNTIME (35 * 60)
35
 #define MAX_PUMP_RUNTIME 30
36
 #define MAX_PUMP_RUNTIME 30
36
 #define MAX_VALVE_RUNTIME (45 * 60)
37
 #define MAX_VALVE_RUNTIME (45 * 60)
37
 
38
 

+ 56
- 0
influx/influx-export.py View File

1
+#!/usr/bin/env python
2
+
3
+from influxdb import InfluxDBClient
4
+from datetime import datetime
5
+import xlsxwriter
6
+
7
+# config
8
+host = '10.23.42.14'
9
+port = 8086
10
+user = 'root'
11
+password = 'root'
12
+dbname = 'giessomat'
13
+
14
+# data
15
+client = InfluxDBClient(host, port, user, password, dbname)
16
+print("Querying DB " + dbname + " on " + host)
17
+result = client.query('SELECT "id", "duration" FROM "plant";')
18
+#print("Result: {0}".format(result))
19
+
20
+data = list(result.get_points())
21
+print("Got " + str(len(data)) + " datapoints")
22
+#print(data)
23
+
24
+ids = list(set([ d['id'] for d in data ]))
25
+ids.sort()
26
+#ids = ['1']
27
+print("IDs found: " + str(ids))
28
+
29
+values = []
30
+times = []
31
+durations = []
32
+for id in ids:
33
+    values.append([d for d in data if d['id'] == id])
34
+    times.append([datetime.strptime(d['time'], '%Y-%m-%dT%H:%M:%S.%fZ') for d in data if d['id'] == id])
35
+    durations.append([d['duration'] for d in data if d['id'] == id])
36
+
37
+workbook = xlsxwriter.Workbook('InfluxExport.xlsx')
38
+worksheet = workbook.add_worksheet()
39
+
40
+bold = workbook.add_format({'bold': 1})
41
+date_format = workbook.add_format({'num_format': 'dd.mm.yyyy hh:mm'})
42
+
43
+for i in range(len(ids)):
44
+    worksheet.write_string(0, (i * 3) + 0, "ID", bold)
45
+    worksheet.write_number(0, (i * 3) + 1, int(ids[i]))
46
+
47
+    worksheet.write_string(1, (i * 3) + 0, "Time", bold)
48
+    worksheet.set_column((i * 3) + 0, (i * 3) + 0, 20)
49
+    for j in range(len(times[i])):
50
+        worksheet.write_datetime(j + 2, (i * 3) + 0, times[i][j], date_format)
51
+
52
+    worksheet.write_string(1, (i * 3) + 1, "Duration", bold)
53
+    for j in range(len(durations[i])):
54
+        worksheet.write_number(j + 2, (i * 3) + 1, durations[i][j])
55
+
56
+workbook.close()

+ 133
- 0
influx/influx-graph.py View File

1
+#!/usr/bin/env python
2
+
3
+from influxdb import InfluxDBClient
4
+import matplotlib.pyplot as plt
5
+import matplotlib
6
+from datetime import datetime
7
+
8
+# config
9
+host = '10.23.42.14'
10
+port = 8086
11
+user = 'root'
12
+password = 'root'
13
+dbname = 'giessomat'
14
+
15
+# data
16
+client = InfluxDBClient(host, port, user, password, dbname)
17
+print("Querying DB " + dbname + " on " + host)
18
+result = client.query('SELECT "id", "duration" FROM "plant";')
19
+#print("Result: {0}".format(result))
20
+
21
+data = list(result.get_points())
22
+print("Got " + str(len(data)) + " datapoints")
23
+#print(data)
24
+
25
+ids = list(set([ d['id'] for d in data ]))
26
+ids.sort()
27
+#ids = ['1']
28
+print("IDs found: " + str(ids))
29
+
30
+values = []
31
+times = []
32
+durations = []
33
+for id in ids:
34
+    values.append([d for d in data if d['id'] == id])
35
+    times.append([datetime.strptime(d['time'], '%Y-%m-%dT%H:%M:%S.%fZ') for d in data if d['id'] == id])
36
+    durations.append([d['duration'] for d in data if d['id'] == id])
37
+
38
+s1 = 0.0
39
+s2 = 0.0
40
+c2 = 0
41
+for i in range(len(ids)):
42
+    s = 0.0
43
+    for j in range(len(durations[i])):
44
+        s += durations[i][j]
45
+        s2 += durations[i][j]
46
+        c2 += 1
47
+    avg = s / len(durations[i])
48
+    print("ID=" + ids[i] + " sum=" + str(s) + " len=" + str(len(durations[i])) + " avg=" + str(avg))
49
+    s1 += avg
50
+avg1 = s1 / len(ids)
51
+avg2 = s2 / c2
52
+print("avg1=" + str(avg1) + " avg2=" + str(avg2))
53
+
54
+# plot results
55
+plt.ioff()
56
+
57
+# ---------------------------
58
+
59
+fig, ax = plt.subplots()
60
+
61
+for i in range(len(ids)):
62
+    dates = matplotlib.dates.date2num(times[i])
63
+    ax.plot_date(dates, durations[i], '-', label='id ' + ids[i])
64
+
65
+    ax.set_xlabel('Time')
66
+    ax.set_ylabel('Duration')
67
+    ax.set_title('Watering Durations')
68
+    ax.legend()
69
+
70
+# ---------------------------
71
+
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
+# ---------------------------
90
+
91
+fig, ax = plt.subplots()
92
+
93
+for i in range(len(ids)):
94
+    ax.plot(range(len(durations[i])), durations[i], '-', label='id ' + ids[i])
95
+
96
+    ax.set_xlabel('Watering No.')
97
+    ax.set_ylabel('Duration')
98
+    ax.set_title('Duration per Watering')
99
+    ax.legend()
100
+
101
+# ---------------------------
102
+
103
+fig, ax = plt.subplots()
104
+
105
+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')
129
+    ax.legend()
130
+
131
+# ---------------------------
132
+
133
+plt.show()

+ 9
- 1
src/Functionality.cpp View File

34
 CircularBuffer<int, I2C_BUF_SIZE> keybuffer;
34
 CircularBuffer<int, I2C_BUF_SIZE> keybuffer;
35
 String linebuffer[4];
35
 String linebuffer[4];
36
 
36
 
37
+bool sm_is_idle(void) {
38
+    return true;
39
+}
40
+
37
 #endif // ! FUNCTION_CONTROL
41
 #endif // ! FUNCTION_CONTROL
38
 
42
 
39
 #include "SerialLCD.h"
43
 #include "SerialLCD.h"
67
 
71
 
68
 Statemachine sm(write_to_all, backspace);
72
 Statemachine sm(write_to_all, backspace);
69
 
73
 
74
+bool sm_is_idle(void) {
75
+    return sm.isIdle();
76
+}
77
+
70
 #endif // FUNCTION_CONTROL
78
 #endif // FUNCTION_CONTROL
71
 
79
 
72
 // ----------------------------------------------------------------------------
80
 // ----------------------------------------------------------------------------
528
                   const char *c, const char *d, int num_input) {
536
                   const char *c, const char *d, int num_input) {
529
     const char *lines[4] = { a, b, c, d };
537
     const char *lines[4] = { a, b, c, d };
530
     
538
     
531
-    debug.println("write_to_all i2c");
539
+    //debug.println("write_to_all i2c");
532
     
540
     
533
     for (int i = 0; i < 4; i++) {
541
     for (int i = 0; i < 4; i++) {
534
         Wire.beginTransmission(OWN_I2C_ADDRESS);
542
         Wire.beginTransmission(OWN_I2C_ADDRESS);

+ 93
- 20
src/Statemachine.cpp View File

74
 
74
 
75
 static const char *state_names[] = {
75
 static const char *state_names[] = {
76
     stringify(init),
76
     stringify(init),
77
-    stringify(menu),
77
+    stringify(menu_a),
78
+    stringify(menu_b),
78
     stringify(auto_mode_a),
79
     stringify(auto_mode_a),
79
     stringify(auto_mode_b),
80
     stringify(auto_mode_b),
80
     stringify(auto_fert),
81
     stringify(auto_fert),
86
     stringify(fillnwater_plant),
87
     stringify(fillnwater_plant),
87
     stringify(fillnwater_tank_run),
88
     stringify(fillnwater_tank_run),
88
     stringify(fillnwater_plant_run),
89
     stringify(fillnwater_plant_run),
90
+    stringify(automation_mode),
89
     stringify(menu_pumps),
91
     stringify(menu_pumps),
90
     stringify(menu_pumps_time),
92
     stringify(menu_pumps_time),
91
     stringify(menu_pumps_go),
93
     stringify(menu_pumps_go),
103
     return state_names[state];
105
     return state_names[state];
104
 }
106
 }
105
 
107
 
108
+bool Statemachine::isIdle(void) {
109
+    return state == init;
110
+}
111
+
106
 Statemachine::Statemachine(print_fn _print, backspace_fn _backspace)
112
 Statemachine::Statemachine(print_fn _print, backspace_fn _backspace)
107
         : db(7), selected_plants(plants.countPlants()) {
113
         : db(7), selected_plants(plants.countPlants()) {
108
     state = init;
114
     state = init;
116
     stop_time = 0;
122
     stop_time = 0;
117
     last_animation_time = 0;
123
     last_animation_time = 0;
118
     error_condition = "";
124
     error_condition = "";
125
+    into_state_time = 0;
119
 }
126
 }
120
 
127
 
121
 void Statemachine::begin(void) {
128
 void Statemachine::begin(void) {
124
 
131
 
125
 void Statemachine::input(int n) {
132
 void Statemachine::input(int n) {
126
     if (state == init) {
133
     if (state == init) {
127
-        switch_to(menu);
128
-    } else if (state == menu) {
134
+        switch_to(menu_a);
135
+    } else if ((state == menu_a) || (state == menu_b)) {
129
         if (n == 1) {
136
         if (n == 1) {
130
             switch_to(auto_mode_a);
137
             switch_to(auto_mode_a);
131
         } else if (n == 2) {
138
         } else if (n == 2) {
132
-            switch_to(menu_pumps);
139
+            switch_to(automation_mode);
133
         } else if (n == 3) {
140
         } else if (n == 3) {
141
+            switch_to(menu_pumps);
142
+        } else if (n == 4) {
134
             switch_to(menu_valves);
143
             switch_to(menu_valves);
135
-        } else if ((n == -1) || (n == -2)) {
144
+        } else if (n == -1) {
136
             switch_to(init);
145
             switch_to(init);
146
+        } else if (n == -2) {
147
+            switch_to((state == menu_a) ? menu_b : menu_a);
137
         }
148
         }
149
+    } else if (state == automation_mode) {
150
+        // TODO
151
+        switch_to(menu_a);
138
     } else if ((state == auto_mode_a) || (state == auto_mode_b)) {
152
     } else if ((state == auto_mode_a) || (state == auto_mode_b)) {
139
         if (n == 1) {
153
         if (n == 1) {
140
             switch_to(auto_fert);
154
             switch_to(auto_fert);
161
             selected_plants.clear();
175
             selected_plants.clear();
162
             switch_to(auto_plant);
176
             switch_to(auto_plant);
163
         } else if (n == -1) {
177
         } else if (n == -1) {
164
-            switch_to(menu);
178
+            switch_to(menu_a);
165
         } else if (n == -2) {
179
         } else if (n == -2) {
166
             switch_to((state == auto_mode_a) ? auto_mode_b : auto_mode_a);
180
             switch_to((state == auto_mode_a) ? auto_mode_b : auto_mode_a);
167
         }
181
         }
251
                 backspace();
265
                 backspace();
252
                 db.removeDigit();
266
                 db.removeDigit();
253
             } else {
267
             } else {
254
-                switch_to(menu);
268
+                switch_to(menu_b);
255
             }
269
             }
256
         } else if (n == -2) {
270
         } else if (n == -2) {
257
             if (!db.hasDigits()) {
271
             if (!db.hasDigits()) {
357
             start_time = millis();
371
             start_time = millis();
358
             switch_to(fillnwater_plant_run);
372
             switch_to(fillnwater_plant_run);
359
         } else if (wl == Plants::empty) {
373
         } else if (wl == Plants::empty) {
360
-            stop_time = millis();
361
             switch_to(auto_mode_a);
374
             switch_to(auto_mode_a);
362
         } else if (wl == Plants::invalid) {
375
         } else if (wl == Plants::invalid) {
363
             error_condition = "Invalid sensor state";
376
             error_condition = "Invalid sensor state";
421
         stop_time = millis();
434
         stop_time = millis();
422
         switch_to(menu_pumps_done);
435
         switch_to(menu_pumps_done);
423
     } else if (state == menu_pumps_done) {
436
     } else if (state == menu_pumps_done) {
424
-        switch_to(menu);
437
+        switch_to(menu_b);
425
     } else if (state == menu_valves) {
438
     } else if (state == menu_valves) {
426
         if (n == -1) {
439
         if (n == -1) {
427
             if (db.hasDigits()) {
440
             if (db.hasDigits()) {
428
                 backspace();
441
                 backspace();
429
                 db.removeDigit();
442
                 db.removeDigit();
430
             } else {
443
             } else {
431
-                switch_to(menu);
444
+                switch_to(menu_b);
432
             }
445
             }
433
         } else if (n == -2) {
446
         } else if (n == -2) {
434
             if (!db.hasDigits()) {
447
             if (!db.hasDigits()) {
508
             stop_time = millis();
521
             stop_time = millis();
509
             switch_to(menu_valves_done);
522
             switch_to(menu_valves_done);
510
     } else if (state == menu_valves_done) {
523
     } else if (state == menu_valves_done) {
511
-        switch_to(menu);
524
+        switch_to(menu_b);
512
     } else if (state == error) {
525
     } else if (state == error) {
513
         if (old_state != error) {
526
         if (old_state != error) {
514
             switch_to(old_state);
527
             switch_to(old_state);
515
         } else {
528
         } else {
516
-            switch_to(menu);
529
+            switch_to(menu_a);
517
         }
530
         }
518
     }
531
     }
519
 }
532
 }
587
             // stop if timeout has been reached
600
             // stop if timeout has been reached
588
             plants.abort();
601
             plants.abort();
589
             stop_time = millis();
602
             stop_time = millis();
590
-            switch_to(auto_done);
603
+            if (state == fillnwater_tank_run) {
604
+                auto wl = plants.getWaterlevel();
605
+                if ((wl != Plants::empty) && (wl != Plants::invalid)) {
606
+                    for (int i = 0; i < plants.countPlants(); i++) {
607
+                        if (selected_plants.isSet(i)) {
608
+                            plants.startPlant(i);
609
+                        }
610
+                    }
611
+
612
+                    selected_time = MAX_AUTO_PLANT_RUNTIME;
613
+                    start_time = millis();
614
+                    switch_to(fillnwater_plant_run);
615
+                } else if (wl == Plants::empty) {
616
+                    stop_time = millis();
617
+                    switch_to(auto_done);
618
+                } else if (wl == Plants::invalid) {
619
+                    error_condition = "Invalid sensor state";
620
+                    state = auto_mode_a;
621
+                    switch_to(error);
622
+                }
623
+            } else {
624
+                switch_to(auto_done);
625
+            }
591
         } else if ((millis() - last_animation_time) >= 500) {
626
         } else if ((millis() - last_animation_time) >= 500) {
592
             // update animation if needed
627
             // update animation if needed
593
             last_animation_time = millis();
628
             last_animation_time = millis();
656
             switch_to(error);
691
             switch_to(error);
657
         }
692
         }
658
     }
693
     }
694
+
695
+    if ((state == menu_a) || (state == menu_b) || (state == automation_mode)
696
+            || (state == auto_mode_a) || (state == auto_mode_b)
697
+            || (state == auto_fert) || (state == auto_done)
698
+            || (state == auto_plant) || (state == fillnwater_plant)
699
+            || (state == menu_pumps) || (state == menu_pumps_time)
700
+            || (state == menu_pumps_go) || (state == menu_pumps_done)
701
+            || (state == menu_valves) || (state == menu_valves_time)
702
+            || (state == menu_valves_go) || (state == menu_valves_done)) {
703
+        unsigned long runtime = millis() - into_state_time;
704
+        if (runtime >= BACK_TO_IDLE_TIMEOUT) {
705
+            debug.print("Idle timeout reached in state ");
706
+            debug.println(state_names[state]);
707
+            switch_to(init);
708
+        }
709
+    }
659
 }
710
 }
660
 
711
 
661
 void Statemachine::switch_to(States s) {
712
 void Statemachine::switch_to(States s) {
662
     old_state = state;
713
     old_state = state;
663
     state = s;
714
     state = s;
715
+    into_state_time = millis();
716
+
717
+    if (old_state != state) {
718
+        // don't spam log with every animation state "change"
719
+        debug.print("switch_to ");
720
+        debug.print(state_names[old_state]);
721
+        debug.print(" --> ");
722
+        debug.println(state_names[state]);
723
+    }
664
     
724
     
665
     if (s == init) {
725
     if (s == init) {
666
         String a = String("- Giess-o-mat V") + FIRMWARE_VERSION + String(" -");
726
         String a = String("- Giess-o-mat V") + FIRMWARE_VERSION + String(" -");
670
               "* Delete prev. digit",
730
               "* Delete prev. digit",
671
               "# Execute input num.",
731
               "# Execute input num.",
672
               -1);
732
               -1);
673
-    } else if (s == menu) {
674
-        print("------- Menu -------",
675
-              "1: Automatic program",
676
-              "2: Fertilizer pumps",
677
-              "3: Outlet valves",
733
+    } else if (s == menu_a) {
734
+        print("----- Menu 1/2 -----",
735
+              "1: Manual Operation",
736
+              "2: Automation",
737
+              "#: Go to page 2/2...",
738
+              -1);
739
+    } else if (s == menu_b) {
740
+        print("----- Menu 2/2 -----",
741
+              "3: Fertilizer pumps",
742
+              "4: Outlet valves",
743
+              "#: Go to page 1/2...",
744
+              -1);
745
+    } else if (state == automation_mode) {
746
+        // TODO
747
+        print("---- Automation ----",
748
+              "TODO NOT IMPLEMENTED",
749
+              "TODO NOT IMPLEMENTED",
750
+              "TODO NOT IMPLEMENTED",
678
               -1);
751
               -1);
679
     } else if (s == auto_mode_a) {
752
     } else if (s == auto_mode_a) {
680
-        print("----- Auto 1/2 -----",
753
+        print("---- Manual 1/2 ----",
681
               "1: Add Fertilizer",
754
               "1: Add Fertilizer",
682
               "2: Fill 'n' Water",
755
               "2: Fill 'n' Water",
683
               "#: Go to page 2/2...",
756
               "#: Go to page 2/2...",
684
               -1);
757
               -1);
685
     } else if (s == auto_mode_b) {
758
     } else if (s == auto_mode_b) {
686
-        print("----- Auto 2/2 -----",
759
+        print("---- Manual 2/2 ----",
687
               "3: Fill Reservoir",
760
               "3: Fill Reservoir",
688
               "4: Water a plant",
761
               "4: Water a plant",
689
               "#: Go to page 1/2...",
762
               "#: Go to page 1/2...",

+ 1
- 1
src/WifiStuff.cpp View File

722
     }
722
     }
723
     
723
     
724
     // reset ESP every 6h to be safe
724
     // reset ESP every 6h to be safe
725
-    if (millis() >= (6 * 60 * 60 * 1000)) {
725
+    if ((millis() >= (6UL * 60UL * 60UL * 1000UL)) && (sm_is_idle())) {
726
         ESP.restart();
726
         ESP.restart();
727
     }
727
     }
728
     
728
     

Loading…
Cancel
Save