Переглянути джерело

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

Thomas Buck 3 роки тому
джерело
коміт
54b445edb9

+ 1
- 0
.gitignore Переглянути файл

@@ -1,2 +1,3 @@
1 1
 .pio
2 2
 include/wifi.h
3
+.directory

+ 2
- 0
include/Functionality.h Переглянути файл

@@ -51,4 +51,6 @@ void write_to_all(const char *a, const char *b,
51 51
                   const char *c, const char *d, int num_input);
52 52
 void backspace(void);
53 53
 
54
+bool sm_is_idle(void);
55
+
54 56
 #endif // _FUNCTIONALITY_H_

+ 6
- 1
include/Statemachine.h Переглянути файл

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

+ 2
- 1
include/config.h Переглянути файл

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

+ 56
- 0
influx/influx-export.py Переглянути файл

@@ -0,0 +1,56 @@
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 Переглянути файл

@@ -0,0 +1,133 @@
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 Переглянути файл

@@ -34,6 +34,10 @@
34 34
 CircularBuffer<int, I2C_BUF_SIZE> keybuffer;
35 35
 String linebuffer[4];
36 36
 
37
+bool sm_is_idle(void) {
38
+    return true;
39
+}
40
+
37 41
 #endif // ! FUNCTION_CONTROL
38 42
 
39 43
 #include "SerialLCD.h"
@@ -67,6 +71,10 @@ int switch_pins[SWITCH_COUNT] = { SWITCH_PINS };
67 71
 
68 72
 Statemachine sm(write_to_all, backspace);
69 73
 
74
+bool sm_is_idle(void) {
75
+    return sm.isIdle();
76
+}
77
+
70 78
 #endif // FUNCTION_CONTROL
71 79
 
72 80
 // ----------------------------------------------------------------------------
@@ -528,7 +536,7 @@ void write_to_all(const char *a, const char *b,
528 536
                   const char *c, const char *d, int num_input) {
529 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 541
     for (int i = 0; i < 4; i++) {
534 542
         Wire.beginTransmission(OWN_I2C_ADDRESS);

+ 93
- 20
src/Statemachine.cpp Переглянути файл

@@ -74,7 +74,8 @@ uint32_t Statemachine::DigitBuffer::getNumber(void) {
74 74
 
75 75
 static const char *state_names[] = {
76 76
     stringify(init),
77
-    stringify(menu),
77
+    stringify(menu_a),
78
+    stringify(menu_b),
78 79
     stringify(auto_mode_a),
79 80
     stringify(auto_mode_b),
80 81
     stringify(auto_fert),
@@ -86,6 +87,7 @@ static const char *state_names[] = {
86 87
     stringify(fillnwater_plant),
87 88
     stringify(fillnwater_tank_run),
88 89
     stringify(fillnwater_plant_run),
90
+    stringify(automation_mode),
89 91
     stringify(menu_pumps),
90 92
     stringify(menu_pumps_time),
91 93
     stringify(menu_pumps_go),
@@ -103,6 +105,10 @@ const char *Statemachine::getStateName(void) {
103 105
     return state_names[state];
104 106
 }
105 107
 
108
+bool Statemachine::isIdle(void) {
109
+    return state == init;
110
+}
111
+
106 112
 Statemachine::Statemachine(print_fn _print, backspace_fn _backspace)
107 113
         : db(7), selected_plants(plants.countPlants()) {
108 114
     state = init;
@@ -116,6 +122,7 @@ Statemachine::Statemachine(print_fn _print, backspace_fn _backspace)
116 122
     stop_time = 0;
117 123
     last_animation_time = 0;
118 124
     error_condition = "";
125
+    into_state_time = 0;
119 126
 }
120 127
 
121 128
 void Statemachine::begin(void) {
@@ -124,17 +131,24 @@ void Statemachine::begin(void) {
124 131
 
125 132
 void Statemachine::input(int n) {
126 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 136
         if (n == 1) {
130 137
             switch_to(auto_mode_a);
131 138
         } else if (n == 2) {
132
-            switch_to(menu_pumps);
139
+            switch_to(automation_mode);
133 140
         } else if (n == 3) {
141
+            switch_to(menu_pumps);
142
+        } else if (n == 4) {
134 143
             switch_to(menu_valves);
135
-        } else if ((n == -1) || (n == -2)) {
144
+        } else if (n == -1) {
136 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 152
     } else if ((state == auto_mode_a) || (state == auto_mode_b)) {
139 153
         if (n == 1) {
140 154
             switch_to(auto_fert);
@@ -161,7 +175,7 @@ void Statemachine::input(int n) {
161 175
             selected_plants.clear();
162 176
             switch_to(auto_plant);
163 177
         } else if (n == -1) {
164
-            switch_to(menu);
178
+            switch_to(menu_a);
165 179
         } else if (n == -2) {
166 180
             switch_to((state == auto_mode_a) ? auto_mode_b : auto_mode_a);
167 181
         }
@@ -251,7 +265,7 @@ void Statemachine::input(int n) {
251 265
                 backspace();
252 266
                 db.removeDigit();
253 267
             } else {
254
-                switch_to(menu);
268
+                switch_to(menu_b);
255 269
             }
256 270
         } else if (n == -2) {
257 271
             if (!db.hasDigits()) {
@@ -357,7 +371,6 @@ void Statemachine::input(int n) {
357 371
             start_time = millis();
358 372
             switch_to(fillnwater_plant_run);
359 373
         } else if (wl == Plants::empty) {
360
-            stop_time = millis();
361 374
             switch_to(auto_mode_a);
362 375
         } else if (wl == Plants::invalid) {
363 376
             error_condition = "Invalid sensor state";
@@ -421,14 +434,14 @@ void Statemachine::input(int n) {
421 434
         stop_time = millis();
422 435
         switch_to(menu_pumps_done);
423 436
     } else if (state == menu_pumps_done) {
424
-        switch_to(menu);
437
+        switch_to(menu_b);
425 438
     } else if (state == menu_valves) {
426 439
         if (n == -1) {
427 440
             if (db.hasDigits()) {
428 441
                 backspace();
429 442
                 db.removeDigit();
430 443
             } else {
431
-                switch_to(menu);
444
+                switch_to(menu_b);
432 445
             }
433 446
         } else if (n == -2) {
434 447
             if (!db.hasDigits()) {
@@ -508,12 +521,12 @@ void Statemachine::input(int n) {
508 521
             stop_time = millis();
509 522
             switch_to(menu_valves_done);
510 523
     } else if (state == menu_valves_done) {
511
-        switch_to(menu);
524
+        switch_to(menu_b);
512 525
     } else if (state == error) {
513 526
         if (old_state != error) {
514 527
             switch_to(old_state);
515 528
         } else {
516
-            switch_to(menu);
529
+            switch_to(menu_a);
517 530
         }
518 531
     }
519 532
 }
@@ -587,7 +600,29 @@ void Statemachine::act(void) {
587 600
             // stop if timeout has been reached
588 601
             plants.abort();
589 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 626
         } else if ((millis() - last_animation_time) >= 500) {
592 627
             // update animation if needed
593 628
             last_animation_time = millis();
@@ -656,11 +691,36 @@ void Statemachine::act(void) {
656 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 712
 void Statemachine::switch_to(States s) {
662 713
     old_state = state;
663 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 725
     if (s == init) {
666 726
         String a = String("- Giess-o-mat V") + FIRMWARE_VERSION + String(" -");
@@ -670,20 +730,33 @@ void Statemachine::switch_to(States s) {
670 730
               "* Delete prev. digit",
671 731
               "# Execute input num.",
672 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 751
               -1);
679 752
     } else if (s == auto_mode_a) {
680
-        print("----- Auto 1/2 -----",
753
+        print("---- Manual 1/2 ----",
681 754
               "1: Add Fertilizer",
682 755
               "2: Fill 'n' Water",
683 756
               "#: Go to page 2/2...",
684 757
               -1);
685 758
     } else if (s == auto_mode_b) {
686
-        print("----- Auto 2/2 -----",
759
+        print("---- Manual 2/2 ----",
687 760
               "3: Fill Reservoir",
688 761
               "4: Water a plant",
689 762
               "#: Go to page 1/2...",

+ 1
- 1
src/WifiStuff.cpp Переглянути файл

@@ -722,7 +722,7 @@ void wifi_run() {
722 722
     }
723 723
     
724 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 726
         ESP.restart();
727 727
     }
728 728
     

Завантаження…
Відмінити
Зберегти