3 Commits

388 changed files with 29814 additions and 220364 deletions

14
.gitignore vendored
View File

@@ -1,19 +1,13 @@
*.sch-bak
PlantCtrlESP32-backups/
board/production/PlantCtrlESP32_2023-11-08_00-45-35/PlantCtrlESP32.zip
board/production/PlantCtrlESP32_2023-11-08_00-45-35/netlist.ipc
.vscode
.embuild/
target/
target
Cargo.lock
node_modules/
rust/src/webserver/bundle.js
rust/src/webserver/index.html
rust/build/
rust/image.bin
*.zip
fp-info-cache
production/
_autosave*
~_autosave*
\#auto_saved_files#
*.lck
build/
image.bin

Binary file not shown.

View File

@@ -1,4 +0,0 @@
(fp_lib_table
(version 7)
(lib (name "Modules")(type "KiCad")(uri "../_Shared/footprint/Modules.pretty")(options "")(descr ""))
)

View File

@@ -1,4 +0,0 @@
(sym_lib_table
(version 7)
(lib (name "Light")(type "KiCad")(uri "/home/empire/workspace/PlantCtrl/Hardware/_Shared/symbols/Light.kicad_sym")(options "")(descr ""))
)

File diff suppressed because it is too large Load Diff

View File

@@ -1,39 +0,0 @@
(footprint SOIC127P600X175-8N (layer F.Cu) (tedit 68EBCB5D)
(descr "")
(attr smd)
(fp_text reference REF** (at -0.268 -3.2064 0) (layer F.SilkS)
(effects (font (size 0.64 0.64) (thickness 0.15)))
)
(fp_text value SOIC127P600X175-8N (at 5.0152 3.1936 0) (layer F.Fab)
(effects (font (size 0.64 0.64) (thickness 0.15)))
)
(pad 1 smd roundrect (roundrect_rratio 0.25) (at -2.475 -1.905) (size 1.97 0.6) (layers F.Cu F.Mask F.Paste) (solder_mask_margin 0.102))
(pad 2 smd roundrect (roundrect_rratio 0.25) (at -2.475 -0.635) (size 1.97 0.6) (layers F.Cu F.Mask F.Paste) (solder_mask_margin 0.102))
(pad 3 smd roundrect (roundrect_rratio 0.25) (at -2.475 0.635) (size 1.97 0.6) (layers F.Cu F.Mask F.Paste) (solder_mask_margin 0.102))
(pad 4 smd roundrect (roundrect_rratio 0.25) (at -2.475 1.905) (size 1.97 0.6) (layers F.Cu F.Mask F.Paste) (solder_mask_margin 0.102))
(pad 5 smd roundrect (roundrect_rratio 0.25) (at 2.475 1.905) (size 1.97 0.6) (layers F.Cu F.Mask F.Paste) (solder_mask_margin 0.102))
(pad 6 smd roundrect (roundrect_rratio 0.25) (at 2.475 0.635) (size 1.97 0.6) (layers F.Cu F.Mask F.Paste) (solder_mask_margin 0.102))
(pad 7 smd roundrect (roundrect_rratio 0.25) (at 2.475 -0.635) (size 1.97 0.6) (layers F.Cu F.Mask F.Paste) (solder_mask_margin 0.102))
(pad 8 smd roundrect (roundrect_rratio 0.25) (at 2.475 -1.905) (size 1.97 0.6) (layers F.Cu F.Mask F.Paste) (solder_mask_margin 0.102))
(fp_line (start -2.0 2.5) (end -2.0 -2.5) (layer F.Fab) (width 0.127))
(fp_line (start -2.0 -2.5) (end 2.0 -2.5) (layer F.Fab) (width 0.127))
(fp_line (start 2.0 -2.5) (end 2.0 2.5) (layer F.Fab) (width 0.127))
(fp_line (start 2.0 2.5) (end -2.0 2.5) (layer F.Fab) (width 0.127))
(fp_line (start -2.0 -2.519) (end 2.0 -2.519) (layer F.SilkS) (width 0.127))
(fp_line (start -2.0 2.519) (end 2.0 2.519) (layer F.SilkS) (width 0.127))
(fp_line (start -3.71 -2.46) (end -2.25 -2.46) (layer F.CrtYd) (width 0.05))
(fp_line (start -2.25 -2.46) (end -2.25 -2.75) (layer F.CrtYd) (width 0.05))
(fp_line (start -2.25 -2.75) (end 2.25 -2.75) (layer F.CrtYd) (width 0.05))
(fp_line (start 2.25 -2.75) (end 2.25 -2.46) (layer F.CrtYd) (width 0.05))
(fp_line (start 2.25 -2.46) (end 3.71 -2.46) (layer F.CrtYd) (width 0.05))
(fp_line (start 3.71 2.46) (end 2.25 2.46) (layer F.CrtYd) (width 0.05))
(fp_line (start 2.25 2.46) (end 2.25 2.75) (layer F.CrtYd) (width 0.05))
(fp_line (start 2.25 2.75) (end -2.25 2.75) (layer F.CrtYd) (width 0.05))
(fp_line (start -2.25 2.75) (end -2.25 2.46) (layer F.CrtYd) (width 0.05))
(fp_line (start -2.25 2.46) (end -3.71 2.46) (layer F.CrtYd) (width 0.05))
(fp_line (start -3.71 2.46) (end -3.71 -2.46) (layer F.CrtYd) (width 0.05))
(fp_line (start 3.71 -2.46) (end 3.71 2.46) (layer F.CrtYd) (width 0.05))
(fp_circle (center -4.1 -1.9) (end -4.0 -1.9) (layer F.SilkS) (width 0.2))
(fp_circle (center -4.1 -1.9) (end -4.0 -1.9) (layer F.Fab) (width 0.2))
)

View File

@@ -1,6 +0,0 @@
(fp_lib_table
(version 7)
(lib (name "amass")(type "KiCad")(uri "${KIPRJMOD}/footprints/amass")(options "")(descr ""))
(lib (name "Modules")(type "KiCad")(uri "${KIPRJMOD}/../_Shared/footprint/Modules.pretty")(options "")(descr ""))
(lib (name "4435")(type "KiCad")(uri "${KIPRJMOD}/footprints/4435")(options "")(descr ""))
)

View File

@@ -1,182 +0,0 @@
(kicad_symbol_lib (version 20211014) (generator kicad_symbol_editor)
(symbol "AO4435" (pin_names (offset 1.016)) (in_bom yes) (on_board yes)
(property "Reference" "Q" (id 0) (at -11.43 8.89 0)
(effects (font (size 1.27 1.27)) (justify bottom left))
)
(property "Value" "AO4435" (id 1) (at -11.43 6.35 0)
(effects (font (size 1.27 1.27)) (justify bottom left))
)
(property "Footprint" "AO4435:SOIC127P600X175-8N" (id 2) (at 0 0 0)
(effects (font (size 1.27 1.27)) (justify bottom) hide)
)
(property "MF" "Alpha &" (id 4) (at 0 0 0)
(effects (font (size 1.27 1.27)) (justify bottom) hide)
)
(property "Description" "\n \n P-Channel 30 V 10.5A (Ta) 3.1W (Ta) Surface Mount 8-SOIC\n \n" (id 5) (at 0 0 0)
(effects (font (size 1.27 1.27)) (justify bottom) hide)
)
(property "Package" "SOIC-8 Alpha & Omega Semiconductor" (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/AO4435/Alpha/view-part/?ref=eda" (id 8) (at 0 0 0)
(effects (font (size 1.27 1.27)) (justify bottom) hide)
)
(property "STANDARD" "IPC 7351B" (id 9) (at 0 0 0)
(effects (font (size 1.27 1.27)) (justify bottom) hide)
)
(property "SnapEDA_Link" "https://www.snapeda.com/parts/AO4435/Alpha/view-part/?ref=snap" (id 10) (at 0 0 0)
(effects (font (size 1.27 1.27)) (justify bottom) hide)
)
(property "MP" "AO4435" (id 11) (at 0 0 0)
(effects (font (size 1.27 1.27)) (justify bottom) hide)
)
(property "Availability" "In Stock" (id 12) (at 0 0 0)
(effects (font (size 1.27 1.27)) (justify bottom) hide)
)
(property "MANUFACTURER" "ALPHA & OMEGA SEMICONDUCTOR" (id 13) (at 0 0 0)
(effects (font (size 1.27 1.27)) (justify bottom) hide)
)
(symbol "AO4435_0_0"
(polyline
(pts (xy -1.778 0.762) (xy -1.778 0.0)) (stroke (width 0.254))
)
(polyline
(pts (xy -1.778 0.0) (xy -1.778 -0.762)) (stroke (width 0.254))
)
(polyline
(pts (xy -1.778 3.175) (xy -1.778 2.54)) (stroke (width 0.254))
)
(polyline
(pts (xy -1.778 2.54) (xy -1.778 1.905)) (stroke (width 0.254))
)
(polyline
(pts (xy -1.778 0.0) (xy 0.0 0.0)) (stroke (width 0.1524))
)
(polyline
(pts (xy 0.0 0.0) (xy 0.0 -2.54)) (stroke (width 0.1524))
)
(polyline
(pts (xy -1.778 -1.905) (xy -1.778 -2.54)) (stroke (width 0.254))
)
(polyline
(pts (xy -1.778 -2.54) (xy -1.778 -3.175)) (stroke (width 0.254))
)
(polyline
(pts (xy -2.54 2.54) (xy -2.54 -2.54)) (stroke (width 0.254))
)
(polyline
(pts (xy 0.0 -2.54) (xy -1.778 -2.54)) (stroke (width 0.1524))
)
(polyline
(pts (xy 1.27 2.54) (xy 1.27 -0.508)) (stroke (width 0.1524))
)
(polyline
(pts (xy 1.27 -0.508) (xy 1.27 -2.54)) (stroke (width 0.1524))
)
(polyline
(pts (xy 0.0 -2.54) (xy 1.27 -2.54)) (stroke (width 0.1524))
)
(polyline
(pts (xy -1.778 2.54) (xy 1.27 2.54)) (stroke (width 0.1524))
)
(polyline
(pts (xy 0.762 -0.508) (xy 1.27 -0.508)) (stroke (width 0.1524))
)
(polyline
(pts (xy 1.27 -0.508) (xy 1.778 -0.508)) (stroke (width 0.1524))
)
(circle (center 0.0 -2.54) (radius 0.3592)
(stroke (width 0.0)) (fill (type none))
)
(circle (center 0.0 2.54) (radius 0.3592)
(stroke (width 0.0)) (fill (type none))
)
(polyline
(pts
(xy 1.27 -0.508)
(xy 1.778 0.254)
(xy 0.762 0.254)
(xy 1.27 -0.508)
)
(stroke (width 0.1524)) (fill (type outline))
)
(polyline
(pts
(xy 0.0 0.0)
(xy -1.016 -0.762)
(xy -1.016 0.762)
(xy 0.0 0.0)
)
(stroke (width 0.1524)) (fill (type outline))
)
(pin passive line (at 0.0 -5.08 90.0) (length 2.54)
(name "~"
(effects (font (size 1.016 1.016)))
)
(number "1"
(effects (font (size 1.016 1.016)))
)
)
(pin passive line (at 0.0 -5.08 90.0) (length 2.54)
(name "~"
(effects (font (size 1.016 1.016)))
)
(number "2"
(effects (font (size 1.016 1.016)))
)
)
(pin passive line (at 0.0 -5.08 90.0) (length 2.54)
(name "~"
(effects (font (size 1.016 1.016)))
)
(number "3"
(effects (font (size 1.016 1.016)))
)
)
(pin passive line (at -5.08 -2.54 0) (length 2.54)
(name "~"
(effects (font (size 1.016 1.016)))
)
(number "4"
(effects (font (size 1.016 1.016)))
)
)
(pin passive line (at 0.0 5.08 270.0) (length 2.54)
(name "~"
(effects (font (size 1.016 1.016)))
)
(number "5"
(effects (font (size 1.016 1.016)))
)
)
(pin passive line (at 0.0 5.08 270.0) (length 2.54)
(name "~"
(effects (font (size 1.016 1.016)))
)
(number "6"
(effects (font (size 1.016 1.016)))
)
)
(pin passive line (at 0.0 5.08 270.0) (length 2.54)
(name "~"
(effects (font (size 1.016 1.016)))
)
(number "7"
(effects (font (size 1.016 1.016)))
)
)
(pin passive line (at 0.0 5.08 270.0) (length 2.54)
(name "~"
(effects (font (size 1.016 1.016)))
)
(number "8"
(effects (font (size 1.016 1.016)))
)
)
)
)
)

View File

@@ -1,5 +0,0 @@
(kicad_symbol_lib
(version 20241209)
(generator "kicad_symbol_editor")
(generator_version "9.0")
)

View File

@@ -1,5 +0,0 @@
(fp_lib_table
(version 7)
(lib (name "esp32c6")(type "KiCad")(uri "${KIPRJMOD}/../_Shared/footprint/esp32c6.pretty")(options "")(descr ""))
(lib (name "Modules")(type "KiCad")(uri "${KIPRJMOD}/../_Shared/footprint/Modules.pretty")(options "")(descr ""))
)

View File

@@ -1,8 +0,0 @@
(sym_lib_table
(version 7)
(lib (name "ESP32-C6-WROOM-1-N8")(type "KiCad")(uri "${KIPRJMOD}/../_Shared/symbols/ESP32-C6-WROOM-1-N8.kicad_sym")(options "")(descr ""))
(lib (name "Sensor_Module")(type "KiCad")(uri "${KIPRJMOD}/../_Shared/symbols/Sensor_Module.kicad_sym")(options "")(descr ""))
(lib (name "Pump")(type "KiCad")(uri "${KIPRJMOD}/../_Shared/symbols/Pump.kicad_sym")(options "")(descr ""))
(lib (name "Light")(type "KiCad")(uri "${KIPRJMOD}/../_Shared/symbols/Light.kicad_sym")(options "")(descr ""))
(lib (name "MPPT")(type "KiCad")(uri "${KIPRJMOD}/../_Shared/symbols/MPPT.kicad_sym")(options "")(descr ""))
)

View File

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

View File

@@ -1,5 +0,0 @@
(fp_lib_table
(version 7)
(lib (name "Modules")(type "KiCad")(uri "/home/empire/workspace/PlantCtrl/Hardware/_Shared/footprint/Modules.pretty")(options "")(descr ""))
(lib (name "4435")(type "KiCad")(uri "${KIPRJMOD}/hardware/footprints/4435")(options "")(descr ""))
)

View File

@@ -1,39 +0,0 @@
(footprint SOIC127P600X175-8N (layer F.Cu) (tedit 68EBCB5D)
(descr "")
(attr smd)
(fp_text reference REF** (at -0.268 -3.2064 0) (layer F.SilkS)
(effects (font (size 0.64 0.64) (thickness 0.15)))
)
(fp_text value SOIC127P600X175-8N (at 5.0152 3.1936 0) (layer F.Fab)
(effects (font (size 0.64 0.64) (thickness 0.15)))
)
(pad 1 smd roundrect (roundrect_rratio 0.25) (at -2.475 -1.905) (size 1.97 0.6) (layers F.Cu F.Mask F.Paste) (solder_mask_margin 0.102))
(pad 2 smd roundrect (roundrect_rratio 0.25) (at -2.475 -0.635) (size 1.97 0.6) (layers F.Cu F.Mask F.Paste) (solder_mask_margin 0.102))
(pad 3 smd roundrect (roundrect_rratio 0.25) (at -2.475 0.635) (size 1.97 0.6) (layers F.Cu F.Mask F.Paste) (solder_mask_margin 0.102))
(pad 4 smd roundrect (roundrect_rratio 0.25) (at -2.475 1.905) (size 1.97 0.6) (layers F.Cu F.Mask F.Paste) (solder_mask_margin 0.102))
(pad 5 smd roundrect (roundrect_rratio 0.25) (at 2.475 1.905) (size 1.97 0.6) (layers F.Cu F.Mask F.Paste) (solder_mask_margin 0.102))
(pad 6 smd roundrect (roundrect_rratio 0.25) (at 2.475 0.635) (size 1.97 0.6) (layers F.Cu F.Mask F.Paste) (solder_mask_margin 0.102))
(pad 7 smd roundrect (roundrect_rratio 0.25) (at 2.475 -0.635) (size 1.97 0.6) (layers F.Cu F.Mask F.Paste) (solder_mask_margin 0.102))
(pad 8 smd roundrect (roundrect_rratio 0.25) (at 2.475 -1.905) (size 1.97 0.6) (layers F.Cu F.Mask F.Paste) (solder_mask_margin 0.102))
(fp_line (start -2.0 2.5) (end -2.0 -2.5) (layer F.Fab) (width 0.127))
(fp_line (start -2.0 -2.5) (end 2.0 -2.5) (layer F.Fab) (width 0.127))
(fp_line (start 2.0 -2.5) (end 2.0 2.5) (layer F.Fab) (width 0.127))
(fp_line (start 2.0 2.5) (end -2.0 2.5) (layer F.Fab) (width 0.127))
(fp_line (start -2.0 -2.519) (end 2.0 -2.519) (layer F.SilkS) (width 0.127))
(fp_line (start -2.0 2.519) (end 2.0 2.519) (layer F.SilkS) (width 0.127))
(fp_line (start -3.71 -2.46) (end -2.25 -2.46) (layer F.CrtYd) (width 0.05))
(fp_line (start -2.25 -2.46) (end -2.25 -2.75) (layer F.CrtYd) (width 0.05))
(fp_line (start -2.25 -2.75) (end 2.25 -2.75) (layer F.CrtYd) (width 0.05))
(fp_line (start 2.25 -2.75) (end 2.25 -2.46) (layer F.CrtYd) (width 0.05))
(fp_line (start 2.25 -2.46) (end 3.71 -2.46) (layer F.CrtYd) (width 0.05))
(fp_line (start 3.71 2.46) (end 2.25 2.46) (layer F.CrtYd) (width 0.05))
(fp_line (start 2.25 2.46) (end 2.25 2.75) (layer F.CrtYd) (width 0.05))
(fp_line (start 2.25 2.75) (end -2.25 2.75) (layer F.CrtYd) (width 0.05))
(fp_line (start -2.25 2.75) (end -2.25 2.46) (layer F.CrtYd) (width 0.05))
(fp_line (start -2.25 2.46) (end -3.71 2.46) (layer F.CrtYd) (width 0.05))
(fp_line (start -3.71 2.46) (end -3.71 -2.46) (layer F.CrtYd) (width 0.05))
(fp_line (start 3.71 -2.46) (end 3.71 2.46) (layer F.CrtYd) (width 0.05))
(fp_circle (center -4.1 -1.9) (end -4.0 -1.9) (layer F.SilkS) (width 0.2))
(fp_circle (center -4.1 -1.9) (end -4.0 -1.9) (layer F.Fab) (width 0.2))
)

View File

@@ -1,278 +0,0 @@
(kicad_symbol_lib
(version 20241209)
(generator "kicad_symbol_editor")
(generator_version "9.0")
(symbol "SL2300"
(pin_names
(offset 1.016)
)
(exclude_from_sim no)
(in_bom yes)
(on_board yes)
(property "Reference" "Q"
(at 0 0 0)
(effects
(font
(size 1.27 1.27)
)
)
)
(property "Value" "SL2300"
(at 7.62 0 0)
(effects
(font
(size 1.27 1.27)
)
)
)
(property "Footprint" ""
(at 0 0 0)
(effects
(font
(size 1.27 1.27)
)
(hide yes)
)
)
(property "Datasheet" ""
(at 0 0 0)
(effects
(font
(size 1.27 1.27)
)
(hide yes)
)
)
(property "Description" ""
(at 0 0 0)
(effects
(font
(size 1.27 1.27)
)
(hide yes)
)
)
(symbol "SL2300_1_1"
(polyline
(pts
(xy -1.016 1.905) (xy -1.016 -1.905)
)
(stroke
(width 0.254)
(type solid)
)
(fill
(type none)
)
)
(polyline
(pts
(xy -1.016 0) (xy -3.81 0)
)
(stroke
(width 0)
(type solid)
)
(fill
(type none)
)
)
(polyline
(pts
(xy -0.508 2.286) (xy -0.508 1.27)
)
(stroke
(width 0.254)
(type solid)
)
(fill
(type none)
)
)
(polyline
(pts
(xy -0.508 0.508) (xy -0.508 -0.508)
)
(stroke
(width 0.254)
(type solid)
)
(fill
(type none)
)
)
(polyline
(pts
(xy -0.508 -1.27) (xy -0.508 -2.286)
)
(stroke
(width 0.254)
(type solid)
)
(fill
(type none)
)
)
(polyline
(pts
(xy -0.508 -1.778) (xy 2.032 -1.778) (xy 2.032 1.778) (xy -0.508 1.778)
)
(stroke
(width 0)
(type solid)
)
(fill
(type none)
)
)
(polyline
(pts
(xy -0.254 0) (xy 0.762 0.381) (xy 0.762 -0.381) (xy -0.254 0)
)
(stroke
(width 0)
(type solid)
)
(fill
(type outline)
)
)
(circle
(center 0.381 0)
(radius 2.794)
(stroke
(width 0.254)
(type solid)
)
(fill
(type none)
)
)
(polyline
(pts
(xy 1.27 2.54) (xy 1.27 1.778)
)
(stroke
(width 0)
(type solid)
)
(fill
(type none)
)
)
(circle
(center 1.27 1.778)
(radius 0.254)
(stroke
(width 0)
(type solid)
)
(fill
(type outline)
)
)
(circle
(center 1.27 -1.778)
(radius 0.254)
(stroke
(width 0)
(type solid)
)
(fill
(type outline)
)
)
(polyline
(pts
(xy 1.27 -2.54) (xy 1.27 0) (xy -0.508 0)
)
(stroke
(width 0)
(type solid)
)
(fill
(type none)
)
)
(polyline
(pts
(xy 1.524 0.508) (xy 1.651 0.381) (xy 2.413 0.381) (xy 2.54 0.254)
)
(stroke
(width 0)
(type solid)
)
(fill
(type none)
)
)
(polyline
(pts
(xy 2.032 0.381) (xy 1.651 -0.254) (xy 2.413 -0.254) (xy 2.032 0.381)
)
(stroke
(width 0)
(type solid)
)
(fill
(type none)
)
)
(pin input line
(at -6.35 0 0)
(length 2.54)
(name "G"
(effects
(font
(size 1.27 1.27)
)
)
)
(number "1"
(effects
(font
(size 1.27 1.27)
)
)
)
)
(pin passive line
(at 1.27 5.08 270)
(length 2.54)
(name "D"
(effects
(font
(size 1.27 1.27)
)
)
)
(number "3"
(effects
(font
(size 1.27 1.27)
)
)
)
)
(pin passive line
(at 1.27 -5.08 90)
(length 2.54)
(name "S"
(effects
(font
(size 1.27 1.27)
)
)
)
(number "2"
(effects
(font
(size 1.27 1.27)
)
)
)
)
)
(embedded_fonts no)
)
)

View File

@@ -1,5 +0,0 @@
(sym_lib_table
(version 7)
(lib (name "Pump")(type "KiCad")(uri "/home/empire/workspace/PlantCtrl/Hardware/_Shared/symbols/Pump.kicad_sym")(options "")(descr ""))
(lib (name "SL2300")(type "KiCad")(uri "${KIPRJMOD}/hardware/symbols/SL2300.kicad_sym")(options "")(descr ""))
)

File diff suppressed because it is too large Load Diff

View File

@@ -1,175 +0,0 @@
(kicad_symbol_lib
(version 20241209)
(generator "kicad_symbol_editor")
(generator_version "9.0")
(symbol "LightPowerSupply"
(exclude_from_sim no)
(in_bom yes)
(on_board yes)
(property "Reference" "U"
(at 0 0 0)
(effects
(font
(size 1.27 1.27)
)
)
)
(property "Value" ""
(at 0 0 0)
(effects
(font
(size 1.27 1.27)
)
)
)
(property "Footprint" ""
(at 0 0 0)
(effects
(font
(size 1.27 1.27)
)
(hide yes)
)
)
(property "Datasheet" ""
(at 0 0 0)
(effects
(font
(size 1.27 1.27)
)
(hide yes)
)
)
(property "Description" ""
(at 0 0 0)
(effects
(font
(size 1.27 1.27)
)
(hide yes)
)
)
(symbol "LightPowerSupply_1_1"
(rectangle
(start -6.35 -1.27)
(end 6.35 -19.05)
(stroke
(width 0)
(type solid)
)
(fill
(type background)
)
)
(pin input line
(at -8.89 -2.54 0)
(length 2.54)
(name "LED_ENABLE"
(effects
(font
(size 1.27 1.27)
)
)
)
(number "1"
(effects
(font
(size 1.27 1.27)
)
)
)
)
(pin power_in line
(at -8.89 -5.08 0)
(length 2.54)
(name "VBAT"
(effects
(font
(size 1.27 1.27)
)
)
)
(number "2"
(effects
(font
(size 1.27 1.27)
)
)
)
)
(pin input line
(at -8.89 -7.62 0)
(length 2.54)
(name "GND"
(effects
(font
(size 1.27 1.27)
)
)
)
(number "3"
(effects
(font
(size 1.27 1.27)
)
)
)
)
(pin input line
(at -8.89 -10.16 0)
(length 2.54)
(name "GND"
(effects
(font
(size 1.27 1.27)
)
)
)
(number "4"
(effects
(font
(size 1.27 1.27)
)
)
)
)
(pin input line
(at -8.89 -12.7 0)
(length 2.54)
(name "GND"
(effects
(font
(size 1.27 1.27)
)
)
)
(number "5"
(effects
(font
(size 1.27 1.27)
)
)
)
)
(pin input line
(at -8.89 -15.24 0)
(length 2.54)
(name "GND"
(effects
(font
(size 1.27 1.27)
)
)
)
(number "6"
(effects
(font
(size 1.27 1.27)
)
)
)
)
)
(embedded_fonts no)
)
)

View File

@@ -1,211 +0,0 @@
(kicad_symbol_lib
(version 20241209)
(generator "kicad_symbol_editor")
(generator_version "9.0")
(symbol "Pump_Output"
(exclude_from_sim no)
(in_bom yes)
(on_board yes)
(property "Reference" "U"
(at -6.35 15.24 0)
(effects
(font
(size 1.27 1.27)
)
)
)
(property "Value" ""
(at 0 0 0)
(effects
(font
(size 1.27 1.27)
)
)
)
(property "Footprint" ""
(at 0 0 0)
(effects
(font
(size 1.27 1.27)
)
(hide yes)
)
)
(property "Datasheet" ""
(at 0 0 0)
(effects
(font
(size 1.27 1.27)
)
(hide yes)
)
)
(property "Description" ""
(at 0 0 0)
(effects
(font
(size 1.27 1.27)
)
(hide yes)
)
)
(symbol "Pump_Output_1_1"
(rectangle
(start -12.7 12.7)
(end 10.16 -15.24)
(stroke
(width 0)
(type solid)
)
(fill
(type background)
)
)
(pin bidirectional line
(at -15.24 1.27 0)
(length 2.54)
(name "SCL"
(effects
(font
(size 1.27 1.27)
)
)
)
(number "5"
(effects
(font
(size 1.27 1.27)
)
)
)
)
(pin bidirectional line
(at -15.24 -1.27 0)
(length 2.54)
(name "SDA"
(effects
(font
(size 1.27 1.27)
)
)
)
(number "6"
(effects
(font
(size 1.27 1.27)
)
)
)
)
(pin power_in line
(at -3.81 15.24 270)
(length 2.54)
(name "3V3"
(effects
(font
(size 1.27 1.27)
)
)
)
(number "7"
(effects
(font
(size 1.27 1.27)
)
)
)
)
(pin power_in line
(at -1.27 15.24 270)
(length 2.54)
(name "VCC"
(effects
(font
(size 1.27 1.27)
)
)
)
(number "8"
(effects
(font
(size 1.27 1.27)
)
)
)
)
(pin power_in line
(at -1.27 -17.78 90)
(length 2.54)
(name "GND"
(effects
(font
(size 1.27 1.27)
)
)
)
(number "1"
(effects
(font
(size 1.27 1.27)
)
)
)
)
(pin power_in line
(at 1.27 -17.78 90)
(length 2.54)
(name "GND"
(effects
(font
(size 1.27 1.27)
)
)
)
(number "2"
(effects
(font
(size 1.27 1.27)
)
)
)
)
(pin power_in line
(at 3.81 -17.78 90)
(length 2.54)
(name "GND"
(effects
(font
(size 1.27 1.27)
)
)
)
(number "3"
(effects
(font
(size 1.27 1.27)
)
)
)
)
(pin power_in line
(at 6.35 -17.78 90)
(length 2.54)
(name "GND"
(effects
(font
(size 1.27 1.27)
)
)
)
(number "4"
(effects
(font
(size 1.27 1.27)
)
)
)
)
)
(embedded_fonts no)
)
)

View File

@@ -1,265 +0,0 @@
(kicad_symbol_lib
(version 20241209)
(generator "kicad_symbol_editor")
(generator_version "9.0")
(symbol "sensor"
(exclude_from_sim no)
(in_bom yes)
(on_board yes)
(property "Reference" "U"
(at 0 0 0)
(effects
(font
(size 1.27 1.27)
)
)
)
(property "Value" ""
(at 0 0 0)
(effects
(font
(size 1.27 1.27)
)
)
)
(property "Footprint" ""
(at 0 0 0)
(effects
(font
(size 1.27 1.27)
)
(hide yes)
)
)
(property "Datasheet" ""
(at 0 0 0)
(effects
(font
(size 1.27 1.27)
)
(hide yes)
)
)
(property "Description" ""
(at 0 0 0)
(effects
(font
(size 1.27 1.27)
)
(hide yes)
)
)
(symbol "sensor_1_1"
(rectangle
(start -5.08 -1.27)
(end 5.08 -30.48)
(stroke
(width 0)
(type solid)
)
(fill
(type background)
)
)
(pin power_in line
(at 7.62 -2.54 180)
(length 2.54)
(name "3V3"
(effects
(font
(size 1.27 1.27)
)
)
)
(number "1"
(effects
(font
(size 1.27 1.27)
)
)
)
)
(pin power_in line
(at 7.62 -5.08 180)
(length 2.54)
(name "VBAT"
(effects
(font
(size 1.27 1.27)
)
)
)
(number "2"
(effects
(font
(size 1.27 1.27)
)
)
)
)
(pin power_in line
(at 7.62 -7.62 180)
(length 2.54)
(name "SDA"
(effects
(font
(size 1.27 1.27)
)
)
)
(number "3"
(effects
(font
(size 1.27 1.27)
)
)
)
)
(pin power_in line
(at 7.62 -10.16 180)
(length 2.54)
(name "SCL"
(effects
(font
(size 1.27 1.27)
)
)
)
(number "4"
(effects
(font
(size 1.27 1.27)
)
)
)
)
(pin output line
(at 7.62 -12.7 180)
(length 2.54)
(name "Signal"
(effects
(font
(size 1.27 1.27)
)
)
)
(number "5"
(effects
(font
(size 1.27 1.27)
)
)
)
)
(pin output line
(at 7.62 -15.24 180)
(length 2.54)
(name "GND"
(effects
(font
(size 1.27 1.27)
)
)
)
(number "8"
(effects
(font
(size 1.27 1.27)
)
)
)
)
(pin output line
(at 7.62 -17.78 180)
(length 2.54)
(name "GND"
(effects
(font
(size 1.27 1.27)
)
)
)
(number "9"
(effects
(font
(size 1.27 1.27)
)
)
)
)
(pin output line
(at 7.62 -20.32 180)
(length 2.54)
(name "GND"
(effects
(font
(size 1.27 1.27)
)
)
)
(number "10"
(effects
(font
(size 1.27 1.27)
)
)
)
)
(pin output line
(at 7.62 -22.86 180)
(length 2.54)
(name "GND"
(effects
(font
(size 1.27 1.27)
)
)
)
(number "11"
(effects
(font
(size 1.27 1.27)
)
)
)
)
(pin output line
(at 7.62 -25.4 180)
(length 2.54)
(name "GND"
(effects
(font
(size 1.27 1.27)
)
)
)
(number "12"
(effects
(font
(size 1.27 1.27)
)
)
)
)
(pin output line
(at 7.62 -27.94 180)
(length 2.54)
(name "GND"
(effects
(font
(size 1.27 1.27)
)
)
)
(number "13"
(effects
(font
(size 1.27 1.27)
)
)
)
)
)
(embedded_fonts no)
)
)

View File

@@ -1,247 +0,0 @@
(kicad_symbol_lib
(version 20241209)
(generator "kicad_symbol_editor")
(generator_version "9.0")
(symbol "sensor"
(exclude_from_sim no)
(in_bom yes)
(on_board yes)
(property "Reference" "U"
(at 0 0 0)
(effects
(font
(size 1.27 1.27)
)
)
)
(property "Value" ""
(at 0 0 0)
(effects
(font
(size 1.27 1.27)
)
)
)
(property "Footprint" ""
(at 0 0 0)
(effects
(font
(size 1.27 1.27)
)
(hide yes)
)
)
(property "Datasheet" ""
(at 0 0 0)
(effects
(font
(size 1.27 1.27)
)
(hide yes)
)
)
(property "Description" ""
(at 0 0 0)
(effects
(font
(size 1.27 1.27)
)
(hide yes)
)
)
(symbol "sensor_1_1"
(rectangle
(start -5.08 -1.27)
(end 5.08 -26.67)
(stroke
(width 0)
(type solid)
)
(fill
(type background)
)
)
(pin power_in line
(at 7.62 -2.54 180)
(length 2.54)
(name "3V3"
(effects
(font
(size 1.27 1.27)
)
)
)
(number "1"
(effects
(font
(size 1.27 1.27)
)
)
)
)
(pin power_in line
(at 7.62 -5.08 180)
(length 2.54)
(name "VBAT"
(effects
(font
(size 1.27 1.27)
)
)
)
(number "2"
(effects
(font
(size 1.27 1.27)
)
)
)
)
(pin power_in line
(at 7.62 -7.62 180)
(length 2.54)
(name "SDA"
(effects
(font
(size 1.27 1.27)
)
)
)
(number "3"
(effects
(font
(size 1.27 1.27)
)
)
)
)
(pin power_in line
(at 7.62 -10.16 180)
(length 2.54)
(name "SCL"
(effects
(font
(size 1.27 1.27)
)
)
)
(number "4"
(effects
(font
(size 1.27 1.27)
)
)
)
)
(pin output line
(at 7.62 -12.7 180)
(length 2.54)
(name "GND"
(effects
(font
(size 1.27 1.27)
)
)
)
(number "8"
(effects
(font
(size 1.27 1.27)
)
)
)
)
(pin output line
(at 7.62 -15.24 180)
(length 2.54)
(name "GND"
(effects
(font
(size 1.27 1.27)
)
)
)
(number "9"
(effects
(font
(size 1.27 1.27)
)
)
)
)
(pin output line
(at 7.62 -17.78 180)
(length 2.54)
(name "GND"
(effects
(font
(size 1.27 1.27)
)
)
)
(number "10"
(effects
(font
(size 1.27 1.27)
)
)
)
)
(pin output line
(at 7.62 -20.32 180)
(length 2.54)
(name "GND"
(effects
(font
(size 1.27 1.27)
)
)
)
(number "11"
(effects
(font
(size 1.27 1.27)
)
)
)
)
(pin output line
(at 7.62 -22.86 180)
(length 2.54)
(name "GND"
(effects
(font
(size 1.27 1.27)
)
)
)
(number "12"
(effects
(font
(size 1.27 1.27)
)
)
)
)
(pin output line
(at 7.62 -25.4 180)
(length 2.54)
(name "GND"
(effects
(font
(size 1.27 1.27)
)
)
)
(number "13"
(effects
(font
(size 1.27 1.27)
)
)
)
)
)
(embedded_fonts no)
)
)

View File

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

View File

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

View File

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

View File

@@ -1,4 +0,0 @@
(fp_lib_table
(version 7)
(lib (name "Sensor")(type "KiCad")(uri "/home/empire/workspace/PlantCtrl/board/modules/Sensors/Sensors/Sensor.pretty")(options "")(descr ""))
)

View File

@@ -1,32 +0,0 @@
;;; .doomrc --- doom runtime config -*- mode: emacs-lisp; lexical-binding: t; -*-
;;; Commentary:
;;; Code:
(require 'doom) ; be silent, byte-compiler
(after! dape
(add-to-list
'dape-configs
`(gdb-dap-openocd
ensure (lambda (config)
(dape-ensure-command config)
(let* ((default-directory
(or (dape-config-get config 'command-cwd)
default-directory))
(command (dape-config-get config 'command))
(output (shell-command-to-string (format "%s --version" command)))
(version (save-match-data
(when (string-match "GNU gdb \\(?:(.*) \\)?\\([0-9.]+\\)" output)
(string-to-number (match-string 1 output))))))
(unless (>= version 14.1)
(user-error "Requires gdb version >= 14.1"))))
modes ()
command-cwd dape-command-cwd
command "gdb"
command-args ("--interpreter=dap")
:request nil
:program nil
:args []
:stopAtBeginningOfMainSubprogram nil))
)
;;; .doomrc ends here

View File

@@ -1,11 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="EMPTY_MODULE" version="4">
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
<excludeFolder url="file://$MODULE_DIR$/target" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>

View File

@@ -1,8 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/ch32-sensor.iml" filepath="$PROJECT_DIR$/.idea/ch32-sensor.iml" />
</modules>
</component>
</project>

View File

@@ -1,7 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$/../../../.." vcs="Git" />
<mapping directory="$PROJECT_DIR$/../../../../website/themes/blowfish" vcs="Git" />
</component>
</project>

View File

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

View File

@@ -1,133 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 13.0.1, SVG Export Plug-In . SVG Version: 6.00 Build 14948) -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="1325px"
height="675px" viewBox="0 0 1325 675" enable-background="new 0 0 1325 675" xml:space="preserve">
<g id="back">
<rect width="1325" height="675"/>
</g>
<g id="logo">
<g>
<path fill="#FFFFFF" d="M109.818,477.621H75.827v34.299h33.992v-11.638l12.612,2.614v6.409c0,4.956-0.932,8.331-2.794,10.126
c-1.864,1.794-5.409,2.691-10.639,2.691H76.647c-5.264,0-8.818-0.897-10.664-2.691c-1.846-1.795-2.769-5.17-2.769-10.126v-28.558
c0-4.922,0.923-8.287,2.769-10.1c1.846-1.812,5.4-2.717,10.664-2.717h32.351c5.229,0,8.775,0.905,10.639,2.717
c1.862,1.813,2.794,5.178,2.794,10.1v5.127l-12.612,2V477.621z"/>
<path fill="#FFFFFF" d="M148.424,522.123v-54.191h12.612v21.02h39.375v-21.02h12.715v54.191h-12.715v-23.328h-39.375v23.328
H148.424z"/>
<path fill="#FFFFFF" d="M230.456,522.123l30.659-54.191h12.151l31.069,54.191h-14.15l-6.511-11.844h-34.351l-6.306,11.844H230.456
z M254.706,500.487h23.738l-11.689-22.046L254.706,500.487z"/>
<path fill="#FFFFFF" d="M318.793,480.748c0-4.922,0.923-8.287,2.769-10.1c1.846-1.812,5.4-2.717,10.664-2.717h39.888
c5.263,0,8.818,0.905,10.664,2.717c1.846,1.813,2.769,5.178,2.769,10.1v28.558c0,4.956-0.923,8.331-2.769,10.126
c-1.846,1.794-5.401,2.691-10.664,2.691h-39.888c-5.264,0-8.818-0.897-10.664-2.691c-1.846-1.795-2.769-5.17-2.769-10.126V480.748
z M331.405,511.92h41.58v-34.299h-41.58V511.92z"/>
<path fill="#FFFFFF" d="M459.066,477.416h-35.479v11.639h33.787c5.297,0,8.877,0.905,10.741,2.717
c1.863,1.813,2.794,5.178,2.794,10.1v7.435c0,4.922-0.932,8.289-2.794,10.101c-1.863,1.812-5.443,2.717-10.741,2.717h-33.479
c-5.298,0-8.878-0.905-10.741-2.717c-1.863-1.812-2.794-5.179-2.794-10.101v-1.487l11.125-2.307v6.408h38.298v-12.355h-33.787
c-5.264,0-8.818-0.905-10.664-2.717s-2.769-5.179-2.769-10.101v-5.999c0-4.922,0.923-8.287,2.769-10.1
c1.846-1.812,5.4-2.717,10.664-2.717h30.762c5.092,0,8.604,0.871,10.536,2.614c1.931,1.743,2.897,4.837,2.897,9.28v1.127
l-11.125,2.615V477.416z"/>
<path fill="#FFFFFF" d="M537.714,522.123v-54.191h12.612v54.191H537.714z"/>
<path fill="#FFFFFF" d="M577.909,522.123v-54.191h10.203l25.532,32.761l24.968-32.761h10.1v54.191h-11.689v-33.479
c0-0.649,0.042-1.47,0.128-2.461c0.084-0.991,0.196-2.103,0.333-3.333c-0.684,1.402-1.282,2.556-1.794,3.461
c-0.513,0.906-0.974,1.649-1.384,2.229l-20.354,27.07h-2.512l-20.405-27.172c-0.752-1.025-1.393-1.991-1.923-2.897
c-0.53-0.905-0.966-1.785-1.307-2.64c0.103,1.127,0.179,2.152,0.231,3.076c0.051,0.922,0.077,1.812,0.077,2.666v33.479H577.909z"
/>
<path fill="#FFFFFF" d="M714.748,480.748c0-4.922,0.923-8.287,2.769-10.1c1.846-1.812,5.4-2.717,10.664-2.717h39.888
c5.263,0,8.818,0.905,10.664,2.717c1.846,1.813,2.769,5.178,2.769,10.1v28.558c0,4.956-0.923,8.331-2.769,10.126
c-1.846,1.794-5.401,2.691-10.664,2.691h-5.845l6.819,13.945h-12.51l-6.102-13.945h-22.251c-5.264,0-8.818-0.897-10.664-2.691
c-1.846-1.795-2.769-5.17-2.769-10.126V480.748z M727.36,511.92h18.56l-5.024-11.74l9.126-3.691l7.332,15.432h11.586v-34.299
H727.36V511.92z"/>
<path fill="#FFFFFF" d="M820.67,467.932v43.988h35.939v-43.988h12.562v41.374c0,4.956-0.923,8.331-2.769,10.126
c-1.846,1.794-5.401,2.691-10.664,2.691H821.49c-5.229,0-8.776-0.897-10.639-2.691c-1.863-1.795-2.794-5.17-2.794-10.126v-41.374
H820.67z"/>
<path fill="#FFFFFF" d="M884.244,522.123l30.659-54.191h12.151l31.068,54.191h-14.15l-6.511-11.844h-34.351l-6.307,11.844H884.244
z M908.494,500.487h23.738l-11.689-22.046L908.494,500.487z"/>
<path fill="#FFFFFF" d="M975.556,522.123v-54.191h34.863c9.946,0,17.594,2.324,22.943,6.973
c5.349,4.648,8.023,11.245,8.023,19.789c0,5.06-1.043,9.613-3.128,13.664c-2.085,4.05-5.023,7.237-8.818,9.562
c-2.357,1.47-5.289,2.538-8.792,3.204c-3.504,0.667-7.99,1-13.458,1H975.556z M988.168,511.92h16.304
c8.682,0,14.834-1.35,18.457-4.05c3.622-2.699,5.435-7.16,5.435-13.382c0-5.537-1.505-9.731-4.512-12.586
c-3.008-2.854-7.452-4.281-13.33-4.281h-22.354V511.92z"/>
<path fill="#FFFFFF" d="M1064.509,522.123v-54.191h46.348c4.307,0,7.425,0.829,9.356,2.486c1.931,1.658,2.896,4.332,2.896,8.023
v12.715c0,3.658-0.966,6.324-2.896,7.998c-1.932,1.675-5.05,2.512-9.356,2.512h-7.075l25.02,20.457h-18.098l-21.842-20.457
h-11.945v20.457H1064.509z M1105.268,477.313h-28.352v15.073h28.352c2.188,0,3.683-0.315,4.486-0.948
c0.803-0.632,1.205-1.751,1.205-3.358v-6.512c0-1.571-0.402-2.674-1.205-3.307C1108.95,477.63,1107.455,477.313,1105.268,477.313z
"/>
<path fill="#FFFFFF" d="M1134.594,522.123l30.659-54.191h12.151l31.068,54.191h-14.15l-6.511-11.844h-34.351l-6.307,11.844
H1134.594z M1158.844,500.487h23.738l-11.689-22.046L1158.844,500.487z"/>
<path fill="#FFFFFF" d="M1244.003,477.621v44.502h-12.612v-44.502h-24.865v-9.689h62.445v9.689H1244.003z"/>
<path fill="#FFFFFF" d="M71.188,626.523l-17.29-44.902h10.79l11.385,30.119l11.937-30.119h7.307l12.149,30.119l10.875-30.119
h9.346l-17.332,44.902h-7.052l-12.362-31.053l-12.532,31.053H71.188z"/>
<path fill="#FFFFFF" d="M151.73,626.523l-17.29-44.902h10.79l11.385,30.119l11.937-30.119h7.307l12.149,30.119l10.875-30.119
h9.346l-17.332,44.902h-7.052l-12.362-31.053l-12.532,31.053H151.73z"/>
<path fill="#FFFFFF" d="M232.273,626.523l-17.29-44.902h10.79l11.385,30.119l11.937-30.119h7.307l12.149,30.119l10.875-30.119
h9.346l-17.332,44.902h-7.052l-12.362-31.053l-12.532,31.053H232.273z"/>
<path fill="#FFFFFF" d="M303.301,613.057h1.572c0.765,0,1.302,0.22,1.614,0.659c0.312,0.439,0.467,1.281,0.467,2.527v7.052
c0,1.359-0.17,2.237-0.51,2.634s-1.388,0.595-3.144,0.595h-7.774c-0.765,0-1.303-0.227-1.614-0.68
c-0.312-0.453-0.467-1.303-0.467-2.549v-7.052c0-1.331,0.17-2.194,0.51-2.591s1.387-0.596,3.144-0.596H303.301z"/>
<path fill="#FFFFFF" d="M363.75,589.65h-28.165v28.42h28.165v-9.644l10.45,2.167v5.31c0,4.106-0.772,6.903-2.315,8.39
c-1.544,1.487-4.482,2.23-8.815,2.23h-26.805c-4.361,0-7.307-0.743-8.836-2.23c-1.529-1.486-2.294-4.283-2.294-8.39v-23.661
c0-4.078,0.765-6.868,2.294-8.369s4.475-2.252,8.836-2.252h26.805c4.333,0,7.271,0.751,8.815,2.252
c1.543,1.501,2.315,4.291,2.315,8.369v4.248l-10.45,1.656V589.65z"/>
<path fill="#FFFFFF" d="M433.418,589.65h-28.165v28.42h28.165v-9.644l10.45,2.167v5.31c0,4.106-0.772,6.903-2.315,8.39
c-1.544,1.487-4.482,2.23-8.815,2.23h-26.805c-4.361,0-7.307-0.743-8.836-2.23c-1.529-1.486-2.294-4.283-2.294-8.39v-23.661
c0-4.078,0.765-6.868,2.294-8.369s4.475-2.252,8.836-2.252h26.805c4.333,0,7.271,0.751,8.815,2.252
c1.543,1.501,2.315,4.291,2.315,8.369v4.248l-10.45,1.656V589.65z"/>
<path fill="#FFFFFF" d="M503.086,589.65h-28.165v28.42h28.165v-9.644l10.45,2.167v5.31c0,4.106-0.772,6.903-2.315,8.39
c-1.544,1.487-4.482,2.23-8.815,2.23h-26.805c-4.361,0-7.307-0.743-8.836-2.23c-1.529-1.486-2.294-4.283-2.294-8.39v-23.661
c0-4.078,0.765-6.868,2.294-8.369s4.475-2.252,8.836-2.252h26.805c4.333,0,7.271,0.751,8.815,2.252
c1.543,1.501,2.315,4.291,2.315,8.369v4.248l-10.45,1.656V589.65z"/>
<path fill="#FFFFFF" d="M527.768,607.832v-8.963h20.263v8.963H527.768z"/>
<path fill="#FFFFFF" d="M563.706,626.523v-44.902h8.454l21.155,27.146l20.688-27.146h8.369v44.902h-9.686v-27.74
c0-0.537,0.035-1.217,0.106-2.039c0.071-0.82,0.163-1.741,0.276-2.761c-0.567,1.161-1.062,2.117-1.487,2.867
c-0.425,0.751-0.807,1.367-1.147,1.849l-16.865,22.43h-2.082l-16.907-22.516c-0.623-0.85-1.154-1.649-1.593-2.399
c-0.439-0.75-0.801-1.479-1.083-2.188c0.085,0.936,0.149,1.785,0.191,2.549c0.043,0.766,0.064,1.502,0.064,2.209v27.74H563.706z"
/>
<path fill="#FFFFFF" d="M635.753,626.523l25.403-44.902h10.068l25.742,44.902h-11.725l-5.395-9.813h-28.462l-5.225,9.813H635.753z
M655.846,608.597h19.668l-9.686-18.267L655.846,608.597z"/>
<path fill="#FFFFFF" d="M711.326,626.523v-44.902h7.137l31.605,26.466c1.076,0.878,2.024,1.706,2.846,2.485s1.572,1.579,2.252,2.4
c-0.17-2.521-0.283-4.305-0.34-5.354c-0.057-1.047-0.085-1.854-0.085-2.421v-23.577h9.388v44.902h-7.137l-32.965-27.74
c-0.793-0.707-1.486-1.345-2.081-1.911s-1.147-1.147-1.657-1.741c0.142,1.388,0.248,2.697,0.319,3.929
c0.07,1.232,0.106,2.302,0.106,3.208v24.256H711.326z"/>
<path fill="#FFFFFF" d="M786.941,626.523v-44.902h7.137l31.605,26.466c1.076,0.878,2.024,1.706,2.846,2.485s1.572,1.579,2.252,2.4
c-0.17-2.521-0.283-4.305-0.34-5.354c-0.057-1.047-0.085-1.854-0.085-2.421v-23.577h9.388v44.902h-7.137l-32.965-27.74
c-0.793-0.707-1.486-1.345-2.081-1.911s-1.147-1.147-1.657-1.741c0.142,1.388,0.248,2.697,0.319,3.929
c0.07,1.232,0.106,2.302,0.106,3.208v24.256H786.941z"/>
<path fill="#FFFFFF" d="M862.642,626.523v-44.902h10.45v17.418h32.625v-17.418h10.535v44.902h-10.535v-19.328h-32.625v19.328
H862.642z"/>
<path fill="#FFFFFF" d="M939.19,626.523v-44.902h45.964v7.646h-35.514v10.025h20.73v7.646h-20.73v11.131h35.938v8.453H939.19z"/>
<path fill="#FFFFFF" d="M1004.866,626.523v-44.902h10.45v44.902H1004.866z"/>
<path fill="#FFFFFF" d="M1038.17,626.523v-44.902h8.453l21.156,27.146l20.688-27.146h8.369v44.902h-9.686v-27.74
c0-0.537,0.035-1.217,0.105-2.039c0.07-0.82,0.163-1.741,0.276-2.761c-0.566,1.161-1.062,2.117-1.487,2.867
c-0.424,0.751-0.807,1.367-1.146,1.849l-16.865,22.43h-2.081l-16.907-22.516c-0.623-0.85-1.154-1.649-1.593-2.399
c-0.439-0.75-0.801-1.479-1.083-2.188c0.084,0.936,0.148,1.785,0.19,2.549c0.043,0.766,0.063,1.502,0.063,2.209v27.74H1038.17z"/>
<path fill="#FFFFFF" d="M1124.83,613.057h1.572c0.764,0,1.302,0.22,1.613,0.659s0.468,1.281,0.468,2.527v7.052
c0,1.359-0.17,2.237-0.51,2.634s-1.388,0.595-3.144,0.595h-7.773c-0.766,0-1.304-0.227-1.615-0.68s-0.467-1.303-0.467-2.549
v-7.052c0-1.331,0.17-2.194,0.51-2.591s1.388-0.596,3.144-0.596H1124.83z"/>
<path fill="#FFFFFF" d="M1147.601,626.523v-44.902h28.887c8.241,0,14.577,1.927,19.01,5.777c4.433,3.853,6.648,9.318,6.648,16.398
c0,4.191-0.864,7.965-2.591,11.32c-1.729,3.356-4.164,5.997-7.307,7.923c-1.955,1.218-4.383,2.103-7.286,2.655
c-2.903,0.552-6.62,0.828-11.151,0.828H1147.601z M1158.051,618.07h13.509c7.193,0,12.291-1.119,15.293-3.356
s4.503-5.933,4.503-11.087c0-4.588-1.246-8.064-3.738-10.43c-2.492-2.364-6.174-3.547-11.045-3.547h-18.521V618.07z"/>
<path fill="#FFFFFF" d="M1221.304,626.523v-44.902h45.964v7.646h-35.514v10.025h20.73v7.646h-20.73v11.131h35.938v8.453H1221.304z
"/>
</g>
<g>
<polygon fill="#FFFFFF" points="1050.25,150.292 1050.25,100.292 1000.25,100.292 1000.25,49.792 799.75,49.792 799.75,100.25
749.75,100.25 749.75,349.791 799.75,349.791 799.75,400.292 1000.25,400.292 1000.25,349.791 1050.25,349.791 1050.25,300.292
950.25,300.292 950.25,349.791 849.75,349.791 849.75,100.292 950.25,100.292 950.25,150.292 "/>
</g>
<g>
<path fill="#FFFFFF" d="M1100.25,49.792v174.75h175V49.792H1100.25z M1251,124.042h-24.75v25h-50.75v24.75h75.5v25.125h-125.75
v-25.125h25.25v-24.75h25v-25h50.75v-25h-75.75v25h-25.25v-25h25.25v-25.25h75.75v25.25H1251V124.042z"/>
</g>
<g>
<polygon fill="#FFFFFF" points="700.25,150.292 700.25,100.292 650.25,100.292 650.25,49.792 449.75,49.792 449.75,100.25
399.75,100.25 399.75,349.791 449.75,349.791 449.75,400.292 650.25,400.292 650.25,349.791 700.25,349.791 700.25,300.292
600.25,300.292 600.25,349.791 499.75,349.791 499.75,100.292 600.25,100.292 600.25,150.292 "/>
</g>
<g>
<polygon fill="#FFFFFF" points="350.25,150.292 350.25,100.292 300.25,100.292 300.25,49.792 99.75,49.792 99.75,100.25
49.75,100.25 49.75,349.791 99.75,349.791 99.75,400.292 300.25,400.292 300.25,349.791 350.25,349.791 350.25,300.292
250.25,300.292 250.25,349.791 149.75,349.791 149.75,100.292 250.25,100.292 250.25,150.292 "/>
</g>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Binary file not shown.

Binary file not shown.

File diff suppressed because it is too large Load Diff

View File

@@ -1,131 +0,0 @@
{
"board": {
"active_layer": 0,
"active_layer_preset": "",
"auto_track_width": true,
"hidden_netclasses": [],
"hidden_nets": [],
"high_contrast_mode": 0,
"net_color_mode": 1,
"opacity": {
"images": 0.6,
"pads": 1.0,
"shapes": 1.0,
"tracks": 1.0,
"vias": 1.0,
"zones": 0.6
},
"selection_filter": {
"dimensions": true,
"footprints": true,
"graphics": true,
"keepouts": true,
"lockedItems": false,
"otherItems": true,
"pads": true,
"text": true,
"tracks": true,
"vias": true,
"zones": true
},
"visible_items": [
"vias",
"footprint_text",
"footprint_anchors",
"ratsnest",
"grid",
"footprints_front",
"footprints_back",
"footprint_values",
"footprint_references",
"tracks",
"drc_errors",
"drawing_sheet",
"bitmaps",
"pads",
"zones",
"drc_warnings",
"drc_exclusions",
"locked_item_shadows",
"conflict_shadows",
"shapes"
],
"visible_layers": "ffffffff_ffffffff_ffffffff_ffffffff",
"zone_display_mode": 0
},
"git": {
"repo_type": "",
"repo_username": "",
"ssh_key": ""
},
"meta": {
"filename": "sensor.kicad_prl",
"version": 5
},
"net_inspector_panel": {
"col_hidden": [
false,
false,
false,
false,
false,
false,
false,
false,
false,
false
],
"col_order": [
0,
1,
2,
3,
4,
5,
6,
7,
8,
9
],
"col_widths": [
0,
0,
0,
0,
0,
0,
0,
0,
0,
0
],
"custom_group_rules": [],
"expanded_rows": [],
"filter_by_net_name": true,
"filter_by_netclass": true,
"filter_text": "",
"group_by_constraint": false,
"group_by_netclass": false,
"show_unconnected_nets": false,
"show_zero_pad_nets": false,
"sort_ascending": true,
"sorting_column": 0
},
"open_jobsets": [],
"project": {
"files": []
},
"schematic": {
"selection_filter": {
"graphics": true,
"images": true,
"labels": true,
"lockedItems": false,
"otherItems": true,
"pins": true,
"symbols": true,
"text": true,
"wires": true
}
}
}

View File

@@ -1,877 +0,0 @@
{
"board": {
"3dviewports": [],
"design_settings": {
"defaults": {
"apply_defaults_to_fp_fields": false,
"apply_defaults_to_fp_shapes": false,
"apply_defaults_to_fp_text": false,
"board_outline_line_width": 0.05,
"copper_line_width": 0.2,
"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.05,
"dimension_precision": 4,
"dimension_units": 3,
"dimensions": {
"arrow_length": 1270000,
"extension_offset": 500000,
"keep_text_aligned": true,
"suppress_zeroes": true,
"text_position": 0,
"units_format": 0
},
"fab_line_width": 0.1,
"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.1,
"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.8,
"height": 1.27,
"width": 2.54
},
"silk_line_width": 0.1,
"silk_text_italic": false,
"silk_text_size_h": 1.0,
"silk_text_size_v": 1.0,
"silk_text_thickness": 0.1,
"silk_text_upright": false,
"zones": {
"min_clearance": 0.5
}
},
"diff_pair_dimensions": [],
"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",
"creepage": "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_filters_mismatch": "ignore",
"footprint_symbol_mismatch": "warning",
"footprint_type_mismatch": "ignore",
"hole_clearance": "error",
"hole_to_hole": "warning",
"holes_co_located": "warning",
"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",
"mirrored_text_on_front_layer": "warning",
"missing_courtyard": "ignore",
"missing_footprint": "warning",
"net_conflict": "warning",
"nonmirrored_text_on_back_layer": "warning",
"npth_inside_courtyard": "ignore",
"padstack": "warning",
"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_on_edge_cuts": "error",
"text_thickness": "warning",
"through_hole_pad_without_hole": "error",
"too_many_vias": "error",
"track_angle": "error",
"track_dangling": "warning",
"track_segment_length": "error",
"track_width": "error",
"tracks_crossing": "error",
"unconnected_items": "error",
"unresolved_variable": "error",
"via_dangling": "warning",
"zones_intersect": "error"
},
"rules": {
"max_error": 0.005,
"min_clearance": 0.0,
"min_connection": 0.0,
"min_copper_edge_clearance": 0.5,
"min_groove_width": 0.0,
"min_hole_clearance": 0.25,
"min_hole_to_hole": 0.25,
"min_microvia_diameter": 0.2,
"min_microvia_drill": 0.1,
"min_resolved_spokes": 2,
"min_silk_clearance": 0.0,
"min_text_height": 0.8,
"min_text_thickness": 0.08,
"min_through_hole_diameter": 0.3,
"min_track_width": 0.0,
"min_via_annular_width": 0.1,
"min_via_diameter": 0.5,
"solder_mask_to_copper_clearance": 0.0,
"use_height_for_length_calcs": true
},
"teardrop_options": [
{
"td_onpthpad": true,
"td_onroundshapesonly": false,
"td_onsmdpad": true,
"td_ontrackend": false,
"td_onvia": true
}
],
"teardrop_parameters": [
{
"td_allow_use_two_tracks": true,
"td_curve_segcount": 0,
"td_height_ratio": 1.0,
"td_length_ratio": 0.5,
"td_maxheight": 2.0,
"td_maxlen": 1.0,
"td_on_pad_in_zone": false,
"td_target_name": "td_round_shape",
"td_width_to_size_filter_ratio": 0.9
},
{
"td_allow_use_two_tracks": true,
"td_curve_segcount": 0,
"td_height_ratio": 1.0,
"td_length_ratio": 0.5,
"td_maxheight": 2.0,
"td_maxlen": 1.0,
"td_on_pad_in_zone": false,
"td_target_name": "td_rect_shape",
"td_width_to_size_filter_ratio": 0.9
},
{
"td_allow_use_two_tracks": true,
"td_curve_segcount": 0,
"td_height_ratio": 1.0,
"td_length_ratio": 0.5,
"td_maxheight": 2.0,
"td_maxlen": 1.0,
"td_on_pad_in_zone": false,
"td_target_name": "td_track_end",
"td_width_to_size_filter_ratio": 0.9
}
],
"track_widths": [],
"tuning_pattern_settings": {
"diff_pair_defaults": {
"corner_radius_percentage": 80,
"corner_style": 1,
"max_amplitude": 1.0,
"min_amplitude": 0.2,
"single_sided": false,
"spacing": 1.0
},
"diff_pair_skew_defaults": {
"corner_radius_percentage": 80,
"corner_style": 1,
"max_amplitude": 1.0,
"min_amplitude": 0.2,
"single_sided": false,
"spacing": 0.6
},
"single_track_defaults": {
"corner_radius_percentage": 80,
"corner_style": 1,
"max_amplitude": 1.0,
"min_amplitude": 0.2,
"single_sided": false,
"spacing": 0.6
}
},
"via_dimensions": [],
"zones_allow_external_fillets": false
},
"ipc2581": {
"dist": "",
"distpn": "",
"internal_id": "",
"mfg": "",
"mpn": ""
},
"layer_pairs": [],
"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_to_bus_conflict": "error",
"bus_to_net_conflict": "error",
"different_unit_footprint": "error",
"different_unit_net": "error",
"duplicate_reference": "error",
"duplicate_sheet_names": "error",
"endpoint_off_grid": "warning",
"extra_units": "error",
"footprint_filter": "ignore",
"footprint_link_issues": "warning",
"four_way_junction": "ignore",
"global_label_dangling": "warning",
"hier_label_mismatch": "error",
"label_dangling": "error",
"label_multiple_wires": "warning",
"lib_symbol_issues": "warning",
"lib_symbol_mismatch": "warning",
"missing_bidi_pin": "warning",
"missing_input_pin": "warning",
"missing_power_pin": "error",
"missing_unit": "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",
"same_local_global_label": "warning",
"similar_label_and_power": "warning",
"similar_labels": "warning",
"similar_power": "warning",
"simulation_model_issue": "ignore",
"single_global_label": "ignore",
"unannotated": "error",
"unconnected_wire_endpoint": "warning",
"undefined_netclass": "error",
"unit_value_mismatch": "error",
"unresolved_variable": "error",
"wire_dangling": "error"
}
},
"libraries": {
"pinned_footprint_libs": [],
"pinned_symbol_libs": []
},
"meta": {
"filename": "sensor.kicad_pro",
"version": 3
},
"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)",
"priority": 2147483647,
"schematic_color": "rgba(0, 0, 0, 0.000)",
"track_width": 0.2,
"via_diameter": 0.6,
"via_drill": 0.3,
"wire_width": 6
}
],
"meta": {
"version": 4
},
"net_colors": null,
"netclass_assignments": null,
"netclass_patterns": []
},
"pcbnew": {
"last_paths": {
"gencad": "",
"idf": "",
"netlist": "",
"plot": "",
"pos_files": "",
"specctra_dsn": "",
"step": "",
"svg": "",
"vrml": ""
},
"page_layout_descr_file": ""
},
"schematic": {
"annotate_start_num": 0,
"bom_export_filename": "${PROJECTNAME}.csv",
"bom_fmt_presets": [],
"bom_fmt_settings": {
"field_delimiter": ",",
"keep_line_breaks": false,
"keep_tabs": false,
"name": "CSV",
"ref_delimiter": ",",
"ref_range_delimiter": "",
"string_delimiter": "\""
},
"bom_presets": [],
"bom_settings": {
"exclude_dnp": false,
"fields_ordered": [
{
"group_by": false,
"label": "Reference",
"name": "Reference",
"show": true
},
{
"group_by": false,
"label": "Qty",
"name": "${QUANTITY}",
"show": true
},
{
"group_by": true,
"label": "Value",
"name": "Value",
"show": true
},
{
"group_by": true,
"label": "DNP",
"name": "${DNP}",
"show": true
},
{
"group_by": true,
"label": "Exclude from BOM",
"name": "${EXCLUDE_FROM_BOM}",
"show": true
},
{
"group_by": true,
"label": "Exclude from Board",
"name": "${EXCLUDE_FROM_BOARD}",
"show": true
},
{
"group_by": true,
"label": "Footprint",
"name": "Footprint",
"show": true
},
{
"group_by": false,
"label": "Datasheet",
"name": "Datasheet",
"show": true
},
{
"group_by": false,
"label": "Actuator/Cap Color",
"name": "Actuator/Cap Color",
"show": false
},
{
"group_by": false,
"label": "Attrition Qty",
"name": "Attrition Qty",
"show": false
},
{
"group_by": false,
"label": "Capacitance",
"name": "Capacitance",
"show": false
},
{
"group_by": false,
"label": "Category",
"name": "Category",
"show": false
},
{
"group_by": false,
"label": "Circuit",
"name": "Circuit",
"show": false
},
{
"group_by": false,
"label": "Class",
"name": "Class",
"show": false
},
{
"group_by": false,
"label": "Contact Current",
"name": "Contact Current",
"show": false
},
{
"group_by": false,
"label": "Diode Configuration",
"name": "Diode Configuration",
"show": false
},
{
"group_by": false,
"label": "Forward Voltage (Vf@If)",
"name": "Forward Voltage (Vf@If)",
"show": false
},
{
"group_by": false,
"label": "Insulation Resistance",
"name": "Insulation Resistance",
"show": false
},
{
"group_by": false,
"label": "LCSC",
"name": "LCSC",
"show": false
},
{
"group_by": false,
"label": "Manufacturer",
"name": "Manufacturer",
"show": false
},
{
"group_by": false,
"label": "Mechanical Life",
"name": "Mechanical Life",
"show": false
},
{
"group_by": false,
"label": "Minimum Qty",
"name": "Minimum Qty",
"show": false
},
{
"group_by": false,
"label": "Mounting Style",
"name": "Mounting Style",
"show": false
},
{
"group_by": false,
"label": "Operating Force",
"name": "Operating Force",
"show": false
},
{
"group_by": false,
"label": "Operating Temperature",
"name": "Operating Temperature",
"show": false
},
{
"group_by": false,
"label": "Operating Temperature Range",
"name": "Operating Temperature Range",
"show": false
},
{
"group_by": false,
"label": "Overload Voltage (Max)",
"name": "Overload Voltage (Max)",
"show": false
},
{
"group_by": false,
"label": "Part",
"name": "Part",
"show": false
},
{
"group_by": false,
"label": "Pin Style",
"name": "Pin Style",
"show": false
},
{
"group_by": false,
"label": "Power(Watts)",
"name": "Power(Watts)",
"show": false
},
{
"group_by": false,
"label": "Price",
"name": "Price",
"show": false
},
{
"group_by": false,
"label": "Process",
"name": "Process",
"show": false
},
{
"group_by": false,
"label": "Rectified Current",
"name": "Rectified Current",
"show": false
},
{
"group_by": false,
"label": "Resistance",
"name": "Resistance",
"show": false
},
{
"group_by": false,
"label": "Reverse Leakage Current",
"name": "Reverse Leakage Current",
"show": false
},
{
"group_by": false,
"label": "Reverse Voltage (Vr)",
"name": "Reverse Voltage (Vr)",
"show": false
},
{
"group_by": false,
"label": "Sim.Pins",
"name": "Sim.Pins",
"show": false
},
{
"group_by": false,
"label": "Stock",
"name": "Stock",
"show": false
},
{
"group_by": false,
"label": "Strike Gundam",
"name": "Strike Gundam",
"show": false
},
{
"group_by": false,
"label": "Switch Height",
"name": "Switch Height",
"show": false
},
{
"group_by": false,
"label": "Switch Length",
"name": "Switch Length",
"show": false
},
{
"group_by": false,
"label": "Switch Width",
"name": "Switch Width",
"show": false
},
{
"group_by": false,
"label": "Temperature Coefficient",
"name": "Temperature Coefficient",
"show": false
},
{
"group_by": false,
"label": "Tolerance",
"name": "Tolerance",
"show": false
},
{
"group_by": false,
"label": "Type",
"name": "Type",
"show": false
},
{
"group_by": false,
"label": "Voltage Rated",
"name": "Voltage Rated",
"show": false
},
{
"group_by": false,
"label": "Voltage Rating (Dc)",
"name": "Voltage Rating (Dc)",
"show": false
},
{
"group_by": false,
"label": "With Lamp",
"name": "With Lamp",
"show": false
},
{
"group_by": false,
"label": "Actuator Style",
"name": "Actuator Style",
"show": false
},
{
"group_by": false,
"label": "Description",
"name": "Description",
"show": false
},
{
"group_by": false,
"label": "#",
"name": "${ITEM_NUMBER}",
"show": false
}
],
"filter_string": "",
"group_symbols": true,
"include_excluded_from_bom": true,
"name": "",
"sort_asc": true,
"sort_field": "Reference"
},
"connection_grid_size": 50.0,
"drawing": {
"dashed_lines_dash_length_ratio": 12.0,
"dashed_lines_gap_length_ratio": 3.0,
"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,
"operating_point_overlay_i_precision": 3,
"operating_point_overlay_i_range": "~A",
"operating_point_overlay_v_precision": 3,
"operating_point_overlay_v_range": "~V",
"overbar_offset_ratio": 1.23,
"pin_symbol_size": 25.0,
"text_offset_ratio": 0.15
},
"legacy_lib_dir": "",
"legacy_lib_list": [],
"meta": {
"version": 1
},
"net_format_name": "",
"page_layout_descr_file": "",
"plot_directory": "",
"space_save_all_events": true,
"spice_current_sheet_as_root": false,
"spice_external_command": "spice \"%I\"",
"spice_model_current_sheet_as_root": true,
"spice_save_all_currents": false,
"spice_save_all_dissipations": false,
"spice_save_all_voltages": false,
"subpart_first_id": 65,
"subpart_id_separator": 0
},
"sheets": [
[
"a6275404-53d1-4c49-9128-131a51db8de5",
"Root"
]
],
"text_variables": {}
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,15 +0,0 @@
[build]
target = "riscv32imc-unknown-none-elf"
[target."riscv32imc-unknown-none-elf"]
rustflags = [
# "-C", "link-arg=-Tlink.x",
]
# runner = "riscv64-unknown-elf-gdb -q -x openocd.gdb"
# runner = "riscv-none-embed-gdb -q -x openocd.gdb"
# runner = "gdb -q -x openocd.gdb"
# runner = "wlink -v flash"
runner = "wchisp flash"
#runner = "wlink -v flash --enable-sdi-print --watch-serial --erase"
# runner = "wlink -v flash"

View File

@@ -1,32 +0,0 @@
;;; .doomrc --- doom runtime config -*- mode: emacs-lisp; lexical-binding: t; -*-
;;; Commentary:
;;; Code:
(require 'doom) ; be silent, byte-compiler
(after! dape
(add-to-list
'dape-configs
`(gdb-dap-openocd
ensure (lambda (config)
(dape-ensure-command config)
(let* ((default-directory
(or (dape-config-get config 'command-cwd)
default-directory))
(command (dape-config-get config 'command))
(output (shell-command-to-string (format "%s --version" command)))
(version (save-match-data
(when (string-match "GNU gdb \\(?:(.*) \\)?\\([0-9.]+\\)" output)
(string-to-number (match-string 1 output))))))
(unless (>= version 14.1)
(user-error "Requires gdb version >= 14.1"))))
modes ()
command-cwd dape-command-cwd
command "gdb"
command-args ("--interpreter=dap")
:request nil
:program nil
:args []
:stopAtBeginningOfMainSubprogram nil))
)
;;; .doomrc ends here

View File

@@ -1,7 +0,0 @@
target extended-remote :3333
set remotetimeout 2000
#symbol-file target/riscv32imc-unknown-none-elf/release/ch32v203-examples
file target/riscv32imc-unknown-none-elf/release/bms
monitor reset halt

View File

@@ -1,2 +0,0 @@
target
vendor

View File

@@ -1,8 +0,0 @@
# Default ignored files
/shelf/
/workspace.xml
# Editor-based HTTP Client requests
/httpRequests/
# Datasource local storage ignored files
/dataSources/
/dataSources.local.xml

View File

@@ -1,11 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="EMPTY_MODULE" version="4">
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
<excludeFolder url="file://$MODULE_DIR$/target" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>

View File

@@ -1,8 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/ch32-sensor.iml" filepath="$PROJECT_DIR$/.idea/ch32-sensor.iml" />
</modules>
</component>
</project>

View File

@@ -1,9 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$/../.." vcs="Git" />
<mapping directory="$PROJECT_DIR$/.." vcs="Git" />
<mapping directory="$PROJECT_DIR$" vcs="Git" />
<mapping directory="$PROJECT_DIR$/../../website/themes/blowfish" vcs="Git" />
</component>
</project>

View File

@@ -1,904 +0,0 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 4
[[package]]
name = "ahash"
version = "0.8.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75"
dependencies = [
"cfg-if",
"once_cell",
"version_check",
"zerocopy",
]
[[package]]
name = "autocfg"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8"
[[package]]
name = "bincode"
version = "2.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "36eaf5d7b090263e8150820482d5d93cd964a81e4019913c972f4edcc6edb740"
dependencies = [
"bincode_derive",
"unty",
]
[[package]]
name = "bincode_derive"
version = "2.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bf95709a440f45e986983918d0e8a1f30a9b1df04918fc828670606804ac3c09"
dependencies = [
"virtue",
]
[[package]]
name = "bit_field"
version = "0.10.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e4b40c7323adcfc0a41c4b88143ed58346ff65a288fc144329c5c45e05d70c6"
[[package]]
name = "bitfield"
version = "0.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2d7e60934ceec538daadb9d8432424ed043a904d8e0243f3c6446bce549a46ac"
[[package]]
name = "bms"
version = "0.1.0"
dependencies = [
"canapi",
"ch32-hal",
"embassy-executor",
"embassy-futures",
"embassy-sync 0.6.2",
"embassy-time",
"embassy-usb",
"embedded-alloc",
"embedded-can",
"heapless",
"log",
"panic-halt",
"qingke",
"qingke-rt",
"static_cell",
]
[[package]]
name = "byteorder"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
[[package]]
name = "canapi"
version = "0.1.0"
dependencies = [
"bincode",
]
[[package]]
name = "cfg-if"
version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2fd1289c04a9ea8cb22300a459a72a385d7c73d3259e2ed7dcb2af674838cfa9"
[[package]]
name = "ch32-hal"
version = "0.1.0"
source = "git+https://github.com/empirephoenix/ch32-hal.git#8f0fc9a36a603cfc130295a79dbb336c504f659c"
dependencies = [
"ch32-metapac",
"critical-section",
"embassy-futures",
"embassy-hal-internal",
"embassy-sync 0.6.2",
"embassy-time",
"embassy-time-driver",
"embassy-time-queue-utils",
"embassy-usb-driver",
"embedded-can",
"embedded-hal 0.2.7",
"embedded-hal 1.0.0",
"embedded-hal-async",
"embedded-hal-nb",
"futures",
"nb 1.1.0",
"proc-macro2",
"qingke",
"qingke-rt",
"quote",
"rand_core",
"sdio-host",
]
[[package]]
name = "ch32-metapac"
version = "0.1.0"
source = "git+https://github.com/ch32-rs/ch32-metapac?rev=b1cbc7a98e43af3fd3170821654784e2c01cb26b#b1cbc7a98e43af3fd3170821654784e2c01cb26b"
dependencies = [
"riscv 0.11.1",
"vcell",
]
[[package]]
name = "critical-section"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "790eea4361631c5e7d22598ecd5723ff611904e3344ce8720784c93e3d83d40b"
[[package]]
name = "darling"
version = "0.20.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fc7f46116c46ff9ab3eb1597a45688b6715c6e628b5c133e288e709a29bcb4ee"
dependencies = [
"darling_core",
"darling_macro",
]
[[package]]
name = "darling_core"
version = "0.20.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0d00b9596d185e565c2207a0b01f8bd1a135483d02d9b7b0a54b11da8d53412e"
dependencies = [
"fnv",
"ident_case",
"proc-macro2",
"quote",
"strsim",
"syn 2.0.106",
]
[[package]]
name = "darling_macro"
version = "0.20.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead"
dependencies = [
"darling_core",
"quote",
"syn 2.0.106",
]
[[package]]
name = "document-features"
version = "0.2.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "95249b50c6c185bee49034bcb378a49dc2b5dff0be90ff6616d31d64febab05d"
dependencies = [
"litrs",
]
[[package]]
name = "embassy-executor"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "90327bcc66333a507f89ecc4e2d911b265c45f5c9bc241f98eee076752d35ac6"
dependencies = [
"critical-section",
"document-features",
"embassy-executor-macros",
]
[[package]]
name = "embassy-executor-macros"
version = "0.6.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3577b1e9446f61381179a330fc5324b01d511624c55f25e3c66c9e3c626dbecf"
dependencies = [
"darling",
"proc-macro2",
"quote",
"syn 2.0.106",
]
[[package]]
name = "embassy-futures"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dc2d050bdc5c21e0862a89256ed8029ae6c290a93aecefc73084b3002cdebb01"
[[package]]
name = "embassy-hal-internal"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0ef3bac31ec146321248a169e9c7b5799f1e0b3829c7a9b324cb4600a7438f59"
dependencies = [
"num-traits",
]
[[package]]
name = "embassy-net-driver"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "524eb3c489760508f71360112bca70f6e53173e6fe48fc5f0efd0f5ab217751d"
[[package]]
name = "embassy-net-driver-channel"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b7b2739fbcf6cd206ae08779c7d709087b16577d255f2ea4a45bc4bbbf305b3f"
dependencies = [
"embassy-futures",
"embassy-net-driver",
"embassy-sync 0.7.2",
]
[[package]]
name = "embassy-sync"
version = "0.6.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8d2c8cdff05a7a51ba0087489ea44b0b1d97a296ca6b1d6d1a33ea7423d34049"
dependencies = [
"cfg-if",
"critical-section",
"embedded-io-async",
"futures-sink",
"futures-util",
"heapless",
]
[[package]]
name = "embassy-sync"
version = "0.7.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "73974a3edbd0bd286759b3d483540f0ebef705919a5f56f4fc7709066f71689b"
dependencies = [
"cfg-if",
"critical-section",
"embedded-io-async",
"futures-core",
"futures-sink",
"heapless",
]
[[package]]
name = "embassy-time"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f820157f198ada183ad62e0a66f554c610cdcd1a9f27d4b316358103ced7a1f8"
dependencies = [
"cfg-if",
"critical-section",
"document-features",
"embassy-time-driver",
"embedded-hal 0.2.7",
"embedded-hal 1.0.0",
"embedded-hal-async",
"futures-util",
]
[[package]]
name = "embassy-time-driver"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a0a244c7dc22c8d0289379c8d8830cae06bb93d8f990194d0de5efb3b5ae7ba6"
dependencies = [
"document-features",
]
[[package]]
name = "embassy-time-queue-utils"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dc55c748d16908a65b166d09ce976575fb8852cf60ccd06174092b41064d8f83"
dependencies = [
"embassy-executor",
"heapless",
]
[[package]]
name = "embassy-usb"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8d0b882133fa684b9d4652351cd7aac5afe8a2c2bf4a7da59f442ff61087cda2"
dependencies = [
"embassy-futures",
"embassy-net-driver-channel",
"embassy-sync 0.6.2",
"embassy-usb-driver",
"heapless",
"ssmarshal",
"usbd-hid",
]
[[package]]
name = "embassy-usb-driver"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "340c5ce591ef58c6449e43f51d2c53efe1bf0bb6a40cbf80afa0d259c7d52c76"
dependencies = [
"embedded-io-async",
]
[[package]]
name = "embedded-alloc"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f2de9133f68db0d4627ad69db767726c99ff8585272716708227008d3f1bddd"
dependencies = [
"critical-section",
"linked_list_allocator",
]
[[package]]
name = "embedded-can"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e9d2e857f87ac832df68fa498d18ddc679175cf3d2e4aa893988e5601baf9438"
dependencies = [
"nb 1.1.0",
]
[[package]]
name = "embedded-hal"
version = "0.2.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "35949884794ad573cf46071e41c9b60efb0cb311e3ca01f7af807af1debc66ff"
dependencies = [
"nb 0.1.3",
"void",
]
[[package]]
name = "embedded-hal"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "361a90feb7004eca4019fb28352a9465666b24f840f5c3cddf0ff13920590b89"
[[package]]
name = "embedded-hal-async"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0c4c685bbef7fe13c3c6dd4da26841ed3980ef33e841cddfa15ce8a8fb3f1884"
dependencies = [
"embedded-hal 1.0.0",
]
[[package]]
name = "embedded-hal-nb"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fba4268c14288c828995299e59b12babdbe170f6c6d73731af1b4648142e8605"
dependencies = [
"embedded-hal 1.0.0",
"nb 1.1.0",
]
[[package]]
name = "embedded-io"
version = "0.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "edd0f118536f44f5ccd48bcb8b111bdc3de888b58c74639dfb034a357d0f206d"
[[package]]
name = "embedded-io-async"
version = "0.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3ff09972d4073aa8c299395be75161d582e7629cd663171d62af73c8d50dba3f"
dependencies = [
"embedded-io",
]
[[package]]
name = "encode_unicode"
version = "0.3.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f"
[[package]]
name = "fnv"
version = "1.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
[[package]]
name = "futures"
version = "0.3.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876"
dependencies = [
"futures-channel",
"futures-core",
"futures-io",
"futures-sink",
"futures-task",
"futures-util",
]
[[package]]
name = "futures-channel"
version = "0.3.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10"
dependencies = [
"futures-core",
"futures-sink",
]
[[package]]
name = "futures-core"
version = "0.3.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e"
[[package]]
name = "futures-io"
version = "0.3.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6"
[[package]]
name = "futures-macro"
version = "0.3.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.106",
]
[[package]]
name = "futures-sink"
version = "0.3.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7"
[[package]]
name = "futures-task"
version = "0.3.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988"
[[package]]
name = "futures-util"
version = "0.3.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81"
dependencies = [
"futures-core",
"futures-macro",
"futures-sink",
"futures-task",
"pin-project-lite",
"pin-utils",
]
[[package]]
name = "hash32"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "47d60b12902ba28e2730cd37e95b8c9223af2808df9e902d4df49588d1470606"
dependencies = [
"byteorder",
]
[[package]]
name = "hashbrown"
version = "0.13.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "43a3c133739dddd0d2990f9a4bdf8eb4b21ef50e4851ca85ab661199821d510e"
dependencies = [
"ahash",
]
[[package]]
name = "heapless"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0bfb9eb618601c89945a70e254898da93b13be0388091d42117462b265bb3fad"
dependencies = [
"hash32",
"portable-atomic",
"stable_deref_trait",
]
[[package]]
name = "ident_case"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39"
[[package]]
name = "linked_list_allocator"
version = "0.10.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9afa463f5405ee81cdb9cc2baf37e08ec7e4c8209442b5d72c04cfb2cd6e6286"
[[package]]
name = "litrs"
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f5e54036fe321fd421e10d732f155734c4e4afd610dd556d9a82833ab3ee0bed"
[[package]]
name = "log"
version = "0.4.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432"
[[package]]
name = "nb"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "801d31da0513b6ec5214e9bf433a77966320625a37860f910be265be6e18d06f"
dependencies = [
"nb 1.1.0",
]
[[package]]
name = "nb"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8d5439c4ad607c3c23abf66de8c8bf57ba8adcd1f129e699851a6e43935d339d"
[[package]]
name = "num-traits"
version = "0.2.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
dependencies = [
"autocfg",
]
[[package]]
name = "once_cell"
version = "1.21.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
[[package]]
name = "panic-halt"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a513e167849a384b7f9b746e517604398518590a9142f4846a32e3c2a4de7b11"
[[package]]
name = "paste"
version = "1.0.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a"
[[package]]
name = "pin-project-lite"
version = "0.2.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b"
[[package]]
name = "pin-utils"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
[[package]]
name = "portable-atomic"
version = "1.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f84267b20a16ea918e43c6a88433c2d54fa145c92a811b5b047ccbe153674483"
dependencies = [
"critical-section",
]
[[package]]
name = "proc-macro-error"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c"
dependencies = [
"proc-macro-error-attr",
"proc-macro2",
"quote",
"syn 1.0.109",
"version_check",
]
[[package]]
name = "proc-macro-error-attr"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869"
dependencies = [
"proc-macro2",
"quote",
"version_check",
]
[[package]]
name = "proc-macro2"
version = "1.0.101"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "89ae43fd86e4158d6db51ad8e2b80f313af9cc74f5c0e03ccb87de09998732de"
dependencies = [
"unicode-ident",
]
[[package]]
name = "qingke"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c0230c5310b68c08a3cf8b59fbeec3e9d8e352bc6500f62cbaf9c677f42c8dfc"
dependencies = [
"bit_field",
"critical-section",
"riscv 0.12.1",
]
[[package]]
name = "qingke-rt"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b955c60adac70c6d40205b1dbe9f57e1151d06aa842069cdbaef7bc07ad283fd"
dependencies = [
"qingke",
"qingke-rt-macros",
]
[[package]]
name = "qingke-rt-macros"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5f2ed46d18953ea5765ab26a07d1f092dffac2da1b4830c4397e02c3cec08501"
dependencies = [
"proc-macro-error",
"proc-macro2",
"quote",
"syn 1.0.109",
]
[[package]]
name = "quote"
version = "1.0.41"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ce25767e7b499d1b604768e7cde645d14cc8584231ea6b295e9c9eb22c02e1d1"
dependencies = [
"proc-macro2",
]
[[package]]
name = "rand_core"
version = "0.6.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
[[package]]
name = "riscv"
version = "0.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2f5c1b8bf41ea746266cdee443d1d1e9125c86ce1447e1a2615abd34330d33a9"
dependencies = [
"critical-section",
"embedded-hal 1.0.0",
]
[[package]]
name = "riscv"
version = "0.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5ea8ff73d3720bdd0a97925f0bf79ad2744b6da8ff36be3840c48ac81191d7a7"
dependencies = [
"critical-section",
"embedded-hal 1.0.0",
"paste",
"riscv-macros",
"riscv-pac",
]
[[package]]
name = "riscv-macros"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f265be5d634272320a7de94cea15c22a3bfdd4eb42eb43edc528415f066a1f25"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.106",
]
[[package]]
name = "riscv-pac"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8188909339ccc0c68cfb5a04648313f09621e8b87dc03095454f1a11f6c5d436"
[[package]]
name = "sdio-host"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f93c025f9cfe4c388c328ece47d11a54a823da3b5ad0370b22d95ad47137f85a"
[[package]]
name = "serde"
version = "1.0.228"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e"
dependencies = [
"serde_core",
]
[[package]]
name = "serde_core"
version = "1.0.228"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.228"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.106",
]
[[package]]
name = "ssmarshal"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f3e6ad23b128192ed337dfa4f1b8099ced0c2bf30d61e551b65fda5916dbb850"
dependencies = [
"encode_unicode",
"serde",
]
[[package]]
name = "stable_deref_trait"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3"
[[package]]
name = "static_cell"
version = "2.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0530892bb4fa575ee0da4b86f86c667132a94b74bb72160f58ee5a4afec74c23"
dependencies = [
"portable-atomic",
]
[[package]]
name = "strsim"
version = "0.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
[[package]]
name = "syn"
version = "1.0.109"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "syn"
version = "2.0.106"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ede7c438028d4436d71104916910f5bb611972c5cfd7f89b8300a8186e6fada6"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "unicode-ident"
version = "1.0.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f63a545481291138910575129486daeaf8ac54aee4387fe7906919f7830c7d9d"
[[package]]
name = "unty"
version = "0.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6d49784317cd0d1ee7ec5c716dd598ec5b4483ea832a2dced265471cc0f690ae"
[[package]]
name = "usb-device"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "98816b1accafbb09085168b90f27e93d790b4bfa19d883466b5e53315b5f06a6"
dependencies = [
"heapless",
"portable-atomic",
]
[[package]]
name = "usbd-hid"
version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6f291ab53d428685cc780f08a2eb9d5d6ff58622db2b36e239a4f715f1e184c"
dependencies = [
"serde",
"ssmarshal",
"usb-device",
"usbd-hid-macros",
]
[[package]]
name = "usbd-hid-descriptors"
version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0eee54712c5d778d2fb2da43b1ce5a7b5060886ef7b09891baeb4bf36910a3ed"
dependencies = [
"bitfield",
]
[[package]]
name = "usbd-hid-macros"
version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bb573c76e7884035ac5e1ab4a81234c187a82b6100140af0ab45757650ccda38"
dependencies = [
"byteorder",
"hashbrown",
"log",
"proc-macro2",
"quote",
"serde",
"syn 1.0.109",
"usbd-hid-descriptors",
]
[[package]]
name = "vcell"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "77439c1b53d2303b20d9459b1ade71a83c716e3f9c34f3228c00e6f185d6c002"
[[package]]
name = "version_check"
version = "0.9.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
[[package]]
name = "virtue"
version = "0.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "051eb1abcf10076295e815102942cc58f9d5e3b4560e46e53c21e8ff6f3af7b1"
[[package]]
name = "void"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d"
[[package]]
name = "zerocopy"
version = "0.8.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0894878a5fa3edfd6da3f88c4805f4c8558e2b996227a3d864f47fe11e38282c"
dependencies = [
"zerocopy-derive",
]
[[package]]
name = "zerocopy-derive"
version = "0.8.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "88d2b8d9c68ad2b9e4340d7832716a4d21a22a1154777ad56ea55c51a9cf3831"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.106",
]

View File

@@ -1,53 +0,0 @@
[package]
name = "bms"
version = "0.1.0"
edition = "2021"
[dependencies]
# Shared CAN API
canapi = { path = "../Shared/canapi" }
ch32-hal = { git = "https://github.com/empirephoenix/ch32-hal.git", features = [
"ch32v203c8t6",
"memory-x",
"embassy",
"rt",
"time-driver-tim2",
], default-features = false }
embassy-executor = { version = "0.7.0", features = [
# "arch-riscv32",
"arch-spin", # TODO: Required for USBD to connect properl
"executor-thread",
] }
#embassy-time = { version = "0.3.2" }
embassy-usb = { version = "0.3.0" }
embassy-futures = { version = "0.1.0" }
embassy-sync = { version = "0.6.0" }
embedded-can = "0.4.1"
embedded-alloc = { version = "0.6.0", default-features = false, features = ["llff"] }
# This is okay because we should automatically use whatever ch32-hal uses
qingke-rt = "*"
qingke = "*"
panic-halt = "1.0"
heapless = { version = "0.8.0", features = ["portable-atomic-critical-section"] }
embassy-time = { version = "0.4.0" }
static_cell = "2.1.1"
log = "0.4.28"
[profile.dev]
#lto = true
opt-level = 1
[profile.release]
strip = true # symbols are not flashed to the microcontroller, so don't strip them.
lto = true
debug = false
opt-level = "z" # Optimize for size.

View File

@@ -1,99 +0,0 @@
# ch32v203-bms
A simple battery management controller software.
## WeActStudio BluePill Plus CH32 Pin Labels
The BluePill Plus CH32 board from WeActStudio uses standard MCU port naming printed on the PCB silkscreen:
- PAx: GPIO Port A pins, labeled PA0 .. PA15
- PBx: GPIO Port B pins, labeled PB0 .. PB15
- PCx: GPIO Port C pins, commonly PC13 .. PC15 are broken out
- Other labels typically present: 3V3, 5V, G (GND), NRST (reset), BOOT (BOOT0), and SWD/RVSWD pads for programming/debug (SWCLK/SWDIO or similar).
For the exact header layout and picture of the silkscreen labels, please refer to the official WeActStudio documentation:
- https://github.com/WeActStudio/WeActStudio.BluePill-Plus-CH32
Pins used by this firmware (as referenced in `src/main.rs`):
- PA1: ADC analog input (combined Trigger/Threshold in the 555-timer example)
- PB0: Digital output (Q in the 555-timer example)
If you need to map a label to code, use the same letter+number as in the silkscreen. For example, `p.PA1` in code corresponds to pin labeled "PA1" on the PCB header, and `p.PB0` corresponds to "PB0".
## Building
``` sh
cargo build --release
```
- Wire the MCUs USB pins to a USB connector:
- D+ (PA12)
- D (PA11)
- GND and 5V (as appropriate for your board; ensure you have a data-capable cable)
After flashing and powering via USB, your OS should enumerate a virtual serial port (e.g., /dev/ttyACM0 on Linux, COMx on Windows, /dev/tty.usbmodem* on macOS). Open it with any terminal program (baud setting is ignored by CDC but 115200 is fine).
Example:
- Linux: `screen /dev/ttyACM0 115200`
- macOS: `screen /dev/tty.usbmodemXXXX 115200`
- Windows: Use PuTTY on the shown COM port.
You can flash the built ELF using wchisp (WCH ISP tool):
``` sh
wchisp flash target/riscv32imc-unknown-none-elf/release/bms
# or, if using a wrapper on your system/container, the command may be:
# wchip wchisp flash target/riscv32imc-unknown-none-elf/release/bms
```
## Unlock / Remove MCU Protection (fix "checksum error")
Some CH32 devices ship with flash protection enabled. When protected, tools may report a checksum error and refuse to program. You can clear the protection by performing a full chip erase with wchisp. This will erase all flash contents.
Steps:
- Ensure WCH-Link/WCH-LinkE is connected to the target and your OS has permissions to access it.
- Verify connection and current status:
``` sh
wchisp info
```
- Mass erase the chip (this also clears protection/lock bits):
``` sh
* start the device (while pressing boot)
wchisp erase
* powercycle the device (while pressing boot)
wchisp config reset
* powercycle the device again (while pressing boot), flash should not work
```
- Flash your firmware again:
``` sh
wchisp flash target/riscv32imc-unknown-none-elf/release/bms
```
Notes:
- The target used in this project is CH32V203C8T6; wchisp detects it automatically with WCH-Link.
- If your wchisp version differs, run `wchisp --help`, `wchisp erase --help`, or consult the tool's README for the exact flag name.
- If the tool still reports protection, look for commands named `unprotect` or `protect --off` in `wchisp --help`. The mass/chip erase is the typical way to clear protection.
## Debugging
For debugging purposes a container file is provided together with wrapper scripts to start the containerized `openocd` and `riscv-gdb` transparently. The wrapper scripts assume that `podman` is setup.
Starting Debug server
```
./bin/openocd
```
Connecting with gdb for interactive debugging
```
./bin/gdb -f target/riscv32imc-unknown-none-elf/release/bms
```

View File

@@ -1,10 +0,0 @@
#!/usr/bin/env bash
set -euo pipefail
CONTAINER_NAME="localhost/wch-dev-tools:latest"
CONTAINER_TOOLS_BASEDIR="$(dirname "$(readlink -f "$0")")"
pushd "$CONTAINER_TOOLS_BASEDIR"
podman build -t "$CONTAINER_NAME" -f "../wch-tools.Containerfile" .
popd

View File

@@ -1,29 +0,0 @@
#!/usr/bin/env bash
set -euo pipefail
CONTAINER_IMAGE="localhost/wch-dev-tools:latest"
CONTAINER_TOOLS_BASEDIR="$(dirname "$(readlink -f "$0")")"
function _fatal {
echo -e "\e[31mERROR\e[0m $(</dev/stdin)$*" 1>&2
exit 1
}
declare -a PODMAN_ARGS=(
"--rm" "-i" "--log-driver=none"
"--network=host"
"--pid=host"
"-v" "$PWD:$PWD:rw"
"-w" "$PWD"
)
[[ -t 1 ]] && PODMAN_ARGS+=("-t")
if ! podman image exists "$CONTAINER_IMAGE"; then
#attempt to build container
"$CONTAINER_TOOLS_BASEDIR/build-wch-tools-container.sh" 1>&2 ||
_fatal "faild to build local image, cannot continue! … please ensure you have an internet connection"
fi
podman run "${PODMAN_ARGS[@]}" --entrypoint riscv-none-elf-gdb-py3 "$CONTAINER_IMAGE" "$@"

View File

@@ -1,44 +0,0 @@
#!/usr/bin/env bash
set -euo pipefail
CONTAINER_IMAGE="localhost/wch-dev-tools:latest"
CONTAINER_TOOLS_BASEDIR="$(dirname "$(readlink -f "$0")")"
function _fatal {
echo -e "\e[31mERROR\e[0m $(</dev/stdin)$*" 1>&2
exit 1
}
declare -a PODMAN_ARGS=(
"--rm" "-i" "--log-driver=none"
"--network=host"
"-v" "$PWD:$PWD:rw"
"-w" "$PWD"
)
for device in /dev/bus/usb/*/*; do
if udevadm info "$device" | grep -q "ID_VENDOR=wch.cn" && \
udevadm info "$device" | grep -q "ID_MODEL=WCH-Link"; then
DEBUGGER_DEV_PATH="$device"
break
fi
done
if [[ -z "${DEBUGGER_DEV_PATH:-}" ]]; then
echo "Could not find hardware debugger … Exiting!" 1>&2
exit 1
else
# add jlink to podman device
PODMAN_ARGS+=("--device=$DEBUGGER_DEV_PATH")
fi
[[ -t 1 ]] && PODMAN_ARGS+=("-t")
if ! podman image exists "$CONTAINER_IMAGE"; then
#attempt to build container
"$CONTAINER_TOOLS_BASEDIR/build-wch-tools-container.sh" 1>&2 ||
_fatal "faild to build local image, cannot continue! … please ensure you have an internet connection"
fi
podman run "${PODMAN_ARGS[@]}" --entrypoint openocd "$CONTAINER_IMAGE" "$@"

View File

@@ -1,11 +0,0 @@
fn main() {
// println!("cargo:rustc-link-arg-bins=--nmagic");
println!("cargo:rustc-link-arg-bins=-Tlink.x");
// println!("cargo:rustc-link-arg-bins=-Tdefmt.x");
let out_dir = std::env::var("OUT_DIR").unwrap();
let out_dir = std::path::PathBuf::from(out_dir);
std::fs::write(out_dir.join("memory.x"), include_bytes!("memory.x")).unwrap();
println!("cargo:rustc-link-search={}", out_dir.display());
println!("cargo:rerun-if-changed=memory.x");
}

View File

@@ -1,125 +0,0 @@
/* CH32V203c8t6 */
MEMORY
{
FLASH : ORIGIN = 0x00000000, LENGTH = 64K /* BANK_1 */
RAM : ORIGIN = 0x20000000, LENGTH = 20K
}
REGION_ALIAS("REGION_TEXT", FLASH);
REGION_ALIAS("REGION_RODATA", FLASH);
REGION_ALIAS("REGION_DATA", RAM);
REGION_ALIAS("REGION_BSS", RAM);
REGION_ALIAS("REGION_HEAP", RAM);
REGION_ALIAS("REGION_STACK", RAM);
/* fault handlers */
PROVIDE(InstructionMisaligned = ExceptionHandler);
PROVIDE(InstructionFault = ExceptionHandler);
PROVIDE(IllegalInstruction = ExceptionHandler);
PROVIDE(Breakpoint = ExceptionHandler);
PROVIDE(LoadMisaligned = ExceptionHandler);
PROVIDE(LoadFault = ExceptionHandler);
PROVIDE(StoreMisaligned = ExceptionHandler);
PROVIDE(StoreFault = ExceptionHandler);;
PROVIDE(UserEnvCall = ExceptionHandler);
PROVIDE(SupervisorEnvCall = ExceptionHandler);
PROVIDE(MachineEnvCall = ExceptionHandler);
PROVIDE(InstructionPageFault = ExceptionHandler);
PROVIDE(LoadPageFault = ExceptionHandler);
PROVIDE(StorePageFault = ExceptionHandler);
/* core interrupt handlers */
PROVIDE(NonMaskableInt = DefaultHandler);
PROVIDE(Software = DefaultHandler);
/* external interrupt handlers */
PROVIDE(WWDG = DefaultHandler);
PROVIDE(PVD = DefaultHandler);
PROVIDE(TAMPER = DefaultHandler);
PROVIDE(RTC = DefaultHandler);
PROVIDE(FLASH = DefaultHandler);
PROVIDE(RCC = DefaultHandler);
PROVIDE(EXTI0 = DefaultHandler);
PROVIDE(EXTI1 = DefaultHandler);
PROVIDE(EXTI2 = DefaultHandler);
PROVIDE(EXTI3 = DefaultHandler);
PROVIDE(EXTI4 = DefaultHandler);
PROVIDE(DMA1_CHANNEL1 = DefaultHandler);
PROVIDE(DMA1_CHANNEL2 = DefaultHandler);
PROVIDE(DMA1_CHANNEL3 = DefaultHandler);
PROVIDE(DMA1_CHANNEL4 = DefaultHandler);
PROVIDE(DMA1_CHANNEL5 = DefaultHandler);
PROVIDE(DMA1_CHANNEL6 = DefaultHandler);
PROVIDE(DMA1_CHANNEL7 = DefaultHandler);
PROVIDE(ADC = DefaultHandler);
PROVIDE(USB_HP_CAN1_TX = DefaultHandler);
/*PROVIDE(USB_LP_CAN1_RX0 = DefaultHandler);*/
PROVIDE(CAN1_RX1 = DefaultHandler);
PROVIDE(CAN1_SCE = DefaultHandler);
PROVIDE(EXTI9_5 = DefaultHandler);
PROVIDE(TIM1_BRK = DefaultHandler);
PROVIDE(TIM1_UP_ = DefaultHandler);
PROVIDE(TIM1_TRG_COM = DefaultHandler);
PROVIDE(TIM1_CC = DefaultHandler);
PROVIDE(TIM2 = DefaultHandler);
PROVIDE(TIM3 = DefaultHandler);
PROVIDE(TIM4 = DefaultHandler);
PROVIDE(I2C1_EV = DefaultHandler);
PROVIDE(I2C1_ER = DefaultHandler);
PROVIDE(I2C2_EV = DefaultHandler);
PROVIDE(I2C2_ER = DefaultHandler);
PROVIDE(SPI1 = DefaultHandler);
PROVIDE(SPI2 = DefaultHandler);
PROVIDE(USART1 = DefaultHandler);
PROVIDE(USART2 = DefaultHandler);
PROVIDE(USART3 = DefaultHandler);
PROVIDE(EXTI15_10 = DefaultHandler);
PROVIDE(RTCALARM = DefaultHandler);
PROVIDE(USBWAKE_UP = DefaultHandler);
PROVIDE(TIM8_BRK = DefaultHandler);
PROVIDE(TIM8_UP_ = DefaultHandler);
PROVIDE(TIM8_TRG_COM = DefaultHandler);
PROVIDE(TIM8_CC = DefaultHandler);
PROVIDE(RNG = DefaultHandler);
PROVIDE(FSMC = DefaultHandler);
PROVIDE(SDIO = DefaultHandler);
PROVIDE(TIM5 = DefaultHandler);
PROVIDE(SPI3 = DefaultHandler);
PROVIDE(UART4 = DefaultHandler);
PROVIDE(UART5 = DefaultHandler);
PROVIDE(TIM6 = DefaultHandler);
PROVIDE(TIM7 = DefaultHandler);
PROVIDE(DMA2_CHANNEL1 = DefaultHandler);
PROVIDE(DMA2_CHANNEL2 = DefaultHandler);
PROVIDE(DMA2_CHANNEL3 = DefaultHandler);
PROVIDE(DMA2_CHANNEL4 = DefaultHandler);
PROVIDE(DMA2_CHANNEL5 = DefaultHandler);
PROVIDE(ETH = DefaultHandler);
PROVIDE(ETH_WKUP = DefaultHandler);
PROVIDE(CAN2_TX = DefaultHandler);
PROVIDE(CAN2_RX0 = DefaultHandler);
PROVIDE(CAN2_RX1 = DefaultHandler);
PROVIDE(CAN2_SCE = DefaultHandler);
PROVIDE(OTG_FS = DefaultHandler);
PROVIDE(USBHSWAKEUP = DefaultHandler);
PROVIDE(USBHS = DefaultHandler);
PROVIDE(DVP = DefaultHandler);
PROVIDE(UART6 = DefaultHandler);
PROVIDE(UART7 = DefaultHandler);
PROVIDE(UART8 = DefaultHandler);
PROVIDE(TIM9_BRK = DefaultHandler);
PROVIDE(TIM9_UP_ = DefaultHandler);
PROVIDE(TIM9_TRG_COM = DefaultHandler);
PROVIDE(TIM9_CC = DefaultHandler);
PROVIDE(TIM10_BRK = DefaultHandler);
PROVIDE(TIM10_UP_ = DefaultHandler);
PROVIDE(TIM10_TRG_COM = DefaultHandler);
PROVIDE(TIM10_CC = DefaultHandler);
PROVIDE(DMA2_CHANNEL6 = DefaultHandler);
PROVIDE(DMA2_CHANNEL7 = DefaultHandler);
PROVIDE(DMA2_CHANNEL8 = DefaultHandler);
PROVIDE(DMA2_CHANNEL9 = DefaultHandler);
PROVIDE(DMA2_CHANNEL10 = DefaultHandler);
PROVIDE(DMA2_CHANNEL11 = DefaultHandler);

View File

@@ -1,17 +0,0 @@
set _CHIPNAME ch32v203
set _TARGETNAME $_CHIPNAME.cpu
#bindto 0.0.0.0
adapter driver wlinke
adapter speed 6000
transport select sdi
sdi newtap $_CHIPNAME cpu -irlen 5 --expected-id 0x00001
target create $_TARGETNAME.0 wch_riscv -chain-position $_TARGETNAME
$_TARGETNAME.0 configure -work-area-phys 0x20000000 -work-area-size 10000 -work-area-backup 1
set _FLASHNAME $_CHIPNAME.flash
flash bank $_FLASHNAME wch_rsicv 0x00000000 0 0 0 $_TARGETNAME.0
init

View File

@@ -1,298 +0,0 @@
#![no_std]
#![no_main]
extern crate alloc;
use crate::hal::peripherals::CAN1;
use core::fmt::Write as _;
use canapi::id::{classify, plant_id, MessageKind, IDENTIFY_CMD_OFFSET, MOISTURE_DATA_OFFSET};
use canapi::SensorSlot;
use ch32_hal::gpio::{Level, Output, Speed};
use ch32_hal::adc::{Adc, SampleTime, ADC_MAX};
use ch32_hal::can;
use ch32_hal::can::{Can, CanFifo, CanFilter, CanFrame, CanMode};
use ch32_hal::mode::{NonBlocking};
use ch32_hal::peripherals::USBD;
use embassy_executor::{Spawner, task};
use embassy_usb::class::cdc_acm::{CdcAcmClass, State};
use embassy_usb::{Builder, UsbDevice};
use embassy_futures::yield_now;
use hal::usbd::{Driver};
use hal::{bind_interrupts};
use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex;
use embassy_sync::channel::{Channel};
use embassy_time::{Instant, Duration, Delay, Timer};
use embedded_can::{Id, StandardId};
use {ch32_hal as hal, panic_halt as _};
macro_rules! mk_static {
($t:ty,$val:expr) => {{
static STATIC_CELL: static_cell::StaticCell<$t> = static_cell::StaticCell::new();
#[deny(unused_attributes)]
let x = STATIC_CELL.uninit().write(($val));
x
}};
}
bind_interrupts!(struct Irqs {
USB_LP_CAN1_RX0 => hal::usbd::InterruptHandler<hal::peripherals::USBD>;
});
use embedded_alloc::LlffHeap as Heap;
use embedded_can::nb::Can as nb_can;
use qingke::riscv::asm::delay;
use log::log;
#[global_allocator]
static HEAP: Heap = Heap::empty();
static LOG_CH: Channel<CriticalSectionRawMutex, heapless::String<128>, 8> = Channel::new();
#[embassy_executor::main(entry = "qingke_rt::entry")]
async fn main(spawner: Spawner) {
ch32_hal::pac::AFIO.pcfr1().write(|w| w.set_can1_rm(2));
//
unsafe {
static mut HEAP_SPACE: [u8; 4096] = [0; 4096]; // 4 KiB heap, adjust as needed
HEAP.init(HEAP_SPACE.as_ptr() as usize, HEAP_SPACE.len());
}
let p = hal::init(hal::Config {
rcc: hal::rcc::Config::SYSCLK_FREQ_144MHZ_HSI,
..Default::default()
});
// Build driver and USB stack using 'static buffers
let driver = Driver::new(p.USBD, Irqs, p.PA12, p.PA11);
let mut config = embassy_usb::Config::new(0xC0DE, 0xCAFE);
config.manufacturer = Some("Embassy");
config.product = Some("USB-serial example");
config.serial_number = Some("12345678");
config.max_power = 100;
config.max_packet_size_0 = 64;
// Windows compatibility requires these; CDC-ACM
config.device_class = 0x02;
config.device_sub_class = 0x02;
config.device_protocol = 0x00;
config.composite_with_iads = false;
let mut builder = Builder::new(
driver,
config,
mk_static!([u8;256], [0; 256]),
mk_static!([u8;256], [0; 256]),
&mut [], // no msos descriptors
mk_static!([u8;64], [0; 64]),
);
// Initialize CDC state and create CDC-ACM class
let class = mk_static!(CdcAcmClass<'static, Driver<'static, hal::peripherals::USBD>>,
CdcAcmClass::new(
&mut builder,
mk_static!(State, State::new()),
64
)
);
// Build USB device
let usb = mk_static!(UsbDevice<Driver<USBD>>, builder.build()) ;
// Create GPIO for 555 Q output (PB0)
let q_out = Output::new(p.PB0, Level::Low, Speed::Low);
// Built-in LED on PB2 mirrors Q state
let led = Output::new(p.PB2, Level::Low, Speed::Low);
let info = Output::new(p.PA3, Level::Low, Speed::Low);
// Create ADC on ADC1 and use PA1 as analog input (Threshold/Trigger)
let adc = Adc::new(p.ADC1, Default::default());
let ain = p.PA1;
let config = can::can::Config::default();
let can: Can<CAN1, NonBlocking> = Can::new_nb(p.CAN1, p.PB8, p.PB9, CanFifo::Fifo0, CanMode::Normal, 125_000, config).expect("Valid");
ch32_hal::pac::AFIO.pcfr1().write(|w| w.set_can1_rm(2));
spawner.spawn(usb_task(usb)).unwrap();
spawner.spawn(usb_writer(class)).unwrap();
// move Q output, LED, ADC and analog input into worker task
spawner.spawn(worker(q_out, led, adc, ain, can)).unwrap();
// Prevent main from exiting
core::future::pending::<()>().await;
}
#[task]
async fn worker(
mut q: Output<'static>,
mut led: Output<'static>,
mut adc: Adc<'static, hal::peripherals::ADC1>,
mut ain: hal::peripherals::PA1,
mut can: Can<'static, CAN1, NonBlocking>,
) {
// 555 emulation state: Q initially Low
let mut q_high = false;
let low_th: u16 = (ADC_MAX as u16) / 3; // ~1/3 Vref
let high_th: u16 = ((ADC_MAX as u32 * 2) / 3) as u16; // ~2/3 Vref
let moisture_address = StandardId::new(plant_id(MOISTURE_DATA_OFFSET, SensorSlot::A, 0)).unwrap();
let identity_address = StandardId::new(plant_id(IDENTIFY_CMD_OFFSET, SensorSlot::A, 0)).unwrap();
let mut filter = CanFilter::new_id_list();
filter
.get(0)
.unwrap()
.set(identity_address.into(), Default::default());
can.add_filter(filter);
//can.add_filter(CanFilter::accept_all());
loop {
// Count rising edges of Q in a 100 ms window
let start = Instant::now();
let mut pulses: u32 = 0;
let mut last_q = q_high;
while Instant::now().checked_duration_since(start).unwrap_or(Duration::from_millis(0))
< Duration::from_millis(1000)
{
// Sample the analog input (Threshold/Trigger on A1)
let val: u16 = adc.convert(&mut ain, SampleTime::CYCLES28_5);
// 555 core behavior:
// - If input <= 1/3 Vref => set Q high (trigger)
// - If input >= 2/3 Vref => set Q low (threshold)
// - Otherwise keep previous Q state (hysteresis)
if val <= low_th {
q_high = true;
} else if val >= high_th {
q_high = false;
}
// Drive output pin accordingly
if q_high {
q.set_high();
} else {
q.set_low();
}
// Count rising edges
if !last_q && q_high {
pulses = pulses.saturating_add(1);
}
last_q = q_high;
// Yield to allow USB and other tasks to run
yield_now().await;
}
// Compute frequency from 100 ms window
let freq_hz = pulses; // pulses per 0.1s => Hz
let mut msg: heapless::String<128> = heapless::String::new();
let _ = write!(
&mut msg,
"555 window=100ms pulses={} freq={} Hz (A1->Q on PB0)\r\n",
pulses, freq_hz
);
log(msg);
let mut moisture = CanFrame::new(moisture_address, &[freq_hz as u8]).unwrap();
match can.transmit(&mut moisture){
Ok(..) => {
let mut msg: heapless::String<128> = heapless::String::new();
let _ = write!(
&mut msg,
"Send to canbus"
);
log(msg);
}
Err(err) => {
let mut msg: heapless::String<128> = heapless::String::new();
let _ = write!(
&mut msg,
"err {:?}"
,err
);
log(msg);
}
}
loop {
yield_now().await;
match can.receive() {
Ok(frame) => {
match frame.id() {
Id::Standard(s_frame) => {
let mut msg: heapless::String<128> = heapless::String::new();
let _ = write!(
&mut msg,
"Received from canbus: {:?} ident is {:?} \r\n",
s_frame.as_raw(),
identity_address.as_raw()
);
log(msg);
if s_frame.as_raw() == identity_address.as_raw() {
for _ in 0..10 {
Timer::after_millis(250).await;
led.toggle();
}
led.set_low();
}
}
Id::Extended(_) => {}
}
}
_ => {
break;
}
}
}
}
}
fn log(message: heapless::String<128>) {
match LOG_CH.try_send(message) {
Ok(_) => {}
Err(_) => {}
}
}
#[task]
async fn usb_task(usb: &'static mut UsbDevice<'static, Driver<'static, hal::peripherals::USBD>>) {
usb.run().await;
}
#[task]
async fn usb_writer(
class: &'static mut CdcAcmClass<'static, Driver<'static, hal::peripherals::USBD>>
) {
loop {
class.wait_connection().await;
printer(class).await;
}
}
async fn printer(class: &mut CdcAcmClass<'static, Driver<'static, USBD>>) {
loop {
let msg = LOG_CH.receive().await;
match class.write_packet(msg.as_bytes()).await {
Ok(_) => {}
Err(_) => {
// Disconnected or endpoint disabled
return;
}
}
}
}

View File

@@ -1,27 +0,0 @@
FROM debian:bookworm
RUN apt update -y && apt upgrade -y && apt install git libjaylink-dev libusb-1.0-0 unzip curl libhidapi-hidraw0 xz-utils -y
RUN cd /root && \
curl -L -o mrs-toolchain.tar.xz "https://github.com/ch32-riscv-ug/MounRiver_Studio_Community_miror/releases/download/1.92-toolchain/MRS_Toolchain_Linux_x64_V1.92.tar.xz" && \
mkdir mrs-toolchain && \
tar -xvf mrs-toolchain.tar.xz -C mrs-toolchain --strip-components=1 && \
mv mrs-toolchain/OpenOCD/bin/openocd /usr/local/bin && \
mv mrs-toolchain/OpenOCD/share/openocd /usr/local/share && \
# mv mrs-toolchain/RISC-V_Embedded_GCC12/bin/riscv-none-elf-gdb /usr/local/bin && \ # both toolchains in MRS are to old to work with emacs dape
# mv mrs-toolchain/RISC-V_Embedded_GCC12/libexec /usr/local && \ # both toolchains in MRS are to old to work with emacs dape
rm -rf mrs-toolchain mrs-toolchain.tar.xz && \
# Use up to date xpack toolchains for gdb
curl -L -o xpack-riscv-toolchain.tar.gz "https://github.com/xpack-dev-tools/riscv-none-elf-gcc-xpack/releases/download/v14.2.0-3/xpack-riscv-none-elf-gcc-14.2.0-3-linux-x64.tar.gz" && \
mkdir xpack-toolchain && \
tar -xvf xpack-riscv-toolchain.tar.gz -C xpack-toolchain --strip-components=1 && \
mv xpack-toolchain/bin/* /usr/local/bin && \
mv xpack-toolchain/lib/ /usr/local && \
mv xpack-toolchain/lib64/ /usr/local && \
mv xpack-toolchain/libexec /usr/local && \
mv xpack-toolchain/riscv-none-elf /usr/local && \
rm -rf xpack-toolchain xpack-riscv-toolchain.tar.gz
RUN mkdir -p /root/.config/gdb && echo "set auto-load safe-path /" >> /root/.config/gdb/gdbinit
ENTRYPOINT [ "/usr/bin/bash" ]

View File

@@ -1,10 +0,0 @@
# ESP-IDF build artifacts
build/
.sdkconfig*
CMakeFiles/
CMakeCache.txt
cmake-build-*/
*.log
*.bin
*.elf
*.map

View File

@@ -1,8 +0,0 @@
# Default ignored files
/shelf/
/workspace.xml
# Editor-based HTTP Client requests
/httpRequests/
# Datasource local storage ignored files
/dataSources/
/dataSources.local.xml

View File

@@ -1,8 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="EMPTY_MODULE" version="4">
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$" />
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>

View File

@@ -1,8 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/bootloader.iml" filepath="$PROJECT_DIR$/.idea/bootloader.iml" />
</modules>
</component>
</project>

View File

@@ -1,11 +0,0 @@
cmake_minimum_required(VERSION 3.16)
# Minimal ESP-IDF project to build only the bootloader
# You must have ESP-IDF installed and IDF_PATH exported.
# Pin the target to ESP32-C6 to ensure correct bootloader build
# (must be set before including project.cmake)
set(IDF_TARGET "esp32c6")
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
project(custom_bootloader)

View File

@@ -1,43 +0,0 @@
Custom ESP-IDF Bootloader (Rollback Enabled)
This minimal project builds a custom ESP-IDF bootloader with rollback support enabled.
You can flash it later alongside a Rust firmware using `espflash`.
What this provides
- A minimal ESP-IDF project (CMake) that can build just the bootloader.
- Rollback support enabled via sdkconfig.defaults (CONFIG_BOOTLOADER_APP_ROLLBACK_ENABLE=y).
- A sample OTA partition table (partitions.csv) suitable for OTA and rollback (otadata + two OTA slots).
- A convenience script to build the bootloader for the desired target.
Requirements
- ESP-IDF installed and set up (IDF_PATH exported, Python env activated).
- A selected target (esp32, esp32s3, esp32c3, etc.).
Build
1) Ensure ESP-IDF is set up:
source "$IDF_PATH/export.sh"
2) Pick a target (examples):
idf.py set-target esp32
# or use the script:
./build_bootloader.sh esp32
3) Build only the bootloader:
idf.py bootloader
# or using the script (which also supports setting target):
./build_bootloader.sh esp32
Artifacts
- build/bootloader/bootloader.bin
Using with espflash (Rust)
- For a no_std Rust firmware, you can pass this custom bootloader to espflash:
espflash flash --bootloader build/bootloader/bootloader.bin \
--partition-table partitions.csv \
<your-app-binary-or-elf>
Notes
- Rollback logic requires an OTA layout (otadata + at least two OTA app partitions). The provided partitions.csv is a starting point; adjust sizes/offsets to match your needs.
- This project doesnt build an application; it exists solely to produce a bootloader with the right configuration.
- If you need different log verbosity or features, run `idf.py menuconfig` and then diff/port the changes back into sdkconfig.defaults.
- Targets supported depend on your ESP-IDF version. Use `idf.py set-target <chip>` or `./build_bootloader.sh <chip>`.

View File

@@ -1,41 +0,0 @@
#!/usr/bin/env bash
set -euo pipefail
# Build script for custom ESP-IDF bootloader with rollback enabled.
# Requirements:
# - ESP-IDF installed
# - IDF_PATH exported
# - Python env prepared (the usual ESP-IDF setup)
# Usage:
# ./build_bootloader.sh [esp32|esp32s3|esp32c3|esp32s2|esp32c2|esp32c6|esp32h2]
# If target is omitted, the last configured target will be used.
TARGET=${1:-}
if [[ -z "${IDF_PATH:-}" ]]; then
echo "ERROR: IDF_PATH is not set. Please install ESP-IDF and export the environment (source export.sh)." >&2
exit 1
fi
# shellcheck source=/dev/null
source "$IDF_PATH/export.sh"
if [[ -n "$TARGET" ]]; then
idf.py set-target "$TARGET"
fi
# Ensure sdkconfig.defaults is considered (ESP-IDF does this automatically).
# Build only the bootloader.
idf.py bootloader
echo
BOOTLOADER_BIN="build/bootloader/bootloader.bin"
if [[ -f "$BOOTLOADER_BIN" ]]; then
echo "Bootloader built: $BOOTLOADER_BIN"
echo "You can use this with espflash via:"
echo " espflash flash --bootloader $BOOTLOADER_BIN [--partition-table partitions.csv] <your-app-binary>"
else
echo "ERROR: Bootloader binary not found. Check build logs above." >&2
exit 2
fi
cp build/bootloader/bootloader.bin ../rust/bootloader.bin

View File

@@ -1 +0,0 @@
idf_component_register(SRCS "dummy.c" INCLUDE_DIRS ".")

View File

@@ -1,4 +0,0 @@
// This file intentionally left almost empty.
// ESP-IDF expects at least one component; the bootloader build does not use this.
void __unused_dummy_symbol(void) {}

File diff suppressed because it is too large Load Diff

View File

@@ -1,17 +0,0 @@
# Target can be set with: idf.py set-target esp32|esp32s3|esp32c3|...
# If not set via idf.py, ESP-IDF may default to a target; it's recommended to set it explicitly.
# Explicitly pin target to ESP32-C6
CONFIG_IDF_TARGET="esp32c6"
CONFIG_IDF_TARGET_ESP32C6=y
CONFIG_IDF_TARGET_ARCH_RISCV=y
# Bootloader configuration
CONFIG_BOOTLOADER_APP_ROLLBACK_ENABLE=y
CONFIG_BOOTLOADER_LOG_LEVEL_INFO=y
# Slightly faster boot by skipping GPIO checks unless you need that feature
CONFIG_BOOTLOADER_SKIP_VALIDATE_IN_DEEP_SLEEP=y
# Partition table config is not required to build bootloader, but shown for clarity when you build full app later
# CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions.csv"
# CONFIG_PARTITION_TABLE_FILENAME="partitions.csv"

File diff suppressed because it is too large Load Diff

View File

@@ -1,8 +0,0 @@
# Default ignored files
/shelf/
/workspace.xml
# Editor-based HTTP Client requests
/httpRequests/
# Datasource local storage ignored files
/dataSources/
/dataSources.local.xml

View File

@@ -1,7 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$/.." vcs="Git" />
<mapping directory="$PROJECT_DIR$/../website/themes/blowfish" vcs="Git" />
</component>
</project>

View File

@@ -1,12 +0,0 @@
rm ./src/webserver/index.html.gz
rm ./src/webserver/bundle.js.gz
set -e
cd ./src_webpack/
npx webpack build
cp index.html.gz ../src/webserver/index.html.gz
cp bundle.js.gz ../src/webserver/bundle.js.gz
cd ../
cargo build --release
espflash save-image --bootloader bootloader.bin --partition-table partitions.csv --chip esp32c6 target/riscv32imac-unknown-none-elf/release/plant-ctrl2 image.bin
espflash flash --monitor --bootloader bootloader.bin --chip esp32c6 --baud 921600 --partition-table partitions.csv target/riscv32imac-unknown-none-elf/release/plant-ctrl2

View File

@@ -1,53 +0,0 @@
use vergen::EmitBuilder;
fn linker_be_nice() {
let args: Vec<String> = std::env::args().collect();
if args.len() > 1 {
let kind = &args[1];
let what = &args[2];
match kind.as_str() {
"undefined-symbol" => match what.as_str() {
"_defmt_timestamp" => {
eprintln!();
eprintln!("💡 `defmt` not found - make sure `defmt.x` is added as a linker script and you have included `use defmt_rtt as _;`");
eprintln!();
}
"_stack_start" => {
eprintln!();
eprintln!("💡 Is the linker script `linkall.x` missing?");
eprintln!();
}
"esp_wifi_preempt_enable"
| "esp_wifi_preempt_yield_task"
| "esp_wifi_preempt_task_create" => {
eprintln!();
eprintln!("💡 `esp-wifi` has no scheduler enabled. Make sure you have the `builtin-scheduler` feature enabled, or that you provide an external scheduler.");
eprintln!();
}
"embedded_test_linker_file_not_added_to_rustflags" => {
eprintln!();
eprintln!("💡 `embedded-test` not found - make sure `embedded-test.x` is added as a linker script for tests");
eprintln!();
}
_ => (),
},
// we don't have anything helpful for "missing-lib" yet
_ => {
std::process::exit(1);
}
}
std::process::exit(0);
}
println!(
"cargo:rustc-link-arg=--error-handling-script={}",
std::env::current_exe().unwrap().display()
);
}
fn main() {
linker_be_nice();
let _ = EmitBuilder::builder().all_git().all_build().emit();
}

View File

@@ -1,11 +0,0 @@
rm ./src/webserver/index.html.gz
rm ./src/webserver/bundle.js.gz
set -e
cd ./src_webpack/
npx webpack build
cp index.html.gz ../src/webserver/index.html.gz
cp bundle.js.gz ../src/webserver/bundle.js.gz
cd ../
cargo build --release
espflash flash --monitor --bootloader bootloader.bin --chip esp32c6 --baud 921600 --partition-table partitions.csv target/riscv32imac-unknown-none-elf/release/plant-ctrl2

View File

@@ -1,13 +0,0 @@
rm image.bin
rm ./src/webserver/index.html.gz
rm ./src/webserver/bundle.js.gz
set -e
cd ./src_webpack/
npx webpack build
cp index.html.gz ../src/webserver/index.html.gz
cp bundle.js.gz ../src/webserver/bundle.js.gz
cd ../
set -e
cargo build --release
espflash save-image --bootloader bootloader.bin --partition-table partitions.csv --chip esp32c6 target/riscv32imac-unknown-none-elf/release/plant-ctrl2 image.bin

View File

@@ -1,6 +0,0 @@
nvs, data, nvs, , 16k,
otadata, data, ota, , 8k,
phy_init, data, phy, , 4k,
ota_0, app, ota_0, , 3968k,
ota_1, app, ota_1, , 3968k,
storage, data, littlefs,, 8M,
1 nvs data nvs 16k
2 otadata data ota 8k
3 phy_init data phy 4k
4 ota_0 app ota_0 3968k
5 ota_1 app ota_1 3968k
6 storage data littlefs 8M

View File

@@ -1,2 +0,0 @@
[toolchain]
channel = "nightly"

View File

@@ -1,994 +0,0 @@
use crate::bail;
use crate::config::{NetworkConfig, PlantControllerConfig};
use crate::hal::{PLANT_COUNT, TIME_ACCESS};
use crate::log::{LogMessage, LOG_ACCESS};
use chrono::{DateTime, Utc};
use serde::Serialize;
use crate::fat_error::{ContextExt, FatError, FatResult};
use crate::hal::little_fs2storage_adapter::LittleFs2Filesystem;
use alloc::string::ToString;
use alloc::sync::Arc;
use alloc::{format, string::String, vec, vec::Vec};
use core::net::{IpAddr, Ipv4Addr, SocketAddr};
use core::str::FromStr;
use core::sync::atomic::Ordering;
use embassy_executor::Spawner;
use embassy_net::udp::UdpSocket;
use embassy_net::{DhcpConfig, Ipv4Cidr, Runner, Stack, StackResources, StaticConfigV4};
use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex;
use embassy_sync::mutex::{Mutex, MutexGuard};
use embassy_sync::once_lock::OnceLock;
use embassy_time::{Duration, Timer, WithTimeout};
use embedded_storage::nor_flash::{check_erase, NorFlash, ReadNorFlash};
use esp_bootloader_esp_idf::ota::OtaImageState::Valid;
use esp_bootloader_esp_idf::ota::{Ota, OtaImageState, Slot};
use esp_bootloader_esp_idf::partitions::FlashRegion;
use esp_hal::gpio::{Input, RtcPinWithResistors};
use esp_hal::rng::Rng;
use esp_hal::rtc_cntl::{
sleep::{TimerWakeupSource, WakeupLevel},
Rtc,
};
use esp_hal::system::software_reset;
use esp_println::println;
use esp_storage::FlashStorage;
use esp_wifi::wifi::{
AccessPointConfiguration, AccessPointInfo, AuthMethod, ClientConfiguration, Configuration,
ScanConfig, ScanTypeConfig, WifiController, WifiDevice, WifiState,
};
use littlefs2::fs::Filesystem;
use littlefs2_core::{FileType, PathBuf, SeekFrom};
use log::{info, warn};
use mcutie::{
Error, McutieBuilder, McutieReceiver, McutieTask, MqttMessage, PublishDisplay, Publishable,
QoS, Topic,
};
use portable_atomic::AtomicBool;
use smoltcp::socket::udp::PacketMetadata;
use smoltcp::wire::DnsQueryType;
use sntpc::{get_time, NtpContext, NtpTimestampGenerator};
#[esp_hal::ram(rtc_fast, persistent)]
static mut LAST_WATERING_TIMESTAMP: [i64; PLANT_COUNT] = [0; PLANT_COUNT];
#[esp_hal::ram(rtc_fast, persistent)]
static mut CONSECUTIVE_WATERING_PLANT: [u32; PLANT_COUNT] = [0; PLANT_COUNT];
#[esp_hal::ram(rtc_fast, persistent)]
static mut LOW_VOLTAGE_DETECTED: i8 = 0;
#[esp_hal::ram(rtc_fast, persistent)]
static mut RESTART_TO_CONF: i8 = 0;
const CONFIG_FILE: &str = "config.json";
const NTP_SERVER: &str = "pool.ntp.org";
static MQTT_CONNECTED_EVENT_RECEIVED: AtomicBool = AtomicBool::new(false);
static MQTT_ROUND_TRIP_RECEIVED: AtomicBool = AtomicBool::new(false);
pub static MQTT_STAY_ALIVE: AtomicBool = AtomicBool::new(false);
static MQTT_BASE_TOPIC: OnceLock<String> = OnceLock::new();
#[derive(Serialize, Debug)]
pub struct FileInfo {
filename: String,
size: usize,
}
#[derive(Serialize, Debug)]
pub struct FileList {
total: usize,
used: usize,
files: Vec<FileInfo>,
}
#[derive(Copy, Clone, Default)]
struct Timestamp {
stamp: DateTime<Utc>,
}
// Minimal esp-idf equivalent for gpio_hold on esp32c6 via ROM functions
extern "C" {
fn gpio_pad_hold(gpio_num: u32);
fn gpio_pad_unhold(gpio_num: u32);
}
#[inline(always)]
pub fn hold_enable(gpio_num: u8) {
unsafe { gpio_pad_hold(gpio_num as u32) }
}
#[inline(always)]
pub fn hold_disable(gpio_num: u8) {
unsafe { gpio_pad_unhold(gpio_num as u32) }
}
impl NtpTimestampGenerator for Timestamp {
fn init(&mut self) {
self.stamp = DateTime::default();
}
fn timestamp_sec(&self) -> u64 {
self.stamp.timestamp() as u64
}
fn timestamp_subsec_micros(&self) -> u32 {
self.stamp.timestamp_subsec_micros()
}
}
pub struct Esp<'a> {
pub fs: Arc<Mutex<CriticalSectionRawMutex, Filesystem<'static, LittleFs2Filesystem>>>,
pub rng: Rng,
//first starter (ap or sta will take these)
pub interface_sta: Option<WifiDevice<'static>>,
pub interface_ap: Option<WifiDevice<'static>>,
pub controller: Arc<Mutex<CriticalSectionRawMutex, WifiController<'static>>>,
pub boot_button: Input<'a>,
// RTC-capable GPIO used as external wake source (store the raw peripheral)
pub wake_gpio1: esp_hal::peripherals::GPIO1<'static>,
pub ota: Ota<'static, FlashStorage>,
pub ota_target: &'static mut FlashRegion<'static, FlashStorage>,
pub current: Slot,
pub slot0_state: OtaImageState,
pub slot1_state: OtaImageState,
}
// SAFETY: On this target we never move Esp across OS threads; the firmware runs single-core
// cooperative tasks with Embassy. All interior mutability of non-Send peripherals is gated
// behind &mut self or embassy_sync Mutex with CriticalSectionRawMutex, which does not rely on
// thread scheduling. Therefore it is sound to mark Esp as Send to satisfy trait object bounds
// (e.g., Box<dyn BoardInteraction + Send>). If you add fields that are accessed from multiple
// CPU cores/threads, reconsider this.
unsafe impl Send for Esp<'_> {}
macro_rules! mk_static {
($t:ty,$val:expr) => {{
static STATIC_CELL: static_cell::StaticCell<$t> = static_cell::StaticCell::new();
#[deny(unused_attributes)]
let x = STATIC_CELL.uninit().write(($val));
x
}};
}
impl Esp<'_> {
pub(crate) async fn delete_file(&self, filename: String) -> FatResult<()> {
let file = PathBuf::try_from(filename.as_str())?;
let access = self.fs.lock().await;
access.remove(&*file)?;
Ok(())
}
pub(crate) async fn write_file(
&mut self,
filename: String,
offset: u32,
buf: &[u8],
) -> Result<(), FatError> {
let file = PathBuf::try_from(filename.as_str())?;
let access = self.fs.lock().await;
access.open_file_with_options_and_then(
|options| options.read(true).write(true).create(true),
&*file,
|file| {
file.seek(SeekFrom::Start(offset))?;
file.write(buf)?;
Ok(())
},
)?;
Ok(())
}
pub async fn get_size(&mut self, filename: String) -> FatResult<usize> {
let file = PathBuf::try_from(filename.as_str())?;
let access = self.fs.lock().await;
let data = access.metadata(&*file)?;
Ok(data.len())
}
pub(crate) async fn get_file(
&mut self,
filename: String,
chunk: u32,
) -> FatResult<([u8; 512], usize)> {
use littlefs2::io::Error as lfs2Error;
let file = PathBuf::try_from(filename.as_str())?;
let access = self.fs.lock().await;
let mut buf = [0_u8; 512];
let mut read = 0;
let offset = chunk * buf.len() as u32;
access.open_file_with_options_and_then(
|options| options.read(true),
&*file,
|file| {
let length = file.len()? as u32;
if length == 0 {
Err(lfs2Error::IO)
} else if length > offset {
file.seek(SeekFrom::Start(offset))?;
read = file.read(&mut buf)?;
Ok(())
} else {
//exactly at end, do nothing
Ok(())
}
},
)?;
Ok((buf, read))
}
pub(crate) async fn write_ota(&mut self, offset: u32, buf: &[u8]) -> Result<(), FatError> {
let _ = check_erase(self.ota_target, offset, offset + 4096);
self.ota_target.erase(offset, offset + 4096)?;
let mut temp = vec![0; buf.len()];
let read_back = temp.as_mut_slice();
//change to nor flash, align writes!
self.ota_target.write(offset, buf)?;
self.ota_target.read(offset, read_back)?;
if buf != read_back {
info!("Expected {:?} but got {:?}", buf, read_back);
bail!(
"Flash error, read back does not match write buffer at offset {:x}",
offset
)
}
Ok(())
}
pub(crate) async fn finalize_ota(&mut self) -> Result<(), FatError> {
let current = self.ota.current_slot()?;
if self.ota.current_ota_state()? != OtaImageState::Valid {
info!(
"Validating current slot {:?} as it was able to ota",
current
);
self.ota.set_current_ota_state(Valid)?;
}
self.ota.set_current_slot(current.next())?;
info!("switched slot");
self.ota.set_current_ota_state(OtaImageState::New)?;
info!("switched state for new partition");
let state_new = self.ota.current_ota_state()?;
info!("state on new partition now {:?}", state_new);
//determine nextslot crc
self.set_restart_to_conf(true);
Ok(())
}
pub(crate) fn mode_override_pressed(&mut self) -> bool {
self.boot_button.is_low()
}
pub(crate) async fn sntp(
&mut self,
_max_wait_ms: u32,
stack: Stack<'_>,
) -> FatResult<DateTime<Utc>> {
println!("start sntp");
let mut rx_meta = [PacketMetadata::EMPTY; 16];
let mut rx_buffer = [0; 4096];
let mut tx_meta = [PacketMetadata::EMPTY; 16];
let mut tx_buffer = [0; 4096];
let mut socket = UdpSocket::new(
stack,
&mut rx_meta,
&mut rx_buffer,
&mut tx_meta,
&mut tx_buffer,
);
socket.bind(123).unwrap();
let context = NtpContext::new(Timestamp::default());
let ntp_addrs = stack
.dns_query(NTP_SERVER, DnsQueryType::A)
.await
.expect("Failed to resolve DNS");
if ntp_addrs.is_empty() {
bail!("Failed to resolve DNS");
}
info!("NTP server: {:?}", ntp_addrs);
let mut counter = 0;
loop {
let addr: IpAddr = ntp_addrs[0].into();
let timeout = get_time(SocketAddr::from((addr, 123)), &socket, context)
.with_timeout(Duration::from_millis((_max_wait_ms / 10) as u64))
.await;
match timeout {
Ok(result) => {
let time = result?;
info!("Time: {:?}", time);
return DateTime::from_timestamp(time.seconds as i64, 0)
.context("Could not convert Sntp result");
}
Err(err) => {
warn!("sntp timeout, retry: {:?}", err);
counter += 1;
if counter > 10 {
bail!("Failed to get time from NTP server");
}
Timer::after(Duration::from_millis(100)).await;
}
}
}
}
pub(crate) async fn wifi_scan(&mut self) -> FatResult<Vec<AccessPointInfo>> {
info!("start wifi scan");
let mut lock = self.controller.try_lock()?;
info!("start wifi scan lock");
let scan_config = ScanConfig {
ssid: None,
bssid: None,
channel: None,
show_hidden: false,
scan_type: ScanTypeConfig::Active {
min: Default::default(),
max: Default::default(),
},
};
let rv = lock.scan_with_config_async(scan_config).await?;
info!("end wifi scan lock");
Ok(rv)
}
pub(crate) fn last_pump_time(&self, plant: usize) -> Option<DateTime<Utc>> {
let ts = unsafe { LAST_WATERING_TIMESTAMP }[plant];
DateTime::from_timestamp_millis(ts)
}
pub(crate) fn store_last_pump_time(&mut self, plant: usize, time: DateTime<Utc>) {
unsafe {
LAST_WATERING_TIMESTAMP[plant] = time.timestamp_millis();
}
}
pub(crate) fn set_low_voltage_in_cycle(&mut self) {
unsafe {
LOW_VOLTAGE_DETECTED = 1;
}
}
pub(crate) fn clear_low_voltage_in_cycle(&mut self) {
unsafe {
LOW_VOLTAGE_DETECTED = 0;
}
}
pub(crate) fn low_voltage_in_cycle(&mut self) -> bool {
unsafe { LOW_VOLTAGE_DETECTED == 1 }
}
pub(crate) fn store_consecutive_pump_count(&mut self, plant: usize, count: u32) {
unsafe {
CONSECUTIVE_WATERING_PLANT[plant] = count;
}
}
pub(crate) fn consecutive_pump_count(&mut self, plant: usize) -> u32 {
unsafe { CONSECUTIVE_WATERING_PLANT[plant] }
}
pub(crate) fn get_restart_to_conf(&mut self) -> bool {
unsafe { RESTART_TO_CONF == 1 }
}
pub(crate) fn set_restart_to_conf(&mut self, to_conf: bool) {
unsafe {
if to_conf {
RESTART_TO_CONF = 1;
} else {
RESTART_TO_CONF = 0;
}
}
}
pub(crate) async fn wifi_ap(&mut self) -> FatResult<Stack<'static>> {
let ssid = match self.load_config().await {
Ok(config) => config.network.ap_ssid.as_str().to_string(),
Err(_) => "PlantCtrl Emergency Mode".to_string(),
};
let spawner = Spawner::for_current_executor().await;
let device = self.interface_ap.take().unwrap();
let gw_ip_addr_str = "192.168.71.1";
let gw_ip_addr = Ipv4Addr::from_str(gw_ip_addr_str).expect("failed to parse gateway ip");
let config = embassy_net::Config::ipv4_static(StaticConfigV4 {
address: Ipv4Cidr::new(gw_ip_addr, 24),
gateway: Some(gw_ip_addr),
dns_servers: Default::default(),
});
let seed = (self.rng.random() as u64) << 32 | self.rng.random() as u64;
println!("init secondary stack");
// Init network stack
let (stack, runner) = embassy_net::new(
device,
config,
mk_static!(StackResources<4>, StackResources::<4>::new()),
seed,
);
let stack = mk_static!(Stack, stack);
let client_config = Configuration::AccessPoint(AccessPointConfiguration {
ssid: ssid.clone(),
..Default::default()
});
self.controller
.lock()
.await
.set_configuration(&client_config)?;
println!("start new");
self.controller.lock().await.start()?;
println!("start net task");
spawner.spawn(net_task(runner)).ok();
println!("run dhcp");
spawner.spawn(run_dhcp(stack.clone(), gw_ip_addr_str)).ok();
loop {
if stack.is_link_up() {
break;
}
Timer::after(Duration::from_millis(500)).await;
}
while !stack.is_config_up() {
Timer::after(Duration::from_millis(100)).await
}
println!("Connect to the AP `${ssid}` and point your browser to http://{gw_ip_addr_str}/");
stack
.config_v4()
.inspect(|c| println!("ipv4 config: {c:?}"));
Ok(stack.clone())
}
pub(crate) async fn wifi(
&mut self,
network_config: &NetworkConfig,
) -> FatResult<Stack<'static>> {
esp_wifi::wifi_set_log_verbose();
let ssid = network_config.ssid.clone();
match &ssid {
Some(ssid) => {
if ssid.is_empty() {
bail!("Wifi ssid was empty")
}
}
None => {
bail!("Wifi ssid was empty")
}
}
let ssid = ssid.unwrap().to_string();
info!("attempting to connect wifi {ssid}");
let password = match network_config.password {
Some(ref password) => password.to_string(),
None => "".to_string(),
};
let max_wait = network_config.max_wait;
let spawner = Spawner::for_current_executor().await;
let device = self.interface_sta.take().unwrap();
let config = embassy_net::Config::dhcpv4(DhcpConfig::default());
let seed = (self.rng.random() as u64) << 32 | self.rng.random() as u64;
// Init network stack
let (stack, runner) = embassy_net::new(
device,
config,
mk_static!(StackResources<8>, StackResources::<8>::new()),
seed,
);
let stack = mk_static!(Stack, stack);
let client_config = Configuration::Client(ClientConfiguration {
ssid,
bssid: None,
auth_method: AuthMethod::WPA2Personal, //FIXME read from config, fill via scan
password,
channel: None,
});
self.controller
.lock()
.await
.set_configuration(&client_config)?;
spawner.spawn(net_task(runner)).ok();
self.controller.lock().await.start_async().await?;
let timeout = {
let guard = TIME_ACCESS.get().await.lock().await;
guard.current_time_us()
} + max_wait as u64 * 1000;
loop {
let state = esp_wifi::wifi::sta_state();
match state {
WifiState::StaStarted => {
self.controller.lock().await.connect()?;
break;
}
_ => {}
}
if {
let guard = TIME_ACCESS.get().await.lock().await;
guard.current_time_us()
} > timeout
{
bail!("Timeout waiting for wifi sta ready")
}
Timer::after(Duration::from_millis(500)).await;
}
let timeout = {
let guard = TIME_ACCESS.get().await.lock().await;
guard.current_time_us()
} + max_wait as u64 * 1000;
loop {
let state = esp_wifi::wifi::sta_state();
match state {
WifiState::StaConnected => {
break;
}
_ => {}
}
if {
let guard = TIME_ACCESS.get().await.lock().await;
guard.current_time_us()
} > timeout
{
bail!("Timeout waiting for wifi sta connected")
}
Timer::after(Duration::from_millis(500)).await;
}
let timeout = {
let guard = TIME_ACCESS.get().await.lock().await;
guard.current_time_us()
} + max_wait as u64 * 1000;
while !stack.is_link_up() {
if {
let guard = TIME_ACCESS.get().await.lock().await;
guard.current_time_us()
} > timeout
{
bail!("Timeout waiting for wifi link up")
}
Timer::after(Duration::from_millis(500)).await;
}
let timeout = {
let guard = TIME_ACCESS.get().await.lock().await;
guard.current_time_us()
} + max_wait as u64 * 1000;
while !stack.is_config_up() {
if {
let guard = TIME_ACCESS.get().await.lock().await;
guard.current_time_us()
} > timeout
{
bail!("Timeout waiting for wifi config up")
}
Timer::after(Duration::from_millis(100)).await
}
info!("Connected WIFI, dhcp: {:?}", stack.config_v4());
Ok(stack.clone())
}
pub fn deep_sleep(
&mut self,
duration_in_ms: u64,
mut rtc: MutexGuard<CriticalSectionRawMutex, Rtc>,
) -> ! {
// Configure and enter deep sleep using esp-hal. Also keep prior behavior where
// duration_in_ms == 0 triggers an immediate reset.
// Mark the current OTA image as valid if we reached here while in pending verify.
if let Ok(cur) = self.ota.current_ota_state() {
if cur == OtaImageState::PendingVerify {
self.ota
.set_current_ota_state(OtaImageState::Valid)
.expect("Could not set image to valid");
}
}
if duration_in_ms == 0 {
software_reset();
} else {
let timer = TimerWakeupSource::new(core::time::Duration::from_millis(duration_in_ms));
let mut wake_pins: [(&mut dyn RtcPinWithResistors, WakeupLevel); 1] =
[(&mut self.wake_gpio1, WakeupLevel::Low)];
let ext1 = esp_hal::rtc_cntl::sleep::Ext1WakeupSource::new(&mut wake_pins);
rtc.sleep_deep(&[&timer, &ext1]);
}
}
pub(crate) async fn load_config(&mut self) -> FatResult<PlantControllerConfig> {
let cfg = PathBuf::try_from(CONFIG_FILE)?;
let config_exist = self.fs.lock().await.exists(&cfg);
if !config_exist {
bail!("No config file stored")
}
let data = self.fs.lock().await.read::<4096>(&cfg)?;
let config: PlantControllerConfig = serde_json::from_slice(&data)?;
return Ok(config);
}
pub(crate) async fn save_config(&mut self, config: Vec<u8>) -> FatResult<()> {
let filesystem = self.fs.lock().await;
let cfg = PathBuf::try_from(CONFIG_FILE)?;
filesystem.write(&cfg, &*config)?;
Ok(())
}
pub(crate) async fn list_files(&self) -> FatResult<FileList> {
let path = PathBuf::new();
let fs = self.fs.lock().await;
let free_size = fs.available_space()?;
let total_size = fs.total_space();
let mut result = FileList {
total: total_size,
used: total_size - free_size,
files: Vec::new(),
};
fs.read_dir_and_then(&path, |dir| {
for entry in dir {
let e = entry?;
if e.file_type() == FileType::File {
result.files.push(FileInfo {
filename: e.path().to_string(),
size: e.metadata().len(),
});
}
}
Ok(())
})?;
Ok(result)
}
pub(crate) async fn init_rtc_deepsleep_memory(
&self,
init_rtc_store: bool,
to_config_mode: bool,
) {
if init_rtc_store {
unsafe {
LAST_WATERING_TIMESTAMP = [0; PLANT_COUNT];
CONSECUTIVE_WATERING_PLANT = [0; PLANT_COUNT];
LOW_VOLTAGE_DETECTED = 0;
if to_config_mode {
RESTART_TO_CONF = 1
} else {
RESTART_TO_CONF = 0;
}
};
} else {
unsafe {
if to_config_mode {
RESTART_TO_CONF = 1;
}
LOG_ACCESS
.lock()
.await
.log(
LogMessage::RestartToConfig,
RESTART_TO_CONF as u32,
0,
"",
"",
)
.await;
LOG_ACCESS
.lock()
.await
.log(
LogMessage::LowVoltage,
LOW_VOLTAGE_DETECTED as u32,
0,
"",
"",
)
.await;
for i in 0..PLANT_COUNT {
log::info!(
"LAST_WATERING_TIMESTAMP[{}] = UTC {}",
i,
LAST_WATERING_TIMESTAMP[i]
);
}
for i in 0..PLANT_COUNT {
log::info!(
"CONSECUTIVE_WATERING_PLANT[{}] = {}",
i,
CONSECUTIVE_WATERING_PLANT[i]
);
}
}
}
}
pub(crate) async fn mqtt(
&mut self,
network_config: &'static NetworkConfig,
stack: Stack<'static>,
) -> FatResult<()> {
let base_topic = network_config
.base_topic
.as_ref()
.context("missing base topic")?;
if base_topic.is_empty() {
bail!("Mqtt base_topic was empty")
}
MQTT_BASE_TOPIC
.init(base_topic.to_string())
.map_err(|_| FatError::String {
error: "Error setting basetopic".to_string(),
})?;
let mqtt_url = network_config
.mqtt_url
.as_ref()
.context("missing mqtt url")?;
if mqtt_url.is_empty() {
bail!("Mqtt url was empty")
}
let last_will_topic = format!("{}/state", base_topic);
let round_trip_topic = format!("{}/internal/roundtrip", base_topic);
let stay_alive_topic = format!("{}/stay_alive", base_topic);
let mut builder: McutieBuilder<'_, String, PublishDisplay<String, &str>, 0> =
McutieBuilder::new(stack, "plant ctrl", mqtt_url);
if network_config.mqtt_user.is_some() && network_config.mqtt_password.is_some() {
builder = builder.with_authentication(
network_config.mqtt_user.as_ref().unwrap().as_str(),
network_config.mqtt_password.as_ref().unwrap().as_str(),
);
info!("With authentification");
}
let lwt = Topic::General(last_will_topic);
let lwt = mk_static!(Topic<String>, lwt);
let lwt = lwt.with_display("lost").retain(true).qos(QoS::AtLeastOnce);
builder = builder.with_last_will(lwt);
//TODO make configurable
builder = builder.with_device_id("plantctrl");
let builder: McutieBuilder<'_, String, PublishDisplay<String, &str>, 2> = builder
.with_subscriptions([
Topic::General(round_trip_topic.clone()),
Topic::General(stay_alive_topic.clone()),
]);
let keep_alive = Duration::from_secs(60 * 60 * 2).as_secs() as u16;
let (receiver, task) = builder.build(keep_alive);
let spawner = Spawner::for_current_executor().await;
spawner.spawn(mqtt_incoming_task(
receiver,
round_trip_topic.clone(),
stay_alive_topic.clone(),
))?;
spawner.spawn(mqtt_runner(task))?;
LOG_ACCESS
.lock()
.await
.log(LogMessage::StayAlive, 0, 0, "", &stay_alive_topic)
.await;
LOG_ACCESS
.lock()
.await
.log(LogMessage::MqttInfo, 0, 0, "", mqtt_url)
.await;
let mqtt_timeout = 15000;
let timeout = {
let guard = TIME_ACCESS.get().await.lock().await;
guard.current_time_us()
} + mqtt_timeout as u64 * 1000;
while !MQTT_CONNECTED_EVENT_RECEIVED.load(Ordering::Relaxed) {
let cur = TIME_ACCESS.get().await.lock().await.current_time_us();
if cur > timeout {
bail!("Timeout waiting MQTT connect event")
}
Timer::after(Duration::from_millis(100)).await;
}
Topic::General(round_trip_topic.clone())
.with_display("online_text")
.publish()
.await
.unwrap();
let timeout = {
let guard = TIME_ACCESS.get().await.lock().await;
guard.current_time_us()
} + mqtt_timeout as u64 * 1000;
while !MQTT_ROUND_TRIP_RECEIVED.load(Ordering::Relaxed) {
let cur = TIME_ACCESS.get().await.lock().await.current_time_us();
if cur > timeout {
//ensure we do not further try to publish
MQTT_CONNECTED_EVENT_RECEIVED.store(false, Ordering::Relaxed);
bail!("Timeout waiting MQTT roundtrip")
}
Timer::after(Duration::from_millis(100)).await;
}
Ok(())
}
pub(crate) async fn mqtt_inner(&mut self, subtopic: &str, message: &str) -> FatResult<()> {
if !subtopic.starts_with("/") {
bail!("Subtopic without / at start {}", subtopic);
}
if subtopic.len() > 192 {
bail!("Subtopic exceeds 192 chars {}", subtopic);
}
let base_topic = MQTT_BASE_TOPIC
.try_get()
.context("missing base topic in static!")?;
let full_topic = format!("{base_topic}{subtopic}");
loop {
let result = Topic::General(full_topic.as_str())
.with_display(message)
.retain(true)
.publish()
.await;
match result {
Ok(()) => return Ok(()),
Err(err) => {
let retry = match err {
Error::IOError => false,
Error::TimedOut => true,
Error::TooLarge => false,
Error::PacketError => false,
Error::Invalid => false,
};
if !retry {
bail!(
"Error during mqtt send on topic {} with message {:#?} error is {:?}",
&full_topic,
message,
err
);
}
info!(
"Retransmit for {} with message {:#?} error is {:?} retrying {}",
&full_topic, message, err, retry
);
Timer::after(Duration::from_millis(100)).await;
}
}
}
}
pub(crate) async fn mqtt_publish(&mut self, subtopic: &str, message: &str) {
let online = MQTT_CONNECTED_EVENT_RECEIVED.load(Ordering::Relaxed);
if !online {
return;
}
let roundtrip_ok = MQTT_ROUND_TRIP_RECEIVED.load(Ordering::Relaxed);
if !roundtrip_ok {
info!("MQTT roundtrip not received yet, dropping message");
return;
}
match self.mqtt_inner(subtopic, message).await {
Ok(()) => {}
Err(err) => {
info!(
"Error during mqtt send on topic {} with message {:#?} error is {:?}",
subtopic, message, err
);
}
};
}
}
#[embassy_executor::task]
async fn mqtt_runner(
task: McutieTask<'static, String, PublishDisplay<'static, String, &'static str>, 2>,
) {
task.run().await;
}
#[embassy_executor::task]
async fn mqtt_incoming_task(
receiver: McutieReceiver,
round_trip_topic: String,
stay_alive_topic: String,
) {
loop {
let message = receiver.receive().await;
match message {
MqttMessage::Connected => {
info!("Mqtt connected");
MQTT_CONNECTED_EVENT_RECEIVED.store(true, Ordering::Relaxed);
}
MqttMessage::Publish(topic, payload) => match topic {
Topic::DeviceType(_type_topic) => {}
Topic::Device(_device_topic) => {}
Topic::General(topic) => {
let subtopic = topic.as_str();
if subtopic.eq(round_trip_topic.as_str()) {
MQTT_ROUND_TRIP_RECEIVED.store(true, Ordering::Relaxed);
} else if subtopic.eq(stay_alive_topic.as_str()) {
let value = payload.eq_ignore_ascii_case("true".as_ref())
|| payload.eq_ignore_ascii_case("1".as_ref());
let a = match value {
true => 1,
false => 0,
};
LOG_ACCESS
.lock()
.await
.log(LogMessage::MqttStayAliveRec, a, 0, "", "")
.await;
MQTT_STAY_ALIVE.store(value, Ordering::Relaxed);
} else {
LOG_ACCESS
.lock()
.await
.log(LogMessage::UnknownTopic, 0, 0, "", &*topic)
.await;
}
}
},
MqttMessage::Disconnected => {
MQTT_CONNECTED_EVENT_RECEIVED.store(false, Ordering::Relaxed);
info!("Mqtt disconnected");
}
MqttMessage::HomeAssistantOnline => {
info!("Home assistant is online");
}
}
}
}
#[embassy_executor::task(pool_size = 2)]
async fn net_task(mut runner: Runner<'static, WifiDevice<'static>>) {
runner.run().await;
}
#[embassy_executor::task]
async fn run_dhcp(stack: Stack<'static>, gw_ip_addr: &'static str) {
use core::net::{Ipv4Addr, SocketAddrV4};
use edge_dhcp::{
io::{self, DEFAULT_SERVER_PORT},
server::{Server, ServerOptions},
};
use edge_nal::UdpBind;
use edge_nal_embassy::{Udp, UdpBuffers};
let ip = Ipv4Addr::from_str(gw_ip_addr).expect("dhcp task failed to parse gw ip");
let mut buf = [0u8; 1500];
let mut gw_buf = [Ipv4Addr::UNSPECIFIED];
let buffers = UdpBuffers::<3, 1024, 1024, 10>::new();
let unbound_socket = Udp::new(stack, &buffers);
let mut bound_socket = unbound_socket
.bind(SocketAddr::V4(SocketAddrV4::new(
Ipv4Addr::UNSPECIFIED,
DEFAULT_SERVER_PORT,
)))
.await
.unwrap();
loop {
_ = io::server::run(
&mut Server::<_, 64>::new_with_et(ip),
&ServerOptions::new(ip, Some(&mut gw_buf)),
&mut bound_socket,
&mut buf,
)
.await
.inspect_err(|e| log::warn!("DHCP server error: {e:?}"));
Timer::after(Duration::from_millis(500)).await;
}
}

View File

@@ -1,88 +0,0 @@
use embedded_storage::nor_flash::{check_erase, NorFlash, ReadNorFlash};
use esp_bootloader_esp_idf::partitions::FlashRegion;
use esp_storage::FlashStorage;
use littlefs2::consts::U4096 as lfsCache;
use littlefs2::consts::U512 as lfsLookahead;
use littlefs2::driver::Storage as lfs2Storage;
use littlefs2::io::Error as lfs2Error;
use littlefs2::io::Result as lfs2Result;
use log::error;
pub struct LittleFs2Filesystem {
pub(crate) storage: &'static mut FlashRegion<'static, FlashStorage>,
}
impl lfs2Storage for LittleFs2Filesystem {
const READ_SIZE: usize = 4096;
const WRITE_SIZE: usize = 4096;
const BLOCK_SIZE: usize = 4096; //usually optimal for flash access
const BLOCK_COUNT: usize = 8 * 1000 * 1000 / 4096; //8Mb in 4k blocks + a little space for stupid calculation errors
const BLOCK_CYCLES: isize = 100;
type CACHE_SIZE = lfsCache;
type LOOKAHEAD_SIZE = lfsLookahead;
fn read(&mut self, off: usize, buf: &mut [u8]) -> lfs2Result<usize> {
let read_size: usize = Self::READ_SIZE;
if off % read_size != 0 {
error!("Littlefs2Filesystem read error: offset not aligned to read size offset: {} read_size: {}", off, read_size);
return Err(lfs2Error::IO);
}
if buf.len() % read_size != 0 {
error!("Littlefs2Filesystem read error: length not aligned to read size length: {} read_size: {}", buf.len(), read_size);
return Err(lfs2Error::IO);
}
match self.storage.read(off as u32, buf) {
Ok(..) => Ok(buf.len()),
Err(err) => {
error!("Littlefs2Filesystem read error: {:?}", err);
Err(lfs2Error::IO)
}
}
}
fn write(&mut self, off: usize, data: &[u8]) -> lfs2Result<usize> {
let write_size: usize = Self::WRITE_SIZE;
if off % write_size != 0 {
error!("Littlefs2Filesystem write error: offset not aligned to write size offset: {} write_size: {}", off, write_size);
return Err(lfs2Error::IO);
}
if data.len() % write_size != 0 {
error!("Littlefs2Filesystem write error: length not aligned to write size length: {} write_size: {}", data.len(), write_size);
return Err(lfs2Error::IO);
}
match self.storage.write(off as u32, data) {
Ok(..) => Ok(data.len()),
Err(err) => {
error!("Littlefs2Filesystem write error: {:?}", err);
Err(lfs2Error::IO)
}
}
}
fn erase(&mut self, off: usize, len: usize) -> lfs2Result<usize> {
let block_size: usize = Self::BLOCK_SIZE;
if off % block_size != 0 {
error!("Littlefs2Filesystem erase error: offset not aligned to block size offset: {} block_size: {}", off, block_size);
return lfs2Result::Err(lfs2Error::IO);
}
if len % block_size != 0 {
error!("Littlefs2Filesystem erase error: length not aligned to block size length: {} block_size: {}", len, block_size);
return lfs2Result::Err(lfs2Error::IO);
}
match check_erase(self.storage, off as u32, (off+len) as u32) {
Ok(_) => {}
Err(err) => {
error!("Littlefs2Filesystem check erase error: {:?}", err);
return lfs2Result::Err(lfs2Error::IO);
}
}
match self.storage.erase(off as u32, (off + len) as u32) {
Ok(..) => lfs2Result::Ok(len),
Err(err) => {
error!("Littlefs2Filesystem erase error: {:?}", err);
lfs2Result::Err(lfs2Error::IO)
}
}
}
}

View File

@@ -1,450 +0,0 @@
use crate::bail;
use crate::fat_error::FatError;
use crate::hal::esp::{hold_disable, hold_enable};
use crate::hal::rtc::RTCModuleInteraction;
use crate::hal::v3_shift_register::ShiftRegister40;
use crate::hal::water::TankSensor;
use crate::hal::{BoardInteraction, FreePeripherals, Moistures, Sensor, PLANT_COUNT, TIME_ACCESS};
use crate::log::{LogMessage, LOG_ACCESS};
use crate::{
config::PlantControllerConfig,
hal::{battery::BatteryInteraction, esp::Esp},
};
use alloc::boxed::Box;
use alloc::format;
use alloc::string::ToString;
use async_trait::async_trait;
use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex;
use embassy_sync::mutex::Mutex;
use embassy_time::Timer;
use embedded_hal::digital::OutputPin as _;
use esp_hal::gpio::{Flex, Input, InputConfig, Level, Output, OutputConfig, Pull};
use esp_hal::pcnt::channel::CtrlMode::Keep;
use esp_hal::pcnt::channel::EdgeMode::{Hold, Increment};
use esp_hal::pcnt::unit::Unit;
use measurements::{Current, Voltage};
const PUMP8_BIT: usize = 0;
const PUMP1_BIT: usize = 1;
const PUMP2_BIT: usize = 2;
const PUMP3_BIT: usize = 3;
const PUMP4_BIT: usize = 4;
const PUMP5_BIT: usize = 5;
const PUMP6_BIT: usize = 6;
const PUMP7_BIT: usize = 7;
const MS_0: usize = 8;
const MS_4: usize = 9;
const MS_2: usize = 10;
const MS_3: usize = 11;
const MS_1: usize = 13;
const SENSOR_ON: usize = 12;
const SENSOR_A_1: u8 = 7;
const SENSOR_A_2: u8 = 6;
const SENSOR_A_3: u8 = 5;
const SENSOR_A_4: u8 = 4;
const SENSOR_A_5: u8 = 3;
const SENSOR_A_6: u8 = 2;
const SENSOR_A_7: u8 = 1;
const SENSOR_A_8: u8 = 0;
const SENSOR_B_1: u8 = 8;
const SENSOR_B_2: u8 = 9;
const SENSOR_B_3: u8 = 10;
const SENSOR_B_4: u8 = 11;
const SENSOR_B_5: u8 = 12;
const SENSOR_B_6: u8 = 13;
const SENSOR_B_7: u8 = 14;
const SENSOR_B_8: u8 = 15;
const CHARGING: usize = 14;
const AWAKE: usize = 15;
const FAULT_3: usize = 16;
const FAULT_8: usize = 17;
const FAULT_7: usize = 18;
const FAULT_6: usize = 19;
const FAULT_5: usize = 20;
const FAULT_4: usize = 21;
const FAULT_1: usize = 22;
const FAULT_2: usize = 23;
const REPEAT_MOIST_MEASURE: usize = 1;
pub struct V3<'a> {
config: PlantControllerConfig,
battery_monitor: Box<dyn BatteryInteraction + Send>,
rtc_module: Box<dyn RTCModuleInteraction + Send>,
esp: Esp<'a>,
shift_register:
Mutex<CriticalSectionRawMutex, ShiftRegister40<Output<'a>, Output<'a>, Output<'a>>>,
_shift_register_enable_invert: Output<'a>,
tank_sensor: TankSensor<'a>,
solar_is_day: Input<'a>,
light: Output<'a>,
main_pump: Output<'a>,
general_fault: Output<'a>,
pub signal_counter: Unit<'static, 0>,
}
pub(crate) fn create_v3(
peripherals: FreePeripherals<'static>,
esp: Esp<'static>,
config: PlantControllerConfig,
battery_monitor: Box<dyn BatteryInteraction + Send>,
rtc_module: Box<dyn RTCModuleInteraction + Send>,
) -> Result<Box<dyn BoardInteraction<'static> + Send + 'static>, FatError> {
log::info!("Start v3");
let clock = Output::new(peripherals.gpio15, Level::Low, OutputConfig::default());
let latch = Output::new(peripherals.gpio3, Level::Low, OutputConfig::default());
let data = Output::new(peripherals.gpio23, Level::Low, OutputConfig::default());
let shift_register = ShiftRegister40::new(clock, latch, data);
//disable all
for mut pin in shift_register.decompose() {
let _ = pin.set_low();
}
// Set always-on status bits
let _ = shift_register.decompose()[AWAKE].set_high();
let _ = shift_register.decompose()[CHARGING].set_high();
// Multiplexer defaults: ms0..ms3 low, ms4 high (disabled)
let _ = shift_register.decompose()[MS_0].set_low();
let _ = shift_register.decompose()[MS_1].set_low();
let _ = shift_register.decompose()[MS_2].set_low();
let _ = shift_register.decompose()[MS_3].set_low();
let _ = shift_register.decompose()[MS_4].set_high();
let one_wire_pin = Flex::new(peripherals.gpio18);
let tank_power_pin = Output::new(peripherals.gpio11, Level::Low, OutputConfig::default());
let flow_sensor_pin = Input::new(
peripherals.gpio4,
InputConfig::default().with_pull(Pull::Up),
);
let tank_sensor = TankSensor::create(
one_wire_pin,
peripherals.adc1,
peripherals.gpio5,
tank_power_pin,
flow_sensor_pin,
peripherals.pcnt1,
)?;
let solar_is_day = Input::new(peripherals.gpio7, InputConfig::default());
let light = Output::new(peripherals.gpio10, Level::Low, OutputConfig::default());
let mut main_pump = Output::new(peripherals.gpio2, Level::Low, OutputConfig::default());
main_pump.set_low();
let mut general_fault = Output::new(peripherals.gpio6, Level::Low, OutputConfig::default());
general_fault.set_low();
let mut shift_register_enable_invert =
Output::new(peripherals.gpio21, Level::Low, OutputConfig::default());
shift_register_enable_invert.set_low();
let signal_counter = peripherals.pcnt0;
signal_counter.set_high_limit(Some(i16::MAX))?;
let ch0 = &signal_counter.channel0;
let edge_pin = Input::new(peripherals.gpio22, InputConfig::default());
ch0.set_edge_signal(edge_pin.peripheral_input());
ch0.set_input_mode(Hold, Increment);
ch0.set_ctrl_mode(Keep, Keep);
signal_counter.listen();
Ok(Box::new(V3 {
config,
battery_monitor,
rtc_module,
esp,
shift_register: Mutex::new(shift_register),
_shift_register_enable_invert: shift_register_enable_invert,
tank_sensor,
solar_is_day,
light,
main_pump,
general_fault,
signal_counter,
}))
}
impl V3<'_> {
async fn inner_measure_moisture_hz(&mut self, plant: usize, sensor: Sensor) -> Result<f32, FatError> {
let mut results = [0_f32; REPEAT_MOIST_MEASURE];
for repeat in 0..REPEAT_MOIST_MEASURE {
self.signal_counter.pause();
self.signal_counter.clear();
//Disable all
{
let shift_register = self.shift_register.lock().await;
shift_register.decompose()[MS_4].set_high()?;
}
let sensor_channel = match sensor {
Sensor::A => match plant {
0 => SENSOR_A_1,
1 => SENSOR_A_2,
2 => SENSOR_A_3,
3 => SENSOR_A_4,
4 => SENSOR_A_5,
5 => SENSOR_A_6,
6 => SENSOR_A_7,
7 => SENSOR_A_8,
_ => bail!("Invalid plant id {}", plant),
},
Sensor::B => match plant {
0 => SENSOR_B_1,
1 => SENSOR_B_2,
2 => SENSOR_B_3,
3 => SENSOR_B_4,
4 => SENSOR_B_5,
5 => SENSOR_B_6,
6 => SENSOR_B_7,
7 => SENSOR_B_8,
_ => bail!("Invalid plant id {}", plant),
},
};
let is_bit_set = |b: u8| -> bool { sensor_channel & (1 << b) != 0 };
{
let shift_register = self.shift_register.lock().await;
let pin_0 = &mut shift_register.decompose()[MS_0];
let pin_1 = &mut shift_register.decompose()[MS_1];
let pin_2 = &mut shift_register.decompose()[MS_2];
let pin_3 = &mut shift_register.decompose()[MS_3];
if is_bit_set(0) {
pin_0.set_high()?;
} else {
pin_0.set_low()?;
}
if is_bit_set(1) {
pin_1.set_high()?;
} else {
pin_1.set_low()?;
}
if is_bit_set(2) {
pin_2.set_high()?;
} else {
pin_2.set_low()?;
}
if is_bit_set(3) {
pin_3.set_high()?;
} else {
pin_3.set_low()?;
}
shift_register.decompose()[MS_4].set_low()?;
shift_register.decompose()[SENSOR_ON].set_high()?;
}
let measurement = 100; //how long to measure and then extrapolate to hz
let factor = 1000f32 / measurement as f32; //scale raw cound by this number to get hz
//give some time to stabilize
Timer::after_millis(10).await;
self.signal_counter.resume();
Timer::after_millis(measurement).await;
self.signal_counter.pause();
{
let shift_register = self.shift_register.lock().await;
shift_register.decompose()[MS_4].set_high()?;
shift_register.decompose()[SENSOR_ON].set_low()?;
}
Timer::after_millis(10).await;
let unscaled = self.signal_counter.value();
let hz = unscaled as f32 * factor;
LOG_ACCESS
.lock()
.await
.log(
LogMessage::RawMeasure,
unscaled as u32,
hz as u32,
&plant.to_string(),
&format!("{sensor:?}"),
)
.await;
results[repeat] = hz;
}
results.sort_by(|a, b| a.partial_cmp(b).unwrap()); // floats don't seem to implement total_ord
let mid = results.len() / 2;
let median = results[mid];
Ok(median)
}
}
#[async_trait(?Send)]
impl<'a> BoardInteraction<'a> for V3<'a> {
fn get_tank_sensor(&mut self) -> Result<&mut TankSensor<'a>, FatError> {
Ok(&mut self.tank_sensor)
}
fn get_esp(&mut self) -> &mut Esp<'a> {
&mut self.esp
}
fn get_config(&mut self) -> &PlantControllerConfig {
&self.config
}
fn get_battery_monitor(&mut self) -> &mut Box<dyn BatteryInteraction + Send> {
&mut self.battery_monitor
}
fn get_rtc_module(&mut self) -> &mut Box<dyn RTCModuleInteraction + Send> {
&mut self.rtc_module
}
async fn set_charge_indicator(&mut self, charging: bool) -> Result<(), FatError> {
let shift_register = self.shift_register.lock().await;
if charging {
let _ = shift_register.decompose()[CHARGING].set_high();
} else {
let _ = shift_register.decompose()[CHARGING].set_low();
}
Ok(())
}
async fn deep_sleep(&mut self, duration_in_ms: u64) -> ! {
let _ = self.shift_register.lock().await.decompose()[AWAKE].set_low();
let guard = TIME_ACCESS.get().await.lock().await;
self.esp.deep_sleep(duration_in_ms, guard)
}
fn is_day(&self) -> bool {
self.solar_is_day.is_high()
}
async fn light(&mut self, enable: bool) -> Result<(), FatError> {
hold_disable(10);
if enable {
self.light.set_high();
} else {
self.light.set_low();
}
hold_enable(10);
Ok(())
}
async fn pump(&mut self, plant: usize, enable: bool) -> Result<(), FatError> {
if enable {
self.main_pump.set_high();
}
let index = match plant {
0 => PUMP1_BIT,
1 => PUMP2_BIT,
2 => PUMP3_BIT,
3 => PUMP4_BIT,
4 => PUMP5_BIT,
5 => PUMP6_BIT,
6 => PUMP7_BIT,
7 => PUMP8_BIT,
_ => bail!("Invalid pump {plant}"),
};
let shift_register = self.shift_register.lock().await;
if enable {
let _ = shift_register.decompose()[index].set_high();
} else {
let _ = shift_register.decompose()[index].set_low();
}
if !enable {
self.main_pump.set_low();
}
Ok(())
}
async fn pump_current(&mut self, _plant: usize) -> Result<Current, FatError> {
bail!("Not implemented in v3")
}
async fn fault(&mut self, plant: usize, enable: bool) -> Result<(), FatError> {
let index = match plant {
0 => FAULT_1,
1 => FAULT_2,
2 => FAULT_3,
3 => FAULT_4,
4 => FAULT_5,
5 => FAULT_6,
6 => FAULT_7,
7 => FAULT_8,
_ => panic!("Invalid plant id {}", plant),
};
let shift_register = self.shift_register.lock().await;
if enable {
let _ = shift_register.decompose()[index].set_high();
} else {
let _ = shift_register.decompose()[index].set_low();
}
Ok(())
}
async fn measure_moisture_hz(&mut self) -> Result<Moistures, FatError> {
let mut result = Moistures::default();
for plant in 0..PLANT_COUNT {
let a = self.inner_measure_moisture_hz(plant, Sensor::A).await;
let b = self.inner_measure_moisture_hz(plant, Sensor::B).await;
let aa = a.unwrap_or_else(|_| u32::MAX as f32);
let bb = b.unwrap_or_else(|_| u32::MAX as f32);
LOG_ACCESS
.lock()
.await
.log(LogMessage::TestSensor, aa as u32, bb as u32, &plant.to_string(), "")
.await;
result.sensor_a_hz[plant] = aa;
result.sensor_b_hz[plant] = bb;
}
Ok(result)
}
async fn general_fault(&mut self, enable: bool) {
hold_disable(6);
if enable {
self.general_fault.set_high();
} else {
self.general_fault.set_low();
}
hold_enable(6);
}
async fn test(&mut self) -> Result<(), FatError> {
self.general_fault(true).await;
Timer::after_millis(100).await;
self.general_fault(false).await;
Timer::after_millis(100).await;
self.light(true).await?;
Timer::after_millis(500).await;
self.light(false).await?;
Timer::after_millis(500).await;
for i in 0..PLANT_COUNT {
self.fault(i, true).await?;
Timer::after_millis(500).await;
self.fault(i, false).await?;
Timer::after_millis(500).await;
}
for i in 0..PLANT_COUNT {
self.pump(i, true).await?;
Timer::after_millis(100).await;
self.pump(i, false).await?;
Timer::after_millis(100).await;
}
self.measure_moisture_hz().await?;
Timer::after_millis(10).await;
Ok(())
}
fn set_config(&mut self, config: PlantControllerConfig) {
self.config = config;
}
async fn get_mptt_voltage(&mut self) -> Result<Voltage, FatError> {
bail!("Not implemented in v3")
}
async fn get_mptt_current(&mut self) -> Result<Current, FatError> {
bail!("Not implemented in v3")
}
}

View File

@@ -1,310 +0,0 @@
use canapi::id::{classify, plant_id, MessageKind, IDENTIFY_CMD_OFFSET};
use crate::bail;
use crate::fat_error::{ContextExt, FatError, FatResult};
use canapi::{SensorSlot};
use crate::hal::{DetectionResult, Moistures, Sensor};
use crate::hal::Box;
use crate::log::{LogMessage, LOG_ACCESS};
use alloc::format;
use alloc::string::ToString;
use async_trait::async_trait;
use bincode::config;
use embassy_embedded_hal::shared_bus::blocking::i2c::I2cDevice;
use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex;
use embassy_time::{Duration, Instant, Timer, WithTimeout};
use embedded_can::{Frame, Id};
use esp_hal::gpio::Output;
use esp_hal::i2c::master::I2c;
use esp_hal::pcnt::unit::Unit;
use esp_hal::twai::{EspTwaiFrame, StandardId, Twai, TwaiConfiguration};
use esp_hal::{Async, Blocking};
use log::{error, info, warn};
use pca9535::{GPIOBank, Pca9535Immediate, StandardExpanderInterface};
const REPEAT_MOIST_MEASURE: usize = 10;
#[async_trait(?Send)]
pub trait SensorInteraction {
async fn measure_moisture_hz(&mut self) -> FatResult<Moistures>;
}
const MS0: u8 = 1_u8;
const MS1: u8 = 0_u8;
const MS2: u8 = 3_u8;
const MS3: u8 = 4_u8;
const MS4: u8 = 2_u8;
const SENSOR_ON: u8 = 5_u8;
pub enum SensorImpl {
PulseCounter {
signal_counter: Unit<'static, 0>,
sensor_expander:
Pca9535Immediate<I2cDevice<'static, CriticalSectionRawMutex, I2c<'static, Blocking>>>,
},
CanBus {
twai_config: Option<TwaiConfiguration<'static, Blocking>>,
can_power: Output<'static>,
},
}
#[async_trait(?Send)]
impl SensorInteraction for SensorImpl {
async fn measure_moisture_hz(&mut self) -> FatResult<Moistures> {
match self {
SensorImpl::PulseCounter {
signal_counter,
sensor_expander,
..
} => {
let mut result = Moistures::default();
for plant in 0..crate::hal::PLANT_COUNT{
result.sensor_a_hz[plant] = Self::inner_pulse(plant, Sensor::A, signal_counter, sensor_expander).await?;
info!("Sensor {} {:?}: {}", plant, Sensor::A, result.sensor_a_hz[plant]);
result.sensor_b_hz[plant] = Self::inner_pulse(plant, Sensor::B, signal_counter, sensor_expander).await?;
info!("Sensor {} {:?}: {}", plant, Sensor::B, result.sensor_b_hz[plant]);
}
Ok(result)
}
SensorImpl::CanBus {
twai_config,
can_power,
} => {
can_power.set_high();
let config = twai_config.take().expect("twai config not set");
let mut twai = config.start();
loop {
let rec = twai.receive();
match rec {
Ok(_) => {}
Err(err) => {
info!("Error receiving CAN message: {:?}", err);
break;
}
}
}
Timer::after_millis(10).await;
let can = Self::inner_can(&mut twai).await;
can_power.set_low();
let config = twai.stop();
twai_config.replace(config);
let value = can?;
Ok(value)
}
}
}
}
impl SensorImpl {
pub async fn autodetect(&mut self) -> FatResult<DetectionResult> {
match self {
SensorImpl::PulseCounter { .. } => {
bail!("Only CAN bus implementation supports autodetection")
}
SensorImpl::CanBus {
twai_config,
can_power,
} => {
// Power on CAN transceiver and start controller
can_power.set_high();
let config = twai_config.take().expect("twai config not set");
let mut as_async = config.into_async().start();
// Give CAN some time to stabilize
Timer::after_millis(10).await;
// Send a few test messages per potential sensor node
for plant in 0..crate::hal::PLANT_COUNT {
for sensor in [Sensor::A, Sensor::B] {
let target = StandardId::new(plant_id(IDENTIFY_CMD_OFFSET, sensor.into(), plant as u16)).context(">> Could not create address for sensor! (plant: {}) <<")?;
let can_buffer = [0_u8; 0];
if let Some(frame) = EspTwaiFrame::new(target, &can_buffer) {
// Try a few times; we intentionally ignore rx here and rely on stub logic
let resu = as_async.transmit_async(&frame).await;
match resu {
Ok(_) => {
info!(
"Sent test message to plant {} sensor {:?}",
plant, sensor
);
}
Err(err) => {
info!("Error sending test message to plant {} sensor {:?}: {:?}", plant, sensor, err);
}
}
} else {
info!("Error building CAN frame");
}
}
}
let mut result = DetectionResult::default();
// Wait for messages to arrive
let _ = Self::wait_for_can_measurements(&mut as_async, &mut result).with_timeout(Duration::from_millis(5000)).await;
let config = as_async.stop().into_blocking();
can_power.set_low();
twai_config.replace(config);
info!("Autodetection result: {:?}", result);
Ok(result)
}
}
}
async fn wait_for_can_measurements(as_async: &mut Twai<'_, Async>, result: &mut DetectionResult) {
loop {
match as_async.receive_async().await {
Ok(can_frame) => {
match can_frame.id() {
Id::Standard(id) => {
info!("Received CAN message: {:?}", id);
let rawid = id.as_raw();
match classify(rawid) {
None => {}
Some(msg) => {
info!("received message of kind {:?} (plant: {}, sensor: {:?})", msg.0, msg.1, msg.2);
if msg.0 == MessageKind::MoistureData {
let plant = msg.1 as usize;
let sensor = msg.2;
match sensor {
SensorSlot::A => {
result.plant[plant].sensor_a = true;
}
SensorSlot::B => {
result.plant[plant].sensor_b = true;
}
}
}
}
}
}
Id::Extended(ext) => {
warn!("Received extended ID: {:?}", ext);
}
}
}
Err(err) => {
error!("Error receiving CAN message: {:?}", err);
break;
}
}
}
}
pub async fn inner_pulse(plant: usize, sensor: Sensor, signal_counter: &mut Unit<'_, 0>, sensor_expander: &mut Pca9535Immediate<I2cDevice<'static, CriticalSectionRawMutex, I2c<'static, Blocking>>>) -> FatResult<f32> {
let mut results = [0_f32; REPEAT_MOIST_MEASURE];
for repeat in 0..REPEAT_MOIST_MEASURE {
signal_counter.pause();
signal_counter.clear();
//Disable all
sensor_expander.pin_set_high(GPIOBank::Bank0, MS4)?;
let sensor_channel = match sensor {
Sensor::A => plant as u32,
Sensor::B => (15 - plant) as u32,
};
let is_bit_set = |b: u8| -> bool { sensor_channel & (1 << b) != 0 };
if is_bit_set(0) {
sensor_expander.pin_set_high(GPIOBank::Bank0, MS0)?;
} else {
sensor_expander.pin_set_low(GPIOBank::Bank0, MS0)?;
}
if is_bit_set(1) {
sensor_expander.pin_set_high(GPIOBank::Bank0, MS1)?;
} else {
sensor_expander.pin_set_low(GPIOBank::Bank0, MS1)?;
}
if is_bit_set(2) {
sensor_expander.pin_set_high(GPIOBank::Bank0, MS2)?;
} else {
sensor_expander.pin_set_low(GPIOBank::Bank0, MS2)?;
}
if is_bit_set(3) {
sensor_expander.pin_set_high(GPIOBank::Bank0, MS3)?;
} else {
sensor_expander.pin_set_low(GPIOBank::Bank0, MS3)?;
}
sensor_expander.pin_set_low(GPIOBank::Bank0, MS4)?;
sensor_expander.pin_set_high(GPIOBank::Bank0, SENSOR_ON)?;
let measurement = 100; // TODO what is this scaling factor? what is its purpose?
let factor = 1000f32 / measurement as f32;
//give some time to stabilize
Timer::after_millis(10).await;
signal_counter.resume();
Timer::after_millis(measurement).await;
signal_counter.pause();
sensor_expander.pin_set_high(GPIOBank::Bank0, MS4)?;
sensor_expander.pin_set_low(GPIOBank::Bank0, SENSOR_ON)?;
sensor_expander.pin_set_low(GPIOBank::Bank0, MS0)?;
sensor_expander.pin_set_low(GPIOBank::Bank0, MS1)?;
sensor_expander.pin_set_low(GPIOBank::Bank0, MS2)?;
sensor_expander.pin_set_low(GPIOBank::Bank0, MS3)?;
Timer::after_millis(10).await;
let unscaled = 1337; //signal_counter.get_counter_value()? as i32;
let hz = unscaled as f32 * factor;
LOG_ACCESS
.lock()
.await
.log(
LogMessage::RawMeasure,
unscaled as u32,
hz as u32,
&plant.to_string(),
&format!("{sensor:?}"),
)
.await;
results[repeat] = hz;
}
results.sort_by(|a, b| a.partial_cmp(b).unwrap()); // floats don't seem to implement total_ord
let mid = results.len() / 2;
let median = results[mid];
Ok(median)
}
async fn inner_can(
twai: &mut Twai<'static, Blocking>,
) -> FatResult<Moistures> {
[0_u8; 8];
config::standard();
let timeout = Instant::now()
.checked_add(embassy_time::Duration::from_millis(100))
.context("Timeout")?;
loop {
let answer = twai.receive();
match answer {
Ok(answer) => {
info!("Received CAN message: {:?}", answer);
}
Err(error) => match error {
nb::Error::Other(error) => {
return Err(FatError::CanBusError { error });
}
nb::Error::WouldBlock => {
if Instant::now() > timeout {
bail!("Timeout waiting for CAN answer");
}
Timer::after_millis(10).await;
}
},
}
}
}
}

View File

@@ -1,191 +0,0 @@
use crate::fat_error::{FatError, FatResult};
use crate::hal::rtc::X25;
use crate::BOARD_ACCESS;
use alloc::borrow::ToOwned;
use alloc::format;
use alloc::string::{String, ToString};
use chrono::DateTime;
use edge_http::io::server::Connection;
use embedded_io_async::{Read, Write};
use log::info;
use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize, PartialEq, Debug)]
pub struct WebBackupHeader {
timestamp: String,
size: u16,
}
pub(crate) async fn get_backup_config<T, const N: usize>(
conn: &mut Connection<'_, T, { N }>,
) -> FatResult<Option<u32>>
where
T: Read + Write,
{
// First pass: verify checksum without sending data
let mut checksum = X25.digest();
let mut chunk = 0_usize;
loop {
let mut board = BOARD_ACCESS.get().await.lock().await;
board.board_hal.progress(chunk as u32).await;
let (buf, len, expected_crc) = board
.board_hal
.get_rtc_module()
.get_backup_config(chunk)
.await?;
// Update checksum with the actual data bytes of this chunk
checksum.update(&buf[..len]);
let is_last = len == 0 || len < buf.len();
if is_last {
let actual_crc = checksum.finalize();
if actual_crc != expected_crc {
BOARD_ACCESS
.get()
.await
.lock()
.await
.board_hal
.clear_progress()
.await;
conn.initiate_response(
409,
Some(
format!(
"Checksum mismatch expected {} got {}",
expected_crc, actual_crc
)
.as_str(),
),
&[],
)
.await?;
return Ok(Some(409));
}
break;
}
chunk += 1;
}
// Second pass: stream data
conn.initiate_response(
200,
Some("OK"),
&[
("Access-Control-Allow-Origin", "*"),
("Access-Control-Allow-Headers", "*"),
("Access-Control-Allow-Methods", "*"),
],
)
.await?;
let mut chunk = 0_usize;
loop {
let mut board = BOARD_ACCESS.get().await.lock().await;
board.board_hal.progress(chunk as u32).await;
let (buf, len, _expected_crc) = board
.board_hal
.get_rtc_module()
.get_backup_config(chunk)
.await?;
if len == 0 {
break;
}
conn.write_all(&buf[..len]).await?;
if len < buf.len() {
break;
}
chunk += 1;
}
BOARD_ACCESS
.get()
.await
.lock()
.await
.board_hal
.clear_progress()
.await;
Ok(Some(200))
}
pub(crate) async fn backup_config<T, const N: usize>(
conn: &mut Connection<'_, T, N>,
) -> FatResult<Option<String>>
where
T: Read + Write,
{
let mut offset = 0_usize;
let mut buf = [0_u8; 32];
let mut checksum = X25.digest();
let mut counter = 0;
loop {
let to_write = conn.read(&mut buf).await?;
if to_write == 0 {
info!("backup finished");
break;
} else {
let mut board = BOARD_ACCESS.get().await.lock().await;
board.board_hal.progress(counter).await;
counter = counter + 1;
board
.board_hal
.get_rtc_module()
.backup_config(offset, &buf[0..to_write])
.await?;
checksum.update(&buf[0..to_write]);
}
offset = offset + to_write;
}
let mut board = BOARD_ACCESS.get().await.lock().await;
board
.board_hal
.get_rtc_module()
.backup_config_finalize(checksum.finalize(), offset)
.await?;
board.board_hal.clear_progress().await;
conn.initiate_response(
200,
Some("OK"),
&[
("Access-Control-Allow-Origin", "*"),
("Access-Control-Allow-Headers", "*"),
("Access-Control-Allow-Methods", "*"),
],
)
.await?;
Ok(Some("saved".to_owned()))
}
pub(crate) async fn backup_info<T, const N: usize>(
_request: &mut Connection<'_, T, N>,
) -> Result<Option<String>, FatError>
where
T: Read + Write,
{
let mut board = BOARD_ACCESS.get().await.lock().await;
let header = board.board_hal.get_rtc_module().get_backup_info().await;
let json = match header {
Ok(h) => {
let timestamp = DateTime::from_timestamp_millis(h.timestamp).unwrap();
let wbh = WebBackupHeader {
timestamp: timestamp.to_rfc3339(),
size: h.size,
};
serde_json::to_string(&wbh)?
}
Err(err) => {
let wbh = WebBackupHeader {
timestamp: err.to_string(),
size: 0,
};
serde_json::to_string(&wbh)?
}
};
Ok(Some(json))
}

View File

@@ -1,160 +0,0 @@
use crate::fat_error::{FatError, FatResult};
use crate::webserver::read_up_to_bytes_from_request;
use crate::BOARD_ACCESS;
use alloc::borrow::ToOwned;
use alloc::format;
use alloc::string::String;
use edge_http::io::server::Connection;
use edge_http::Method;
use embedded_io_async::{Read, Write};
use log::info;
pub(crate) async fn list_files<T, const N: usize>(
_request: &mut Connection<'_, T, N>,
) -> FatResult<Option<String>> {
let mut board = BOARD_ACCESS.get().await.lock().await;
let result = board.board_hal.get_esp().list_files().await?;
let file_list_json = serde_json::to_string(&result)?;
Ok(Some(file_list_json))
}
pub(crate) async fn file_operations<T, const N: usize>(
conn: &mut Connection<'_, T, { N }>,
method: Method,
path: &&str,
prefix: &&str,
) -> Result<Option<u32>, FatError>
where
T: Read + Write,
{
let filename = &path[prefix.len()..];
info!("file request for {} with method {}", filename, method);
Ok(match method {
Method::Delete => {
let mut board = BOARD_ACCESS.get().await.lock().await;
board
.board_hal
.get_esp()
.delete_file(filename.to_owned())
.await?;
conn.initiate_response(
200,
Some("OK"),
&[
("Access-Control-Allow-Origin", "*"),
("Access-Control-Allow-Headers", "*"),
("Access-Control-Allow-Methods", "*"),
],
)
.await?;
Some(200)
}
Method::Get => {
let disposition = format!("attachment; filename=\"{filename}\"");
let size = {
let mut board = BOARD_ACCESS.get().await.lock().await;
board
.board_hal
.get_esp()
.get_size(filename.to_owned())
.await?
};
conn.initiate_response(
200,
Some("OK"),
&[
("Content-Type", "application/octet-stream"),
("Content-Disposition", disposition.as_str()),
("Content-Length", &format!("{}", size)),
("Access-Control-Allow-Origin", "*"),
("Access-Control-Allow-Headers", "*"),
("Access-Control-Allow-Methods", "*"),
],
)
.await?;
let mut chunk = 0;
loop {
let mut board = BOARD_ACCESS.get().await.lock().await;
board.board_hal.progress(chunk).await;
let read_chunk = board
.board_hal
.get_esp()
.get_file(filename.to_owned(), chunk)
.await?;
let length = read_chunk.1;
if length == 0 {
info!("file request for {} finished", filename);
break;
}
let data = &read_chunk.0[0..length];
conn.write_all(data).await?;
if length < read_chunk.0.len() {
info!("file request for {} finished", filename);
break;
}
chunk = chunk + 1;
}
BOARD_ACCESS
.get()
.await
.lock()
.await
.board_hal
.clear_progress()
.await;
Some(200)
}
Method::Post => {
{
let mut board = BOARD_ACCESS.get().await.lock().await;
//ensure the file is deleted first; otherwise we would need to truncate the file which will not work with streaming
let _ = board
.board_hal
.get_esp()
.delete_file(filename.to_owned())
.await;
}
let mut offset = 0_usize;
let mut chunk = 0;
loop {
let buf = read_up_to_bytes_from_request(conn, Some(4096)).await?;
if buf.len() == 0 {
info!("file request for {} finished", filename);
break;
} else {
let mut board = BOARD_ACCESS.get().await.lock().await;
board.board_hal.progress(chunk as u32).await;
board
.board_hal
.get_esp()
.write_file(filename.to_owned(), offset as u32, &buf)
.await?;
}
offset = offset + buf.len();
chunk = chunk + 1;
}
BOARD_ACCESS
.get()
.await
.lock()
.await
.board_hal
.clear_progress()
.await;
conn.initiate_response(
200,
Some("OK"),
&[
("Access-Control-Allow-Origin", "*"),
("Access-Control-Allow-Headers", "*"),
("Access-Control-Allow-Methods", "*"),
],
)
.await?;
Some(200)
}
_ => None,
})
}

View File

@@ -1,186 +0,0 @@
use core::str::FromStr;
use crate::fat_error::{FatError, FatResult};
use crate::hal::{esp_time, PLANT_COUNT};
use crate::log::LogMessage;
use crate::plant_state::{MoistureSensorState, PlantState};
use crate::tank::determine_tank_state;
use crate::{get_version, BOARD_ACCESS};
use alloc::format;
use alloc::string::{String, ToString};
use alloc::vec::Vec;
use chrono_tz::Tz;
use edge_http::io::server::Connection;
use embedded_io_async::{Read, Write};
use log::info;
use serde::Serialize;
#[derive(Serialize, Debug)]
struct LoadData<'a> {
rtc: &'a str,
native: &'a str,
}
#[derive(Serialize, Debug)]
struct Moistures {
moisture_a: Vec<String>,
moisture_b: Vec<String>,
}
#[derive(Serialize, Debug)]
struct SolarState {
mppt_voltage: f32,
mppt_current: f32,
is_day: bool,
}
pub(crate) async fn get_live_moisture<T, const N: usize>(
_request: &mut Connection<'_, T, N>,
) -> FatResult<Option<String>>
where
T: Read + Write,
{
let mut board = BOARD_ACCESS.get().await.lock().await;
let moistures = board.board_hal.measure_moisture_hz().await?;
let mut plant_state = Vec::new();
for i in 0..PLANT_COUNT {
plant_state.push(PlantState::read_hardware_state(moistures, i, &mut board).await);
}
let a = Vec::from_iter(plant_state.iter().map(|s| match &s.sensor_a {
MoistureSensorState::Disabled => "disabled".to_string(),
MoistureSensorState::MoistureValue {
raw_hz,
moisture_percent,
} => {
format!("{moisture_percent:.2}% {raw_hz}hz",)
}
MoistureSensorState::SensorError(err) => format!("{err:?}"),
}));
let b = Vec::from_iter(plant_state.iter().map(|s| match &s.sensor_b {
MoistureSensorState::Disabled => "disabled".to_string(),
MoistureSensorState::MoistureValue {
raw_hz,
moisture_percent,
} => {
format!("{moisture_percent:.2}% {raw_hz}hz",)
}
MoistureSensorState::SensorError(err) => format!("{err:?}"),
}));
let data = Moistures {
moisture_a: a,
moisture_b: b,
};
let json = serde_json::to_string(&data)?;
Ok(Some(json))
}
pub(crate) async fn tank_info<T, const N: usize>(
_request: &mut Connection<'_, T, N>,
) -> Result<Option<String>, FatError>
where
T: Read + Write,
{
let mut board = BOARD_ACCESS.get().await.lock().await;
let tank_state = determine_tank_state(&mut board).await;
//should be multisampled
let sensor = board.board_hal.get_tank_sensor()?;
let water_temp: FatResult<f32> = sensor.water_temperature_c().await;
Ok(Some(serde_json::to_string(&tank_state.as_mqtt_info(
&board.board_hal.get_config().tank,
&water_temp,
))?))
}
pub(crate) async fn get_timezones() -> FatResult<Option<String>> {
// Get all timezones compiled into the binary from chrono-tz
let timezones: Vec<&'static str> = chrono_tz::TZ_VARIANTS.iter().map(|tz| tz.name()).collect();
let json = serde_json::to_string(&timezones)?;
Ok(Some(json))
}
pub(crate) async fn get_solar_state<T, const N: usize>(
_request: &mut Connection<'_, T, N>,
) -> FatResult<Option<String>> {
let mut board = BOARD_ACCESS.get().await.lock().await;
let state = SolarState {
mppt_voltage: board.board_hal.get_mptt_voltage().await?.as_millivolts() as f32,
mppt_current: board.board_hal.get_mptt_current().await?.as_milliamperes() as f32,
is_day: board.board_hal.is_day(),
};
Ok(Some(serde_json::to_string(&state)?))
}
pub(crate) async fn get_version_web<T, const N: usize>(
_request: &mut Connection<'_, T, N>,
) -> FatResult<Option<String>> {
let mut board = BOARD_ACCESS.get().await.lock().await;
Ok(Some(serde_json::to_string(&get_version(&mut board).await)?))
}
pub(crate) async fn get_config<T, const N: usize>(
_request: &mut Connection<'_, T, N>,
) -> FatResult<Option<String>> {
let mut board = BOARD_ACCESS.get().await.lock().await;
let json = serde_json::to_string(&board.board_hal.get_config())?;
Ok(Some(json))
}
pub(crate) async fn get_battery_state<T, const N: usize>(
_request: &mut Connection<'_, T, N>,
) -> FatResult<Option<String>> {
let mut board = BOARD_ACCESS.get().await.lock().await;
let battery_state = board
.board_hal
.get_battery_monitor()
.get_battery_state()
.await?;
Ok(Some(serde_json::to_string(&battery_state)?))
}
pub(crate) async fn get_time<T, const N: usize>(
_request: &mut Connection<'_, T, N>,
) -> FatResult<Option<String>> {
let mut board = BOARD_ACCESS.get().await.lock().await;
let conf = board.board_hal.get_config();
let tz:Tz = match conf.timezone.as_ref(){
None => {
Tz::UTC
}
Some(tz_string) => {
match Tz::from_str(tz_string) {
Ok(tz) => {
tz
}
Err(err) => {
info!("failed parsing timezone {}", err);
Tz::UTC
}
}
}
};
let native = esp_time().await.with_timezone(&tz).to_rfc3339();
let rtc = match board.board_hal.get_rtc_module().get_rtc_time().await {
Ok(time) => time.with_timezone(&tz).to_rfc3339(),
Err(err) => {
format!("Error getting time: {}", err)
}
};
let data = LoadData {
rtc: rtc.as_str(),
native: native.as_str(),
};
let json = serde_json::to_string(&data)?;
Ok(Some(json))
}
pub(crate) async fn get_log_localization_config<T, const N: usize>(
_request: &mut Connection<'_, T, N>,
) -> FatResult<Option<String>> {
Ok(Some(serde_json::to_string(
&LogMessage::to_log_localisation_config(),
)?))
}

View File

@@ -1,36 +0,0 @@
use crate::fat_error::FatResult;
use crate::log::LOG_ACCESS;
use edge_http::io::server::Connection;
use embedded_io_async::{Read, Write};
pub(crate) async fn get_log<T, const N: usize>(
conn: &mut Connection<'_, T, N>,
) -> FatResult<Option<u32>>
where
T: Read + Write,
{
let log = LOG_ACCESS.lock().await.get();
conn.initiate_response(
200,
Some("OK"),
&[
("Content-Type", "text/javascript"),
("Access-Control-Allow-Origin", "*"),
("Access-Control-Allow-Headers", "*"),
("Access-Control-Allow-Methods", "*"),
],
)
.await?;
conn.write_all("[".as_bytes()).await?;
let mut append = false;
for entry in log {
if append {
conn.write_all(",".as_bytes()).await?;
}
append = true;
let json = serde_json::to_string(&entry)?;
conn.write_all(json.as_bytes()).await?;
}
conn.write_all("]".as_bytes()).await?;
Ok(Some(200))
}

View File

@@ -1,50 +0,0 @@
use crate::fat_error::FatError;
use edge_http::io::server::Connection;
use embedded_io_async::{Read, Write};
pub(crate) async fn serve_favicon<T, const N: usize>(
conn: &mut Connection<'_, T, { N }>,
) -> Result<Option<u32>, FatError>
where
T: Read + Write,
{
conn.initiate_response(200, Some("OK"), &[("Content-Type", "image/x-icon")])
.await?;
conn.write_all(include_bytes!("favicon.ico")).await?;
Ok(Some(200))
}
pub(crate) async fn serve_index<T, const N: usize>(
conn: &mut Connection<'_, T, { N }>,
) -> Result<Option<u32>, FatError>
where
T: Read + Write,
{
conn.initiate_response(
200,
Some("OK"),
&[("Content-Type", "text/html"), ("Content-Encoding", "gzip")],
)
.await?;
conn.write_all(include_bytes!("index.html.gz")).await?;
Ok(Some(200))
}
pub(crate) async fn serve_bundle<T, const N: usize>(
conn: &mut Connection<'_, T, { N }>,
) -> Result<Option<u32>, FatError>
where
T: Read + Write,
{
conn.initiate_response(
200,
Some("OK"),
&[
("Content-Type", "text/javascript"),
("Content-Encoding", "gzip"),
],
)
.await?;
conn.write_all(include_bytes!("bundle.js.gz")).await?;
Ok(Some(200))
}

View File

@@ -1,256 +0,0 @@
//offer ota and config mode
mod backup_manager;
mod file_manager;
mod get_json;
mod get_log;
mod get_static;
mod ota;
mod post_json;
use crate::fat_error::{FatError, FatResult};
use crate::webserver::backup_manager::{backup_config, backup_info, get_backup_config};
use crate::webserver::file_manager::{file_operations, list_files};
use crate::webserver::get_json::{
get_battery_state, get_config, get_live_moisture, get_log_localization_config, get_solar_state,
get_time, get_timezones, get_version_web, tank_info,
};
use crate::webserver::get_log::get_log;
use crate::webserver::get_static::{serve_bundle, serve_favicon, serve_index};
use crate::webserver::ota::ota_operations;
use crate::webserver::post_json::{
board_test, night_lamp_test, pump_test, set_config, wifi_scan, write_time, detect_sensors,
};
use crate::{bail, BOARD_ACCESS};
use alloc::borrow::ToOwned;
use alloc::string::{String, ToString};
use alloc::sync::Arc;
use alloc::vec::Vec;
use core::fmt::{Debug, Display};
use core::net::{IpAddr, Ipv4Addr, SocketAddr};
use core::result::Result::Ok;
use core::sync::atomic::{AtomicBool, Ordering};
use edge_http::io::server::{Connection, Handler, Server};
use edge_http::Method;
use edge_nal::TcpBind;
use edge_nal_embassy::{Tcp, TcpBuffers};
use embassy_net::Stack;
use embassy_time::Instant;
use embedded_io_async::{Read, Write};
use log::{error, info};
struct HTTPRequestRouter {
reboot_now: Arc<AtomicBool>,
}
impl Handler for HTTPRequestRouter {
type Error<E: Debug> = FatError;
async fn handle<'a, T, const N: usize>(
&self,
_task_id: impl Display + Copy,
conn: &mut Connection<'a, T, N>,
) -> Result<(), FatError>
where
T: Read + Write,
{
let start = Instant::now();
let headers = conn.headers()?;
let method = headers.method;
let path = headers.path;
let prefix = "/file?filename=";
let status = if path.starts_with(prefix) {
file_operations(conn, method, &path, &prefix).await?
} else if path == "/ota" {
ota_operations(conn, method).await.map_err(|e| {
error!("Error handling ota: {}", e);
e
})?
} else {
match method {
Method::Get => match path {
"/favicon.ico" => serve_favicon(conn).await?,
"/" => serve_index(conn).await?,
"/bundle.js" => serve_bundle(conn).await?,
"/log" => get_log(conn).await?,
"/get_backup_config" => get_backup_config(conn).await?,
&_ => {
let json = match path {
"/version" => Some(get_version_web(conn).await),
"/time" => Some(get_time(conn).await),
"/battery" => Some(get_battery_state(conn).await),
"/solar" => Some(get_solar_state(conn).await),
"/get_config" => Some(get_config(conn).await),
"/files" => Some(list_files(conn).await),
"/log_localization" => Some(get_log_localization_config(conn).await),
"/tank" => Some(tank_info(conn).await),
"/backup_info" => Some(backup_info(conn).await),
"/timezones" => Some(get_timezones().await),
"/moisture" => Some(get_live_moisture(conn).await),
_ => None,
};
match json {
None => None,
Some(json) => Some(handle_json(conn, json).await?),
}
}
},
Method::Post => {
let json = match path {
"/wifiscan" => Some(wifi_scan(conn).await),
"/set_config" => Some(set_config(conn).await),
"/time" => Some(write_time(conn).await),
"/backup_config" => Some(backup_config(conn).await),
"/pumptest" => Some(pump_test(conn).await),
"/lamptest" => Some(night_lamp_test(conn).await),
"/boardtest" => Some(board_test().await),
"/detect_sensors" => Some(detect_sensors().await),
"/reboot" => {
let mut board = BOARD_ACCESS.get().await.lock().await;
board.board_hal.get_esp().set_restart_to_conf(true);
self.reboot_now.store(true, Ordering::Relaxed);
Some(Ok(None))
}
"/exit" => {
let mut board = BOARD_ACCESS.get().await.lock().await;
board.board_hal.get_esp().set_restart_to_conf(false);
self.reboot_now.store(true, Ordering::Relaxed);
Some(Ok(None))
}
_ => None,
};
match json {
None => None,
Some(json) => Some(handle_json(conn, json).await?),
}
}
Method::Options | Method::Delete | Method::Head | Method::Put => None,
_ => None,
}
};
let code = match status {
None => {
conn.initiate_response(404, Some("Not found"), &[]).await?;
404
}
Some(code) => code,
};
conn.complete().await?;
let response_time = Instant::now().duration_since(start).as_millis();
info!("\"{method} {path}\" {code} {response_time}ms");
Ok(())
}
}
async fn read_up_to_bytes_from_request<T, const N: usize>(
request: &mut Connection<'_, T, N>,
limit: Option<usize>,
) -> FatResult<Vec<u8>>
where
T: Read + Write,
{
let max_read = limit.unwrap_or(1024);
let mut data_store = Vec::new();
let mut total_read = 0;
loop {
let left = max_read - total_read;
let mut buf = [0_u8; 64];
let s_buf = if buf.len() <= left {
&mut buf
} else {
&mut buf[0..left]
};
let read = request.read(s_buf).await?;
if read == 0 {
break;
}
let actual_data = &s_buf[0..read];
total_read += read;
if total_read > max_read {
bail!("Request too large {total_read} > {max_read}");
}
data_store.push(actual_data.to_owned());
}
let final_buffer = data_store.concat();
Ok(final_buffer)
}
#[embassy_executor::task]
pub async fn http_server(reboot_now: Arc<AtomicBool>, stack: Stack<'static>) {
let buffer: TcpBuffers<2, 1024, 1024> = TcpBuffers::new();
let tcp = Tcp::new(stack, &buffer);
let acceptor = tcp
.bind(SocketAddr::new(IpAddr::V4(Ipv4Addr::UNSPECIFIED), 80))
.await
.unwrap();
let mut server: Server<2, 512, 15> = Server::new();
server
.run(Some(5000), acceptor, HTTPRequestRouter { reboot_now })
.await
.expect("Tcp stack error");
info!("Webserver started and waiting for connections");
//TODO https if mbed_esp lands
}
async fn handle_json<'a, T, const N: usize>(
conn: &mut Connection<'a, T, N>,
chain: FatResult<Option<String>>,
) -> FatResult<u32>
where
T: Read + Write,
<T as embedded_io_async::ErrorType>::Error: Debug,
{
match chain {
Ok(answer) => match answer {
Some(json) => {
conn.initiate_response(
200,
Some("OK"),
&[
("Access-Control-Allow-Origin", "*"),
("Access-Control-Allow-Headers", "*"),
("Access-Control-Allow-Methods", "*"),
("Content-Type", "application/json"),
],
)
.await?;
conn.write_all(json.as_bytes()).await?;
Ok(200)
}
None => {
conn.initiate_response(
200,
Some("OK"),
&[
("Access-Control-Allow-Origin", "*"),
("Access-Control-Allow-Headers", "*"),
("Access-Control-Allow-Methods", "*"),
],
)
.await?;
Ok(200)
}
},
Err(err) => {
let error_text = err.to_string();
info!("error handling process {}", error_text);
conn.initiate_response(
500,
Some("OK"),
&[
("Access-Control-Allow-Origin", "*"),
("Access-Control-Allow-Headers", "*"),
("Access-Control-Allow-Methods", "*"),
],
)
.await?;
conn.write_all(error_text.as_bytes()).await?;
Ok(500)
}
}
}

View File

@@ -1,76 +0,0 @@
use crate::fat_error::FatError;
use crate::webserver::read_up_to_bytes_from_request;
use crate::BOARD_ACCESS;
use edge_http::io::server::Connection;
use edge_http::Method;
use embedded_io_async::{Read, Write};
use log::info;
pub(crate) async fn ota_operations<T, const N: usize>(
conn: &mut Connection<'_, T, { N }>,
method: Method,
) -> Result<Option<u32>, FatError>
where
T: Read + Write,
{
Ok(match method {
Method::Options => {
conn.initiate_response(
200,
Some("OK"),
&[
("Access-Control-Allow-Origin", "*"),
("Access-Control-Allow-Headers", "*"),
("Access-Control-Allow-Methods", "*"),
],
)
.await?;
Some(200)
}
Method::Post => {
let mut offset = 0_usize;
let mut chunk = 0;
loop {
let buf = read_up_to_bytes_from_request(conn, Some(4096)).await?;
if buf.len() == 0 {
info!("file request for ota finished");
let mut board = BOARD_ACCESS.get().await.lock().await;
board.board_hal.get_esp().finalize_ota().await?;
break;
} else {
let mut board = BOARD_ACCESS.get().await.lock().await;
board.board_hal.progress(chunk as u32).await;
// Erase next block if we are at a 4K boundary (including the first block at offset 0)
info!("erasing and writing block 0x{offset:x}");
board
.board_hal
.get_esp()
.write_ota(offset as u32, &*buf)
.await?;
}
offset = offset + buf.len();
chunk = chunk + 1;
}
BOARD_ACCESS
.get()
.await
.lock()
.await
.board_hal
.clear_progress()
.await;
conn.initiate_response(
200,
Some("OK"),
&[
("Access-Control-Allow-Origin", "*"),
("Access-Control-Allow-Headers", "*"),
("Access-Control-Allow-Methods", "*"),
],
)
.await?;
Some(200)
}
_ => None,
})
}

View File

@@ -1,119 +0,0 @@
use crate::config::PlantControllerConfig;
use crate::fat_error::FatResult;
use crate::hal::esp_set_time;
use crate::webserver::read_up_to_bytes_from_request;
use crate::{do_secure_pump, BOARD_ACCESS};
use alloc::string::{String, ToString};
use alloc::vec::Vec;
use chrono::DateTime;
use edge_http::io::server::Connection;
use embedded_io_async::{Read, Write};
use log::info;
use serde::{Deserialize, Serialize};
#[derive(Deserialize)]
pub struct NightLampCommand {
active: bool,
}
#[derive(Serialize, Debug)]
struct SSIDList {
ssids: Vec<String>,
}
#[derive(Deserialize, Debug)]
struct SetTime<'a> {
time: &'a str,
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
pub struct TestPump {
pump: usize,
}
pub(crate) async fn wifi_scan<T, const N: usize>(
_request: &mut Connection<'_, T, N>,
) -> FatResult<Option<String>> {
let mut board = BOARD_ACCESS.get().await.lock().await;
info!("start wifi scan");
let mut ssids: Vec<String> = Vec::new();
let scan_result = board.board_hal.get_esp().wifi_scan().await?;
scan_result
.iter()
.for_each(|s| ssids.push(s.ssid.to_string()));
let ssid_json = serde_json::to_string(&SSIDList { ssids })?;
info!("Sending ssid list {}", &ssid_json);
Ok(Some(ssid_json))
}
pub(crate) async fn board_test() -> FatResult<Option<String>> {
let mut board = BOARD_ACCESS.get().await.lock().await;
board.board_hal.test().await?;
Ok(None)
}
pub(crate) async fn detect_sensors() -> FatResult<Option<String>> {
let mut board = BOARD_ACCESS.get().await.lock().await;
let result = board.board_hal.detect_sensors().await?;
let json = serde_json::to_string(&result)?;
Ok(Some(json))
}
pub(crate) async fn pump_test<T, const N: usize>(
request: &mut Connection<'_, T, N>,
) -> FatResult<Option<String>>
where
T: Read + Write,
{
let actual_data = read_up_to_bytes_from_request(request, None).await?;
let pump_test: TestPump = serde_json::from_slice(&actual_data)?;
let mut board = BOARD_ACCESS.get().await.lock().await;
let config = &board.board_hal.get_config().plants[pump_test.pump].clone();
let pump_result = do_secure_pump(&mut board, pump_test.pump, config, false).await;
//ensure it is disabled before unwrapping
board.board_hal.pump(pump_test.pump, false).await?;
Ok(Some(serde_json::to_string(&pump_result?)?))
}
pub(crate) async fn night_lamp_test<T, const N: usize>(
request: &mut Connection<'_, T, N>,
) -> FatResult<Option<String>>
where
T: Read + Write,
{
let actual_data = read_up_to_bytes_from_request(request, None).await?;
let light_command: NightLampCommand = serde_json::from_slice(&actual_data)?;
let mut board = BOARD_ACCESS.get().await.lock().await;
board.board_hal.light(light_command.active).await?;
Ok(None)
}
pub(crate) async fn write_time<T, const N: usize>(
request: &mut Connection<'_, T, N>,
) -> FatResult<Option<String>>
where
T: Read + Write,
{
let actual_data = read_up_to_bytes_from_request(request, None).await?;
let time: SetTime = serde_json::from_slice(&actual_data)?;
let parsed = DateTime::parse_from_rfc3339(time.time).unwrap();
esp_set_time(parsed).await?;
Ok(None)
}
pub(crate) async fn set_config<T, const N: usize>(
request: &mut Connection<'_, T, N>,
) -> FatResult<Option<String>>
where
T: Read + Write,
{
let all = read_up_to_bytes_from_request(request, Some(4096)).await?;
let length = all.len();
let config: PlantControllerConfig = serde_json::from_slice(&all)?;
let mut board = BOARD_ACCESS.get().await.lock().await;
board.board_hal.get_esp().save_config(all).await?;
info!("Wrote config config {:?} with size {}", config, length);
board.board_hal.set_config(config);
Ok(Some("Ok".to_string()))
}

View File

@@ -1,4 +0,0 @@
index.html.gz
bundle.js.gz
index.html
bundle.js

View File

@@ -1,93 +0,0 @@
class ToastService {
private container: HTMLElement;
private stylesInjected = false;
constructor() {
this.container = this.ensureContainer();
this.injectStyles();
}
info(message: string, timeoutMs: number = 5000) {
const el = this.createToast(message, 'info');
this.container.appendChild(el);
// Auto-dismiss after timeout
const timer = window.setTimeout(() => this.dismiss(el), timeoutMs);
// Dismiss on click immediately
el.addEventListener('click', () => {
window.clearTimeout(timer);
this.dismiss(el);
});
}
error(message: string) {
const el = this.createToast(message, 'error');
this.container.appendChild(el);
// Only dismiss on click
el.addEventListener('click', () => this.dismiss(el));
}
private dismiss(el: HTMLElement) {
if (!el.parentElement) return;
el.parentElement.removeChild(el);
}
private createToast(message: string, type: 'info' | 'error'): HTMLElement {
const div = document.createElement('div');
div.className = `toast ${type}`;
div.textContent = message;
div.setAttribute('role', 'status');
div.setAttribute('aria-live', 'polite');
return div;
}
private ensureContainer(): HTMLElement {
let container = document.getElementById('toast-container');
if (!container) {
container = document.createElement('div');
container.id = 'toast-container';
document.body.appendChild(container);
}
return container;
}
private injectStyles() {
if (this.stylesInjected) return;
const style = document.createElement('style');
style.textContent = `
#toast-container {
position: fixed;
top: 12px;
right: 12px;
display: flex;
flex-direction: column;
gap: 8px;
z-index: 9999;
}
.toast {
max-width: 320px;
padding: 10px 12px;
border-radius: 6px;
box-shadow: 0 2px 6px rgba(0,0,0,0.2);
cursor: pointer;
user-select: none;
font-family: sans-serif;
font-size: 14px;
line-height: 1.3;
}
.toast.info {
background-color: #d4edda; /* green-ish */
color: #155724;
border-left: 4px solid #28a745;
}
.toast.error {
background-color: #f8d7da; /* red-ish */
color: #721c24;
border-left: 4px solid #dc3545;
}
`;
document.head.appendChild(style);
this.stylesInjected = true;
}
}
export const toast = new ToastService();

View File

@@ -1,14 +0,0 @@
[package]
name = "canapi"
version = "0.1.0"
edition = "2021"
[lib]
name = "canapi"
path = "src/lib.rs"
[features]
default = []
[dependencies]
bincode = { version = "2.0.1", default-features = false, features = ["derive"] }

View File

@@ -1,135 +0,0 @@
#![no_std]
//! CAN bus API shared crate for PlantCtrl sensors and controller.
//! Addressing and messages are defined here to be reused by all bus participants.
use bincode::{Decode, Encode};
/// Total plants supported by addressing (0..=15)
pub const MAX_PLANTS: u8 = 16;
/// Sensors per plant: 0..=1 => A/B
#[derive(Debug, Clone, Copy, PartialEq, Eq, Encode, Decode)]
#[repr(u8)]
pub enum SensorSlot {
A = 0,
B = 1,
}
impl SensorSlot {
pub const fn from_index(idx: u8) -> Option<Self> {
match idx {
0 => Some(SensorSlot::A),
1 => Some(SensorSlot::B),
_ => None,
}
}
}
/// Legacy sensor base address kept for compatibility with existing code.
/// Each plant uses SENSOR_BASE_ADDRESS + plant_index (0..PLANT_COUNT-1).
/// 11-bit standard ID space, safe range.
pub const SENSOR_BASE_ADDRESS: u16 = 1000;
/// Typed topics within the SENSOR_BASE space.
/// Additional offsets allow distinct message semantics while keeping plant-indexed layout.
pub mod id {
use crate::{SensorSlot, MAX_PLANTS, SENSOR_BASE_ADDRESS};
/// Number of plants addressable per sensor slot group
pub const PLANTS_PER_GROUP: u16 = MAX_PLANTS as u16; // 16
/// Offset applied for SensorSlot::B within a message group
pub const B_OFFSET: u16 = PLANTS_PER_GROUP; // 16
// Message group base offsets relative to SENSOR_BASE_ADDRESS
pub const MOISTURE_DATA_OFFSET: u16 = 0; // periodic data from sensor (sensor -> controller)
pub const IDENTIFY_CMD_OFFSET: u16 = 32; // identify LED command (controller -> sensor)
#[inline]
pub const fn plant_id(message_type_offset: u16, sensor: SensorSlot, plant: u16) -> u16 {
match sensor {
SensorSlot::A => SENSOR_BASE_ADDRESS + message_type_offset + plant,
SensorSlot::B => SENSOR_BASE_ADDRESS + message_type_offset + B_OFFSET + plant,
}
}
/// Kinds of message spaces recognized by the addressing scheme.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum MessageKind {
MoistureData, // sensor -> controller
IdentifyCmd, // controller -> sensor
}
/// Try to classify a received 11-bit standard ID into a known message kind and extract plant and sensor slot.
/// Returns (kind, plant, slot) on success.
#[inline]
pub const fn classify(id: u16) -> Option<(MessageKind, u8, SensorSlot)> {
// Ensure the ID is within our base space
if id < SENSOR_BASE_ADDRESS {
return None;
}
let rel = id - SENSOR_BASE_ADDRESS;
// Helper: decode within a given group offset
const fn decode_in_group(rel: u16, group_base: u16) -> Option<(u8, SensorSlot)> {
if rel < group_base { return None; }
let inner = rel - group_base;
if inner < PLANTS_PER_GROUP { // A slot
Some((inner as u8, SensorSlot::A))
} else if inner >= B_OFFSET && inner < B_OFFSET + PLANTS_PER_GROUP { // B slot
Some(((inner - B_OFFSET) as u8, SensorSlot::B))
} else {
None
}
}
// Check known groups in order
if let Some((plant, slot)) = decode_in_group(rel, MOISTURE_DATA_OFFSET) {
return Some((MessageKind::MoistureData, plant, slot));
}
if let Some((plant, slot)) = decode_in_group(rel, IDENTIFY_CMD_OFFSET) {
return Some((MessageKind::IdentifyCmd, plant, slot));
}
None
}
/// Returns Some((plant, slot)) regardless of message kind, if the id falls into any known group; otherwise None.
#[inline]
pub const fn extract_plant_slot(id: u16) -> Option<(u8, SensorSlot)> {
match classify(id) {
Some((_kind, plant, slot)) => Some((plant, slot)),
None => None,
}
}
/// Check if an id corresponds exactly to the given message kind, plant and slot.
#[inline]
pub const fn is_identify_for(id: u16, plant: u8, slot: SensorSlot) -> bool {
id == plant_id(IDENTIFY_CMD_OFFSET, slot, plant as u16)
}
#[inline]
pub const fn is_moisture_data_for(id: u16, plant: u8, slot: SensorSlot) -> bool {
id == plant_id(MOISTURE_DATA_OFFSET, slot, plant as u16)
}
}
/// Periodic moisture data sent by sensors.
/// Fits into 5 bytes with bincode-v2 (no varint): u8 + u8 + u16 = 4, alignment may keep 4.
#[derive(Debug, Clone, Copy, Encode, Decode)]
pub struct MoistureData {
pub plant: u8, // 0..MAX_PLANTS-1
pub sensor: SensorSlot, // A/B
pub hz: u16, // measured frequency of moisture sensor
}
/// Request a sensor to report immediately (controller -> sensor).
#[derive(Debug, Clone, Copy, Encode, Decode)]
pub struct MoistureRequest {
pub plant: u8,
pub sensor: SensorSlot, // target sensor (sensor filters by this)
}
/// Control a sensor's identify LED, if received by sensor, blink for a few seconds
#[derive(Debug, Clone, Copy, Encode, Decode)]
pub struct IdentifyLed {}

Some files were not shown because too many files have changed in this diff Show More