Gbox 4.20
Grow box automation and monitoring - <a href='https://sites.google.com/site/growboxguy/'>https://sites.google.com/site/growboxguy/</a>
 
Loading...
Searching...
No Matches
hempy.cpp
Go to the documentation of this file.
1#include "hempy.h"
2#include <algorithm>
3
4namespace esphome
5{
6 namespace hempy
7 {
9 {
10 update_interval(DefaultUpdateInterval); // Sync the HempyBucket and WeightSensor's update interval
11 DryWeight->publish_state(StartWateringWeight->state); // At startup use StartWateringWeight, DryWeight will be calculated after each watering
12 WetWeight->publish_state(MaxWateringWeight->state); // Wet weight at startup is unknown and set to MaxWateringWeight, WetWeight will be calculated after each watering
13 ESP_LOGI("hempy", "%s ready", Name.c_str());
14 }
15
17 {
18 AverageWeight = update_average(WeightSensor->state);
20 StateSensor->publish_state(to_text_state(State)); // Publish the current state to Home Assistant
21 ESP_LOGI("hempy", "%s State: %s, Weight: %.2fkg (Average: %.1f, Increment: %.1f, Max: %.1f), Drain:%.1fkg (%.0fsec), Evaporation:%.1fkg (Dry: %.2fkg, Wet: %.2fkg)",
22 Name.c_str(), to_text_state(State), WeightSensor->state, AverageWeight, WateringIncrement->state, MaxWateringWeight->state, DrainTargetWeight->state, DrainWaitTime->state, EvaporationTargetWeight->state, DryWeight->state, WetWeight->state);
23 }
24
25 void HempyBucket::refresh() // Action when a manual update is requested
26 {
27 WeightSensor->update(); // Force sensor update
28 AverageReset = true;
29 update();
30 }
31
33 {
34 return AverageWeight;
35 }
36
37 /*
38 void HempyBucket::set_active_waterings_limit(uint32_t limit)
39 {
40 ActiveWateringsLimit = limit;
41 }
42 */
43
44 void HempyBucket::update_interval(uint32_t miliseconds) // Update the Polling frequency -> how often should update() run
45 {
46 this->set_update_interval(miliseconds); // Apply to HempyBucket
47 this->stop_poller(); // To apply the changes must restart the poller
48 this->start_poller();
49 WeightSensor->set_update_interval(miliseconds); // Apply to WeightSensor
50 WeightSensor->stop_poller();
51 WeightSensor->start_poller();
52 }
53
54 void HempyBucket::update_state(HempyStates NewState, bool Force)
55 {
56 if (UpdateInProgress && !Force)
57 {
58 return; // Exit if an update is already in progress
59 }
60 UpdateInProgress = true;
61 bool BlockOverWritingState = false; // Used when a state transitions to a new state
62 uint32_t CurrentTime = millis();
63 if (State != NewState)
64 {
65 StateTimer = CurrentTime; // Start measuring the time spent in the new State
66 ESP_LOGI("hempy", "%s State: %s -> %s", Name.c_str(), to_text_state(State), to_text_state(NewState));
67 }
68
69 switch (NewState)
70 {
72 if (WaterPump->state)
73 WaterPump->turn_off();
74 if (State != NewState) // When the state just changed
75 update_interval(DefaultUpdateInterval); // Restore the original update_interval from YAML
76 break;
78 if (WaterPump->state)
79 WaterPump->turn_off(); // Pump is considered failed. With automatic watering it should never switch to DRY mode. Triggers a notification.
80 if (State != NewState) // When the state just changed
81 update_interval(1000);
82 if (WeightSensor->state >= MaxWateringWeight->state || WeightSensor->state >= WetWeight->state) // Check if manual watering reached the Max Weight
83 {
85 BlockOverWritingState = true;
86 }
87 if (!ManualWateringDetected && DryWeight->state > 0 && WeightSensor->state > DryWeight->state && AverageWeight < WeightSensor->state)
88 {
89 ManualWateringDetected = true;
90 ManualWateringStarted = millis();
91 }
92 if (ManualWateringDetected && CurrentTime - ManualWateringStarted >= ManualWateringTime->state * 1000)
93 {
94 ManualWateringDetected = false;
95 if (!AverageReset && DryWeight->state > 0 && AverageWeight >= DryWeight->state)
96 {
98 BlockOverWritingState = true;
99 }
100 }
101 break;
103 if (WaterPump->state)
104 WaterPump->turn_off();
105 if (State != NewState) // When the state just changed
106 update_interval(DefaultUpdateInterval); // Restore the original update_interval from YAML
107 if (!AverageReset && ((StartWateringWeight->state > 0 && AverageWeight <= StartWateringWeight->state) || (EvaporationTargetWeight->state > 0 && DryWeight->state > 0 && AverageWeight <= DryWeight->state)))
108 {
110 BlockOverWritingState = true;
111 }
112 break;
114 if (State != NewState)
115 {
116 update_interval(1000); // 1sec - Speed up calling update()
117 StateWeight = WeightSensor->state; // Store the bucket weight before starting the pump
118 if (State != HempyStates::DRAINING) // First time entering the WATERING-DRAINING cycles
119 {
120 WateringTimer = 0;
121 }
122 PumpOnTimer = CurrentTime;
123 WaterPump->turn_on();
124 }
125 if (WeightSensor->state >= MaxWateringWeight->state || (WeightSensor->state >= StateWeight + WateringIncrement->state && WeightSensor->state >= WetWeight->state)) // Max weight reached OR (WateringIncrement's worth of water was added AND WetWeight is reached), wait for it to drain
126 {
127 WateringTimer += CurrentTime - PumpOnTimer;
129 BlockOverWritingState = true;
130 }
131 else if ((CurrentTime - PumpOnTimer) > MaxWateringTime->state * 1000) // Disable watering in case MaxWateringTime is reached: Consider the pump broken
132 {
133 ESP_LOGW("Hempy", "%s Timeout, pump failed", Name.c_str());
134 update_state(HempyStates::DRY, true); // Switch to DRY state - Triggers alert in Home Assistant
135 BlockOverWritingState = true;
136 }
137 break;
139 if (WaterPump->state) // If the pump is on -> turn it off
140 WaterPump->turn_off();
141 if (State != NewState)
142 {
143 StateWeight = WeightSensor->state;
144 ESP_LOGW("Hempy", "Stored drain start weight: %.2f", StateWeight);
145 }
146 if (CurrentTime - StateTimer >= (DrainWaitTime->state * 1000)) // Waiting for the water to drain
147 {
148 DrainProgress += StateWeight - WeightSensor->state; // Calculate how much water has drained
149 // ESP_LOGW("Hempy", "if %.2f <= %.2f - %.2f ", WeightSensor->state, StateWeight, DrainTargetWeight->state);
150 if (DrainProgress >= DrainTargetWeight->state || WeightSensor->state >= MaxWateringWeight->state) // Check if enough water was drained into the waste reservoir, OR MaxWateringWeight is reached
151 {
152 WetWeight->publish_state(WeightSensor->state); // Store the wet weight
153 float CalculatedDryWeight = WeightSensor->state - EvaporationTargetWeight->state; // Calculate next watering weight
154 if (CalculatedDryWeight >= StartWateringWeight->state)
155 DryWeight->publish_state(CalculatedDryWeight);
156 else
157 DryWeight->publish_state(StartWateringWeight->state);
158 DrainProgress = 0; // Reset the drain tracker
160 }
161 else
162 {
164 }
165 BlockOverWritingState = true;
166 }
167 break;
168 }
169
170 if (State != NewState && !BlockOverWritingState)
171 {
172 State = NewState;
173 }
174 UpdateInProgress = false;
175 }
176
178 {
179 switch (state)
180 {
182 return "DISABLED";
183 case HempyStates::DRY:
184 return "DRY";
186 return "IDLE";
188 return "WATERING";
190 return "DRAINING";
191 default:
192 return "?";
193 }
194 }
195
200
201 void HempyBucket::toggle_watering_logic(int8_t RequestedState)
202 {
203 if ((RequestedState == -1 && State == HempyStates::DISABLED) || RequestedState)
204 {
206 ESP_LOGW("Hempy", "%s Watering logic enabled", Name.c_str());
207 }
208 else
209 {
211 ESP_LOGW("Hempy", "%s Watering logic disabled", Name.c_str());
212 }
213 }
214
216 {
217 if (State != HempyStates::WATERING && State != HempyStates::DRAINING) // If watering is not in progress: start watering
219 }
220
226
232
234 {
235 if (State != HempyStates::WATERING && State != HempyStates::DRAINING) // If watering is not in progress: start watering
237 else
238 update_state(HempyStates::IDLE, true); // If watering is in progress: stop it (second click stops watering)
239 }
240
241 void HempyBucket::update_next_watering_weight(float NewWateringWeight) // Force update the next watering weight (Called when Start Water Weight is changed on the dashboard)
242 {
243 if (DryWeight->state != NewWateringWeight)
244 {
245 DryWeight->publish_state(NewWateringWeight);
246 ESP_LOGI("hempy", "%s Next watering weight: %.2f", Name.c_str(), NewWateringWeight);
247 }
248 }
249
250 void HempyBucket::update_evaporation_target(float EvaporationTarget) // Force update the next watering weight (Called when Start Water Weight is changed on the dashboard)
251 {
252 if (WetWeight->state > 0)
253 {
254 float NewDryWeight = WetWeight->state - EvaporationTarget; // Calculate new dry weight
255 if (StartWateringWeight->state < NewDryWeight)
256 DryWeight->publish_state(NewDryWeight); // use the new calculated watering weight
257 else
258 DryWeight->publish_state(StartWateringWeight->state); // use the Start Weight from the UI
259 ESP_LOGI("hempy", "%s Next watering weight: %.2f", Name.c_str(), DryWeight->state);
260 }
261 }
262
263 float HempyBucket::update_average(float NewValue)
264 {
265 if (isnan(NewValue) || isinf(NewValue))
266 {
267 ESP_LOGE("hempy", "Invalid sensor reading detected: %.2f, skipping it.", NewValue);
268 return AverageWeight; // Keep the last valid average and skip this invalid value
269 }
270 else
271 {
272 if (AverageReset)
273 {
274 for (int i = 0; i < AverageQueueSize; ++i)
275 {
276 AverageReadings[i] = NewValue;
277 }
278 AverageTotal = AverageQueueSize * NewValue;
279 AverageReset = false;
280 }
281 else
282 {
283 AverageTotal += NewValue - AverageReadings[AverageCurrent]; // Add the difference of the new and previous reading to AverageTotal
284 AverageReadings[AverageCurrent] = NewValue;
285 }
286 AverageCurrent = (AverageCurrent + 1) % AverageQueueSize;
287 // ESP_LOGD("hempy", "Debug: NewValue=%.2f, AverageWeight=%.2f, AverageTotal=%.2f, AverageQueueSize=%d, AverageCurrent=%d,AverageReset=%d, Readings=[%.2f, %.2f, %.2f, %.2f, %.2f]",
288 // NewValue, AverageWeight, AverageTotal, AverageQueueSize, AverageCurrent, AverageReset,
289 // AverageReadings[0], AverageReadings[1], AverageReadings[2], AverageReadings[3], AverageReadings[4]);
290 return AverageTotal / AverageQueueSize;
291 }
292 }
293 } // namespace hempy
294} // namespace esphome
char CurrentTime[MaxWordLength]
Buffer for storing current time in text format.
const char * to_text_state(HempyStates state)
Definition hempy.cpp:177
void update() override
Definition hempy.cpp:16
void update_state(HempyStates NewState, bool Force=false)
Definition hempy.cpp:54
void update_evaporation_target(float EvaporationTarget)
Definition hempy.cpp:250
void toggle_watering_logic(int8_t RequestedState=-1)
Definition hempy.cpp:201
void update_next_watering_weight(float weight)
Definition hempy.cpp:241
void update_interval(uint32_t miliseconds)
Definition hempy.cpp:44
void setup() override
Definition hempy.cpp:8
float update_average(float NewValue)
Definition hempy.cpp:263