ESP32 based project
This commit is contained in:
parent
b6d3f96239
commit
c8ebe2a6bc
35
board/.gitignore
vendored
Normal file
35
board/.gitignore
vendored
Normal file
@ -0,0 +1,35 @@
|
||||
# Software code stuff
|
||||
*.swp
|
||||
*.o
|
||||
*.hex
|
||||
*.lst
|
||||
*.eep
|
||||
*.sym
|
||||
*.map
|
||||
*.lss
|
||||
*.elf
|
||||
.dep/
|
||||
# KiCAD board stuff
|
||||
# export files for BOM
|
||||
*.csv
|
||||
*.tsv
|
||||
*.xml
|
||||
# backup files
|
||||
*.bak
|
||||
# Temporary files
|
||||
*.000
|
||||
*.bak
|
||||
*.bck
|
||||
*.kicad_pcb-bak
|
||||
*~
|
||||
_autosave-*
|
||||
*.tmp
|
||||
*-cache.lib
|
||||
*-rescue.lib
|
||||
*-save.pro
|
||||
*-save.kicad_pcb
|
||||
|
||||
# Netlist files (exported from Eeschema)
|
||||
*.net
|
||||
|
||||
# Autorouter files (exported from Pcbnew)
|
3476
board/PlantCtrlESP32.kicad_pcb
Normal file
3476
board/PlantCtrlESP32.kicad_pcb
Normal file
File diff suppressed because it is too large
Load Diff
281
board/PlantCtrlESP32.pro
Normal file
281
board/PlantCtrlESP32.pro
Normal file
@ -0,0 +1,281 @@
|
||||
update=Mi 26 Aug 2020 18:08:55 CEST
|
||||
version=1
|
||||
last_client=kicad
|
||||
[general]
|
||||
version=1
|
||||
RootSch=
|
||||
BoardNm=
|
||||
[cvpcb]
|
||||
version=1
|
||||
NetIExt=net
|
||||
[eeschema]
|
||||
version=1
|
||||
LibDir=
|
||||
[eeschema/libraries]
|
||||
[pcbnew]
|
||||
version=1
|
||||
PageLayoutDescrFile=
|
||||
LastNetListRead=PlantCtrlESP32.net
|
||||
CopperLayerCount=2
|
||||
BoardThickness=1.6
|
||||
AllowMicroVias=0
|
||||
AllowBlindVias=0
|
||||
RequireCourtyardDefinitions=0
|
||||
ProhibitOverlappingCourtyards=1
|
||||
MinTrackWidth=0.2
|
||||
MinViaDiameter=0.4
|
||||
MinViaDrill=0.3
|
||||
MinMicroViaDiameter=0.2
|
||||
MinMicroViaDrill=0.09999999999999999
|
||||
MinHoleToHole=0.25
|
||||
TrackWidth1=1.2
|
||||
ViaDiameter1=0.8
|
||||
ViaDrill1=0.4
|
||||
dPairWidth1=0.2
|
||||
dPairGap1=0.25
|
||||
dPairViaGap1=0.25
|
||||
SilkLineWidth=0.12
|
||||
SilkTextSizeV=1
|
||||
SilkTextSizeH=1
|
||||
SilkTextSizeThickness=0.15
|
||||
SilkTextItalic=0
|
||||
SilkTextUpright=1
|
||||
CopperLineWidth=0.2
|
||||
CopperTextSizeV=1.5
|
||||
CopperTextSizeH=1.5
|
||||
CopperTextThickness=0.3
|
||||
CopperTextItalic=0
|
||||
CopperTextUpright=1
|
||||
EdgeCutLineWidth=0.05
|
||||
CourtyardLineWidth=0.05
|
||||
OthersLineWidth=0.15
|
||||
OthersTextSizeV=1
|
||||
OthersTextSizeH=1
|
||||
OthersTextSizeThickness=0.15
|
||||
OthersTextItalic=0
|
||||
OthersTextUpright=1
|
||||
SolderMaskClearance=0.051
|
||||
SolderMaskMinWidth=0.25
|
||||
SolderPasteClearance=0
|
||||
SolderPasteRatio=-0
|
||||
[pcbnew/Layer.F.Cu]
|
||||
Name=F.Cu
|
||||
Type=0
|
||||
Enabled=1
|
||||
[pcbnew/Layer.In1.Cu]
|
||||
Name=In1.Cu
|
||||
Type=0
|
||||
Enabled=0
|
||||
[pcbnew/Layer.In2.Cu]
|
||||
Name=In2.Cu
|
||||
Type=0
|
||||
Enabled=0
|
||||
[pcbnew/Layer.In3.Cu]
|
||||
Name=In3.Cu
|
||||
Type=0
|
||||
Enabled=0
|
||||
[pcbnew/Layer.In4.Cu]
|
||||
Name=In4.Cu
|
||||
Type=0
|
||||
Enabled=0
|
||||
[pcbnew/Layer.In5.Cu]
|
||||
Name=In5.Cu
|
||||
Type=0
|
||||
Enabled=0
|
||||
[pcbnew/Layer.In6.Cu]
|
||||
Name=In6.Cu
|
||||
Type=0
|
||||
Enabled=0
|
||||
[pcbnew/Layer.In7.Cu]
|
||||
Name=In7.Cu
|
||||
Type=0
|
||||
Enabled=0
|
||||
[pcbnew/Layer.In8.Cu]
|
||||
Name=In8.Cu
|
||||
Type=0
|
||||
Enabled=0
|
||||
[pcbnew/Layer.In9.Cu]
|
||||
Name=In9.Cu
|
||||
Type=0
|
||||
Enabled=0
|
||||
[pcbnew/Layer.In10.Cu]
|
||||
Name=In10.Cu
|
||||
Type=0
|
||||
Enabled=0
|
||||
[pcbnew/Layer.In11.Cu]
|
||||
Name=In11.Cu
|
||||
Type=0
|
||||
Enabled=0
|
||||
[pcbnew/Layer.In12.Cu]
|
||||
Name=In12.Cu
|
||||
Type=0
|
||||
Enabled=0
|
||||
[pcbnew/Layer.In13.Cu]
|
||||
Name=In13.Cu
|
||||
Type=0
|
||||
Enabled=0
|
||||
[pcbnew/Layer.In14.Cu]
|
||||
Name=In14.Cu
|
||||
Type=0
|
||||
Enabled=0
|
||||
[pcbnew/Layer.In15.Cu]
|
||||
Name=In15.Cu
|
||||
Type=0
|
||||
Enabled=0
|
||||
[pcbnew/Layer.In16.Cu]
|
||||
Name=In16.Cu
|
||||
Type=0
|
||||
Enabled=0
|
||||
[pcbnew/Layer.In17.Cu]
|
||||
Name=In17.Cu
|
||||
Type=0
|
||||
Enabled=0
|
||||
[pcbnew/Layer.In18.Cu]
|
||||
Name=In18.Cu
|
||||
Type=0
|
||||
Enabled=0
|
||||
[pcbnew/Layer.In19.Cu]
|
||||
Name=In19.Cu
|
||||
Type=0
|
||||
Enabled=0
|
||||
[pcbnew/Layer.In20.Cu]
|
||||
Name=In20.Cu
|
||||
Type=0
|
||||
Enabled=0
|
||||
[pcbnew/Layer.In21.Cu]
|
||||
Name=In21.Cu
|
||||
Type=0
|
||||
Enabled=0
|
||||
[pcbnew/Layer.In22.Cu]
|
||||
Name=In22.Cu
|
||||
Type=0
|
||||
Enabled=0
|
||||
[pcbnew/Layer.In23.Cu]
|
||||
Name=In23.Cu
|
||||
Type=0
|
||||
Enabled=0
|
||||
[pcbnew/Layer.In24.Cu]
|
||||
Name=In24.Cu
|
||||
Type=0
|
||||
Enabled=0
|
||||
[pcbnew/Layer.In25.Cu]
|
||||
Name=In25.Cu
|
||||
Type=0
|
||||
Enabled=0
|
||||
[pcbnew/Layer.In26.Cu]
|
||||
Name=In26.Cu
|
||||
Type=0
|
||||
Enabled=0
|
||||
[pcbnew/Layer.In27.Cu]
|
||||
Name=In27.Cu
|
||||
Type=0
|
||||
Enabled=0
|
||||
[pcbnew/Layer.In28.Cu]
|
||||
Name=In28.Cu
|
||||
Type=0
|
||||
Enabled=0
|
||||
[pcbnew/Layer.In29.Cu]
|
||||
Name=In29.Cu
|
||||
Type=0
|
||||
Enabled=0
|
||||
[pcbnew/Layer.In30.Cu]
|
||||
Name=In30.Cu
|
||||
Type=0
|
||||
Enabled=0
|
||||
[pcbnew/Layer.B.Cu]
|
||||
Name=B.Cu
|
||||
Type=0
|
||||
Enabled=1
|
||||
[pcbnew/Layer.B.Adhes]
|
||||
Enabled=1
|
||||
[pcbnew/Layer.F.Adhes]
|
||||
Enabled=1
|
||||
[pcbnew/Layer.B.Paste]
|
||||
Enabled=1
|
||||
[pcbnew/Layer.F.Paste]
|
||||
Enabled=1
|
||||
[pcbnew/Layer.B.SilkS]
|
||||
Enabled=1
|
||||
[pcbnew/Layer.F.SilkS]
|
||||
Enabled=1
|
||||
[pcbnew/Layer.B.Mask]
|
||||
Enabled=1
|
||||
[pcbnew/Layer.F.Mask]
|
||||
Enabled=1
|
||||
[pcbnew/Layer.Dwgs.User]
|
||||
Enabled=1
|
||||
[pcbnew/Layer.Cmts.User]
|
||||
Enabled=1
|
||||
[pcbnew/Layer.Eco1.User]
|
||||
Enabled=1
|
||||
[pcbnew/Layer.Eco2.User]
|
||||
Enabled=1
|
||||
[pcbnew/Layer.Edge.Cuts]
|
||||
Enabled=1
|
||||
[pcbnew/Layer.Margin]
|
||||
Enabled=1
|
||||
[pcbnew/Layer.B.CrtYd]
|
||||
Enabled=1
|
||||
[pcbnew/Layer.F.CrtYd]
|
||||
Enabled=1
|
||||
[pcbnew/Layer.B.Fab]
|
||||
Enabled=1
|
||||
[pcbnew/Layer.F.Fab]
|
||||
Enabled=1
|
||||
[pcbnew/Layer.Rescue]
|
||||
Enabled=0
|
||||
[pcbnew/Netclasses]
|
||||
[pcbnew/Netclasses/Default]
|
||||
Name=Default
|
||||
Clearance=0.2
|
||||
TrackWidth=1.2
|
||||
ViaDiameter=0.8
|
||||
ViaDrill=0.4
|
||||
uViaDiameter=0.3
|
||||
uViaDrill=0.1
|
||||
dPairWidth=0.2
|
||||
dPairGap=0.25
|
||||
dPairViaGap=0.25
|
||||
[pcbnew/Netclasses/1]
|
||||
Name=5V
|
||||
Clearance=0.2
|
||||
TrackWidth=1.4
|
||||
ViaDiameter=0.8
|
||||
ViaDrill=0.4
|
||||
uViaDiameter=0.3
|
||||
uViaDrill=0.1
|
||||
dPairWidth=0.2
|
||||
dPairGap=0.25
|
||||
dPairViaGap=0.25
|
||||
[pcbnew/Netclasses/2]
|
||||
Name=Mini
|
||||
Clearance=0.2
|
||||
TrackWidth=1
|
||||
ViaDiameter=0.8
|
||||
ViaDrill=0.4
|
||||
uViaDiameter=0.3
|
||||
uViaDrill=0.1
|
||||
dPairWidth=0.2
|
||||
dPairGap=0.25
|
||||
dPairViaGap=0.25
|
||||
[pcbnew/Netclasses/3]
|
||||
Name=Power
|
||||
Clearance=0.2
|
||||
TrackWidth=1.7
|
||||
ViaDiameter=0.8
|
||||
ViaDrill=0.4
|
||||
uViaDiameter=0.3
|
||||
uViaDrill=0.1
|
||||
dPairWidth=0.2
|
||||
dPairGap=0.25
|
||||
dPairViaGap=0.25
|
||||
[schematic_editor]
|
||||
version=1
|
||||
PageLayoutDescrFile=
|
||||
PlotDirectoryName=/tmp/
|
||||
SubpartIdSeparator=0
|
||||
SubpartFirstId=65
|
||||
NetFmtName=Pcbnew
|
||||
SpiceAjustPassiveValues=0
|
||||
LabSize=50
|
||||
ERC_TestSimilarLabels=1
|
1198
board/PlantCtrlESP32.sch
Normal file
1198
board/PlantCtrlESP32.sch
Normal file
File diff suppressed because it is too large
Load Diff
1274
board/PlantCtrlESP32.sch-bak
Normal file
1274
board/PlantCtrlESP32.sch-bak
Normal file
File diff suppressed because it is too large
Load Diff
11
board/ReadMe.md
Normal file
11
board/ReadMe.md
Normal file
@ -0,0 +1,11 @@
|
||||
# ESP32 Plant Control Board
|
||||
|
||||
The board was built with manily through hole components for easy build and production with a mill.
|
||||
|
||||
## GPIO Mapping
|
||||
See in the parent folder at **include/ControllerConfiguration.h**
|
||||
|
||||
## Routing
|
||||
In order to use the the mill, the following parameter were used:
|
||||
* Clearance 0.2mm
|
||||
* Track width of 1.2mm or 1.0mm at minimum (when pins of a part are too close)
|
1
board/fp-info-cache
Normal file
1
board/fp-info-cache
Normal file
@ -0,0 +1 @@
|
||||
0
|
5
board/gerber/.gitignore
vendored
Normal file
5
board/gerber/.gitignore
vendored
Normal file
@ -0,0 +1,5 @@
|
||||
*.gbr
|
||||
*.drl
|
||||
*.ngc
|
||||
*.png
|
||||
*.bakT*
|
33564
board/gerber/PlantCtrlESP32-drl_map.dxf
Normal file
33564
board/gerber/PlantCtrlESP32-drl_map.dxf
Normal file
File diff suppressed because it is too large
Load Diff
39
board/gerber/ReadMe.md
Normal file
39
board/gerber/ReadMe.md
Normal file
@ -0,0 +1,39 @@
|
||||
# Export Gerber
|
||||
The exported gerber files can be used to convert it into gcode for a mill
|
||||
## Export settings
|
||||
|
||||
Open the board in KiCad and select:
|
||||
File | Plot
|
||||
### General
|
||||
Plot format: Gerber
|
||||
### Include Layer
|
||||
Include the Layer ***B.Cu*** and ***Edge.Cuts***
|
||||
[ ] Plot border and title block
|
||||
[x] Plot footprint values
|
||||
[x] Plot footprint reference
|
||||
[ ] Force plotting of invisible values / refs
|
||||
[x] Exclude PCB edge layer from other layers
|
||||
[x] Exclude pads from silk screen
|
||||
[ ] Do not tent vias
|
||||
[x] Use auxilary axis as origin
|
||||
Drill marks: None
|
||||
Scaling: 1:1
|
||||
Plot mode: Filled
|
||||
Default line width: 0.1mm
|
||||
[ ] Mirrored plot
|
||||
[ ] Negated plot
|
||||
### Gerber Options
|
||||
[ ] Use Protel filename extensions
|
||||
[ ] Generate Geber job file
|
||||
[ ] Substract soldermask from silkscreen
|
||||
Coordinate format: 4.6, unit mm
|
||||
[ ] Use extended X2 format
|
||||
[ ] Include netlist attributes
|
||||
### Doing
|
||||
Click
|
||||
* **Plot**
|
||||
* **Generate Drill Files ...**
|
||||
* [x] PTH and NPTH in a single file
|
||||
* Map File Format: DXF
|
||||
* Drill Units: mm
|
||||
* Drill Origin: Auxilary axis
|
16
board/gerber/generatePCB.sh
Executable file
16
board/gerber/generatePCB.sh
Executable file
@ -0,0 +1,16 @@
|
||||
#!/bin/bash
|
||||
# Needs the tool pcb2gcode
|
||||
# Was documented at: http://marcuswolschon.blogspot.de/2013/02/milling-pcbs-using-gerber2gcode.html
|
||||
|
||||
MILLSPEED=600
|
||||
MILLFEED=200
|
||||
PROJECT=PlantCtrlESP32
|
||||
pcb2gcode --back $PROJECT-B_Cu.gbr --metric --zsafe 5 --zchange 10 --zwork -0.01 --offset 0.02 --mill-feed $MILLFEED --mill-speed $MILLSPEED --drill $PROJECT.drl --zdrill -2.5 --drill-feed $MILLFEED --drill-speed $MILLSPEED --basename $PROJECT
|
||||
|
||||
if [ "$1" == "C3MA" ]; then
|
||||
#update all Tools higher and equal to T4 in generated file
|
||||
for i in 4 5 6 7; do
|
||||
echo "Replace T$i"
|
||||
sed -i.bakT$i "s/T${i}/T3/" ${PROJECT}_drill.ngc
|
||||
done
|
||||
fi
|
17
esp32/PlantControl.code-workspace
Normal file
17
esp32/PlantControl.code-workspace
Normal file
@ -0,0 +1,17 @@
|
||||
{
|
||||
"folders": [
|
||||
{
|
||||
"path": "."
|
||||
}
|
||||
],
|
||||
"settings": {
|
||||
"files.associations": {
|
||||
"*.tcc": "cpp",
|
||||
"bitset": "cpp",
|
||||
"algorithm": "cpp",
|
||||
"istream": "cpp",
|
||||
"limits": "cpp",
|
||||
"streambuf": "cpp"
|
||||
}
|
||||
}
|
||||
}
|
15
esp32/Readme.md
Normal file
15
esp32/Readme.md
Normal file
@ -0,0 +1,15 @@
|
||||
# PlantControl
|
||||
## Hardware
|
||||
|
||||
Uses ESP32MniniKit
|
||||
|
||||
### Used Pins:
|
||||
* IO27 for DS18B20 temperature sensor
|
||||
|
||||
## Software
|
||||
* Mqtt topics
|
||||
* temperature
|
||||
* switch1
|
||||
* Settings:
|
||||
* ds18b20 - Enables Temperature measurement
|
||||
* deepsleep - Setup intervall how long the controller sleeps
|
11
esp32/generatePCB.sh
Executable file
11
esp32/generatePCB.sh
Executable file
@ -0,0 +1,11 @@
|
||||
#!/bin/bash
|
||||
# Needs the tool pcb2gcode
|
||||
# Was documented at: http://marcuswolschon.blogspot.de/2013/02/milling-pcbs-using-gerber2gcode.html
|
||||
|
||||
MILLSPEED=600
|
||||
MILLFEED=200
|
||||
PROJECT=PlantCtrlESP32
|
||||
pcb2gcode --back $PROJECT-B_Cu.gbr --outline $PROJECT-Edge_Cuts.gbr --metric \
|
||||
--zsafe 5 --zchange 10 --zwork -0.01 --offset 0.02 --mill-feed $MILLFEED --mill-speed $MILLSPEED \
|
||||
--drill $PROJECT.drl --zdrill -2.5 --drill-feed $MILLFEED --drill-speed $MILLSPEED --basename $PROJECT \
|
||||
--zcut 1.0 --cut-infeed 1.0 --cut-feed $MILLFEED --cutter-diameter 1.0 --cut-speed $MILLSPEED
|
1
esp32/homie/.gitignore
vendored
Normal file
1
esp32/homie/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
config.json
|
BIN
esp32/homie/ui_bundle.gz
Normal file
BIN
esp32/homie/ui_bundle.gz
Normal file
Binary file not shown.
63
esp32/host/Readme.md
Normal file
63
esp32/host/Readme.md
Normal file
@ -0,0 +1,63 @@
|
||||
# Configuration
|
||||
## File
|
||||
Generate a file as, described in
|
||||
https://homieiot.github.io/homie-esp8266/docs/3.0.0/configuration/json-configuration-file/
|
||||
For further details have a look at the Readme.md one level above.
|
||||
|
||||
## Upload
|
||||
* Start ESP
|
||||
* Login to Wifi, opened by the ESP
|
||||
* Use the script to upload the configuration file
|
||||
* restart the ESP
|
||||
|
||||
# Remote Upload
|
||||
|
||||
|
||||
This script will allow you to send an OTA update to your device.
|
||||
|
||||
## Installation
|
||||
|
||||
Requirements are:
|
||||
* paho-mqtt
|
||||
|
||||
## 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
|
||||
firmware
|
||||
|
||||
ota firmware update scirpt for ESP8226 implemenation of the Homie mqtt IoT
|
||||
convention.
|
||||
|
||||
positional arguments:
|
||||
firmware path to the firmware to be sent to the device
|
||||
|
||||
arguments:
|
||||
-h, --help show this help message and exit
|
||||
-l BROKER_HOST, --broker-host BROKER_HOST
|
||||
host name or ip address of the mqtt broker
|
||||
-p BROKER_PORT, --broker-port BROKER_PORT
|
||||
port of the mqtt broker
|
||||
-u BROKER_USERNAME, --broker-username BROKER_USERNAME
|
||||
username used to authenticate with the mqtt broker
|
||||
-d BROKER_PASSWORD, --broker-password BROKER_PASSWORD
|
||||
password used to authenticate with the mqtt broker
|
||||
-t BASE_TOPIC, --base-topic BASE_TOPIC
|
||||
base topic of the homie devices on the broker
|
||||
-i DEVICE_ID, --device-id DEVICE_ID
|
||||
homie device id
|
||||
```
|
||||
|
||||
* `BROKER_HOST` and `BROKER_PORT` defaults to 127.0.0.1 and 1883 respectively if not set.
|
||||
* `BROKER_USERNAME` and `BROKER_PASSWORD` are optional.
|
||||
* `BASE_TOPIC` has to end with a slash, defaults to `homie/` if not set.
|
||||
|
||||
### Example:
|
||||
|
||||
```bash
|
||||
python ota_updater.py -l localhost -u admin -d secure -t "homie/" -i "device-id" /path/to/firmware.bin
|
||||
```
|
||||
|
||||
### Source
|
||||
https://github.com/homieiot/homie-esp8266/blob/develop/scripts/ota_updater
|
27
esp32/host/config-example.json
Normal file
27
esp32/host/config-example.json
Normal file
@ -0,0 +1,27 @@
|
||||
{
|
||||
"name": "PlantControl",
|
||||
"device_id": "PlantCtrl1",
|
||||
"device_stats_interval": 60,
|
||||
"wifi": {
|
||||
"ssid": "SSID",
|
||||
"bssid" : "BSSID",
|
||||
"password": "mysecretPassword",
|
||||
"channel": 1
|
||||
},
|
||||
"mqtt": {
|
||||
"host": "[0-255].[0-255].[0-255].[0-255]",
|
||||
"port": 1883,
|
||||
"base_topic": "mqtt/topic/",
|
||||
"auth": false
|
||||
},
|
||||
"ota": {
|
||||
"enabled": true
|
||||
},
|
||||
"settings": {
|
||||
"deepsleep": 60000,
|
||||
"plants" : 3,
|
||||
"moist1" : 2000,
|
||||
"moist2" : 2000,
|
||||
"moist3" : 2000
|
||||
}
|
||||
}
|
174
esp32/host/ota_updater.py
Executable file
174
esp32/host/ota_updater.py
Executable file
@ -0,0 +1,174 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
from __future__ import division, print_function
|
||||
import paho.mqtt.client as mqtt
|
||||
import base64, sys, math
|
||||
from hashlib import md5
|
||||
|
||||
# The callback for when the client receives a CONNACK response from the server.
|
||||
def on_connect(client, userdata, flags, rc):
|
||||
if rc != 0:
|
||||
print("Connection Failed with result code {}".format(rc))
|
||||
client.disconnect()
|
||||
else:
|
||||
print("Connected with result code {}".format(rc))
|
||||
|
||||
client.subscribe("{base_topic}{device_id}/$state".format(**userdata)) # v3 / v4 devices
|
||||
client.subscribe("{base_topic}{device_id}/$online".format(**userdata)) # v2 devices
|
||||
|
||||
|
||||
print("Waiting for device to come online...")
|
||||
|
||||
|
||||
# The callback for when a PUBLISH message is received from the server.
|
||||
def on_message(client, userdata, msg):
|
||||
# decode string for python2/3 compatiblity
|
||||
msg.payload = msg.payload.decode()
|
||||
|
||||
if msg.topic.endswith('$implementation/ota/status'):
|
||||
status = int(msg.payload.split()[0])
|
||||
|
||||
if userdata.get("published"):
|
||||
if status == 206: # in progress
|
||||
# state in progress, print progress bar
|
||||
progress, total = [int(x) for x in msg.payload.split()[1].split('/')]
|
||||
bar_width = 30
|
||||
bar = int(bar_width*(progress/total))
|
||||
print("\r[", '+'*bar, ' '*(bar_width-bar), "] ", msg.payload.split()[1], end='', sep='')
|
||||
if (progress == total):
|
||||
print()
|
||||
sys.stdout.flush()
|
||||
elif status == 304: # not modified
|
||||
print("Device firmware already up to date with md5 checksum: {}".format(userdata.get('md5')))
|
||||
client.disconnect()
|
||||
elif status == 403: # forbidden
|
||||
print("Device ota disabled, aborting...")
|
||||
client.disconnect()
|
||||
|
||||
elif msg.topic.endswith('$fw/checksum'):
|
||||
checksum = msg.payload
|
||||
|
||||
if userdata.get("published"):
|
||||
if checksum == userdata.get('md5'):
|
||||
print("Device back online. Update Successful!")
|
||||
else:
|
||||
print("Expecting checksum {}, got {}, update failed!".format(userdata.get('md5'), checksum))
|
||||
client.disconnect()
|
||||
else:
|
||||
if checksum != userdata.get('md5'): # save old md5 for comparison with new firmware
|
||||
userdata.update({'old_md5': checksum})
|
||||
else:
|
||||
print("Device firmware already up to date with md5 checksum: {}".format(checksum))
|
||||
client.disconnect()
|
||||
|
||||
elif msg.topic.endswith('ota/enabled'):
|
||||
if msg.payload == 'true':
|
||||
userdata.update({'ota_enabled': True})
|
||||
else:
|
||||
print("Device ota disabled, aborting...")
|
||||
client.disconnect()
|
||||
|
||||
elif msg.topic.endswith('$state') or msg.topic.endswith('$online'):
|
||||
if (msg.topic.endswith('$state') and msg.payload != 'ready') or (msg.topic.endswith('$online') and msg.payload == 'false'):
|
||||
return
|
||||
|
||||
# calcluate firmware md5
|
||||
firmware_md5 = md5(userdata['firmware']).hexdigest()
|
||||
userdata.update({'md5': firmware_md5})
|
||||
|
||||
# Subscribing in on_connect() means that if we lose the connection and
|
||||
# reconnect then subscriptions will be renewed.
|
||||
client.subscribe("{base_topic}{device_id}/$implementation/ota/status".format(**userdata))
|
||||
client.subscribe("{base_topic}{device_id}/$implementation/ota/enabled".format(**userdata))
|
||||
client.subscribe("{base_topic}{device_id}/$fw/#".format(**userdata))
|
||||
|
||||
# Wait for device info to come in and invoke the on_message callback where update will continue
|
||||
print("Waiting for device info...")
|
||||
|
||||
if ( not userdata.get("published") ) and ( userdata.get('ota_enabled') ) and \
|
||||
( 'old_md5' in userdata.keys() ) and ( userdata.get('md5') != userdata.get('old_md5') ):
|
||||
# push the firmware binary
|
||||
userdata.update({"published": True})
|
||||
topic = "{base_topic}{device_id}/$implementation/ota/firmware/{md5}".format(**userdata)
|
||||
print("Publishing new firmware with checksum {}".format(userdata.get('md5')))
|
||||
client.publish(topic, userdata['firmware'])
|
||||
|
||||
|
||||
def main(broker_host, broker_port, broker_username, broker_password, broker_ca_cert, base_topic, device_id, firmware):
|
||||
# initialise mqtt client and register callbacks
|
||||
client = mqtt.Client()
|
||||
client.on_connect = on_connect
|
||||
client.on_message = on_message
|
||||
|
||||
# set username and password if given
|
||||
if broker_username and broker_password:
|
||||
client.username_pw_set(broker_username, broker_password)
|
||||
|
||||
if broker_ca_cert is not None:
|
||||
client.tls_set(
|
||||
ca_certs=broker_ca_cert
|
||||
)
|
||||
|
||||
# save data to be used in the callbacks
|
||||
client.user_data_set({
|
||||
"base_topic": base_topic,
|
||||
"device_id": device_id,
|
||||
"firmware": firmware
|
||||
})
|
||||
|
||||
# start connection
|
||||
print("Connecting to mqtt broker {} on port {}".format(broker_host, broker_port))
|
||||
client.connect(broker_host, broker_port, 60)
|
||||
|
||||
# Blocking call that processes network traffic, dispatches callbacks and handles reconnecting.
|
||||
client.loop_forever()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
import argparse
|
||||
|
||||
parser = argparse.ArgumentParser(
|
||||
description='ota firmware update scirpt for ESP8226 implemenation of the Homie mqtt IoT convention.')
|
||||
|
||||
# ensure base topic always ends with a '/'
|
||||
def base_topic_arg(s):
|
||||
s = str(s)
|
||||
if not s.endswith('/'):
|
||||
s = s + '/'
|
||||
return s
|
||||
|
||||
# specify arguments
|
||||
parser.add_argument('-l', '--broker-host', type=str, required=False,
|
||||
help='host name or ip address of the mqtt broker', default="127.0.0.1")
|
||||
parser.add_argument('-p', '--broker-port', type=int, required=False,
|
||||
help='port of the mqtt broker', default=1883)
|
||||
parser.add_argument('-u', '--broker-username', type=str, required=False,
|
||||
help='username used to authenticate with the mqtt broker')
|
||||
parser.add_argument('-d', '--broker-password', type=str, required=False,
|
||||
help='password used to authenticate with the mqtt broker')
|
||||
parser.add_argument('-t', '--base-topic', type=base_topic_arg, required=False,
|
||||
help='base topic of the homie devices on the broker', default="homie/")
|
||||
parser.add_argument('-i', '--device-id', type=str, required=True,
|
||||
help='homie device id')
|
||||
parser.add_argument('firmware', type=argparse.FileType('rb'),
|
||||
help='path to the firmware to be sent to the device')
|
||||
|
||||
parser.add_argument("--broker-tls-cacert", default=None, required=False,
|
||||
help="CA certificate bundle used to validate TLS connections. If set, TLS will be enabled on the broker conncetion"
|
||||
)
|
||||
|
||||
# workaround for http://bugs.python.org/issue9694
|
||||
parser._optionals.title = "arguments"
|
||||
|
||||
# get and validate arguments
|
||||
args = parser.parse_args()
|
||||
|
||||
# read the contents of firmware into buffer
|
||||
fw_buffer = args.firmware.read()
|
||||
args.firmware.close()
|
||||
firmware = bytearray()
|
||||
firmware.extend(fw_buffer)
|
||||
|
||||
# Invoke the business logic
|
||||
main(args.broker_host, args.broker_port, args.broker_username,
|
||||
args.broker_password, args.broker_tls_cacert, args.base_topic, args.device_id, firmware)
|
13
esp32/host/upload.sh
Executable file
13
esp32/host/upload.sh
Executable file
@ -0,0 +1,13 @@
|
||||
#!/bin/bash
|
||||
echo "Homie device is in AP mode, then the configuration can be uploaded"
|
||||
|
||||
if [ ! -f config.json ]; then
|
||||
echo "Create config file according to :"
|
||||
echo "https://homieiot.github.io/homie-esp8266/docs/3.0.0/configuration/json-configuration-file/"
|
||||
exit 2
|
||||
fi
|
||||
|
||||
echo "Check connection to Plug in AP-mode"
|
||||
ping -c 4 192.168.123.1
|
||||
|
||||
curl -X PUT http://192.168.123.1/config --header "Content-Type: application/json" -d @config.json
|
55
esp32/include/ControllerConfiguration.h
Normal file
55
esp32/include/ControllerConfiguration.h
Normal file
@ -0,0 +1,55 @@
|
||||
/**
|
||||
* @file ControllerConfiguration.h
|
||||
* @author your name (you@domain.com)
|
||||
* @brief
|
||||
* @version 0.1
|
||||
* @date 2020-05-30
|
||||
*
|
||||
* @copyright Copyright (c) 2020
|
||||
* Describe the used PINs of the controller
|
||||
*/
|
||||
#ifndef CONTROLLER_CONFIG_H
|
||||
#define CONTROLLER_CONFIG_H
|
||||
|
||||
#define FIRMWARE_VERSION "0.9.5"
|
||||
|
||||
#define ADC_TO_VOLT(adc) ((adc) * 3.3 ) / 4095)
|
||||
#define ADC_TO_VOLT_WITH_MULTI(adc, multi) (((adc) * 3.3 * (multi)) / 4095)
|
||||
|
||||
#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 MS_TO_S 1000
|
||||
|
||||
#define SENSOR_LIPO 34 /**< GPIO 34 (ADC1) */
|
||||
#define SENSOR_SOLAR 35 /**< GPIO 35 (ADC1) */
|
||||
#define SENSOR_PLANT1 32 /**< GPIO 32 (ADC1) */
|
||||
#define SENSOR_PLANT2 33 /**< GPIO 33 (ADC1) */
|
||||
#define SENSOR_PLANT3 25 /**< GPIO 25 (ADC2) */
|
||||
#define SENSOR_PLANT4 26 /**< GPIO 26 (ADC2) */
|
||||
#define SENSOR_PLANT5 27 /**< GPIO 27 (ADC2) */
|
||||
#define SENSOR_PLANT6 14 /**< GPIO 14 (ADC2) */
|
||||
|
||||
#define OUTPUT_PUMP1 5 /**< GPIO 5 */
|
||||
#define OUTPUT_PUMP2 18 /**< GPIO 18 */
|
||||
#define OUTPUT_PUMP3 19 /**< GPIO 19 */
|
||||
#define OUTPUT_PUMP4 21 /**< GPIO 21 */
|
||||
#define OUTPUT_PUMP5 22 /**< GPIO 22 */
|
||||
#define OUTPUT_PUMP6 23 /**< GPIO 23 */
|
||||
|
||||
#define OUTPUT_SENSOR 4 /**< GPIO 4 */
|
||||
#define INPUT_WATER_LOW 2 /**< GPIO 2 */
|
||||
#define INPUT_WATER_EMPTY 15 /**< GPIO 15 */
|
||||
#define INPUT_WATER_OVERFLOW 12 /**< GPIO 12 */
|
||||
|
||||
#define SENSOR_DS18B20 13 /**< GPIO 13 */
|
||||
#define BUTTON 0 /**< GPIO 0 */
|
||||
|
||||
#define MIN_TIME_RUNNING 10UL /**< Amount of seconds the controller must stay awoken */
|
||||
#define MAX_PLANTS 3
|
||||
#define EMPTY_LIPO_MULTIPL 3 /**< Multiplier to increase time for sleeping when lipo is empty */
|
||||
#define MINIMUM_LIPO_VOLT 3.3f /**< Minimum voltage of the Lipo, that must be present */
|
||||
#define MINIMUM_SOLAR_VOLT 4.0f /**< Minimum voltage of the sun, to detect daylight */
|
||||
|
||||
#define HC_SR04 /**< Ultrasonic distance sensor to measure water level */
|
||||
|
||||
#endif
|
52
esp32/include/DS18B20.h
Normal file
52
esp32/include/DS18B20.h
Normal file
@ -0,0 +1,52 @@
|
||||
/**
|
||||
* @file DS18B20.h
|
||||
* @author your name (you@domain.com)
|
||||
* @brief
|
||||
* @version 0.1
|
||||
* @date 2020-06-09
|
||||
*
|
||||
* @copyright Copyright (c) 2020
|
||||
* Based on the LUA code from the ESP8266
|
||||
* --------------------------------------------------------------------------------
|
||||
* -- DS18B20 one wire module for NODEMCU
|
||||
* -- NODEMCU TEAM
|
||||
* -- LICENCE: http://opensource.org/licenses/MIT
|
||||
* -- Vowstar <vowstar@nodemcu.com>
|
||||
* -- 2015/02/14 sza2 <sza2trash@gmail.com> Fix for negative values
|
||||
* --------------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
#ifndef DS18B20_H
|
||||
#define DS18B20_H
|
||||
|
||||
#include <OneWire.h>
|
||||
|
||||
class Ds18B20 {
|
||||
private:
|
||||
OneWire* mDs;
|
||||
int foundDevices;
|
||||
public:
|
||||
Ds18B20(int pin) {
|
||||
this->mDs = new OneWire(pin);
|
||||
}
|
||||
|
||||
~Ds18B20() {
|
||||
delete this->mDs;
|
||||
}
|
||||
/**
|
||||
* @brief read amount sensots
|
||||
* check for available of DS18B20 sensors
|
||||
* @return amount of sensors
|
||||
*/
|
||||
int readDevices(void);
|
||||
|
||||
/**
|
||||
* @brief Read all temperatures in celsius
|
||||
*
|
||||
* @param pTemperatures array of float valuies
|
||||
* @param maxTemperatures size of the given array
|
||||
* @return int amount of read temperature values
|
||||
*/
|
||||
int readAllTemperatures(float* pTemperatures, int maxTemperatures);
|
||||
};
|
||||
#endif
|
76
esp32/include/PlantCtrl.h
Normal file
76
esp32/include/PlantCtrl.h
Normal file
@ -0,0 +1,76 @@
|
||||
/**
|
||||
* @file PlantCtrl.h
|
||||
* @author your name (you@domain.com)
|
||||
* @brief Abstraction to handle the Sensors
|
||||
* @version 0.1
|
||||
* @date 2020-05-27
|
||||
*
|
||||
* @copyright Copyright (c) 2020
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef PLANT_CTRL_H
|
||||
#define PLANT_CTRL_H
|
||||
|
||||
class Plant {
|
||||
|
||||
private:
|
||||
int mPinSensor=0; /**< Pin of the moist sensor */
|
||||
int mPinPump=0; /**< Pin of the pump */
|
||||
|
||||
int mValue = 0; /**< Value of the moist sensor */
|
||||
|
||||
int mAnalogValue=0; /**< moist sensor values, used for a calculation */
|
||||
|
||||
public:
|
||||
|
||||
/**
|
||||
* @brief Construct a new Plant object
|
||||
*
|
||||
* @param pinSensor Pin of the Sensor to use to measure moist
|
||||
* @param pinPump Pin of the Pump to use
|
||||
*/
|
||||
Plant(int pinSensor, int pinPump);
|
||||
|
||||
/**
|
||||
* @brief Add a value, to be measured
|
||||
*
|
||||
* @param analogValue
|
||||
*/
|
||||
void addSenseValue(int analogValue);
|
||||
|
||||
/**
|
||||
* @brief Calculate the value based on the information
|
||||
* @see amountMeasurePoints
|
||||
* Internal memory, used by addSenseValue will be resetted
|
||||
* @return int analog value
|
||||
*/
|
||||
void calculateSensorValue(int amountMeasurePoints);
|
||||
|
||||
/**
|
||||
* @brief Get the Sensor Pin of the analog measuring
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
int getSensorPin() { return mPinSensor; }
|
||||
|
||||
/**
|
||||
* @brief Get the Pump Pin object
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
int getPumpPin() { return mPinPump; }
|
||||
|
||||
int getSensorValue() { return mValue; }
|
||||
|
||||
/**
|
||||
* @brief Check if a plant is too dry and needs some water.
|
||||
*
|
||||
* @param boundary
|
||||
* @return true
|
||||
* @return false
|
||||
*/
|
||||
bool isPumpRequired(int boundary) { return (this->mValue < boundary); }
|
||||
};
|
||||
|
||||
#endif
|
39
esp32/include/README
Normal file
39
esp32/include/README
Normal file
@ -0,0 +1,39 @@
|
||||
|
||||
This directory is intended for project header files.
|
||||
|
||||
A header file is a file containing C declarations and macro definitions
|
||||
to be shared between several project source files. You request the use of a
|
||||
header file in your project source file (C, C++, etc) located in `src` folder
|
||||
by including it, with the C preprocessing directive `#include'.
|
||||
|
||||
```src/main.c
|
||||
|
||||
#include "header.h"
|
||||
|
||||
int main (void)
|
||||
{
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
Including a header file produces the same results as copying the header file
|
||||
into each source file that needs it. Such copying would be time-consuming
|
||||
and error-prone. With a header file, the related declarations appear
|
||||
in only one place. If they need to be changed, they can be changed in one
|
||||
place, and programs that include the header file will automatically use the
|
||||
new version when next recompiled. The header file eliminates the labor of
|
||||
finding and changing all the copies as well as the risk that a failure to
|
||||
find one copy will result in inconsistencies within a program.
|
||||
|
||||
In C, the usual convention is to give header files names that end with `.h'.
|
||||
It is most portable to use only letters, digits, dashes, and underscores in
|
||||
header file names, and at most one dot.
|
||||
|
||||
Read more about using header files in official GCC documentation:
|
||||
|
||||
* Include Syntax
|
||||
* Include Operation
|
||||
* Once-Only Headers
|
||||
* Computed Includes
|
||||
|
||||
https://gcc.gnu.org/onlinedocs/cpp/Header-Files.html
|
46
esp32/lib/README
Normal file
46
esp32/lib/README
Normal file
@ -0,0 +1,46 @@
|
||||
|
||||
This directory is intended for project specific (private) libraries.
|
||||
PlatformIO will compile them to static libraries and link into executable file.
|
||||
|
||||
The source code of each library should be placed in a an own separate directory
|
||||
("lib/your_library_name/[here are source files]").
|
||||
|
||||
For example, see a structure of the following two libraries `Foo` and `Bar`:
|
||||
|
||||
|--lib
|
||||
| |
|
||||
| |--Bar
|
||||
| | |--docs
|
||||
| | |--examples
|
||||
| | |--src
|
||||
| | |- Bar.c
|
||||
| | |- Bar.h
|
||||
| | |- library.json (optional, custom build options, etc) https://docs.platformio.org/page/librarymanager/config.html
|
||||
| |
|
||||
| |--Foo
|
||||
| | |- Foo.c
|
||||
| | |- Foo.h
|
||||
| |
|
||||
| |- README --> THIS FILE
|
||||
|
|
||||
|- platformio.ini
|
||||
|--src
|
||||
|- main.c
|
||||
|
||||
and a contents of `src/main.c`:
|
||||
```
|
||||
#include <Foo.h>
|
||||
#include <Bar.h>
|
||||
|
||||
int main (void)
|
||||
{
|
||||
...
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
PlatformIO Library Dependency Finder will find automatically dependent
|
||||
libraries scanning project source files.
|
||||
|
||||
More information about PlatformIO Library Dependency Finder
|
||||
- https://docs.platformio.org/page/librarymanager/ldf.html
|
21
esp32/platformio.ini
Normal file
21
esp32/platformio.ini
Normal file
@ -0,0 +1,21 @@
|
||||
; PlatformIO Project Configuration File
|
||||
;
|
||||
; Build options: build flags, source filter
|
||||
; Upload options: custom upload port, speed and extra flags
|
||||
; Library options: dependencies, extra library storages
|
||||
; Advanced options: extra scripting
|
||||
;
|
||||
; Please visit documentation for the other options and examples
|
||||
; https://docs.platformio.org/page/projectconf.html
|
||||
|
||||
[env:esp32doit-devkit-v1]
|
||||
platform = espressif32
|
||||
board = esp32doit-devkit-v1
|
||||
framework = arduino
|
||||
build_flags = -DPIO_FRAMEWORK_ARDUINO_LWIP2_LOW_MEMORY
|
||||
board_build.partitions = huge_app.csv
|
||||
|
||||
; the latest development brankitchen-lightch (convention V3.0.x)
|
||||
lib_deps = https://github.com/homieiot/homie-esp8266.git#v3.0
|
||||
OneWire
|
||||
upload_port = /dev/ttyUSB0
|
105
esp32/src/DS18B20.cpp
Normal file
105
esp32/src/DS18B20.cpp
Normal file
@ -0,0 +1,105 @@
|
||||
/**
|
||||
* @file DS18B20.cpp
|
||||
* @author your name (you@domain.com)
|
||||
* @brief
|
||||
* @version 0.1
|
||||
* @date 2020-06-09
|
||||
*
|
||||
* @copyright Copyright (c) 2020
|
||||
*
|
||||
*/
|
||||
|
||||
#include "DS18B20.h"
|
||||
|
||||
#define STARTCONV 0x44
|
||||
#define READSCRATCH 0xBE // Read EEPROM
|
||||
#define TEMP_LSB 0
|
||||
#define TEMP_MSB 1
|
||||
#define SCRATCHPADSIZE 9
|
||||
#define OFFSET_CRC8 8 /**< 9th byte has the CRC of the complete data */
|
||||
|
||||
//Printf debugging
|
||||
//#define DS_DEBUG
|
||||
|
||||
int Ds18B20::readDevices() {
|
||||
byte addr[8];
|
||||
|
||||
int amount = -1;
|
||||
while (this->mDs->search(addr)) {
|
||||
amount++;
|
||||
}
|
||||
this->mDs->reset_search();
|
||||
return amount;
|
||||
}
|
||||
|
||||
int Ds18B20::readAllTemperatures(float* pTemperatures, int maxTemperatures) {
|
||||
byte addr[8];
|
||||
uint8_t scratchPad[SCRATCHPADSIZE];
|
||||
int currentTemp = 0;
|
||||
|
||||
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->select(addr);
|
||||
this->mDs->write(STARTCONV);
|
||||
this->mDs->reset();
|
||||
this->mDs->select(addr);
|
||||
this->mDs->write(READSCRATCH);
|
||||
|
||||
// Read all registers in a simple loop
|
||||
// byte 0: temperature LSB
|
||||
// byte 1: temperature MSB
|
||||
// byte 2: high alarm temp
|
||||
// byte 3: low alarm temp
|
||||
// byte 4: DS18S20: store for crc
|
||||
// DS18B20 & DS1822: configuration register
|
||||
// byte 5: internal use & crc
|
||||
// byte 6: DS18S20: COUNT_REMAIN
|
||||
// DS18B20 & DS1822: store for crc
|
||||
// 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++) {
|
||||
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;
|
||||
#ifdef DS_DEBUG
|
||||
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) {
|
||||
pTemperatures[currentTemp] = celsius;
|
||||
} else {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
currentTemp++;
|
||||
}
|
||||
this->mDs->reset();
|
||||
#ifdef DS_DEBUG
|
||||
Serial.println(" No more addresses.");
|
||||
Serial.println();
|
||||
#endif
|
||||
|
||||
return currentTemp;
|
||||
}
|
27
esp32/src/PlantCtrl.cpp
Normal file
27
esp32/src/PlantCtrl.cpp
Normal file
@ -0,0 +1,27 @@
|
||||
/**
|
||||
* @file PlantCtrl.cpp
|
||||
* @author your name (you@domain.com)
|
||||
* @brief
|
||||
* @version 0.1
|
||||
* @date 2020-05-27
|
||||
*
|
||||
* @copyright Copyright (c) 2020
|
||||
*
|
||||
|
||||
*/
|
||||
|
||||
#include "PlantCtrl.h"
|
||||
|
||||
Plant::Plant(int pinSensor, int pinPump) {
|
||||
this->mPinSensor = pinSensor;
|
||||
this->mPinPump = pinPump;
|
||||
}
|
||||
|
||||
void Plant::addSenseValue(int analog) {
|
||||
this->mAnalogValue += analog;
|
||||
}
|
||||
|
||||
void Plant::calculateSensorValue(int amountMeasurePoints) {
|
||||
this->mValue = this->mAnalogValue / amountMeasurePoints;
|
||||
this->mAnalogValue = 0;
|
||||
}
|
703
esp32/src/main.cpp
Normal file
703
esp32/src/main.cpp
Normal file
@ -0,0 +1,703 @@
|
||||
/**
|
||||
* @file main.cpp
|
||||
* @author Ollo
|
||||
* @brief PlantControl
|
||||
* @version 0.1
|
||||
* @date 2020-05-01
|
||||
*
|
||||
* @copyright Copyright (c) 2020
|
||||
*
|
||||
*/
|
||||
#include "PlantCtrl.h"
|
||||
#include "ControllerConfiguration.h"
|
||||
#include "DS18B20.h"
|
||||
#include <Homie.h>
|
||||
|
||||
const unsigned long TEMPREADCYCLE = 30000; /**< Check temperature all half minutes */
|
||||
|
||||
#define AMOUNT_SENOR_QUERYS 8
|
||||
#define SENSOR_QUERY_SHIFTS 3
|
||||
#define SOLAR4SENSORS 6.0f
|
||||
#define TEMP_INIT_VALUE -999.0f
|
||||
#define TEMP_MAX_VALUE 85.0f
|
||||
|
||||
bool mLoopInited = false;
|
||||
bool mDeepSleep = false;
|
||||
|
||||
bool mPumpIsRunning=false;
|
||||
|
||||
int plantSensor1 = 0;
|
||||
|
||||
int lipoSenor = -1;
|
||||
int lipoSensorValues = 0;
|
||||
int solarSensor = -1;
|
||||
int solarSensorValues = 0;
|
||||
|
||||
int mWaterAtEmptyLevel = 0;
|
||||
#ifndef HC_SR04
|
||||
int mWaterLow = 0;
|
||||
#else
|
||||
int mWaterGone = -1; /**< Amount of centimeter, where no water is seen */
|
||||
#endif
|
||||
int mOverflow = 0;
|
||||
|
||||
int readCounter = 0;
|
||||
int mButtonClicks = 0;
|
||||
|
||||
|
||||
#if (MAX_PLANTS >= 1)
|
||||
HomieNode plant1("plant1", "Plant 1", "Plant");
|
||||
#endif
|
||||
#if (MAX_PLANTS >= 2)
|
||||
HomieNode plant2("plant2", "Plant 2", "Plant");
|
||||
#endif
|
||||
#if (MAX_PLANTS >= 3)
|
||||
HomieNode plant3("plant3", "Plant 3", "Plant");
|
||||
#endif
|
||||
#if (MAX_PLANTS >= 4)
|
||||
HomieNode plant4("plant4", "Plant 4", "Plant");
|
||||
#endif
|
||||
#if (MAX_PLANTS >= 5)
|
||||
HomieNode plant5("plant5", "Plant 5", "Plant");
|
||||
#endif
|
||||
#if (MAX_PLANTS >= 6)
|
||||
HomieNode plant6("plant6", "Plant 6", "Plant");
|
||||
#endif
|
||||
|
||||
HomieNode sensorLipo("lipo", "Battery Status", "Lipo");
|
||||
HomieNode sensorSolar("solar", "Solar Status", "Solarpanel");
|
||||
HomieNode sensorWater("water", "WaterSensor", "Water");
|
||||
HomieNode sensorTemp("temperature", "Temperature", "temperature");
|
||||
|
||||
HomieSetting<long> deepSleepTime("deepsleep", "time in milliseconds to sleep (0 deactivats it)");
|
||||
HomieSetting<long> deepSleepNightTime("nightsleep", "time in milliseconds to sleep (0 usese same setting: deepsleep at night, too)");
|
||||
HomieSetting<long> wateringTime("watering", "time seconds the pump is running (60 is the default)");
|
||||
HomieSetting<long> plantCnt("plants", "amout of plants to control (1 ... 6)");
|
||||
|
||||
#ifdef HC_SR04
|
||||
HomieSetting<long> waterLevel("watermaxlevel", "Water maximum level in centimeter (50 cm default)");
|
||||
#endif
|
||||
|
||||
|
||||
#if (MAX_PLANTS >= 1)
|
||||
HomieSetting<long> plant1SensorTrigger("moist1", "Moist1 sensor value, when pump activates");
|
||||
#endif
|
||||
#if (MAX_PLANTS >= 2)
|
||||
HomieSetting<long> plant2SensorTrigger("moist2", "Moist2 sensor value, when pump activates");
|
||||
#endif
|
||||
#if (MAX_PLANTS >= 3)
|
||||
HomieSetting<long> plant3SensorTrigger("moist3", "Moist3 sensor value, when pump activates");
|
||||
#endif
|
||||
#if (MAX_PLANTS >= 4)
|
||||
HomieSetting<long> plant4SensorTrigger("moist4", "Moist4 sensor value, when pump activates");
|
||||
#endif
|
||||
#if (MAX_PLANTS >= 5)
|
||||
HomieSetting<long> plant5SensorTrigger("moist5", "Moist5 sensor value, when pump activates");
|
||||
#endif
|
||||
#if (MAX_PLANTS >= 6)
|
||||
HomieSetting<long> plant6SensorTrigger("moist6", "Moist6 sensor value, when pump activates");
|
||||
#endif
|
||||
|
||||
Ds18B20 dallas(SENSOR_DS18B20);
|
||||
|
||||
Plant mPlants[MAX_PLANTS] = {
|
||||
#if (MAX_PLANTS >= 1)
|
||||
Plant(SENSOR_PLANT1, OUTPUT_PUMP1),
|
||||
#endif
|
||||
#if (MAX_PLANTS >= 2)
|
||||
Plant(SENSOR_PLANT2, OUTPUT_PUMP2),
|
||||
#endif
|
||||
#if (MAX_PLANTS >= 3)
|
||||
Plant(SENSOR_PLANT3, OUTPUT_PUMP3),
|
||||
#endif
|
||||
#if (MAX_PLANTS >= 4)
|
||||
Plant(SENSOR_PLANT4, OUTPUT_PUMP4),
|
||||
#endif
|
||||
#if (MAX_PLANTS >= 5)
|
||||
Plant(SENSOR_PLANT5, OUTPUT_PUMP5),
|
||||
#endif
|
||||
#if (MAX_PLANTS >= 6)
|
||||
Plant(SENSOR_PLANT6, OUTPUT_PUMP6)
|
||||
#endif
|
||||
};
|
||||
|
||||
void readAnalogValues() {
|
||||
if (readCounter < AMOUNT_SENOR_QUERYS) {
|
||||
lipoSensorValues += analogRead(SENSOR_LIPO);
|
||||
solarSensorValues += analogRead(SENSOR_SOLAR);
|
||||
readCounter++;
|
||||
} else {
|
||||
lipoSenor = (lipoSensorValues >> SENSOR_QUERY_SHIFTS);
|
||||
lipoSensorValues = 0;
|
||||
solarSensor = (solarSensorValues >> SENSOR_QUERY_SHIFTS);
|
||||
solarSensorValues = 0;
|
||||
|
||||
readCounter = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief cyclic Homie callback
|
||||
* All logic, to be done by the controller cyclically
|
||||
*/
|
||||
void loopHandler() {
|
||||
|
||||
/* Move from Setup to this position, because the Settings are only here available */
|
||||
if (!mLoopInited) {
|
||||
// Configure Deep Sleep:
|
||||
if (deepSleepTime.get()) {
|
||||
Serial << "HOMIE | Setup sleeping for " << deepSleepTime.get() << " ms" << endl;
|
||||
}
|
||||
if (wateringTime.get()) {
|
||||
Serial << "HOMIE | Setup watering for " << abs(wateringTime.get()) << " s" << endl;
|
||||
}
|
||||
/* Publish default values */
|
||||
plant1.setProperty("switch").send(String("OFF"));
|
||||
plant2.setProperty("switch").send(String("OFF"));
|
||||
plant3.setProperty("switch").send(String("OFF"));
|
||||
|
||||
#if (MAX_PLANTS >= 4)
|
||||
plant4.setProperty("switch").send(String("OFF"));
|
||||
plant5.setProperty("switch").send(String("OFF"));
|
||||
plant6.setProperty("switch").send(String("OFF"));
|
||||
#endif
|
||||
|
||||
for(int i=0; i < plantCnt.get(); i++) {
|
||||
mPlants[i].calculateSensorValue(AMOUNT_SENOR_QUERYS);
|
||||
int boundary4MoistSensor=-1;
|
||||
switch (i)
|
||||
{
|
||||
case 0:
|
||||
boundary4MoistSensor = plant1SensorTrigger.get();
|
||||
plant1.setProperty("moist").send(String(100 * mPlants[i].getSensorValue() / 4095 ));
|
||||
break;
|
||||
case 1:
|
||||
boundary4MoistSensor = plant2SensorTrigger.get();
|
||||
plant2.setProperty("moist").send(String(100 * mPlants[i].getSensorValue() / 4095));
|
||||
break;
|
||||
case 2:
|
||||
boundary4MoistSensor = plant3SensorTrigger.get();
|
||||
plant3.setProperty("moist").send(String(100 * mPlants[i].getSensorValue() / 4095));
|
||||
break;
|
||||
#if (MAX_PLANTS >= 4)
|
||||
case 3:
|
||||
boundary4MoistSensor = plant4SensorTrigger.get();
|
||||
plant4.setProperty("moist").send(String(100 * mPlants[i].getSensorValue() / 4095));
|
||||
break;
|
||||
case 4:
|
||||
boundary4MoistSensor = plant5SensorTrigger.get();
|
||||
plant5.setProperty("moist").send(String(100 * mPlants[i].getSensorValue() / 4095));
|
||||
break;
|
||||
case 5:
|
||||
boundary4MoistSensor = plant6SensorTrigger.get();
|
||||
plant6.setProperty("moist").send(String(100 * mPlants[i].getSensorValue() / 4095));
|
||||
break;
|
||||
#endif
|
||||
}
|
||||
|
||||
#ifndef HC_SR04
|
||||
if (SOLAR_VOLT(solarSensor) > SOLAR4SENSORS) {
|
||||
if (mWaterLow && mWaterAtEmptyLevel) {
|
||||
sensorWater.setProperty("remaining").send("50");
|
||||
} else if (!mWaterLow && mWaterAtEmptyLevel) {
|
||||
sensorWater.setProperty("remaining").send("10");
|
||||
} else if (!mWaterLow && !mWaterAtEmptyLevel) {
|
||||
sensorWater.setProperty("remaining").send("0");
|
||||
} else if (!mWaterLow && !mWaterAtEmptyLevel) {
|
||||
sensorWater.setProperty("remaining").send("-1");
|
||||
}
|
||||
} else {
|
||||
Serial << "Sun not strong enough for sensors (" << String(SOLAR_VOLT(solarSensor)) << "V )" << endl;
|
||||
}
|
||||
#else
|
||||
mWaterAtEmptyLevel = (mWaterGone <= waterLevel.get());
|
||||
int waterLevelPercent = (100 * mWaterGone) / waterLevel.get();
|
||||
sensorWater.setProperty("remaining").send(String(waterLevelPercent));
|
||||
Serial << "Water : " << mWaterGone << " cm (" << waterLevelPercent << "%)" << endl;
|
||||
#endif
|
||||
mPumpIsRunning=false;
|
||||
/* Check if a plant needs water */
|
||||
if (mPlants[i].isPumpRequired(boundary4MoistSensor) &&
|
||||
(mWaterAtEmptyLevel) &&
|
||||
(!mPumpIsRunning)) {
|
||||
if (digitalRead(mPlants[i].getPumpPin()) == LOW) {
|
||||
Serial << "Plant" << (i+1) << " needs water" << endl;
|
||||
switch (i)
|
||||
{
|
||||
case 0:
|
||||
plant1.setProperty("switch").send(String("ON"));
|
||||
break;
|
||||
case 1:
|
||||
plant2.setProperty("switch").send(String("ON"));
|
||||
break;
|
||||
case 2:
|
||||
plant3.setProperty("switch").send(String("ON"));
|
||||
break;
|
||||
#if (MAX_PLANTS >= 4)
|
||||
case 3:
|
||||
plant4.setProperty("switch").send(String("ON"));
|
||||
break;
|
||||
case 4:
|
||||
plant5.setProperty("switch").send(String("ON"));
|
||||
break;
|
||||
case 5:
|
||||
plant6.setProperty("switch").send(String("ON"));
|
||||
break;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
digitalWrite(mPlants[i].getPumpPin(), HIGH);
|
||||
mPumpIsRunning=true;
|
||||
}
|
||||
}
|
||||
}
|
||||
mLoopInited = true;
|
||||
|
||||
readAnalogValues();
|
||||
|
||||
if ((millis() % 1500) == 0) {
|
||||
sensorLipo.setProperty("percent").send( String(100 * lipoSenor / 4095) );
|
||||
sensorLipo.setProperty("volt").send( String(ADC_5V_TO_3V3(lipoSenor)) );
|
||||
sensorSolar.setProperty("percent").send(String((100 * solarSensor ) / 4095));
|
||||
sensorSolar.setProperty("volt").send( String(SOLAR_VOLT(solarSensor)) );
|
||||
} else if ((millis() % 1000) == 0) {
|
||||
float temp[2] = { TEMP_INIT_VALUE, TEMP_INIT_VALUE };
|
||||
float* pFloat = temp;
|
||||
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(pFloat[0]));
|
||||
}
|
||||
} else if (devices >= 2) {
|
||||
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]));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Main Loop functionality */
|
||||
if ((!mPumpIsRunning) || (!mWaterAtEmptyLevel) ) {
|
||||
/* let the ESP sleep qickly, as nothing must be done */
|
||||
if ((millis() >= (MIN_TIME_RUNNING * MS_TO_S)) && (deepSleepTime.get() > 0)) {
|
||||
mDeepSleep = true;
|
||||
Serial << "No Water or Pump" << endl;
|
||||
}
|
||||
}
|
||||
|
||||
/* Always check, that after 5 minutes the device is sleeping */
|
||||
/* Pump is running, go to sleep after defined time */
|
||||
if ((millis() >= (((MIN_TIME_RUNNING + abs(wateringTime.get())) * MS_TO_S) + 5)) &&
|
||||
(deepSleepTime.get() > 0)) {
|
||||
Serial << "No sleeping activated (maximum)" << endl;
|
||||
Serial << "Pump was running:" << mPumpIsRunning << "Water level is empty: " << mWaterAtEmptyLevel << endl;
|
||||
mDeepSleep = true;
|
||||
} else if ((millis() >= (((MIN_TIME_RUNNING + abs(wateringTime.get())) * MS_TO_S) + 0)) &&
|
||||
(deepSleepTime.get() > 0)) {
|
||||
Serial << "Maximum time reached: " << endl;
|
||||
Serial << (mPumpIsRunning ? "Pump was running " : "No Pump ") << (mWaterAtEmptyLevel ? "Water level is empty" : "Water available") << endl;
|
||||
mDeepSleep = true;
|
||||
}
|
||||
}
|
||||
|
||||
bool switchGeneralPumpHandler(const int pump, const HomieRange& range, const String& value) {
|
||||
if (range.isRange) return false; // only one switch is present
|
||||
switch (pump)
|
||||
{
|
||||
#if MAX_PLANTS >= 1
|
||||
case 0:
|
||||
#endif
|
||||
#if MAX_PLANTS >= 2
|
||||
case 1:
|
||||
#endif
|
||||
#if MAX_PLANTS >= 3
|
||||
#endif
|
||||
case 2:
|
||||
#if MAX_PLANTS >= 4
|
||||
case 3:
|
||||
#endif
|
||||
#if MAX_PLANTS >= 5
|
||||
case 4:
|
||||
#endif
|
||||
#if MAX_PLANTS >= 6
|
||||
case 5:
|
||||
#endif
|
||||
|
||||
if ((value.equals("ON")) || (value.equals("On")) || (value.equals("on")) || (value.equals("true"))) {
|
||||
digitalWrite(mPlants[pump].getPumpPin(), HIGH);
|
||||
return true;
|
||||
} else if ((value.equals("OFF")) || (value.equals("Off")) || (value.equals("off")) || (value.equals("false")) ) {
|
||||
digitalWrite(mPlants[pump].getPumpPin(), LOW);
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Handle Mqtt commands for the pumpe, responsible for the first plant
|
||||
*
|
||||
* @param range multiple transmitted values (not used for this function)
|
||||
* @param value single value
|
||||
* @return true when the command was parsed and executed succuessfully
|
||||
* @return false on errors when parsing the request
|
||||
*/
|
||||
bool switch1Handler(const HomieRange& range, const String& value) {
|
||||
return switchGeneralPumpHandler(0, range, value);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @brief Handle Mqtt commands for the pumpe, responsible for the second plant
|
||||
*
|
||||
* @param range multiple transmitted values (not used for this function)
|
||||
* @param value single value
|
||||
* @return true when the command was parsed and executed succuessfully
|
||||
* @return false on errors when parsing the request
|
||||
*/
|
||||
bool switch2Handler(const HomieRange& range, const String& value) {
|
||||
return switchGeneralPumpHandler(1, range, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Handle Mqtt commands for the pumpe, responsible for the third plant
|
||||
*
|
||||
* @param range multiple transmitted values (not used for this function)
|
||||
* @param value single value
|
||||
* @return true when the command was parsed and executed succuessfully
|
||||
* @return false on errors when parsing the request
|
||||
*/
|
||||
bool switch3Handler(const HomieRange& range, const String& value) {
|
||||
return switchGeneralPumpHandler(2, range, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Sensors, that are connected to GPIOs, mandatory for WIFI.
|
||||
* These sensors (ADC2) can only be read when no Wifi is used.
|
||||
*/
|
||||
void readSensors() {
|
||||
/* activate all sensors */
|
||||
pinMode(OUTPUT_SENSOR, OUTPUT);
|
||||
digitalWrite(OUTPUT_SENSOR, HIGH);
|
||||
/* Use Pump 4 to activate and deactivate the Sensors */
|
||||
#if (MAX_PLANTS < 4)
|
||||
pinMode(OUTPUT_PUMP4, OUTPUT);
|
||||
digitalWrite(OUTPUT_PUMP4, HIGH);
|
||||
#endif
|
||||
|
||||
delay(100);
|
||||
/* wait before reading something */
|
||||
for (int readCnt=0;readCnt < AMOUNT_SENOR_QUERYS; readCnt++) {
|
||||
for(int i=0; i < MAX_PLANTS; i++) {
|
||||
mPlants[i].addSenseValue(analogRead(mPlants[i].getSensorPin()));
|
||||
}
|
||||
}
|
||||
|
||||
#ifndef HC_SR04
|
||||
mWaterAtEmptyLevel = digitalRead(INPUT_WATER_EMPTY);
|
||||
mWaterLow = digitalRead(INPUT_WATER_LOW);
|
||||
mOverflow = digitalRead(INPUT_WATER_OVERFLOW);
|
||||
#else
|
||||
/* Use the Ultrasonic sensor to measure waterLevel */
|
||||
|
||||
/* deactivate all sensors and measure the pulse */
|
||||
digitalWrite(INPUT_WATER_EMPTY, LOW);
|
||||
delayMicroseconds(2);
|
||||
digitalWrite(INPUT_WATER_EMPTY, HIGH);
|
||||
delayMicroseconds(10);
|
||||
digitalWrite(INPUT_WATER_EMPTY, LOW);
|
||||
float duration = pulseIn(INPUT_WATER_LOW, HIGH);
|
||||
float distance = (duration*.0343)/2;
|
||||
mWaterGone = (int) distance;
|
||||
Serial << "HC_SR04 | Distance : " << String(distance) << " cm" << endl;
|
||||
#endif
|
||||
/* deactivate the sensors */
|
||||
digitalWrite(OUTPUT_SENSOR, LOW);
|
||||
#if (MAX_PLANTS < 4)
|
||||
digitalWrite(OUTPUT_PUMP4, LOW);
|
||||
#endif
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Startup function
|
||||
* Is called once, the controller is started
|
||||
*/
|
||||
void setup() {
|
||||
/* Required to read the temperature once */
|
||||
float temp[2] = {0, 0};
|
||||
float* pFloat = temp;
|
||||
|
||||
/* read button */
|
||||
pinMode(BUTTON, INPUT);
|
||||
/* Prepare Water sensors */
|
||||
pinMode(INPUT_WATER_EMPTY, INPUT);
|
||||
pinMode(INPUT_WATER_LOW, INPUT);
|
||||
pinMode(INPUT_WATER_OVERFLOW, INPUT);
|
||||
|
||||
Serial.begin(115200);
|
||||
Serial.setTimeout(1000); // Set timeout of 1 second
|
||||
Serial << endl << endl;
|
||||
Serial << "Read analog sensors..." << endl;
|
||||
/* Disable Wifi and bluetooth */
|
||||
WiFi.mode(WIFI_OFF);
|
||||
/* now ADC2 can be used */
|
||||
readSensors();
|
||||
/* activate Wifi again */
|
||||
WiFi.mode(WIFI_STA);
|
||||
|
||||
|
||||
Homie_setFirmware("PlantControl", FIRMWARE_VERSION);
|
||||
Homie.setLoopFunction(loopHandler);
|
||||
|
||||
// Load the settings
|
||||
deepSleepTime.setDefaultValue(0);
|
||||
deepSleepNightTime.setDefaultValue(0);
|
||||
wateringTime.setDefaultValue(60);
|
||||
plantCnt.setDefaultValue(0).setValidator([] (long candidate) {
|
||||
return ((candidate >= 0) && (candidate <= 6) );
|
||||
});
|
||||
plant1SensorTrigger.setDefaultValue(0);
|
||||
plant2SensorTrigger.setDefaultValue(0);
|
||||
plant3SensorTrigger.setDefaultValue(0);
|
||||
#if (MAX_PLANTS >= 4)
|
||||
plant4SensorTrigger.setDefaultValue(0);
|
||||
plant5SensorTrigger.setDefaultValue(0);
|
||||
plant6SensorTrigger.setDefaultValue(0);
|
||||
#endif
|
||||
|
||||
#ifdef HC_SR04
|
||||
waterLevel.setDefaultValue(50);
|
||||
#endif
|
||||
|
||||
// Advertise topics
|
||||
plant1.advertise("switch").setName("Pump 1")
|
||||
.setDatatype("boolean")
|
||||
.settable(switch1Handler);
|
||||
plant1.advertise("moist").setName("Percent")
|
||||
.setDatatype("number")
|
||||
.setUnit("%");
|
||||
plant2.advertise("switch").setName("Pump 2")
|
||||
.setDatatype("boolean")
|
||||
.settable(switch2Handler);
|
||||
plant2.advertise("moist").setName("Percent")
|
||||
.setDatatype("number")
|
||||
.setUnit("%");
|
||||
plant3.advertise("switch").setName("Pump 3")
|
||||
.setDatatype("boolean")
|
||||
.settable(switch3Handler);
|
||||
plant3.advertise("moist").setName("Percent")
|
||||
.setDatatype("number")
|
||||
.setUnit("%");
|
||||
#if (MAX_PLANTS >= 4)
|
||||
plant4.advertise("moist").setName("Percent")
|
||||
.setDatatype("number")
|
||||
.setUnit("%");
|
||||
plant5.advertise("moist").setName("Percent")
|
||||
.setDatatype("number")
|
||||
.setUnit("%");
|
||||
plant6.advertise("moist").setName("Percent")
|
||||
.setDatatype("number")
|
||||
.setUnit("%");
|
||||
#endif
|
||||
sensorTemp.advertise("control")
|
||||
.setName("Temperature")
|
||||
.setDatatype("number")
|
||||
.setUnit("°C");
|
||||
sensorTemp.advertise("temp")
|
||||
.setName("Temperature")
|
||||
.setDatatype("number")
|
||||
.setUnit("°C");
|
||||
|
||||
sensorLipo.advertise("percent")
|
||||
.setName("Percent")
|
||||
.setDatatype("number")
|
||||
.setUnit("%");
|
||||
sensorLipo.advertise("volt")
|
||||
.setName("Volt")
|
||||
.setDatatype("number")
|
||||
.setUnit("V");
|
||||
|
||||
sensorSolar.advertise("percent")
|
||||
.setName("Percent")
|
||||
.setDatatype("number")
|
||||
.setUnit("%");
|
||||
sensorSolar.advertise("volt")
|
||||
.setName("Volt")
|
||||
.setDatatype("number")
|
||||
.setUnit("V");
|
||||
sensorWater.advertise("remaining").setDatatype("number").setUnit("%");
|
||||
|
||||
Homie.setup();
|
||||
|
||||
/* Intialize inputs and outputs */
|
||||
for(int i=0; i < plantCnt.get(); i++) {
|
||||
pinMode(mPlants[i].getPumpPin(), OUTPUT);
|
||||
pinMode(mPlants[i].getSensorPin(), ANALOG);
|
||||
digitalWrite(mPlants[i].getPumpPin(), LOW);
|
||||
}
|
||||
/* Setup Solar and Lipo measurement */
|
||||
pinMode(SENSOR_LIPO, ANALOG);
|
||||
pinMode(SENSOR_SOLAR, ANALOG);
|
||||
/* Read analog values at the start */
|
||||
do {
|
||||
readAnalogValues();
|
||||
} while (readCounter != 0);
|
||||
|
||||
|
||||
// Configure Deep Sleep:
|
||||
if ((deepSleepNightTime.get() > 0) &&
|
||||
( SOLAR_VOLT(solarSensor) < MINIMUM_SOLAR_VOLT)) {
|
||||
Serial << "HOMIE | Setup sleeping for " << deepSleepNightTime.get() << " ms as sun is at " << SOLAR_VOLT(solarSensor) << "V" << endl;
|
||||
uint64_t usSleepTime = deepSleepNightTime.get() * 1000U;
|
||||
esp_sleep_enable_timer_wakeup(usSleepTime);
|
||||
}else if (deepSleepTime.get()) {
|
||||
Serial << "HOMIE | Setup sleeping for " << deepSleepTime.get() << " ms" << endl;
|
||||
uint64_t usSleepTime = deepSleepTime.get() * 1000U;
|
||||
esp_sleep_enable_timer_wakeup(usSleepTime);
|
||||
}
|
||||
|
||||
if ( (ADC_5V_TO_3V3(lipoSenor) < MINIMUM_LIPO_VOLT) && (deepSleepTime.get()) ) {
|
||||
long sleepEmptyLipo = (deepSleepTime.get() * EMPTY_LIPO_MULTIPL);
|
||||
Serial << "HOMIE | Change sleeping to " << sleepEmptyLipo << " ms as lipo is at " << ADC_5V_TO_3V3(lipoSenor) << "V" << endl;
|
||||
esp_sleep_enable_timer_wakeup(sleepEmptyLipo * 1000U);
|
||||
mDeepSleep = true;
|
||||
}
|
||||
|
||||
/* Read the temperature sensors once, as first time 85 degree is returned */
|
||||
Serial << "DS18B20 | sensors: " << String(dallas.readDevices()) << endl;
|
||||
delay(200);
|
||||
if (dallas.readAllTemperatures(pFloat, 2) > 0) {
|
||||
Serial << "DS18B20 | Temperature 1: " << String(temp[0]) << endl;
|
||||
Serial << "DS18B20 | Temperature 2: " << String(temp[1]) << endl;
|
||||
}
|
||||
delay(200);
|
||||
if (dallas.readAllTemperatures(pFloat, 2) > 0) {
|
||||
Serial << "Temperature 1: " << String(temp[0]) << endl;
|
||||
Serial << "Temperature 2: " << String(temp[1]) << endl;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Cyclic call
|
||||
* Executs the Homie base functionallity or triggers sleeping, if requested.
|
||||
*/
|
||||
void loop() {
|
||||
if (!mDeepSleep) {
|
||||
if (Serial.available() > 0) {
|
||||
// read the incoming byte:
|
||||
int incomingByte = Serial.read();
|
||||
|
||||
switch ((char) incomingByte)
|
||||
{
|
||||
case 'P':
|
||||
Serial << "Activate Sensor OUTPUT " << endl;
|
||||
pinMode(OUTPUT_SENSOR, OUTPUT);
|
||||
digitalWrite(OUTPUT_SENSOR, HIGH);
|
||||
break;
|
||||
case 'p':
|
||||
Serial << "Deactivate Sensor OUTPUT " << endl;
|
||||
pinMode(OUTPUT_SENSOR, OUTPUT);
|
||||
digitalWrite(OUTPUT_SENSOR, LOW);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if ((digitalRead(BUTTON) == LOW) && (mButtonClicks % 2) == 0) {
|
||||
float temp[2] = {0, 0};
|
||||
float* pFloat = temp;
|
||||
mButtonClicks++;
|
||||
|
||||
Serial << "SELF TEST (clicks: " << String(mButtonClicks) << ")" << endl;
|
||||
|
||||
Serial << "DS18B20 sensors: " << String(dallas.readDevices()) << endl;
|
||||
delay(200);
|
||||
if (dallas.readAllTemperatures(pFloat, 2) > 0) {
|
||||
Serial << "Temperature 1: " << String(temp[0]) << endl;
|
||||
Serial << "Temperature 2: " << String(temp[1]) << endl;
|
||||
}
|
||||
|
||||
switch(mButtonClicks) {
|
||||
case 1:
|
||||
case 3:
|
||||
case 5:
|
||||
if (mButtonClicks > 1) {
|
||||
Serial << "Read analog sensors..." << endl;
|
||||
/* Disable Wifi and bluetooth */
|
||||
WiFi.mode(WIFI_OFF);
|
||||
delay(50);
|
||||
/* now ADC2 can be used */
|
||||
readSensors();
|
||||
}
|
||||
#ifndef HC_SR04
|
||||
Serial << "Water Low: " << String(mWaterLow) << endl;
|
||||
Serial << "Water Empty: " << String(mWaterAtEmptyLevel) << endl;
|
||||
Serial << "Water Overflow: " << String(mOverflow) << endl;
|
||||
#else
|
||||
Serial << "Water gone: " << String(mWaterGone) << " cm" << endl;
|
||||
#endif
|
||||
for(int i=0; i < MAX_PLANTS; i++) {
|
||||
mPlants[i].calculateSensorValue(AMOUNT_SENOR_QUERYS);
|
||||
|
||||
Serial << "Moist Sensor " << (i+1) << ": " << String(mPlants[i].getSensorValue()) << " Volt: " << String(ADC_5V_TO_3V3(mPlants[i].getSensorValue())) << endl;
|
||||
}
|
||||
/* Read enough values */
|
||||
do {
|
||||
readAnalogValues();
|
||||
Serial << "Read Analog (" << String(readCounter) << ")" << endl;;
|
||||
} while (readCounter != 0);
|
||||
|
||||
Serial << "Lipo Sensor - Raw: " << String(lipoSenor) << " Volt: " << String(ADC_5V_TO_3V3(lipoSenor)) << endl;
|
||||
Serial << "Solar Sensor - Raw: " << String(solarSensor) << " Volt: " << String(SOLAR_VOLT(solarSensor)) << endl;
|
||||
break;
|
||||
case 7:
|
||||
Serial << "Activate Sensor OUTPUT " << endl;
|
||||
pinMode(OUTPUT_SENSOR, OUTPUT);
|
||||
digitalWrite(OUTPUT_SENSOR, HIGH);
|
||||
break;
|
||||
case 9:
|
||||
Serial << "Activate Pump1 GPIO" << String(mPlants[0].getPumpPin()) << endl;
|
||||
digitalWrite(mPlants[0].getPumpPin(), HIGH);
|
||||
break;
|
||||
case 11:
|
||||
Serial << "Activate Pump2 GPIO" << String(mPlants[1].getPumpPin()) << endl;
|
||||
digitalWrite(mPlants[1].getPumpPin(), HIGH);
|
||||
break;
|
||||
case 13:
|
||||
Serial << "Activate Pump3 GPIO" << String(mPlants[2].getPumpPin()) << endl;
|
||||
digitalWrite(mPlants[2].getPumpPin(), HIGH);
|
||||
break;
|
||||
case 15:
|
||||
Serial << "Activate Pump4/Sensor GPIO" << String(OUTPUT_PUMP4) << endl;
|
||||
digitalWrite(OUTPUT_PUMP4, HIGH);
|
||||
break;
|
||||
default:
|
||||
Serial << "No further tests! Please reboot" << endl;
|
||||
}
|
||||
Serial.flush();
|
||||
}else if (mButtonClicks > 0 && (digitalRead(BUTTON) == HIGH) && (mButtonClicks % 2) == 1) {
|
||||
Serial << "Self Test Ended" << endl;
|
||||
mButtonClicks++;
|
||||
/* Always reset all outputs */
|
||||
digitalWrite(OUTPUT_SENSOR, LOW);
|
||||
for(int i=0; i < MAX_PLANTS; i++) {
|
||||
digitalWrite(mPlants[i].getPumpPin(), LOW);
|
||||
}
|
||||
digitalWrite(OUTPUT_PUMP4, LOW);
|
||||
} else if (mButtonClicks == 0) {
|
||||
Homie.loop();
|
||||
}
|
||||
|
||||
} else {
|
||||
Serial << (millis()/ 1000) << "s running; sleeeping ..." << endl;
|
||||
Serial.flush();
|
||||
esp_deep_sleep_start();
|
||||
}
|
||||
}
|
11
esp32/test/README
Normal file
11
esp32/test/README
Normal file
@ -0,0 +1,11 @@
|
||||
|
||||
This directory is intended for PIO Unit Testing and project tests.
|
||||
|
||||
Unit Testing is a software testing method by which individual units of
|
||||
source code, sets of one or more MCU program modules together with associated
|
||||
control data, usage procedures, and operating procedures, are tested to
|
||||
determine whether they are fit for use. Unit testing finds problems early
|
||||
in the development cycle.
|
||||
|
||||
More information about PIO Unit Testing:
|
||||
- https://docs.platformio.org/page/plus/unit-testing.html
|
Loading…
Reference in New Issue
Block a user