Merged documents into ultrasonic sensor stuff

This commit is contained in:
Ollo 2020-11-11 21:42:29 +01:00
commit 04bfaf4d94
21 changed files with 6752 additions and 1363 deletions

View File

@ -1,16 +1,25 @@
# PlantCtrl
## Documentation of Power-Modes
https://lastminuteengineers.com/esp32-sleep-modes-power-consumption/#esp32-deep-sleep
The following problems shall be solved with this project:
* Solar Powered
* Low Powered
* Plant monitoring
* Plant watering
* IoT
gpio 17 only out no hold
gpio 16 only out no hold
# Hardware
Open hardware design (powered by KiCAD).
The complete PCB is stored in the ***board*** sub directory.
There the following components are connected:
* ESP32 NodeMCU Module (the one of A-Z Delivery was used)
* Lipo
* 7 moist sensors
* 7 pump
* DC-DC convert (generating voltage from Lipo for pumps)
* DS18B20 temperature sensors
* water tank ultrasonic sensor (via HC_SR04)
* general purpose expansion pin
solar charger 2A?
https://www.aliexpress.com/item/4000238259949.html?spm=a2g0o.productlist.0.0.7e50231cCWGu0Z&algo_pvid=9ab7b0d3-5026-438b-972b-1d4a81d4dc56&algo_expid=9ab7b0d3-5026-438b-972b-1d4a81d4dc56-11&btsid=0b0a0ac215999246489888249e72a9&ws_ab_test=searchweb0_0,searchweb201602_,searchweb201603_
MT3608 boost für pumpe
https://www.aliexpress.com/item/32925951391.html?spm=a2g0o.productlist.0.0.39e21087nAzH9q&algo_pvid=7db0a849-62f7-4403-88e3-615ee4d99339&algo_expid=7db0a849-62f7-4403-88e3-615ee4d99339-0&btsid=0b0a0ac215999252934777876e7253&ws_ab_test=searchweb0_0,searchweb201602_,searchweb201603_
DS18B20 one wire temp sensor
# Software
The firmware for the controller is stored in ***esp32*** sub directory.

File diff suppressed because it is too large Load Diff

View File

@ -4,10 +4,10 @@ EELAYER END
$Descr A4 11693 8268
encoding utf-8
Sheet 1 1
Title ""
Date ""
Rev ""
Comp ""
Title "Plant Controller"
Date "2020-11-11"
Rev "0.3"
Comp "C3MA"
Comment1 ""
Comment2 ""
Comment3 ""
@ -134,7 +134,7 @@ Text GLabel 1150 2050 0 50 Input ~ 0
PLANT6_PUMP
Text Notes 950 550 0 50 ~ 0
Pump Control
Text Notes 1100 5400 0 50 ~ 0
Text Notes 2600 7500 0 50 ~ 0
Lipo
Text Notes 4050 2650 1 50 ~ 0
Sensors
@ -542,44 +542,44 @@ Wire Wire Line
$Comp
L Connector_Generic:Conn_01x04 J3
U 1 1 5F837F50
P 1750 3150
F 0 "J3" H 1830 3142 50 0000 L CNN
F 1 "Conn_01x04" H 1830 3051 50 0000 L CNN
F 2 "misc_footprints:MT3608_module_SMT" H 1750 3150 50 0001 C CNN
F 3 "~" H 1750 3150 50 0001 C CNN
1 1750 3150
P 1600 2750
F 0 "J3" H 1680 2742 50 0000 L CNN
F 1 "Conn_01x04" H 1680 2651 50 0000 L CNN
F 2 "misc_footprints:MT3608_module_SMT" H 1600 2750 50 0001 C CNN
F 3 "~" H 1600 2750 50 0001 C CNN
1 1600 2750
1 0 0 -1
$EndComp
Text GLabel 1550 3050 0 50 Input ~ 0
Text GLabel 1400 2650 0 50 Input ~ 0
PWR_PUMP_CONVERTER
Text GLabel 1550 3150 0 50 Input ~ 0
Text GLabel 1400 2750 0 50 Input ~ 0
GND
Text GLabel 1550 3250 0 50 Input ~ 0
Text GLabel 1400 2850 0 50 Input ~ 0
GND
Text GLabel 1550 3350 0 50 Input ~ 0
Text GLabel 1400 2950 0 50 Input ~ 0
PUMP_PWR
$Comp
L LP38690DT-3.3:LP38690DT-3.3 U3
U 1 1 5F84FA14
P 1400 5300
F 0 "U3" H 1400 5665 50 0000 C CNN
F 1 "LP38690DT-3.3" H 1400 5574 50 0000 C CNN
F 2 "ESP32:DPAK457P991X255-3N" H 1400 5300 50 0001 L BNN
F 3 "IPC-7351B" H 1400 5300 50 0001 L BNN
F 4 "Texas Instruments" H 1400 5300 50 0001 L BNN "Field4"
F 5 "M" H 1400 5300 50 0001 L BNN "Field5"
F 6 "2.55mm" H 1400 5300 50 0001 L BNN "Field6"
1 1400 5300
P 2900 7400
F 0 "U3" H 2900 7765 50 0000 C CNN
F 1 "LP38690DT-3.3" H 2900 7674 50 0000 C CNN
F 2 "ESP32:DPAK457P991X255-3N" H 2900 7400 50 0001 L BNN
F 3 "IPC-7351B" H 2900 7400 50 0001 L BNN
F 4 "Texas Instruments" H 2900 7400 50 0001 L BNN "Field4"
F 5 "M" H 2900 7400 50 0001 L BNN "Field5"
F 6 "2.55mm" H 2900 7400 50 0001 L BNN "Field6"
1 2900 7400
1 0 0 -1
$EndComp
Text GLabel 2000 5400 2 50 Input ~ 0
Text GLabel 3500 7500 2 50 Input ~ 0
GND
Text GLabel 2000 5200 2 50 Input ~ 0
Text GLabel 3500 7300 2 50 Input ~ 0
3_3V
Text GLabel 4150 5700 0 50 Input ~ 0
3_3V
NoConn ~ 4200 7500
Text GLabel 800 5200 0 50 Input ~ 0
Text GLabel 2300 7300 0 50 Input ~ 0
LIPO+
Wire Wire Line
10550 6100 10700 6100
@ -1004,37 +1004,28 @@ CUSTOM_GPIO
$Comp
L Connector:Conn_01x02_Female J4
U 1 1 5F8D742C
P 1300 4100
F 0 "J4" H 1328 4076 50 0000 L CNN
F 1 "Conn_01x02_Female" H 1000 3900 50 0000 L CNN
F 2 "misc_footprints:BatteryHolder_Keystone_1042_1x18650" H 1300 4100 50 0001 C CNN
F 3 "~" H 1300 4100 50 0001 C CNN
1 1300 4100
P 3050 4300
F 0 "J4" H 3078 4276 50 0000 L CNN
F 1 "Conn_01x02_Female" H 2450 4100 50 0000 L CNN
F 2 "misc_footprints:BatteryHolder_Keystone_1042_1x18650" H 3050 4300 50 0001 C CNN
F 3 "~" H 3050 4300 50 0001 C CNN
1 3050 4300
1 0 0 -1
$EndComp
Text Notes 1200 4000 0 50 ~ 0
Fuse for Lipo
Text GLabel 900 4200 0 50 Input ~ 0
GND
Wire Wire Line
900 4200 950 4200
Text Notes 1300 3850 0 50 ~ 0
Protection for Lipo
$Comp
L power:GND #PWR0120
U 1 1 5F95011F
P 950 4450
F 0 "#PWR0120" H 950 4200 50 0001 C CNN
F 1 "GND" H 955 4277 50 0000 C CNN
F 2 "" H 950 4450 50 0001 C CNN
F 3 "" H 950 4450 50 0001 C CNN
1 950 4450
P 2000 5200
F 0 "#PWR0120" H 2000 4950 50 0001 C CNN
F 1 "GND" H 2005 5027 50 0000 C CNN
F 2 "" H 2000 5200 50 0001 C CNN
F 3 "" H 2000 5200 50 0001 C CNN
1 2000 5200
1 0 0 -1
$EndComp
Wire Wire Line
950 4450 950 4200
Connection ~ 950 4200
Wire Wire Line
950 4200 1100 4200
Text Notes 900 2950 0 50 ~ 0
Text Notes 750 2550 0 50 ~ 0
Pump Voltage Converter
Wire Wire Line
11050 6250 11050 6300
@ -1049,21 +1040,21 @@ F 3 "~" H 3050 2550 50 0001 C CNN
1 3050 2550
1 0 0 -1
$EndComp
Text GLabel 2350 2750 0 50 Input ~ 0
Text GLabel 2500 2750 0 50 Input ~ 0
PWR_SENSORS
Text GLabel 2350 2650 0 50 Input ~ 0
Text GLabel 2500 2650 0 50 Input ~ 0
TANK_TRIGGER
Text GLabel 2350 2550 0 50 Input ~ 0
Text GLabel 2500 2550 0 50 Input ~ 0
TANK_ECHO
$Comp
L power:GND #PWR0121
U 1 1 5F9F8100
P 2850 2450
F 0 "#PWR0121" H 2850 2200 50 0001 C CNN
F 1 "GND" V 2855 2322 50 0000 R CNN
F 2 "" H 2850 2450 50 0001 C CNN
F 3 "" H 2850 2450 50 0001 C CNN
1 2850 2450
P 2500 2450
F 0 "#PWR0121" H 2500 2200 50 0001 C CNN
F 1 "GND" V 2505 2322 50 0000 R CNN
F 2 "" H 2500 2450 50 0001 C CNN
F 3 "" H 2500 2450 50 0001 C CNN
1 2500 2450
0 1 1 0
$EndComp
Wire Wire Line
@ -1082,15 +1073,14 @@ $EndComp
$Comp
L power:PWR_FLAG #FLG0101
U 1 1 5FA3662A
P 950 4450
F 0 "#FLG0101" H 950 4525 50 0001 C CNN
F 1 "PWR_FLAG" V 950 4578 50 0000 L CNN
F 2 "" H 950 4450 50 0001 C CNN
F 3 "~" H 950 4450 50 0001 C CNN
1 950 4450
0 1 1 0
P 2000 5200
F 0 "#FLG0101" H 2000 5275 50 0001 C CNN
F 1 "PWR_FLAG" H 1900 5350 50 0000 L CNN
F 2 "" H 2000 5200 50 0001 C CNN
F 3 "~" H 2000 5200 50 0001 C CNN
1 2000 5200
1 0 0 -1
$EndComp
Connection ~ 950 4450
Text GLabel 5900 6800 2 50 Input ~ 0
SENSORS_ENABLE
$Comp
@ -2391,44 +2381,31 @@ $EndComp
Connection ~ 10700 5400
Wire Wire Line
10700 5400 10700 5550
Wire Wire Line
1000 3500 1550 3500
Text GLabel 1550 3800 0 50 Input ~ 0
Text GLabel 1650 3600 0 50 Input ~ 0
SOLAR_IN
Text GLabel 1550 3600 0 50 Input ~ 0
GND
Text GLabel 1550 3700 0 50 Input ~ 0
GND
$Comp
L Connector_Generic:Conn_01x04 J2
U 1 1 5F7E5709
P 1750 3600
F 0 "J2" H 1830 3592 50 0000 L CNN
F 1 "Conn_01x04" H 1830 3501 50 0000 L CNN
F 2 "Connector_PinSocket_2.54mm:PinSocket_1x04_P2.54mm_Horizontal" H 1750 3600 50 0001 C CNN
F 3 "~" H 1750 3600 50 0001 C CNN
1 1750 3600
P 1850 3400
F 0 "J2" H 1930 3392 50 0000 L CNN
F 1 "Conn_01x04" H 1930 3301 50 0000 L CNN
F 2 "Connector_PinSocket_2.54mm:PinSocket_1x04_P2.54mm_Horizontal" H 1850 3400 50 0001 C CNN
F 3 "~" H 1850 3400 50 0001 C CNN
1 1850 3400
1 0 0 -1
$EndComp
Wire Wire Line
1000 4100 1100 4100
Connection ~ 1000 3800
Wire Wire Line
1000 3800 1000 3500
$Comp
L Device:Fuse F1
U 1 1 5F8D8528
P 1000 3950
F 0 "F1" H 1060 3996 50 0000 L CNN
F 1 "Fuse" H 1060 3905 50 0000 L CNN
F 2 "Fuse:Fuse_Blade_ATO_directSolder" V 930 3950 50 0001 C CNN
F 3 "~" H 1000 3950 50 0001 C CNN
1 1000 3950
1 0 0 -1
P 1150 3300
F 0 "F1" V 1200 3250 50 0000 L CNN
F 1 "Fuse" V 1050 3200 50 0000 L CNN
F 2 "Fuse:Fuse_Blade_ATO_directSolder" V 1080 3300 50 0001 C CNN
F 3 "~" H 1150 3300 50 0001 C CNN
1 1150 3300
0 1 1 0
$EndComp
Wire Wire Line
1000 3800 850 3800
Text GLabel 850 3800 0 50 Input ~ 0
Text GLabel 1250 3450 0 50 Input ~ 0
LIPO+
$Comp
L power:GND #PWR0138
@ -2696,29 +2673,29 @@ Text GLabel 8200 4850 1 50 Input ~ 0
Wire Wire Line
8200 4850 8200 4950
Wire Wire Line
2350 2550 2400 2550
2500 2550 2550 2550
Wire Wire Line
2350 2650 2600 2650
2500 2650 2750 2650
$Comp
L Device:D_Schottky D8
U 1 1 5FA219F6
P 2400 2300
F 0 "D8" V 2446 2221 50 0000 R CNN
F 1 "BAS40" V 2355 2221 50 0000 R CNN
F 2 "Diode_SMD:D_1206_3216Metric_Pad1.42x1.75mm_HandSolder" H 2400 2300 50 0001 C CNN
F 3 "~" H 2400 2300 50 0001 C CNN
1 2400 2300
P 2550 2250
F 0 "D8" V 2596 2171 50 0000 R CNN
F 1 "BAS40" V 2505 2171 50 0000 R CNN
F 2 "Diode_SMD:D_1206_3216Metric_Pad1.42x1.75mm_HandSolder" H 2550 2250 50 0001 C CNN
F 3 "~" H 2550 2250 50 0001 C CNN
1 2550 2250
0 1 1 0
$EndComp
$Comp
L Device:D_Schottky D10
U 1 1 5FA22159
P 2600 2300
F 0 "D10" V 2646 2221 50 0000 R CNN
F 1 "BAS40" V 2555 2221 50 0000 R CNN
F 2 "Diode_SMD:D_1206_3216Metric_Pad1.42x1.75mm_HandSolder" H 2600 2300 50 0001 C CNN
F 3 "~" H 2600 2300 50 0001 C CNN
1 2600 2300
P 2750 2250
F 0 "D10" V 2796 2171 50 0000 R CNN
F 1 "BAS40" V 2705 2171 50 0000 R CNN
F 2 "Diode_SMD:D_1206_3216Metric_Pad1.42x1.75mm_HandSolder" H 2750 2250 50 0001 C CNN
F 3 "~" H 2750 2250 50 0001 C CNN
1 2750 2250
0 1 1 0
$EndComp
Wire Wire Line
@ -2728,77 +2705,64 @@ Wire Wire Line
Connection ~ 6200 5800
Wire Wire Line
6200 5800 6250 5800
Wire Wire Line
2850 2750 2350 2750
$Comp
L Device:D_Schottky D9
U 1 1 5FA542CE
P 2400 2950
F 0 "D9" V 2446 2871 50 0000 R CNN
F 1 "BAS40" V 2355 2871 50 0000 R CNN
F 2 "Diode_SMD:D_1206_3216Metric_Pad1.42x1.75mm_HandSolder" H 2400 2950 50 0001 C CNN
F 3 "~" H 2400 2950 50 0001 C CNN
1 2400 2950
P 2550 2950
F 0 "D9" V 2596 2871 50 0000 R CNN
F 1 "BAS40" V 2505 2871 50 0000 R CNN
F 2 "Diode_SMD:D_1206_3216Metric_Pad1.42x1.75mm_HandSolder" H 2550 2950 50 0001 C CNN
F 3 "~" H 2550 2950 50 0001 C CNN
1 2550 2950
0 1 1 0
$EndComp
$Comp
L Device:D_Schottky D11
U 1 1 5FA54A31
P 2600 2950
F 0 "D11" V 2646 2871 50 0000 R CNN
F 1 "BAS40" V 2555 2871 50 0000 R CNN
F 2 "Diode_SMD:D_1206_3216Metric_Pad1.42x1.75mm_HandSolder" H 2600 2950 50 0001 C CNN
F 3 "~" H 2600 2950 50 0001 C CNN
1 2600 2950
P 2750 2950
F 0 "D11" V 2796 2871 50 0000 R CNN
F 1 "BAS40" V 2705 2871 50 0000 R CNN
F 2 "Diode_SMD:D_1206_3216Metric_Pad1.42x1.75mm_HandSolder" H 2750 2950 50 0001 C CNN
F 3 "~" H 2750 2950 50 0001 C CNN
1 2750 2950
0 1 1 0
$EndComp
Wire Wire Line
2400 2550 2400 2800
Connection ~ 2400 2550
2550 2550 2550 2800
Connection ~ 2550 2550
Wire Wire Line
2400 2550 2850 2550
2550 2550 2850 2550
Connection ~ 2750 2650
Wire Wire Line
2400 2450 2400 2550
2750 2650 2850 2650
Wire Wire Line
2600 2450 2600 2650
Connection ~ 2600 2650
2750 2650 2750 2800
Wire Wire Line
2600 2650 2850 2650
2550 3100 2550 3150
Wire Wire Line
2600 2650 2600 2800
2550 3150 2650 3150
Wire Wire Line
2400 3100 2400 3150
2750 3150 2750 3100
Wire Wire Line
2400 3150 2500 3150
2550 2100 2550 2050
Wire Wire Line
2600 3150 2600 3100
Wire Wire Line
2400 2150 2400 2100
Wire Wire Line
2400 2100 2500 2100
Wire Wire Line
2600 2100 2600 2150
2750 2050 2750 2100
$Comp
L power:GND #PWR0140
U 1 1 5FAD1A1F
P 2500 3150
F 0 "#PWR0140" H 2500 2900 50 0001 C CNN
F 1 "GND" V 2505 3022 50 0000 R CNN
F 2 "" H 2500 3150 50 0001 C CNN
F 3 "" H 2500 3150 50 0001 C CNN
1 2500 3150
P 2650 3150
F 0 "#PWR0140" H 2650 2900 50 0001 C CNN
F 1 "GND" V 2655 3022 50 0000 R CNN
F 2 "" H 2650 3150 50 0001 C CNN
F 3 "" H 2650 3150 50 0001 C CNN
1 2650 3150
1 0 0 -1
$EndComp
Connection ~ 2500 3150
Connection ~ 2650 3150
Wire Wire Line
2500 3150 2600 3150
Text GLabel 2500 2100 1 50 Input ~ 0
2650 3150 2750 3150
Text GLabel 2650 2000 1 50 Input ~ 0
3_3V
Wire Wire Line
2500 2050 2500 2100
Connection ~ 2500 2100
Wire Wire Line
2500 2100 2600 2100
Text Notes 500 6650 0 105 ~ 0
External Fix Power
$Comp
@ -2861,8 +2825,6 @@ Wire Wire Line
10150 800 10200 800
Wire Wire Line
10200 800 10200 950
Wire Wire Line
10200 800 10650 800
Wire Wire Line
10650 800 10650 950
Connection ~ 10200 800
@ -2886,4 +2848,171 @@ Text GLabel 10150 800 0 50 Input ~ 0
PWR_SENSORS
Text GLabel 10500 2100 2 50 Input ~ 0
PWR_SENSORS
$Comp
L DW01:DW01 IC1
U 1 1 5FB36987
P 1100 4200
F 0 "IC1" H 1600 4467 50 0000 C CNN
F 1 "DW01" H 1600 4376 50 0000 C CNN
F 2 "SOT95P280X135-6N" H 1100 4200 50 0001 L BNN
F 3 "" H 1100 4200 50 0001 L BNN
F 4 "1.35mm" H 1100 4200 50 0001 L BNN "HEIGHT"
F 5 "ic" H 1100 4200 50 0001 L BNN "DESCRIPTION"
F 6 "" H 1100 4200 50 0001 L BNN "LCSC_PRICE-STOCK"
F 7 "DW01" H 1100 4200 50 0001 L BNN "MANUFACTURER_PART_NUMBER"
F 8 "" H 1100 4200 50 0001 L BNN "LCSC_PART_NUMBER"
F 9 "Slkor" H 1100 4200 50 0001 L BNN "MANUFACTURER_NAME"
1 1100 4200
1 0 0 -1
$EndComp
$Comp
L ESP32-DEVKITC-32D:SL2300 Q12
U 1 1 5FB3ECD2
P 1750 5250
F 0 "Q12" V 2042 5250 50 0000 C CNN
F 1 "SL2300" V 1951 5250 50 0000 C CNN
F 2 "" H 1750 5250 50 0001 C CNN
F 3 "" H 1750 5250 50 0001 C CNN
1 1750 5250
0 -1 -1 0
$EndComp
Wire Wire Line
1550 5200 1400 5200
Wire Wire Line
1650 3500 1650 3400
$Comp
L Device:R R52
U 1 1 5FC59C43
P 950 4300
F 0 "R52" V 950 4300 50 0000 C CNN
F 1 "1K" V 800 4300 50 0000 C CNN
F 2 "" V 880 4300 50 0001 C CNN
F 3 "~" H 950 4300 50 0001 C CNN
1 950 4300
0 -1 -1 0
$EndComp
$Comp
L Device:R R53
U 1 1 5FC908BA
P 2550 4300
F 0 "R53" V 2343 4300 50 0000 C CNN
F 1 "100Ohm" V 2434 4300 50 0000 C CNN
F 2 "" V 2480 4300 50 0001 C CNN
F 3 "~" H 2550 4300 50 0001 C CNN
1 2550 4300
0 1 1 0
$EndComp
Wire Wire Line
1950 5200 2000 5200
$Comp
L Device:C C10
U 1 1 5FCD9EFE
P 2250 4150
F 0 "C10" H 2365 4196 50 0000 L CNN
F 1 "C" H 2365 4105 50 0000 L CNN
F 2 "" H 2288 4000 50 0001 C CNN
F 3 "~" H 2250 4150 50 0001 C CNN
1 2250 4150
1 0 0 -1
$EndComp
Text GLabel 1650 3500 0 50 Input ~ 0
GND
Text GLabel 2100 5200 2 50 Input ~ 0
GND
Text GLabel 2700 4400 0 50 Input ~ 0
GND_BATT
Text GLabel 1000 5200 0 50 Input ~ 0
GND_BATT
Connection ~ 2000 5200
Wire Wire Line
2000 5200 2100 5200
Wire Wire Line
2100 4300 2250 4300
Text GLabel 2300 4000 2 50 Input ~ 0
GND_BATT
Text GLabel 800 4300 0 50 Input ~ 0
GND
Connection ~ 2250 4300
Wire Wire Line
2250 4300 2400 4300
Wire Wire Line
2100 4200 2100 4000
Wire Wire Line
2100 4000 2250 4000
Wire Wire Line
2250 4000 2300 4000
Connection ~ 2250 4000
Wire Wire Line
2700 4300 2850 4300
Wire Wire Line
2700 4400 2850 4400
Text GLabel 2800 4300 1 50 Input ~ 0
VCC_BATT
Text GLabel 1000 3300 0 50 Input ~ 0
VCC_BATT
Wire Wire Line
1300 3300 1350 3300
Wire Wire Line
1350 3450 1250 3450
Wire Wire Line
1350 3300 1350 3450
Connection ~ 1350 3300
Wire Wire Line
1350 3300 1650 3300
Wire Wire Line
2850 2750 2500 2750
Wire Wire Line
2550 2400 2550 2550
Wire Wire Line
2750 2400 2750 2650
Wire Wire Line
2850 2450 2500 2450
Wire Wire Line
2550 2050 2650 2050
Wire Wire Line
2650 2000 2650 2050
Connection ~ 2650 2050
Wire Wire Line
2650 2050 2750 2050
Text GLabel 850 4200 0 50 Input ~ 0
LIPO_OD
Text GLabel 1200 4850 1 50 Input ~ 0
LIPO_OD
Text GLabel 850 4400 0 50 Input ~ 0
LIPO_OC
Wire Wire Line
850 4200 1100 4200
Wire Wire Line
850 4400 1100 4400
Text GLabel 1750 5600 3 50 Input ~ 0
LIPO_OC
Wire Wire Line
1750 5600 1750 5500
$Comp
L ESP32-DEVKITC-32D:SL2300 Q11
U 1 1 5FB813FB
P 1200 5150
F 0 "Q11" V 1399 5150 50 0000 C CNN
F 1 "SL2300" V 1490 5150 50 0000 C CNN
F 2 "" H 1200 5150 50 0001 C CNN
F 3 "" H 1200 5150 50 0001 C CNN
1 1200 5150
0 1 1 0
$EndComp
Wire Wire Line
1200 4850 1200 4900
NoConn ~ 2100 4400
$Comp
L power:PWR_FLAG #FLG0102
U 1 1 6013ADBD
P 10700 5400
F 0 "#FLG0102" H 10700 5475 50 0001 C CNN
F 1 "PWR_FLAG" H 10600 5550 50 0000 L CNN
F 2 "" H 10700 5400 50 0001 C CNN
F 3 "~" H 10700 5400 50 0001 C CNN
1 10700 5400
0 -1 -1 0
$EndComp
Wire Wire Line
10200 800 10650 800
$EndSCHEMATC

View File

@ -2,4 +2,5 @@
(lib (name LP38690DT-3.3)(type Legacy)(uri ${KIPRJMOD}/kicad-stuff/LP38690DT-3.3.lib)(options "")(descr ""))
(lib (name ESP32-DEVKITC-32D)(type Legacy)(uri ${KIPRJMOD}/kicad-stuff/ESP32/ESP32-DEVKITC-32D.lib)(options "")(descr ""))
(lib (name PlantCtrlESP32-rescue)(type Legacy)(uri ${KIPRJMOD}/PlantCtrlESP32-rescue.lib)(options "")(descr ""))
(lib (name DW01)(type Legacy)(uri ${KIPRJMOD}/kicad-stuff/DW01.lib)(options "")(descr ""))
)

1
esp32/.gitignore vendored
View File

@ -3,3 +3,4 @@
.vscode/c_cpp_properties.json
.vscode/launch.json
.vscode/ipch
doc/

2522
esp32/Doxyfile Normal file

File diff suppressed because it is too large Load Diff

View File

@ -20,6 +20,23 @@ Uses ESP32MiniKit
* Temperature
* Custom GPIO
## Documentation of Power-Modes
https://lastminuteengineers.com/esp32-sleep-modes-power-consumption/#esp32-deep-sleep
gpio 17 only out no hold
gpio 16 only out no hold
## Additional hardware
solar charger 2A?
https://www.aliexpress.com/item/4000238259949.html?spm=a2g0o.productlist.0.0.7e50231cCWGu0Z&algo_pvid=9ab7b0d3-5026-438b-972b-1d4a81d4dc56&algo_expid=9ab7b0d3-5026-438b-972b-1d4a81d4dc56-11&btsid=0b0a0ac215999246489888249e72a9&ws_ab_test=searchweb0_0,searchweb201602_,searchweb201603_
MT3608 boost für pumpe
https://www.aliexpress.com/item/32925951391.html?spm=a2g0o.productlist.0.0.39e21087nAzH9q&algo_pvid=7db0a849-62f7-4403-88e3-615ee4d99339&algo_expid=7db0a849-62f7-4403-88e3-615ee4d99339-0&btsid=0b0a0ac215999252934777876e7253&ws_ab_test=searchweb0_0,searchweb201602_,searchweb201603_
DS18B20 one wire temp sensor
# Features
## Empires Wunschliste
* Pflanze

View File

@ -12,7 +12,6 @@ For further details have a look at the Readme.md one level above.
# Remote Upload
This script will allow you to send an OTA update to your device.
## Installation
@ -22,6 +21,12 @@ Requirements are:
## Usage
See ***upload-via-mqtt.sh***
# Remote Upload - Backend
## Usage
```text
usage: ota_updater.py [-h] -l BROKER_HOST -p BROKER_PORT [-u BROKER_USERNAME]
[-d BROKER_PASSWORD] [-t BASE_TOPIC] -i DEVICE_ID

27
esp32/host/upload-via-mqtt.sh Executable file
View File

@ -0,0 +1,27 @@
#!//bin/bash
if [ $# -ne 3 ]; then
echo "Homie prefex and device index must be specified:"
echo "$0 <mqtt host> <prefix> <device index>"
echo "e.g."
echo "$0 192.168.0.2 test/ MyDeviceId"
exit 1
fi
mqttHost=$1
mqttPrefix=$2
homieId=$3
firmwareFile=../.pio/build/esp32doit-devkit-v1/firmware.bin
if [ ! -f $firmwareFile ]; then
echo "the script $0 must be started in host/ sub directory"
exit 2
fi
mosquitto_pub -h $mqttHost -t "${mqttPrefix}${homieId}/stay/alive/set" -m "1" -r
echo "Waiting ..."
mosquitto_sub -h $mqttHost -t "${mqttPrefix}${homieId}/#" -R -C 1
python ota_updater.py -l $mqttHost -t "$mqttPrefix" -i "$homieId" $firmwareFile
mosquitto_pub -h $mqttHost -t "${mqttPrefix}${homieId}/stay/alive/set" -m "0" -r
exit 0

View File

@ -6,18 +6,55 @@
* @date 2020-05-30
*
* @copyright Copyright (c) 2020
*
* \mainpage Configuration of the controller
* @{
* Describe the used PINs of the controller
*
* @subpage Controller
*
* @subpage Homie
*
* @subpage Configuration
*
* There are several modes in the controller
* \dot
* digraph Operationmode {
* ranksep=.75;
* poweroff [ label="off" ];
* mode1 [ label="Mode 1 - Sensor only", shape=box, width=2 ];
* mode2 [ label="Mode 2 - Wifi enabled", shape=box ];
* mode3 [ label="Mode 3 - Stay alive", shape=box ];
* mode1 -> mode2 [ label="wakeup reason", fontsize=10 ];
* mode1 -> mode2 [ label="Time duration", fontsize=10 ];
* mode2 -> mode3 [ label="Over the Air Update", fontsize=10 ];
* mode3 -> mode2 [ label="Over the Air Finished", fontsize=10 ];
* mode3 -> mode2 [ label="Mqtt Command", fontsize=10 ];
* mode2 -> mode3 [ label="Mqtt Command", fontsize=10 ];
* poweroff -> mode1 [ label="deep sleep wakeup", fontsize=10 ];
* mode1 -> poweroff [ label="enter deep sleep", fontsize=10 ];
* mode2 -> poweroff [ label="Mqtt queue empty", fontsize=10 ];
* }
* \enddot
*
* Before entering Deep sleep the controller is configured with an wakeup time.
*
* @}
*/
#ifndef CONTROLLER_CONFIG_H
#define CONTROLLER_CONFIG_H
#define FIRMWARE_VERSION "1.0.6"
/** \addtogroup Configuration
* @{
*/
#define FIRMWARE_VERSION "1.0.9"
#define ADC_TO_VOLT(adc) ((adc) * 3.3 ) / 4095)
#define ADC_TO_VOLT_WITH_MULTI(adc, multi) (((adc) * 3.3 * (multi)) / 4095)
#define ADC_TO_VOLT_WITH_MULTI(adc, multi) (((adc)*3.3 * (multi)) / 4095)
#define MOIST_SENSOR_MAX_ADC (85 * 4095 / 100)
#define MOIST_SENSOR_MIN_ADC (25 * 4095 / 100)
#define SOLAR_VOLT(adc) ADC_TO_VOLT_WITH_MULTI(adc, 4.0306) /**< 100k and 33k voltage dividor */
#define ADC_5V_TO_3V3(adc) ADC_TO_VOLT_WITH_MULTI(adc, 1.7) /**< 33k and 47k8 voltage dividor */
#define ADC_5V_TO_3V3(adc) ADC_TO_VOLT_WITH_MULTI(adc, 1.69) /**< 33k and 47k8 voltage dividor */
#define MS_TO_S 1000
#define SENSOR_LIPO 34 /**< GPIO 34 (ADC1) */
@ -41,16 +78,16 @@
#define OUTPUT_SENSOR 16 /**< GPIO 16 - Enable Sensors */
#define OUTPUT_PUMP 13 /**< GPIO 13 - Enable Pumps */
#define SENSOR_DS18B20 2 /**< GPIO 2 */
#define BUTTON 0 /**< GPIO 0 */
#define SENSOR_DS18B20 2 /**< GPIO 2 - Temperatur sensor */
#define BUTTON 0 /**< GPIO 0 - Fix button of NodeMCU */
#define MIN_TIME_RUNNING 5UL /**< Amount of seconds the controller must stay awoken */
#define MAX_PLANTS 7
#define MINIMUM_LIPO_VOLT 3.6f /**< Minimum voltage of the Lipo, that must be present */
#define NO_LIPO_VOLT 2.0f /**< No Lipo connected */
#define MINIMUM_SOLAR_VOLT 4.0f /**< Minimum voltage of the sun, to detect daylight */
#define SOLAR_CHARGE_MIN_VOLTAGE 7
#define SOLAR_CHARGE_MAX_VOLTAGE 9
#define SOLAR_CHARGE_MIN_VOLTAGE 7 /**< Sun is rising (morning detected) */
#define SOLAR_CHARGE_MAX_VOLTAGE 9 /**< Sun is shining (noon) */
#define HC_SR04 /**< Ultrasonic distance sensor to measure water level */
#define SENSOR_SR04_ECHO 17 /**< GPIO 17 - Echo */
@ -58,7 +95,13 @@
#define MAX_CONFIG_SETTING_ITEMS 50 /**< Parameter, that can be configured in Homie */
#define PANIK_MODE_DEEPSLEEP (60*60*5U) /**< 5 hours in usecond */
#define PANIK_MODE_DEEPSLEEP_US (PANIK_MODE_DEEPSLEEP*1000*1000)
#define PANIK_MODE_DEEPSLEEP (60 * 60 * 5U) /**< 5 hours in usecond */
#define PANIK_MODE_DEEPSLEEP_US (PANIK_MODE_DEEPSLEEP * 1000 * 1000)
#define TEMPERATURE_DELTA_TRIGGER_IN_C 1.0f
#define MOIST_DELTA_TRIGGER_ADC 10
#define SOLAR_DELTA_VOLT_ADC 3
#define LIPO_DELTA_VOLT_ADC 0.2 /**< trigger for lipo voltage */
/* @} */
#endif

View File

@ -21,16 +21,20 @@
#include <OneWire.h>
class Ds18B20 {
private:
OneWire* mDs;
class Ds18B20
{
private:
OneWire *mDs;
int foundDevices;
public:
Ds18B20(int pin) {
public:
Ds18B20(int pin)
{
this->mDs = new OneWire(pin);
}
~Ds18B20() {
~Ds18B20()
{
delete this->mDs;
}
/**
@ -47,6 +51,6 @@ class Ds18B20 {
* @param maxTemperatures size of the given array
* @return int amount of read temperature values
*/
int readAllTemperatures(float* pTemperatures, int maxTemperatures);
int readAllTemperatures(float *pTemperatures, int maxTemperatures);
};
#endif

View File

@ -1,4 +1,6 @@
/**
/** \addtogroup Homie
* @{
*
* @file HomieConfiguration.h
* @author your name (you@domain.com)
* @brief
@ -7,6 +9,7 @@
*
* @copyright Copyright (c) 2020
* All Settings, configurable in Homie
*
*/
#ifndef HOMIE_PLANT_CONFIG_H
#define HOMIE_PLANT_CONFIG_H
@ -16,37 +19,52 @@
#define MAX_PLANTS 7
/**
*********************************** Attributes *******************************
*/
* @name Attributes
* generated Information
* @{
**/
HomieNode plant0("plant0", "Plant 0", "Plant");
HomieNode plant1("plant1", "Plant 1", "Plant");
HomieNode plant2("plant2", "Plant 2", "Plant");
HomieNode plant3("plant3", "Plant 3", "Plant");
HomieNode plant4("plant4", "Plant 4", "Plant");
HomieNode plant5("plant5", "Plant 5", "Plant");
HomieNode plant6("plant6", "Plant 6", "Plant");
HomieNode plant0("plant0", "Plant 0", "Plant"); /**< dynamic Homie information for first plant */
HomieNode plant1("plant1", "Plant 1", "Plant"); /**< dynamic Homie information for second plant */
HomieNode plant2("plant2", "Plant 2", "Plant"); /**< dynamic Homie information for first plant */
HomieNode plant3("plant3", "Plant 3", "Plant"); /**< dynamic Homie information for first plant */
HomieNode plant4("plant4", "Plant 4", "Plant"); /**< dynamic Homie information for first plant */
HomieNode plant5("plant5", "Plant 5", "Plant"); /**< dynamic Homie information for first plant */
HomieNode plant6("plant6", "Plant 6", "Plant"); /**< dynamic Homie information for first plant */
HomieNode sensorLipo("lipo", "Battery Status", "Lipo");
HomieNode sensorSolar("solar", "Solar Status", "Solarpanel");
HomieNode sensorWater("water", "WaterSensor", "Water");
HomieNode sensorTemp("temperature", "Temperature", "temperature");
HomieNode stayAlive("stay", "alive", "alive");
HomieNode stayAlive("stay", "alive", "alive"); /**< Necessary for Mqtt Active Command */
/* @} */
/**
*********************************** Settings *******************************
* @name Settings
* General settings for the controller
* @{
*/
HomieSetting<long> deepSleepTime("deepsleep", "time in milliseconds to sleep (0 deactivats it)");
HomieSetting<long> deepSleepNightTime("nightsleep", "time in milliseconds to sleep (0 uses same setting: deepsleep at night, too)");
HomieSetting<long> maxTimeBetweenMQTTUpdates("mqttSleep", "time in seconds to start into mode2");
HomieSetting<long> deepSleepTime("deepsleep", "time in seconds to sleep (0 deactivats it)");
HomieSetting<long> deepSleepNightTime("nightsleep", "time in seconds to sleep (0 uses same setting: deepsleep at night, too)");
HomieSetting<long> wateringDeepSleep("pumpdeepsleep", "time seconds to sleep, while a pump is running");
HomieSetting<long> waterLevelMax("watermaxlevel", "distance (mm) at maximum water level");
HomieSetting<long> waterLevelMin("waterminlevel", "distance (mm) at minimum water level (pumps still covered)");
HomieSetting<long> waterLevelWarn("waterlevelwarn", "warn (mm) if below this water level %");
HomieSetting<long> waterLevelVol("waterVolume", "(ml) between minimum and maximum");
HomieSetting<const char *> ntpServer("ntpServer", "NTP server (pool.ntp.org as default)");
/** Plant specific ones */
/**
*@}
*/
/**
* @name Plant specific ones
* Setting for one plant
* @{
**/
#define GENERATE_PLANT(plant, strplant) \
HomieSetting<long> mSensorDry##plant = HomieSetting<long>("moistdry" strplant, "Plant " strplant "- Moist sensor dry threshold"); \
@ -54,16 +72,22 @@ HomieSetting<long> waterLevelVol("waterVolume", "(ml) between minimum and maximu
HomieSetting<long> mPumpAllowedHourRangeEnd##plant = HomieSetting<long>("rangehourend" strplant, "Plant" strplant " - Range pump allowed hour end (0-23)"); \
HomieSetting<bool> mPumpOnlyWhenLowLight##plant = HomieSetting<bool>("onlyWhenLowLightZ" strplant, "Plant" strplant " - Enable the Pump only, when there is light but not enought to charge battery"); \
HomieSetting<long> mPumpCooldownInHours##plant = HomieSetting<long>("cooldownpump" strplant, "Plant" strplant " - How long to wait until the pump is activated again (minutes)"); \
PlantSettings_t mSetting##plant = { &mSensorDry##plant, &mPumpAllowedHourRangeStart##plant, &mPumpAllowedHourRangeEnd##plant, &mPumpOnlyWhenLowLight##plant, &mPumpCooldownInHours##plant };
PlantSettings_t mSetting##plant = {&mSensorDry##plant, &mPumpAllowedHourRangeStart##plant, &mPumpAllowedHourRangeEnd##plant, &mPumpOnlyWhenLowLight##plant, &mPumpCooldownInHours##plant}; \
/**< Generate all settings for one plant
*
* Feature to start pumping only at morning: @link{SOLAR_CHARGE_MIN_VOLTAGE} and @link{SOLAR_CHARGE_MAX_VOLTAGE}
*/
GENERATE_PLANT(0, "0");
GENERATE_PLANT(1, "1");
GENERATE_PLANT(2, "2");
GENERATE_PLANT(3, "3");
GENERATE_PLANT(4, "4");
GENERATE_PLANT(5, "5");
GENERATE_PLANT(6, "6");
/**
* @}
*/
GENERATE_PLANT(0, "0"); /**< Homie settings for first plant */
GENERATE_PLANT(1, "1"); /**< Homie settings for second Plant */
GENERATE_PLANT(2, "2"); /**< Homie settings for third plant */
GENERATE_PLANT(3, "3"); /**< Homie settings for fourth plant */
GENERATE_PLANT(4, "4"); /**< Homie settings for fifth plant */
GENERATE_PLANT(5, "5"); /**< Homie settings for sixth plant */
GENERATE_PLANT(6, "6"); /**< Homie settings for seventh plant */
#endif /* HOMIE_PLANT_CONFIG_H */
#endif /* HOMIE_PLANT_CONFIG_H @} */

View File

@ -14,13 +14,15 @@
#include <Homie.h>
#define DEACTIVATED_PLANT 5000
#define MISSING_SENSOR 5001
typedef struct PlantSettings_t {
HomieSetting<long>* pSensorDry;
HomieSetting<long>* pPumpAllowedHourRangeStart;
HomieSetting<long>* pPumpAllowedHourRangeEnd;
HomieSetting<bool>* pPumpOnlyWhenLowLight;
HomieSetting<long>* pPumpCooldownInHours;
typedef struct PlantSettings_t
{
HomieSetting<long> *pSensorDry;
HomieSetting<long> *pPumpAllowedHourRangeStart;
HomieSetting<long> *pPumpAllowedHourRangeEnd;
HomieSetting<bool> *pPumpOnlyWhenLowLight;
HomieSetting<long> *pPumpCooldownInHours;
} PlantSettings_t;
#endif

View File

@ -15,17 +15,18 @@
#include "HomieTypes.h"
#include "RunningMedian.h"
class Plant {
class Plant
{
private:
RunningMedian moistureRaw = RunningMedian(5);
HomieNode* mPlant = NULL;
int mPinSensor=0; /**< Pin of the moist sensor */
int mPinPump=0; /**< Pin of the pump */
PlantSettings_t* mSetting;
HomieNode *mPlant = NULL;
int mPinSensor = 0; /**< Pin of the moist sensor */
int mPinPump = 0; /**< Pin of the pump */
bool mConnected = false;
public:
PlantSettings_t *mSetting;
/**
* @brief Construct a new Plant object
*
@ -34,8 +35,8 @@ public:
*/
Plant(int pinSensor, int pinPump,
int plantId,
HomieNode* plant,
PlantSettings_t* setting);
HomieNode *plant,
PlantSettings_t *setting);
void postMQTTconnection(void);
@ -47,8 +48,6 @@ public:
*/
void addSenseValue(void);
int getSensorValue() { return moistureRaw.getMedian(); }
void deactivatePump(void);
void activatePump(void);
@ -59,16 +58,42 @@ public:
* @return true
* @return false
*/
bool isPumpRequired() {
return (this->mSetting->pSensorDry != NULL)
&& (this->moistureRaw.getMedian() > this->mSetting->pSensorDry->get())
&& (this->mSetting->pSensorDry->get() != DEACTIVATED_PLANT);
bool isPumpRequired()
{
bool isDry = getCurrentMoisture() > getSettingsMoisture();
bool isActive = isPumpTriggerActive();
return isDry && isActive;
}
HomieInternals::SendingPromise& setProperty(const String& property) const {
bool isPumpTriggerActive()
{
return this->mSetting->pSensorDry->get() != DEACTIVATED_PLANT;
}
float getCurrentMoisture()
{
if(moistureRaw.getCount()==0){
return MISSING_SENSOR;
}
return this->moistureRaw.getMedian();
}
long getSettingsMoisture()
{
if (this->mSetting->pSensorDry != NULL)
{
return this->mSetting->pSensorDry->get();
}
else
{
return DEACTIVATED_PLANT;
}
}
HomieInternals::SendingPromise &setProperty(const String &property) const
{
return mPlant->setProperty(property);
}
bool switchHandler(const HomieRange& range, const String& value);
bool switchHandler(const HomieRange &range, const String &value);
void init(void);
@ -76,16 +101,23 @@ public:
* @brief determine, if the plant was recently casted
* @param sinceLastActivation timestamp of last time
*/
bool isInCooldown(long sinceLastActivation) {
bool isInCooldown(long sinceLastActivation)
{
/* if the time difference is greater than one month, we know these are initial values */
if (sinceLastActivation > (60 * 60 * 24 * 30)) {
if (sinceLastActivation > (60 * 60 * 24 * 30))
{
return false;
}
return (this->mSetting->pPumpCooldownInHours->get() > sinceLastActivation / 3600);
return (getCooldownInSeconds() > sinceLastActivation);
}
bool isAllowedOnlyAtLowLight(void) {
long getCooldownInSeconds(){
return this->mSetting->pPumpCooldownInHours->get()*60*60;
}
bool isAllowedOnlyAtLowLight(void)
{
return this->mSetting->pPumpOnlyWhenLowLight->get();
}
};

View File

@ -17,14 +17,12 @@
// not tested ==> use at own risk :)
// #define RUNNING_MEDIAN_USE_MALLOC
// should at least be 5 to be practical,
// odd sizes results in a 'real' middle element and will be a bit faster.
// even sizes takes the average of the two middle elements as median
#define MEDIAN_MIN_SIZE 5
#define MEDIAN_MAX_SIZE 19
class RunningMedian
{
public:
@ -58,7 +56,6 @@ public:
// returns current used elements, getCount() <= getSize()
uint8_t getCount() { return _cnt; };
protected:
boolean _sorted;
uint8_t _size;
@ -66,8 +63,8 @@ protected:
uint8_t _idx;
#ifdef RUNNING_MEDIAN_USE_MALLOC
float * _ar;
uint8_t * _p;
float *_ar;
uint8_t *_p;
#else
float _ar[MEDIAN_MAX_SIZE];
uint8_t _p[MEDIAN_MAX_SIZE];

View File

@ -1,202 +0,0 @@
/**
arduino-timer - library for delaying function calls
Copyright (c) 2018, Michael Contreras
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
1. Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#ifndef _CM_ARDUINO_TIMER_H__
#define _CM_ARDUINO_TIMER_H__
#if defined(ARDUINO) && ARDUINO >= 100
#include <Arduino.h>
#else
#include <WProgram.h>
#endif
#ifndef TIMER_MAX_TASKS
#define TIMER_MAX_TASKS 0x10
#endif
template <
size_t max_tasks = TIMER_MAX_TASKS, /* max allocated tasks */
unsigned long (*time_func)() = millis, /* time function for timer */
typename T = void * /* handler argument type */
>
class Timer {
public:
typedef uintptr_t Task; /* public task handle */
typedef bool (*handler_t)(T opaque); /* task handler func signature */
/* Calls handler with opaque as argument in delay units of time */
Task
in(unsigned long delay, handler_t h, T opaque = T())
{
return task_id(add_task(time_func(), delay, h, opaque));
}
/* Calls handler with opaque as argument at time */
Task
at(unsigned long time, handler_t h, T opaque = T())
{
const unsigned long now = time_func();
return task_id(add_task(now, time - now, h, opaque));
}
/* Calls handler with opaque as argument every interval units of time */
Task
every(unsigned long interval, handler_t h, T opaque = T())
{
return task_id(add_task(time_func(), interval, h, opaque, interval));
}
/* Cancel the timer task */
void
cancel(Task &task)
{
if (!task) return;
for (size_t i = 0; i < max_tasks; ++i) {
struct task * const t = &tasks[i];
if (t->handler && (t->id ^ task) == (uintptr_t)t) {
remove(t);
break;
}
}
task = (Task)NULL;
}
/* Ticks the timer forward - call this function in loop() */
unsigned long
tick()
{
unsigned long ticks = (unsigned long)-1;
for (size_t i = 0; i < max_tasks; ++i) {
struct task * const task = &tasks[i];
if (task->handler) {
const unsigned long t = time_func();
const unsigned long duration = t - task->start;
if (duration >= task->expires) {
task->repeat = task->handler(task->opaque) && task->repeat;
if (task->repeat) task->start = t;
else remove(task);
} else {
const unsigned long remaining = task->expires - duration;
ticks = remaining < ticks ? remaining : ticks;
}
}
}
return ticks == (unsigned long)-1 ? 0 : ticks;
}
private:
size_t ctr;
struct task {
handler_t handler; /* task handler callback func */
T opaque; /* argument given to the callback handler */
unsigned long start,
expires; /* when the task expires */
size_t repeat, /* repeat task */
id;
} tasks[max_tasks];
inline
void
remove(struct task *task)
{
task->handler = NULL;
task->opaque = T();
task->start = 0;
task->expires = 0;
task->repeat = 0;
task->id = 0;
}
inline
Task
task_id(const struct task * const t)
{
const Task id = (Task)t;
return id ? id ^ t->id : id;
}
inline
struct task *
next_task_slot()
{
for (size_t i = 0; i < max_tasks; ++i) {
struct task * const slot = &tasks[i];
if (slot->handler == NULL) return slot;
}
return NULL;
}
inline
struct task *
add_task(unsigned long start, unsigned long expires,
handler_t h, T opaque, bool repeat = 0)
{
struct task * const slot = next_task_slot();
if (!slot) return NULL;
if (++ctr == 0) ++ctr; // overflow
slot->id = ctr;
slot->handler = h;
slot->opaque = opaque;
slot->start = start;
slot->expires = expires;
slot->repeat = repeat;
return slot;
}
};
/* create a timer with the default settings */
inline Timer<>
timer_create_default()
{
return Timer<>();
}
#endif

View File

@ -15,6 +15,8 @@ framework = arduino
build_flags = -DPIO_FRAMEWORK_ARDUINO_LWIP2_LOW_MEMORY
board_build.partitions = defaultWithSmallerSpiffs.csv
upload_port=/dev/ttyUSB0
; the latest development brankitchen-lightch (convention V3.0.x)
lib_deps = ArduinoJson@6.16.1
https://github.com/homieiot/homie-esp8266.git#v3.0

View File

@ -21,36 +21,35 @@
//Printf debugging
//#define DS_DEBUG
int Ds18B20::readDevices() {
int Ds18B20::readDevices()
{
byte addr[8];
int amount = -1;
while (this->mDs->search(addr)) {
while (this->mDs->search(addr))
{
amount++;
}
this->mDs->reset_search();
return amount;
}
int Ds18B20::readAllTemperatures(float* pTemperatures, int maxTemperatures) {
int Ds18B20::readAllTemperatures(float *pTemperatures, int maxTemperatures)
{
byte addr[8];
uint8_t scratchPad[SCRATCHPADSIZE];
int currentTemp = 0;
#ifdef DS_DEBUG
int i;
#endif
while (this->mDs->search(addr)) {
#ifdef DS_DEBUG
Serial.print(" ROM =");
for (i = 0; i < 8; i++) {
Serial.write(' ');
Serial.print(addr[i], HEX);
}
#endif
while (this->mDs->search(addr))
{
this->mDs->reset();
this->mDs->select(addr);
this->mDs->write(STARTCONV);
}
delay(750);
while (this->mDs->search(addr))
{
this->mDs->reset();
this->mDs->select(addr);
this->mDs->write(READSCRATCH);
@ -68,31 +67,27 @@ int Ds18B20::readAllTemperatures(float* pTemperatures, int maxTemperatures) {
// byte 7: DS18S20: COUNT_PER_C
// DS18B20 & DS1822: store for crc
// byte 8: SCRATCHPAD_CRC
#ifdef DS_DEBUG
Serial.write("\r\nDATA:");
for (uint8_t i = 0; i < 9; i++) {
Serial.print(scratchPad[i], HEX);
}
#else
delay(50);
#endif
for (uint8_t i = 0; i < 9; i++) {
for (uint8_t i = 0; i < 9; i++)
{
scratchPad[i] = this->mDs->read();
}
uint8_t crc8 = this->mDs->crc8(scratchPad, 8);
/* Only work an valid data */
if (crc8 == scratchPad[OFFSET_CRC8]) {
int16_t fpTemperature = (((int16_t) scratchPad[TEMP_MSB]) << 11)
| (((int16_t) scratchPad[TEMP_LSB]) << 3);
float celsius = (float) fpTemperature * 0.0078125;
if (crc8 == scratchPad[OFFSET_CRC8])
{
int16_t fpTemperature = (((int16_t)scratchPad[TEMP_MSB]) << 11) | (((int16_t)scratchPad[TEMP_LSB]) << 3);
float celsius = (float)fpTemperature * 0.0078125;
#ifdef DS_DEBUG
Serial.printf("\r\nTemp%d %f °C (Raw: %d, %x =? %x)\r\n", (currentTemp+1), celsius, fpTemperature, crc8, scratchPad[8]);
Serial.printf("\r\nTemp%d %f °C (Raw: %d, %x =? %x)\r\n", (currentTemp + 1), celsius, fpTemperature, crc8, scratchPad[8]);
#endif
/* check, if the buffer as some space for our data */
if (currentTemp < maxTemperatures) {
if (currentTemp < maxTemperatures)
{
pTemperatures[currentTemp] = celsius;
} else {
}
else
{
return -1;
}
}

View File

@ -11,32 +11,35 @@
*/
#include "PlantCtrl.h"
#include "ControllerConfiguration.h"
Plant::Plant(int pinSensor, int pinPump,int plantId, HomieNode* plant, PlantSettings_t* setting) {
Plant::Plant(int pinSensor, int pinPump, int plantId, HomieNode *plant, PlantSettings_t *setting)
{
this->mPinSensor = pinSensor;
this->mPinPump = pinPump;
this->mPlant = plant;
this->mSetting = setting;
}
void Plant::init(void) {
void Plant::init(void)
{
/* Initialize Home Settings validator */
this->mSetting->pSensorDry->setDefaultValue(DEACTIVATED_PLANT);
this->mSetting->pSensorDry->setValidator([] (long candidate) {
return (((candidate >= 0) && (candidate <= 4095) ) || candidate == DEACTIVATED_PLANT);
this->mSetting->pSensorDry->setValidator([](long candidate) {
return (((candidate >= 0) && (candidate <= 4095)) || candidate == DEACTIVATED_PLANT);
});
this->mSetting->pPumpAllowedHourRangeStart->setDefaultValue(8); // start at 8:00
this->mSetting->pPumpAllowedHourRangeStart->setValidator([] (long candidate) {
return ((candidate >= 0) && (candidate <= 23) );
this->mSetting->pPumpAllowedHourRangeStart->setValidator([](long candidate) {
return ((candidate >= 0) && (candidate <= 23));
});
this->mSetting->pPumpAllowedHourRangeEnd->setDefaultValue(20); // stop pumps at 20:00
this->mSetting->pPumpAllowedHourRangeEnd->setValidator([] (long candidate) {
return ((candidate >= 0) && (candidate <= 23) );
this->mSetting->pPumpAllowedHourRangeEnd->setValidator([](long candidate) {
return ((candidate >= 0) && (candidate <= 23));
});
this->mSetting->pPumpOnlyWhenLowLight->setDefaultValue(true);
this->mSetting->pPumpCooldownInHours->setDefaultValue(20); // minutes
this->mSetting->pPumpCooldownInHours->setValidator([] (long candidate) {
return ((candidate >= 0) && (candidate <= 1024) );
this->mSetting->pPumpCooldownInHours->setValidator([](long candidate) {
return ((candidate >= 0) && (candidate <= 1024));
});
/* Initialize Hardware */
@ -45,43 +48,50 @@ void Plant::init(void) {
digitalWrite(this->mPinPump, LOW);
}
void Plant::addSenseValue(void) {
this->moistureRaw.add( analogRead(this->mPinSensor) );
void Plant::addSenseValue(void)
{
int raw = analogRead(this->mPinSensor);
if(raw < MOIST_SENSOR_MAX_ADC && raw > MOIST_SENSOR_MIN_ADC){
this->moistureRaw.add(raw);
}
}
void Plant::postMQTTconnection(void) {
void Plant::postMQTTconnection(void)
{
const String OFF = String("OFF");
this->mConnected=true;
this->mConnected = true;
this->mPlant->setProperty("switch").send(OFF);
}
void Plant::deactivatePump(void) {
void Plant::deactivatePump(void)
{
digitalWrite(this->mPinPump, LOW);
if (this->mConnected) {
if (this->mConnected)
{
const String OFF = String("OFF");
this->mPlant->setProperty("switch").send(OFF);
}
}
void Plant::activatePump(void) {
void Plant::activatePump(void)
{
digitalWrite(this->mPinPump, HIGH);
if (this->mConnected) {
if (this->mConnected)
{
const String OFF = String("ON");
this->mPlant->setProperty("switch").send(OFF);
}
}
void Plant::advertise(void) {
void Plant::advertise(void)
{
// Advertise topics
this->mPlant->advertise("switch").setName("Pump 1")
.setDatatype("boolean");
this->mPlant->advertise("switch").setName("Pump 1").setDatatype("boolean");
//FIXME add .settable(this->switchHandler)
this->mPlant->advertise("moist").setName("Percent")
.setDatatype("number")
.setUnit("%");
this->mPlant->advertise("moist").setName("Percent").setDatatype("number").setUnit("%");
this->mPlant->advertise("moistraw").setName("adc").setDatatype("number").setUnit("3.3/4096V");
}
/* FIXME
bool Plant::switchHandler(const HomieRange& range, const String& value) {
if (range.isRange) return false; // only one switch is present

View File

@ -31,8 +31,8 @@ RunningMedian::RunningMedian(const uint8_t size)
_size = constrain(size, MEDIAN_MIN_SIZE, MEDIAN_MAX_SIZE);
#ifdef RUNNING_MEDIAN_USE_MALLOC
_ar = (float *) malloc(_size * sizeof(float));
_p = (uint8_t *) malloc(_size * sizeof(uint8_t));
_ar = (float *)malloc(_size * sizeof(float));
_p = (uint8_t *)malloc(_size * sizeof(uint8_t));
#endif
clear();
@ -63,16 +63,20 @@ void RunningMedian::clear()
void RunningMedian::add(float value)
{
_ar[_idx++] = value;
if (_idx >= _size) _idx = 0; // wrap around
if (_cnt < _size) _cnt++;
if (_idx >= _size)
_idx = 0; // wrap around
if (_cnt < _size)
_cnt++;
_sorted = false;
}
float RunningMedian::getMedian()
{
if (_cnt == 0) return NAN;
if (_cnt == 0)
return NAN;
if (_sorted == false) sort();
if (_sorted == false)
sort();
if (_cnt & 0x01) // is it odd sized?
{
@ -83,7 +87,8 @@ float RunningMedian::getMedian()
float RunningMedian::getAverage()
{
if (_cnt == 0) return NAN;
if (_cnt == 0)
return NAN;
float sum = 0;
for (uint8_t i = 0; i < _cnt; i++)
@ -95,13 +100,16 @@ float RunningMedian::getAverage()
float RunningMedian::getAverage(uint8_t nMedians)
{
if ((_cnt == 0) || (nMedians == 0)) return NAN;
if ((_cnt == 0) || (nMedians == 0))
return NAN;
if (_cnt < nMedians) nMedians = _cnt; // when filling the array for first time
if (_cnt < nMedians)
nMedians = _cnt; // when filling the array for first time
uint8_t start = ((_cnt - nMedians) / 2);
uint8_t stop = start + nMedians;
if (_sorted == false) sort();
if (_sorted == false)
sort();
float sum = 0;
for (uint8_t i = start; i < stop; i++)
@ -113,7 +121,8 @@ float RunningMedian::getAverage(uint8_t nMedians)
float RunningMedian::getElement(const uint8_t n)
{
if ((_cnt == 0) || (n >= _cnt)) return NAN;
if ((_cnt == 0) || (n >= _cnt))
return NAN;
uint8_t pos = _idx + n;
if (pos >= _cnt) // faster than %
@ -125,16 +134,19 @@ float RunningMedian::getElement(const uint8_t n)
float RunningMedian::getSortedElement(const uint8_t n)
{
if ((_cnt == 0) || (n >= _cnt)) return NAN;
if ((_cnt == 0) || (n >= _cnt))
return NAN;
if (_sorted == false) sort();
if (_sorted == false)
sort();
return _ar[_p[n]];
}
// n can be max <= half the (filled) size
float RunningMedian::predict(const uint8_t n)
{
if ((_cnt == 0) || (n >= _cnt / 2)) return NAN;
if ((_cnt == 0) || (n >= _cnt / 2))
return NAN;
float med = getMedian(); // takes care of sorting !
if (_cnt & 0x01)
@ -162,7 +174,8 @@ void RunningMedian::sort()
flag = false;
}
}
if (flag) break;
if (flag)
break;
}
_sorted = true;
}

View File

@ -1,4 +1,6 @@
/**
/** \addtogroup Controller
* @{
*
* @file main.cpp
* @author Ollo
* @brief PlantControl
@ -6,7 +8,6 @@
* @date 2020-05-01
*
* @copyright Copyright (c) 2020
*
*/
#include "PlantCtrl.h"
#include "ControllerConfiguration.h"
@ -16,8 +17,8 @@
#include "time.h"
#include "esp_sleep.h"
#include "RunningMedian.h"
#include <arduino-timer.h>
#include <stdint.h>
#include <math.h>
const unsigned long TEMPREADCYCLE = 30000; /**< Check temperature all half minutes */
@ -26,44 +27,36 @@ const unsigned long TEMPREADCYCLE = 30000; /**< Check temperature all half minut
#define SOLAR4SENSORS 6.0f
#define TEMP_INIT_VALUE -999.0f
#define TEMP_MAX_VALUE 85.0f
#define HalfHour 60
typedef struct
{
long lastActive; /**< Timestamp, a pump was activated */
long moistTrigger; /**< Trigger value of the moist sensor */
long moisture; /**< last measured moist value */
} rtc_plant_t;
/********************* non volatile enable after deepsleep *******************************/
RTC_DATA_ATTR rtc_plant_t rtcPlant[MAX_PLANTS];
RTC_DATA_ATTR long gotoMode2AfterThisTimestamp = 0;
RTC_DATA_ATTR long rtcDeepSleepTime = 0; /**< Time, when the microcontroller shall be up again */
RTC_DATA_ATTR long rtcLastActive0 = 0;
RTC_DATA_ATTR long rtcMoistureTrigger0 = 0; /**<Level for the moisture sensor */
RTC_DATA_ATTR long rtcLastActive1 = 0;
RTC_DATA_ATTR long rtcMoistureTrigger1 = 0; /**<Level for the moisture sensor */
RTC_DATA_ATTR long rtcLastActive2 = 0;
RTC_DATA_ATTR long rtcMoistureTrigger2 = 0; /**<Level for the moisture sensor */
RTC_DATA_ATTR long rtcLastActive3 = 0;
RTC_DATA_ATTR long rtcMoistureTrigger3 = 0; /**<Level for the moisture sensor */
RTC_DATA_ATTR long rtcLastActive4 = 0;
RTC_DATA_ATTR long rtcMoistureTrigger4 = 0; /**<Level for the moisture sensor */
RTC_DATA_ATTR long rtcLastActive5 = 0;
RTC_DATA_ATTR long rtcMoistureTrigger5 = 0; /**<Level for the moisture sensor */
RTC_DATA_ATTR long rtcLastActive6 = 0;
RTC_DATA_ATTR long rtcMoistureTrigger6 = 0; /**<Level for the moisture sensor */
RTC_DATA_ATTR int lastPumpRunning = 0;
RTC_DATA_ATTR long lastWaterValue = 0;
const char *ntpServer = "pool.ntp.org";
RTC_DATA_ATTR float rtcLastTemp1 = 0.0f;
RTC_DATA_ATTR float rtcLastTemp2 = 0.0f;
RTC_DATA_ATTR int gBootCount = 0;
RTC_DATA_ATTR int gCurrentPlant = 0; /**< Value Range: 1 ... 7 (0: no plant needs water) */
bool warmBoot = true;
bool mode3Active = false; /**< Controller must not sleep */
bool mDeepsleep = false;
bool volatile mode3Active = false; /**< Controller must not sleep */
bool volatile mDeepsleep = false;
int plantSensor1 = 0;
int readCounter = 0;
bool mConfigured = false;
auto wait4sleep = timer_create_default(); // create a timer with default settings
RTC_DATA_ATTR int gBootCount = 0;
RTC_DATA_ATTR int gCurrentPlant = 0; /**< Value Range: 1 ... 7 (0: no plant needs water) */
RunningMedian lipoRawSensor = RunningMedian(5);
RunningMedian solarRawSensor = RunningMedian(5);
RunningMedian waterRawSensor = RunningMedian(5);
@ -91,10 +84,41 @@ float getSolarVoltage()
return SOLAR_VOLT(solarRawSensor.getAverage());
}
void setMoistureTrigger(int plantId, long value)
{
if ((plantId >= 0) && (plantId < MAX_PLANTS))
{
rtcPlant[plantId].moistTrigger = value;
}
}
void setLastMoisture(int plantId, long value)
{
if ((plantId >= 0) && (plantId < MAX_PLANTS))
{
rtcPlant[plantId].moisture = value;
}
}
long getLastMoisture(int plantId)
{
if ((plantId >= 0) && (plantId < MAX_PLANTS))
{
return rtcPlant[plantId].moisture;
}
else
{
return -1;
}
}
void readSystemSensors()
{
for (int i=0; i < 5; i++) {
lipoRawSensor.add(analogRead(SENSOR_LIPO));
solarRawSensor.add(analogRead(SENSOR_SOLAR));
}
Serial << "Lipo " << lipoRawSensor.getAverage() << " -> " << getBatteryVoltage() << endl;
}
int determineNextPump();
@ -107,39 +131,42 @@ long getCurrentTime()
return tv_now.tv_sec;
}
//wait till homie flushed mqtt ect.
bool prepareSleep(void *)
{
//FIXME wait till pending mqtt is done, then start sleep via event or whatever
//Homie.disableResetTrigger();
bool queueIsEmpty = true;
if (queueIsEmpty)
{
mDeepsleep = true;
}
return false; // repeat? true there is something in the queue to be done
}
void espDeepSleepFor(long seconds, bool activatePump = false)
{
delay(1500);
if (mode3Active)
{
Serial << "abort deepsleep, mode3Active" << endl;
return;
}
for (int i = 0; i < 10; i++)
{
long cTime = getCurrentTime();
if (cTime < 100000)
{
Serial << "Wait for ntp" << endl;
delay(100);
}
else
{
break;
}
}
esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_PERIPH, ESP_PD_OPTION_OFF);
esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_FAST_MEM, ESP_PD_OPTION_OFF);
esp_sleep_pd_config(ESP_PD_DOMAIN_XTAL, ESP_PD_OPTION_ON);
esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_SLOW_MEM, ESP_PD_OPTION_ON);
if (activatePump)
{
esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_SLOW_MEM, ESP_PD_OPTION_ON);
gpio_deep_sleep_hold_en();
gpio_hold_en(GPIO_NUM_13); //pump pwr
}
else
{
esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_SLOW_MEM, ESP_PD_OPTION_OFF);
gpio_hold_dis(GPIO_NUM_13); //pump pwr
gpio_deep_sleep_hold_dis();
digitalWrite(OUTPUT_PUMP, LOW);
digitalWrite(OUTPUT_SENSOR, LOW);
for (int i = 0; i < MAX_PLANTS; i++)
{
mPlants[i].deactivatePump();
@ -148,19 +175,17 @@ void espDeepSleepFor(long seconds, bool activatePump = false)
//gpio_hold_en(GPIO_NUM_23); //p0
//FIXME fix for outher outputs
Serial.print("Going to sleep for ");
Serial.print("Trying to sleep for ");
Serial.print(seconds);
Serial.println(" seconds");
esp_sleep_enable_timer_wakeup((seconds * 1000U * 1000U));
wait4sleep.in(500, prepareSleep);
mDeepsleep = true;
}
void mode2MQTT()
{
readSystemSensors();
configTime(0, 0, ntpServer);
digitalWrite(OUTPUT_PUMP, LOW);
for (int i = 0; i < MAX_PLANTS; i++)
{
@ -169,7 +194,7 @@ void mode2MQTT()
if (deepSleepTime.get())
{
Serial << "sleeping for " << deepSleepTime.get() << endl;
Serial << "deepsleep time is configured to " << deepSleepTime.get() << endl;
}
/* Publish default values */
@ -180,7 +205,23 @@ void mode2MQTT()
}
for (int i = 0; i < MAX_PLANTS; i++)
{
mPlants[i].setProperty("moist").send(String(100 * mPlants[i].getSensorValue() / 4095));
long raw = mPlants[i].getCurrentMoisture();
long pct = 100 - map(raw, MOIST_SENSOR_MIN_ADC, MOIST_SENSOR_MAX_ADC, 0, 100);
if (raw == MISSING_SENSOR)
{
pct = 0;
}
if (pct < 0)
{
pct = 0;
}
if (pct > 100)
{
pct = 100;
}
mPlants[i].setProperty("moist").send(String(pct));
mPlants[i].setProperty("moistraw").send(String(raw));
}
sensorWater.setProperty("remaining").send(String(waterLevelMax.get() - waterRawSensor.getAverage()));
Serial << "W : " << waterRawSensor.getAverage() << " cm (" << String(waterLevelMax.get() - waterRawSensor.getAverage()) << "%)" << endl;
@ -191,34 +232,31 @@ void mode2MQTT()
sensorSolar.setProperty("percent").send(String((100 * solarRawSensor.getAverage()) / 4095));
sensorSolar.setProperty("volt").send(String(getSolarVoltage()));
float temp[2] = {TEMP_INIT_VALUE, TEMP_INIT_VALUE};
float *pFloat = temp;
int devices = dallas.readAllTemperatures(pFloat, 2);
if (devices < 2)
float t1 = temp1.getMedian();
if (t1 != NAN)
{
if ((pFloat[0] > TEMP_INIT_VALUE) && (pFloat[0] < TEMP_MAX_VALUE))
{
sensorTemp.setProperty("control").send(String(pFloat[0]));
sensorTemp.setProperty("control").send(String(t1));
}
}
else if (devices >= 2)
float t2 = temp2.getMedian();
if (t2 != NAN)
{
if ((pFloat[0] > TEMP_INIT_VALUE) && (pFloat[0] < TEMP_MAX_VALUE))
{
sensorTemp.setProperty("temp").send(String(pFloat[0]));
}
if ((pFloat[1] > TEMP_INIT_VALUE) && (pFloat[1] < TEMP_MAX_VALUE))
{
sensorTemp.setProperty("control").send(String(pFloat[1]));
}
sensorTemp.setProperty("temp").send(String(t2));
}
bool lipoTempWarning = abs(temp[0] - temp[1]) > 5;
//give mqtt time, use via publish callback instead?
delay(100);
bool lipoTempWarning = t1 != 85 && t2 != 85 && abs(t1 - t2) > 10;
if (lipoTempWarning)
{
Serial.println("Lipo temp incorrect, panic mode deepsleep");
espDeepSleepFor(PANIK_MODE_DEEPSLEEP);
return;
Serial.println("Lipo temp incorrect, panic mode deepsleep TODO");
//espDeepSleepFor(PANIK_MODE_DEEPSLEEP);
//return;
}
for (int i = 0; i < MAX_PLANTS; i++)
{
setMoistureTrigger(i, mPlants[i].mSetting->pSensorDry->get());
}
bool hasWater = true; //FIXMEmWaterGone > waterLevelMin.get();
@ -229,24 +267,33 @@ void mode2MQTT()
Serial.println("Want to pump but no water");
}
if (lastPumpRunning != -1 && hasWater)
{
if (mode3Active)
{
Serial.println("Mode 3 active, ignoring pump request");
}
else
{
digitalWrite(OUTPUT_PUMP, HIGH);
setLastActivationForPump(lastPumpRunning, getCurrentTime());
mPlants[lastPumpRunning].activatePump();
}
}
if (lastPumpRunning == -1 || !hasWater)
{
if (getSolarVoltage() < SOLAR_CHARGE_MIN_VOLTAGE)
{
gotoMode2AfterThisTimestamp = getCurrentTime() + deepSleepNightTime.get();
gotoMode2AfterThisTimestamp = getCurrentTime() + maxTimeBetweenMQTTUpdates.get();
Serial.println("No pumps to activate and low light, deepSleepNight");
espDeepSleepFor(deepSleepNightTime.get());
rtcDeepSleepTime = deepSleepNightTime.get();
}
else
{
gotoMode2AfterThisTimestamp = getCurrentTime() + deepSleepTime.get();
gotoMode2AfterThisTimestamp = getCurrentTime() + maxTimeBetweenMQTTUpdates.get();
Serial.println("No pumps to activate, deepSleep");
espDeepSleepFor(deepSleepTime.get());
rtcDeepSleepTime = deepSleepTime.get();
}
}
else
@ -257,109 +304,47 @@ void mode2MQTT()
}
}
void setMoistureTrigger(int plantId, long value)
long getMoistureTrigger(int plantId)
{
if (plantId == 0)
if ((plantId >= 0) && (plantId < MAX_PLANTS))
{
rtcMoistureTrigger0 = value;
return rtcPlant[plantId].moistTrigger;
}
if (plantId == 1)
else
{
rtcMoistureTrigger1 = value;
}
if (plantId == 2)
{
rtcMoistureTrigger2 = value;
}
if (plantId == 3)
{
rtcMoistureTrigger3 = value;
}
if (plantId == 4)
{
rtcMoistureTrigger4 = value;
}
if (plantId == 5)
{
rtcMoistureTrigger5 = value;
}
if (plantId == 6)
{
rtcMoistureTrigger6 = value;
return -1;
}
}
void setLastActivationForPump(int plantId, long value)
{
if (plantId == 0)
if ((plantId >= 0) && (plantId < MAX_PLANTS))
{
rtcLastActive0 = value;
}
if (plantId == 1)
{
rtcLastActive1 = value;
}
if (plantId == 2)
{
rtcLastActive2 = value;
}
if (plantId == 3)
{
rtcLastActive3 = value;
}
if (plantId == 4)
{
rtcLastActive4 = value;
}
if (plantId == 5)
{
rtcLastActive5 = value;
}
if (plantId == 6)
{
rtcLastActive6 = value;
rtcPlant[plantId].lastActive = value;
}
}
long getLastActivationForPump(int plantId)
{
if (plantId == 0)
if ((plantId >= 0) && (plantId < MAX_PLANTS))
{
return rtcLastActive0;
return rtcPlant[plantId].lastActive;
}
if (plantId == 1)
else
{
return rtcLastActive1;
}
if (plantId == 2)
{
return rtcLastActive2;
}
if (plantId == 3)
{
return rtcLastActive3;
}
if (plantId == 4)
{
return rtcLastActive4;
}
if (plantId == 5)
{
return rtcLastActive5;
}
if (plantId == 6)
{
return rtcLastActive6;
}
return -1;
}
}
/**
* @brief Sensors, that are connected to GPIOs, mandatory for WIFI.
* These sensors (ADC2) can only be read when no Wifi is used.
*/
void readSensors()
bool readSensors()
{
float temp[2] = {TEMP_MAX_VALUE, TEMP_MAX_VALUE};
float *pFloat = temp;
bool leaveMode1 = false;
Serial << "Read Sensors" << endl;
readSystemSensors();
@ -368,7 +353,7 @@ void readSensors()
pinMode(OUTPUT_SENSOR, OUTPUT);
digitalWrite(OUTPUT_SENSOR, HIGH);
delay(100);
delay(20);
/* wait before reading something */
for (int readCnt = 0; readCnt < AMOUNT_SENOR_QUERYS; readCnt++)
{
@ -376,6 +361,19 @@ void readSensors()
{
mPlants[i].addSenseValue();
}
delay(10);
}
for (int i = 0; i < MAX_PLANTS; i++)
{
long current = mPlants[i].getCurrentMoisture();
long delta = abs(getLastMoisture(i) - current);
bool tmp = (delta > MOIST_DELTA_TRIGGER_ADC);
setLastMoisture(i, current);
if (tmp)
{
leaveMode1 = true;
Serial.printf("Mode2 start due to moist delta in plant %d with %ld \r\n", i, delta);
}
}
Serial << "DS18B20" << endl;
@ -384,23 +382,33 @@ void readSensors()
delay(200);
/* Required to read the temperature once */
float temp[2] = {0, 0};
float *pFloat = temp;
for (int i = 0; i < 10; i++)
for (int i = 0; i < 5; i++)
{
if (dallas.readAllTemperatures(pFloat, 2) > 0)
int sensors = dallas.readAllTemperatures(pFloat, 2);
if (sensors > 0)
{
Serial << "t1: " << String(temp[0]) << endl;
Serial << "t2: " << String(temp[1]) << endl;
// first read returns crap, ignore result and read again
if (i <= 2)
{
temp1.add(temp[0]);
}
if (sensors > 1)
{
Serial << "t2: " << String(temp[1]) << endl;
temp2.add(temp[1]);
}
delay(50);
}
delay(200);
if ((temp1.getAverage() - rtcLastTemp1 > TEMPERATURE_DELTA_TRIGGER_IN_C) ||
(rtcLastTemp1 - temp1.getAverage() > TEMPERATURE_DELTA_TRIGGER_IN_C)) {
leaveMode1 = true;
}
if ((temp2.getAverage() - rtcLastTemp2 > TEMPERATURE_DELTA_TRIGGER_IN_C) ||
(rtcLastTemp2 - temp2.getAverage() > TEMPERATURE_DELTA_TRIGGER_IN_C)) {
leaveMode1 = true;
}
rtcLastTemp1 = temp1.getAverage();
rtcLastTemp2 = temp2.getAverage();
/* Use the Ultrasonic sensor to measure waterLevel */
for (int i = 0; i < 5; i++)
@ -417,6 +425,7 @@ void readSensors()
}
/* deactivate the sensors */
digitalWrite(OUTPUT_SENSOR, LOW);
return leaveMode1;
}
//Homie.getMqttClient().disconnect();
@ -429,9 +438,11 @@ void onHomieEvent(const HomieEvent &event)
Homie.getLogger() << "My statistics" << endl;
break;
case HomieEventType::MQTT_READY:
Serial.printf("NTP Setup with server %s\r\n", ntpServer.get());
configTime(0, 0, ntpServer.get());
//wait for rtc sync?
rtcDeepSleepTime = deepSleepTime.get();
Serial << "MQTT ready " << rtcDeepSleepTime << " ms ds" << endl;
Serial << "Setup plants" << endl;
for (int i = 0; i < MAX_PLANTS; i++)
{
mPlants[i].postMQTTconnection();
@ -444,8 +455,9 @@ void onHomieEvent(const HomieEvent &event)
esp_deep_sleep_start();
break;
case HomieEventType::OTA_STARTED:
Homie.getLogger() << "OTA started" << endl;
digitalWrite(OUTPUT_SENSOR, HIGH);
digitalWrite(OUTPUT_PUMP, LOW);
digitalWrite(OUTPUT_PUMP, HIGH);
gpio_hold_dis(GPIO_NUM_13); //pump pwr
gpio_deep_sleep_hold_dis();
for (int i = 0; i < MAX_PLANTS; i++)
@ -455,6 +467,7 @@ void onHomieEvent(const HomieEvent &event)
mode3Active = true;
break;
case HomieEventType::OTA_SUCCESSFUL:
Homie.getLogger() << "OTA successfull" << endl;
digitalWrite(OUTPUT_SENSOR, LOW);
digitalWrite(OUTPUT_PUMP, LOW);
ESP.restart();
@ -470,32 +483,46 @@ int determineNextPump()
bool isLowLight = (solarValue > SOLAR_CHARGE_MIN_VOLTAGE || solarValue < SOLAR_CHARGE_MAX_VOLTAGE);
//FIXME instead of for, use sorted by last activation index to ensure equal runtime?
int pumpToUse = -1;
for (int i = 0; i < MAX_PLANTS; i++)
{
Plant plant = mPlants[i];
long lastActivation = getLastActivationForPump(i);
long sinceLastActivation = getCurrentTime() - lastActivation;
//this pump is in cooldown skip it and disable low power mode trigger for it
if (mPlants[i].isInCooldown(sinceLastActivation))
if (plant.isInCooldown(sinceLastActivation))
{
Serial.printf("%d Skipping due to cooldown\r\n", i);
Serial.printf("%d Skipping due to cooldown %ld / %ld \r\n", i, sinceLastActivation, plant.getCooldownInSeconds());
setMoistureTrigger(i, DEACTIVATED_PLANT);
continue;
}
//skip as it is not low light
if (!isLowLight && mPlants[i].isAllowedOnlyAtLowLight())
if (!isLowLight && plant.isAllowedOnlyAtLowLight())
{
Serial.println("Skipping due to light");
Serial.printf("%d No pump required: due to light\r\n", i);
continue;
}
if (mPlants->isPumpRequired())
if (plant.getCurrentMoisture() == MISSING_SENSOR && plant.isPumpTriggerActive())
{
Serial.printf("%d No pump possible: missing sensor \r\n", i);
continue;
}
if (plant.isPumpRequired())
{
Serial.printf("%d Requested pumping\r\n", i);
return i;
pumpToUse = i;
}
Serial.printf("%d No pump required\r\n", i);
else if (plant.isPumpTriggerActive())
{
Serial.printf("%d No pump required: moisture acceptable %f / %ld\r\n", i, plant.getCurrentMoisture(), plant.getSettingsMoisture());
}
return -1;
else
{
Serial.printf("%d No pump required: disabled pump trigger \r\n", i);
}
}
return pumpToUse;
}
/**
@ -510,7 +537,6 @@ bool aliveHandler(const HomieRange &range, const String &value)
{
if (range.isRange)
return false; // only one controller is present
Serial << value << endl;
if (value.equals("ON") || value.equals("On") || value.equals("1"))
{
mode3Active = true;
@ -536,9 +562,11 @@ void systemInit()
// Set default values
//in seconds
deepSleepTime.setDefaultValue(10);
deepSleepNightTime.setDefaultValue(30);
maxTimeBetweenMQTTUpdates.setDefaultValue(120);
deepSleepTime.setDefaultValue(60);
deepSleepNightTime.setDefaultValue(600);
wateringDeepSleep.setDefaultValue(5);
ntpServer.setDefaultValue("pool.ntp.org");
/* waterLevelMax 1000 */ /* 100cm in mm */
waterLevelMin.setDefaultValue(50); /* 5cm in mm */
@ -547,6 +575,7 @@ void systemInit()
Homie.setLoopFunction(homieLoop);
Homie.onEvent(onHomieEvent);
//Homie.disableLogging();
Homie.setup();
mConfigured = Homie.isConfigured();
@ -589,63 +618,46 @@ void systemInit()
bool mode1()
{
Serial.println("m1");
Serial.println("==== Mode 1 ====");
Serial << getCurrentTime() << " curtime" << endl;
/* Disable all sleeping stuff before reading sensors */
gpio_deep_sleep_hold_dis();
readSensors();
bool deltaTrigger = readSensors();
//queue sensor values for
if ((rtcDeepSleepTime == 0) ||
(rtcMoistureTrigger0 == 0) ||
(rtcMoistureTrigger1 == 0) ||
(rtcMoistureTrigger2 == 0) ||
(rtcMoistureTrigger3 == 0) ||
(rtcMoistureTrigger4 == 0) ||
(rtcMoistureTrigger5 == 0) ||
(rtcMoistureTrigger6 == 0))
if (deltaTrigger)
{
Serial.println("RTCm2");
Serial.println("1 delta triggered, going to mode2");
return true;
}
if (rtcDeepSleepTime == 0)
{
Serial.println("1 missing rtc value, going to mode2");
return true;
}
for (int i = 0; i < MAX_PLANTS; i++)
{
long trigger = getMoistureTrigger(i);
if (trigger == 0)
{
Serial << "Missing rtc trigger " << i << endl;
return true;
}
if (trigger == DEACTIVATED_PLANT)
{
continue;
}
long raw = mPlants[i].getCurrentMoisture();
if (raw == MISSING_SENSOR)
{
continue;
}
if (raw > trigger)
{
Serial << "plant " << i << " dry " << raw << " / " << trigger << " starting mode 2" << endl;
return true;
}
}
if ((rtcMoistureTrigger0 != DEACTIVATED_PLANT) && (mPlants[0].getSensorValue() < rtcMoistureTrigger0))
{
Serial.println("mt0");
return true;
}
if ((rtcMoistureTrigger1 != DEACTIVATED_PLANT) && (mPlants[1].getSensorValue() < rtcMoistureTrigger1))
{
Serial.println("mt1");
return true;
}
if ((rtcMoistureTrigger2 != DEACTIVATED_PLANT) && (mPlants[2].getSensorValue() < rtcMoistureTrigger2))
{
Serial.println("mt2");
return true;
}
if ((rtcMoistureTrigger3 != DEACTIVATED_PLANT) && (mPlants[3].getSensorValue() < rtcMoistureTrigger3))
{
Serial.println("mt3");
return true;
}
if ((rtcMoistureTrigger4 != DEACTIVATED_PLANT) && (mPlants[4].getSensorValue() < rtcMoistureTrigger4))
{
Serial.println("mt4");
return true;
}
if ((rtcMoistureTrigger5 != DEACTIVATED_PLANT) && (mPlants[5].getSensorValue() < rtcMoistureTrigger5))
{
Serial.println("mt5");
return true;
}
if ((rtcMoistureTrigger6 != DEACTIVATED_PLANT) && (mPlants[6].getSensorValue() < rtcMoistureTrigger6))
{
Serial.println("mt6");
return true;
}
//check how long it was already in mode1 if to long goto mode2
long cTime = getCurrentTime();
@ -660,18 +672,22 @@ bool mode1()
Serial.println("Starting mode 2 after specified mode1 time");
return true;
}
else
{
Serial << "Mode2 Timer " << gotoMode2AfterThisTimestamp << " curtime " << cTime << endl;
}
return false;
}
void mode2()
{
Serial.println("m2");
Serial.println("==== Mode 2 ====");
systemInit();
/* Jump into Mode 3, if not configured */
if (!mConfigured)
{
Serial.println("m3");
Serial.println("==== Mode 3 ====");
mode3Active = true;
}
}
@ -732,8 +748,7 @@ void setup()
else
{
Serial.println("nop");
Serial.flush();
esp_deep_sleep_start();
espDeepSleepFor(rtcDeepSleepTime);
}
}
@ -741,22 +756,36 @@ void setup()
* @brief Cyclic call
* Executs the Homie base functionallity or triggers sleeping, if requested.
*/
long nextBlink = 0;
void loop()
{
if (!mDeepsleep)
if (!mDeepsleep || mode3Active)
{
Homie.loop();
}
else
{
Serial << "Bye" << endl;
Serial.flush();
esp_deep_sleep_start();
}
if (millis() > 30000 && !mode3Active)
{
Serial << (millis() / 1000) << "s alive" << endl;
Serial << (millis() / 1000) << "not terminated watchdog putting to sleep" << endl;
Serial.flush();
esp_deep_sleep_start();
espDeepSleepFor(rtcDeepSleepTime);
}
/* Toggel Senor LED to visualize mode 3 */
if (mode3Active)
{
if (nextBlink < millis())
{
nextBlink = millis() + 500;
digitalWrite(OUTPUT_SENSOR, !digitalRead(OUTPUT_SENSOR));
}
}
}
/** @}*/