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 # 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 # Hardware
gpio 16 only out no hold 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? # Software
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_ The firmware for the controller is stored in ***esp32*** sub directory.
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

File diff suppressed because it is too large Load Diff

View File

@ -4,10 +4,10 @@ EELAYER END
$Descr A4 11693 8268 $Descr A4 11693 8268
encoding utf-8 encoding utf-8
Sheet 1 1 Sheet 1 1
Title "" Title "Plant Controller"
Date "" Date "2020-11-11"
Rev "" Rev "0.3"
Comp "" Comp "C3MA"
Comment1 "" Comment1 ""
Comment2 "" Comment2 ""
Comment3 "" Comment3 ""
@ -134,7 +134,7 @@ Text GLabel 1150 2050 0 50 Input ~ 0
PLANT6_PUMP PLANT6_PUMP
Text Notes 950 550 0 50 ~ 0 Text Notes 950 550 0 50 ~ 0
Pump Control Pump Control
Text Notes 1100 5400 0 50 ~ 0 Text Notes 2600 7500 0 50 ~ 0
Lipo Lipo
Text Notes 4050 2650 1 50 ~ 0 Text Notes 4050 2650 1 50 ~ 0
Sensors Sensors
@ -542,44 +542,44 @@ Wire Wire Line
$Comp $Comp
L Connector_Generic:Conn_01x04 J3 L Connector_Generic:Conn_01x04 J3
U 1 1 5F837F50 U 1 1 5F837F50
P 1750 3150 P 1600 2750
F 0 "J3" H 1830 3142 50 0000 L CNN F 0 "J3" H 1680 2742 50 0000 L CNN
F 1 "Conn_01x04" H 1830 3051 50 0000 L CNN F 1 "Conn_01x04" H 1680 2651 50 0000 L CNN
F 2 "misc_footprints:MT3608_module_SMT" H 1750 3150 50 0001 C CNN F 2 "misc_footprints:MT3608_module_SMT" H 1600 2750 50 0001 C CNN
F 3 "~" H 1750 3150 50 0001 C CNN F 3 "~" H 1600 2750 50 0001 C CNN
1 1750 3150 1 1600 2750
1 0 0 -1 1 0 0 -1
$EndComp $EndComp
Text GLabel 1550 3050 0 50 Input ~ 0 Text GLabel 1400 2650 0 50 Input ~ 0
PWR_PUMP_CONVERTER PWR_PUMP_CONVERTER
Text GLabel 1550 3150 0 50 Input ~ 0 Text GLabel 1400 2750 0 50 Input ~ 0
GND GND
Text GLabel 1550 3250 0 50 Input ~ 0 Text GLabel 1400 2850 0 50 Input ~ 0
GND GND
Text GLabel 1550 3350 0 50 Input ~ 0 Text GLabel 1400 2950 0 50 Input ~ 0
PUMP_PWR PUMP_PWR
$Comp $Comp
L LP38690DT-3.3:LP38690DT-3.3 U3 L LP38690DT-3.3:LP38690DT-3.3 U3
U 1 1 5F84FA14 U 1 1 5F84FA14
P 1400 5300 P 2900 7400
F 0 "U3" H 1400 5665 50 0000 C CNN F 0 "U3" H 2900 7765 50 0000 C CNN
F 1 "LP38690DT-3.3" H 1400 5574 50 0000 C CNN F 1 "LP38690DT-3.3" H 2900 7674 50 0000 C CNN
F 2 "ESP32:DPAK457P991X255-3N" H 1400 5300 50 0001 L BNN F 2 "ESP32:DPAK457P991X255-3N" H 2900 7400 50 0001 L BNN
F 3 "IPC-7351B" H 1400 5300 50 0001 L BNN F 3 "IPC-7351B" H 2900 7400 50 0001 L BNN
F 4 "Texas Instruments" H 1400 5300 50 0001 L BNN "Field4" F 4 "Texas Instruments" H 2900 7400 50 0001 L BNN "Field4"
F 5 "M" H 1400 5300 50 0001 L BNN "Field5" F 5 "M" H 2900 7400 50 0001 L BNN "Field5"
F 6 "2.55mm" H 1400 5300 50 0001 L BNN "Field6" F 6 "2.55mm" H 2900 7400 50 0001 L BNN "Field6"
1 1400 5300 1 2900 7400
1 0 0 -1 1 0 0 -1
$EndComp $EndComp
Text GLabel 2000 5400 2 50 Input ~ 0 Text GLabel 3500 7500 2 50 Input ~ 0
GND GND
Text GLabel 2000 5200 2 50 Input ~ 0 Text GLabel 3500 7300 2 50 Input ~ 0
3_3V 3_3V
Text GLabel 4150 5700 0 50 Input ~ 0 Text GLabel 4150 5700 0 50 Input ~ 0
3_3V 3_3V
NoConn ~ 4200 7500 NoConn ~ 4200 7500
Text GLabel 800 5200 0 50 Input ~ 0 Text GLabel 2300 7300 0 50 Input ~ 0
LIPO+ LIPO+
Wire Wire Line Wire Wire Line
10550 6100 10700 6100 10550 6100 10700 6100
@ -1004,37 +1004,28 @@ CUSTOM_GPIO
$Comp $Comp
L Connector:Conn_01x02_Female J4 L Connector:Conn_01x02_Female J4
U 1 1 5F8D742C U 1 1 5F8D742C
P 1300 4100 P 3050 4300
F 0 "J4" H 1328 4076 50 0000 L CNN F 0 "J4" H 3078 4276 50 0000 L CNN
F 1 "Conn_01x02_Female" H 1000 3900 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 1300 4100 50 0001 C CNN F 2 "misc_footprints:BatteryHolder_Keystone_1042_1x18650" H 3050 4300 50 0001 C CNN
F 3 "~" H 1300 4100 50 0001 C CNN F 3 "~" H 3050 4300 50 0001 C CNN
1 1300 4100 1 3050 4300
1 0 0 -1 1 0 0 -1
$EndComp $EndComp
Text Notes 1200 4000 0 50 ~ 0 Text Notes 1300 3850 0 50 ~ 0
Fuse for Lipo Protection for Lipo
Text GLabel 900 4200 0 50 Input ~ 0
GND
Wire Wire Line
900 4200 950 4200
$Comp $Comp
L power:GND #PWR0120 L power:GND #PWR0120
U 1 1 5F95011F U 1 1 5F95011F
P 950 4450 P 2000 5200
F 0 "#PWR0120" H 950 4200 50 0001 C CNN F 0 "#PWR0120" H 2000 4950 50 0001 C CNN
F 1 "GND" H 955 4277 50 0000 C CNN F 1 "GND" H 2005 5027 50 0000 C CNN
F 2 "" H 950 4450 50 0001 C CNN F 2 "" H 2000 5200 50 0001 C CNN
F 3 "" H 950 4450 50 0001 C CNN F 3 "" H 2000 5200 50 0001 C CNN
1 950 4450 1 2000 5200
1 0 0 -1 1 0 0 -1
$EndComp $EndComp
Wire Wire Line Text Notes 750 2550 0 50 ~ 0
950 4450 950 4200
Connection ~ 950 4200
Wire Wire Line
950 4200 1100 4200
Text Notes 900 2950 0 50 ~ 0
Pump Voltage Converter Pump Voltage Converter
Wire Wire Line Wire Wire Line
11050 6250 11050 6300 11050 6250 11050 6300
@ -1049,21 +1040,21 @@ F 3 "~" H 3050 2550 50 0001 C CNN
1 3050 2550 1 3050 2550
1 0 0 -1 1 0 0 -1
$EndComp $EndComp
Text GLabel 2350 2750 0 50 Input ~ 0 Text GLabel 2500 2750 0 50 Input ~ 0
PWR_SENSORS PWR_SENSORS
Text GLabel 2350 2650 0 50 Input ~ 0 Text GLabel 2500 2650 0 50 Input ~ 0
TANK_TRIGGER TANK_TRIGGER
Text GLabel 2350 2550 0 50 Input ~ 0 Text GLabel 2500 2550 0 50 Input ~ 0
TANK_ECHO TANK_ECHO
$Comp $Comp
L power:GND #PWR0121 L power:GND #PWR0121
U 1 1 5F9F8100 U 1 1 5F9F8100
P 2850 2450 P 2500 2450
F 0 "#PWR0121" H 2850 2200 50 0001 C CNN F 0 "#PWR0121" H 2500 2200 50 0001 C CNN
F 1 "GND" V 2855 2322 50 0000 R CNN F 1 "GND" V 2505 2322 50 0000 R CNN
F 2 "" H 2850 2450 50 0001 C CNN F 2 "" H 2500 2450 50 0001 C CNN
F 3 "" H 2850 2450 50 0001 C CNN F 3 "" H 2500 2450 50 0001 C CNN
1 2850 2450 1 2500 2450
0 1 1 0 0 1 1 0
$EndComp $EndComp
Wire Wire Line Wire Wire Line
@ -1082,15 +1073,14 @@ $EndComp
$Comp $Comp
L power:PWR_FLAG #FLG0101 L power:PWR_FLAG #FLG0101
U 1 1 5FA3662A U 1 1 5FA3662A
P 950 4450 P 2000 5200
F 0 "#FLG0101" H 950 4525 50 0001 C CNN F 0 "#FLG0101" H 2000 5275 50 0001 C CNN
F 1 "PWR_FLAG" V 950 4578 50 0000 L CNN F 1 "PWR_FLAG" H 1900 5350 50 0000 L CNN
F 2 "" H 950 4450 50 0001 C CNN F 2 "" H 2000 5200 50 0001 C CNN
F 3 "~" H 950 4450 50 0001 C CNN F 3 "~" H 2000 5200 50 0001 C CNN
1 950 4450 1 2000 5200
0 1 1 0 1 0 0 -1
$EndComp $EndComp
Connection ~ 950 4450
Text GLabel 5900 6800 2 50 Input ~ 0 Text GLabel 5900 6800 2 50 Input ~ 0
SENSORS_ENABLE SENSORS_ENABLE
$Comp $Comp
@ -2391,44 +2381,31 @@ $EndComp
Connection ~ 10700 5400 Connection ~ 10700 5400
Wire Wire Line Wire Wire Line
10700 5400 10700 5550 10700 5400 10700 5550
Wire Wire Line Text GLabel 1650 3600 0 50 Input ~ 0
1000 3500 1550 3500
Text GLabel 1550 3800 0 50 Input ~ 0
SOLAR_IN SOLAR_IN
Text GLabel 1550 3600 0 50 Input ~ 0
GND
Text GLabel 1550 3700 0 50 Input ~ 0
GND
$Comp $Comp
L Connector_Generic:Conn_01x04 J2 L Connector_Generic:Conn_01x04 J2
U 1 1 5F7E5709 U 1 1 5F7E5709
P 1750 3600 P 1850 3400
F 0 "J2" H 1830 3592 50 0000 L CNN F 0 "J2" H 1930 3392 50 0000 L CNN
F 1 "Conn_01x04" H 1830 3501 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 1750 3600 50 0001 C CNN F 2 "Connector_PinSocket_2.54mm:PinSocket_1x04_P2.54mm_Horizontal" H 1850 3400 50 0001 C CNN
F 3 "~" H 1750 3600 50 0001 C CNN F 3 "~" H 1850 3400 50 0001 C CNN
1 1750 3600 1 1850 3400
1 0 0 -1 1 0 0 -1
$EndComp $EndComp
Wire Wire Line
1000 4100 1100 4100
Connection ~ 1000 3800
Wire Wire Line
1000 3800 1000 3500
$Comp $Comp
L Device:Fuse F1 L Device:Fuse F1
U 1 1 5F8D8528 U 1 1 5F8D8528
P 1000 3950 P 1150 3300
F 0 "F1" H 1060 3996 50 0000 L CNN F 0 "F1" V 1200 3250 50 0000 L CNN
F 1 "Fuse" H 1060 3905 50 0000 L CNN F 1 "Fuse" V 1050 3200 50 0000 L CNN
F 2 "Fuse:Fuse_Blade_ATO_directSolder" V 930 3950 50 0001 C CNN F 2 "Fuse:Fuse_Blade_ATO_directSolder" V 1080 3300 50 0001 C CNN
F 3 "~" H 1000 3950 50 0001 C CNN F 3 "~" H 1150 3300 50 0001 C CNN
1 1000 3950 1 1150 3300
1 0 0 -1 0 1 1 0
$EndComp $EndComp
Wire Wire Line Text GLabel 1250 3450 0 50 Input ~ 0
1000 3800 850 3800
Text GLabel 850 3800 0 50 Input ~ 0
LIPO+ LIPO+
$Comp $Comp
L power:GND #PWR0138 L power:GND #PWR0138
@ -2696,29 +2673,29 @@ Text GLabel 8200 4850 1 50 Input ~ 0
Wire Wire Line Wire Wire Line
8200 4850 8200 4950 8200 4850 8200 4950
Wire Wire Line Wire Wire Line
2350 2550 2400 2550 2500 2550 2550 2550
Wire Wire Line Wire Wire Line
2350 2650 2600 2650 2500 2650 2750 2650
$Comp $Comp
L Device:D_Schottky D8 L Device:D_Schottky D8
U 1 1 5FA219F6 U 1 1 5FA219F6
P 2400 2300 P 2550 2250
F 0 "D8" V 2446 2221 50 0000 R CNN F 0 "D8" V 2596 2171 50 0000 R CNN
F 1 "BAS40" V 2355 2221 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 2400 2300 50 0001 C CNN F 2 "Diode_SMD:D_1206_3216Metric_Pad1.42x1.75mm_HandSolder" H 2550 2250 50 0001 C CNN
F 3 "~" H 2400 2300 50 0001 C CNN F 3 "~" H 2550 2250 50 0001 C CNN
1 2400 2300 1 2550 2250
0 1 1 0 0 1 1 0
$EndComp $EndComp
$Comp $Comp
L Device:D_Schottky D10 L Device:D_Schottky D10
U 1 1 5FA22159 U 1 1 5FA22159
P 2600 2300 P 2750 2250
F 0 "D10" V 2646 2221 50 0000 R CNN F 0 "D10" V 2796 2171 50 0000 R CNN
F 1 "BAS40" V 2555 2221 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 2600 2300 50 0001 C CNN F 2 "Diode_SMD:D_1206_3216Metric_Pad1.42x1.75mm_HandSolder" H 2750 2250 50 0001 C CNN
F 3 "~" H 2600 2300 50 0001 C CNN F 3 "~" H 2750 2250 50 0001 C CNN
1 2600 2300 1 2750 2250
0 1 1 0 0 1 1 0
$EndComp $EndComp
Wire Wire Line Wire Wire Line
@ -2728,77 +2705,64 @@ Wire Wire Line
Connection ~ 6200 5800 Connection ~ 6200 5800
Wire Wire Line Wire Wire Line
6200 5800 6250 5800 6200 5800 6250 5800
Wire Wire Line
2850 2750 2350 2750
$Comp $Comp
L Device:D_Schottky D9 L Device:D_Schottky D9
U 1 1 5FA542CE U 1 1 5FA542CE
P 2400 2950 P 2550 2950
F 0 "D9" V 2446 2871 50 0000 R CNN F 0 "D9" V 2596 2871 50 0000 R CNN
F 1 "BAS40" V 2355 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 2400 2950 50 0001 C CNN F 2 "Diode_SMD:D_1206_3216Metric_Pad1.42x1.75mm_HandSolder" H 2550 2950 50 0001 C CNN
F 3 "~" H 2400 2950 50 0001 C CNN F 3 "~" H 2550 2950 50 0001 C CNN
1 2400 2950 1 2550 2950
0 1 1 0 0 1 1 0
$EndComp $EndComp
$Comp $Comp
L Device:D_Schottky D11 L Device:D_Schottky D11
U 1 1 5FA54A31 U 1 1 5FA54A31
P 2600 2950 P 2750 2950
F 0 "D11" V 2646 2871 50 0000 R CNN F 0 "D11" V 2796 2871 50 0000 R CNN
F 1 "BAS40" V 2555 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 2600 2950 50 0001 C CNN F 2 "Diode_SMD:D_1206_3216Metric_Pad1.42x1.75mm_HandSolder" H 2750 2950 50 0001 C CNN
F 3 "~" H 2600 2950 50 0001 C CNN F 3 "~" H 2750 2950 50 0001 C CNN
1 2600 2950 1 2750 2950
0 1 1 0 0 1 1 0
$EndComp $EndComp
Wire Wire Line Wire Wire Line
2400 2550 2400 2800 2550 2550 2550 2800
Connection ~ 2400 2550 Connection ~ 2550 2550
Wire Wire Line Wire Wire Line
2400 2550 2850 2550 2550 2550 2850 2550
Connection ~ 2750 2650
Wire Wire Line Wire Wire Line
2400 2450 2400 2550 2750 2650 2850 2650
Wire Wire Line Wire Wire Line
2600 2450 2600 2650 2750 2650 2750 2800
Connection ~ 2600 2650
Wire Wire Line Wire Wire Line
2600 2650 2850 2650 2550 3100 2550 3150
Wire Wire Line Wire Wire Line
2600 2650 2600 2800 2550 3150 2650 3150
Wire Wire Line Wire Wire Line
2400 3100 2400 3150 2750 3150 2750 3100
Wire Wire Line Wire Wire Line
2400 3150 2500 3150 2550 2100 2550 2050
Wire Wire Line Wire Wire Line
2600 3150 2600 3100 2750 2050 2750 2100
Wire Wire Line
2400 2150 2400 2100
Wire Wire Line
2400 2100 2500 2100
Wire Wire Line
2600 2100 2600 2150
$Comp $Comp
L power:GND #PWR0140 L power:GND #PWR0140
U 1 1 5FAD1A1F U 1 1 5FAD1A1F
P 2500 3150 P 2650 3150
F 0 "#PWR0140" H 2500 2900 50 0001 C CNN F 0 "#PWR0140" H 2650 2900 50 0001 C CNN
F 1 "GND" V 2505 3022 50 0000 R CNN F 1 "GND" V 2655 3022 50 0000 R CNN
F 2 "" H 2500 3150 50 0001 C CNN F 2 "" H 2650 3150 50 0001 C CNN
F 3 "" H 2500 3150 50 0001 C CNN F 3 "" H 2650 3150 50 0001 C CNN
1 2500 3150 1 2650 3150
1 0 0 -1 1 0 0 -1
$EndComp $EndComp
Connection ~ 2500 3150 Connection ~ 2650 3150
Wire Wire Line Wire Wire Line
2500 3150 2600 3150 2650 3150 2750 3150
Text GLabel 2500 2100 1 50 Input ~ 0 Text GLabel 2650 2000 1 50 Input ~ 0
3_3V 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 Text Notes 500 6650 0 105 ~ 0
External Fix Power External Fix Power
$Comp $Comp
@ -2861,8 +2825,6 @@ Wire Wire Line
10150 800 10200 800 10150 800 10200 800
Wire Wire Line Wire Wire Line
10200 800 10200 950 10200 800 10200 950
Wire Wire Line
10200 800 10650 800
Wire Wire Line Wire Wire Line
10650 800 10650 950 10650 800 10650 950
Connection ~ 10200 800 Connection ~ 10200 800
@ -2886,4 +2848,171 @@ Text GLabel 10150 800 0 50 Input ~ 0
PWR_SENSORS PWR_SENSORS
Text GLabel 10500 2100 2 50 Input ~ 0 Text GLabel 10500 2100 2 50 Input ~ 0
PWR_SENSORS 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 $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 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 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 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/c_cpp_properties.json
.vscode/launch.json .vscode/launch.json
.vscode/ipch .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 * Temperature
* Custom GPIO * 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 # Features
## Empires Wunschliste ## Empires Wunschliste
* Pflanze * Pflanze

View File

@ -12,7 +12,6 @@ For further details have a look at the Readme.md one level above.
# Remote Upload # Remote Upload
This script will allow you to send an OTA update to your device. This script will allow you to send an OTA update to your device.
## Installation ## Installation
@ -22,6 +21,12 @@ Requirements are:
## Usage ## Usage
See ***upload-via-mqtt.sh***
# Remote Upload - Backend
## Usage
```text ```text
usage: ota_updater.py [-h] -l BROKER_HOST -p BROKER_PORT [-u BROKER_USERNAME] usage: ota_updater.py [-h] -l BROKER_HOST -p BROKER_PORT [-u BROKER_USERNAME]
[-d BROKER_PASSWORD] [-t BASE_TOPIC] -i DEVICE_ID [-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,59 +6,102 @@
* @date 2020-05-30 * @date 2020-05-30
* *
* @copyright Copyright (c) 2020 * @copyright Copyright (c) 2020
* Describe the used PINs of the controller *
* \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 #ifndef CONTROLLER_CONFIG_H
#define CONTROLLER_CONFIG_H #define CONTROLLER_CONFIG_H
/** \addtogroup Configuration
* @{
*/
#define FIRMWARE_VERSION "1.0.9"
#define FIRMWARE_VERSION "1.0.6" #define ADC_TO_VOLT(adc) ((adc) * 3.3 ) / 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 ADC_TO_VOLT(adc) ((adc) * 3.3 ) / 4095) #define SOLAR_VOLT(adc) ADC_TO_VOLT_WITH_MULTI(adc, 4.0306) /**< 100k and 33k voltage dividor */
#define ADC_TO_VOLT_WITH_MULTI(adc, multi) (((adc) * 3.3 * (multi)) / 4095) #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 SOLAR_VOLT(adc) ADC_TO_VOLT_WITH_MULTI(adc, 4.0306) /**< 100k and 33k voltage dividor */ #define SENSOR_LIPO 34 /**< GPIO 34 (ADC1) */
#define ADC_5V_TO_3V3(adc) ADC_TO_VOLT_WITH_MULTI(adc, 1.7) /**< 33k and 47k8 voltage dividor */ #define SENSOR_SOLAR 35 /**< GPIO 35 (ADC1) */
#define MS_TO_S 1000 #define SENSOR_PLANT0 32 /**< GPIO 32 (ADC1) */
#define SENSOR_PLANT1 33 /**< GPIO 33 (ADC1) */
#define SENSOR_PLANT2 25 /**< GPIO 25 (ADC2) */
#define SENSOR_PLANT3 26 /**< GPIO 26 (ADC2) */
#define SENSOR_PLANT4 27 /**< GPIO 27 (ADC2) */
#define SENSOR_PLANT5 14 /**< GPIO 14 (ADC2) */
#define SENSOR_PLANT6 12 /**< GPIO 12 (ADC2) */
#define SENSOR_LIPO 34 /**< GPIO 34 (ADC1) */ #define OUTPUT_PUMP0 23 /**< GPIO 23 */
#define SENSOR_SOLAR 35 /**< GPIO 35 (ADC1) */ #define OUTPUT_PUMP1 22 /**< GPIO 22 */
#define SENSOR_PLANT0 32 /**< GPIO 32 (ADC1) */ #define OUTPUT_PUMP2 21 /**< GPIO 21 */
#define SENSOR_PLANT1 33 /**< GPIO 33 (ADC1) */ #define OUTPUT_PUMP3 19 /**< GPIO 19 */
#define SENSOR_PLANT2 25 /**< GPIO 25 (ADC2) */ #define OUTPUT_PUMP4 18 /**< GPIO 18 */
#define SENSOR_PLANT3 26 /**< GPIO 26 (ADC2) */ #define OUTPUT_PUMP5 5 /**< GPIO 5 */
#define SENSOR_PLANT4 27 /**< GPIO 27 (ADC2) */ #define OUTPUT_PUMP6 15 /**< GPIO 15 */
#define SENSOR_PLANT5 14 /**< GPIO 14 (ADC2) */
#define SENSOR_PLANT6 12 /**< GPIO 12 (ADC2) */
#define OUTPUT_PUMP0 23 /**< GPIO 23 */ #define OUTPUT_SENSOR 16 /**< GPIO 16 - Enable Sensors */
#define OUTPUT_PUMP1 22 /**< GPIO 22 */ #define OUTPUT_PUMP 13 /**< GPIO 13 - Enable Pumps */
#define OUTPUT_PUMP2 21 /**< GPIO 21 */
#define OUTPUT_PUMP3 19 /**< GPIO 19 */
#define OUTPUT_PUMP4 18 /**< GPIO 18 */
#define OUTPUT_PUMP5 5 /**< GPIO 5 */
#define OUTPUT_PUMP6 15 /**< GPIO 15 */
#define OUTPUT_SENSOR 16 /**< GPIO 16 - Enable Sensors */
#define OUTPUT_PUMP 13 /**< GPIO 13 - Enable Pumps */
#define SENSOR_DS18B20 2 /**< GPIO 2 */ #define SENSOR_DS18B20 2 /**< GPIO 2 - Temperatur sensor */
#define BUTTON 0 /**< GPIO 0 */ #define BUTTON 0 /**< GPIO 0 - Fix button of NodeMCU */
#define MIN_TIME_RUNNING 5UL /**< Amount of seconds the controller must stay awoken */ #define MIN_TIME_RUNNING 5UL /**< Amount of seconds the controller must stay awoken */
#define MAX_PLANTS 7 #define MAX_PLANTS 7
#define MINIMUM_LIPO_VOLT 3.6f /**< Minimum voltage of the Lipo, that must be present */ #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 NO_LIPO_VOLT 2.0f /**< No Lipo connected */
#define MINIMUM_SOLAR_VOLT 4.0f /**< Minimum voltage of the sun, to detect daylight */ #define MINIMUM_SOLAR_VOLT 4.0f /**< Minimum voltage of the sun, to detect daylight */
#define SOLAR_CHARGE_MIN_VOLTAGE 7 #define SOLAR_CHARGE_MIN_VOLTAGE 7 /**< Sun is rising (morning detected) */
#define SOLAR_CHARGE_MAX_VOLTAGE 9 #define SOLAR_CHARGE_MAX_VOLTAGE 9 /**< Sun is shining (noon) */
#define HC_SR04 /**< Ultrasonic distance sensor to measure water level */ #define HC_SR04 /**< Ultrasonic distance sensor to measure water level */
#define SENSOR_SR04_ECHO 17 /**< GPIO 17 - Echo */ #define SENSOR_SR04_ECHO 17 /**< GPIO 17 - Echo */
#define SENSOR_SR04_TRIG 23 /**< GPIO 23 - Trigger */ #define SENSOR_SR04_TRIG 23 /**< GPIO 23 - Trigger */
#define MAX_CONFIG_SETTING_ITEMS 50 /**< Parameter, that can be configured in Homie */ #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 (60 * 60 * 5U) /**< 5 hours in usecond */
#define PANIK_MODE_DEEPSLEEP_US (PANIK_MODE_DEEPSLEEP*1000*1000) #define PANIK_MODE_DEEPSLEEP_US (PANIK_MODE_DEEPSLEEP * 1000 * 1000)
#endif #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,32 +21,36 @@
#include <OneWire.h> #include <OneWire.h>
class Ds18B20 { class Ds18B20
private: {
OneWire* mDs; private:
int foundDevices; OneWire *mDs;
public: int foundDevices;
Ds18B20(int pin) {
this->mDs = new OneWire(pin);
}
~Ds18B20() { public:
delete this->mDs; Ds18B20(int pin)
} {
/** this->mDs = new OneWire(pin);
}
~Ds18B20()
{
delete this->mDs;
}
/**
* @brief read amount sensots * @brief read amount sensots
* check for available of DS18B20 sensors * check for available of DS18B20 sensors
* @return amount of sensors * @return amount of sensors
*/ */
int readDevices(void); int readDevices(void);
/** /**
* @brief Read all temperatures in celsius * @brief Read all temperatures in celsius
* *
* @param pTemperatures array of float valuies * @param pTemperatures array of float valuies
* @param maxTemperatures size of the given array * @param maxTemperatures size of the given array
* @return int amount of read temperature values * @return int amount of read temperature values
*/ */
int readAllTemperatures(float* pTemperatures, int maxTemperatures); int readAllTemperatures(float *pTemperatures, int maxTemperatures);
}; };
#endif #endif

View File

@ -1,4 +1,6 @@
/** /** \addtogroup Homie
* @{
*
* @file HomieConfiguration.h * @file HomieConfiguration.h
* @author your name (you@domain.com) * @author your name (you@domain.com)
* @brief * @brief
@ -7,6 +9,7 @@
* *
* @copyright Copyright (c) 2020 * @copyright Copyright (c) 2020
* All Settings, configurable in Homie * All Settings, configurable in Homie
*
*/ */
#ifndef HOMIE_PLANT_CONFIG_H #ifndef HOMIE_PLANT_CONFIG_H
#define HOMIE_PLANT_CONFIG_H #define HOMIE_PLANT_CONFIG_H
@ -16,54 +19,75 @@
#define MAX_PLANTS 7 #define MAX_PLANTS 7
/** /**
*********************************** Attributes ******************************* * @name Attributes
*/ * generated Information
* @{
**/
HomieNode plant0("plant0", "Plant 0", "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 plant1("plant1", "Plant 1", "Plant"); HomieNode plant2("plant2", "Plant 2", "Plant"); /**< dynamic Homie information for first plant */
HomieNode plant2("plant2", "Plant 2", "Plant"); HomieNode plant3("plant3", "Plant 3", "Plant"); /**< dynamic Homie information for first plant */
HomieNode plant3("plant3", "Plant 3", "Plant"); HomieNode plant4("plant4", "Plant 4", "Plant"); /**< dynamic Homie information for first plant */
HomieNode plant4("plant4", "Plant 4", "Plant"); HomieNode plant5("plant5", "Plant 5", "Plant"); /**< dynamic Homie information for first plant */
HomieNode plant5("plant5", "Plant 5", "Plant"); HomieNode plant6("plant6", "Plant 6", "Plant"); /**< dynamic Homie information for first plant */
HomieNode plant6("plant6", "Plant 6", "Plant");
HomieNode sensorLipo("lipo", "Battery Status", "Lipo"); HomieNode sensorLipo("lipo", "Battery Status", "Lipo");
HomieNode sensorSolar("solar", "Solar Status", "Solarpanel"); HomieNode sensorSolar("solar", "Solar Status", "Solarpanel");
HomieNode sensorWater("water", "WaterSensor", "Water"); HomieNode sensorWater("water", "WaterSensor", "Water");
HomieNode sensorTemp("temperature", "Temperature", "temperature"); 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> maxTimeBetweenMQTTUpdates("mqttSleep", "time in seconds to start into mode2");
HomieSetting<long> deepSleepNightTime("nightsleep", "time in milliseconds to sleep (0 uses same setting: deepsleep at night, too)"); 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> wateringDeepSleep("pumpdeepsleep", "time seconds to sleep, while a pump is running");
HomieSetting<long> waterLevelMax("watermaxlevel", "distance (mm) at maximum water level"); 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> waterLevelMin("waterminlevel", "distance (mm) at minimum water level (pumps still covered)");
HomieSetting<long> waterLevelWarn("waterlevelwarn", "warn (mm) if below this water level %"); HomieSetting<long> waterLevelWarn("waterlevelwarn", "warn (mm) if below this water level %");
HomieSetting<long> waterLevelVol("waterVolume", "(ml) between minimum and maximum"); HomieSetting<long> waterLevelVol("waterVolume", "(ml) between minimum and maximum");
HomieSetting<const char *> ntpServer("ntpServer", "NTP server (pool.ntp.org as default)");
/** Plant specific ones */ /**
*@}
*/
#define GENERATE_PLANT(plant, strplant) \ /**
HomieSetting<long> mSensorDry##plant = HomieSetting<long>("moistdry" strplant, "Plant " strplant "- Moist sensor dry threshold"); \ * @name Plant specific ones
HomieSetting<long> mPumpAllowedHourRangeStart##plant = HomieSetting<long>("rangehourstart" strplant, "Plant" strplant " - Range pump allowed hour start (0-23)"); \ * Setting for one plant
HomieSetting<long> mPumpAllowedHourRangeEnd##plant = HomieSetting<long>("rangehourend" strplant, "Plant" strplant " - Range pump allowed hour end (0-23)"); \ * @{
**/
#define GENERATE_PLANT(plant, strplant) \
HomieSetting<long> mSensorDry##plant = HomieSetting<long>("moistdry" strplant, "Plant " strplant "- Moist sensor dry threshold"); \
HomieSetting<long> mPumpAllowedHourRangeStart##plant = HomieSetting<long>("rangehourstart" strplant, "Plant" strplant " - Range pump allowed hour start (0-23)"); \
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<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)"); \ 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
GENERATE_PLANT(0, "0"); *
GENERATE_PLANT(1, "1"); * Feature to start pumping only at morning: @link{SOLAR_CHARGE_MIN_VOLTAGE} and @link{SOLAR_CHARGE_MAX_VOLTAGE}
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

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

View File

@ -15,17 +15,18 @@
#include "HomieTypes.h" #include "HomieTypes.h"
#include "RunningMedian.h" #include "RunningMedian.h"
class Plant { class Plant
{
private: private:
RunningMedian moistureRaw = RunningMedian(5); RunningMedian moistureRaw = RunningMedian(5);
HomieNode* mPlant = NULL; HomieNode *mPlant = NULL;
int mPinSensor=0; /**< Pin of the moist sensor */ int mPinSensor = 0; /**< Pin of the moist sensor */
int mPinPump=0; /**< Pin of the pump */ int mPinPump = 0; /**< Pin of the pump */
PlantSettings_t* mSetting;
bool mConnected = false; bool mConnected = false;
public: public:
PlantSettings_t *mSetting;
/** /**
* @brief Construct a new Plant object * @brief Construct a new Plant object
* *
@ -33,9 +34,9 @@ public:
* @param pinPump Pin of the Pump to use * @param pinPump Pin of the Pump to use
*/ */
Plant(int pinSensor, int pinPump, Plant(int pinSensor, int pinPump,
int plantId, int plantId,
HomieNode* plant, HomieNode *plant,
PlantSettings_t* setting); PlantSettings_t *setting);
void postMQTTconnection(void); void postMQTTconnection(void);
@ -46,8 +47,6 @@ public:
* *
*/ */
void addSenseValue(void); void addSenseValue(void);
int getSensorValue() { return moistureRaw.getMedian(); }
void deactivatePump(void); void deactivatePump(void);
@ -59,16 +58,42 @@ public:
* @return true * @return true
* @return false * @return false
*/ */
bool isPumpRequired() { bool isPumpRequired()
return (this->mSetting->pSensorDry != NULL) {
&& (this->moistureRaw.getMedian() > this->mSetting->pSensorDry->get()) bool isDry = getCurrentMoisture() > getSettingsMoisture();
&& (this->mSetting->pSensorDry->get() != DEACTIVATED_PLANT); 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); return mPlant->setProperty(property);
} }
bool switchHandler(const HomieRange& range, const String& value); bool switchHandler(const HomieRange &range, const String &value);
void init(void); void init(void);
@ -76,16 +101,23 @@ public:
* @brief determine, if the plant was recently casted * @brief determine, if the plant was recently casted
* @param sinceLastActivation timestamp of last time * @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 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 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(); return this->mSetting->pPumpOnlyWhenLowLight->get();
} }
}; };

View File

@ -17,13 +17,11 @@
// not tested ==> use at own risk :) // not tested ==> use at own risk :)
// #define RUNNING_MEDIAN_USE_MALLOC // #define RUNNING_MEDIAN_USE_MALLOC
// should at least be 5 to be practical, // should at least be 5 to be practical,
// odd sizes results in a 'real' middle element and will be a bit faster. // 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 // even sizes takes the average of the two middle elements as median
#define MEDIAN_MIN_SIZE 5 #define MEDIAN_MIN_SIZE 5
#define MEDIAN_MAX_SIZE 19 #define MEDIAN_MAX_SIZE 19
class RunningMedian class RunningMedian
{ {
@ -45,7 +43,7 @@ public:
float getAverage(uint8_t nMedian); float getAverage(uint8_t nMedian);
float getHighest() { return getSortedElement(_cnt - 1); }; float getHighest() { return getSortedElement(_cnt - 1); };
float getLowest() { return getSortedElement(0); }; float getLowest() { return getSortedElement(0); };
// get n'th element from the values in time order // get n'th element from the values in time order
float getElement(const uint8_t n); float getElement(const uint8_t n);
@ -58,7 +56,6 @@ public:
// returns current used elements, getCount() <= getSize() // returns current used elements, getCount() <= getSize()
uint8_t getCount() { return _cnt; }; uint8_t getCount() { return _cnt; };
protected: protected:
boolean _sorted; boolean _sorted;
uint8_t _size; uint8_t _size;
@ -66,8 +63,8 @@ protected:
uint8_t _idx; uint8_t _idx;
#ifdef RUNNING_MEDIAN_USE_MALLOC #ifdef RUNNING_MEDIAN_USE_MALLOC
float * _ar; float *_ar;
uint8_t * _p; uint8_t *_p;
#else #else
float _ar[MEDIAN_MAX_SIZE]; float _ar[MEDIAN_MAX_SIZE];
uint8_t _p[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 build_flags = -DPIO_FRAMEWORK_ARDUINO_LWIP2_LOW_MEMORY
board_build.partitions = defaultWithSmallerSpiffs.csv board_build.partitions = defaultWithSmallerSpiffs.csv
upload_port=/dev/ttyUSB0
; the latest development brankitchen-lightch (convention V3.0.x) ; the latest development brankitchen-lightch (convention V3.0.x)
lib_deps = ArduinoJson@6.16.1 lib_deps = ArduinoJson@6.16.1
https://github.com/homieiot/homie-esp8266.git#v3.0 https://github.com/homieiot/homie-esp8266.git#v3.0

View File

@ -11,46 +11,45 @@
#include "DS18B20.h" #include "DS18B20.h"
#define STARTCONV 0x44 #define STARTCONV 0x44
#define READSCRATCH 0xBE // Read EEPROM #define READSCRATCH 0xBE // Read EEPROM
#define TEMP_LSB 0 #define TEMP_LSB 0
#define TEMP_MSB 1 #define TEMP_MSB 1
#define SCRATCHPADSIZE 9 #define SCRATCHPADSIZE 9
#define OFFSET_CRC8 8 /**< 9th byte has the CRC of the complete data */ #define OFFSET_CRC8 8 /**< 9th byte has the CRC of the complete data */
//Printf debugging //Printf debugging
//#define DS_DEBUG //#define DS_DEBUG
int Ds18B20::readDevices() { int Ds18B20::readDevices()
{
byte addr[8]; byte addr[8];
int amount = -1; int amount = -1;
while (this->mDs->search(addr)) { while (this->mDs->search(addr))
{
amount++; amount++;
} }
this->mDs->reset_search(); this->mDs->reset_search();
return amount; return amount;
} }
int Ds18B20::readAllTemperatures(float* pTemperatures, int maxTemperatures) { int Ds18B20::readAllTemperatures(float *pTemperatures, int maxTemperatures)
{
byte addr[8]; byte addr[8];
uint8_t scratchPad[SCRATCHPADSIZE]; uint8_t scratchPad[SCRATCHPADSIZE];
int currentTemp = 0; int currentTemp = 0;
#ifdef DS_DEBUG
int i;
#endif
while (this->mDs->search(addr)) { 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
this->mDs->reset(); this->mDs->reset();
this->mDs->select(addr); this->mDs->select(addr);
this->mDs->write(STARTCONV); this->mDs->write(STARTCONV);
}
delay(750);
while (this->mDs->search(addr))
{
this->mDs->reset(); this->mDs->reset();
this->mDs->select(addr); this->mDs->select(addr);
this->mDs->write(READSCRATCH); this->mDs->write(READSCRATCH);
@ -68,36 +67,32 @@ int Ds18B20::readAllTemperatures(float* pTemperatures, int maxTemperatures) {
// byte 7: DS18S20: COUNT_PER_C // byte 7: DS18S20: COUNT_PER_C
// DS18B20 & DS1822: store for crc // DS18B20 & DS1822: store for crc
// byte 8: SCRATCHPAD_CRC // byte 8: SCRATCHPAD_CRC
#ifdef DS_DEBUG for (uint8_t i = 0; i < 9; i++)
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++) {
scratchPad[i] = this->mDs->read(); scratchPad[i] = this->mDs->read();
} }
uint8_t crc8 = this->mDs->crc8(scratchPad, 8); uint8_t crc8 = this->mDs->crc8(scratchPad, 8);
/* Only work an valid data */ /* Only work an valid data */
if (crc8 == scratchPad[OFFSET_CRC8]) { if (crc8 == scratchPad[OFFSET_CRC8])
int16_t fpTemperature = (((int16_t) scratchPad[TEMP_MSB]) << 11) {
| (((int16_t) scratchPad[TEMP_LSB]) << 3); int16_t fpTemperature = (((int16_t)scratchPad[TEMP_MSB]) << 11) | (((int16_t)scratchPad[TEMP_LSB]) << 3);
float celsius = (float) fpTemperature * 0.0078125; float celsius = (float)fpTemperature * 0.0078125;
#ifdef DS_DEBUG #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 #endif
/* check, if the buffer as some space for our data */ /* check, if the buffer as some space for our data */
if (currentTemp < maxTemperatures) { if (currentTemp < maxTemperatures)
{
pTemperatures[currentTemp] = celsius; pTemperatures[currentTemp] = celsius;
} else { }
else
{
return -1; return -1;
} }
} }
currentTemp++; currentTemp++;
} }
this->mDs->reset(); this->mDs->reset();
#ifdef DS_DEBUG #ifdef DS_DEBUG
Serial.println(" No more addresses."); Serial.println(" No more addresses.");

View File

@ -11,77 +11,87 @@
*/ */
#include "PlantCtrl.h" #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->mPinSensor = pinSensor;
this->mPinPump = pinPump; this->mPinPump = pinPump;
this->mPlant = plant; this->mPlant = plant;
this->mSetting = setting; this->mSetting = setting;
} }
void Plant::init(void) { void Plant::init(void)
{
/* Initialize Home Settings validator */ /* Initialize Home Settings validator */
this->mSetting->pSensorDry->setDefaultValue(DEACTIVATED_PLANT); this->mSetting->pSensorDry->setDefaultValue(DEACTIVATED_PLANT);
this->mSetting->pSensorDry->setValidator([] (long candidate) { this->mSetting->pSensorDry->setValidator([](long candidate) {
return (((candidate >= 0) && (candidate <= 4095) ) || candidate == DEACTIVATED_PLANT); return (((candidate >= 0) && (candidate <= 4095)) || candidate == DEACTIVATED_PLANT);
}); });
this->mSetting->pPumpAllowedHourRangeStart->setDefaultValue(8); // start at 8:00 this->mSetting->pPumpAllowedHourRangeStart->setDefaultValue(8); // start at 8:00
this->mSetting->pPumpAllowedHourRangeStart->setValidator([] (long candidate) { this->mSetting->pPumpAllowedHourRangeStart->setValidator([](long candidate) {
return ((candidate >= 0) && (candidate <= 23) ); return ((candidate >= 0) && (candidate <= 23));
}); });
this->mSetting->pPumpAllowedHourRangeEnd->setDefaultValue(20); // stop pumps at 20:00 this->mSetting->pPumpAllowedHourRangeEnd->setDefaultValue(20); // stop pumps at 20:00
this->mSetting->pPumpAllowedHourRangeEnd->setValidator([] (long candidate) { this->mSetting->pPumpAllowedHourRangeEnd->setValidator([](long candidate) {
return ((candidate >= 0) && (candidate <= 23) ); return ((candidate >= 0) && (candidate <= 23));
}); });
this->mSetting->pPumpOnlyWhenLowLight->setDefaultValue(true); this->mSetting->pPumpOnlyWhenLowLight->setDefaultValue(true);
this->mSetting->pPumpCooldownInHours->setDefaultValue(20); // minutes this->mSetting->pPumpCooldownInHours->setDefaultValue(20); // minutes
this->mSetting->pPumpCooldownInHours->setValidator([] (long candidate) { this->mSetting->pPumpCooldownInHours->setValidator([](long candidate) {
return ((candidate >= 0) && (candidate <= 1024) ); return ((candidate >= 0) && (candidate <= 1024));
}); });
/* Initialize Hardware */ /* Initialize Hardware */
pinMode(this->mPinPump, OUTPUT); pinMode(this->mPinPump, OUTPUT);
pinMode(this->mPinSensor, ANALOG); pinMode(this->mPinSensor, ANALOG);
digitalWrite(this->mPinPump, LOW); digitalWrite(this->mPinPump, LOW);
} }
void Plant::addSenseValue(void) { void Plant::addSenseValue(void)
this->moistureRaw.add( analogRead(this->mPinSensor) ); {
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"); const String OFF = String("OFF");
this->mConnected=true; this->mConnected = true;
this->mPlant->setProperty("switch").send(OFF); this->mPlant->setProperty("switch").send(OFF);
} }
void Plant::deactivatePump(void) { void Plant::deactivatePump(void)
{
digitalWrite(this->mPinPump, LOW); digitalWrite(this->mPinPump, LOW);
if (this->mConnected) { if (this->mConnected)
{
const String OFF = String("OFF"); const String OFF = String("OFF");
this->mPlant->setProperty("switch").send(OFF); this->mPlant->setProperty("switch").send(OFF);
} }
} }
void Plant::activatePump(void) { void Plant::activatePump(void)
{
digitalWrite(this->mPinPump, HIGH); digitalWrite(this->mPinPump, HIGH);
if (this->mConnected) { if (this->mConnected)
{
const String OFF = String("ON"); const String OFF = String("ON");
this->mPlant->setProperty("switch").send(OFF); this->mPlant->setProperty("switch").send(OFF);
} }
} }
void Plant::advertise(void) { void Plant::advertise(void)
{
// Advertise topics // Advertise topics
this->mPlant->advertise("switch").setName("Pump 1") this->mPlant->advertise("switch").setName("Pump 1").setDatatype("boolean");
.setDatatype("boolean");
//FIXME add .settable(this->switchHandler) //FIXME add .settable(this->switchHandler)
this->mPlant->advertise("moist").setName("Percent") this->mPlant->advertise("moist").setName("Percent").setDatatype("number").setUnit("%");
.setDatatype("number") this->mPlant->advertise("moistraw").setName("adc").setDatatype("number").setUnit("3.3/4096V");
.setUnit("%");
} }
/* FIXME /* FIXME
bool Plant::switchHandler(const HomieRange& range, const String& value) { bool Plant::switchHandler(const HomieRange& range, const String& value) {
if (range.isRange) return false; // only one switch is present 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); _size = constrain(size, MEDIAN_MIN_SIZE, MEDIAN_MAX_SIZE);
#ifdef RUNNING_MEDIAN_USE_MALLOC #ifdef RUNNING_MEDIAN_USE_MALLOC
_ar = (float *) malloc(_size * sizeof(float)); _ar = (float *)malloc(_size * sizeof(float));
_p = (uint8_t *) malloc(_size * sizeof(uint8_t)); _p = (uint8_t *)malloc(_size * sizeof(uint8_t));
#endif #endif
clear(); clear();
@ -63,18 +63,22 @@ void RunningMedian::clear()
void RunningMedian::add(float value) void RunningMedian::add(float value)
{ {
_ar[_idx++] = value; _ar[_idx++] = value;
if (_idx >= _size) _idx = 0; // wrap around if (_idx >= _size)
if (_cnt < _size) _cnt++; _idx = 0; // wrap around
if (_cnt < _size)
_cnt++;
_sorted = false; _sorted = false;
} }
float RunningMedian::getMedian() 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? if (_cnt & 0x01) // is it odd sized?
{ {
return _ar[_p[_cnt / 2]]; return _ar[_p[_cnt / 2]];
} }
@ -83,7 +87,8 @@ float RunningMedian::getMedian()
float RunningMedian::getAverage() float RunningMedian::getAverage()
{ {
if (_cnt == 0) return NAN; if (_cnt == 0)
return NAN;
float sum = 0; float sum = 0;
for (uint8_t i = 0; i < _cnt; i++) for (uint8_t i = 0; i < _cnt; i++)
@ -95,13 +100,16 @@ float RunningMedian::getAverage()
float RunningMedian::getAverage(uint8_t nMedians) 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 start = ((_cnt - nMedians) / 2);
uint8_t stop = start + nMedians; uint8_t stop = start + nMedians;
if (_sorted == false) sort(); if (_sorted == false)
sort();
float sum = 0; float sum = 0;
for (uint8_t i = start; i < stop; i++) 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) 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; uint8_t pos = _idx + n;
if (pos >= _cnt) // faster than % if (pos >= _cnt) // faster than %
@ -125,18 +134,21 @@ float RunningMedian::getElement(const uint8_t n)
float RunningMedian::getSortedElement(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]]; return _ar[_p[n]];
} }
// n can be max <= half the (filled) size // n can be max <= half the (filled) size
float RunningMedian::predict(const uint8_t n) 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 ! float med = getMedian(); // takes care of sorting !
if (_cnt & 0x01) if (_cnt & 0x01)
{ {
return max(med - _ar[_p[_cnt / 2 - n]], _ar[_p[_cnt / 2 + n]] - med); return max(med - _ar[_p[_cnt / 2 - n]], _ar[_p[_cnt / 2 + n]] - med);
@ -162,7 +174,8 @@ void RunningMedian::sort()
flag = false; flag = false;
} }
} }
if (flag) break; if (flag)
break;
} }
_sorted = true; _sorted = true;
} }

View File

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