Compare commits
No commits in common. "feature/2.0" and "v2.x" have entirely different histories.
feature/2.
...
v2.x
File diff suppressed because it is too large
Load Diff
@ -1,6 +1,6 @@
|
||||
{
|
||||
"board": {
|
||||
"active_layer": 36,
|
||||
"active_layer": 39,
|
||||
"active_layer_preset": "",
|
||||
"auto_track_width": false,
|
||||
"hidden_netclasses": [],
|
||||
@ -8,7 +8,7 @@
|
||||
"high_contrast_mode": 0,
|
||||
"net_color_mode": 1,
|
||||
"opacity": {
|
||||
"images": 0.4399999976158142,
|
||||
"images": 0.6,
|
||||
"pads": 1.0,
|
||||
"tracks": 1.0,
|
||||
"vias": 1.0,
|
||||
@ -68,7 +68,7 @@
|
||||
39,
|
||||
40
|
||||
],
|
||||
"visible_layers": "ffc7055_fffffff8",
|
||||
"visible_layers": "ffdfffe_ffffffff",
|
||||
"zone_display_mode": 1
|
||||
},
|
||||
"git": {
|
||||
|
@ -37,9 +37,9 @@
|
||||
"other_text_thickness": 0.15,
|
||||
"other_text_upright": false,
|
||||
"pads": {
|
||||
"drill": 0.25,
|
||||
"height": 0.35,
|
||||
"width": 0.35
|
||||
"drill": 0.0,
|
||||
"height": 3.0,
|
||||
"width": 1.6
|
||||
},
|
||||
"silk_line_width": 0.12,
|
||||
"silk_text_italic": false,
|
||||
@ -58,31 +58,13 @@
|
||||
"width": 0.0
|
||||
}
|
||||
],
|
||||
"drc_exclusions": [
|
||||
"footprint_symbol_mismatch|177050000|59025000|a624af3d-bffa-4ff7-9554-e16d3c677f69|00000000-0000-0000-0000-000000000000",
|
||||
"footprint_symbol_mismatch|237580000|53970000|c9d8d35b-26b7-4992-9d25-be9130d57b1a|00000000-0000-0000-0000-000000000000",
|
||||
"footprint_symbol_mismatch|256580000|49370000|b33af7ef-63da-4a51-8d8a-183cadd974de|00000000-0000-0000-0000-000000000000",
|
||||
"net_conflict|177050000|59025000|09cad967-1882-4dd3-8900-445282e228e5|00000000-0000-0000-0000-000000000000",
|
||||
"net_conflict|177050000|59025000|20ab85c0-b3f3-4826-a86d-065fee01e11f|00000000-0000-0000-0000-000000000000",
|
||||
"net_conflict|177050000|59025000|3da9717d-9800-42f9-97d1-56d23bf085aa|00000000-0000-0000-0000-000000000000",
|
||||
"net_conflict|177050000|59025000|444aab2b-3a9b-444e-b60c-b5b5ff830942|00000000-0000-0000-0000-000000000000",
|
||||
"net_conflict|177050000|59025000|6b067fd3-d374-4937-8779-958994d9163b|00000000-0000-0000-0000-000000000000",
|
||||
"net_conflict|177050000|59025000|9839c562-7672-4ea8-a74d-bea83ae26677|00000000-0000-0000-0000-000000000000",
|
||||
"net_conflict|177050000|59025000|9ce2df19-edf4-40d2-8e85-8c33008b8df0|00000000-0000-0000-0000-000000000000",
|
||||
"net_conflict|177050000|59025000|a8ab716a-cd1e-4842-ad8e-3d6d1db9770b|00000000-0000-0000-0000-000000000000",
|
||||
"net_conflict|177050000|59025000|aaf09ae3-4ace-49d7-a050-44cb4c93d63b|00000000-0000-0000-0000-000000000000",
|
||||
"net_conflict|177050000|59025000|af55e8a2-ba8d-462e-807f-99ca5906f801|00000000-0000-0000-0000-000000000000",
|
||||
"net_conflict|177050000|59025000|c36efd78-869f-40e7-86fc-97e5ed683fec|00000000-0000-0000-0000-000000000000",
|
||||
"net_conflict|177050000|59025000|d668fda0-e4be-4e1f-95b8-8cd59a67cb21|00000000-0000-0000-0000-000000000000",
|
||||
"net_conflict|177050000|59025000|d99401c6-2b75-46f7-8616-cdd7755709ee|00000000-0000-0000-0000-000000000000",
|
||||
"net_conflict|177050000|59025000|f1fd5816-e8bd-4ba6-9d53-54b58d25e2dc|00000000-0000-0000-0000-000000000000"
|
||||
],
|
||||
"drc_exclusions": [],
|
||||
"meta": {
|
||||
"filename": "board_design_settings.json",
|
||||
"version": 2
|
||||
},
|
||||
"rule_severities": {
|
||||
"annular_width": "ignore",
|
||||
"annular_width": "error",
|
||||
"clearance": "error",
|
||||
"connection_width": "warning",
|
||||
"copper_edge_clearance": "error",
|
||||
@ -114,7 +96,7 @@
|
||||
"padstack": "warning",
|
||||
"pth_inside_courtyard": "ignore",
|
||||
"shorting_items": "error",
|
||||
"silk_edge_clearance": "ignore",
|
||||
"silk_edge_clearance": "warning",
|
||||
"silk_over_copper": "ignore",
|
||||
"silk_overlap": "ignore",
|
||||
"skew_out_of_range": "error",
|
||||
@ -1118,133 +1100,13 @@
|
||||
"label": "DNP",
|
||||
"name": "${DNP}",
|
||||
"show": true
|
||||
},
|
||||
{
|
||||
"group_by": false,
|
||||
"label": "#",
|
||||
"name": "${ITEM_NUMBER}",
|
||||
"show": false
|
||||
},
|
||||
{
|
||||
"group_by": false,
|
||||
"label": "Availability",
|
||||
"name": "Availability",
|
||||
"show": false
|
||||
},
|
||||
{
|
||||
"group_by": false,
|
||||
"label": "Check_prices",
|
||||
"name": "Check_prices",
|
||||
"show": false
|
||||
},
|
||||
{
|
||||
"group_by": false,
|
||||
"label": "Description_1",
|
||||
"name": "Description_1",
|
||||
"show": false
|
||||
},
|
||||
{
|
||||
"group_by": false,
|
||||
"label": "LCSC",
|
||||
"name": "LCSC",
|
||||
"show": false
|
||||
},
|
||||
{
|
||||
"group_by": false,
|
||||
"label": "LCSC_PART_NUMBER",
|
||||
"name": "LCSC_PART_NUMBER",
|
||||
"show": true
|
||||
},
|
||||
{
|
||||
"group_by": false,
|
||||
"label": "MANUFACTURER",
|
||||
"name": "MANUFACTURER",
|
||||
"show": false
|
||||
},
|
||||
{
|
||||
"group_by": false,
|
||||
"label": "MAXIMUM_PACKAGE_HEIGHT",
|
||||
"name": "MAXIMUM_PACKAGE_HEIGHT",
|
||||
"show": false
|
||||
},
|
||||
{
|
||||
"group_by": false,
|
||||
"label": "MF",
|
||||
"name": "MF",
|
||||
"show": false
|
||||
},
|
||||
{
|
||||
"group_by": false,
|
||||
"label": "MP",
|
||||
"name": "MP",
|
||||
"show": false
|
||||
},
|
||||
{
|
||||
"group_by": false,
|
||||
"label": "PARTREV",
|
||||
"name": "PARTREV",
|
||||
"show": false
|
||||
},
|
||||
{
|
||||
"group_by": false,
|
||||
"label": "Package",
|
||||
"name": "Package",
|
||||
"show": false
|
||||
},
|
||||
{
|
||||
"group_by": false,
|
||||
"label": "Price",
|
||||
"name": "Price",
|
||||
"show": false
|
||||
},
|
||||
{
|
||||
"group_by": false,
|
||||
"label": "Purchase-URL",
|
||||
"name": "Purchase-URL",
|
||||
"show": false
|
||||
},
|
||||
{
|
||||
"group_by": false,
|
||||
"label": "STANDARD",
|
||||
"name": "STANDARD",
|
||||
"show": false
|
||||
},
|
||||
{
|
||||
"group_by": false,
|
||||
"label": "Sim.Device",
|
||||
"name": "Sim.Device",
|
||||
"show": false
|
||||
},
|
||||
{
|
||||
"group_by": false,
|
||||
"label": "Sim.Pins",
|
||||
"name": "Sim.Pins",
|
||||
"show": false
|
||||
},
|
||||
{
|
||||
"group_by": false,
|
||||
"label": "Sim.Type",
|
||||
"name": "Sim.Type",
|
||||
"show": false
|
||||
},
|
||||
{
|
||||
"group_by": false,
|
||||
"label": "SnapEDA_Link",
|
||||
"name": "SnapEDA_Link",
|
||||
"show": false
|
||||
},
|
||||
{
|
||||
"group_by": false,
|
||||
"label": "Description",
|
||||
"name": "Description",
|
||||
"show": false
|
||||
}
|
||||
],
|
||||
"filter_string": "",
|
||||
"group_symbols": true,
|
||||
"name": "",
|
||||
"name": "Grouped By Value",
|
||||
"sort_asc": true,
|
||||
"sort_field": "LCSC_PART_NUMBER"
|
||||
"sort_field": "Reference"
|
||||
},
|
||||
"connection_grid_size": 50.0,
|
||||
"drawing": {
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -1,354 +0,0 @@
|
||||
|
||||
(kicad_symbol_lib (version 20211014) (generator kicad_symbol_editor)
|
||||
(symbol "ESP32-C6-WROOM-1-N8" (pin_names (offset 1.016)) (in_bom yes) (on_board yes)
|
||||
(property "Reference" "U" (id 0) (at -15.24 23.622 0)
|
||||
(effects (font (size 1.27 1.27)) (justify bottom left))
|
||||
)
|
||||
(property "Value" "ESP32-C6-WROOM-1-N8" (id 1) (at -15.24 -25.4 0)
|
||||
(effects (font (size 1.27 1.27)) (justify bottom left))
|
||||
)
|
||||
(property "Footprint" "ESP32-C6-WROOM-1-N8:XCVR_ESP32-C6-WROOM-1-N8" (id 2) (at 0 0 0)
|
||||
(effects (font (size 1.27 1.27)) (justify bottom) hide)
|
||||
)
|
||||
(property "MF" "Espressif Systems" (id 4) (at 0 0 0)
|
||||
(effects (font (size 1.27 1.27)) (justify bottom) hide)
|
||||
)
|
||||
(property "MAXIMUM_PACKAGE_HEIGHT" "3.25mm" (id 5) (at 0 0 0)
|
||||
(effects (font (size 1.27 1.27)) (justify bottom) hide)
|
||||
)
|
||||
(property "Package" "None" (id 6) (at 0 0 0)
|
||||
(effects (font (size 1.27 1.27)) (justify bottom) hide)
|
||||
)
|
||||
(property "Price" "None" (id 7) (at 0 0 0)
|
||||
(effects (font (size 1.27 1.27)) (justify bottom) hide)
|
||||
)
|
||||
(property "Check_prices" "https://www.snapeda.com/parts/ESP32-C6-WROOM-1-N8/Espressif+Systems/view-part/?ref=eda" (id 8) (at 0 0 0)
|
||||
(effects (font (size 1.27 1.27)) (justify bottom) hide)
|
||||
)
|
||||
(property "STANDARD" "Manufacturer Recommendations" (id 9) (at 0 0 0)
|
||||
(effects (font (size 1.27 1.27)) (justify bottom) hide)
|
||||
)
|
||||
(property "PARTREV" "1.0" (id 10) (at 0 0 0)
|
||||
(effects (font (size 1.27 1.27)) (justify bottom) hide)
|
||||
)
|
||||
(property "SnapEDA_Link" "https://www.snapeda.com/parts/ESP32-C6-WROOM-1-N8/Espressif+Systems/view-part/?ref=snap" (id 11) (at 0 0 0)
|
||||
(effects (font (size 1.27 1.27)) (justify bottom) hide)
|
||||
)
|
||||
(property "MP" "ESP32-C6-WROOM-1-N8" (id 12) (at 0 0 0)
|
||||
(effects (font (size 1.27 1.27)) (justify bottom) hide)
|
||||
)
|
||||
(property "Purchase-URL" "https://www.snapeda.com/api/url_track_click_mouser/?unipart_id=12616380&manufacturer=Espressif Systems&part_name=ESP32-C6-WROOM-1-N8&search_term=None" (id 13) (at 0 0 0)
|
||||
(effects (font (size 1.27 1.27)) (justify bottom) hide)
|
||||
)
|
||||
(property "Description" "\nMultiprotocol Modules ESP32-C6 module, Wi-Fi 6 in 2.4 GHz band, Bluetooth 5, Zigbee 3.0 and Thread. ESP34-WROOM Compatible - ENGINEERING SAMPLE\n" (id 14) (at 0 0 0)
|
||||
(effects (font (size 1.27 1.27)) (justify bottom) hide)
|
||||
)
|
||||
(property "Availability" "In Stock" (id 15) (at 0 0 0)
|
||||
(effects (font (size 1.27 1.27)) (justify bottom) hide)
|
||||
)
|
||||
(property "MANUFACTURER" "Espressif Systems" (id 16) (at 0 0 0)
|
||||
(effects (font (size 1.27 1.27)) (justify bottom) hide)
|
||||
)
|
||||
(symbol "ESP32-C6-WROOM-1-N8_0_0"
|
||||
(rectangle (start -15.24 -22.86) (end 15.24 22.86)
|
||||
(stroke (width 0.254)) (fill (type background))
|
||||
)
|
||||
(pin power_in line (at 20.32 -20.32 180.0) (length 5.08)
|
||||
(name "GND"
|
||||
(effects (font (size 1.016 1.016)))
|
||||
)
|
||||
(number "1"
|
||||
(effects (font (size 1.016 1.016)))
|
||||
)
|
||||
)
|
||||
(pin power_in line (at 20.32 -20.32 180.0) (length 5.08)
|
||||
(name "GND"
|
||||
(effects (font (size 1.016 1.016)))
|
||||
)
|
||||
(number "28"
|
||||
(effects (font (size 1.016 1.016)))
|
||||
)
|
||||
)
|
||||
(pin power_in line (at 20.32 -20.32 180.0) (length 5.08)
|
||||
(name "GND"
|
||||
(effects (font (size 1.016 1.016)))
|
||||
)
|
||||
(number "29_1"
|
||||
(effects (font (size 1.016 1.016)))
|
||||
)
|
||||
)
|
||||
(pin power_in line (at 20.32 -20.32 180.0) (length 5.08)
|
||||
(name "GND"
|
||||
(effects (font (size 1.016 1.016)))
|
||||
)
|
||||
(number "29_2"
|
||||
(effects (font (size 1.016 1.016)))
|
||||
)
|
||||
)
|
||||
(pin power_in line (at 20.32 -20.32 180.0) (length 5.08)
|
||||
(name "GND"
|
||||
(effects (font (size 1.016 1.016)))
|
||||
)
|
||||
(number "29_3"
|
||||
(effects (font (size 1.016 1.016)))
|
||||
)
|
||||
)
|
||||
(pin power_in line (at 20.32 -20.32 180.0) (length 5.08)
|
||||
(name "GND"
|
||||
(effects (font (size 1.016 1.016)))
|
||||
)
|
||||
(number "29_4"
|
||||
(effects (font (size 1.016 1.016)))
|
||||
)
|
||||
)
|
||||
(pin power_in line (at 20.32 -20.32 180.0) (length 5.08)
|
||||
(name "GND"
|
||||
(effects (font (size 1.016 1.016)))
|
||||
)
|
||||
(number "29_5"
|
||||
(effects (font (size 1.016 1.016)))
|
||||
)
|
||||
)
|
||||
(pin power_in line (at 20.32 -20.32 180.0) (length 5.08)
|
||||
(name "GND"
|
||||
(effects (font (size 1.016 1.016)))
|
||||
)
|
||||
(number "29_6"
|
||||
(effects (font (size 1.016 1.016)))
|
||||
)
|
||||
)
|
||||
(pin power_in line (at 20.32 -20.32 180.0) (length 5.08)
|
||||
(name "GND"
|
||||
(effects (font (size 1.016 1.016)))
|
||||
)
|
||||
(number "29_7"
|
||||
(effects (font (size 1.016 1.016)))
|
||||
)
|
||||
)
|
||||
(pin power_in line (at 20.32 -20.32 180.0) (length 5.08)
|
||||
(name "GND"
|
||||
(effects (font (size 1.016 1.016)))
|
||||
)
|
||||
(number "29_8"
|
||||
(effects (font (size 1.016 1.016)))
|
||||
)
|
||||
)
|
||||
(pin power_in line (at 20.32 -20.32 180.0) (length 5.08)
|
||||
(name "GND"
|
||||
(effects (font (size 1.016 1.016)))
|
||||
)
|
||||
(number "29_9"
|
||||
(effects (font (size 1.016 1.016)))
|
||||
)
|
||||
)
|
||||
(pin power_in line (at 20.32 20.32 180.0) (length 5.08)
|
||||
(name "3V3"
|
||||
(effects (font (size 1.016 1.016)))
|
||||
)
|
||||
(number "2"
|
||||
(effects (font (size 1.016 1.016)))
|
||||
)
|
||||
)
|
||||
(pin input line (at -20.32 15.24 0) (length 5.08)
|
||||
(name "EN"
|
||||
(effects (font (size 1.016 1.016)))
|
||||
)
|
||||
(number "3"
|
||||
(effects (font (size 1.016 1.016)))
|
||||
)
|
||||
)
|
||||
(pin bidirectional line (at -20.32 0.0 0) (length 5.08)
|
||||
(name "IO4"
|
||||
(effects (font (size 1.016 1.016)))
|
||||
)
|
||||
(number "4"
|
||||
(effects (font (size 1.016 1.016)))
|
||||
)
|
||||
)
|
||||
(pin bidirectional line (at -20.32 -2.54 0) (length 5.08)
|
||||
(name "IO5"
|
||||
(effects (font (size 1.016 1.016)))
|
||||
)
|
||||
(number "5"
|
||||
(effects (font (size 1.016 1.016)))
|
||||
)
|
||||
)
|
||||
(pin bidirectional line (at -20.32 -5.08 0) (length 5.08)
|
||||
(name "IO6"
|
||||
(effects (font (size 1.016 1.016)))
|
||||
)
|
||||
(number "6"
|
||||
(effects (font (size 1.016 1.016)))
|
||||
)
|
||||
)
|
||||
(pin bidirectional line (at -20.32 -7.62 0) (length 5.08)
|
||||
(name "IO7"
|
||||
(effects (font (size 1.016 1.016)))
|
||||
)
|
||||
(number "7"
|
||||
(effects (font (size 1.016 1.016)))
|
||||
)
|
||||
)
|
||||
(pin bidirectional line (at -20.32 10.16 0) (length 5.08)
|
||||
(name "IO0"
|
||||
(effects (font (size 1.016 1.016)))
|
||||
)
|
||||
(number "8"
|
||||
(effects (font (size 1.016 1.016)))
|
||||
)
|
||||
)
|
||||
(pin bidirectional line (at -20.32 7.62 0) (length 5.08)
|
||||
(name "IO1"
|
||||
(effects (font (size 1.016 1.016)))
|
||||
)
|
||||
(number "9"
|
||||
(effects (font (size 1.016 1.016)))
|
||||
)
|
||||
)
|
||||
(pin bidirectional line (at -20.32 -10.16 0) (length 5.08)
|
||||
(name "IO8"
|
||||
(effects (font (size 1.016 1.016)))
|
||||
)
|
||||
(number "10"
|
||||
(effects (font (size 1.016 1.016)))
|
||||
)
|
||||
)
|
||||
(pin bidirectional line (at 20.32 15.24 180.0) (length 5.08)
|
||||
(name "IO10"
|
||||
(effects (font (size 1.016 1.016)))
|
||||
)
|
||||
(number "11"
|
||||
(effects (font (size 1.016 1.016)))
|
||||
)
|
||||
)
|
||||
(pin bidirectional line (at 20.32 12.7 180.0) (length 5.08)
|
||||
(name "IO11"
|
||||
(effects (font (size 1.016 1.016)))
|
||||
)
|
||||
(number "12"
|
||||
(effects (font (size 1.016 1.016)))
|
||||
)
|
||||
)
|
||||
(pin bidirectional line (at 20.32 10.16 180.0) (length 5.08)
|
||||
(name "IO12"
|
||||
(effects (font (size 1.016 1.016)))
|
||||
)
|
||||
(number "13"
|
||||
(effects (font (size 1.016 1.016)))
|
||||
)
|
||||
)
|
||||
(pin bidirectional line (at 20.32 7.62 180.0) (length 5.08)
|
||||
(name "IO13"
|
||||
(effects (font (size 1.016 1.016)))
|
||||
)
|
||||
(number "14"
|
||||
(effects (font (size 1.016 1.016)))
|
||||
)
|
||||
)
|
||||
(pin bidirectional line (at -20.32 5.08 0) (length 5.08)
|
||||
(name "IO2"
|
||||
(effects (font (size 1.016 1.016)))
|
||||
)
|
||||
(number "27"
|
||||
(effects (font (size 1.016 1.016)))
|
||||
)
|
||||
)
|
||||
(pin bidirectional line (at -20.32 2.54 0) (length 5.08)
|
||||
(name "IO3"
|
||||
(effects (font (size 1.016 1.016)))
|
||||
)
|
||||
(number "26"
|
||||
(effects (font (size 1.016 1.016)))
|
||||
)
|
||||
)
|
||||
(pin bidirectional line (at 20.32 2.54 180.0) (length 5.08)
|
||||
(name "TXD0/GPIO16"
|
||||
(effects (font (size 1.016 1.016)))
|
||||
)
|
||||
(number "25"
|
||||
(effects (font (size 1.016 1.016)))
|
||||
)
|
||||
)
|
||||
(pin bidirectional line (at 20.32 0.0 180.0) (length 5.08)
|
||||
(name "RXD0/GPIO17"
|
||||
(effects (font (size 1.016 1.016)))
|
||||
)
|
||||
(number "24"
|
||||
(effects (font (size 1.016 1.016)))
|
||||
)
|
||||
)
|
||||
(pin bidirectional line (at 20.32 5.08 180.0) (length 5.08)
|
||||
(name "IO15"
|
||||
(effects (font (size 1.016 1.016)))
|
||||
)
|
||||
(number "23"
|
||||
(effects (font (size 1.016 1.016)))
|
||||
)
|
||||
)
|
||||
(pin no_connect line (at -20.32 -17.78 0) (length 5.08)
|
||||
(name "NC"
|
||||
(effects (font (size 1.016 1.016)))
|
||||
)
|
||||
(number "22"
|
||||
(effects (font (size 1.016 1.016)))
|
||||
)
|
||||
)
|
||||
(pin bidirectional line (at 20.32 -15.24 180.0) (length 5.08)
|
||||
(name "IO23"
|
||||
(effects (font (size 1.016 1.016)))
|
||||
)
|
||||
(number "21"
|
||||
(effects (font (size 1.016 1.016)))
|
||||
)
|
||||
)
|
||||
(pin bidirectional line (at 20.32 -12.7 180.0) (length 5.08)
|
||||
(name "IO22"
|
||||
(effects (font (size 1.016 1.016)))
|
||||
)
|
||||
(number "20"
|
||||
(effects (font (size 1.016 1.016)))
|
||||
)
|
||||
)
|
||||
(pin bidirectional line (at 20.32 -10.16 180.0) (length 5.08)
|
||||
(name "IO21"
|
||||
(effects (font (size 1.016 1.016)))
|
||||
)
|
||||
(number "19"
|
||||
(effects (font (size 1.016 1.016)))
|
||||
)
|
||||
)
|
||||
(pin bidirectional line (at 20.32 -7.62 180.0) (length 5.08)
|
||||
(name "IO20"
|
||||
(effects (font (size 1.016 1.016)))
|
||||
)
|
||||
(number "18"
|
||||
(effects (font (size 1.016 1.016)))
|
||||
)
|
||||
)
|
||||
(pin bidirectional line (at 20.32 -5.08 180.0) (length 5.08)
|
||||
(name "IO19"
|
||||
(effects (font (size 1.016 1.016)))
|
||||
)
|
||||
(number "17"
|
||||
(effects (font (size 1.016 1.016)))
|
||||
)
|
||||
)
|
||||
(pin bidirectional line (at 20.32 -2.54 180.0) (length 5.08)
|
||||
(name "IO18"
|
||||
(effects (font (size 1.016 1.016)))
|
||||
)
|
||||
(number "16"
|
||||
(effects (font (size 1.016 1.016)))
|
||||
)
|
||||
)
|
||||
(pin bidirectional line (at -20.32 -12.7 0) (length 5.08)
|
||||
(name "IO9"
|
||||
(effects (font (size 1.016 1.016)))
|
||||
)
|
||||
(number "15"
|
||||
(effects (font (size 1.016 1.016)))
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
@ -1,104 +0,0 @@
|
||||
|
||||
(footprint XCVR_ESP32-C6-WROOM-1-N8 (layer F.Cu) (tedit 66216AE3)
|
||||
(descr "")
|
||||
(attr smd)
|
||||
(fp_text reference REF** (at -5.825 -13.885 0) (layer F.SilkS)
|
||||
(effects (font (size 1.0 1.0) (thickness 0.15)))
|
||||
)
|
||||
(fp_text value XCVR_ESP32-C6-WROOM-1-N8 (at 6.24 13.865 0) (layer F.Fab)
|
||||
(effects (font (size 1.0 1.0) (thickness 0.15)))
|
||||
)
|
||||
(pad 1 smd rect (at -8.75 -5.26) (size 1.5 0.9) (layers F.Cu F.Mask F.Paste) (solder_mask_margin 0.102))
|
||||
(pad 2 smd rect (at -8.75 -3.99) (size 1.5 0.9) (layers F.Cu F.Mask F.Paste) (solder_mask_margin 0.102))
|
||||
(pad 3 smd rect (at -8.75 -2.72) (size 1.5 0.9) (layers F.Cu F.Mask F.Paste) (solder_mask_margin 0.102))
|
||||
(pad 4 smd rect (at -8.75 -1.45) (size 1.5 0.9) (layers F.Cu F.Mask F.Paste) (solder_mask_margin 0.102))
|
||||
(pad 5 smd rect (at -8.75 -0.18) (size 1.5 0.9) (layers F.Cu F.Mask F.Paste) (solder_mask_margin 0.102))
|
||||
(pad 6 smd rect (at -8.75 1.09) (size 1.5 0.9) (layers F.Cu F.Mask F.Paste) (solder_mask_margin 0.102))
|
||||
(pad 7 smd rect (at -8.75 2.36) (size 1.5 0.9) (layers F.Cu F.Mask F.Paste) (solder_mask_margin 0.102))
|
||||
(pad 8 smd rect (at -8.75 3.63) (size 1.5 0.9) (layers F.Cu F.Mask F.Paste) (solder_mask_margin 0.102))
|
||||
(pad 9 smd rect (at -8.75 4.9) (size 1.5 0.9) (layers F.Cu F.Mask F.Paste) (solder_mask_margin 0.102))
|
||||
(pad 10 smd rect (at -8.75 6.17) (size 1.5 0.9) (layers F.Cu F.Mask F.Paste) (solder_mask_margin 0.102))
|
||||
(pad 11 smd rect (at -8.75 7.44) (size 1.5 0.9) (layers F.Cu F.Mask F.Paste) (solder_mask_margin 0.102))
|
||||
(pad 12 smd rect (at -8.75 8.71) (size 1.5 0.9) (layers F.Cu F.Mask F.Paste) (solder_mask_margin 0.102))
|
||||
(pad 13 smd rect (at -8.75 9.98) (size 1.5 0.9) (layers F.Cu F.Mask F.Paste) (solder_mask_margin 0.102))
|
||||
(pad 14 smd rect (at -8.75 11.25) (size 1.5 0.9) (layers F.Cu F.Mask F.Paste) (solder_mask_margin 0.102))
|
||||
(pad 15 smd rect (at 8.75 11.25) (size 1.5 0.9) (layers F.Cu F.Mask F.Paste) (solder_mask_margin 0.102))
|
||||
(pad 16 smd rect (at 8.75 9.98) (size 1.5 0.9) (layers F.Cu F.Mask F.Paste) (solder_mask_margin 0.102))
|
||||
(pad 17 smd rect (at 8.75 8.71) (size 1.5 0.9) (layers F.Cu F.Mask F.Paste) (solder_mask_margin 0.102))
|
||||
(pad 18 smd rect (at 8.75 7.44) (size 1.5 0.9) (layers F.Cu F.Mask F.Paste) (solder_mask_margin 0.102))
|
||||
(pad 19 smd rect (at 8.75 6.17) (size 1.5 0.9) (layers F.Cu F.Mask F.Paste) (solder_mask_margin 0.102))
|
||||
(pad 20 smd rect (at 8.75 4.9) (size 1.5 0.9) (layers F.Cu F.Mask F.Paste) (solder_mask_margin 0.102))
|
||||
(pad 21 smd rect (at 8.75 3.63) (size 1.5 0.9) (layers F.Cu F.Mask F.Paste) (solder_mask_margin 0.102))
|
||||
(pad 22 smd rect (at 8.75 2.36) (size 1.5 0.9) (layers F.Cu F.Mask F.Paste) (solder_mask_margin 0.102))
|
||||
(pad 23 smd rect (at 8.75 1.09) (size 1.5 0.9) (layers F.Cu F.Mask F.Paste) (solder_mask_margin 0.102))
|
||||
(pad 24 smd rect (at 8.75 -0.18) (size 1.5 0.9) (layers F.Cu F.Mask F.Paste) (solder_mask_margin 0.102))
|
||||
(pad 25 smd rect (at 8.75 -1.45) (size 1.5 0.9) (layers F.Cu F.Mask F.Paste) (solder_mask_margin 0.102))
|
||||
(pad 26 smd rect (at 8.75 -2.72) (size 1.5 0.9) (layers F.Cu F.Mask F.Paste) (solder_mask_margin 0.102))
|
||||
(pad 27 smd rect (at 8.75 -3.99) (size 1.5 0.9) (layers F.Cu F.Mask F.Paste) (solder_mask_margin 0.102))
|
||||
(pad 28 smd rect (at 8.75 -5.26) (size 1.5 0.9) (layers F.Cu F.Mask F.Paste) (solder_mask_margin 0.102))
|
||||
(pad 29_5 smd rect (at -1.505 0.46) (size 0.8 0.8) (layers F.Cu F.Mask F.Paste) (solder_mask_margin 0.102))
|
||||
(pad 29_1 smd rect (at -2.755 -0.79) (size 0.8 0.8) (layers F.Cu F.Mask F.Paste) (solder_mask_margin 0.102))
|
||||
(pad 29_2 smd rect (at -1.505 -0.79) (size 0.8 0.8) (layers F.Cu F.Mask F.Paste) (solder_mask_margin 0.102))
|
||||
(pad 29_3 smd rect (at -0.255 -0.79) (size 0.8 0.8) (layers F.Cu F.Mask F.Paste) (solder_mask_margin 0.102))
|
||||
(pad 29_4 smd rect (at -2.755 0.46) (size 0.8 0.8) (layers F.Cu F.Mask F.Paste) (solder_mask_margin 0.102))
|
||||
(pad 29_6 smd rect (at -0.255 0.46) (size 0.8 0.8) (layers F.Cu F.Mask F.Paste) (solder_mask_margin 0.102))
|
||||
(pad 29_7 smd rect (at -2.755 1.71) (size 0.8 0.8) (layers F.Cu F.Mask F.Paste) (solder_mask_margin 0.102))
|
||||
(pad 29_8 smd rect (at -1.505 1.71) (size 0.8 0.8) (layers F.Cu F.Mask F.Paste) (solder_mask_margin 0.102))
|
||||
(pad 29_9 smd rect (at -0.255 1.71) (size 0.8 0.8) (layers F.Cu F.Mask F.Paste) (solder_mask_margin 0.102))
|
||||
(pad 30_1 thru_hole circle (at -2.13 -0.79) (size 0.35 0.35) (drill 0.25) (layers *.Cu))
|
||||
(pad 30_2 thru_hole circle (at -0.88 -0.79) (size 0.35 0.35) (drill 0.25) (layers *.Cu))
|
||||
(pad 30_3 thru_hole circle (at -2.755 -0.165) (size 0.35 0.35) (drill 0.25) (layers *.Cu))
|
||||
(pad 30_4 thru_hole circle (at -1.505 -0.165) (size 0.35 0.35) (drill 0.25) (layers *.Cu))
|
||||
(pad 30_5 thru_hole circle (at -0.255 -0.165) (size 0.35 0.35) (drill 0.25) (layers *.Cu))
|
||||
(pad 30_6 thru_hole circle (at -2.13 0.46) (size 0.35 0.35) (drill 0.25) (layers *.Cu))
|
||||
(pad 30_7 thru_hole circle (at -0.88 0.46) (size 0.35 0.35) (drill 0.25) (layers *.Cu))
|
||||
(pad 30_8 thru_hole circle (at -2.755 1.085) (size 0.35 0.35) (drill 0.25) (layers *.Cu))
|
||||
(pad 30_9 thru_hole circle (at -1.505 1.085) (size 0.35 0.35) (drill 0.25) (layers *.Cu))
|
||||
(pad 30_10 thru_hole circle (at -0.255 1.085) (size 0.35 0.35) (drill 0.25) (layers *.Cu))
|
||||
(pad 30_11 thru_hole circle (at -2.13 1.71) (size 0.35 0.35) (drill 0.25) (layers *.Cu))
|
||||
(pad 30_12 thru_hole circle (at -0.88 1.71) (size 0.35 0.35) (drill 0.25) (layers *.Cu))
|
||||
(fp_line (start -9.0 12.75) (end 9.0 12.75) (layer F.Fab) (width 0.127))
|
||||
(fp_line (start -9.0 -12.75) (end 9.0 -12.75) (layer F.Fab) (width 0.127))
|
||||
(fp_line (start 9.0 -12.75) (end 9.0 12.75) (layer F.Fab) (width 0.127))
|
||||
(fp_line (start -9.0 12.75) (end 9.0 12.75) (layer F.SilkS) (width 0.127))
|
||||
(fp_line (start -9.0 12.75) (end -9.0 12.02) (layer F.SilkS) (width 0.127))
|
||||
(fp_line (start 9.0 12.02) (end 9.0 12.75) (layer F.SilkS) (width 0.127))
|
||||
(fp_line (start -9.0 -6.03) (end -9.0 -12.75) (layer F.SilkS) (width 0.127))
|
||||
(fp_line (start -9.0 -12.75) (end 9.0 -12.75) (layer F.SilkS) (width 0.127))
|
||||
(fp_line (start 9.0 -12.75) (end 9.0 -6.03) (layer F.SilkS) (width 0.127))
|
||||
(zone (net 0) (net_name "") (layers *.Cu) (hatch full 0.508)
|
||||
(connect_pads (clearance 0))
|
||||
(min_thickness 0.01)
|
||||
(keepout (tracks allowed) (vias not_allowed) (pads allowed ) (copperpour allowed) (footprints allowed))
|
||||
(fill (thermal_gap 0.508) (thermal_bridge_width 0.508))
|
||||
(polygon
|
||||
(pts
|
||||
(xy -9.0 -12.75)
|
||||
(xy 9.0 -12.75)
|
||||
(xy 9.0 -6.75)
|
||||
(xy -9.0 -6.75)
|
||||
)
|
||||
)
|
||||
)
|
||||
(zone (net 0) (net_name "") (layer F.Cu) (hatch full 0.508)
|
||||
(connect_pads (clearance 0))
|
||||
(min_thickness 0.01)
|
||||
(keepout (tracks not_allowed) (vias not_allowed) (pads not_allowed ) (copperpour not_allowed) (footprints allowed))
|
||||
(fill (thermal_gap 0.508) (thermal_bridge_width 0.508))
|
||||
(polygon
|
||||
(pts
|
||||
(xy -9.0 -12.75)
|
||||
(xy 9.0 -12.75)
|
||||
(xy 9.0 -6.75)
|
||||
(xy -9.0 -6.75)
|
||||
)
|
||||
)
|
||||
)
|
||||
(fp_line (start -9.75 -13.0) (end -9.75 13.0) (layer F.CrtYd) (width 0.05))
|
||||
(fp_line (start -9.75 13.0) (end 9.75 13.0) (layer F.CrtYd) (width 0.05))
|
||||
(fp_line (start 9.75 13.0) (end 9.75 -13.0) (layer F.CrtYd) (width 0.05))
|
||||
(fp_line (start 9.75 -13.0) (end -9.75 -13.0) (layer F.CrtYd) (width 0.05))
|
||||
(fp_line (start -9.0 12.75) (end -9.0 -12.75) (layer F.Fab) (width 0.127))
|
||||
(fp_circle (center -10.0 -5.25) (end -9.9 -5.25) (layer F.SilkS) (width 0.2))
|
||||
(fp_circle (center -10.0 -5.25) (end -9.9 -5.25) (layer F.Fab) (width 0.2))
|
||||
)
|
@ -1 +0,0 @@
|
||||
{"EXTRA_LAYERS": "", "EXTEND_EDGE_CUT": false, "AUTO TRANSLATE": true, "AUTO FILL": true, "EXCLUDE DNP": true}
|
@ -4,5 +4,4 @@
|
||||
(lib (name "ESP32")(type "KiCad")(uri "${KIPRJMOD}/kicad-stuff/ESP32")(options "")(descr ""))
|
||||
(lib (name "kicad-stuff")(type "KiCad")(uri "${KIPRJMOD}/kicad-stuff")(options "")(descr ""))
|
||||
(lib (name "board")(type "KiCad")(uri "${KIPRJMOD}/")(options "")(descr ""))
|
||||
(lib (name "esp32c6")(type "KiCad")(uri "${KIPRJMOD}/esp32c6")(options "")(descr ""))
|
||||
)
|
||||
|
Binary file not shown.
@ -8,5 +8,4 @@
|
||||
(lib (name "CN3306")(type "KiCad")(uri "${KIPRJMOD}/CN3306.kicad_sym")(options "")(descr ""))
|
||||
(lib (name "CN3795")(type "KiCad")(uri "${KIPRJMOD}/CN3795.kicad_sym")(options "")(descr ""))
|
||||
(lib (name "BQ34Z100PWR-G1")(type "KiCad")(uri "${KIPRJMOD}/kicad-stuff/BQ34Z100PWR-G1.kicad_sym")(options "")(descr ""))
|
||||
(lib (name "ESP32-C6-WROOM-1-N8")(type "KiCad")(uri "${KIPRJMOD}/esp32c6/ESP32-C6-WROOM-1-N8.kicad_sym")(options "")(descr ""))
|
||||
)
|
||||
|
10
esp32/.gitignore
vendored
Normal file
10
esp32/.gitignore
vendored
Normal file
@ -0,0 +1,10 @@
|
||||
*.swp
|
||||
.pio
|
||||
.vscode/.browse.c_cpp.db*
|
||||
.vscode/c_cpp_properties.json
|
||||
.vscode/launch.json
|
||||
.vscode/ipch
|
||||
doc/
|
||||
custom_platformio.ini
|
||||
cppcheck-build-dir
|
||||
host/settings.json
|
10
esp32/.vscode/extensions.json
vendored
Normal file
10
esp32/.vscode/extensions.json
vendored
Normal file
@ -0,0 +1,10 @@
|
||||
{
|
||||
// See http://go.microsoft.com/fwlink/?LinkId=827846
|
||||
// for the documentation about the extensions.json format
|
||||
"recommendations": [
|
||||
"platformio.platformio-ide"
|
||||
],
|
||||
"unwantedRecommendations": [
|
||||
"ms-vscode.cpptools-extension-pack"
|
||||
]
|
||||
}
|
3
esp32/CMakeLists.txt
Normal file
3
esp32/CMakeLists.txt
Normal file
@ -0,0 +1,3 @@
|
||||
cmake_minimum_required(VERSION 3.16.0)
|
||||
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
|
||||
project(esp32)
|
2522
esp32/Doxyfile
Normal file
2522
esp32/Doxyfile
Normal file
File diff suppressed because it is too large
Load Diff
31
esp32/PlantControl.code-workspace
Normal file
31
esp32/PlantControl.code-workspace
Normal file
@ -0,0 +1,31 @@
|
||||
{
|
||||
"folders": [
|
||||
{
|
||||
"path": "."
|
||||
}
|
||||
],
|
||||
"settings": {
|
||||
"files.associations": {
|
||||
"functional": "cpp",
|
||||
"*.tcc": "cpp",
|
||||
"map": "cpp",
|
||||
"*.cps": "javascript",
|
||||
"bitset": "cpp",
|
||||
"algorithm": "cpp",
|
||||
"istream": "cpp",
|
||||
"limits": "cpp",
|
||||
"streambuf": "cpp",
|
||||
"string": "cpp",
|
||||
"typeinfo": "cpp",
|
||||
"cmath": "cpp",
|
||||
"iterator": "cpp",
|
||||
"array": "cpp",
|
||||
"tuple": "cpp",
|
||||
"utility": "cpp",
|
||||
"fstream": "cpp",
|
||||
"ostream": "cpp",
|
||||
"sstream": "cpp"
|
||||
"system_error": "cpp"
|
||||
}
|
||||
}
|
||||
}
|
69
esp32/Readme.md
Normal file
69
esp32/Readme.md
Normal file
@ -0,0 +1,69 @@
|
||||
# PlantControl
|
||||
## Hardware
|
||||
|
||||
Main processor
|
||||
* ESP32 with 16MB Flash
|
||||
|
||||
One-Wire
|
||||
* Temperatur Sensor (DS18B20)
|
||||
* Lipo-Monitoring (DS2438)
|
||||
|
||||
Lipo Protection
|
||||
* Open drain 3.3V detector (CN61CN33 @ jlcpcb parts)
|
||||
|
||||
|
||||
|
||||
### Used Pins:
|
||||
* See '''include/ControllerConfiguration.h'''
|
||||
|
||||
## Software
|
||||
* MQTT topics
|
||||
|
||||
# Hardware
|
||||
## Features
|
||||
* Support for up to
|
||||
* 7 Moister sensors
|
||||
* 7 Pumps
|
||||
* Sensors
|
||||
* Solar powered (voltage)
|
||||
* Lipo-Powered (DS2438 for monitoring)
|
||||
* Temperature
|
||||
* Laser distance sensor [VL53L0X]
|
||||
* Custom GPIO
|
||||
|
||||
## Documentation of Power-Modes
|
||||
https://lastminuteengineers.com/esp32-sleep-modes-power-consumption/#esp32-deep-sleep
|
||||
|
||||
|
||||
gpio 17 only out no hold
|
||||
gpio 16 only out no hold
|
||||
|
||||
## Additional hardware
|
||||
solar charger 2A?
|
||||
https://www.aliexpress.com/item/4000238259949.html?spm=a2g0o.productlist.0.0.7e50231cCWGu0Z&algo_pvid=9ab7b0d3-5026-438b-972b-1d4a81d4dc56&algo_expid=9ab7b0d3-5026-438b-972b-1d4a81d4dc56-11&btsid=0b0a0ac215999246489888249e72a9&ws_ab_test=searchweb0_0,searchweb201602_,searchweb201603_
|
||||
|
||||
MT3608 boost für pumpe
|
||||
https://www.aliexpress.com/item/32925951391.html?spm=a2g0o.productlist.0.0.39e21087nAzH9q&algo_pvid=7db0a849-62f7-4403-88e3-615ee4d99339&algo_expid=7db0a849-62f7-4403-88e3-615ee4d99339-0&btsid=0b0a0ac215999252934777876e7253&ws_ab_test=searchweb0_0,searchweb201602_,searchweb201603_
|
||||
|
||||
DS18B20 one wire temp sensor
|
||||
|
||||
|
||||
# Features
|
||||
## Empires Wunschliste
|
||||
* Pflanze
|
||||
* Pumpe
|
||||
* [x] Zeitspann (wann laufen darf)
|
||||
* [x] Helligkeitstrigger (Um den Morgen zum pumpen zu erkennen)
|
||||
* [-] Maximal Dauer zum Pumpen (als Zeit oder Milliliter)
|
||||
* [x] Zeitspanne zwischen zwei Pumpvorgängen
|
||||
* Moister sensor
|
||||
* [x] Schwellwert für Pumpe
|
||||
* Tank
|
||||
* Füllstand Anzeige (in Liter)
|
||||
* Minimum Wasserstand (in cm damit Pumpen nicht leer laufen; enspricht 0 nutzbaren Liter)
|
||||
* [x] Maximaler Wasserstand des Tanks (in cm & Liter)
|
||||
|
||||
## Masterplan 2.0
|
||||
* Partitionslayout
|
||||
|
||||
|
16
esp32/custom_platformio.ini.example
Normal file
16
esp32/custom_platformio.ini.example
Normal file
@ -0,0 +1,16 @@
|
||||
[env:esp32doit-devkit-v1]
|
||||
platform = espressif32
|
||||
board = esp32doit-devkit-v1
|
||||
framework = arduino
|
||||
build_flags = -DPIO_FRAMEWORK_ARDUINO_LWIP2_LOW_MEMORY
|
||||
board_build.partitions = defaultWithSmallerSpiffs.csv
|
||||
|
||||
; the latest development brankitchen-lightch (convention V3.0.x)
|
||||
lib_deps = ArduinoJson@6.16.1
|
||||
OneWire
|
||||
DallasTemperature
|
||||
pololu/VL53L0X
|
||||
https://github.com/homieiot/homie-esp8266.git#develop
|
||||
|
||||
; add additional parameter, like the upload port
|
||||
upload_port=/dev/ttyUSB1
|
1
esp32/data/homie/.gitignore
vendored
Normal file
1
esp32/data/homie/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
config.json
|
12
esp32/data/homie/Readme.md
Normal file
12
esp32/data/homie/Readme.md
Normal file
@ -0,0 +1,12 @@
|
||||
# Filesystem
|
||||
## Configuration
|
||||
Use the config-example.json from the host folder and create here a config.json file.
|
||||
## HowTo upload
|
||||
Start Platform.io
|
||||
Open a new Atom-Terminal and generate the filesystem with the following command :
|
||||
```pio run -t buildfs```
|
||||
Upload this new generated filesystem with:
|
||||
```pio run -t uploadfs```
|
||||
|
||||
## Command pio
|
||||
Can be found at ```~/.platformio/penv/bin/pio```
|
BIN
esp32/data/homie/ui_bundle.gz
Normal file
BIN
esp32/data/homie/ui_bundle.gz
Normal file
Binary file not shown.
7
esp32/defaultWithSmallerSpiffs.csv
Normal file
7
esp32/defaultWithSmallerSpiffs.csv
Normal file
@ -0,0 +1,7 @@
|
||||
# Name, Type, SubType, Offset, Size, Flags
|
||||
nvs, data, nvs, 0x9000, 0x4000
|
||||
otadata, data, ota, 0xD000, 0x2000
|
||||
phy_init, data, phy, 0xF000, 0x1000
|
||||
ota_0, app, ota_0, 0x10000, 0x1E0000
|
||||
ota_1, app, ota_1, 0x1F0000, 0x1E0000
|
||||
spiffs, data, spiffs, 0x3D0000, 0x30000
|
|
90
esp32/host/Readme.md
Normal file
90
esp32/host/Readme.md
Normal file
@ -0,0 +1,90 @@
|
||||
# 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
|
||||
|
||||
See ***upload-via-mqtt.sh***
|
||||
|
||||
# Remote Upload - Backend
|
||||
|
||||
## Usage
|
||||
|
||||
```text
|
||||
usage: ota_updater.py [-h] -l BROKER_HOST -p BROKER_PORT [-u BROKER_USERNAME]
|
||||
[-d BROKER_PASSWORD] [-t BASE_TOPIC] -i DEVICE_ID
|
||||
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
|
||||
```
|
||||
|
||||
The Parameter can be extracted from the serial console
|
||||
```serial
|
||||
{} Stored configuration
|
||||
• Hardware device ID: 12345abcd
|
||||
• Device ID: MyDeviceId
|
||||
• Name: MyDeviceName
|
||||
• Device Stats Interval: 60 sec
|
||||
• Wi-Fi:
|
||||
◦ SSID: MyWifi
|
||||
◦ Password not shown
|
||||
• MQTT:
|
||||
◦ Host: 192.168.0.2
|
||||
◦ Port: 1883
|
||||
◦ Base topic: /test/
|
||||
◦ Auth? no
|
||||
```
|
||||
|
||||
will result in the following command (when executed in this folder):
|
||||
```bash
|
||||
python ota_updater.py -l 192.168.0.2 -t "/test/" -i "MyDeviceId" ../.pio/build/esp32doit-devkit-v1/firmware.bin
|
||||
```
|
||||
|
||||
### Source
|
||||
https://github.com/homieiot/homie-esp8266/blob/develop/scripts/ota_updater
|
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)
|
49
esp32/host/settings.json.example
Normal file
49
esp32/host/settings.json.example
Normal file
@ -0,0 +1,49 @@
|
||||
{
|
||||
"settings": {
|
||||
"sleep":600,
|
||||
"nightsleep": 1200,
|
||||
"pumpsleep": 5,
|
||||
"tankmax": 1000,
|
||||
"tankmin": 100,
|
||||
"tankwarn": 200,
|
||||
"tankVolume": 100,
|
||||
"lipoDSAddr": "abcdefghijklmnop",
|
||||
"tankDSAddr": "abcdefghijklmnop",
|
||||
"ntpServer":"pool.ntp.org",
|
||||
"dry0":50,
|
||||
"hourstart0":6,
|
||||
"hourend0":20,
|
||||
"lowLight0": false,
|
||||
"delay0": 30,
|
||||
"dry1":-1,
|
||||
"hourstart1":6,
|
||||
"hourend1":20,
|
||||
"lowLight1": false,
|
||||
"delay1": 30,
|
||||
"dry2":-1,
|
||||
"hourstart2":6,
|
||||
"hourend2":20,
|
||||
"lowLight2": false,
|
||||
"delay2": 30,
|
||||
"dry3":-1,
|
||||
"hourstart3":6,
|
||||
"hourend3":20,
|
||||
"lowLight3": false,
|
||||
"delay3": 30,
|
||||
"dry4":-1,
|
||||
"hourstart4":6,
|
||||
"hourend4":20,
|
||||
"lowLight4": false,
|
||||
"delay4": 30,
|
||||
"dry5":-1,
|
||||
"hourstart5":6,
|
||||
"hourend5":20,
|
||||
"lowLight5": false,
|
||||
"delay5": 30,
|
||||
"dry6":-1,
|
||||
"hourstart6":6,
|
||||
"hourend6":20,
|
||||
"lowLight6": false,
|
||||
"delay6": 30
|
||||
}
|
||||
}
|
41
esp32/host/upload-settings.sh
Executable file
41
esp32/host/upload-settings.sh
Executable file
@ -0,0 +1,41 @@
|
||||
#!//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
|
||||
|
||||
maxSteps=6
|
||||
|
||||
settingsFile=settings.json
|
||||
if [ ! -f $settingsFile ]; then
|
||||
echo "$settingsFile missing"
|
||||
echo "check $settingsFile.example"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
mosquitto_pub -h $mqttHost -t "${mqttPrefix}${homieId}/stay/alive/set" -m "1" -r
|
||||
echo "(1 / $maxSteps) Waiting ..."
|
||||
mosquitto_sub -h $mqttHost -t "${mqttPrefix}${homieId}/#" -R -C 1
|
||||
set -e
|
||||
echo "(2 / $maxSteps) Waiting 30 seconds ..."
|
||||
sleep 30
|
||||
mosquitto_pub -h $mqttHost -t "${mqttPrefix}${homieId}/\$implementation/config/set" -f $settingsFile
|
||||
echo "(3 / $maxSteps) Waiting for reboot ..."
|
||||
sleep 1
|
||||
mosquitto_sub -h $mqttHost -t "${mqttPrefix}${homieId}/#" -R -C 1
|
||||
echo "(4 / $maxSteps) Alive"
|
||||
sleep 20
|
||||
echo "(5 / $maxSteps) Create Backup ..."
|
||||
mosquitto_pub -h $mqttHost -t "${mqttPrefix}${homieId}/config/backup/set" -m "true" -r
|
||||
sleep 5
|
||||
echo "(6 / $maxSteps) Shutdown ..."
|
||||
mosquitto_pub -h $mqttHost -t "${mqttPrefix}${homieId}/stay/alive/set" -m "0" -r
|
||||
exit 0
|
28
esp32/host/upload-via-mqtt.sh
Executable file
28
esp32/host/upload-via-mqtt.sh
Executable file
@ -0,0 +1,28 @@
|
||||
#!//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
|
||||
set -e
|
||||
python3 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
|
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
|
136
esp32/include/ControllerConfiguration.h
Normal file
136
esp32/include/ControllerConfiguration.h
Normal file
@ -0,0 +1,136 @@
|
||||
/**
|
||||
* @file ControllerConfiguration.h
|
||||
* @author your name (you@domain.com)
|
||||
* @brief
|
||||
* @version 0.1
|
||||
* @date 2020-05-30
|
||||
*
|
||||
* @copyright Copyright (c) 2020
|
||||
*
|
||||
* \mainpage Configuration of the controller
|
||||
* @{
|
||||
* Describe the used PINs of the controller
|
||||
*
|
||||
* @subpage Controller
|
||||
*
|
||||
* @subpage Homie
|
||||
*
|
||||
* @subpage Configuration
|
||||
*
|
||||
* There are several modes in the controller
|
||||
* \dot
|
||||
* digraph Operationmode {
|
||||
* ranksep=.75;
|
||||
* poweroff [ label="off" ];
|
||||
* mode1 [ label="Mode 1 - Sensor only", shape=box, width=2 ];
|
||||
* mode2 [ label="Mode 2 - Wifi enabled", shape=box ];
|
||||
* mode3 [ label="Mode 3 - Stay alive", shape=box ];
|
||||
* mode1 -> mode2 [ label="wakeup reason", fontsize=10 ];
|
||||
* mode1 -> mode2 [ label="Time duration", fontsize=10 ];
|
||||
* mode2 -> mode3 [ label="Over the Air Update", fontsize=10 ];
|
||||
* mode3 -> mode2 [ label="Over the Air Finished", fontsize=10 ];
|
||||
* mode3 -> mode2 [ label="Mqtt Command", fontsize=10 ];
|
||||
* mode2 -> mode3 [ label="Mqtt Command", fontsize=10 ];
|
||||
* poweroff -> mode1 [ label="deep sleep wakeup", fontsize=10 ];
|
||||
* mode1 -> poweroff [ label="enter deep sleep", fontsize=10 ];
|
||||
* mode2 -> poweroff [ label="Mqtt queue empty", fontsize=10 ];
|
||||
* }
|
||||
* \enddot
|
||||
*
|
||||
* Before entering Deep sleep the controller is configured with an wakeup time.
|
||||
*
|
||||
* @}
|
||||
*/
|
||||
#ifndef CONTROLLER_CONFIG_H
|
||||
#define CONTROLLER_CONFIG_H
|
||||
/** \addtogroup GPIO Settings
|
||||
* @{
|
||||
*/
|
||||
#define SENSOR_PLANT0 GPIO_NUM_32 /**< GPIO 32 (ADC1) */
|
||||
#define SENSOR_PLANT1 GPIO_NUM_33 /**< GPIO 33 (ADC1) */
|
||||
#define SENSOR_PLANT2 GPIO_NUM_25 /**< GPIO 25 (ADC2) */
|
||||
#define SENSOR_PLANT3 GPIO_NUM_26 /**< GPIO 26 (ADC2) */
|
||||
#define SENSOR_PLANT4 GPIO_NUM_27 /**< GPIO 27 (ADC2) */
|
||||
#define SENSOR_PLANT5 GPIO_NUM_39 /**< SENSOR_VIN */
|
||||
#define SENSOR_PLANT6 GPIO_NUM_36 /**< SENSOR_VP */
|
||||
|
||||
#define OUTPUT_PUMP0 GPIO_NUM_15 /**< GPIO 15 */
|
||||
#define OUTPUT_PUMP1 GPIO_NUM_5 /**< GPIO 5 */
|
||||
#define OUTPUT_PUMP2 GPIO_NUM_18 /**< GPIO 18 */
|
||||
#define OUTPUT_PUMP3 GPIO_NUM_19 /**< GPIO 19 */
|
||||
#define OUTPUT_PUMP4 GPIO_NUM_21 /**< GPIO 21 */
|
||||
#define OUTPUT_PUMP5 GPIO_NUM_22 /**< GPIO 22 */
|
||||
#define OUTPUT_PUMP6 GPIO_NUM_23 /**< GPIO 23 */
|
||||
|
||||
#define OUTPUT_ENABLE_SENSOR GPIO_NUM_14 /**< GPIO 14 - Enable Sensors */
|
||||
#define OUTPUT_ENABLE_PUMP GPIO_NUM_13 /**< GPIO 13 - Enable Pumps */
|
||||
|
||||
#define SENSOR_ONEWIRE GPIO_NUM_4 /**< GPIO 12 - Temperatur sensor, Battery and other cool onewire stuff */
|
||||
#define SENSOR_TANK_SDA GPIO_NUM_17 /**< GPIO 17 - water sensor SDA */
|
||||
#define SENSOR_TANK_SCL GPIO_NUM_16 /**< GPIO 16 - water sensor SCL */
|
||||
#define BUTTON GPIO_NUM_0 /**< GPIO 0 - Fix button of NodeMCU */
|
||||
|
||||
#define CUSTOM1_PIN1 GPIO_NUM_34 /** direct gpio */
|
||||
#define CUSTOM1_PIN3 GPIO_NUM_35 /** direct gpio */
|
||||
#define CUSTOM1_PIN5 GPIO_NUM_2 /** mosfet controlled */
|
||||
#define CUSTOM1_PIN7 GPIO_NUM_12 /** mosfet controlled */
|
||||
|
||||
/* @} */
|
||||
|
||||
/** \addtogroup Configuration
|
||||
* @{
|
||||
*/
|
||||
|
||||
#ifdef FLOWMETER_PIN
|
||||
#define FLOWMETER_PULSES_PER_ML 2.2
|
||||
#define FIRMWARE_FEATURE1 "Flow"
|
||||
#else
|
||||
#define FIRMWARE_FEATURE1 ""
|
||||
#endif
|
||||
|
||||
#ifdef TIMED_LIGHT_PIN
|
||||
#define FIRMWARE_FEATURE2 "Light"
|
||||
#else
|
||||
#define FIRMWARE_FEATURE2 ""
|
||||
#endif
|
||||
|
||||
#define FIRMWARE_BASENAME "PlantControl"
|
||||
#define FIRMWARE_NAME FIRMWARE_BASENAME FIRMWARE_FEATURE1 FIRMWARE_FEATURE2
|
||||
#define FIRMWARE_VERSION "3.01 HW0.10b"
|
||||
|
||||
#define MOIST_SENSOR_MAX_FRQ 5200 // 60kHz (500Hz margin)
|
||||
#define MOIST_SENSOR_MIN_FRQ 500 // 0.5kHz (500Hz margin)
|
||||
|
||||
#define ANALOG_SENSOR_MAX_MV 1300 //successive approximation of good range
|
||||
#define ANALOG_SENSOR_MIN_MV 100 //successive approximation of good range
|
||||
|
||||
#define SOLAR_VOLT_FACTOR 11
|
||||
#define BATTSENSOR_INDEX_SOLAR 0
|
||||
#define BATTSENSOR_INDEX_BATTERY 1
|
||||
|
||||
#define MQTT_TIMEOUT (1000 * 60) /**< After 10 seconds, MQTT is expected to be connected */
|
||||
#define ESP_STALE_TIMEOUT (MQTT_TIMEOUT+(700*1000))
|
||||
|
||||
#define MAX_PLANTS 7
|
||||
#define SOLAR_CHARGE_MIN_VOLTAGE 7 /**< Sun is rising (morning detected) */
|
||||
#define SOLAR_CHARGE_MAX_VOLTAGE 9 /**< Sun is shining (noon) */
|
||||
#define SOLAR_MAX_VOLTAGE_POSSIBLE 100 /**< higher values are treated as not connected sensor */
|
||||
#define VOLT_MAX_BATT 4.2f
|
||||
#define VOLT_MIN_BATT 3.0f /**< Minimum battery voltage for normal operation */
|
||||
#define LOWVOLT_SLEEP_FACTOR 3 /**< Factor for nightsleep delay, if the battery drops below minimum (@see VOLT_MIN_BATT) */
|
||||
#define LOWVOLT_SLEEP_MINIMUM 1800 /**< At low voltage sleep at least for 30 minutes */
|
||||
|
||||
#define MAX_CONFIG_SETTING_ITEMS 100 /**< Parameter, that can be configured in Homie */
|
||||
#define MAX_JSON_CONFIG_FILE_SIZE_CUSTOM 2500
|
||||
|
||||
#define TEMPERATUR_TIMEOUT 3000 /**< 3 Seconds timeout for the temperatures sensors */
|
||||
#define WATERSENSOR_TIMEOUT 3000 /**< 3 Seconds timeout for the water distance sensor */
|
||||
#define WATERSENSOR_CYCLE 10 /**< 5 sensor measurement are performed */
|
||||
#define DS18B20_RESOLUTION 9 /**< 9bit temperature resolution -> 0.5°C steps */
|
||||
|
||||
#define UTC_OFFSET_DE 3600 /* UTC offset in seconds for Germany */
|
||||
#define UTF_OFFSET_DE_DST 3600 /* offset in seconds if daylight saving time is active */
|
||||
|
||||
/* @} */
|
||||
|
||||
#endif
|
111
esp32/include/DS2438.h
Normal file
111
esp32/include/DS2438.h
Normal file
@ -0,0 +1,111 @@
|
||||
/**
|
||||
* @file DS2438.h
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef DS2438_h
|
||||
#define DS2438_h
|
||||
|
||||
#include <Arduino.h>
|
||||
#include <OneWire.h>
|
||||
#include "RunningMedian.h"
|
||||
|
||||
#define DS2438_TEMPERATURE_CONVERSION_COMMAND 0x44
|
||||
#define DS2438_VOLTAGE_CONVERSION_COMMAND 0xb4
|
||||
#define DS2438_WRITE_SCRATCHPAD_COMMAND 0x4e
|
||||
#define DS2438_COPY_SCRATCHPAD_COMMAND 0x48
|
||||
#define DS2438_READ_SCRATCHPAD_COMMAND 0xbe
|
||||
#define DS2438_RECALL_MEMORY_COMMAND 0xb8
|
||||
|
||||
#define PAGE_MIN 0
|
||||
#define PAGE_MAX 7
|
||||
|
||||
#define DS2438_CHA 0
|
||||
#define DS2438_CHB 1
|
||||
|
||||
#define DS2438_MODE_CHA 0x01
|
||||
#define DS2438_MODE_CHB 0x02
|
||||
#define DS2438_MODE_TEMPERATURE 0x04
|
||||
|
||||
#define DS2438_TEMPERATURE_DELAY 10
|
||||
#define DS2438_VOLTAGE_CONVERSION_DELAY 8
|
||||
|
||||
#define DS2438_MEDIAN_COUNT 5
|
||||
#define DS2438_MEDIAN_DELAY 50
|
||||
|
||||
#define DEFAULT_PAGE0(var) uint8_t var[8] { \
|
||||
0b00001011 /* X, ADB=0, NVB=0, TB=0, AD=1, EE=0, CA=1, IAD=1 */, \
|
||||
0, /* Temperatur */ \
|
||||
0, /* Temperatur */ \
|
||||
0, /* Voltage */ \
|
||||
0, /* Voltage */ \
|
||||
0, /* Current */ \
|
||||
0, /* Current */ \
|
||||
0b10000000 /* Threshold to 4LSB */ \
|
||||
}
|
||||
|
||||
typedef struct PageOne {
|
||||
uint8_t eleapsedTimerByte0; /**< LSB of timestamp */
|
||||
uint8_t eleapsedTimerByte1;
|
||||
uint8_t eleapsedTimerByte2;
|
||||
uint8_t eleapsedTimerByte3; /**< MSB of timestamp */
|
||||
uint8_t ICA; /**< Integrated Current Accumulator (current flowing into and out of the battery) */
|
||||
uint8_t offsetRegisterByte0; /**< Offset for ADC calibdation */
|
||||
uint8_t offsetRegisterByte1; /**< Offset for ADC calibdation */
|
||||
uint8_t reserved;
|
||||
} PageOne_t;
|
||||
|
||||
typedef struct PageSeven {
|
||||
uint8_t userByte0;
|
||||
uint8_t userByte1;
|
||||
uint8_t userByte2;
|
||||
uint8_t userByte3;
|
||||
uint8_t CCA0; /**< Charging Current Accumulator (CCA) */
|
||||
uint8_t CCA1; /**< Charging Current Accumulator (CCA) */
|
||||
uint8_t DCA0; /**< Discharge Current Accumulator (DCA) */
|
||||
uint8_t DCA1; /**< Discharge Current Accumulator (DCA) */
|
||||
} PageSeven_t;
|
||||
|
||||
typedef uint8_t DeviceAddress[8];
|
||||
|
||||
class DS2438 {
|
||||
public:
|
||||
DS2438(OneWire *ow, float currentShunt, int retryOnCRCError);
|
||||
|
||||
void begin();
|
||||
void updateMultiple();
|
||||
double getTemperature();
|
||||
float getVoltage(int channel=DS2438_CHA);
|
||||
float getCurrent();
|
||||
long getICA();
|
||||
long getCCA();
|
||||
long getDCA();
|
||||
float getAh();
|
||||
boolean isError();
|
||||
boolean isFound();
|
||||
private:
|
||||
bool validAddress(const uint8_t*);
|
||||
bool validFamily(const uint8_t* deviceAddress);
|
||||
void update(bool firstIteration);
|
||||
bool deviceFound = false;
|
||||
OneWire *_ow;
|
||||
DeviceAddress _address;
|
||||
uint8_t _mode;
|
||||
RunningMedian _temperature = RunningMedian(DS2438_MEDIAN_COUNT*2);
|
||||
RunningMedian _voltageA = RunningMedian(DS2438_MEDIAN_COUNT);
|
||||
RunningMedian _voltageB = RunningMedian(DS2438_MEDIAN_COUNT);
|
||||
RunningMedian _current = RunningMedian(DS2438_MEDIAN_COUNT);
|
||||
float _currentShunt;
|
||||
int _retryOnCRCError;
|
||||
long _CCA;
|
||||
long _DCA;
|
||||
long _ICA;
|
||||
boolean _error;
|
||||
boolean startConversion(int channel, boolean doTemperature);
|
||||
boolean selectChannel(int channel);
|
||||
void writePage(int page, uint8_t *data);
|
||||
boolean readPage(int page, uint8_t *data);
|
||||
};
|
||||
|
||||
#endif
|
9
esp32/include/FileUtils.h
Normal file
9
esp32/include/FileUtils.h
Normal file
@ -0,0 +1,9 @@
|
||||
#ifndef FILEUTILS_H
|
||||
#define FILEUTILS_H
|
||||
|
||||
bool doesFileExist(const char *source);
|
||||
bool copyFile(const char *source, const char *target);
|
||||
bool deleteFile(const char *source);
|
||||
void printFile(const char *source);
|
||||
|
||||
#endif
|
135
esp32/include/HomieConfiguration.h
Normal file
135
esp32/include/HomieConfiguration.h
Normal file
@ -0,0 +1,135 @@
|
||||
/** \addtogroup Homie
|
||||
* @{
|
||||
*
|
||||
* @file HomieConfiguration.h
|
||||
* @author your name (you@domain.com)
|
||||
* @brief
|
||||
* @version 0.1
|
||||
* @date 2020-10-16
|
||||
*
|
||||
* @copyright Copyright (c) 2020
|
||||
* All Settings, configurable in Homie
|
||||
*
|
||||
*/
|
||||
#ifndef HOMIE_PLANT_CONFIG_H
|
||||
#define HOMIE_PLANT_CONFIG_H
|
||||
|
||||
#include "HomieTypes.h"
|
||||
|
||||
#define MAX_PLANTS 7
|
||||
|
||||
/**
|
||||
* @name Homie Attributes
|
||||
* generated Information
|
||||
* @{
|
||||
**/
|
||||
|
||||
#define NUMBER_TYPE "Float" /**< numberic information, published or read in Homie */
|
||||
|
||||
/**
|
||||
* @}
|
||||
*
|
||||
* @name Temperatur Node
|
||||
* @{
|
||||
**/
|
||||
|
||||
#define TEMPERATURE_NAME "Temperature"
|
||||
#define TEMPERATURE_UNIT "°C"
|
||||
#define TEMPERATUR_SENSOR_LIPO "lipo" /**< Homie node: temperatur, setting: lipo temperatur (or close to it) */
|
||||
#define TEMPERATUR_SENSOR_CHIP "chip" /**< Homie node: temperatur, setting: battery chip */
|
||||
#define TEMPERATUR_SENSOR_WATER "water" /**< Homie node: temperatur, setting: water temperatur */
|
||||
/** @}
|
||||
*
|
||||
* @name Plant Nodes
|
||||
* @{
|
||||
*/
|
||||
|
||||
HomieNode plant0("plant0", "Plant 0", "Plant"); /**< dynamic Homie information for first plant */
|
||||
HomieNode plant1("plant1", "Plant 1", "Plant"); /**< dynamic Homie information for second plant */
|
||||
HomieNode plant2("plant2", "Plant 2", "Plant"); /**< dynamic Homie information for third plant */
|
||||
HomieNode plant3("plant3", "Plant 3", "Plant"); /**< dynamic Homie information for fourth plant */
|
||||
HomieNode plant4("plant4", "Plant 4", "Plant"); /**< dynamic Homie information for fivth plant */
|
||||
HomieNode plant5("plant5", "Plant 5", "Plant"); /**< dynamic Homie information for sixth plant */
|
||||
HomieNode plant6("plant6", "Plant 6", "Plant"); /**< dynamic Homie information for seventh plant */
|
||||
|
||||
#if defined(TIMED_LIGHT_PIN)
|
||||
HomieNode timedLightNode("timedLight", "TimedLight", "Status");
|
||||
#endif // TIMED_LIGHT_PIN
|
||||
|
||||
|
||||
HomieNode sensorLipo("lipo", "Battery Status", "Lipo");
|
||||
HomieNode sensorSolar("solar", "Solar Status", "Solarpanel");
|
||||
HomieNode sensorWater("water", "WaterSensor", "Water");
|
||||
HomieNode sensorTemp("temperature", "Temperature", "temperature");
|
||||
HomieNode stayAlive("stay", "alive", "alive"); /**< Necessary for Mqtt Active Command */
|
||||
|
||||
/**
|
||||
* @}
|
||||
*/
|
||||
|
||||
/**
|
||||
* @name Settings
|
||||
* General settings for the controller
|
||||
* @{
|
||||
*/
|
||||
HomieSetting<long> deepSleepTime("sleep", "time in seconds to sleep");
|
||||
HomieSetting<long> deepSleepNightTime("nightsleep", "time in seconds to sleep (0 uses same setting: deepsleep at night, too)");
|
||||
HomieSetting<long> pumpIneffectiveWarning("pumpConsecutiveWarn", "if the pump was triggered this amount directly after each cooldown, without resolving dryness, warn");
|
||||
HomieSetting<long> waterLevelMax("tankmax", "distance (mm) at maximum water level");
|
||||
HomieSetting<long> waterLevelMin("tankmin", "distance (mm) at minimum water level (pumps still covered)");
|
||||
HomieSetting<long> waterLevelWarn("tankwarn", "warn (mm) if below this water level %");
|
||||
HomieSetting<long> waterLevelVol("tankVolume", "(ml) between minimum and maximum");
|
||||
HomieSetting<const char *> lipoSensorAddr("lipoDSAddr", "1wire address for lipo temperature sensor");
|
||||
HomieSetting<const char *> waterSensorAddr("tankDSAddr", "1wire address for water temperature sensor");
|
||||
HomieSetting<const char *> ntpServer("ntpServer", "NTP server (pool.ntp.org as default)");
|
||||
|
||||
#if defined(TIMED_LIGHT_PIN)
|
||||
HomieSetting<double> timedLightVoltageCutoff("LightVoltageCutoff", "voltage at wich to disable light");
|
||||
HomieSetting<long> timedLightStart("LightStart", "hour to start light");
|
||||
HomieSetting<long> timedLightEnd("LightEnd", "hour to end light");
|
||||
HomieSetting<bool> timedLightOnlyWhenDark("LightOnlyDark", "only enable light, if solar is low");
|
||||
HomieSetting<long> timedLightPowerLevel("LightPowerLevel", "0-255 power level");
|
||||
#endif // TIMED_LIGHT_PIN
|
||||
|
||||
|
||||
/**
|
||||
* @}
|
||||
*/
|
||||
|
||||
/**
|
||||
* @name Plant specific ones
|
||||
* Setting for one plant
|
||||
* @{
|
||||
**/
|
||||
|
||||
#define GENERATE_PLANT(plant, strplant) \
|
||||
HomieSetting<double> mSensorDry##plant = HomieSetting<double>("dry" strplant, "Plant" strplant " - Moist sensor dry %"); \
|
||||
HomieSetting<long> mPumpAllowedHourRangeStart##plant = HomieSetting<long>("hourstart" strplant, "Plant" strplant " - Range pump allowed hour start (0-23)"); \
|
||||
HomieSetting<long> mPumpAllowedHourRangeEnd##plant = HomieSetting<long>("hourend" strplant, "Plant" strplant " - Range pump allowed hour end (0-23)"); \
|
||||
HomieSetting<bool> mPumpOnlyWhenLowLight##plant = HomieSetting<bool>("lowLight" strplant, "Plant" strplant " - Enable the Pump only, when there is no sunlight"); \
|
||||
HomieSetting<long> mPumpCooldownInSeconds##plant = HomieSetting<long>("delay" strplant, "Plant" strplant " - How long to wait until the pump is activated again (sec)"); \
|
||||
HomieSetting<long> pPumpDuration##plant = HomieSetting<long>("pumpDuration" strplant, "Plant" strplant " - time seconds to water when pump is active"); \
|
||||
HomieSetting<long> pPumpMl##plant = HomieSetting<long>("pumpAmount" strplant, "Plant" strplant " - ml (if using flowmeter) to water when pump is active"); \
|
||||
HomieSetting<long> pPowerLevel##plant = HomieSetting<long>("powerLevel" strplant, "Plant" strplant " - pwm duty cycle in percent"); \
|
||||
PlantSettings_t mSetting##plant = {&mSensorDry##plant, &mPumpAllowedHourRangeStart##plant, &mPumpAllowedHourRangeEnd##plant, &mPumpOnlyWhenLowLight##plant, &mPumpCooldownInSeconds##plant, &pPumpDuration##plant, &pPowerLevel##plant, &pPumpMl##plant}; \
|
||||
/**< Generate all settings for one plant \
|
||||
* \
|
||||
* Feature to start pumping only at morning: @link{SOLAR_CHARGE_MIN_VOLTAGE} and @link{SOLAR_CHARGE_MAX_VOLTAGE} \
|
||||
*/
|
||||
|
||||
/**
|
||||
* @}
|
||||
*/
|
||||
|
||||
GENERATE_PLANT(0, "0"); /**< 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 @} */
|
||||
|
95
esp32/include/HomieTypes.h
Normal file
95
esp32/include/HomieTypes.h
Normal file
@ -0,0 +1,95 @@
|
||||
/**
|
||||
* @file HomieTypes.h
|
||||
* @author your name (you@domain.com)
|
||||
* @brief
|
||||
* @version 0.1
|
||||
* @date 2020-10-16
|
||||
*
|
||||
* @copyright Copyright (c) 2020
|
||||
* All Settings, configurable in Homie
|
||||
*/
|
||||
#ifndef HOMIE_PLANT_CFG_CONFIG_H
|
||||
#define HOMIE_PLANT_CFG_CONFIG_H
|
||||
|
||||
#include <Homie.h>
|
||||
|
||||
/**
|
||||
* @name Sensor types
|
||||
* possible sensors:
|
||||
* @{
|
||||
**/
|
||||
|
||||
#define FOREACH_SENSOR(SENSOR) \
|
||||
SENSOR(NONE) \
|
||||
SENSOR(FREQUENCY_MOD_RESISTANCE_PROBE) \
|
||||
SENSOR(ANALOG_RESISTANCE_PROBE)
|
||||
|
||||
/**
|
||||
* @}
|
||||
*/
|
||||
|
||||
#define GENERATE_ENUM(ENUM) ENUM,
|
||||
#define GENERATE_STRING(STRING) #STRING,
|
||||
|
||||
enum SENSOR_MODE {
|
||||
FOREACH_SENSOR(GENERATE_ENUM)
|
||||
};
|
||||
|
||||
static const char *SENSOR_STRING[] = {
|
||||
FOREACH_SENSOR(GENERATE_STRING)
|
||||
};
|
||||
|
||||
//plant pump is deactivated, but sensor values are still recorded and published
|
||||
#define DEACTIVATED_PLANT -1
|
||||
//special value to indicate a missing sensor when the plant is not deactivated but no valid sensor value was read
|
||||
#define MISSING_SENSOR -2
|
||||
//plant uses only cooldown and duration, moisture is measured but ignored, allowedHours is ignored (eg. make a 30min on 30min off cycle)
|
||||
#define HYDROPONIC_MODE -3
|
||||
//plant uses cooldown and duration and workhours, moisture is measured but ignored
|
||||
#define TIMER_ONLY -4
|
||||
//special value to indicate a shorted sensor when the plant is not deactivated but the sensor reads short circuit value
|
||||
#define SHORT_CIRCUIT_MODE -5
|
||||
|
||||
/**
|
||||
* @brief State of plants
|
||||
*
|
||||
*/
|
||||
#define PLANTSTATE_NUM_DEACTIVATED -1
|
||||
#define PLANTSTATE_NUM_NO_SENSOR -2
|
||||
#define PLANTSTATE_NUM_WET 0x00
|
||||
#define PLANTSTATE_NUM_SUNNY_ALARM 0x11
|
||||
#define PLANTSTATE_NUM_ACTIVE_ALARM 0x41
|
||||
#define PLANTSTATE_NUM_ACTIVE_SUPESSED -3
|
||||
#define PLANTSTATE_NUM_ACTIVE 0x40
|
||||
#define PLANTSTATE_NUM_SUNNY 0x10
|
||||
#define PLANTSTATE_NUM_COOLDOWN_ALARM 0x21
|
||||
#define PLANTSTATE_NUM_COOLDOWN 0x20
|
||||
#define PLANTSTATE_NUM_AFTERWORK_ALARM 0x31
|
||||
#define PLANTSTATE_NUM_AFTERWORK 0x30
|
||||
|
||||
#define PLANTSTATE_STR_DEACTIVATED "deactivated"
|
||||
#define PLANTSTATE_STR_NO_SENSOR "nosensor"
|
||||
#define PLANTSTATE_STR_WET "wet"
|
||||
#define PLANTSTATE_STR_SUNNY_ALARM "sunny+alarm"
|
||||
#define PLANTSTATE_STR_ACTIVE_ALARM "active+alarm"
|
||||
#define PLANTSTATE_STR_ACTIVE_SUPESSED "active+supressed"
|
||||
#define PLANTSTATE_STR_ACTIVE "active"
|
||||
#define PLANTSTATE_STR_SUNNY "sunny"
|
||||
#define PLANTSTATE_STR_COOLDOWN_ALARM "cooldown+alarm"
|
||||
#define PLANTSTATE_STR_COOLDOWN "cooldown"
|
||||
#define PLANTSTATE_STR_AFTERWORK_ALARM "after-work+alarm"
|
||||
#define PLANTSTATE_STR_AFTERWORK "after-work"
|
||||
|
||||
typedef struct PlantSettings_t
|
||||
{
|
||||
HomieSetting<double> *pSensorDry;
|
||||
HomieSetting<long> *pPumpAllowedHourRangeStart;
|
||||
HomieSetting<long> *pPumpAllowedHourRangeEnd;
|
||||
HomieSetting<bool> *pPumpOnlyWhenLowLight;
|
||||
HomieSetting<long> *pPumpCooldownInSeconds;
|
||||
HomieSetting<long> *pPumpDuration;
|
||||
HomieSetting<long> *pPumpPowerLevel;
|
||||
HomieSetting<long> *pPumpMl;
|
||||
} PlantSettings_t;
|
||||
|
||||
#endif
|
50
esp32/include/LogDefines.h
Normal file
50
esp32/include/LogDefines.h
Normal file
@ -0,0 +1,50 @@
|
||||
#ifndef LOG_DEFINES_H
|
||||
#define LOG_DEFINES_H
|
||||
|
||||
#define LOG_LEVEL_ERROR 0
|
||||
#define LOG_LEVEL_WARN 1
|
||||
#define LOG_LEVEL_INFO 2
|
||||
#define LOG_LEVEL_DEBUG 3
|
||||
|
||||
|
||||
#define LOG_TANKSENSOR_FAIL_DETECT "Failed to detect and initialize distance sensor!"
|
||||
#define LOG_TANKSENSOR_FAIL_DETECT_CODE -1
|
||||
|
||||
#define LOG_BACKUP_SUCCESSFUL "Backup sucessful"
|
||||
#define LOG_BACKUP_SUCCESSFUL_CODE 1
|
||||
|
||||
#define LOG_BACKUP_FAILED "Backup error"
|
||||
#define LOG_BACKUP_FAILED_CODE -2
|
||||
|
||||
#define LOG_PUMP_BUTNOTANK_MESSAGE "Want to pump but no water"
|
||||
#define LOG_PUMP_BUTNOTANK_CODE -3
|
||||
|
||||
#define LOG_HARDWARECOUNTER_ERROR_MESSAGE "PCNR returned error"
|
||||
#define LOG_HARDWARECOUNTER_ERROR_CODE -4
|
||||
|
||||
#define LOG_SENSORMODE_UNKNOWN "Unknown sensor mode requested"
|
||||
#define LOG_SENSORMODE_UNKNOWN_CODE -5
|
||||
|
||||
#define LOG_SENSOR_MISSING -6
|
||||
|
||||
#define LOG_PUMP_AND_DOWNLOADMODE "Download mode, ignoring pump request"
|
||||
#define LOG_PUMP_AND_DOWNLOADMODE_CODE 2
|
||||
|
||||
#define LOG_VERY_COLD_WATER "Water potentially frozen, ignoring pump request"
|
||||
#define LOG_VERY_COLD_WATER_CODE -7
|
||||
|
||||
#define LOG_PUMP_FULLTANK_MESSAGE "Water Sensor distance unrealistic"
|
||||
#define LOG_PUMP_FULLTANK_CODE 3
|
||||
|
||||
//msg is dynamic defined
|
||||
#define LOG_PUMP_INEFFECTIVE -4
|
||||
#define LOG_PUMP_STARTED_CODE 10
|
||||
#define LOG_DEBUG_CODE 1001
|
||||
#define LOG_SLEEP_NIGHT 100
|
||||
#define LOG_SLEEP_DAY 101
|
||||
#define LOG_SLEEP_LOWVOLTAGE 502
|
||||
#define LOG_SLEEP_CYCLE 102
|
||||
#define LOG_MISSING_PUMP -4
|
||||
#define LOG_BOOT_ERROR_DETECTION 10000
|
||||
#define LOG_SOLAR_CHARGER_MISSING 300
|
||||
#endif
|
28
esp32/include/MQTTUtils.h
Normal file
28
esp32/include/MQTTUtils.h
Normal file
@ -0,0 +1,28 @@
|
||||
#ifndef MQTTUtils_h
|
||||
#define MQTTUtils_h
|
||||
|
||||
#include <Homie.h>
|
||||
|
||||
#define LOG_TOPIC "log\0"
|
||||
#define TEST_TOPIC "roundtrip\0"
|
||||
#define BACKUP_TOPIC "$implementation/config/backup/set\0"
|
||||
|
||||
#define CONFIG_FILE "/homie/config.json"
|
||||
#define CONFIG_FILE_BACKUP "/homie/config.json.bak"
|
||||
|
||||
#define getTopic(test, topic) \
|
||||
char *topic = new char[strlen(Homie.getConfiguration().mqtt.baseTopic) + strlen(Homie.getConfiguration().deviceId) + 1 + strlen(test) + 1]; \
|
||||
strcpy(topic, Homie.getConfiguration().mqtt.baseTopic); \
|
||||
strcat(topic, Homie.getConfiguration().deviceId); \
|
||||
strcat(topic, "/"); \
|
||||
strcat(topic, test);
|
||||
|
||||
bool aliveWasRead();
|
||||
bool mqttReady();
|
||||
void startMQTTRoundtripTest();
|
||||
|
||||
void log(int level, String message, int code);
|
||||
void mqttWrite(HomieNode* target,const char* key, String value);
|
||||
void mqttWrite(HomieNode* target,String key, String value);
|
||||
|
||||
#endif
|
8
esp32/include/MathUtils.h
Normal file
8
esp32/include/MathUtils.h
Normal file
@ -0,0 +1,8 @@
|
||||
#ifndef MATHUTILS_H
|
||||
#define MATHUTILS_H
|
||||
|
||||
|
||||
bool equalish(double x, double y);
|
||||
double mapf(double x, double in_min, double in_max, double out_min, double out_max);
|
||||
|
||||
#endif
|
220
esp32/include/PlantCtrl.h
Normal file
220
esp32/include/PlantCtrl.h
Normal file
@ -0,0 +1,220 @@
|
||||
/**
|
||||
* @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
|
||||
|
||||
#include "HomieTypes.h"
|
||||
#include <HomieNode.hpp>
|
||||
#include "ControllerConfiguration.h"
|
||||
#include "RunningMedian.h"
|
||||
#include "MathUtils.h"
|
||||
#include "MQTTUtils.h"
|
||||
#include "LogDefines.h"
|
||||
|
||||
#define ANALOG_REREADS 5
|
||||
#define MOISTURE_MEASUREMENT_DURATION 400 /** ms */
|
||||
#define PWM_FREQ 50000
|
||||
#define PWM_BITS 8
|
||||
|
||||
class Plant
|
||||
{
|
||||
|
||||
private:
|
||||
HomieNode *mPlant = NULL;
|
||||
HomieInternals::PropertyInterface mPump;
|
||||
RunningMedian mMoisture_raw = RunningMedian(ANALOG_REREADS);
|
||||
RunningMedian mTemperature_degree = RunningMedian(ANALOG_REREADS);
|
||||
int mPinSensor = 0; /**< Pin of the moist sensor */
|
||||
int mPinPump = 0; /**< Pin of the pump */
|
||||
bool mConnected = false;
|
||||
int mPlantId = -1;
|
||||
SENSOR_MODE mSensorMode;
|
||||
|
||||
|
||||
public:
|
||||
PlantSettings_t *mSetting;
|
||||
/**
|
||||
* @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,
|
||||
int plantId,
|
||||
HomieNode *plant,
|
||||
PlantSettings_t *setting, SENSOR_MODE mode);
|
||||
|
||||
void postMQTTconnection(void);
|
||||
|
||||
void advertise(void);
|
||||
|
||||
// for sensor that might take any time
|
||||
void blockingMoistureMeasurement(void);
|
||||
// for sensor that need a start and a end in defined timing
|
||||
void startMoistureMeasurement(void);
|
||||
void stopMoistureMeasurement(void);
|
||||
|
||||
void deactivatePump(void);
|
||||
|
||||
void activatePump(void);
|
||||
|
||||
String getSensorModeString()
|
||||
{
|
||||
SENSOR_MODE mode = getSensorMode();
|
||||
return SENSOR_STRING[mode];
|
||||
}
|
||||
|
||||
bool isTimerOnly()
|
||||
{
|
||||
long current = this->mSetting->pSensorDry->get();
|
||||
return equalish(current, TIMER_ONLY);
|
||||
}
|
||||
|
||||
bool isHydroponic()
|
||||
{
|
||||
long current = this->mSetting->pSensorDry->get();
|
||||
return equalish(current, HYDROPONIC_MODE);
|
||||
}
|
||||
|
||||
SENSOR_MODE getSensorMode()
|
||||
{
|
||||
return mSensorMode;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Check if a plant is too dry and needs some water.
|
||||
*
|
||||
* @return true
|
||||
* @return false
|
||||
*/
|
||||
bool isPumpRequired()
|
||||
{
|
||||
if (isHydroponic() || isTimerOnly())
|
||||
{
|
||||
// hydroponic only uses timer based controll
|
||||
return true;
|
||||
}
|
||||
bool isDry = getCurrentMoisturePCT() < getTargetMoisturePCT();
|
||||
bool isActive = isPumpTriggerActive();
|
||||
return isDry && isActive;
|
||||
}
|
||||
|
||||
bool isPumpTriggerActive()
|
||||
{
|
||||
long current = this->mSetting->pSensorDry->get();
|
||||
return !equalish(current, DEACTIVATED_PLANT);
|
||||
}
|
||||
|
||||
float getTargetMoisturePCT()
|
||||
{
|
||||
if (isPumpTriggerActive())
|
||||
{
|
||||
return this->mSetting->pSensorDry->get();
|
||||
}
|
||||
else
|
||||
{
|
||||
return DEACTIVATED_PLANT;
|
||||
}
|
||||
}
|
||||
|
||||
float getCurrentMoisturePCT()
|
||||
{
|
||||
switch (getSensorMode())
|
||||
{
|
||||
case NONE:
|
||||
return DEACTIVATED_PLANT;
|
||||
case FREQUENCY_MOD_RESISTANCE_PROBE:
|
||||
return mapf(mMoisture_raw.getMedian(), MOIST_SENSOR_MIN_FRQ, MOIST_SENSOR_MAX_FRQ, 0, 100);
|
||||
case ANALOG_RESISTANCE_PROBE:
|
||||
return mapf(mMoisture_raw.getMedian(), ANALOG_SENSOR_MAX_MV, ANALOG_SENSOR_MIN_MV, 0, 100);
|
||||
}
|
||||
return MISSING_SENSOR;
|
||||
}
|
||||
|
||||
float getCurrentMoistureRaw()
|
||||
{
|
||||
if (getSensorMode() == FREQUENCY_MOD_RESISTANCE_PROBE)
|
||||
{
|
||||
if (mMoisture_raw.getMedian() < MOIST_SENSOR_MIN_FRQ)
|
||||
{
|
||||
return MISSING_SENSOR;
|
||||
}
|
||||
else if (mMoisture_raw.getMedian() > MOIST_SENSOR_MAX_FRQ)
|
||||
{
|
||||
return SHORT_CIRCUIT_MODE;
|
||||
}
|
||||
}
|
||||
|
||||
return mMoisture_raw.getMedian();
|
||||
}
|
||||
|
||||
HomieInternals::SendingPromise &setProperty(const String &property) const
|
||||
{
|
||||
return mPlant->setProperty(property);
|
||||
}
|
||||
|
||||
void init(void);
|
||||
void initSensors(void);
|
||||
|
||||
long getCooldownInSeconds()
|
||||
{
|
||||
return this->mSetting->pPumpCooldownInSeconds->get();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get the Hours when pumping should start
|
||||
*
|
||||
* @return hour
|
||||
*/
|
||||
int getHoursStart()
|
||||
{
|
||||
return this->mSetting->pPumpAllowedHourRangeStart->get();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get the Hours when pumping should end
|
||||
*
|
||||
* @return hour
|
||||
*/
|
||||
int getHoursEnd()
|
||||
{
|
||||
return this->mSetting->pPumpAllowedHourRangeEnd->get();
|
||||
}
|
||||
|
||||
bool isAllowedOnlyAtLowLight(void)
|
||||
{
|
||||
if (this->isHydroponic())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return this->mSetting->pPumpOnlyWhenLowLight->get();
|
||||
}
|
||||
|
||||
void publishState(int stateNumber, String stateString);
|
||||
|
||||
bool switchHandler(const HomieRange &range, const String &value);
|
||||
|
||||
void setSwitchHandler(HomieInternals::PropertyInputHandler f);
|
||||
|
||||
long getPumpDuration()
|
||||
{
|
||||
return this->mSetting->pPumpDuration->get();
|
||||
}
|
||||
long getPumpMl()
|
||||
{
|
||||
return this->mSetting->pPumpMl->get();
|
||||
}
|
||||
|
||||
|
||||
};
|
||||
|
||||
#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
|
75
esp32/include/RunningMedian.h
Normal file
75
esp32/include/RunningMedian.h
Normal file
@ -0,0 +1,75 @@
|
||||
#pragma once
|
||||
//
|
||||
// FILE: RunningMedian.h
|
||||
// AUTHOR: Rob dot Tillaart at gmail dot com
|
||||
// PURPOSE: RunningMedian library for Arduino
|
||||
// VERSION: 0.2.1
|
||||
// URL: https://github.com/RobTillaart/RunningMedian
|
||||
// URL: http://arduino.cc/playground/Main/RunningMedian
|
||||
// HISTORY: See RunningMedian.cpp
|
||||
//
|
||||
|
||||
#include "Arduino.h"
|
||||
|
||||
#define RUNNING_MEDIAN_VERSION "0.2.1"
|
||||
|
||||
// prepare for dynamic version
|
||||
// not tested ==> use at own risk :)
|
||||
// #define RUNNING_MEDIAN_USE_MALLOC
|
||||
|
||||
// should at least be 5 to be practical,
|
||||
// odd sizes results in a 'real' middle element and will be a bit faster.
|
||||
// even sizes takes the average of the two middle elements as median
|
||||
#define MEDIAN_MIN_SIZE 5
|
||||
#define MEDIAN_MAX_SIZE 19
|
||||
|
||||
class RunningMedian
|
||||
{
|
||||
public:
|
||||
// # elements in the internal buffer
|
||||
explicit RunningMedian(const uint8_t size);
|
||||
~RunningMedian();
|
||||
|
||||
// resets internal buffer and var
|
||||
void clear();
|
||||
// adds a new value to internal buffer, optionally replacing the oldest element.
|
||||
void add(const float value);
|
||||
// returns the median == middle element
|
||||
float getMedian();
|
||||
|
||||
// returns average of the values in the internal buffer
|
||||
float getAverage();
|
||||
// returns average of the middle nMedian values, removes noise from outliers
|
||||
float getAverage(uint8_t nMedian);
|
||||
|
||||
float getHighest() { return getSortedElement(_cnt - 1); };
|
||||
float getLowest() { return getSortedElement(0); };
|
||||
|
||||
// get n'th element from the values in time order
|
||||
float getElement(const uint8_t n);
|
||||
// get n'th element from the values in size order
|
||||
float getSortedElement(const uint8_t n);
|
||||
// predict the max change of median after n additions
|
||||
float predict(const uint8_t n);
|
||||
|
||||
uint8_t getSize() { return _size; };
|
||||
// returns current used elements, getCount() <= getSize()
|
||||
uint8_t getCount() { return _cnt; };
|
||||
|
||||
protected:
|
||||
boolean _sorted;
|
||||
uint8_t _size;
|
||||
uint8_t _cnt;
|
||||
uint8_t _idx;
|
||||
|
||||
#ifdef RUNNING_MEDIAN_USE_MALLOC
|
||||
float *_ar;
|
||||
uint8_t *_p;
|
||||
#else
|
||||
float _ar[MEDIAN_MAX_SIZE];
|
||||
uint8_t _p[MEDIAN_MAX_SIZE];
|
||||
#endif
|
||||
void sort();
|
||||
};
|
||||
|
||||
// END OF FILE
|
4
esp32/include/TimeUtils.h
Normal file
4
esp32/include/TimeUtils.h
Normal file
@ -0,0 +1,4 @@
|
||||
#pragma once
|
||||
|
||||
long getCurrentTime(void);
|
||||
int getCurrentHour(void);
|
27
esp32/include/WakeReason.h
Normal file
27
esp32/include/WakeReason.h
Normal file
@ -0,0 +1,27 @@
|
||||
/**
|
||||
* @file WakeReason.h
|
||||
* @author your name (you@domain.com)
|
||||
* @brief
|
||||
* @version 0.1
|
||||
* @date 2020-11-28
|
||||
*
|
||||
* @copyright Copyright (c) 2020
|
||||
*
|
||||
*/
|
||||
#ifndef WAKEUP_REASON_H
|
||||
#define WAKEUP_REASON_H
|
||||
|
||||
#define WAKEUP_REASON_UNDEFINED 0
|
||||
#define WAKEUP_REASON_TEMP1_CHANGE 2
|
||||
#define WAKEUP_REASON_TEMP2_CHANGE 3
|
||||
#define WAKEUP_REASON_BATTERY_CHANGE 4
|
||||
#define WAKEUP_REASON_SOLAR_CHANGE 5
|
||||
#define WAKEUP_REASON_RTC_MISSING 6
|
||||
#define WAKEUP_REASON_TIME_UNSET 7
|
||||
#define WAKEUP_REASON_MODE2_WAKEUP_TIMER 8
|
||||
|
||||
|
||||
#define WAKEUP_REASON_MOIST_CHANGE 20 /**< <code>20-26</code> for plant0 to plant9 */
|
||||
#define WAKEUP_REASON_PLANT_DRY 30 /**< <code>30-36</code> for plant0 to plant9 */
|
||||
|
||||
#endif
|
177
esp32/include/ulp-pwm.h
Normal file
177
esp32/include/ulp-pwm.h
Normal file
@ -0,0 +1,177 @@
|
||||
#ifndef ULP_PWM_h
|
||||
#define ILP_PWM_h
|
||||
|
||||
#include <Arduino.h>
|
||||
#include "driver/rtc_io.h"
|
||||
#include "soc/rtc_cntl_reg.h"
|
||||
#include "soc/rtc.h"
|
||||
#include "esp32/ulp.h"
|
||||
#include "ControllerConfiguration.h"
|
||||
|
||||
#define LBL_START 1
|
||||
#define LBL_DELAY_ON 2
|
||||
#define LBL_DELAY_OFF 3
|
||||
#define LBL_SKIP_ON 4
|
||||
#define LBL_SKIP_OFF 5
|
||||
#define REGISTER_DELAY_LOOP_COUNTER R0
|
||||
#define REGISTER_TICKS_ON R1
|
||||
#define REGISTER_TICKS_OFF R2
|
||||
#define TOTAL_TICKS_DELAY 255
|
||||
#define PIN TIMED_LIGHT_PIN
|
||||
|
||||
//support 20 vars
|
||||
const size_t ulp_var_offset = CONFIG_ULP_COPROC_RESERVE_MEM - 20;
|
||||
//use the first for dimming
|
||||
const size_t ulp_dimm_offset = ulp_var_offset + 1;
|
||||
const size_t ulp_alive_offset = ulp_var_offset + 2;
|
||||
|
||||
//see https://github.com/perseus086/ESP32-notes
|
||||
const uint32_t rtc_bit[40] = {
|
||||
25, //gpio0
|
||||
0, //gpio1
|
||||
26, //gpio2
|
||||
0, //gpio3
|
||||
24, //gpio4
|
||||
0, //gpio5
|
||||
0, //gpio6
|
||||
0, //gpio7
|
||||
0, //gpio8
|
||||
0, //gpio9
|
||||
0, //gpio10
|
||||
0, //gpio11
|
||||
29, //gpio12
|
||||
28, //gpio13
|
||||
30, //gpio14
|
||||
27, //gpio15
|
||||
0, //gpio16
|
||||
31, //gpio17
|
||||
0, //gpio18
|
||||
0, //gpio19
|
||||
0, //gpio20
|
||||
0, //gpio21
|
||||
0, //gpio22
|
||||
0, //gpio23
|
||||
0, //gpio24
|
||||
20, //gpio25
|
||||
21, //gpio26
|
||||
0, //gpio27
|
||||
0, //gpio28
|
||||
0, //gpio29
|
||||
0, //gpio30
|
||||
0, //gpio31
|
||||
23, //gpio32
|
||||
22, //gpio33
|
||||
18, //gpio34
|
||||
19, //gpio35
|
||||
14, //gpio36
|
||||
15, //gpio37
|
||||
16, //gpio38
|
||||
17 //gpio39
|
||||
};
|
||||
|
||||
static inline void ulp_internal_data_write(size_t offset, uint16_t value)
|
||||
{
|
||||
if (offset >= CONFIG_ULP_COPROC_RESERVE_MEM || offset <= ulp_var_offset)
|
||||
{
|
||||
Serial.print("Invalid ULP offset detected, refusing write!");
|
||||
Serial.print(offset);
|
||||
Serial.print("-");
|
||||
Serial.print(ulp_var_offset);
|
||||
Serial.print("-");
|
||||
Serial.println(CONFIG_ULP_COPROC_RESERVE_MEM);
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
RTC_SLOW_MEM[offset] = value;
|
||||
}
|
||||
}
|
||||
|
||||
static inline uint16_t ulp_internal_data_read(size_t offset)
|
||||
{
|
||||
if (offset >= CONFIG_ULP_COPROC_RESERVE_MEM || offset <= ulp_var_offset)
|
||||
{
|
||||
Serial.print("Invalid ULP offset detected");
|
||||
Serial.print(offset);
|
||||
Serial.print("-");
|
||||
Serial.print(ulp_var_offset);
|
||||
Serial.print("-");
|
||||
Serial.println(CONFIG_ULP_COPROC_RESERVE_MEM);
|
||||
}
|
||||
return RTC_SLOW_MEM[offset] & 0xffff;
|
||||
}
|
||||
|
||||
void ulp_internal_start(void)
|
||||
{
|
||||
rtc_gpio_init(PIN);
|
||||
rtc_gpio_set_direction(PIN, RTC_GPIO_MODE_OUTPUT_ONLY);
|
||||
rtc_gpio_set_level(PIN, 0);
|
||||
const uint32_t rtc_gpio = rtc_io_number_get(PIN);
|
||||
|
||||
// Define ULP program
|
||||
const ulp_insn_t ulp_prog[] = {
|
||||
M_LABEL(LBL_START),
|
||||
|
||||
I_MOVI(REGISTER_DELAY_LOOP_COUNTER, 1),
|
||||
I_MOVI(REGISTER_TICKS_ON, 0),
|
||||
I_ST(REGISTER_DELAY_LOOP_COUNTER, REGISTER_TICKS_ON, ulp_alive_offset), //store 1 with 0 offset into alive
|
||||
|
||||
I_LD(REGISTER_TICKS_ON, REGISTER_TICKS_ON, ulp_dimm_offset), //REGISTER_TICKS_ON = RTC_DATA[0+ulp_dimm_offset]
|
||||
//in total there is always 255 delay loop iterations, but in different duty cycle
|
||||
I_MOVI(REGISTER_TICKS_OFF, TOTAL_TICKS_DELAY),
|
||||
I_SUBR(REGISTER_TICKS_OFF, REGISTER_TICKS_OFF, REGISTER_TICKS_ON),
|
||||
|
||||
//on phase
|
||||
I_MOVR(REGISTER_DELAY_LOOP_COUNTER, REGISTER_TICKS_ON),
|
||||
M_BL(LBL_SKIP_ON, 1), //if never on, skip on phase
|
||||
I_WR_REG(RTC_GPIO_OUT_REG, rtc_gpio, rtc_gpio, HIGH), // on
|
||||
M_LABEL(LBL_DELAY_ON),
|
||||
I_DELAY(1), //wait 1 clock
|
||||
I_SUBI(REGISTER_DELAY_LOOP_COUNTER, REGISTER_DELAY_LOOP_COUNTER, 1), // REGISTER_DELAY_LOOP_COUNTER--
|
||||
M_BGE(LBL_DELAY_ON, 1), //if time left, goto start of on loop
|
||||
M_LABEL(LBL_SKIP_ON),
|
||||
|
||||
//off phase
|
||||
I_MOVR(REGISTER_DELAY_LOOP_COUNTER, REGISTER_TICKS_OFF),
|
||||
|
||||
M_BL(LBL_SKIP_OFF, 1), //if never off, skip on phase
|
||||
I_WR_REG(RTC_GPIO_OUT_REG, rtc_gpio, rtc_gpio, LOW), // on
|
||||
M_LABEL(3),
|
||||
I_DELAY(1), //wait 1 clock
|
||||
I_SUBI(REGISTER_DELAY_LOOP_COUNTER, REGISTER_DELAY_LOOP_COUNTER, 1), // REGISTER_DELAY_LOOP_COUNTER--
|
||||
M_BGE(3, 1), //if time left, goto start of on loop
|
||||
M_LABEL(LBL_SKIP_OFF),
|
||||
|
||||
M_BX(LBL_START),
|
||||
};
|
||||
// Run ULP program
|
||||
size_t size = sizeof(ulp_prog) / sizeof(ulp_insn_t);
|
||||
assert(size < ulp_var_offset && "ULP_DATA_OFFSET needs to be greater or equal to the program size");
|
||||
esp_err_t error = ulp_process_macros_and_load(0, ulp_prog, &size);
|
||||
Serial.print("ULP bootstrap status ");
|
||||
Serial.println(error);
|
||||
|
||||
//allow glitchless start
|
||||
ulp_internal_data_write(ulp_alive_offset, 0);
|
||||
|
||||
error = ulp_run(0);
|
||||
Serial.print("ULP start status ");
|
||||
Serial.println(error);
|
||||
}
|
||||
|
||||
static inline void ulp_pwm_set_level(uint8_t level)
|
||||
{
|
||||
ulp_internal_data_write(ulp_dimm_offset, level);
|
||||
}
|
||||
|
||||
static inline void ulp_pwm_init()
|
||||
{
|
||||
ulp_internal_data_write(ulp_alive_offset, 0);
|
||||
delay(10);
|
||||
if (ulp_internal_data_read(ulp_alive_offset) == 0)
|
||||
{
|
||||
ulp_internal_start();
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
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
|
40
esp32/platformio.ini
Normal file
40
esp32/platformio.ini
Normal file
@ -0,0 +1,40 @@
|
||||
; 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@6.3.2
|
||||
board = esp32doit-devkit-v1
|
||||
framework = arduino
|
||||
build_flags = -DPIO_FRAMEWORK_ARDUINO_LWIP2_LOW_MEMORY
|
||||
-DPLANT0_SENSORTYPE=FREQUENCY_MOD_RESISTANCE_PROBE
|
||||
-DPLANT1_SENSORTYPE=FREQUENCY_MOD_RESISTANCE_PROBE
|
||||
-DPLANT2_SENSORTYPE=FREQUENCY_MOD_RESISTANCE_PROBE
|
||||
-DPLANT3_SENSORTYPE=FREQUENCY_MOD_RESISTANCE_PROBE
|
||||
-DPLANT4_SENSORTYPE=FREQUENCY_MOD_RESISTANCE_PROBE
|
||||
-DPLANT5_SENSORTYPE=FREQUENCY_MOD_RESISTANCE_PROBE
|
||||
-DPLANT6_SENSORTYPE=FREQUENCY_MOD_RESISTANCE_PROBE
|
||||
-DTIMED_LIGHT_PIN=CUSTOM1_PIN5
|
||||
-DFLOWMETER_PIN=CUSTOM1_PIN1
|
||||
|
||||
board_build.partitions = defaultWithSmallerSpiffs.csv
|
||||
|
||||
;#https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-guides/partition-tables.html
|
||||
|
||||
|
||||
; the latest development brankitchen-lightch (convention V3.0.x)
|
||||
lib_deps = bblanchon/ArduinoJson@^6.20.1
|
||||
paulstoffregen/OneWire@^2.3.6
|
||||
milesburton/DallasTemperature@^3.11.0
|
||||
pololu/VL53L0X@^1.3.1
|
||||
https://github.com/homieiot/homie-esp8266.git#develop
|
||||
|
||||
[platformio]
|
||||
|
||||
extra_configs = custom_platformio.ini
|
1638
esp32/sdkconfig.esp32doit-devkit-v1
Normal file
1638
esp32/sdkconfig.esp32doit-devkit-v1
Normal file
File diff suppressed because it is too large
Load Diff
6
esp32/src/CMakeLists.txt
Normal file
6
esp32/src/CMakeLists.txt
Normal file
@ -0,0 +1,6 @@
|
||||
# This file was automatically generated for projects
|
||||
# without default 'CMakeLists.txt' file.
|
||||
|
||||
FILE(GLOB_RECURSE app_sources ${CMAKE_SOURCE_DIR}/src/*.*)
|
||||
|
||||
idf_component_register(SRCS ${app_sources})
|
272
esp32/src/DS2438.cpp
Normal file
272
esp32/src/DS2438.cpp
Normal file
@ -0,0 +1,272 @@
|
||||
/*
|
||||
* DS2438.cpp
|
||||
*
|
||||
* by Joe Bechter
|
||||
*
|
||||
* (C) 2012, bechter.com
|
||||
*
|
||||
* All files, software, schematics and designs are provided as-is with no warranty.
|
||||
* All files, software, schematics and designs are for experimental/hobby use.
|
||||
* Under no circumstances should any part be used for critical systems where safety,
|
||||
* life or property depends upon it. You are responsible for all use.
|
||||
* You are free to use, modify, derive or otherwise extend for your own non-commercial purposes provided
|
||||
* 1. No part of this software or design may be used to cause injury or death to humans or animals.
|
||||
* 2. Use is non-commercial.
|
||||
* 3. Credit is given to the author (i.e. portions © bechter.com), and provide a link to the original source.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "DS2438.h"
|
||||
|
||||
// DSROM FIELDS
|
||||
#define DSROM_FAMILY 0
|
||||
#define DSROM_CRC 7
|
||||
|
||||
#define DS2438MODEL 0x26
|
||||
|
||||
DS2438::DS2438(OneWire *ow, float currentShunt, int retryOnCRCError) {
|
||||
_ow = ow;
|
||||
_currentShunt = currentShunt;
|
||||
_retryOnCRCError = retryOnCRCError;
|
||||
};
|
||||
|
||||
void DS2438::begin(){
|
||||
DeviceAddress searchDeviceAddress;
|
||||
|
||||
_ow->reset_search();
|
||||
memset(searchDeviceAddress,0, 8);
|
||||
_temperature.clear();
|
||||
_voltageA.clear();
|
||||
_voltageB.clear();
|
||||
_error = true;
|
||||
_mode = (DS2438_MODE_CHA | DS2438_MODE_CHB | DS2438_MODE_TEMPERATURE);
|
||||
|
||||
deviceFound = false; // Reset the number of devices when we enumerate wire devices
|
||||
|
||||
while (_ow->search(searchDeviceAddress)) {
|
||||
if (validAddress(searchDeviceAddress)) {
|
||||
if (validFamily(searchDeviceAddress)) {
|
||||
memcpy(_address,searchDeviceAddress,8);
|
||||
DEFAULT_PAGE0(defaultConfig);
|
||||
writePage(0, defaultConfig);
|
||||
deviceFound = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool DS2438::isFound(){
|
||||
return deviceFound;
|
||||
}
|
||||
|
||||
bool DS2438::validAddress(const uint8_t* deviceAddress) {
|
||||
return (_ow->crc8(deviceAddress, 7) == deviceAddress[DSROM_CRC]);
|
||||
}
|
||||
|
||||
bool DS2438::validFamily(const uint8_t* deviceAddress) {
|
||||
switch (deviceAddress[DSROM_FAMILY]) {
|
||||
case DS2438MODEL:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
void DS2438::updateMultiple(){
|
||||
for(int i = 0;i< DS2438_MEDIAN_COUNT; i++){
|
||||
update(i==0);
|
||||
if(_error){
|
||||
return;
|
||||
}
|
||||
delay(DS2438_MEDIAN_DELAY);
|
||||
}
|
||||
}
|
||||
|
||||
void DS2438::update(bool firstIteration) {
|
||||
uint8_t data[9];
|
||||
_error = true;
|
||||
|
||||
if(!isFound()){
|
||||
return;
|
||||
}
|
||||
|
||||
if (_mode & DS2438_MODE_CHA || _mode == DS2438_MODE_TEMPERATURE) {
|
||||
boolean doTemperature = _mode & DS2438_MODE_TEMPERATURE;
|
||||
if (!startConversion(DS2438_CHA, doTemperature)) {
|
||||
Serial.println("Error starting temp conversion ds2438 channel a");
|
||||
return;
|
||||
}
|
||||
if (!readPage(0, data)){
|
||||
|
||||
Serial.println("Error reading zero page ds2438 channel a");
|
||||
return;
|
||||
}
|
||||
|
||||
if (doTemperature) {
|
||||
_temperature.add((double)(((((int16_t)data[2]) << 8) | (data[1] & 0x0ff)) >> 3) * 0.03125);
|
||||
}
|
||||
if (_mode & DS2438_MODE_CHA) {
|
||||
_voltageA.add((((data[4] << 8) & 0x00300) | (data[3] & 0x0ff)) / 100.0);
|
||||
}
|
||||
}
|
||||
if (_mode & DS2438_MODE_CHB) {
|
||||
boolean doTemperature = _mode & DS2438_MODE_TEMPERATURE && !(_mode & DS2438_MODE_CHA);
|
||||
if (!startConversion(DS2438_CHB, doTemperature)) {
|
||||
Serial.println("Error starting temp conversion channel b ds2438");
|
||||
return;
|
||||
}
|
||||
if (!readPage(0, data)){
|
||||
Serial.println("Error reading zero page ds2438 channel b");
|
||||
return;
|
||||
}
|
||||
if (doTemperature) {
|
||||
int16_t upperByte = ((int16_t)data[2]) << 8;
|
||||
int16_t lowerByte = data[1] >> 3;
|
||||
int16_t fullByte = (upperByte | lowerByte);
|
||||
_temperature.add(((double)fullByte) * 0.03125);
|
||||
}
|
||||
_voltageB.add((((data[4] << 8) & 0x00300) | (data[3] & 0x0ff)) / 100.0);
|
||||
}
|
||||
|
||||
int16_t upperByte = ((int16_t)data[6]) << 8;
|
||||
int16_t lowerByte = data[5];
|
||||
int16_t fullByte = (int16_t)(upperByte | lowerByte);
|
||||
float fullByteb = fullByte;
|
||||
_current.add((fullByteb) / ((4096.0f * _currentShunt)));
|
||||
|
||||
|
||||
if(firstIteration){
|
||||
if (readPage(1, data)){
|
||||
PageOne_t *pOne = (PageOne_t *) data;
|
||||
_ICA = pOne->ICA;
|
||||
}
|
||||
|
||||
if (readPage(7, data)){
|
||||
PageSeven_t *pSeven = (PageSeven_t *) data;
|
||||
_CCA = pSeven->CCA0 | ((int16_t) pSeven->CCA1) << 8;
|
||||
_DCA = pSeven->DCA0 | ((int16_t) pSeven->DCA1) << 8;
|
||||
}
|
||||
}
|
||||
_error = false;
|
||||
}
|
||||
|
||||
double DS2438::getTemperature() {
|
||||
return _temperature.getMedian();
|
||||
}
|
||||
|
||||
float DS2438::getAh(){
|
||||
return _ICA / (2048.0f * _currentShunt);
|
||||
}
|
||||
|
||||
long DS2438::getICA(){
|
||||
return _ICA;
|
||||
}
|
||||
|
||||
long DS2438::getDCA(){
|
||||
return _DCA;
|
||||
}
|
||||
|
||||
long DS2438::getCCA(){
|
||||
return _CCA;
|
||||
}
|
||||
|
||||
|
||||
float DS2438::getVoltage(int channel) {
|
||||
if (channel == DS2438_CHA) {
|
||||
return _voltageA.getMedian();
|
||||
} else if (channel == DS2438_CHB) {
|
||||
return _voltageB.getMedian();
|
||||
} else {
|
||||
return 0.0;
|
||||
}
|
||||
}
|
||||
|
||||
float DS2438::getCurrent() {
|
||||
return _current.getMedian();
|
||||
}
|
||||
|
||||
boolean DS2438::isError() {
|
||||
return _error;
|
||||
}
|
||||
|
||||
boolean DS2438::startConversion(int channel, boolean doTemperature) {
|
||||
if(!isFound()){
|
||||
return false;
|
||||
}
|
||||
if (!selectChannel(channel)){
|
||||
return false;
|
||||
}
|
||||
_ow->reset();
|
||||
_ow->select(_address);
|
||||
if (doTemperature) {
|
||||
_ow->write(DS2438_TEMPERATURE_CONVERSION_COMMAND, 0);
|
||||
delay(DS2438_TEMPERATURE_DELAY);
|
||||
_ow->reset();
|
||||
_ow->select(_address);
|
||||
}
|
||||
_ow->write(DS2438_VOLTAGE_CONVERSION_COMMAND, 0);
|
||||
delay(DS2438_VOLTAGE_CONVERSION_DELAY);
|
||||
return true;
|
||||
}
|
||||
|
||||
boolean DS2438::selectChannel(int channel) {
|
||||
if(!isFound()){
|
||||
return false;
|
||||
}
|
||||
uint8_t data[9];
|
||||
if (readPage(0, data)) {
|
||||
if (channel == DS2438_CHB){
|
||||
data[0] = data[0] | 0x08;
|
||||
}
|
||||
else {
|
||||
data[0] = data[0] & 0xf7;
|
||||
}
|
||||
writePage(0, data);
|
||||
return true;
|
||||
}
|
||||
Serial.println("Could not read page zero data");
|
||||
return false;
|
||||
}
|
||||
|
||||
void DS2438::writePage(int page, uint8_t *data) {
|
||||
_ow->reset();
|
||||
_ow->select(_address);
|
||||
_ow->write(DS2438_WRITE_SCRATCHPAD_COMMAND, 0);
|
||||
if ((page >= PAGE_MIN) && (page <= PAGE_MAX)) {
|
||||
_ow->write(page, 0);
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
for (int i = 0; i < 8; i++){
|
||||
_ow->write(data[i], 0);
|
||||
}
|
||||
_ow->reset();
|
||||
_ow->select(_address);
|
||||
_ow->write(DS2438_COPY_SCRATCHPAD_COMMAND, 0);
|
||||
_ow->write(page, 0);
|
||||
}
|
||||
|
||||
boolean DS2438::readPage(int page, uint8_t *data) {
|
||||
bool valid = false;
|
||||
for(int retry = 0;retry < this->_retryOnCRCError && !valid; retry ++){
|
||||
//TODO if all data is 0 0 is a valid crc, but most likly not as intended
|
||||
_ow->reset();
|
||||
_ow->select(_address);
|
||||
_ow->write(DS2438_RECALL_MEMORY_COMMAND, 0);
|
||||
if ((page >= PAGE_MIN) && (page <= PAGE_MAX)) {
|
||||
_ow->write(page, 0);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
_ow->reset();
|
||||
_ow->select(_address);
|
||||
_ow->write(DS2438_READ_SCRATCHPAD_COMMAND, 0);
|
||||
_ow->write(page, 0);
|
||||
for (int i = 0; i < 9; i++){
|
||||
data[i] = _ow->read();
|
||||
}
|
||||
valid = _ow->crc8(data, 8) == data[8];
|
||||
}
|
||||
return valid;
|
||||
}
|
||||
|
81
esp32/src/FileUtils.cpp
Normal file
81
esp32/src/FileUtils.cpp
Normal file
@ -0,0 +1,81 @@
|
||||
#include <Homie.h>
|
||||
#include "FileUtils.h"
|
||||
|
||||
bool deleteFile(const char *source)
|
||||
{
|
||||
Serial << "deleting file " << source << endl;
|
||||
if (!SPIFFS.begin())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
bool deleted = SPIFFS.remove(source);
|
||||
if (deleted)
|
||||
{
|
||||
Serial << "Deleted " << source << endl;
|
||||
}
|
||||
else
|
||||
{
|
||||
Serial << "Could not delete " << source << endl;
|
||||
}
|
||||
return deleted;
|
||||
}
|
||||
|
||||
void printFile(const char *source)
|
||||
{
|
||||
Serial << "printing file " << source << endl;
|
||||
if (!SPIFFS.begin())
|
||||
{
|
||||
Serial << "could not start spiffs " << source << endl;
|
||||
return;
|
||||
}
|
||||
File file = SPIFFS.open(source, FILE_READ);
|
||||
if (!file)
|
||||
{
|
||||
Serial << "could not start open " << source << endl;
|
||||
return;
|
||||
}
|
||||
Serial << file.readString() << endl;
|
||||
Serial << "Finished printing file " << source << endl;
|
||||
file.close();
|
||||
}
|
||||
|
||||
bool doesFileExist(const char *source)
|
||||
{
|
||||
Serial << "checking if file exist " << source << endl;
|
||||
if (!SPIFFS.begin())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
bool exists = SPIFFS.exists(source);
|
||||
Serial << "File " << source << (exists ? "" : " not") << " found " << endl;
|
||||
return exists;
|
||||
}
|
||||
|
||||
bool copyFile(const char *source, const char *target)
|
||||
{
|
||||
Serial << "copy started " << source << " -> " << target << endl;
|
||||
if (!SPIFFS.begin())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
File file = SPIFFS.open(source, FILE_READ);
|
||||
File file2 = SPIFFS.open(target, FILE_WRITE);
|
||||
Serial.flush();
|
||||
if (!file)
|
||||
{
|
||||
Serial << "There was an error opening " << source << " for reading" << endl;
|
||||
return false;
|
||||
}
|
||||
if (!file2)
|
||||
{
|
||||
Serial << "There was an error opening " << target << " for reading" << endl;
|
||||
file.close();
|
||||
return false;
|
||||
}
|
||||
file2.println(file.readString());
|
||||
Serial << "copy finished " << source << " -> " << target << endl;
|
||||
file.close();
|
||||
file2.close();
|
||||
return true;
|
||||
}
|
98
esp32/src/MQTTUtils.cpp
Normal file
98
esp32/src/MQTTUtils.cpp
Normal file
@ -0,0 +1,98 @@
|
||||
#include "MQTTUtils.h"
|
||||
#include "FileUtils.h"
|
||||
#include "LogDefines.h"
|
||||
|
||||
|
||||
bool volatile mAliveWasRead = false;
|
||||
|
||||
void log(int level, String message, int statusCode)
|
||||
{
|
||||
String buffer;
|
||||
StaticJsonDocument<200> doc;
|
||||
// Read the current time
|
||||
time_t now; // this is the epoch
|
||||
tm tm; // the structure tm holds time information in a more convient way
|
||||
doc["level"] = level;
|
||||
doc["message"] = message;
|
||||
doc["statusCode"] = statusCode;
|
||||
time(&now);
|
||||
localtime_r(&now, &tm);
|
||||
if (tm.tm_year > (2021 - 1970)) { /* Only add the time, if we have at least 2021 */
|
||||
doc["time"] = String(String(1900 + tm.tm_year) + "-" + String(tm.tm_mon + 1) + "-" + String(tm.tm_mday) +
|
||||
" " + String(tm.tm_hour) + ":" + String(tm.tm_min) + ":" + String(tm.tm_sec));
|
||||
}
|
||||
|
||||
serializeJson(doc, buffer);
|
||||
if (mAliveWasRead)
|
||||
{
|
||||
getTopic(LOG_TOPIC, logTopic)
|
||||
Homie.getMqttClient()
|
||||
.subscribe(logTopic, 2);
|
||||
|
||||
Homie.getMqttClient().publish(logTopic, 2, false, buffer.c_str());
|
||||
delete logTopic;
|
||||
}
|
||||
Serial << statusCode << "@" << level << " : " << message << endl;
|
||||
Serial.flush();
|
||||
}
|
||||
|
||||
|
||||
void mqttWrite(HomieNode* target,String key, String value){
|
||||
if(mAliveWasRead){
|
||||
target->setProperty(key).send(value);
|
||||
}
|
||||
}
|
||||
|
||||
void mqttWrite(HomieNode* target,const char* key, String value){
|
||||
if(aliveWasRead()){
|
||||
target->setProperty(key).send(value);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void onMQTTMessage(char *incoming, char *payload, AsyncMqttClientMessageProperties properties, size_t len, size_t index, size_t total)
|
||||
{
|
||||
getTopic(TEST_TOPIC, testTopic);
|
||||
if (strcmp(incoming, testTopic) == 0)
|
||||
{
|
||||
mAliveWasRead = true;
|
||||
}
|
||||
delete testTopic;
|
||||
getTopic(BACKUP_TOPIC, backupTopic);
|
||||
if (strcmp(incoming, backupTopic) == 0)
|
||||
{
|
||||
if (strcmp(payload, "true") == 0)
|
||||
{
|
||||
bool backupSucessful = copyFile(CONFIG_FILE, CONFIG_FILE_BACKUP);
|
||||
printFile(CONFIG_FILE_BACKUP);
|
||||
if (backupSucessful)
|
||||
{
|
||||
log(LOG_LEVEL_INFO, LOG_BACKUP_SUCCESSFUL, LOG_BACKUP_SUCCESSFUL_CODE);
|
||||
}
|
||||
else
|
||||
{
|
||||
log(LOG_LEVEL_INFO, LOG_BACKUP_FAILED, LOG_BACKUP_FAILED_CODE);
|
||||
}
|
||||
Homie.getMqttClient().publish(backupTopic, 2, true, "false");
|
||||
}
|
||||
}
|
||||
delete backupTopic;
|
||||
}
|
||||
|
||||
bool aliveWasRead(){
|
||||
return mAliveWasRead;
|
||||
}
|
||||
|
||||
void startMQTTRoundtripTest(){
|
||||
{
|
||||
getTopic(TEST_TOPIC, testopic)
|
||||
Homie.getMqttClient()
|
||||
.subscribe(testopic, 2);
|
||||
Homie.getMqttClient().publish(testopic, 2, false, "ping");
|
||||
Homie.getMqttClient().onMessage(onMQTTMessage);
|
||||
|
||||
getTopic(BACKUP_TOPIC, backupTopic)
|
||||
Homie.getMqttClient()
|
||||
.subscribe(backupTopic, 2);
|
||||
}
|
||||
}
|
12
esp32/src/MathUtil.cpp
Normal file
12
esp32/src/MathUtil.cpp
Normal file
@ -0,0 +1,12 @@
|
||||
|
||||
#include "MathUtils.h"
|
||||
#include <Arduino.h>
|
||||
bool equalish(double x, double y)
|
||||
{
|
||||
return (abs(x - y) < 0.5);
|
||||
}
|
||||
|
||||
double mapf(double x, double in_min, double in_max, double out_min, double out_max)
|
||||
{
|
||||
return (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min;
|
||||
}
|
276
esp32/src/PlantCtrl.cpp
Normal file
276
esp32/src/PlantCtrl.cpp
Normal file
@ -0,0 +1,276 @@
|
||||
/**
|
||||
* @file PlantCtrl.cpp
|
||||
* @author your name (you@domain.com)
|
||||
* @brief
|
||||
* @version 0.1
|
||||
* @date 2020-05-27
|
||||
*
|
||||
* @copyright Copyright (c) 2020
|
||||
*
|
||||
|
||||
*/
|
||||
#include "PlantCtrl.h"
|
||||
#include "ControllerConfiguration.h"
|
||||
#include "TimeUtils.h"
|
||||
#include "driver/pcnt.h"
|
||||
#include "MQTTUtils.h"
|
||||
|
||||
Plant::Plant(int pinSensor, int pinPump, int plantId, HomieNode *plant, PlantSettings_t *setting, SENSOR_MODE mode)
|
||||
{
|
||||
this->mPinSensor = pinSensor;
|
||||
this->mPinPump = pinPump;
|
||||
this->mPlant = plant;
|
||||
this->mSetting = setting;
|
||||
this->mPlantId = plantId;
|
||||
this->mSensorMode = mode;
|
||||
}
|
||||
|
||||
void Plant::init(void)
|
||||
{
|
||||
/* Initialize Home Settings validator */
|
||||
this->mSetting->pSensorDry->setDefaultValue(DEACTIVATED_PLANT);
|
||||
this->mSetting->pSensorDry->setValidator([](long candidate)
|
||||
{ return (((candidate >= 0.0) && (candidate <= 100.0)) || equalish(candidate, DEACTIVATED_PLANT) || equalish(candidate, HYDROPONIC_MODE) || equalish(candidate, TIMER_ONLY)); });
|
||||
|
||||
this->mSetting->pPumpAllowedHourRangeStart->setDefaultValue(5); // start at 5:00 UTC or 7:00 ECST
|
||||
this->mSetting->pPumpAllowedHourRangeStart->setValidator([](long candidate)
|
||||
{ return ((candidate >= 0) && (candidate <= 23)); });
|
||||
this->mSetting->pPumpAllowedHourRangeEnd->setDefaultValue(18); // stop pumps at 18 UTC or 20:00 ECST
|
||||
this->mSetting->pPumpAllowedHourRangeEnd->setValidator([](long candidate)
|
||||
{ return ((candidate >= 0) && (candidate <= 23)); });
|
||||
this->mSetting->pPumpOnlyWhenLowLight->setDefaultValue(false);
|
||||
this->mSetting->pPumpCooldownInSeconds->setDefaultValue(60 * 60); // 1 hour
|
||||
this->mSetting->pPumpCooldownInSeconds->setValidator([](long candidate)
|
||||
{ return (candidate >= 0); });
|
||||
|
||||
this->mSetting->pPumpDuration->setDefaultValue(30);
|
||||
this->mSetting->pPumpDuration->setValidator([](long candidate)
|
||||
{ return ((candidate >= 0) && (candidate <= 1000)); });
|
||||
this->mSetting->pPumpMl->setDefaultValue(1000);
|
||||
this->mSetting->pPumpMl->setValidator([](long candidate)
|
||||
{ return ((candidate >= 0) && (candidate <= 5000)); });
|
||||
this->mSetting->pPumpPowerLevel->setDefaultValue(100);
|
||||
this->mSetting->pPumpPowerLevel->setValidator([](long candidate)
|
||||
{ return ((candidate >= 0) && (candidate <= 100)); });
|
||||
|
||||
/* Initialize Hardware */
|
||||
ledcSetup(this->mPlantId, PWM_FREQ, PWM_BITS);
|
||||
ledcAttachPin(mPinPump, this->mPlantId);
|
||||
ledcWrite(this->mPlantId, 0);
|
||||
pinMode(this->mPinSensor, INPUT);
|
||||
}
|
||||
|
||||
void Plant::initSensors(void)
|
||||
{
|
||||
switch (getSensorMode())
|
||||
{
|
||||
case FREQUENCY_MOD_RESISTANCE_PROBE:
|
||||
{
|
||||
|
||||
pcnt_unit_t unit = (pcnt_unit_t)(PCNT_UNIT_0 + this->mPlantId);
|
||||
pcnt_config_t pcnt_config = {}; // Instancia PCNT config
|
||||
|
||||
pcnt_config.pulse_gpio_num = this->mPinSensor; // Configura GPIO para entrada dos pulsos
|
||||
pcnt_config.ctrl_gpio_num = PCNT_PIN_NOT_USED; // Configura GPIO para controle da contagem
|
||||
pcnt_config.unit = unit; // Unidade de contagem PCNT - 0
|
||||
pcnt_config.channel = PCNT_CHANNEL_0; // Canal de contagem PCNT - 0
|
||||
pcnt_config.counter_h_lim = INT16_MAX; // Limite maximo de contagem - 20000
|
||||
pcnt_config.pos_mode = PCNT_COUNT_INC; // Incrementa contagem na subida do pulso
|
||||
pcnt_config.neg_mode = PCNT_COUNT_DIS; // Incrementa contagem na descida do pulso
|
||||
pcnt_config.lctrl_mode = PCNT_MODE_KEEP; // PCNT - modo lctrl desabilitado
|
||||
pcnt_config.hctrl_mode = PCNT_MODE_KEEP; // PCNT - modo hctrl - se HIGH conta incrementando
|
||||
pcnt_unit_config(&pcnt_config); // Configura o contador PCNT
|
||||
|
||||
pcnt_counter_pause(unit); // Pausa o contador PCNT
|
||||
pcnt_counter_clear(unit); // Zera o contador PCNT
|
||||
break;
|
||||
}
|
||||
case ANALOG_RESISTANCE_PROBE:
|
||||
{
|
||||
adcAttachPin(this->mPinSensor);
|
||||
break;
|
||||
}
|
||||
case NONE:
|
||||
{
|
||||
// do nothing
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Plant::blockingMoistureMeasurement(void)
|
||||
{
|
||||
switch (getSensorMode())
|
||||
{
|
||||
case ANALOG_RESISTANCE_PROBE:
|
||||
{
|
||||
for (int i = 0; i < ANALOG_REREADS; i++)
|
||||
{
|
||||
this->mMoisture_raw.add(analogReadMilliVolts(this->mPinSensor));
|
||||
delay(5);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case FREQUENCY_MOD_RESISTANCE_PROBE:
|
||||
case NONE:
|
||||
{
|
||||
// nothing to do here
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Plant::startMoistureMeasurement(void)
|
||||
{
|
||||
switch (getSensorMode())
|
||||
{
|
||||
case FREQUENCY_MOD_RESISTANCE_PROBE:
|
||||
{
|
||||
pcnt_unit_t unit = (pcnt_unit_t)(PCNT_UNIT_0 + this->mPlantId);
|
||||
pcnt_counter_resume(unit);
|
||||
break;
|
||||
}
|
||||
case ANALOG_RESISTANCE_PROBE:
|
||||
case NONE:
|
||||
{
|
||||
// do nothing here
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Plant::stopMoistureMeasurement(void)
|
||||
{
|
||||
switch (getSensorMode())
|
||||
{
|
||||
case FREQUENCY_MOD_RESISTANCE_PROBE:
|
||||
{
|
||||
int16_t pulses;
|
||||
pcnt_unit_t unit = (pcnt_unit_t)(PCNT_UNIT_0 + this->mPlantId);
|
||||
pcnt_counter_pause(unit);
|
||||
esp_err_t result = pcnt_get_counter_value(unit, &pulses);
|
||||
pcnt_counter_clear(unit);
|
||||
if (result != ESP_OK)
|
||||
{
|
||||
log(LOG_LEVEL_ERROR, LOG_HARDWARECOUNTER_ERROR_MESSAGE, LOG_HARDWARECOUNTER_ERROR_CODE);
|
||||
this->mMoisture_raw.clear();
|
||||
this->mMoisture_raw.add(-1);
|
||||
}
|
||||
else
|
||||
{
|
||||
this->mMoisture_raw.add(pulses * (1000 / MOISTURE_MEASUREMENT_DURATION));
|
||||
}
|
||||
break;
|
||||
}
|
||||
case ANALOG_RESISTANCE_PROBE:
|
||||
case NONE:
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Plant::postMQTTconnection(void)
|
||||
{
|
||||
const String OFF = String("OFF");
|
||||
this->mConnected = true;
|
||||
this->mPlant->setProperty("switch").send(OFF);
|
||||
|
||||
float pct = getCurrentMoisturePCT();
|
||||
float raw = getCurrentMoistureRaw();
|
||||
if (equalish(raw, MISSING_SENSOR))
|
||||
{
|
||||
pct = 0;
|
||||
}
|
||||
if (pct < 0)
|
||||
{
|
||||
pct = 0;
|
||||
}
|
||||
if (pct > 100)
|
||||
{
|
||||
pct = 100;
|
||||
}
|
||||
|
||||
this->mPlant->setProperty("moist").send(String(pct));
|
||||
this->mPlant->setProperty("sensormode").send(getSensorModeString());
|
||||
this->mPlant->setProperty("moistraw").send(String(raw));
|
||||
this->mPlant->setProperty("moisttrigger").send(String(getTargetMoisturePCT()));
|
||||
}
|
||||
|
||||
void Plant::deactivatePump(void)
|
||||
{
|
||||
int plantId = this->mPlantId;
|
||||
Serial << "deactivating pump " << plantId << endl;
|
||||
ledcWrite(this->mPlantId, 0);
|
||||
if (this->mConnected)
|
||||
{
|
||||
const String OFF = String("OFF");
|
||||
this->mPlant->setProperty("switch").send(OFF);
|
||||
}
|
||||
}
|
||||
|
||||
void Plant::publishState(int stateNumber, String stateString)
|
||||
{
|
||||
String buffer;
|
||||
StaticJsonDocument<200> doc;
|
||||
if (this->mConnected)
|
||||
{
|
||||
doc["number"] = stateNumber;
|
||||
doc["message"] = stateString;
|
||||
serializeJson(doc, buffer);
|
||||
this->mPlant->setProperty("state").send(buffer.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
void Plant::activatePump(void)
|
||||
{
|
||||
int plantId = this->mPlantId;
|
||||
|
||||
Serial << "activating pump " << plantId << endl;
|
||||
long desiredPowerLevelPercent = this->mSetting->pPumpPowerLevel->get();
|
||||
ledcWrite(this->mPlantId, desiredPowerLevelPercent * PWM_BITS);
|
||||
if (this->mConnected)
|
||||
{
|
||||
const String ON = String("ON");
|
||||
this->mPlant->setProperty("switch").send(ON);
|
||||
this->mPlant->setProperty("lastPump").send(String(getCurrentTime()));
|
||||
}
|
||||
}
|
||||
|
||||
bool Plant::switchHandler(const HomieRange &range, const String &value)
|
||||
{
|
||||
if (range.isRange)
|
||||
{
|
||||
return false; // only one switch is present
|
||||
}
|
||||
|
||||
if ((value.equals("ON")) || (value.equals("On")) || (value.equals("on")) || (value.equals("true")))
|
||||
{
|
||||
this->activatePump();
|
||||
return true;
|
||||
}
|
||||
else if ((value.equals("OFF")) || (value.equals("Off")) || (value.equals("off")) || (value.equals("false")))
|
||||
{
|
||||
this->deactivatePump();
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
void Plant::setSwitchHandler(HomieInternals::PropertyInputHandler f)
|
||||
{
|
||||
this->mPump.settable(f);
|
||||
}
|
||||
|
||||
void Plant::advertise(void)
|
||||
{
|
||||
// Advertise topics
|
||||
mPump = this->mPlant->advertise("switch").setName("Pump").setDatatype("Boolean");
|
||||
this->mPlant->advertise("lastPump").setName("lastPump").setDatatype("Integer").setUnit("unixtime").setRetained(true);
|
||||
this->mPlant->advertise("moist").setName("Percent").setDatatype("Float").setUnit("%").setRetained(true);
|
||||
this->mPlant->advertise("moistraw").setName("frequency").setDatatype("Float").setUnit("hz").setRetained(true);
|
||||
this->mPlant->advertise("state").setName("state").setDatatype("String").setRetained(true);
|
||||
|
||||
}
|
183
esp32/src/RunningMedian.cpp
Normal file
183
esp32/src/RunningMedian.cpp
Normal file
@ -0,0 +1,183 @@
|
||||
//
|
||||
// FILE: RunningMedian.cpp
|
||||
// AUTHOR: Rob.Tillaart at gmail.com
|
||||
// VERSION: 0.2.1
|
||||
// PURPOSE: RunningMedian library for Arduino
|
||||
//
|
||||
// HISTORY:
|
||||
// 0.1.00 - 2011-02-16 initial version
|
||||
// 0.1.01 - 2011-02-22 added remarks from CodingBadly
|
||||
// 0.1.02 - 2012-03-15 added
|
||||
// 0.1.03 - 2013-09-30 added _sorted flag, minor refactor
|
||||
// 0.1.04 - 2013-10-17 added getAverage(uint8_t) - kudo's to Sembazuru
|
||||
// 0.1.05 - 2013-10-18 fixed bug in sort; removes default constructor; dynamic memory
|
||||
// 0.1.06 - 2013-10-19 faster sort, dynamic arrays, replaced sorted float array with indirection array
|
||||
// 0.1.07 - 2013-10-19 add correct median if _cnt is even.
|
||||
// 0.1.08 - 2013-10-20 add getElement(), add getSottedElement() add predict()
|
||||
// 0.1.09 - 2014-11-25 float to double (support ARM)
|
||||
// 0.1.10 - 2015-03-07 fix clear
|
||||
// 0.1.11 - 2015-03-29 undo 0.1.10 fix clear
|
||||
// 0.1.12 - 2015-07-12 refactor constructor + const
|
||||
// 0.1.13 - 2015-10-30 fix getElement(n) - kudos to Gdunge
|
||||
// 0.1.14 - 2017-07-26 revert double to float - issue #33
|
||||
// 0.1.15 - 2018-08-24 make runningMedian Configurable #110
|
||||
// 0.2.0 2020-04-16 refactor.
|
||||
// 0.2.1 2020-06-19 fix library.json
|
||||
|
||||
#include "RunningMedian.h"
|
||||
|
||||
RunningMedian::RunningMedian(const uint8_t size)
|
||||
{
|
||||
_size = constrain(size, MEDIAN_MIN_SIZE, MEDIAN_MAX_SIZE);
|
||||
|
||||
#ifdef RUNNING_MEDIAN_USE_MALLOC
|
||||
_ar = (float *)malloc(_size * sizeof(float));
|
||||
_p = (uint8_t *)malloc(_size * sizeof(uint8_t));
|
||||
#endif
|
||||
|
||||
clear();
|
||||
}
|
||||
|
||||
RunningMedian::~RunningMedian()
|
||||
{
|
||||
#ifdef RUNNING_MEDIAN_USE_MALLOC
|
||||
free(_ar);
|
||||
free(_p);
|
||||
#endif
|
||||
}
|
||||
|
||||
// resets all counters
|
||||
void RunningMedian::clear()
|
||||
{
|
||||
_cnt = 0;
|
||||
_idx = 0;
|
||||
_sorted = false;
|
||||
for (uint8_t i = 0; i < _size; i++)
|
||||
{
|
||||
_p[i] = i;
|
||||
}
|
||||
}
|
||||
|
||||
// adds a new value to the data-set
|
||||
// or overwrites the oldest if full.
|
||||
void RunningMedian::add(float value)
|
||||
{
|
||||
_ar[_idx++] = value;
|
||||
if (_idx >= _size)
|
||||
_idx = 0; // wrap around
|
||||
if (_cnt < _size)
|
||||
_cnt++;
|
||||
_sorted = false;
|
||||
}
|
||||
|
||||
float RunningMedian::getMedian()
|
||||
{
|
||||
if (_cnt == 0)
|
||||
return NAN;
|
||||
|
||||
if (_sorted == false)
|
||||
sort();
|
||||
|
||||
if (_cnt & 0x01) // is it odd sized?
|
||||
{
|
||||
return _ar[_p[_cnt / 2]];
|
||||
}
|
||||
return (_ar[_p[_cnt / 2]] + _ar[_p[_cnt / 2 - 1]]) / 2;
|
||||
}
|
||||
|
||||
float RunningMedian::getAverage()
|
||||
{
|
||||
if (_cnt == 0)
|
||||
return NAN;
|
||||
|
||||
float sum = 0;
|
||||
for (uint8_t i = 0; i < _cnt; i++)
|
||||
{
|
||||
sum += _ar[i];
|
||||
}
|
||||
return sum / _cnt;
|
||||
}
|
||||
|
||||
float RunningMedian::getAverage(uint8_t nMedians)
|
||||
{
|
||||
if ((_cnt == 0) || (nMedians == 0))
|
||||
return NAN;
|
||||
|
||||
if (_cnt < nMedians)
|
||||
nMedians = _cnt; // when filling the array for first time
|
||||
uint8_t start = ((_cnt - nMedians) / 2);
|
||||
uint8_t stop = start + nMedians;
|
||||
|
||||
if (_sorted == false)
|
||||
sort();
|
||||
|
||||
float sum = 0;
|
||||
for (uint8_t i = start; i < stop; i++)
|
||||
{
|
||||
sum += _ar[_p[i]];
|
||||
}
|
||||
return sum / nMedians;
|
||||
}
|
||||
|
||||
float RunningMedian::getElement(const uint8_t n)
|
||||
{
|
||||
if ((_cnt == 0) || (n >= _cnt))
|
||||
return NAN;
|
||||
|
||||
uint8_t pos = _idx + n;
|
||||
if (pos >= _cnt) // faster than %
|
||||
{
|
||||
pos -= _cnt;
|
||||
}
|
||||
return _ar[pos];
|
||||
}
|
||||
|
||||
float RunningMedian::getSortedElement(const uint8_t n)
|
||||
{
|
||||
if ((_cnt == 0) || (n >= _cnt))
|
||||
return NAN;
|
||||
|
||||
if (_sorted == false)
|
||||
sort();
|
||||
return _ar[_p[n]];
|
||||
}
|
||||
|
||||
// n can be max <= half the (filled) size
|
||||
float RunningMedian::predict(const uint8_t n)
|
||||
{
|
||||
if ((_cnt == 0) || (n >= _cnt / 2))
|
||||
return NAN;
|
||||
|
||||
float med = getMedian(); // takes care of sorting !
|
||||
if (_cnt & 0x01)
|
||||
{
|
||||
return max(med - _ar[_p[_cnt / 2 - n]], _ar[_p[_cnt / 2 + n]] - med);
|
||||
}
|
||||
float f1 = (_ar[_p[_cnt / 2 - n]] + _ar[_p[_cnt / 2 - n - 1]]) / 2;
|
||||
float f2 = (_ar[_p[_cnt / 2 + n]] + _ar[_p[_cnt / 2 + n - 1]]) / 2;
|
||||
return max(med - f1, f2 - med) / 2;
|
||||
}
|
||||
|
||||
void RunningMedian::sort()
|
||||
{
|
||||
// bubble sort with flag
|
||||
for (uint8_t i = 0; i < _cnt - 1; i++)
|
||||
{
|
||||
bool flag = true;
|
||||
for (uint8_t j = 1; j < _cnt - i; j++)
|
||||
{
|
||||
if (_ar[_p[j - 1]] > _ar[_p[j]])
|
||||
{
|
||||
uint8_t t = _p[j - 1];
|
||||
_p[j - 1] = _p[j];
|
||||
_p[j] = t;
|
||||
flag = false;
|
||||
}
|
||||
}
|
||||
if (flag)
|
||||
break;
|
||||
}
|
||||
_sorted = true;
|
||||
}
|
||||
|
||||
// -- END OF FILE --
|
18
esp32/src/TimeUtils.cpp
Normal file
18
esp32/src/TimeUtils.cpp
Normal file
@ -0,0 +1,18 @@
|
||||
#include "TimeUtils.h"
|
||||
#include <Homie.h>
|
||||
|
||||
long getCurrentTime()
|
||||
{
|
||||
struct timeval tv_now;
|
||||
gettimeofday(&tv_now, NULL);
|
||||
return tv_now.tv_sec;
|
||||
}
|
||||
|
||||
int getCurrentHour()
|
||||
{
|
||||
struct tm info;
|
||||
time_t now;
|
||||
time(&now);
|
||||
localtime_r(&now, &info);
|
||||
return info.tm_hour;
|
||||
}
|
1244
esp32/src/main.cpp
Normal file
1244
esp32/src/main.cpp
Normal file
File diff suppressed because it is too large
Load Diff
16
esp32/staticCodeAnalysis.cppcheck
Normal file
16
esp32/staticCodeAnalysis.cppcheck
Normal file
@ -0,0 +1,16 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="1">
|
||||
<builddir>cppcheck-build-dir</builddir>
|
||||
<platform>arm32-wchar_t4.xml</platform>
|
||||
<analyze-all-vs-configs>false</analyze-all-vs-configs>
|
||||
<check-headers>true</check-headers>
|
||||
<check-unused-templates>false</check-unused-templates>
|
||||
<max-ctu-depth>10</max-ctu-depth>
|
||||
<paths>
|
||||
<dir name="src"/>
|
||||
<dir name="include"/>
|
||||
</paths>
|
||||
<libraries>
|
||||
<library>cppcheck-lib</library>
|
||||
</libraries>
|
||||
</project>
|
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
|
5
esp32test/Esp32DeepSleepTest/.gitignore
vendored
Normal file
5
esp32test/Esp32DeepSleepTest/.gitignore
vendored
Normal file
@ -0,0 +1,5 @@
|
||||
.pio
|
||||
.vscode/.browse.c_cpp.db*
|
||||
.vscode/c_cpp_properties.json
|
||||
.vscode/launch.json
|
||||
.vscode/ipch
|
10
esp32test/Esp32DeepSleepTest/.vscode/extensions.json
vendored
Normal file
10
esp32test/Esp32DeepSleepTest/.vscode/extensions.json
vendored
Normal file
@ -0,0 +1,10 @@
|
||||
{
|
||||
// See http://go.microsoft.com/fwlink/?LinkId=827846
|
||||
// for the documentation about the extensions.json format
|
||||
"recommendations": [
|
||||
"platformio.platformio-ide"
|
||||
],
|
||||
"unwantedRecommendations": [
|
||||
"ms-vscode.cpptools-extension-pack"
|
||||
]
|
||||
}
|
28
esp32test/Esp32DeepSleepTest/PlantControlTest.code-workspace
Normal file
28
esp32test/Esp32DeepSleepTest/PlantControlTest.code-workspace
Normal file
@ -0,0 +1,28 @@
|
||||
{
|
||||
"folders": [
|
||||
{
|
||||
"path": "."
|
||||
}
|
||||
],
|
||||
"settings": {
|
||||
"files.associations": {
|
||||
"*.tcc": "cpp",
|
||||
"bitset": "cpp",
|
||||
"algorithm": "cpp",
|
||||
"istream": "cpp",
|
||||
"limits": "cpp",
|
||||
"streambuf": "cpp",
|
||||
"functional": "cpp",
|
||||
"string": "cpp",
|
||||
"typeinfo": "cpp",
|
||||
"cmath": "cpp",
|
||||
"array": "cpp",
|
||||
"deque": "cpp",
|
||||
"unordered_map": "cpp",
|
||||
"unordered_set": "cpp",
|
||||
"vector": "cpp",
|
||||
"initializer_list": "cpp",
|
||||
"regex": "cpp"
|
||||
}
|
||||
}
|
||||
}
|
39
esp32test/Esp32DeepSleepTest/include/README
Normal file
39
esp32test/Esp32DeepSleepTest/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
|
187
esp32test/Esp32DeepSleepTest/include/ulp-pwm.h
Normal file
187
esp32test/Esp32DeepSleepTest/include/ulp-pwm.h
Normal file
@ -0,0 +1,187 @@
|
||||
#ifndef ULP_PWM_h
|
||||
#define ILP_PWM_h
|
||||
|
||||
#include <Arduino.h>
|
||||
#include "driver/rtc_io.h"
|
||||
#include "soc/rtc_cntl_reg.h"
|
||||
#include "soc/rtc.h"
|
||||
#include "esp32/ulp.h"
|
||||
|
||||
#define LBL_START 1
|
||||
#define LBL_DELAY_ON 2
|
||||
#define LBL_DELAY_OFF 3
|
||||
#define LBL_SKIP_ON 4
|
||||
#define LBL_SKIP_OFF 5
|
||||
#define REGISTER_DELAY_LOOP_COUNTER R0
|
||||
#define REGISTER_TICKS_ON R1
|
||||
#define REGISTER_TICKS_OFF R2
|
||||
#define TOTAL_TICKS_DELAY 255
|
||||
#define PIN GPIO_NUM_12
|
||||
|
||||
//support 20 vars
|
||||
const size_t ulp_var_offset = CONFIG_ULP_COPROC_RESERVE_MEM - 20;
|
||||
//use the first for dimming
|
||||
const size_t ulp_dimm_offset = ulp_var_offset + 1;
|
||||
const size_t ulp_alive_offset = ulp_var_offset + 2;
|
||||
|
||||
//see https://github.com/perseus086/ESP32-notes
|
||||
const uint32_t rtc_bit[40] = {
|
||||
25, //gpio0
|
||||
0, //gpio1
|
||||
26, //gpio2
|
||||
0, //gpio3
|
||||
24, //gpio4
|
||||
0, //gpio5
|
||||
0, //gpio6
|
||||
0, //gpio7
|
||||
0, //gpio8
|
||||
0, //gpio9
|
||||
0, //gpio10
|
||||
0, //gpio11
|
||||
29, //gpio12
|
||||
28, //gpio13
|
||||
30, //gpio14
|
||||
27, //gpio15
|
||||
0, //gpio16
|
||||
31, //gpio17
|
||||
0, //gpio18
|
||||
0, //gpio19
|
||||
0, //gpio20
|
||||
0, //gpio21
|
||||
0, //gpio22
|
||||
0, //gpio23
|
||||
0, //gpio24
|
||||
20, //gpio25
|
||||
21, //gpio26
|
||||
0, //gpio27
|
||||
0, //gpio28
|
||||
0, //gpio29
|
||||
0, //gpio30
|
||||
0, //gpio31
|
||||
23, //gpio32
|
||||
22, //gpio33
|
||||
18, //gpio34
|
||||
19, //gpio35
|
||||
14, //gpio36
|
||||
15, //gpio37
|
||||
16, //gpio38
|
||||
17 //gpio39
|
||||
};
|
||||
|
||||
static inline void ulp_data_write(size_t offset, uint16_t value)
|
||||
{
|
||||
if (offset >= CONFIG_ULP_COPROC_RESERVE_MEM || offset <= ulp_var_offset)
|
||||
{
|
||||
Serial.print("Invalid ULP offset detected, refusing write!");
|
||||
Serial.print(offset);
|
||||
Serial.print("-");
|
||||
Serial.print(ulp_var_offset);
|
||||
Serial.print("-");
|
||||
Serial.println(CONFIG_ULP_COPROC_RESERVE_MEM);
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
RTC_SLOW_MEM[offset] = value;
|
||||
}
|
||||
}
|
||||
|
||||
static inline uint16_t ulp_data_read(size_t offset)
|
||||
{
|
||||
if (offset >= CONFIG_ULP_COPROC_RESERVE_MEM || offset <= ulp_var_offset)
|
||||
{
|
||||
Serial.print("Invalid ULP offset detected");
|
||||
Serial.print(offset);
|
||||
Serial.print("-");
|
||||
Serial.print(ulp_var_offset);
|
||||
Serial.print("-");
|
||||
Serial.println(CONFIG_ULP_COPROC_RESERVE_MEM);
|
||||
}
|
||||
return RTC_SLOW_MEM[offset] & 0xffff;
|
||||
}
|
||||
|
||||
static inline uint32_t rtc_io_number_get(gpio_num_t gpio_num)
|
||||
{
|
||||
assert(rtc_gpio_is_valid_gpio(gpio_num) && "Invalid GPIO for RTC");
|
||||
uint32_t bit = rtc_bit[gpio_num];
|
||||
Serial.print("Resolved GPIO ");
|
||||
Serial.print(gpio_num);
|
||||
Serial.print(" to rtc bit ");
|
||||
Serial.println(bit);
|
||||
return bit;
|
||||
}
|
||||
|
||||
void ulp_pwm_start(void)
|
||||
{
|
||||
rtc_gpio_init(PIN);
|
||||
rtc_gpio_set_direction(PIN, RTC_GPIO_MODE_OUTPUT_ONLY);
|
||||
rtc_gpio_set_level(PIN, 0);
|
||||
const uint32_t rtc_gpio = rtc_io_number_get(PIN);
|
||||
|
||||
// Define ULP program
|
||||
const ulp_insn_t ulp_prog[] = {
|
||||
M_LABEL(LBL_START),
|
||||
|
||||
I_MOVI(REGISTER_DELAY_LOOP_COUNTER, 1),
|
||||
I_MOVI(REGISTER_TICKS_ON, 0),
|
||||
I_ST(REGISTER_DELAY_LOOP_COUNTER, REGISTER_TICKS_ON, ulp_alive_offset), //store 1 with 0 offset into alive
|
||||
|
||||
I_LD(REGISTER_TICKS_ON, REGISTER_TICKS_ON, ulp_dimm_offset), //REGISTER_TICKS_ON = RTC_DATA[0+ulp_dimm_offset]
|
||||
//in total there is always 255 delay loop iterations, but in different duty cycle
|
||||
I_MOVI(REGISTER_TICKS_OFF, TOTAL_TICKS_DELAY),
|
||||
I_SUBR(REGISTER_TICKS_OFF, REGISTER_TICKS_OFF, REGISTER_TICKS_ON),
|
||||
|
||||
//on phase
|
||||
I_MOVR(REGISTER_DELAY_LOOP_COUNTER, REGISTER_TICKS_ON),
|
||||
M_BL(LBL_SKIP_ON, 1), //if never on, skip on phase
|
||||
I_WR_REG(RTC_GPIO_OUT_REG, rtc_gpio, rtc_gpio, HIGH), // on
|
||||
M_LABEL(LBL_DELAY_ON),
|
||||
I_DELAY(1), //wait 1 clock
|
||||
I_SUBI(REGISTER_DELAY_LOOP_COUNTER, REGISTER_DELAY_LOOP_COUNTER, 1), // REGISTER_DELAY_LOOP_COUNTER--
|
||||
M_BGE(LBL_DELAY_ON, 1), //if time left, goto start of on loop
|
||||
M_LABEL(LBL_SKIP_ON),
|
||||
|
||||
//off phase
|
||||
I_MOVR(REGISTER_DELAY_LOOP_COUNTER, REGISTER_TICKS_OFF),
|
||||
|
||||
M_BL(LBL_SKIP_OFF, 1), //if never off, skip on phase
|
||||
I_WR_REG(RTC_GPIO_OUT_REG, rtc_gpio, rtc_gpio, LOW), // on
|
||||
M_LABEL(3),
|
||||
I_DELAY(1), //wait 1 clock
|
||||
I_SUBI(REGISTER_DELAY_LOOP_COUNTER, REGISTER_DELAY_LOOP_COUNTER, 1), // REGISTER_DELAY_LOOP_COUNTER--
|
||||
M_BGE(3, 1), //if time left, goto start of on loop
|
||||
M_LABEL(LBL_SKIP_OFF),
|
||||
|
||||
M_BX(LBL_START),
|
||||
};
|
||||
// Run ULP program
|
||||
size_t size = sizeof(ulp_prog) / sizeof(ulp_insn_t);
|
||||
assert(size < ulp_var_offset && "ULP_DATA_OFFSET needs to be greater or equal to the program size");
|
||||
esp_err_t error = ulp_process_macros_and_load(0, ulp_prog, &size);
|
||||
Serial.print("ULP bootstrap status ");
|
||||
Serial.println(error);
|
||||
|
||||
//allow glitchless start
|
||||
ulp_data_write(ulp_alive_offset, 0);
|
||||
|
||||
error = ulp_run(0);
|
||||
Serial.print("ULP start status ");
|
||||
Serial.println(error);
|
||||
}
|
||||
|
||||
static inline void ulp_pwm_set_level(uint8_t level)
|
||||
{
|
||||
ulp_data_write(ulp_dimm_offset, level);
|
||||
}
|
||||
|
||||
static inline void ulp_pwm_init()
|
||||
{
|
||||
ulp_data_write(ulp_alive_offset, 0);
|
||||
delay(10);
|
||||
if (ulp_data_read(ulp_alive_offset) == 0)
|
||||
{
|
||||
ulp_pwm_start();
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
46
esp32test/Esp32DeepSleepTest/lib/README
Normal file
46
esp32test/Esp32DeepSleepTest/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
|
16
esp32test/Esp32DeepSleepTest/platformio.ini
Normal file
16
esp32test/Esp32DeepSleepTest/platformio.ini
Normal file
@ -0,0 +1,16 @@
|
||||
; 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
|
||||
lib_deps = pololu/VL53L0X@^1.3.1
|
||||
build_flags = -DPIO_FRAMEWORK_ARDUINO_LWIP2_LOW_MEMORY -DBOOTLOADER_LOG_LEVEL_VERBOSE -DLOG_DEFAULT_LEVEL_VERBOSE -DCORE_DEBUG_LEVEL=5
|
83
esp32test/Esp32DeepSleepTest/src/main.cpp
Normal file
83
esp32test/Esp32DeepSleepTest/src/main.cpp
Normal file
@ -0,0 +1,83 @@
|
||||
#include <Arduino.h>
|
||||
#include "driver/pcnt.h"
|
||||
#include <VL53L0X.h>
|
||||
|
||||
#define SENSOR_TANK_SDA GPIO_NUM_16 /**< GPIO 16 - echo feedback of water sensor */
|
||||
#define SENSOR_TANK_SCL GPIO_NUM_17 /**< GPIO 17 - trigger for water sensor */
|
||||
|
||||
|
||||
#define OUTPUT_SENSOR 14
|
||||
#define SENSOR_PLANT 17
|
||||
|
||||
VL53L0X tankSensor;
|
||||
bool distanceReady = false;
|
||||
|
||||
void initializeTanksensor() {
|
||||
Wire.begin(SENSOR_TANK_SDA, SENSOR_TANK_SCL, 100000UL /* 100kHz */);
|
||||
tankSensor.setBus(&Wire);
|
||||
delay(100);
|
||||
tankSensor.setTimeout(500);
|
||||
long start = millis();
|
||||
while (start + 500 > millis())
|
||||
{
|
||||
if (tankSensor.init())
|
||||
{
|
||||
distanceReady = true;
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
delay(20);
|
||||
}
|
||||
}
|
||||
|
||||
if ((distanceReady) && (!tankSensor.timeoutOccurred()))
|
||||
{
|
||||
Serial.println("Sensor init done");
|
||||
tankSensor.setSignalRateLimit(0.1);
|
||||
// increase laser pulse periods (defaults are 14 and 10 PCLKs)
|
||||
tankSensor.setVcselPulsePeriod(VL53L0X::VcselPeriodPreRange, 18);
|
||||
tankSensor.setVcselPulsePeriod(VL53L0X::VcselPeriodFinalRange, 14);
|
||||
tankSensor.setMeasurementTimingBudget(200000);
|
||||
tankSensor.startContinuous();
|
||||
} else {
|
||||
Serial.println("Sensor init failed");
|
||||
}
|
||||
}
|
||||
|
||||
void setup()
|
||||
{
|
||||
Serial.begin(115200);
|
||||
pinMode(OUTPUT_SENSOR, OUTPUT);
|
||||
|
||||
digitalWrite(OUTPUT_SENSOR, HIGH);
|
||||
Serial.println("Nodemcu ESP32 Start done");
|
||||
|
||||
initializeTanksensor();
|
||||
}
|
||||
|
||||
void loop() {
|
||||
|
||||
delay(500);
|
||||
|
||||
|
||||
if ((distanceReady) && (!tankSensor.timeoutOccurred()))
|
||||
{
|
||||
uint16_t distance = tankSensor.readRangeSingleMillimeters();
|
||||
if (distance > 8000) {
|
||||
Serial.println("Reset due invalid distance: 8 meter");
|
||||
Wire.end();
|
||||
delay(1000);
|
||||
initializeTanksensor();
|
||||
} else {
|
||||
Serial.print("Distance");
|
||||
Serial.println(distance);
|
||||
}
|
||||
} else {
|
||||
Serial.println("Timeout");
|
||||
tankSensor.stopContinuous();
|
||||
Wire.end();
|
||||
delay(100);
|
||||
initializeTanksensor();
|
||||
}
|
||||
}
|
11
esp32test/Esp32DeepSleepTest/test/README
Normal file
11
esp32test/Esp32DeepSleepTest/test/README
Normal file
@ -0,0 +1,11 @@
|
||||
|
||||
This directory is intended for PlatformIO 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 PlatformIO Unit Testing:
|
||||
- https://docs.platformio.org/page/plus/unit-testing.html
|
BIN
hardware/sensor_mod.png
Normal file
BIN
hardware/sensor_mod.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 279 KiB |
BIN
hardware/verguss form sensor v1.stl
Normal file
BIN
hardware/verguss form sensor v1.stl
Normal file
Binary file not shown.
Binary file not shown.
6224
plantctrl-extension/plantctrl-extension.kicad_pcb
Normal file
6224
plantctrl-extension/plantctrl-extension.kicad_pcb
Normal file
File diff suppressed because it is too large
Load Diff
78
plantctrl-extension/plantctrl-extension.kicad_prl
Normal file
78
plantctrl-extension/plantctrl-extension.kicad_prl
Normal file
@ -0,0 +1,78 @@
|
||||
{
|
||||
"board": {
|
||||
"active_layer": 31,
|
||||
"active_layer_preset": "All Layers",
|
||||
"auto_track_width": false,
|
||||
"hidden_netclasses": [],
|
||||
"hidden_nets": [],
|
||||
"high_contrast_mode": 0,
|
||||
"net_color_mode": 1,
|
||||
"opacity": {
|
||||
"images": 0.6,
|
||||
"pads": 1.0,
|
||||
"tracks": 1.0,
|
||||
"vias": 1.0,
|
||||
"zones": 0.6
|
||||
},
|
||||
"ratsnest_display_mode": 0,
|
||||
"selection_filter": {
|
||||
"dimensions": true,
|
||||
"footprints": true,
|
||||
"graphics": true,
|
||||
"keepouts": true,
|
||||
"lockedItems": true,
|
||||
"otherItems": true,
|
||||
"pads": true,
|
||||
"text": true,
|
||||
"tracks": true,
|
||||
"vias": true,
|
||||
"zones": true
|
||||
},
|
||||
"visible_items": [
|
||||
0,
|
||||
1,
|
||||
2,
|
||||
3,
|
||||
4,
|
||||
5,
|
||||
8,
|
||||
9,
|
||||
10,
|
||||
11,
|
||||
12,
|
||||
13,
|
||||
15,
|
||||
16,
|
||||
17,
|
||||
18,
|
||||
19,
|
||||
20,
|
||||
21,
|
||||
22,
|
||||
23,
|
||||
24,
|
||||
25,
|
||||
26,
|
||||
27,
|
||||
28,
|
||||
29,
|
||||
30,
|
||||
32,
|
||||
33,
|
||||
34,
|
||||
35,
|
||||
36,
|
||||
39,
|
||||
40
|
||||
],
|
||||
"visible_layers": "fffffff_ffffffff",
|
||||
"zone_display_mode": 0
|
||||
},
|
||||
"meta": {
|
||||
"filename": "plantctrl-extension.kicad_prl",
|
||||
"version": 3
|
||||
},
|
||||
"project": {
|
||||
"files": []
|
||||
}
|
||||
}
|
495
plantctrl-extension/plantctrl-extension.kicad_pro
Normal file
495
plantctrl-extension/plantctrl-extension.kicad_pro
Normal file
@ -0,0 +1,495 @@
|
||||
{
|
||||
"board": {
|
||||
"3dviewports": [],
|
||||
"design_settings": {
|
||||
"defaults": {
|
||||
"board_outline_line_width": 0.09999999999999999,
|
||||
"copper_line_width": 0.19999999999999998,
|
||||
"copper_text_italic": false,
|
||||
"copper_text_size_h": 1.5,
|
||||
"copper_text_size_v": 1.5,
|
||||
"copper_text_thickness": 0.3,
|
||||
"copper_text_upright": false,
|
||||
"courtyard_line_width": 0.049999999999999996,
|
||||
"dimension_precision": 4,
|
||||
"dimension_units": 3,
|
||||
"dimensions": {
|
||||
"arrow_length": 1270000,
|
||||
"extension_offset": 500000,
|
||||
"keep_text_aligned": true,
|
||||
"suppress_zeroes": false,
|
||||
"text_position": 0,
|
||||
"units_format": 1
|
||||
},
|
||||
"fab_line_width": 0.09999999999999999,
|
||||
"fab_text_italic": false,
|
||||
"fab_text_size_h": 1.0,
|
||||
"fab_text_size_v": 1.0,
|
||||
"fab_text_thickness": 0.15,
|
||||
"fab_text_upright": false,
|
||||
"other_line_width": 0.15,
|
||||
"other_text_italic": false,
|
||||
"other_text_size_h": 1.0,
|
||||
"other_text_size_v": 1.0,
|
||||
"other_text_thickness": 0.15,
|
||||
"other_text_upright": false,
|
||||
"pads": {
|
||||
"drill": 0.762,
|
||||
"height": 1.524,
|
||||
"width": 1.524
|
||||
},
|
||||
"silk_line_width": 0.15,
|
||||
"silk_text_italic": false,
|
||||
"silk_text_size_h": 1.0,
|
||||
"silk_text_size_v": 1.0,
|
||||
"silk_text_thickness": 0.15,
|
||||
"silk_text_upright": false,
|
||||
"zones": {
|
||||
"45_degree_only": false,
|
||||
"min_clearance": 0.508
|
||||
}
|
||||
},
|
||||
"diff_pair_dimensions": [
|
||||
{
|
||||
"gap": 0.0,
|
||||
"via_gap": 0.0,
|
||||
"width": 0.0
|
||||
}
|
||||
],
|
||||
"drc_exclusions": [],
|
||||
"meta": {
|
||||
"version": 2
|
||||
},
|
||||
"rule_severities": {
|
||||
"annular_width": "error",
|
||||
"clearance": "error",
|
||||
"connection_width": "warning",
|
||||
"copper_edge_clearance": "error",
|
||||
"copper_sliver": "warning",
|
||||
"courtyards_overlap": "error",
|
||||
"diff_pair_gap_out_of_range": "error",
|
||||
"diff_pair_uncoupled_length_too_long": "error",
|
||||
"drill_out_of_range": "error",
|
||||
"duplicate_footprints": "warning",
|
||||
"extra_footprint": "warning",
|
||||
"footprint": "error",
|
||||
"footprint_type_mismatch": "error",
|
||||
"hole_clearance": "error",
|
||||
"hole_near_hole": "error",
|
||||
"invalid_outline": "error",
|
||||
"isolated_copper": "warning",
|
||||
"item_on_disabled_layer": "error",
|
||||
"items_not_allowed": "error",
|
||||
"length_out_of_range": "error",
|
||||
"lib_footprint_issues": "warning",
|
||||
"lib_footprint_mismatch": "warning",
|
||||
"malformed_courtyard": "error",
|
||||
"microvia_drill_out_of_range": "error",
|
||||
"missing_courtyard": "ignore",
|
||||
"missing_footprint": "warning",
|
||||
"net_conflict": "warning",
|
||||
"npth_inside_courtyard": "ignore",
|
||||
"padstack": "error",
|
||||
"pth_inside_courtyard": "ignore",
|
||||
"shorting_items": "error",
|
||||
"silk_edge_clearance": "warning",
|
||||
"silk_over_copper": "warning",
|
||||
"silk_overlap": "warning",
|
||||
"skew_out_of_range": "error",
|
||||
"solder_mask_bridge": "error",
|
||||
"starved_thermal": "error",
|
||||
"text_height": "warning",
|
||||
"text_thickness": "warning",
|
||||
"through_hole_pad_without_hole": "error",
|
||||
"too_many_vias": "error",
|
||||
"track_dangling": "warning",
|
||||
"track_width": "error",
|
||||
"tracks_crossing": "error",
|
||||
"unconnected_items": "error",
|
||||
"unresolved_variable": "error",
|
||||
"via_dangling": "warning",
|
||||
"zones_intersect": "error"
|
||||
},
|
||||
"rules": {
|
||||
"allow_blind_buried_vias": false,
|
||||
"allow_microvias": false,
|
||||
"max_error": 0.005,
|
||||
"min_clearance": 0.0,
|
||||
"min_connection": 0.0,
|
||||
"min_copper_edge_clearance": 0.0,
|
||||
"min_hole_clearance": 0.25,
|
||||
"min_hole_to_hole": 0.25,
|
||||
"min_microvia_diameter": 0.19999999999999998,
|
||||
"min_microvia_drill": 0.09999999999999999,
|
||||
"min_resolved_spokes": 2,
|
||||
"min_silk_clearance": 0.0,
|
||||
"min_text_height": 0.7999999999999999,
|
||||
"min_text_thickness": 0.08,
|
||||
"min_through_hole_diameter": 0.3,
|
||||
"min_track_width": 0.19999999999999998,
|
||||
"min_via_annular_width": 0.049999999999999996,
|
||||
"min_via_diameter": 0.39999999999999997,
|
||||
"solder_mask_clearance": 0.0,
|
||||
"solder_mask_min_width": 0.0,
|
||||
"solder_mask_to_copper_clearance": 0.0,
|
||||
"use_height_for_length_calcs": true
|
||||
},
|
||||
"teardrop_options": [
|
||||
{
|
||||
"td_allow_use_two_tracks": true,
|
||||
"td_curve_segcount": 5,
|
||||
"td_on_pad_in_zone": false,
|
||||
"td_onpadsmd": true,
|
||||
"td_onroundshapesonly": false,
|
||||
"td_ontrackend": false,
|
||||
"td_onviapad": true
|
||||
}
|
||||
],
|
||||
"teardrop_parameters": [
|
||||
{
|
||||
"td_curve_segcount": 0,
|
||||
"td_height_ratio": 1.0,
|
||||
"td_length_ratio": 0.5,
|
||||
"td_maxheight": 2.0,
|
||||
"td_maxlen": 1.0,
|
||||
"td_target_name": "td_round_shape",
|
||||
"td_width_to_size_filter_ratio": 0.9
|
||||
},
|
||||
{
|
||||
"td_curve_segcount": 0,
|
||||
"td_height_ratio": 1.0,
|
||||
"td_length_ratio": 0.5,
|
||||
"td_maxheight": 2.0,
|
||||
"td_maxlen": 1.0,
|
||||
"td_target_name": "td_rect_shape",
|
||||
"td_width_to_size_filter_ratio": 0.9
|
||||
},
|
||||
{
|
||||
"td_curve_segcount": 0,
|
||||
"td_height_ratio": 1.0,
|
||||
"td_length_ratio": 0.5,
|
||||
"td_maxheight": 2.0,
|
||||
"td_maxlen": 1.0,
|
||||
"td_target_name": "td_track_end",
|
||||
"td_width_to_size_filter_ratio": 0.9
|
||||
}
|
||||
],
|
||||
"track_widths": [
|
||||
0.0,
|
||||
0.15,
|
||||
0.5,
|
||||
1.0
|
||||
],
|
||||
"via_dimensions": [
|
||||
{
|
||||
"diameter": 0.0,
|
||||
"drill": 0.0
|
||||
}
|
||||
],
|
||||
"zones_allow_external_fillets": false,
|
||||
"zones_use_no_outline": true
|
||||
},
|
||||
"layer_presets": [],
|
||||
"viewports": []
|
||||
},
|
||||
"boards": [],
|
||||
"cvpcb": {
|
||||
"equivalence_files": []
|
||||
},
|
||||
"erc": {
|
||||
"erc_exclusions": [],
|
||||
"meta": {
|
||||
"version": 0
|
||||
},
|
||||
"pin_map": [
|
||||
[
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
1,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
2
|
||||
],
|
||||
[
|
||||
0,
|
||||
2,
|
||||
0,
|
||||
1,
|
||||
0,
|
||||
0,
|
||||
1,
|
||||
0,
|
||||
2,
|
||||
2,
|
||||
2,
|
||||
2
|
||||
],
|
||||
[
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
1,
|
||||
0,
|
||||
1,
|
||||
0,
|
||||
1,
|
||||
2
|
||||
],
|
||||
[
|
||||
0,
|
||||
1,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
1,
|
||||
1,
|
||||
2,
|
||||
1,
|
||||
1,
|
||||
2
|
||||
],
|
||||
[
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
1,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
2
|
||||
],
|
||||
[
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
2
|
||||
],
|
||||
[
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
0,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
2
|
||||
],
|
||||
[
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
1,
|
||||
0,
|
||||
0,
|
||||
1,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
2
|
||||
],
|
||||
[
|
||||
0,
|
||||
2,
|
||||
1,
|
||||
2,
|
||||
0,
|
||||
0,
|
||||
1,
|
||||
0,
|
||||
2,
|
||||
2,
|
||||
2,
|
||||
2
|
||||
],
|
||||
[
|
||||
0,
|
||||
2,
|
||||
0,
|
||||
1,
|
||||
0,
|
||||
0,
|
||||
1,
|
||||
0,
|
||||
2,
|
||||
0,
|
||||
0,
|
||||
2
|
||||
],
|
||||
[
|
||||
0,
|
||||
2,
|
||||
1,
|
||||
1,
|
||||
0,
|
||||
0,
|
||||
1,
|
||||
0,
|
||||
2,
|
||||
0,
|
||||
0,
|
||||
2
|
||||
],
|
||||
[
|
||||
2,
|
||||
2,
|
||||
2,
|
||||
2,
|
||||
2,
|
||||
2,
|
||||
2,
|
||||
2,
|
||||
2,
|
||||
2,
|
||||
2,
|
||||
2
|
||||
]
|
||||
],
|
||||
"rule_severities": {
|
||||
"bus_definition_conflict": "error",
|
||||
"bus_entry_needed": "error",
|
||||
"bus_label_syntax": "error",
|
||||
"bus_to_bus_conflict": "error",
|
||||
"bus_to_net_conflict": "error",
|
||||
"different_unit_footprint": "error",
|
||||
"different_unit_net": "error",
|
||||
"duplicate_reference": "error",
|
||||
"duplicate_sheet_names": "error",
|
||||
"extra_units": "error",
|
||||
"global_label_dangling": "warning",
|
||||
"hier_label_mismatch": "error",
|
||||
"label_dangling": "error",
|
||||
"lib_symbol_issues": "warning",
|
||||
"multiple_net_names": "warning",
|
||||
"net_not_bus_member": "warning",
|
||||
"no_connect_connected": "warning",
|
||||
"no_connect_dangling": "warning",
|
||||
"pin_not_connected": "error",
|
||||
"pin_not_driven": "error",
|
||||
"pin_to_pin": "warning",
|
||||
"power_pin_not_driven": "error",
|
||||
"similar_labels": "warning",
|
||||
"unannotated": "error",
|
||||
"unit_value_mismatch": "error",
|
||||
"unresolved_variable": "error",
|
||||
"wire_dangling": "error"
|
||||
}
|
||||
},
|
||||
"libraries": {
|
||||
"pinned_footprint_libs": [],
|
||||
"pinned_symbol_libs": []
|
||||
},
|
||||
"meta": {
|
||||
"filename": "plantctrl-extension.kicad_pro",
|
||||
"version": 1
|
||||
},
|
||||
"net_settings": {
|
||||
"classes": [
|
||||
{
|
||||
"bus_width": 12,
|
||||
"clearance": 0.2,
|
||||
"diff_pair_gap": 0.25,
|
||||
"diff_pair_via_gap": 0.25,
|
||||
"diff_pair_width": 0.2,
|
||||
"line_style": 0,
|
||||
"microvia_diameter": 0.3,
|
||||
"microvia_drill": 0.1,
|
||||
"name": "Default",
|
||||
"pcb_color": "rgba(0, 0, 0, 0.000)",
|
||||
"schematic_color": "rgba(0, 0, 0, 0.000)",
|
||||
"track_width": 0.25,
|
||||
"via_diameter": 0.8,
|
||||
"via_drill": 0.4,
|
||||
"wire_width": 6
|
||||
}
|
||||
],
|
||||
"meta": {
|
||||
"version": 3
|
||||
},
|
||||
"net_colors": null,
|
||||
"netclass_assignments": null,
|
||||
"netclass_patterns": []
|
||||
},
|
||||
"pcbnew": {
|
||||
"last_paths": {
|
||||
"gencad": "",
|
||||
"idf": "",
|
||||
"netlist": "",
|
||||
"specctra_dsn": "",
|
||||
"step": "",
|
||||
"vrml": ""
|
||||
},
|
||||
"page_layout_descr_file": ""
|
||||
},
|
||||
"schematic": {
|
||||
"annotate_start_num": 0,
|
||||
"drawing": {
|
||||
"default_line_thickness": 6.0,
|
||||
"default_text_size": 50.0,
|
||||
"field_names": [],
|
||||
"intersheets_ref_own_page": false,
|
||||
"intersheets_ref_prefix": "",
|
||||
"intersheets_ref_short": false,
|
||||
"intersheets_ref_show": false,
|
||||
"intersheets_ref_suffix": "",
|
||||
"junction_size_choice": 3,
|
||||
"label_size_ratio": 0.375,
|
||||
"pin_symbol_size": 25.0,
|
||||
"text_offset_ratio": 0.15
|
||||
},
|
||||
"legacy_lib_dir": "",
|
||||
"legacy_lib_list": [],
|
||||
"meta": {
|
||||
"version": 1
|
||||
},
|
||||
"net_format_name": "",
|
||||
"ngspice": {
|
||||
"fix_include_paths": true,
|
||||
"fix_passive_vals": false,
|
||||
"meta": {
|
||||
"version": 0
|
||||
},
|
||||
"model_mode": 0,
|
||||
"workbook_filename": ""
|
||||
},
|
||||
"page_layout_descr_file": "",
|
||||
"plot_directory": "",
|
||||
"spice_adjust_passive_values": false,
|
||||
"spice_external_command": "spice \"%I\"",
|
||||
"subpart_first_id": 65,
|
||||
"subpart_id_separator": 0
|
||||
},
|
||||
"sheets": [
|
||||
[
|
||||
"e63e39d7-6ac0-4ffd-8aa3-1841a4541b55",
|
||||
""
|
||||
]
|
||||
],
|
||||
"text_variables": {}
|
||||
}
|
1249
plantctrl-extension/plantctrl-extension.kicad_sch
Normal file
1249
plantctrl-extension/plantctrl-extension.kicad_sch
Normal file
File diff suppressed because it is too large
Load Diff
@ -1,23 +1,20 @@
|
||||
[build]
|
||||
#target = "xtensa-esp32-espidf"
|
||||
target = "riscv32imac-esp-espidf"
|
||||
target = "xtensa-esp32-espidf"
|
||||
|
||||
[target.riscv32imac-esp-espidf]
|
||||
[target.xtensa-esp32-espidf]
|
||||
linker = "ldproxy"
|
||||
# runner = "espflash flash --monitor --partition-table partitions.csv -b no-reset" # Select this runner for espflash v2.x.x
|
||||
# runner = "espflash flash --monitor --baud 921600 --partition-table partitions.csv -b no-reset" # Select this runner for espflash v2.x.x
|
||||
#runner = "espflash flash --monitor --partition-table partitions.csv" # Select this runner for espflash v2.x.x
|
||||
# runner = espflash erase-parts otadata
|
||||
|
||||
runner = "cargo runner"
|
||||
runner = "espflash flash --monitor --baud 921600 --partition-table partitions.csv" # Select this runner for espflash v2.x.x
|
||||
#runner = "cargo runner"
|
||||
rustflags = [ "--cfg", "espidf_time64"] # Extending time_t for ESP IDF 5: https://github.com/esp-rs/rust/issues/110
|
||||
|
||||
[unstable]
|
||||
build-std = ["std", "panic_abort"]
|
||||
|
||||
[env]
|
||||
MCU="esp32c6"
|
||||
MCU="esp32"
|
||||
# Note: this variable is not used by the pio builder (`cargo build --features pio`)
|
||||
ESP_IDF_VERSION = "v5.2.1"
|
||||
ESP_IDF_VERSION = "v5.1.1"
|
||||
CHRONO_TZ_TIMEZONE_FILTER="UTC|Europe/Berlin"
|
||||
CARGO_WORKSPACE_DIR = { value = "", relative = true }
|
||||
RUST_BACKTRACE = "full"
|
||||
|
@ -6,18 +6,26 @@ edition = "2021"
|
||||
resolver = "2"
|
||||
rust-version = "1.71"
|
||||
|
||||
[profile.dev]
|
||||
[profile.release]
|
||||
# Explicitly disable LTO which the Xtensa codegen backend has issues
|
||||
lto = false
|
||||
#lto = "thin"
|
||||
opt-level = "s"
|
||||
strip = false
|
||||
#codegen-units = 1
|
||||
debug = true
|
||||
overflow-checks = true
|
||||
panic = "abort"
|
||||
panic = "unwind"
|
||||
incremental = true
|
||||
opt-level = "s"
|
||||
|
||||
[profile.dev.build-override]
|
||||
opt-level = 1
|
||||
[profile.dev]
|
||||
# Explicitly disable LTO which the Xtensa codegen backend has issues
|
||||
#lto = "thin"
|
||||
opt-level = "s"
|
||||
strip = false
|
||||
#codegen-units = 1
|
||||
debug = true
|
||||
overflow-checks = true
|
||||
panic = "unwind"
|
||||
incremental = true
|
||||
|
||||
[package.metadata.cargo_runner]
|
||||
@ -27,7 +35,7 @@ command = [
|
||||
"espflash",
|
||||
"save-image",
|
||||
"--chip",
|
||||
"esp32c6",
|
||||
"esp32",
|
||||
"image.bin"
|
||||
]
|
||||
|
||||
@ -47,6 +55,7 @@ embassy = ["esp-idf-svc/embassy-sync", "esp-idf-svc/critical-section", "esp-idf-
|
||||
|
||||
[dependencies]
|
||||
log = { version = "0.4", default-features = false }
|
||||
esp-idf-svc = { version = "0.48.0", default-features = false }
|
||||
serde = { version = "1.0.192", features = ["derive"] }
|
||||
average = { version = "0.14.1" , features = ["std"] }
|
||||
#esp32 = "0.28.0"
|
||||
@ -55,7 +64,6 @@ ds18b20 = "0.1.1"
|
||||
embedded-svc = { version = "0.27.0", features = ["experimental"] }
|
||||
esp-idf-hal = "0.43.0"
|
||||
esp-idf-sys = { version = "0.34.0", features = ["binstart", "native"] }
|
||||
esp-idf-svc = { version = "0.48.0", default-features = false }
|
||||
esp_idf_build = "0.1.3"
|
||||
chrono = { version = "0.4.23", default-features = false , features = ["iana-time-zone" , "alloc"] }
|
||||
chrono-tz = {version="0.8.0", default-features = false , features = [ "filter-by-regex" ]}
|
||||
@ -70,13 +78,6 @@ once_cell = "1.19.0"
|
||||
measurements = "0.11.0"
|
||||
bq34z100 = "0.2.1"
|
||||
|
||||
[patch.crates-io]
|
||||
#esp-idf-hal = { git = "https://github.com/esp-rs/esp-idf-hal.git" }
|
||||
esp-idf-hal = { git = "https://github.com/empirephoenix/esp-idf-hal.git" }
|
||||
#esp-idf-sys = { git = "https://github.com/empirephoenix/esp-idf-sys.git" }
|
||||
#esp-idf-sys = { git = "https://github.com/esp-rs/esp-idf-sys.git" }
|
||||
#esp-idf-svc = { git = "https://github.com/esp-rs/esp-idf-svc.git" }
|
||||
|
||||
[build-dependencies]
|
||||
embuild = "0.31.3"
|
||||
vergen = { version = "8.2.6", features = ["build", "git", "gitcl"] }
|
||||
|
Binary file not shown.
@ -1,10 +1,8 @@
|
||||
# Rust often needs a bit of an extra main task stack size compared to C (the default is 3K)
|
||||
CONFIG_ESP_MAIN_TASK_STACK_SIZE=50000
|
||||
CONFIG_ESP_MAIN_TASK_STACK_SIZE=25000
|
||||
|
||||
# Use this to set FreeRTOS kernel tick frequency to 1000 Hz (100 Hz by default).
|
||||
# This allows to use 1 ms granuality for thread sleeps (10 ms by default).
|
||||
CONFIG_FREERTOS_HZ=1000
|
||||
|
||||
CONFIG_BOOTLOADER_APP_ROLLBACK_ENABLE=y
|
||||
CONFIG_I2C_ENABLE_DEBUG_LOG=y
|
||||
DEBUG_LEVEL=5
|
807
rust/src/main.rs
807
rust/src/main.rs
File diff suppressed because it is too large
Load Diff
@ -4,10 +4,7 @@ use chrono_tz::Europe::Berlin;
|
||||
use embedded_svc::wifi::{
|
||||
AccessPointConfiguration, AccessPointInfo, AuthMethod, ClientConfiguration, Configuration,
|
||||
};
|
||||
use esp_idf_hal::adc::attenuation;
|
||||
use esp_idf_hal::adc::oneshot::config::AdcChannelConfig;
|
||||
use esp_idf_hal::adc::oneshot::{AdcChannelDriver, AdcDriver};
|
||||
use esp_idf_hal::i2c::{APBTickType, I2cConfig, I2cDriver, I2cError};
|
||||
use esp_idf_hal::i2c::{I2cConfig, I2cDriver, I2cError};
|
||||
use esp_idf_hal::units::FromValueType;
|
||||
use esp_idf_svc::eventloop::EspSystemEventLoop;
|
||||
use esp_idf_svc::ipv4::IpInfo;
|
||||
@ -35,8 +32,9 @@ use std::sync::{Arc, Mutex};
|
||||
use std::time::Duration;
|
||||
|
||||
use embedded_hal::digital::OutputPin;
|
||||
use esp_idf_hal::adc::{attenuation, AdcChannelDriver, AdcDriver};
|
||||
use esp_idf_hal::delay::Delay;
|
||||
use esp_idf_hal::gpio::{AnyInputPin, Gpio18, Gpio5, IOPin, InputOutput, Level, PinDriver, Pull};
|
||||
use esp_idf_hal::gpio::{AnyInputPin, Gpio39, Gpio4, InputOutput, Level, PinDriver, Pull};
|
||||
use esp_idf_hal::pcnt::{
|
||||
PcntChannel, PcntChannelConfig, PcntControlMode, PcntCountMode, PcntDriver, PinIndex,
|
||||
};
|
||||
@ -84,7 +82,8 @@ pub enum ClearConfigType {
|
||||
None,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
#[derive(Debug)]
|
||||
#[derive(PartialEq)]
|
||||
pub enum Sensor {
|
||||
A,
|
||||
B,
|
||||
@ -127,7 +126,7 @@ pub trait PlantCtrlBoardInteraction {
|
||||
|
||||
fn measure_moisture_hz(&self, plant: usize, sensor: Sensor) -> Result<i32>;
|
||||
fn pump(&self, plant: usize, enable: bool) -> Result<()>;
|
||||
fn last_pump_time(&self, plant: usize) -> Option<chrono::DateTime<Utc>>;
|
||||
fn last_pump_time(&self, plant: usize) -> chrono::DateTime<Utc>;
|
||||
fn store_last_pump_time(&mut self, plant: usize, time: chrono::DateTime<Utc>);
|
||||
fn store_consecutive_pump_count(&mut self, plant: usize, count: u32);
|
||||
fn consecutive_pump_count(&mut self, plant: usize) -> u32;
|
||||
@ -159,22 +158,23 @@ pub struct PlantHal {}
|
||||
|
||||
pub struct PlantCtrlBoard<'a> {
|
||||
shift_register: ShiftRegister40<
|
||||
PinDriver<'a, esp_idf_hal::gpio::AnyIOPin, InputOutput>,
|
||||
PinDriver<'a, esp_idf_hal::gpio::AnyIOPin, InputOutput>,
|
||||
PinDriver<'a, esp_idf_hal::gpio::AnyIOPin, InputOutput>,
|
||||
PinDriver<'a, esp_idf_hal::gpio::Gpio21, InputOutput>,
|
||||
PinDriver<'a, esp_idf_hal::gpio::Gpio22, InputOutput>,
|
||||
PinDriver<'a, esp_idf_hal::gpio::Gpio19, InputOutput>,
|
||||
>,
|
||||
tank_channel: AdcChannelDriver<'a, Gpio5, AdcDriver<'a, esp_idf_hal::adc::ADC1>>,
|
||||
solar_is_day: PinDriver<'a, esp_idf_hal::gpio::AnyIOPin, esp_idf_hal::gpio::Input>,
|
||||
boot_button: PinDriver<'a, esp_idf_hal::gpio::AnyIOPin, esp_idf_hal::gpio::Input>,
|
||||
tank_driver: AdcDriver<'a, esp_idf_hal::adc::ADC1>,
|
||||
tank_channel: esp_idf_hal::adc::AdcChannelDriver<'a, { attenuation::DB_11 }, Gpio39>,
|
||||
solar_is_day: PinDriver<'a, esp_idf_hal::gpio::Gpio25, esp_idf_hal::gpio::Input>,
|
||||
boot_button: PinDriver<'a, esp_idf_hal::gpio::Gpio0, esp_idf_hal::gpio::Input>,
|
||||
signal_counter: PcntDriver<'a>,
|
||||
light: PinDriver<'a, esp_idf_hal::gpio::AnyIOPin, InputOutput>,
|
||||
main_pump: PinDriver<'a, esp_idf_hal::gpio::AnyIOPin, InputOutput>,
|
||||
tank_power: PinDriver<'a, esp_idf_hal::gpio::AnyIOPin, InputOutput>,
|
||||
general_fault: PinDriver<'a, esp_idf_hal::gpio::AnyIOPin, InputOutput>,
|
||||
light: PinDriver<'a, esp_idf_hal::gpio::Gpio26, InputOutput>,
|
||||
main_pump: PinDriver<'a, esp_idf_hal::gpio::Gpio23, InputOutput>,
|
||||
tank_power: PinDriver<'a, esp_idf_hal::gpio::Gpio27, InputOutput>,
|
||||
general_fault: PinDriver<'a, esp_idf_hal::gpio::Gpio13, InputOutput>,
|
||||
pub wifi_driver: EspWifi<'a>,
|
||||
one_wire_bus: OneWire<PinDriver<'a, Gpio18, esp_idf_hal::gpio::InputOutput>>,
|
||||
one_wire_bus: OneWire<PinDriver<'a, Gpio4, esp_idf_hal::gpio::InputOutput>>,
|
||||
mqtt_client: Option<EspMqttClient<'a>>,
|
||||
battery_driver: Option<Bq34z100g1Driver<I2cDriver<'a>, Delay>>,
|
||||
battery_driver: Bq34z100g1Driver<I2cDriver<'a>, Delay>,
|
||||
}
|
||||
|
||||
impl PlantCtrlBoardInteraction for PlantCtrlBoard<'_> {
|
||||
@ -223,7 +223,7 @@ impl PlantCtrlBoardInteraction for PlantCtrlBoard<'_> {
|
||||
|
||||
let mut store = [0_u16; TANK_MULTI_SAMPLE];
|
||||
for multisample in 0..TANK_MULTI_SAMPLE {
|
||||
let value = self.tank_channel.read()?;
|
||||
let value = self.tank_driver.read(&mut self.tank_channel)?;
|
||||
store[multisample] = value;
|
||||
}
|
||||
store.sort();
|
||||
@ -242,7 +242,10 @@ impl PlantCtrlBoardInteraction for PlantCtrlBoard<'_> {
|
||||
let mut percent = r2 / 190_f32 * 100_f32;
|
||||
percent = percent.clamp(0.0, 100.0);
|
||||
|
||||
println!("Tank sensor raw {} percent {}", median, percent);
|
||||
println!(
|
||||
"Tank sensor raw {} percent {}",
|
||||
median, percent
|
||||
);
|
||||
return Ok(percent as u16);
|
||||
}
|
||||
|
||||
@ -268,13 +271,15 @@ impl PlantCtrlBoardInteraction for PlantCtrlBoard<'_> {
|
||||
fn pump(&self, plant: usize, enable: bool) -> Result<()> {
|
||||
let index = plant * PINS_PER_PLANT + PLANT_PUMP_OFFSET;
|
||||
//currently infailable error, keep for future as result anyway
|
||||
self.shift_register.decompose()[index].set_state(enable.into())?;
|
||||
self.shift_register.decompose()[index]
|
||||
.set_state(enable.into())
|
||||
.unwrap();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn last_pump_time(&self, plant: usize) -> Option<chrono::DateTime<Utc>> {
|
||||
fn last_pump_time(&self, plant: usize) -> chrono::DateTime<Utc> {
|
||||
let ts = unsafe { LAST_WATERING_TIMESTAMP }[plant];
|
||||
return Some(DateTime::from_timestamp_millis(ts)?);
|
||||
return DateTime::from_timestamp_millis(ts).unwrap();
|
||||
}
|
||||
|
||||
fn store_last_pump_time(&mut self, plant: usize, time: chrono::DateTime<Utc>) {
|
||||
@ -354,12 +359,20 @@ impl PlantCtrlBoardInteraction for PlantCtrlBoard<'_> {
|
||||
let factor = 1000 as f32 / measurement as f32;
|
||||
|
||||
self.shift_register.decompose()[index].set_high().unwrap();
|
||||
if plant == 0 && sensor == Sensor::A {
|
||||
let index = plant * PINS_PER_PLANT + PLANT_MOIST_PUMP_OFFSET;
|
||||
//self.shift_register.decompose()[index].set_high().unwrap();
|
||||
}
|
||||
//give some time to stabilize
|
||||
delay.delay_ms(10);
|
||||
self.signal_counter.counter_resume()?;
|
||||
delay.delay_ms(measurement);
|
||||
self.signal_counter.counter_pause()?;
|
||||
self.shift_register.decompose()[index].set_low().unwrap();
|
||||
if plant == 0 && sensor == Sensor::A {
|
||||
let index = plant * PINS_PER_PLANT + PLANT_MOIST_PUMP_OFFSET;
|
||||
//self.shift_register.decompose()[index].set_low().unwrap();
|
||||
}
|
||||
let unscaled = self.signal_counter.get_counter_value()? as i32;
|
||||
let hz = (unscaled as f32 * factor) as i32;
|
||||
println!("Measuring {:?} @ {} with {}", sensor, plant, hz);
|
||||
@ -367,9 +380,7 @@ impl PlantCtrlBoardInteraction for PlantCtrlBoard<'_> {
|
||||
}
|
||||
|
||||
fn general_fault(&mut self, enable: bool) {
|
||||
unsafe { gpio_hold_dis(self.general_fault.pin()) };
|
||||
self.general_fault.set_state(enable.into()).unwrap();
|
||||
unsafe { gpio_hold_en(self.general_fault.pin()) };
|
||||
}
|
||||
|
||||
fn wifi_ap(&mut self) -> Result<()> {
|
||||
@ -404,13 +415,13 @@ impl PlantCtrlBoardInteraction for PlantCtrlBoard<'_> {
|
||||
))?;
|
||||
}
|
||||
None => {
|
||||
self.wifi_driver.set_configuration(&Configuration::Client(
|
||||
ClientConfiguration {
|
||||
self.wifi_driver
|
||||
.set_configuration(&Configuration::Client(ClientConfiguration {
|
||||
ssid: ssid,
|
||||
auth_method: AuthMethod::None,
|
||||
..Default::default()
|
||||
},
|
||||
))?;
|
||||
}))
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
@ -432,7 +443,7 @@ impl PlantCtrlBoardInteraction for PlantCtrlBoard<'_> {
|
||||
}
|
||||
println!("Should be connected now");
|
||||
|
||||
while !self.wifi_driver.is_up()? {
|
||||
while !self.wifi_driver.is_up().unwrap() {
|
||||
println!("Waiting for network being up");
|
||||
delay.delay_ms(250);
|
||||
counter += 250;
|
||||
@ -444,7 +455,7 @@ impl PlantCtrlBoardInteraction for PlantCtrlBoard<'_> {
|
||||
}
|
||||
}
|
||||
//update freertos registers ;)
|
||||
let address = self.wifi_driver.sta_netif().get_ip_info()?;
|
||||
let address = self.wifi_driver.sta_netif().get_ip_info().unwrap();
|
||||
println!("IP info: {:?}", address);
|
||||
Ok(address)
|
||||
}
|
||||
@ -619,6 +630,7 @@ impl PlantCtrlBoardInteraction for PlantCtrlBoard<'_> {
|
||||
|
||||
fn mqtt(&mut self, config: &Config) -> Result<()> {
|
||||
let last_will_topic = format!("{}/state", config.base_topic);
|
||||
|
||||
let mqtt_client_config = MqttClientConfiguration {
|
||||
lwt: Some(LwtConfiguration {
|
||||
topic: &last_will_topic,
|
||||
@ -632,25 +644,15 @@ impl PlantCtrlBoardInteraction for PlantCtrlBoard<'_> {
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let mqtt_connected_event_received = Arc::new(AtomicBool::new(false));
|
||||
let mqtt_connected_event_ok = Arc::new(AtomicBool::new(false));
|
||||
|
||||
let round_trip_ok = Arc::new(AtomicBool::new(false));
|
||||
let round_trip_topic = format!("{}/internal/roundtrip", config.base_topic);
|
||||
let stay_alive_topic = format!("{}/stay_alive", config.base_topic);
|
||||
println!("Round trip topic is {}", round_trip_topic);
|
||||
println!("Stay alive topic is {}", stay_alive_topic);
|
||||
|
||||
let mqtt_connected_event_received_copy = mqtt_connected_event_received.clone();
|
||||
let mqtt_connected_event_ok_copy = mqtt_connected_event_ok.clone();
|
||||
let stay_alive_topic_copy = stay_alive_topic.clone();
|
||||
let round_trip_topic_copy = round_trip_topic.clone();
|
||||
let round_trip_ok_copy = round_trip_ok.clone();
|
||||
println!(
|
||||
"Connecting mqtt {} with id {}",
|
||||
config.mqtt_url,
|
||||
mqtt_client_config.client_id.unwrap_or("not set")
|
||||
);
|
||||
let mut client =
|
||||
EspMqttClient::new_cb(&config.mqtt_url, &mqtt_client_config, move |event| {
|
||||
let payload = event.payload();
|
||||
@ -677,41 +679,11 @@ impl PlantCtrlBoardInteraction for PlantCtrlBoard<'_> {
|
||||
}
|
||||
}
|
||||
}
|
||||
embedded_svc::mqtt::client::EventPayload::Connected(_) => {
|
||||
mqtt_connected_event_received_copy
|
||||
.store(true, std::sync::atomic::Ordering::Relaxed);
|
||||
mqtt_connected_event_ok_copy
|
||||
.store(true, std::sync::atomic::Ordering::Relaxed);
|
||||
println!("Mqtt connected");
|
||||
}
|
||||
embedded_svc::mqtt::client::EventPayload::Disconnected => {
|
||||
mqtt_connected_event_received_copy
|
||||
.store(true, std::sync::atomic::Ordering::Relaxed);
|
||||
mqtt_connected_event_ok_copy
|
||||
.store(false, std::sync::atomic::Ordering::Relaxed);
|
||||
println!("Mqtt disconnected");
|
||||
}
|
||||
embedded_svc::mqtt::client::EventPayload::Error(esp_error) => {
|
||||
println!("EspMqttError reported {:?}", esp_error);
|
||||
mqtt_connected_event_received_copy
|
||||
.store(true, std::sync::atomic::Ordering::Relaxed);
|
||||
mqtt_connected_event_ok_copy
|
||||
.store(false, std::sync::atomic::Ordering::Relaxed);
|
||||
println!("Mqtt error");
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
})?;
|
||||
|
||||
let wait_for_connections_event = 0;
|
||||
while wait_for_connections_event < 100 {
|
||||
match mqtt_connected_event_received.load(std::sync::atomic::Ordering::Relaxed) {
|
||||
true => {
|
||||
println!("Mqtt connection callback received, progressing");
|
||||
match mqtt_connected_event_ok.load(std::sync::atomic::Ordering::Relaxed) {
|
||||
true => {
|
||||
println!("Mqtt did callback as connected, testing with roundtrip now");
|
||||
//subscribe to roundtrip
|
||||
|
||||
client.subscribe(round_trip_topic.as_str(), ExactlyOnce)?;
|
||||
client.subscribe(stay_alive_topic.as_str(), ExactlyOnce)?;
|
||||
//publish to roundtrip
|
||||
@ -737,18 +709,6 @@ impl PlantCtrlBoardInteraction for PlantCtrlBoard<'_> {
|
||||
}
|
||||
bail!("Mqtt did not complete roundtrip in time");
|
||||
}
|
||||
false => {
|
||||
bail!("Mqtt did respond but with failure")
|
||||
}
|
||||
}
|
||||
}
|
||||
false => {
|
||||
unsafe { vTaskDelay(10) };
|
||||
}
|
||||
}
|
||||
}
|
||||
bail!("Mqtt did not fire connection callback in time");
|
||||
}
|
||||
|
||||
fn mqtt_publish(&mut self, config: &Config, subtopic: &str, message: &[u8]) -> Result<()> {
|
||||
if !subtopic.starts_with("/") {
|
||||
@ -763,8 +723,8 @@ impl PlantCtrlBoardInteraction for PlantCtrlBoard<'_> {
|
||||
println!("Not connected to mqtt");
|
||||
bail!("Not connected to mqtt");
|
||||
}
|
||||
match &mut self.mqtt_client {
|
||||
Some(client) => {
|
||||
let client = self.mqtt_client.as_mut().unwrap();
|
||||
|
||||
let mut full_topic: heapless::String<256> = heapless::String::new();
|
||||
if full_topic.push_str(&config.base_topic).is_err() {
|
||||
println!("Some error assembling full_topic 1");
|
||||
@ -783,116 +743,79 @@ impl PlantCtrlBoardInteraction for PlantCtrlBoard<'_> {
|
||||
Delay::new(10).delay_ms(50);
|
||||
match publish {
|
||||
OkStd(message_id) => {
|
||||
println!(
|
||||
"Published mqtt topic {} with message {:#?} msgid is {:?}",
|
||||
full_topic,
|
||||
String::from_utf8_lossy(message),
|
||||
message_id
|
||||
);
|
||||
return Ok(());
|
||||
}
|
||||
println!("Published mqtt topic {} with message {:#?} msgid is {:?}",full_topic, String::from_utf8_lossy(message), message_id);
|
||||
return Ok(())
|
||||
},
|
||||
Err(err) => {
|
||||
println!(
|
||||
"Error during mqtt send on topic {} with message {:#?} error is {:?}",
|
||||
full_topic,
|
||||
String::from_utf8_lossy(message),
|
||||
err
|
||||
);
|
||||
return Err(err)?;
|
||||
}
|
||||
};
|
||||
}
|
||||
None => {
|
||||
bail!("No mqtt client");
|
||||
}
|
||||
println!("Error during mqtt send on topic {} with message {:#?} error is {:?}",full_topic, String::from_utf8_lossy(message), err);
|
||||
return Err(err)?
|
||||
},
|
||||
}
|
||||
|
||||
;
|
||||
}
|
||||
|
||||
fn state_charge_percent(&mut self) -> Result<u8> {
|
||||
match &mut self.battery_driver {
|
||||
Some(driver) => match driver.state_of_charge() {
|
||||
match self.battery_driver.state_of_charge() {
|
||||
OkStd(r) => Ok(r),
|
||||
Err(err) => bail!("Error reading SoC {:?}", err),
|
||||
},
|
||||
None => bail!("Error reading SoC bq34z100 not found"),
|
||||
}
|
||||
}
|
||||
|
||||
fn remaining_milli_ampere_hour(&mut self) -> Result<u16> {
|
||||
match &mut self.battery_driver {
|
||||
Some(driver) => match driver.remaining_capacity() {
|
||||
match self.battery_driver.remaining_capacity() {
|
||||
OkStd(r) => Ok(r),
|
||||
Err(err) => bail!("Error reading Remaining Capacity {:?}", err),
|
||||
},
|
||||
None => bail!("Error reading Remaining Capacity bq34z100 not found"),
|
||||
}
|
||||
}
|
||||
|
||||
fn max_milli_ampere_hour(&mut self) -> Result<u16> {
|
||||
match &mut self.battery_driver {
|
||||
Some(driver) => match driver.full_charge_capacity() {
|
||||
match self.battery_driver.full_charge_capacity() {
|
||||
OkStd(r) => Ok(r),
|
||||
Err(err) => bail!("Error reading Full Charge Capacity {:?}", err),
|
||||
},
|
||||
None => bail!("Error reading Full Charge Capacity bq34z100 not found"),
|
||||
}
|
||||
}
|
||||
|
||||
fn design_milli_ampere_hour(&mut self) -> Result<u16> {
|
||||
match &mut self.battery_driver {
|
||||
Some(driver) => match driver.design_capacity() {
|
||||
match self.battery_driver.design_capacity() {
|
||||
OkStd(r) => Ok(r),
|
||||
Err(err) => bail!("Error reading Design Capacity {:?}", err),
|
||||
},
|
||||
None => bail!("Error reading Design Capacity bq34z100 not found"),
|
||||
}
|
||||
}
|
||||
|
||||
fn voltage_milli_volt(&mut self) -> Result<u16> {
|
||||
match &mut self.battery_driver {
|
||||
Some(driver) => match driver.voltage() {
|
||||
return match self.battery_driver.voltage() {
|
||||
OkStd(r) => Ok(r),
|
||||
Err(err) => bail!("Error reading voltage {:?}", err),
|
||||
},
|
||||
None => bail!("Error reading voltage bq34z100 not found"),
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
fn average_current_milli_ampere(&mut self) -> Result<i16> {
|
||||
match &mut self.battery_driver {
|
||||
Some(driver) => match driver.average_current() {
|
||||
match self.battery_driver.average_current() {
|
||||
OkStd(r) => Ok(r),
|
||||
Err(err) => bail!("Error reading Average Current {:?}", err),
|
||||
},
|
||||
None => bail!("Error reading Average Current bq34z100 not found"),
|
||||
}
|
||||
}
|
||||
|
||||
fn cycle_count(&mut self) -> Result<u16> {
|
||||
match &mut self.battery_driver {
|
||||
Some(driver) => match driver.cycle_count() {
|
||||
match self.battery_driver.cycle_count() {
|
||||
OkStd(r) => Ok(r),
|
||||
Err(err) => bail!("Error reading Cycle Count {:?}", err),
|
||||
},
|
||||
None => bail!("Error reading Cycle Count bq34z100 not found"),
|
||||
}
|
||||
}
|
||||
|
||||
fn state_health_percent(&mut self) -> Result<u8> {
|
||||
match &mut self.battery_driver {
|
||||
Some(driver) => match driver.state_of_health() {
|
||||
match self.battery_driver.state_of_health() {
|
||||
OkStd(r) => Ok(r as u8),
|
||||
Err(err) => bail!("Error reading State of Health {:?}", err),
|
||||
},
|
||||
None => bail!("Error reading State of Health bq34z100 not found"),
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
fn print_battery(
|
||||
battery_driver: &mut Bq34z100g1Driver<I2cDriver, Delay>,
|
||||
) -> Result<(), Bq34Z100Error<I2cError>> {
|
||||
println!("Try communicating with battery");
|
||||
let fwversion = battery_driver.fw_version().unwrap_or_else(|e| {
|
||||
println!("Firmeware {:?}", e);
|
||||
0
|
||||
@ -951,23 +874,18 @@ impl CreatePlantHal<'_> for PlantHal {
|
||||
fn create() -> Result<Mutex<PlantCtrlBoard<'static>>> {
|
||||
let peripherals = Peripherals::take()?;
|
||||
|
||||
let i2c = peripherals.i2c0;
|
||||
let i2c = peripherals.i2c1;
|
||||
let config = I2cConfig::new()
|
||||
.scl_enable_pullup(true)
|
||||
.sda_enable_pullup(true)
|
||||
.baudrate(1_u32.kHz().into())
|
||||
.timeout(APBTickType::from(Duration::from_millis(100)));
|
||||
let scl = peripherals.pins.gpio19.downgrade();
|
||||
let sda = peripherals.pins.gpio20.downgrade();
|
||||
.scl_enable_pullup(false)
|
||||
.sda_enable_pullup(false)
|
||||
.baudrate(10_u32.kHz().into());
|
||||
let scl = peripherals.pins.gpio16;
|
||||
let sda = peripherals.pins.gpio17;
|
||||
|
||||
let driver = I2cDriver::new(i2c, sda, scl, &config).unwrap();
|
||||
|
||||
let i2c_port = driver.port();
|
||||
let mut timeout: i32 = 0;
|
||||
esp!(unsafe { esp_idf_sys::i2c_get_timeout(i2c_port, &mut timeout) }).unwrap();
|
||||
println!("init i2c timeout is {}", timeout);
|
||||
|
||||
//esp!(unsafe { esp_idf_sys::i2c_set_timeout(i2c_port, 22)}).unwrap();
|
||||
esp!(unsafe { esp_idf_sys::i2c_set_timeout(i2c_port, 1048000) }).unwrap();
|
||||
|
||||
let mut battery_driver: Bq34z100g1Driver<I2cDriver, Delay> = Bq34z100g1Driver {
|
||||
i2c: driver,
|
||||
@ -975,18 +893,18 @@ impl CreatePlantHal<'_> for PlantHal {
|
||||
flash_block_data: [0; 32],
|
||||
};
|
||||
|
||||
let mut clock = PinDriver::input_output(peripherals.pins.gpio15.downgrade())?;
|
||||
let mut clock = PinDriver::input_output(peripherals.pins.gpio21)?;
|
||||
clock.set_pull(Pull::Floating).unwrap();
|
||||
let mut latch = PinDriver::input_output(peripherals.pins.gpio3.downgrade())?;
|
||||
let mut latch = PinDriver::input_output(peripherals.pins.gpio22)?;
|
||||
latch.set_pull(Pull::Floating).unwrap();
|
||||
let mut data = PinDriver::input_output(peripherals.pins.gpio23.downgrade())?;
|
||||
let mut data = PinDriver::input_output(peripherals.pins.gpio19)?;
|
||||
data.set_pull(Pull::Floating).unwrap();
|
||||
let shift_register = ShiftRegister40::new(clock.into(), latch.into(), data.into());
|
||||
for mut pin in shift_register.decompose() {
|
||||
pin.set_low().unwrap();
|
||||
}
|
||||
|
||||
let mut one_wire_pin = PinDriver::input_output_od(peripherals.pins.gpio18)?;
|
||||
let mut one_wire_pin = PinDriver::input_output_od(peripherals.pins.gpio4)?;
|
||||
one_wire_pin.set_pull(Pull::Floating).unwrap();
|
||||
//TODO make to none if not possible to init
|
||||
|
||||
@ -1041,7 +959,7 @@ impl CreatePlantHal<'_> for PlantHal {
|
||||
|
||||
let mut counter_unit1 = PcntDriver::new(
|
||||
peripherals.pcnt0,
|
||||
Some(peripherals.pins.gpio22),
|
||||
Some(peripherals.pins.gpio18),
|
||||
Option::<AnyInputPin>::None,
|
||||
Option::<AnyInputPin>::None,
|
||||
Option::<AnyInputPin>::None,
|
||||
@ -1075,30 +993,29 @@ impl CreatePlantHal<'_> for PlantHal {
|
||||
let nvs = EspDefaultNvsPartition::take()?;
|
||||
let wifi_driver = EspWifi::new(peripherals.modem, sys_loop, Some(nvs))?;
|
||||
|
||||
let adc_config = AdcChannelConfig {
|
||||
attenuation: attenuation::DB_11,
|
||||
let adc_config = esp_idf_hal::adc::config::Config {
|
||||
resolution: esp_idf_hal::adc::config::Resolution::Resolution12Bit,
|
||||
calibration: true,
|
||||
};
|
||||
let tank_driver = AdcDriver::new(peripherals.adc1)?;
|
||||
let tank_channel: AdcChannelDriver<Gpio5, AdcDriver<esp_idf_hal::adc::ADC1>> =
|
||||
AdcChannelDriver::new(tank_driver, peripherals.pins.gpio5, &adc_config)?;
|
||||
let tank_driver = AdcDriver::new(peripherals.adc1, &adc_config)?;
|
||||
let tank_channel: AdcChannelDriver<'_, { attenuation::DB_11 }, Gpio39> =
|
||||
AdcChannelDriver::new(peripherals.pins.gpio39)?;
|
||||
|
||||
let mut solar_is_day = PinDriver::input(peripherals.pins.gpio8.downgrade())?;
|
||||
let mut solar_is_day = PinDriver::input(peripherals.pins.gpio25)?;
|
||||
solar_is_day.set_pull(Pull::Floating)?;
|
||||
|
||||
let mut boot_button = PinDriver::input(peripherals.pins.gpio9.downgrade())?;
|
||||
let mut boot_button = PinDriver::input(peripherals.pins.gpio0)?;
|
||||
boot_button.set_pull(Pull::Floating)?;
|
||||
|
||||
let mut light = PinDriver::input_output(peripherals.pins.gpio10.downgrade())?;
|
||||
let mut light = PinDriver::input_output(peripherals.pins.gpio26)?;
|
||||
light.set_pull(Pull::Floating).unwrap();
|
||||
|
||||
let mut main_pump = PinDriver::input_output(peripherals.pins.gpio2.downgrade())?;
|
||||
let mut main_pump = PinDriver::input_output(peripherals.pins.gpio23)?;
|
||||
main_pump.set_pull(Pull::Floating)?;
|
||||
main_pump.set_low()?;
|
||||
let mut tank_power = PinDriver::input_output(peripherals.pins.gpio11.downgrade())?;
|
||||
let mut tank_power = PinDriver::input_output(peripherals.pins.gpio27)?;
|
||||
tank_power.set_pull(Pull::Floating)?;
|
||||
let mut general_fault = PinDriver::input_output(peripherals.pins.gpio6.downgrade())?;
|
||||
let mut general_fault = PinDriver::input_output(peripherals.pins.gpio13)?;
|
||||
general_fault.set_pull(Pull::Floating)?;
|
||||
general_fault.set_low()?;
|
||||
let one_wire_bus = OneWire::new(one_wire_pin)
|
||||
@ -1109,11 +1026,10 @@ impl CreatePlantHal<'_> for PlantHal {
|
||||
let status = print_battery(&mut battery_driver);
|
||||
if status.is_err() {
|
||||
println!("Error communicating with battery!! {:?}", status.err());
|
||||
} else {
|
||||
println!("Managed to comunnicate with battery");
|
||||
}
|
||||
let rv = Mutex::new(PlantCtrlBoard {
|
||||
shift_register,
|
||||
tank_driver,
|
||||
tank_channel,
|
||||
solar_is_day,
|
||||
boot_button,
|
||||
@ -1125,8 +1041,7 @@ impl CreatePlantHal<'_> for PlantHal {
|
||||
signal_counter: counter_unit1,
|
||||
wifi_driver,
|
||||
mqtt_client: None,
|
||||
//battery_driver: None,
|
||||
battery_driver: Some(battery_driver),
|
||||
battery_driver,
|
||||
});
|
||||
Ok(rv)
|
||||
}
|
||||
|
@ -25,12 +25,12 @@ struct SSIDList<'a> {
|
||||
#[derive(Serialize, Debug)]
|
||||
struct VersionInfo<'a> {
|
||||
git_hash: &'a str,
|
||||
build_time: &'a str,
|
||||
build_time: &'a str
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
|
||||
pub struct TestPump{
|
||||
pump: usize,
|
||||
pump: usize
|
||||
}
|
||||
|
||||
pub fn httpd_initial(reboot_now: Arc<AtomicBool>) -> Box<EspHttpServer<'static>> {
|
||||
@ -273,8 +273,7 @@ pub fn shared() -> Box<EspHttpServer<'static>> {
|
||||
}
|
||||
let actual_data = &buf[0..read.unwrap()];
|
||||
println!("Raw data {}", from_utf8(actual_data).unwrap());
|
||||
let pump_test: Result<TestPump, serde_json::Error> =
|
||||
serde_json::from_slice(actual_data);
|
||||
let pump_test: Result<TestPump, serde_json::Error> = serde_json::from_slice(actual_data);
|
||||
if pump_test.is_err() {
|
||||
let error_text = pump_test.unwrap_err().to_string();
|
||||
println!("Could not parse TestPump {}", error_text);
|
||||
|
Loading…
Reference in New Issue
Block a user