Compare commits

..

No commits in common. "feature/2.0" and "v2.x" have entirely different histories.

78 changed files with 162096 additions and 108591 deletions

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,6 @@
{ {
"board": { "board": {
"active_layer": 36, "active_layer": 39,
"active_layer_preset": "", "active_layer_preset": "",
"auto_track_width": false, "auto_track_width": false,
"hidden_netclasses": [], "hidden_netclasses": [],
@ -8,7 +8,7 @@
"high_contrast_mode": 0, "high_contrast_mode": 0,
"net_color_mode": 1, "net_color_mode": 1,
"opacity": { "opacity": {
"images": 0.4399999976158142, "images": 0.6,
"pads": 1.0, "pads": 1.0,
"tracks": 1.0, "tracks": 1.0,
"vias": 1.0, "vias": 1.0,
@ -68,7 +68,7 @@
39, 39,
40 40
], ],
"visible_layers": "ffc7055_fffffff8", "visible_layers": "ffdfffe_ffffffff",
"zone_display_mode": 1 "zone_display_mode": 1
}, },
"git": { "git": {

View File

@ -37,9 +37,9 @@
"other_text_thickness": 0.15, "other_text_thickness": 0.15,
"other_text_upright": false, "other_text_upright": false,
"pads": { "pads": {
"drill": 0.25, "drill": 0.0,
"height": 0.35, "height": 3.0,
"width": 0.35 "width": 1.6
}, },
"silk_line_width": 0.12, "silk_line_width": 0.12,
"silk_text_italic": false, "silk_text_italic": false,
@ -58,31 +58,13 @@
"width": 0.0 "width": 0.0
} }
], ],
"drc_exclusions": [ "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"
],
"meta": { "meta": {
"filename": "board_design_settings.json", "filename": "board_design_settings.json",
"version": 2 "version": 2
}, },
"rule_severities": { "rule_severities": {
"annular_width": "ignore", "annular_width": "error",
"clearance": "error", "clearance": "error",
"connection_width": "warning", "connection_width": "warning",
"copper_edge_clearance": "error", "copper_edge_clearance": "error",
@ -114,7 +96,7 @@
"padstack": "warning", "padstack": "warning",
"pth_inside_courtyard": "ignore", "pth_inside_courtyard": "ignore",
"shorting_items": "error", "shorting_items": "error",
"silk_edge_clearance": "ignore", "silk_edge_clearance": "warning",
"silk_over_copper": "ignore", "silk_over_copper": "ignore",
"silk_overlap": "ignore", "silk_overlap": "ignore",
"skew_out_of_range": "error", "skew_out_of_range": "error",
@ -1118,133 +1100,13 @@
"label": "DNP", "label": "DNP",
"name": "${DNP}", "name": "${DNP}",
"show": true "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": "", "filter_string": "",
"group_symbols": true, "group_symbols": true,
"name": "", "name": "Grouped By Value",
"sort_asc": true, "sort_asc": true,
"sort_field": "LCSC_PART_NUMBER" "sort_field": "Reference"
}, },
"connection_grid_size": 50.0, "connection_grid_size": 50.0,
"drawing": { "drawing": {

File diff suppressed because it is too large Load Diff

View File

@ -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)))
)
)
)
)
)

View File

@ -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))
)

View File

@ -1 +0,0 @@
{"EXTRA_LAYERS": "", "EXTEND_EDGE_CUT": false, "AUTO TRANSLATE": true, "AUTO FILL": true, "EXCLUDE DNP": true}

View File

@ -4,5 +4,4 @@
(lib (name "ESP32")(type "KiCad")(uri "${KIPRJMOD}/kicad-stuff/ESP32")(options "")(descr "")) (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 "kicad-stuff")(type "KiCad")(uri "${KIPRJMOD}/kicad-stuff")(options "")(descr ""))
(lib (name "board")(type "KiCad")(uri "${KIPRJMOD}/")(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.

View File

@ -8,5 +8,4 @@
(lib (name "CN3306")(type "KiCad")(uri "${KIPRJMOD}/CN3306.kicad_sym")(options "")(descr "")) (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 "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 "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
View 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
View 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
View 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

File diff suppressed because it is too large Load Diff

View 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
View 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

View 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
View File

@ -0,0 +1 @@
config.json

View 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```

Binary file not shown.

View 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
1 # Name, Type, SubType, Offset, Size, Flags
2 nvs, data, nvs, 0x9000, 0x4000
3 otadata, data, ota, 0xD000, 0x2000
4 phy_init, data, phy, 0xF000, 0x1000
5 ota_0, app, ota_0, 0x10000, 0x1E0000
6 ota_1, app, ota_1, 0x1F0000, 0x1E0000
7 spiffs, data, spiffs, 0x3D0000, 0x30000

90
esp32/host/Readme.md Normal file
View 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
View 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)

View 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
View 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
View 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
View 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

View 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
View 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

View 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

View 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 @} */

View 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

View 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
View 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

View 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
View 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
View 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

View 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

View File

@ -0,0 +1,4 @@
#pragma once
long getCurrentTime(void);
int getCurrentHour(void);

View 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
View 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
View 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
View 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

File diff suppressed because it is too large Load Diff

6
esp32/src/CMakeLists.txt Normal file
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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

File diff suppressed because it is too large Load Diff

View 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
View 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

View File

@ -0,0 +1,5 @@
.pio
.vscode/.browse.c_cpp.db*
.vscode/c_cpp_properties.json
.vscode/launch.json
.vscode/ipch

View 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"
]
}

View 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"
}
}
}

View 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

View 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

View 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

View 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

View 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();
}
}

View 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

Binary file not shown.

After

Width:  |  Height:  |  Size: 279 KiB

Binary file not shown.

File diff suppressed because it is too large Load Diff

View 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": []
}
}

View 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": {}
}

File diff suppressed because it is too large Load Diff

View File

@ -1,23 +1,20 @@
[build] [build]
#target = "xtensa-esp32-espidf" target = "xtensa-esp32-espidf"
target = "riscv32imac-esp-espidf"
[target.riscv32imac-esp-espidf] [target.xtensa-esp32-espidf]
linker = "ldproxy" 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 --partition-table partitions.csv" # 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 erase-parts otadata # runner = espflash erase-parts otadata
runner = "espflash flash --monitor --baud 921600 --partition-table partitions.csv" # Select this runner for espflash v2.x.x
runner = "cargo runner" #runner = "cargo runner"
rustflags = [ "--cfg", "espidf_time64"] # Extending time_t for ESP IDF 5: https://github.com/esp-rs/rust/issues/110 rustflags = [ "--cfg", "espidf_time64"] # Extending time_t for ESP IDF 5: https://github.com/esp-rs/rust/issues/110
[unstable] [unstable]
build-std = ["std", "panic_abort"] build-std = ["std", "panic_abort"]
[env] [env]
MCU="esp32c6" MCU="esp32"
# Note: this variable is not used by the pio builder (`cargo build --features pio`) # 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" CHRONO_TZ_TIMEZONE_FILTER="UTC|Europe/Berlin"
CARGO_WORKSPACE_DIR = { value = "", relative = true } CARGO_WORKSPACE_DIR = { value = "", relative = true }
RUST_BACKTRACE = "full"

View File

@ -6,18 +6,26 @@ edition = "2021"
resolver = "2" resolver = "2"
rust-version = "1.71" rust-version = "1.71"
[profile.dev] [profile.release]
# Explicitly disable LTO which the Xtensa codegen backend has issues # Explicitly disable LTO which the Xtensa codegen backend has issues
lto = false #lto = "thin"
opt-level = "s"
strip = false strip = false
#codegen-units = 1
debug = true debug = true
overflow-checks = true overflow-checks = true
panic = "abort" panic = "unwind"
incremental = true incremental = true
opt-level = "s"
[profile.dev.build-override] [profile.dev]
opt-level = 1 # 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 incremental = true
[package.metadata.cargo_runner] [package.metadata.cargo_runner]
@ -27,7 +35,7 @@ command = [
"espflash", "espflash",
"save-image", "save-image",
"--chip", "--chip",
"esp32c6", "esp32",
"image.bin" "image.bin"
] ]
@ -47,6 +55,7 @@ embassy = ["esp-idf-svc/embassy-sync", "esp-idf-svc/critical-section", "esp-idf-
[dependencies] [dependencies]
log = { version = "0.4", default-features = false } log = { version = "0.4", default-features = false }
esp-idf-svc = { version = "0.48.0", default-features = false }
serde = { version = "1.0.192", features = ["derive"] } serde = { version = "1.0.192", features = ["derive"] }
average = { version = "0.14.1" , features = ["std"] } average = { version = "0.14.1" , features = ["std"] }
#esp32 = "0.28.0" #esp32 = "0.28.0"
@ -55,7 +64,6 @@ ds18b20 = "0.1.1"
embedded-svc = { version = "0.27.0", features = ["experimental"] } embedded-svc = { version = "0.27.0", features = ["experimental"] }
esp-idf-hal = "0.43.0" esp-idf-hal = "0.43.0"
esp-idf-sys = { version = "0.34.0", features = ["binstart", "native"] } 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" esp_idf_build = "0.1.3"
chrono = { version = "0.4.23", default-features = false , features = ["iana-time-zone" , "alloc"] } 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" ]} 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" measurements = "0.11.0"
bq34z100 = "0.2.1" 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] [build-dependencies]
embuild = "0.31.3" embuild = "0.31.3"
vergen = { version = "8.2.6", features = ["build", "git", "gitcl"] } vergen = { version = "8.2.6", features = ["build", "git", "gitcl"] }

View File

@ -1,10 +1,8 @@
# Rust often needs a bit of an extra main task stack size compared to C (the default is 3K) # 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). # 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). # This allows to use 1 ms granuality for thread sleeps (10 ms by default).
CONFIG_FREERTOS_HZ=1000 CONFIG_FREERTOS_HZ=1000
CONFIG_BOOTLOADER_APP_ROLLBACK_ENABLE=y CONFIG_BOOTLOADER_APP_ROLLBACK_ENABLE=y
CONFIG_I2C_ENABLE_DEBUG_LOG=y
DEBUG_LEVEL=5

File diff suppressed because it is too large Load Diff

View File

@ -4,10 +4,7 @@ use chrono_tz::Europe::Berlin;
use embedded_svc::wifi::{ use embedded_svc::wifi::{
AccessPointConfiguration, AccessPointInfo, AuthMethod, ClientConfiguration, Configuration, AccessPointConfiguration, AccessPointInfo, AuthMethod, ClientConfiguration, Configuration,
}; };
use esp_idf_hal::adc::attenuation; use esp_idf_hal::i2c::{I2cConfig, I2cDriver, I2cError};
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::units::FromValueType; use esp_idf_hal::units::FromValueType;
use esp_idf_svc::eventloop::EspSystemEventLoop; use esp_idf_svc::eventloop::EspSystemEventLoop;
use esp_idf_svc::ipv4::IpInfo; use esp_idf_svc::ipv4::IpInfo;
@ -35,8 +32,9 @@ use std::sync::{Arc, Mutex};
use std::time::Duration; use std::time::Duration;
use embedded_hal::digital::OutputPin; use embedded_hal::digital::OutputPin;
use esp_idf_hal::adc::{attenuation, AdcChannelDriver, AdcDriver};
use esp_idf_hal::delay::Delay; 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::{ use esp_idf_hal::pcnt::{
PcntChannel, PcntChannelConfig, PcntControlMode, PcntCountMode, PcntDriver, PinIndex, PcntChannel, PcntChannelConfig, PcntControlMode, PcntCountMode, PcntDriver, PinIndex,
}; };
@ -84,7 +82,8 @@ pub enum ClearConfigType {
None, None,
} }
#[derive(Debug, PartialEq)] #[derive(Debug)]
#[derive(PartialEq)]
pub enum Sensor { pub enum Sensor {
A, A,
B, B,
@ -127,7 +126,7 @@ pub trait PlantCtrlBoardInteraction {
fn measure_moisture_hz(&self, plant: usize, sensor: Sensor) -> Result<i32>; fn measure_moisture_hz(&self, plant: usize, sensor: Sensor) -> Result<i32>;
fn pump(&self, plant: usize, enable: bool) -> Result<()>; 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_last_pump_time(&mut self, plant: usize, time: chrono::DateTime<Utc>);
fn store_consecutive_pump_count(&mut self, plant: usize, count: u32); fn store_consecutive_pump_count(&mut self, plant: usize, count: u32);
fn consecutive_pump_count(&mut self, plant: usize) -> u32; fn consecutive_pump_count(&mut self, plant: usize) -> u32;
@ -145,7 +144,7 @@ pub trait PlantCtrlBoardInteraction {
fn wifi_ap(&mut self) -> Result<()>; fn wifi_ap(&mut self) -> Result<()>;
fn wifi_scan(&mut self) -> Result<Vec<AccessPointInfo>>; fn wifi_scan(&mut self) -> Result<Vec<AccessPointInfo>>;
fn test(&mut self) -> Result<()>; fn test(&mut self) -> Result<()>;
fn test_pump(&mut self, plant: usize) -> Result<()>; fn test_pump(&mut self, plant:usize) -> Result<()>;
fn is_wifi_config_file_existant(&mut self) -> bool; fn is_wifi_config_file_existant(&mut self) -> bool;
fn mqtt(&mut self, config: &Config) -> Result<()>; fn mqtt(&mut self, config: &Config) -> Result<()>;
fn mqtt_publish(&mut self, config: &Config, subtopic: &str, message: &[u8]) -> Result<()>; fn mqtt_publish(&mut self, config: &Config, subtopic: &str, message: &[u8]) -> Result<()>;
@ -159,22 +158,23 @@ pub struct PlantHal {}
pub struct PlantCtrlBoard<'a> { pub struct PlantCtrlBoard<'a> {
shift_register: ShiftRegister40< shift_register: ShiftRegister40<
PinDriver<'a, esp_idf_hal::gpio::AnyIOPin, InputOutput>, PinDriver<'a, esp_idf_hal::gpio::Gpio21, InputOutput>,
PinDriver<'a, esp_idf_hal::gpio::AnyIOPin, InputOutput>, PinDriver<'a, esp_idf_hal::gpio::Gpio22, InputOutput>,
PinDriver<'a, esp_idf_hal::gpio::AnyIOPin, InputOutput>, PinDriver<'a, esp_idf_hal::gpio::Gpio19, InputOutput>,
>, >,
tank_channel: AdcChannelDriver<'a, Gpio5, AdcDriver<'a, esp_idf_hal::adc::ADC1>>, tank_driver: AdcDriver<'a, esp_idf_hal::adc::ADC1>,
solar_is_day: PinDriver<'a, esp_idf_hal::gpio::AnyIOPin, esp_idf_hal::gpio::Input>, tank_channel: esp_idf_hal::adc::AdcChannelDriver<'a, { attenuation::DB_11 }, Gpio39>,
boot_button: PinDriver<'a, esp_idf_hal::gpio::AnyIOPin, esp_idf_hal::gpio::Input>, 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>, signal_counter: PcntDriver<'a>,
light: 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::AnyIOPin, InputOutput>, main_pump: PinDriver<'a, esp_idf_hal::gpio::Gpio23, InputOutput>,
tank_power: PinDriver<'a, esp_idf_hal::gpio::AnyIOPin, InputOutput>, tank_power: PinDriver<'a, esp_idf_hal::gpio::Gpio27, InputOutput>,
general_fault: PinDriver<'a, esp_idf_hal::gpio::AnyIOPin, InputOutput>, general_fault: PinDriver<'a, esp_idf_hal::gpio::Gpio13, InputOutput>,
pub wifi_driver: EspWifi<'a>, 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>>, mqtt_client: Option<EspMqttClient<'a>>,
battery_driver: Option<Bq34z100g1Driver<I2cDriver<'a>, Delay>>, battery_driver: Bq34z100g1Driver<I2cDriver<'a>, Delay>,
} }
impl PlantCtrlBoardInteraction for PlantCtrlBoard<'_> { impl PlantCtrlBoardInteraction for PlantCtrlBoard<'_> {
@ -223,7 +223,7 @@ impl PlantCtrlBoardInteraction for PlantCtrlBoard<'_> {
let mut store = [0_u16; TANK_MULTI_SAMPLE]; let mut store = [0_u16; TANK_MULTI_SAMPLE];
for multisample in 0..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[multisample] = value;
} }
store.sort(); store.sort();
@ -242,7 +242,10 @@ impl PlantCtrlBoardInteraction for PlantCtrlBoard<'_> {
let mut percent = r2 / 190_f32 * 100_f32; let mut percent = r2 / 190_f32 * 100_f32;
percent = percent.clamp(0.0, 100.0); 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); return Ok(percent as u16);
} }
@ -268,13 +271,15 @@ impl PlantCtrlBoardInteraction for PlantCtrlBoard<'_> {
fn pump(&self, plant: usize, enable: bool) -> Result<()> { fn pump(&self, plant: usize, enable: bool) -> Result<()> {
let index = plant * PINS_PER_PLANT + PLANT_PUMP_OFFSET; let index = plant * PINS_PER_PLANT + PLANT_PUMP_OFFSET;
//currently infailable error, keep for future as result anyway //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(()) 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]; 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>) { 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; let factor = 1000 as f32 / measurement as f32;
self.shift_register.decompose()[index].set_high().unwrap(); 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 //give some time to stabilize
delay.delay_ms(10); delay.delay_ms(10);
self.signal_counter.counter_resume()?; self.signal_counter.counter_resume()?;
delay.delay_ms(measurement); delay.delay_ms(measurement);
self.signal_counter.counter_pause()?; self.signal_counter.counter_pause()?;
self.shift_register.decompose()[index].set_low().unwrap(); 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 unscaled = self.signal_counter.get_counter_value()? as i32;
let hz = (unscaled as f32 * factor) as i32; let hz = (unscaled as f32 * factor) as i32;
println!("Measuring {:?} @ {} with {}", sensor, plant, hz); println!("Measuring {:?} @ {} with {}", sensor, plant, hz);
@ -367,9 +380,7 @@ impl PlantCtrlBoardInteraction for PlantCtrlBoard<'_> {
} }
fn general_fault(&mut self, enable: bool) { fn general_fault(&mut self, enable: bool) {
unsafe { gpio_hold_dis(self.general_fault.pin()) };
self.general_fault.set_state(enable.into()).unwrap(); self.general_fault.set_state(enable.into()).unwrap();
unsafe { gpio_hold_en(self.general_fault.pin()) };
} }
fn wifi_ap(&mut self) -> Result<()> { fn wifi_ap(&mut self) -> Result<()> {
@ -404,13 +415,13 @@ impl PlantCtrlBoardInteraction for PlantCtrlBoard<'_> {
))?; ))?;
} }
None => { None => {
self.wifi_driver.set_configuration(&Configuration::Client( self.wifi_driver
ClientConfiguration { .set_configuration(&Configuration::Client(ClientConfiguration {
ssid: ssid, ssid: ssid,
auth_method: AuthMethod::None, auth_method: AuthMethod::None,
..Default::default() ..Default::default()
}, }))
))?; .unwrap();
} }
} }
@ -432,7 +443,7 @@ impl PlantCtrlBoardInteraction for PlantCtrlBoard<'_> {
} }
println!("Should be connected now"); println!("Should be connected now");
while !self.wifi_driver.is_up()? { while !self.wifi_driver.is_up().unwrap() {
println!("Waiting for network being up"); println!("Waiting for network being up");
delay.delay_ms(250); delay.delay_ms(250);
counter += 250; counter += 250;
@ -444,7 +455,7 @@ impl PlantCtrlBoardInteraction for PlantCtrlBoard<'_> {
} }
} }
//update freertos registers ;) //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); println!("IP info: {:?}", address);
Ok(address) Ok(address)
} }
@ -565,7 +576,7 @@ impl PlantCtrlBoardInteraction for PlantCtrlBoard<'_> {
Ok(self.wifi_driver.get_scan_result()?) Ok(self.wifi_driver.get_scan_result()?)
} }
fn test_pump(&mut self, plant: usize) -> Result<()> { fn test_pump(&mut self, plant:usize) -> Result<()> {
self.any_pump(true)?; self.any_pump(true)?;
self.pump(plant, true)?; self.pump(plant, true)?;
unsafe { vTaskDelay(30000) }; unsafe { vTaskDelay(30000) };
@ -619,6 +630,7 @@ impl PlantCtrlBoardInteraction for PlantCtrlBoard<'_> {
fn mqtt(&mut self, config: &Config) -> Result<()> { fn mqtt(&mut self, config: &Config) -> Result<()> {
let last_will_topic = format!("{}/state", config.base_topic); let last_will_topic = format!("{}/state", config.base_topic);
let mqtt_client_config = MqttClientConfiguration { let mqtt_client_config = MqttClientConfiguration {
lwt: Some(LwtConfiguration { lwt: Some(LwtConfiguration {
topic: &last_will_topic, topic: &last_will_topic,
@ -627,30 +639,20 @@ impl PlantCtrlBoardInteraction for PlantCtrlBoard<'_> {
retain: true, retain: true,
}), }),
client_id: Some("plantctrl"), client_id: Some("plantctrl"),
keep_alive_interval: Some(Duration::from_secs(60 * 60 * 2)), keep_alive_interval : Some(Duration::from_secs(60*60*2)),
//room for improvement //room for improvement
..Default::default() ..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_ok = Arc::new(AtomicBool::new(false));
let round_trip_topic = format!("{}/internal/roundtrip", config.base_topic); let round_trip_topic = format!("{}/internal/roundtrip", config.base_topic);
let stay_alive_topic = format!("{}/stay_alive", config.base_topic); let stay_alive_topic = format!("{}/stay_alive", config.base_topic);
println!("Round trip topic is {}", round_trip_topic); println!("Round trip topic is {}", round_trip_topic);
println!("Stay alive topic is {}", stay_alive_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 stay_alive_topic_copy = stay_alive_topic.clone();
let round_trip_topic_copy = round_trip_topic.clone(); let round_trip_topic_copy = round_trip_topic.clone();
let round_trip_ok_copy = round_trip_ok.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 = let mut client =
EspMqttClient::new_cb(&config.mqtt_url, &mqtt_client_config, move |event| { EspMqttClient::new_cb(&config.mqtt_url, &mqtt_client_config, move |event| {
let payload = event.payload(); let payload = event.payload();
@ -677,77 +679,35 @@ 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");
}
_ => {} _ => {}
} }
})?; })?;
//subscribe to roundtrip
let wait_for_connections_event = 0; client.subscribe(round_trip_topic.as_str(), ExactlyOnce)?;
while wait_for_connections_event < 100 { client.subscribe(stay_alive_topic.as_str(), ExactlyOnce)?;
match mqtt_connected_event_received.load(std::sync::atomic::Ordering::Relaxed) { //publish to roundtrip
client.publish(
round_trip_topic.as_str(),
ExactlyOnce,
false,
"online_test".as_bytes(),
)?;
let wait_for_roundtrip = 0;
while wait_for_roundtrip < 100 {
match round_trip_ok.load(std::sync::atomic::Ordering::Relaxed) {
true => { true => {
println!("Mqtt connection callback received, progressing"); println!("Round trip registered, proceeding");
match mqtt_connected_event_ok.load(std::sync::atomic::Ordering::Relaxed) { self.mqtt_client = Some(client);
true => { return Ok(());
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
client.publish(
round_trip_topic.as_str(),
ExactlyOnce,
false,
"online_test".as_bytes(),
)?;
let wait_for_roundtrip = 0;
while wait_for_roundtrip < 100 {
match round_trip_ok.load(std::sync::atomic::Ordering::Relaxed) {
true => {
println!("Round trip registered, proceeding");
self.mqtt_client = Some(client);
return Ok(());
}
false => {
unsafe { vTaskDelay(10) };
}
}
}
bail!("Mqtt did not complete roundtrip in time");
}
false => {
bail!("Mqtt did respond but with failure")
}
}
} }
false => { false => {
unsafe { vTaskDelay(10) }; unsafe { vTaskDelay(10) };
} }
} }
} }
bail!("Mqtt did not fire connection callback in time"); bail!("Mqtt did not complete roundtrip in time");
} }
fn mqtt_publish(&mut self, config: &Config, subtopic: &str, message: &[u8]) -> Result<()> { fn mqtt_publish(&mut self, config: &Config, subtopic: &str, message: &[u8]) -> Result<()> {
@ -763,136 +723,99 @@ impl PlantCtrlBoardInteraction for PlantCtrlBoard<'_> {
println!("Not connected to mqtt"); println!("Not connected to mqtt");
bail!("Not connected to mqtt"); bail!("Not connected to mqtt");
} }
match &mut self.mqtt_client { let client = self.mqtt_client.as_mut().unwrap();
Some(client) => {
let mut full_topic: heapless::String<256> = heapless::String::new(); let mut full_topic: heapless::String<256> = heapless::String::new();
if full_topic.push_str(&config.base_topic).is_err() { if full_topic.push_str(&config.base_topic).is_err() {
println!("Some error assembling full_topic 1"); println!("Some error assembling full_topic 1");
bail!("Some error assembling full_topic 1") bail!("Some error assembling full_topic 1")
}; };
if full_topic.push_str(subtopic).is_err() { if full_topic.push_str(subtopic).is_err() {
println!("Some error assembling full_topic 2"); println!("Some error assembling full_topic 2");
bail!("Some error assembling full_topic 2") bail!("Some error assembling full_topic 2")
}; };
let publish = client.publish( let publish = client.publish(
&full_topic, &full_topic,
embedded_svc::mqtt::client::QoS::ExactlyOnce, embedded_svc::mqtt::client::QoS::ExactlyOnce,
true, true,
message, message,
); );
Delay::new(10).delay_ms(50); Delay::new(10).delay_ms(50);
match publish { match publish {
OkStd(message_id) => { OkStd(message_id) => {
println!( println!("Published mqtt topic {} with message {:#?} msgid is {:?}",full_topic, String::from_utf8_lossy(message), message_id);
"Published mqtt topic {} with message {:#?} msgid is {:?}", return Ok(())
full_topic, },
String::from_utf8_lossy(message), Err(err) => {
message_id println!("Error during mqtt send on topic {} with message {:#?} error is {:?}",full_topic, String::from_utf8_lossy(message), err);
); return Err(err)?
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");
}
} }
;
} }
fn state_charge_percent(&mut self) -> Result<u8> { fn state_charge_percent(&mut self) -> Result<u8> {
match &mut self.battery_driver { match self.battery_driver.state_of_charge() {
Some(driver) => match driver.state_of_charge() { OkStd(r) => Ok(r),
OkStd(r) => Ok(r), Err(err) => bail!("Error reading SoC {:?}", err),
Err(err) => bail!("Error reading SoC {:?}", err),
},
None => bail!("Error reading SoC bq34z100 not found"),
} }
} }
fn remaining_milli_ampere_hour(&mut self) -> Result<u16> { fn remaining_milli_ampere_hour(&mut self) -> Result<u16> {
match &mut self.battery_driver { match self.battery_driver.remaining_capacity() {
Some(driver) => match driver.remaining_capacity() { OkStd(r) => Ok(r),
OkStd(r) => Ok(r), Err(err) => bail!("Error reading Remaining Capacity {:?}", err),
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> { fn max_milli_ampere_hour(&mut self) -> Result<u16> {
match &mut self.battery_driver { match self.battery_driver.full_charge_capacity() {
Some(driver) => match driver.full_charge_capacity() { OkStd(r) => Ok(r),
OkStd(r) => Ok(r), Err(err) => bail!("Error reading Full Charge Capacity {:?}", err),
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> { fn design_milli_ampere_hour(&mut self) -> Result<u16> {
match &mut self.battery_driver { match self.battery_driver.design_capacity() {
Some(driver) => match driver.design_capacity() { OkStd(r) => Ok(r),
OkStd(r) => Ok(r), Err(err) => bail!("Error reading Design Capacity {:?}", err),
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> { fn voltage_milli_volt(&mut self) -> Result<u16> {
match &mut self.battery_driver { return match self.battery_driver.voltage() {
Some(driver) => match driver.voltage() { OkStd(r) => Ok(r),
OkStd(r) => Ok(r), Err(err) => bail!("Error reading voltage {:?}", err),
Err(err) => bail!("Error reading voltage {:?}", err), };
},
None => bail!("Error reading voltage bq34z100 not found"),
}
} }
fn average_current_milli_ampere(&mut self) -> Result<i16> { fn average_current_milli_ampere(&mut self) -> Result<i16> {
match &mut self.battery_driver { match self.battery_driver.average_current() {
Some(driver) => match driver.average_current() { OkStd(r) => Ok(r),
OkStd(r) => Ok(r), Err(err) => bail!("Error reading Average Current {:?}", err),
Err(err) => bail!("Error reading Average Current {:?}", err),
},
None => bail!("Error reading Average Current bq34z100 not found"),
} }
} }
fn cycle_count(&mut self) -> Result<u16> { fn cycle_count(&mut self) -> Result<u16> {
match &mut self.battery_driver { match self.battery_driver.cycle_count() {
Some(driver) => match driver.cycle_count() { OkStd(r) => Ok(r),
OkStd(r) => Ok(r), Err(err) => bail!("Error reading Cycle Count {:?}", err),
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> { fn state_health_percent(&mut self) -> Result<u8> {
match &mut self.battery_driver { match self.battery_driver.state_of_health() {
Some(driver) => match driver.state_of_health() { OkStd(r) => Ok(r as u8),
OkStd(r) => Ok(r as u8), Err(err) => bail!("Error reading State of Health {:?}", err),
Err(err) => bail!("Error reading State of Health {:?}", err),
},
None => bail!("Error reading State of Health bq34z100 not found"),
} }
} }
} }
fn print_battery( fn print_battery(
battery_driver: &mut Bq34z100g1Driver<I2cDriver, Delay>, battery_driver: &mut Bq34z100g1Driver<I2cDriver, Delay>,
) -> Result<(), Bq34Z100Error<I2cError>> { ) -> Result<(), Bq34Z100Error<I2cError>> {
println!("Try communicating with battery");
let fwversion = battery_driver.fw_version().unwrap_or_else(|e| { let fwversion = battery_driver.fw_version().unwrap_or_else(|e| {
println!("Firmeware {:?}", e); println!("Firmeware {:?}", e);
0 0
@ -951,23 +874,18 @@ impl CreatePlantHal<'_> for PlantHal {
fn create() -> Result<Mutex<PlantCtrlBoard<'static>>> { fn create() -> Result<Mutex<PlantCtrlBoard<'static>>> {
let peripherals = Peripherals::take()?; let peripherals = Peripherals::take()?;
let i2c = peripherals.i2c0; let i2c = peripherals.i2c1;
let config = I2cConfig::new() let config = I2cConfig::new()
.scl_enable_pullup(true) .scl_enable_pullup(false)
.sda_enable_pullup(true) .sda_enable_pullup(false)
.baudrate(1_u32.kHz().into()) .baudrate(10_u32.kHz().into());
.timeout(APBTickType::from(Duration::from_millis(100))); let scl = peripherals.pins.gpio16;
let scl = peripherals.pins.gpio19.downgrade(); let sda = peripherals.pins.gpio17;
let sda = peripherals.pins.gpio20.downgrade();
let driver = I2cDriver::new(i2c, sda, scl, &config).unwrap(); let driver = I2cDriver::new(i2c, sda, scl, &config).unwrap();
let i2c_port = driver.port(); let i2c_port = driver.port();
let mut timeout: i32 = 0; esp!(unsafe { esp_idf_sys::i2c_set_timeout(i2c_port, 1048000) }).unwrap();
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();
let mut battery_driver: Bq34z100g1Driver<I2cDriver, Delay> = Bq34z100g1Driver { let mut battery_driver: Bq34z100g1Driver<I2cDriver, Delay> = Bq34z100g1Driver {
i2c: driver, i2c: driver,
@ -975,18 +893,18 @@ impl CreatePlantHal<'_> for PlantHal {
flash_block_data: [0; 32], 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(); 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(); 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(); data.set_pull(Pull::Floating).unwrap();
let shift_register = ShiftRegister40::new(clock.into(), latch.into(), data.into()); let shift_register = ShiftRegister40::new(clock.into(), latch.into(), data.into());
for mut pin in shift_register.decompose() { for mut pin in shift_register.decompose() {
pin.set_low().unwrap(); 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(); one_wire_pin.set_pull(Pull::Floating).unwrap();
//TODO make to none if not possible to init //TODO make to none if not possible to init
@ -1041,7 +959,7 @@ impl CreatePlantHal<'_> for PlantHal {
let mut counter_unit1 = PcntDriver::new( let mut counter_unit1 = PcntDriver::new(
peripherals.pcnt0, peripherals.pcnt0,
Some(peripherals.pins.gpio22), Some(peripherals.pins.gpio18),
Option::<AnyInputPin>::None, Option::<AnyInputPin>::None,
Option::<AnyInputPin>::None, Option::<AnyInputPin>::None,
Option::<AnyInputPin>::None, Option::<AnyInputPin>::None,
@ -1075,30 +993,29 @@ impl CreatePlantHal<'_> for PlantHal {
let nvs = EspDefaultNvsPartition::take()?; let nvs = EspDefaultNvsPartition::take()?;
let wifi_driver = EspWifi::new(peripherals.modem, sys_loop, Some(nvs))?; let wifi_driver = EspWifi::new(peripherals.modem, sys_loop, Some(nvs))?;
let adc_config = AdcChannelConfig { let adc_config = esp_idf_hal::adc::config::Config {
attenuation: attenuation::DB_11,
resolution: esp_idf_hal::adc::config::Resolution::Resolution12Bit, resolution: esp_idf_hal::adc::config::Resolution::Resolution12Bit,
calibration: true, calibration: true,
}; };
let tank_driver = AdcDriver::new(peripherals.adc1)?; let tank_driver = AdcDriver::new(peripherals.adc1, &adc_config)?;
let tank_channel: AdcChannelDriver<Gpio5, AdcDriver<esp_idf_hal::adc::ADC1>> = let tank_channel: AdcChannelDriver<'_, { attenuation::DB_11 }, Gpio39> =
AdcChannelDriver::new(tank_driver, peripherals.pins.gpio5, &adc_config)?; 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)?; 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)?; 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(); 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_pull(Pull::Floating)?;
main_pump.set_low()?; 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)?; 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_pull(Pull::Floating)?;
general_fault.set_low()?; general_fault.set_low()?;
let one_wire_bus = OneWire::new(one_wire_pin) let one_wire_bus = OneWire::new(one_wire_pin)
@ -1109,11 +1026,10 @@ impl CreatePlantHal<'_> for PlantHal {
let status = print_battery(&mut battery_driver); let status = print_battery(&mut battery_driver);
if status.is_err() { if status.is_err() {
println!("Error communicating with battery!! {:?}", status.err()); println!("Error communicating with battery!! {:?}", status.err());
} else {
println!("Managed to comunnicate with battery");
} }
let rv = Mutex::new(PlantCtrlBoard { let rv = Mutex::new(PlantCtrlBoard {
shift_register, shift_register,
tank_driver,
tank_channel, tank_channel,
solar_is_day, solar_is_day,
boot_button, boot_button,
@ -1125,8 +1041,7 @@ impl CreatePlantHal<'_> for PlantHal {
signal_counter: counter_unit1, signal_counter: counter_unit1,
wifi_driver, wifi_driver,
mqtt_client: None, mqtt_client: None,
//battery_driver: None, battery_driver,
battery_driver: Some(battery_driver),
}); });
Ok(rv) Ok(rv)
} }

View File

@ -25,12 +25,12 @@ struct SSIDList<'a> {
#[derive(Serialize, Debug)] #[derive(Serialize, Debug)]
struct VersionInfo<'a> { struct VersionInfo<'a> {
git_hash: &'a str, git_hash: &'a str,
build_time: &'a str, build_time: &'a str
} }
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)] #[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
pub struct TestPump { pub struct TestPump{
pump: usize, pump: usize
} }
pub fn httpd_initial(reboot_now: Arc<AtomicBool>) -> Box<EspHttpServer<'static>> { pub fn httpd_initial(reboot_now: Arc<AtomicBool>) -> Box<EspHttpServer<'static>> {
@ -174,7 +174,7 @@ pub fn shared() -> Box<EspHttpServer<'static>> {
let mut response = request.into_ok_response()?; let mut response = request.into_ok_response()?;
let git_hash = env!("VERGEN_GIT_DESCRIBE"); let git_hash = env!("VERGEN_GIT_DESCRIBE");
let build_time = env!("VERGEN_BUILD_TIMESTAMP"); let build_time = env!("VERGEN_BUILD_TIMESTAMP");
let version_info = VersionInfo { let version_info = VersionInfo{
git_hash, git_hash,
build_time, build_time,
}; };
@ -253,40 +253,39 @@ pub fn shared() -> Box<EspHttpServer<'static>> {
}) })
.unwrap(); .unwrap();
server server
.fn_handler("/boardtest", Method::Post, move |_| { .fn_handler("/boardtest", Method::Post, move |_| {
let mut board = BOARD_ACCESS.lock().unwrap(); let mut board = BOARD_ACCESS.lock().unwrap();
board.test()?; board.test()?;
anyhow::Ok(()) anyhow::Ok(())
}) })
.unwrap(); .unwrap();
server server
.fn_handler("/pumptest", Method::Post, |mut request| { .fn_handler("/pumptest", Method::Post, |mut request| {
let mut buf = [0_u8; 3072]; let mut buf = [0_u8; 3072];
let read = request.read(&mut buf); let read = request.read(&mut buf);
if read.is_err() { if read.is_err() {
let error_text = read.unwrap_err().to_string(); let error_text = read.unwrap_err().to_string();
println!("Could not parse testrequest {}", error_text); println!("Could not parse testrequest {}", error_text);
request request
.into_status_response(500)? .into_status_response(500)?
.write(error_text.as_bytes())?; .write(error_text.as_bytes())?;
return anyhow::Ok(()); return anyhow::Ok(());
} }
let actual_data = &buf[0..read.unwrap()]; let actual_data = &buf[0..read.unwrap()];
println!("Raw data {}", from_utf8(actual_data).unwrap()); println!("Raw data {}", from_utf8(actual_data).unwrap());
let pump_test: Result<TestPump, serde_json::Error> = let pump_test: Result<TestPump, serde_json::Error> = serde_json::from_slice(actual_data);
serde_json::from_slice(actual_data); if pump_test.is_err() {
if pump_test.is_err() { let error_text = pump_test.unwrap_err().to_string();
let error_text = pump_test.unwrap_err().to_string(); println!("Could not parse TestPump {}", error_text);
println!("Could not parse TestPump {}", error_text); request
request .into_status_response(500)?
.into_status_response(500)? .write(error_text.as_bytes())?;
.write(error_text.as_bytes())?; return Ok(());
return Ok(()); }
} let mut board = BOARD_ACCESS.lock().unwrap();
let mut board = BOARD_ACCESS.lock().unwrap(); board.test_pump(pump_test.unwrap().pump)?;
board.test_pump(pump_test.unwrap().pump)?; anyhow::Ok(())
anyhow::Ok(()) })
}) .unwrap();
.unwrap();
server server
} }