69 Commits

Author SHA1 Message Date
50b2e994ca can prepare stuff 2025-08-14 22:10:53 +02:00
7a7f000743 add missing sensor port labels 2025-08-08 23:24:05 +02:00
d650358bab Add flowsensor prototype, add canbus to backplane 2025-07-27 13:38:53 +02:00
9f113adf7e v4 case 2025-07-26 17:52:19 +02:00
735f836458 fix unit being wrong for solar display 2025-07-02 19:11:36 +02:00
f02d935f40 hide unused html elements based on config test1 2025-06-27 21:23:18 +02:00
cc4e9efffd v4.0 backplane 1.1 2025-06-26 22:38:03 +02:00
5d91daf23d add pump current configuration 2025-06-26 02:39:32 +02:00
577c38d026 allow mppt module to fail during startup without aborting 2025-06-25 01:38:54 +02:00
6b711e29fc extract rtc module, extract tank module, fix backupview refresh, switch to embedded storage for eeprom 2025-06-25 01:18:36 +02:00
5fb8705d9a split rtc module out to reduce repreated exactly same code 2025-06-24 20:59:12 +02:00
e57e87af3f fix backplane usb placement, use uncritial pins for high during flash/boot 2025-06-23 23:19:36 +02:00
450197c7cd merge stuff 2025-06-23 22:21:03 +02:00
736925f7de Merge pull request 'Housekeeping' (#18) from housekeeping into develop
Reviewed-on: #18
2025-06-23 22:15:50 +02:00
1fb9d2ab1b Merge pull request 'reduced sizes so all executable code stays below 8mb' (#19) from feature/partition_table_stuff into develop
Reviewed-on: #19
2025-06-23 22:15:34 +02:00
ea60d804b3 reduced sizes so all executable code stays below 8mb 2025-06-22 17:36:59 +02:00
d059b7b1db Merge branch 'develop' into housekeeping and clippy and spellcheck 2025-06-20 23:59:14 +02:00
e7b6bda747 Merge branch 'develop' into housekeeping 2025-06-20 23:40:32 +02:00
04849162cd added solar ina handling, adjusted website 2025-06-20 23:37:59 +02:00
5621f17e61 fix all warnings 2025-06-20 21:31:12 +02:00
d0e5627815 keep node artifacts in webpack directory
add commands to build.rs to move assets to required location within src
2025-06-20 20:53:32 +02:00
7115809f2b fix all compiler warnings 2025-06-20 20:53:29 +02:00
c1d0ce542c fix confusing lifetime warning 2025-06-20 20:53:28 +02:00
b7b08d8747 fix unused variable warnings 2025-06-20 20:53:26 +02:00
8d68f0ef14 remove embassy references in Cargo.toml
in newer esp-rust toolchains having these dependencies defiened leeds to
compile errors.
2025-06-20 20:53:23 +02:00
34b20b1f8f Merge pull request 'Refactor Battery State for more robustness against missing BatteryMonitor' (#17) from fix/deep-sleep-timeout-robustness into develop
Reviewed-on: #17
2025-06-20 20:52:48 +02:00
71973f8fe9 integrate battery state update into full codebase 2025-06-20 19:05:06 +02:00
506f0c36b5 refactor battery state 2025-06-20 18:46:57 +02:00
4b919b9613 chore: cargo fmt 2025-06-20 16:26:03 +02:00
017078ef8e fix suplicate battery encoding on website 2025-06-20 03:11:45 +02:00
c84e863d0f Merge branch 'feature/board-hal-splitting' into develop 2025-06-20 02:59:37 +02:00
de54afce52 webfix for sensora enable, ina start 2025-06-19 22:57:54 +02:00
a0e7f97887 Merge branch 'feature/board-hal-splitting' into develop 2025-06-19 20:07:51 +02:00
3a2e59874e fix config on submit not updated, fix startup initial always in ap mode 2025-06-19 20:06:43 +02:00
69077239a5 fix tread safty issues by annotating Box Traits correctly 2025-06-19 18:36:48 +02:00
e2cbf9618e fixed lifetime annotations + thread safty still needs work 2025-06-19 18:17:29 +02:00
fc1991523a splitting wip 2025-06-19 16:58:37 +02:00
c429c829b4 start splitting board hal enum into structs 2025-06-19 13:57:36 +02:00
e2cce88390 combine all branches 2025-06-19 13:05:47 +02:00
751f07e6fd Merge branch 'feature/v4_modular' into develop
# Conflicts:
#	rust/src/config.rs
#	rust/src/main.rs
#	rust/src/tank.rs
2025-06-18 19:07:24 +02:00
04bac4d0b6 v4 remarks 2025-06-18 18:50:46 +02:00
e24dc77fa9 Merge pull request 'Part 1 of V3 extraction in preperation of v4 and v3 software merge' (#16) from boardhal_abstract into develop
Reviewed-on: #16
2025-06-17 22:22:09 +02:00
05400c7c4a v4 board impl 2025-06-14 00:40:47 +02:00
6499b18ada allow startup with initial board mode and nearly no pin configs 2025-06-11 22:08:59 +02:00
a9d7936376 add null board 2025-06-11 17:35:23 +02:00
38f4ada433 Part 1 of V3 extraction in preperation of v4 and v3 software merge 2025-06-08 15:59:24 +02:00
3fe9aaeb6f cleanup main and network state handling 2025-05-27 23:47:14 +02:00
4f4d15e4a4 fix is_enabled being inverted 2025-05-19 12:16:18 +02:00
9f48b46738 use same output for moisture on webpage as in mqtt 2025-05-19 12:16:09 +02:00
0614a0e1ef adjust clearance to economy production 2025-05-09 15:06:45 +02:00
9e82d00a91 add simplified sensor board 2025-05-08 21:55:22 +02:00
171b130a29 fix config always assumed changed 2025-05-07 00:48:06 +02:00
bfc3fbc6e1 cargo fmt 2025-05-07 00:01:23 +02:00
26da6b39cc clippy happier 2025-05-07 00:00:21 +02:00
a401d4de7b webpage adjustments 2025-05-06 22:33:48 +02:00
5fe1dc8f40 add ability to override frequency per plant and adjust timezone, fix missing workhour for plants 2025-05-06 22:33:33 +02:00
f8274ea7a8 add better chrono filter 2025-05-06 22:32:39 +02:00
1cae6c4aad producability rework 2025-05-05 22:08:27 +02:00
e3ed41a223 finalizer lightout 2025-05-05 20:03:39 +02:00
93b169f849 mppt rework, production optimization 2025-05-05 00:48:28 +02:00
ee5c67769b plantctrl main board 2025-04-30 22:02:26 +02:00
5d3abe0834 mppt ready, main pcb wip 2025-04-29 22:59:06 +02:00
d4ffa7d2f1 main module wip 2025-04-28 21:52:39 +02:00
62d8a38e86 Merge pull request 'refactor/plant-state-handling' (#11) from refactor/plant-state-handling into develop
Reviewed-on: #11
2025-04-25 19:35:51 +02:00
519c8d2c52 finish refactor of plant state logic 2025-04-24 23:21:27 +02:00
e941a4973d save compiling state during refactor 2025-04-24 20:45:39 +02:00
2b5c1da484 WIP refactor plant_state 2025-04-24 20:45:37 +02:00
cf31ce8d43 WIP introduce plant_state module 2025-04-24 20:45:32 +02:00
d9c3d4e13c add config field to enable moisture sensor a 2025-04-24 20:44:39 +02:00
89 changed files with 309736 additions and 83464 deletions

View File

@@ -1,82 +0,0 @@
(kicad_symbol_lib (version 20220914) (generator kicad_symbol_editor)
(symbol "CN3306" (in_bom yes) (on_board yes)
(property "Reference" "U" (at 5.08 -5.08 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)
)
(property "Datasheet" "" (at 0 0 0)
(effects (font (size 1.27 1.27)) hide)
)
(symbol "CN3306_1_1"
(rectangle (start -2.54 7.62) (end 12.7 -16.51)
(stroke (width 0) (type default))
(fill (type background))
)
(pin input line (at 15.24 -8.89 180) (length 2.54)
(name "FB" (effects (font (size 1.27 1.27))))
(number "1" (effects (font (size 1.27 1.27))))
)
(pin input line (at 5.08 10.16 270) (length 2.54)
(name "COME" (effects (font (size 1.27 1.27))))
(number "10" (effects (font (size 1.27 1.27))))
)
(pin input line (at 15.24 -13.97 180) (length 2.54)
(name "VCC" (effects (font (size 1.27 1.27))))
(number "11" (effects (font (size 1.27 1.27))))
)
(pin input line (at 15.24 -11.43 180) (length 2.54)
(name "VCC" (effects (font (size 1.27 1.27))))
(number "12" (effects (font (size 1.27 1.27))))
)
(pin input line (at 15.24 5.08 180) (length 2.54)
(name "DRV" (effects (font (size 1.27 1.27))))
(number "13" (effects (font (size 1.27 1.27))))
)
(pin input line (at 3.81 -19.05 90) (length 2.54)
(name "GND" (effects (font (size 1.27 1.27))))
(number "14" (effects (font (size 1.27 1.27))))
)
(pin input line (at 6.35 -19.05 90) (length 2.54)
(name "GND" (effects (font (size 1.27 1.27))))
(number "15" (effects (font (size 1.27 1.27))))
)
(pin input line (at 15.24 2.54 180) (length 2.54)
(name "ISW" (effects (font (size 1.27 1.27))))
(number "16" (effects (font (size 1.27 1.27))))
)
(pin input line (at -5.08 -10.16 0) (length 2.54)
(name "COMP" (effects (font (size 1.27 1.27))))
(number "2" (effects (font (size 1.27 1.27))))
)
(pin input line (at -5.08 -1.27 0) (length 2.54)
(name "MPPT" (effects (font (size 1.27 1.27))))
(number "3" (effects (font (size 1.27 1.27))))
)
(pin input line (at -5.08 -7.62 0) (length 2.54)
(name "SHDN" (effects (font (size 1.27 1.27))))
(number "5" (effects (font (size 1.27 1.27))))
)
(pin input line (at -5.08 3.81 0) (length 2.54)
(name "CHRG" (effects (font (size 1.27 1.27))))
(number "6" (effects (font (size 1.27 1.27))))
)
(pin input line (at -5.08 1.27 0) (length 2.54)
(name "DONE" (effects (font (size 1.27 1.27))))
(number "7" (effects (font (size 1.27 1.27))))
)
(pin input line (at 15.24 -3.81 180) (length 2.54)
(name "CSP" (effects (font (size 1.27 1.27))))
(number "8" (effects (font (size 1.27 1.27))))
)
(pin input line (at 15.24 -6.35 180) (length 2.54)
(name "ONE" (effects (font (size 1.27 1.27))))
(number "9" (effects (font (size 1.27 1.27))))
)
)
)
)

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
{
"board": {
"active_layer": 0,
"active_layer": 4,
"active_layer_preset": "All Layers",
"auto_track_width": false,
"hidden_netclasses": [],

View File

@@ -37,9 +37,9 @@
"other_text_thickness": 0.15,
"other_text_upright": false,
"pads": {
"drill": 0.25,
"height": 0.35,
"width": 0.35
"drill": 1.0,
"height": 1.7,
"width": 1.7
},
"silk_line_width": 0.12,
"silk_text_italic": false,
@@ -58,12 +58,7 @@
"width": 0.0
}
],
"drc_exclusions": [
[
"footprint_symbol_mismatch|177050000|59025000|a624af3d-bffa-4ff7-9554-e16d3c677f69|00000000-0000-0000-0000-000000000000",
""
]
],
"drc_exclusions": [],
"meta": {
"filename": "board_design_settings.json",
"version": 2
@@ -96,7 +91,7 @@
"length_out_of_range": "error",
"lib_footprint_issues": "warning",
"lib_footprint_mismatch": "warning",
"malformed_courtyard": "error",
"malformed_courtyard": "ignore",
"microvia_drill_out_of_range": "error",
"mirrored_text_on_front_layer": "warning",
"missing_courtyard": "ignore",
@@ -467,8 +462,8 @@
"no_connect_dangling": "warning",
"pin_not_connected": "error",
"pin_not_driven": "error",
"pin_to_pin": "error",
"power_pin_not_driven": "error",
"pin_to_pin": "ignore",
"power_pin_not_driven": "ignore",
"same_local_global_label": "warning",
"similar_label_and_power": "warning",
"similar_labels": "warning",
@@ -1081,7 +1076,7 @@
},
"schematic": {
"annotate_start_num": 0,
"bom_export_filename": "",
"bom_export_filename": "PlantCtrlESP32.csv",
"bom_fmt_presets": [],
"bom_fmt_settings": {
"field_delimiter": ",",

File diff suppressed because it is too large Load Diff

View File

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

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
{
"board": {
"active_layer": 5,
"active_layer": 25,
"active_layer_preset": "All Layers",
"auto_track_width": false,
"hidden_netclasses": [],

View File

@@ -58,7 +58,16 @@
"width": 0.0
}
],
"drc_exclusions": [],
"drc_exclusions": [
[
"copper_edge_clearance|127050000|19750000|9699c74c-42f0-475d-905c-32f2bfc21b27|f47bb74a-cab1-4bf9-acd5-ef1613b09d02",
""
],
[
"starved_thermal|119050000|20500000|7ae79caf-9e0f-4e4d-8ab8-b020d8cfa707|e5f29b1c-951c-45bf-a2c4-47dcea02bfd5|F.Cu",
""
]
],
"meta": {
"version": 2
},
@@ -105,7 +114,7 @@
"silk_overlap": "warning",
"skew_out_of_range": "error",
"solder_mask_bridge": "error",
"starved_thermal": "error",
"starved_thermal": "warning",
"text_height": "warning",
"text_on_edge_cuts": "error",
"text_thickness": "warning",
@@ -139,7 +148,7 @@
"min_track_width": 0.0,
"min_via_annular_width": 0.1,
"min_via_diameter": 0.5,
"solder_mask_to_copper_clearance": 0.0,
"solder_mask_to_copper_clearance": 0.005,
"use_height_for_length_calcs": true
},
"teardrop_options": [
@@ -582,7 +591,7 @@
"group_by": false,
"label": "LCSC_PART_NUMBER",
"name": "LCSC_PART_NUMBER",
"show": false
"show": true
},
{
"group_by": true,

View File

@@ -2735,7 +2735,7 @@
(on_board yes)
(dnp no)
(uuid "0425b7e6-6cf5-40f7-ac09-e360b3fb146c")
(property "Reference" "R24"
(property "Reference" "R3"
(at 190.5 62.23 0)
(effects
(font
@@ -2798,7 +2798,7 @@
(instances
(project "LightOut"
(path "/2a8304c5-3c9a-4ae8-a3b2-19491060e8fb"
(reference "R24")
(reference "R3")
(unit 1)
)
)
@@ -2814,7 +2814,7 @@
(dnp no)
(fields_autoplaced yes)
(uuid "11b34252-84e7-4de5-96b2-86a1a5342860")
(property "Reference" "L2"
(property "Reference" "L1"
(at 171.45 45.085 90)
(effects
(font
@@ -2884,7 +2884,7 @@
(instances
(project "LightOut"
(path "/2a8304c5-3c9a-4ae8-a3b2-19491060e8fb"
(reference "L2")
(reference "L1")
(unit 1)
)
)
@@ -2897,7 +2897,7 @@
(exclude_from_sim no)
(in_bom yes)
(on_board yes)
(dnp no)
(dnp yes)
(fields_autoplaced yes)
(uuid "17e9522f-7617-435f-bc97-f04977cc30a5")
(property "Reference" "U1"
@@ -2982,7 +2982,7 @@
(dnp no)
(fields_autoplaced yes)
(uuid "20b688f4-63bd-4d15-9259-1efb3da269b2")
(property "Reference" "C71"
(property "Reference" "C4"
(at 207.645 64.7699 0)
(effects
(font
@@ -3054,7 +3054,7 @@
(instances
(project "LightOut"
(path "/2a8304c5-3c9a-4ae8-a3b2-19491060e8fb"
(reference "C71")
(reference "C4")
(unit 1)
)
)
@@ -3070,7 +3070,7 @@
(dnp no)
(fields_autoplaced yes)
(uuid "22a34af5-c1f2-4bfa-a346-349e29fb1cf1")
(property "Reference" "R5"
(property "Reference" "R2"
(at 124.46 44.45 90)
(effects
(font
@@ -3078,7 +3078,7 @@
)
)
)
(property "Value" "2.2Ohm (90mW loss)"
(property "Value" "1Ohm"
(at 124.46 46.99 90)
(effects
(font
@@ -3113,7 +3113,7 @@
(hide yes)
)
)
(property "LCSC" "C17513"
(property "LCSC" "C25271"
(at 124.46 49.53 0)
(effects
(font
@@ -3122,7 +3122,7 @@
(hide yes)
)
)
(property "LCSC_PART_NUMBER" "C2930032"
(property "LCSC_PART_NUMBER" "C25271"
(at 124.46 49.53 0)
(effects
(font
@@ -3140,7 +3140,7 @@
(instances
(project "LightOut"
(path "/2a8304c5-3c9a-4ae8-a3b2-19491060e8fb"
(reference "R5")
(reference "R2")
(unit 1)
)
)
@@ -3236,7 +3236,7 @@
(on_board yes)
(dnp no)
(uuid "523d86ff-c1fb-4885-b234-1324756802c1")
(property "Reference" "R23"
(property "Reference" "R1"
(at 190.5 53.34 0)
(effects
(font
@@ -3299,7 +3299,7 @@
(instances
(project "LightOut"
(path "/2a8304c5-3c9a-4ae8-a3b2-19491060e8fb"
(reference "R23")
(reference "R1")
(unit 1)
)
)
@@ -3314,7 +3314,7 @@
(on_board yes)
(dnp no)
(uuid "5c9e2472-0ea9-44fa-9826-1ffbec963aeb")
(property "Reference" "R22"
(property "Reference" "R5"
(at 107.8484 51.308 0)
(effects
(font
@@ -3377,7 +3377,7 @@
(instances
(project "LightOut"
(path "/2a8304c5-3c9a-4ae8-a3b2-19491060e8fb"
(reference "R22")
(reference "R5")
(unit 1)
)
)
@@ -3392,7 +3392,7 @@
(on_board yes)
(dnp no)
(uuid "63be2ae3-3f0f-4a07-aeae-294be6e75c31")
(property "Reference" "R21"
(property "Reference" "R6"
(at 104.648 64.8716 0)
(effects
(font
@@ -3455,7 +3455,7 @@
(instances
(project "LightOut"
(path "/2a8304c5-3c9a-4ae8-a3b2-19491060e8fb"
(reference "R21")
(reference "R6")
(unit 1)
)
)
@@ -3471,7 +3471,7 @@
(dnp no)
(fields_autoplaced yes)
(uuid "66d062a4-4adc-4d5b-b235-ae3b04da22cd")
(property "Reference" "C13"
(property "Reference" "C2"
(at 133.985 63.4999 0)
(effects
(font
@@ -3543,7 +3543,7 @@
(instances
(project "LightOut"
(path "/2a8304c5-3c9a-4ae8-a3b2-19491060e8fb"
(reference "C13")
(reference "C2")
(unit 1)
)
)
@@ -3558,7 +3558,7 @@
(on_board yes)
(dnp no)
(uuid "6db97863-981e-4f95-a1fc-506e785ba0c2")
(property "Reference" "Q2"
(property "Reference" "Q1"
(at 95.377 66.1416 0)
(effects
(font
@@ -3624,7 +3624,7 @@
(instances
(project "LightOut"
(path "/2a8304c5-3c9a-4ae8-a3b2-19491060e8fb"
(reference "Q2")
(reference "Q1")
(unit 1)
)
)
@@ -3640,7 +3640,7 @@
(dnp no)
(fields_autoplaced yes)
(uuid "76624502-fca9-4ec5-bd55-d8c2393958dd")
(property "Reference" "C16"
(property "Reference" "C5"
(at 163.83 46.99 90)
(effects
(font
@@ -3710,7 +3710,7 @@
(instances
(project "LightOut"
(path "/2a8304c5-3c9a-4ae8-a3b2-19491060e8fb"
(reference "C16")
(reference "C5")
(unit 1)
)
)
@@ -3724,7 +3724,7 @@
(exclude_from_sim no)
(in_bom yes)
(on_board yes)
(dnp yes)
(dnp no)
(fields_autoplaced yes)
(uuid "7c62f4d3-1cf7-4fa2-a112-701dfb3880ab")
(property "Reference" "LIGHT1"
@@ -3794,7 +3794,7 @@
(on_board yes)
(dnp no)
(uuid "7ff0ccf7-1026-4845-bd57-72661ae57edf")
(property "Reference" "Q_PWR1"
(property "Reference" "QP_1"
(at 115.57 64.77 0)
(effects
(font
@@ -3863,7 +3863,7 @@
(instances
(project "LightOut"
(path "/2a8304c5-3c9a-4ae8-a3b2-19491060e8fb"
(reference "Q_PWR1")
(reference "QP_1")
(unit 1)
)
)
@@ -3878,7 +3878,7 @@
(on_board yes)
(dnp no)
(uuid "a19bda47-2414-4647-8682-22d4699f150d")
(property "Reference" "C17"
(property "Reference" "C1"
(at 199.39 57.15 0)
(effects
(font
@@ -3950,7 +3950,7 @@
(instances
(project "LightOut"
(path "/2a8304c5-3c9a-4ae8-a3b2-19491060e8fb"
(reference "C17")
(reference "C1")
(unit 1)
)
)
@@ -3963,9 +3963,9 @@
(exclude_from_sim no)
(in_bom yes)
(on_board yes)
(dnp yes)
(dnp no)
(uuid "aca8686e-aff2-4166-85c4-f2cbe9ddc5e0")
(property "Reference" "I2C3"
(property "Reference" "I1"
(at 100.0506 52.5272 90)
(effects
(font
@@ -3974,7 +3974,7 @@
(justify right)
)
)
(property "Value" "GREEN"
(property "Value" "LED White"
(at 97.7392 52.5272 90)
(effects
(font
@@ -4028,7 +4028,7 @@
(instances
(project "LightOut"
(path "/2a8304c5-3c9a-4ae8-a3b2-19491060e8fb"
(reference "I2C3")
(reference "I1")
(unit 1)
)
)
@@ -4044,7 +4044,7 @@
(dnp no)
(fields_autoplaced yes)
(uuid "b771ae42-ef20-433b-882c-cd86d5c9dc48")
(property "Reference" "U5"
(property "Reference" "U2"
(at 143.51 44.45 0)
(effects
(font
@@ -4126,7 +4126,7 @@
(instances
(project "LightOut"
(path "/2a8304c5-3c9a-4ae8-a3b2-19491060e8fb"
(reference "U5")
(reference "U2")
(unit 1)
)
)
@@ -4142,7 +4142,7 @@
(dnp no)
(fields_autoplaced yes)
(uuid "ce2d65a3-531d-4585-8743-939a548f9d2f")
(property "Reference" "C72"
(property "Reference" "C3"
(at 182.88 53.34 0)
(effects
(font
@@ -4205,7 +4205,7 @@
(instances
(project "LightOut"
(path "/2a8304c5-3c9a-4ae8-a3b2-19491060e8fb"
(reference "C72")
(reference "C3")
(unit 1)
)
)
@@ -4220,7 +4220,7 @@
(on_board yes)
(dnp no)
(uuid "d7286e86-efc2-4c1d-8247-d0443e857cbc")
(property "Reference" "R20"
(property "Reference" "R7"
(at 98.9584 64.008 0)
(effects
(font
@@ -4283,7 +4283,7 @@
(instances
(project "LightOut"
(path "/2a8304c5-3c9a-4ae8-a3b2-19491060e8fb"
(reference "R20")
(reference "R7")
(unit 1)
)
)
@@ -4298,7 +4298,7 @@
(on_board yes)
(dnp no)
(uuid "f48fb307-e738-4d85-b552-0cbce90ea597")
(property "Reference" "R19"
(property "Reference" "R8"
(at 86.868 69.9516 0)
(effects
(font
@@ -4361,7 +4361,7 @@
(instances
(project "LightOut"
(path "/2a8304c5-3c9a-4ae8-a3b2-19491060e8fb"
(reference "R19")
(reference "R8")
(unit 1)
)
)
@@ -4376,7 +4376,7 @@
(on_board yes)
(dnp no)
(uuid "fb34f92d-3c0f-49d2-9c25-915e8b197b69")
(property "Reference" "R7"
(property "Reference" "R4"
(at 79.9084 69.088 0)
(effects
(font
@@ -4439,7 +4439,7 @@
(instances
(project "LightOut"
(path "/2a8304c5-3c9a-4ae8-a3b2-19491060e8fb"
(reference "R7")
(reference "R4")
(unit 1)
)
)

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
{
"board": {
"active_layer": 0,
"active_layer": 5,
"active_layer_preset": "All Layers",
"auto_track_width": false,
"hidden_netclasses": [],

View File

@@ -60,11 +60,47 @@
],
"drc_exclusions": [
[
"silk_over_copper|115549999|32059610|4d3349dc-5b52-43de-8407-342b46c9ab03|00000000-0000-0000-0000-000000000000",
"courtyards_overlap|112084965|21032500|499f5fe7-5885-47c2-974b-f649b529c51e|6df87025-4003-41e5-8291-17fff766dac2",
""
],
[
"starved_thermal|84500000|37000000|1c111c53-aa66-4ea7-a1fd-488f9eb7dc56|8d490f05-82aa-4679-a53e-ec4019506065|F.Cu",
"courtyards_overlap|113305000|20282500|19502f41-24bc-474e-a572-d11957ca9fa9|499f5fe7-5885-47c2-974b-f649b529c51e",
""
],
[
"silk_overlap|110300000|20977500|4acc8ffe-9ac8-4a84-956a-115a7d7b3e4c|57669fc8-8e26-4233-af7a-24a49cb88f5a",
""
],
[
"silk_overlap|112537500|21285000|897a7275-18f3-4da7-9aa2-dd2257c674f9|aef215d0-4617-401a-9e7b-04f4e00e75a8",
""
],
[
"silk_overlap|112537500|23950000|91e8dcc0-f8c0-4d84-aa8d-100828268b67|aef215d0-4617-401a-9e7b-04f4e00e75a8",
""
],
[
"silk_overlap|113440000|21550000|0c747b16-d7ce-4265-bf62-3ef103975f38|d32e574f-a2c5-4328-b36b-1bf733cc7a57",
""
],
[
"silk_overlap|113440000|22327500|0c747b16-d7ce-4265-bf62-3ef103975f38|edcef2ac-d633-4378-9e2e-d34d824f48da",
""
],
[
"silk_overlap|113440000|22327500|729846c7-71d4-40af-9ddc-5c4441b51013|edcef2ac-d633-4378-9e2e-d34d824f48da",
""
],
[
"silk_overlap|113440000|22550000|729846c7-71d4-40af-9ddc-5c4441b51013|d32e574f-a2c5-4328-b36b-1bf733cc7a57",
""
],
[
"starved_thermal|115000000|21190000|1c111c53-aa66-4ea7-a1fd-488f9eb7dc56|96d7d8f0-db01-449e-8fed-d03c5a7d0f39|F.Cu",
""
],
[
"starved_thermal|86675000|37750000|1c111c53-aa66-4ea7-a1fd-488f9eb7dc56|d0eae3f0-eb5a-480d-9ab2-1af2af11e319|F.Cu",
""
]
],
@@ -114,7 +150,7 @@
"silk_overlap": "warning",
"skew_out_of_range": "error",
"solder_mask_bridge": "error",
"starved_thermal": "error",
"starved_thermal": "warning",
"text_height": "warning",
"text_on_edge_cuts": "error",
"text_thickness": "warning",
@@ -148,7 +184,7 @@
"min_track_width": 0.0,
"min_via_annular_width": 0.1,
"min_via_diameter": 0.5,
"solder_mask_to_copper_clearance": 0.0,
"solder_mask_to_copper_clearance": 0.005,
"use_height_for_length_calcs": true
},
"teardrop_options": [
@@ -198,6 +234,7 @@
"track_widths": [
0.0,
0.1,
0.2,
0.5,
1.0,
1.5,
@@ -470,7 +507,7 @@
"no_connect_dangling": "warning",
"pin_not_connected": "error",
"pin_not_driven": "error",
"pin_to_pin": "warning",
"pin_to_pin": "ignore",
"power_pin_not_driven": "error",
"same_local_global_label": "warning",
"similar_label_and_power": "warning",
@@ -558,6 +595,30 @@
"name": "Reference",
"show": true
},
{
"group_by": true,
"label": "Value",
"name": "Value",
"show": true
},
{
"group_by": true,
"label": "Footprint",
"name": "Footprint",
"show": true
},
{
"group_by": false,
"label": "Datasheet",
"name": "Datasheet",
"show": true
},
{
"group_by": false,
"label": "Description",
"name": "Description",
"show": false
},
{
"group_by": false,
"label": "Qty",
@@ -565,11 +626,89 @@
"show": true
},
{
"group_by": true,
"label": "Value",
"name": "Value",
"group_by": false,
"label": "#",
"name": "${ITEM_NUMBER}",
"show": false
},
{
"group_by": false,
"label": "Availability",
"name": "Availability",
"show": false
},
{
"group_by": false,
"label": "Check_prices",
"name": "Check_prices",
"show": false
},
{
"group_by": false,
"label": "Description_1",
"name": "Description_1",
"show": false
},
{
"group_by": false,
"label": "Field5",
"name": "Field5",
"show": false
},
{
"group_by": false,
"label": "LCSC_PART_NUMBER",
"name": "LCSC_PART_NUMBER",
"show": true
},
{
"group_by": false,
"label": "MF",
"name": "MF",
"show": false
},
{
"group_by": false,
"label": "MP",
"name": "MP",
"show": false
},
{
"group_by": false,
"label": "Package",
"name": "Package",
"show": false
},
{
"group_by": false,
"label": "Price",
"name": "Price",
"show": false
},
{
"group_by": false,
"label": "Sim.Device",
"name": "Sim.Device",
"show": false
},
{
"group_by": false,
"label": "Sim.Pins",
"name": "Sim.Pins",
"show": false
},
{
"group_by": false,
"label": "Sim.Type",
"name": "Sim.Type",
"show": false
},
{
"group_by": false,
"label": "SnapEDA_Link",
"name": "SnapEDA_Link",
"show": false
},
{
"group_by": true,
"label": "DNP",
@@ -587,24 +726,12 @@
"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
}
],
"filter_string": "",
"group_symbols": true,
"include_excluded_from_bom": true,
"name": "Default Editing",
"name": "",
"sort_asc": true,
"sort_field": "Reference"
},

File diff suppressed because it is too large Load Diff

View File

@@ -59,9 +59,9 @@
(end -37 25.75)
(stroke
(width 0.05)
(type default)
(type solid)
)
(layer "Edge.Cuts")
(layer "F.SilkS")
(uuid "d893208d-94bf-4628-afce-16c271a81597")
)
(fp_line
@@ -69,9 +69,9 @@
(end 37 3.75)
(stroke
(width 0.05)
(type default)
(type solid)
)
(layer "Edge.Cuts")
(layer "F.SilkS")
(uuid "e7c8e738-92f2-4bc1-b1c0-bfc6cdddb430")
)
(fp_line
@@ -79,9 +79,9 @@
(end 37 25.75)
(stroke
(width 0.05)
(type default)
(type solid)
)
(layer "Edge.Cuts")
(layer "F.SilkS")
(uuid "bff1db86-47a1-47dd-bed5-8a9912edae2a")
)
(fp_line
@@ -89,9 +89,9 @@
(end -37 25.75)
(stroke
(width 0.05)
(type default)
(type solid)
)
(layer "Edge.Cuts")
(layer "F.SilkS")
(uuid "0c7608c2-fdf4-4830-90bc-0872989771ea")
)
(fp_text user "${REFERENCE}"

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
(sym_lib_table
(version 7)
(lib (name "CN3795")(type "KiCad")(uri "/home/empire/workspace/PlantCtrl/board/CN3795.kicad_sym")(options "")(descr ""))
(lib (name "CN3795")(type "KiCad")(uri "${KIPRJMOD}/symbols/CN3795.kicad_sym")(options "")(descr ""))
(lib (name "MPPT")(type "KiCad")(uri "${KIPRJMOD}/MPPT.kicad_sym")(options "")(descr ""))
(lib (name "ESP32-DEVKITC-32D")(type "KiCad")(uri "/home/empire/workspace/PlantCtrl/board/kicad-stuff/ESP32/ESP32-DEVKITC-32D.kicad_sym")(options "")(descr ""))
(lib (name "SL2300")(type "KiCad")(uri "${KIPRJMOD}/symbols/SL2300.kicad_sym")(options "")(descr ""))
)

View File

@@ -0,0 +1,278 @@
(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

@@ -428,7 +428,7 @@
(symbol "Sensor_1_1"
(rectangle
(start -5.08 -1.27)
(end 5.08 -29.21)
(end 5.08 -34.29)
(stroke
(width 0)
(type solid)
@@ -455,7 +455,7 @@
)
)
)
(pin no_connect line
(pin power_in line
(at 7.62 -5.08 180)
(length 2.54)
(name "VBAT"
@@ -527,10 +527,10 @@
)
)
)
(pin output line
(pin input line
(at 7.62 -15.24 180)
(length 2.54)
(name "GND"
(name "CAN_H"
(effects
(font
(size 1.27 1.27)
@@ -545,10 +545,10 @@
)
)
)
(pin output line
(pin input line
(at 7.62 -17.78 180)
(length 2.54)
(name "GND"
(name "CAN_L"
(effects
(font
(size 1.27 1.27)
@@ -635,6 +635,42 @@
)
)
)
(pin output line
(at 7.62 -30.48 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 -33.02 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)
)

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
{
"board": {
"active_layer": 0,
"active_layer": 5,
"active_layer_preset": "All Layers",
"auto_track_width": false,
"hidden_netclasses": [],
@@ -50,7 +50,7 @@
"shapes"
],
"visible_layers": "ffffffff_ffffffff_ffffffff_ffffffff",
"zone_display_mode": 1
"zone_display_mode": 0
},
"git": {
"repo_type": "",

View File

@@ -62,10 +62,6 @@
[
"footprint_type_mismatch|145100000|72500000|5ecd01c7-e707-43f2-ada2-bb695111d219|00000000-0000-0000-0000-000000000000",
""
],
[
"silk_over_copper|177059610|78100001|e5735006-b5a9-492a-868f-a91cc93975c2|00000000-0000-0000-0000-000000000000",
""
]
],
"meta": {
@@ -600,6 +596,12 @@
"name": "${ITEM_NUMBER}",
"show": false
},
{
"group_by": false,
"label": "Field5",
"name": "Field5",
"show": false
},
{
"group_by": false,
"label": "LCSC_PART_NUMBER",

View File

@@ -593,190 +593,6 @@
)
(embedded_fonts no)
)
(symbol "Device:R_Shunt"
(pin_numbers
(hide yes)
)
(pin_names
(offset 0)
)
(exclude_from_sim no)
(in_bom yes)
(on_board yes)
(property "Reference" "R"
(at -4.445 0 90)
(effects
(font
(size 1.27 1.27)
)
)
)
(property "Value" "R_Shunt"
(at -2.54 0 90)
(effects
(font
(size 1.27 1.27)
)
)
)
(property "Footprint" ""
(at -1.778 0 90)
(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" "Shunt resistor"
(at 0 0 0)
(effects
(font
(size 1.27 1.27)
)
(hide yes)
)
)
(property "ki_keywords" "R res shunt resistor"
(at 0 0 0)
(effects
(font
(size 1.27 1.27)
)
(hide yes)
)
)
(property "ki_fp_filters" "R_*Shunt*"
(at 0 0 0)
(effects
(font
(size 1.27 1.27)
)
(hide yes)
)
)
(symbol "R_Shunt_0_1"
(rectangle
(start -1.016 -2.54)
(end 1.016 2.54)
(stroke
(width 0.254)
(type default)
)
(fill
(type none)
)
)
(polyline
(pts
(xy 0 -2.54) (xy 1.27 -2.54)
)
(stroke
(width 0)
(type default)
)
(fill
(type none)
)
)
(polyline
(pts
(xy 1.27 2.54) (xy 0 2.54)
)
(stroke
(width 0)
(type default)
)
(fill
(type none)
)
)
)
(symbol "R_Shunt_1_1"
(pin passive line
(at 0 5.08 270)
(length 2.54)
(name "1"
(effects
(font
(size 1.27 1.27)
)
)
)
(number "1"
(effects
(font
(size 1.27 1.27)
)
)
)
)
(pin passive line
(at 0 -5.08 90)
(length 2.54)
(name "4"
(effects
(font
(size 1.27 1.27)
)
)
)
(number "4"
(effects
(font
(size 1.27 1.27)
)
)
)
)
(pin passive line
(at 3.81 2.54 180)
(length 2.54)
(name "2"
(effects
(font
(size 1.27 1.27)
)
)
)
(number "2"
(effects
(font
(size 1.27 1.27)
)
)
)
)
(pin passive line
(at 3.81 -2.54 180)
(length 2.54)
(name "3"
(effects
(font
(size 1.27 1.27)
)
)
)
(number "3"
(effects
(font
(size 1.27 1.27)
)
)
)
)
)
(embedded_fonts no)
)
(symbol "Diode:BAS40-04"
(pin_names
(offset 1.016)
@@ -2351,7 +2167,7 @@
)
(uuid "a692c3ed-8364-41a4-a25b-0517c8420c06")
)
(text "Address 0101-000 -> dec 40"
(text "Address 1000010 -> dec 66"
(exclude_from_sim no)
(at 70.358 47.498 0)
(effects
@@ -2414,6 +2230,12 @@
(color 0 0 0 0)
(uuid "4396d9fd-e4b3-4774-89c0-f5879b587ea9")
)
(junction
(at 36.83 85.09)
(diameter 0)
(color 0 0 0 0)
(uuid "50facde5-c849-43d8-9b86-fae1afb52c8a")
)
(junction
(at 276.86 200.66)
(diameter 0)
@@ -2450,6 +2272,12 @@
(color 0 0 0 0)
(uuid "78e83bad-91e3-4858-9956-66ba0240bfef")
)
(junction
(at 38.1 77.47)
(diameter 0)
(color 0 0 0 0)
(uuid "7c52ad28-f246-4488-981a-94a88c75e0ef")
)
(junction
(at 153.67 106.68)
(diameter 0)
@@ -2462,6 +2290,12 @@
(color 0 0 0 0)
(uuid "94038c3f-a8df-486c-9c85-7d546ccf7920")
)
(junction
(at 38.1 85.09)
(diameter 0)
(color 0 0 0 0)
(uuid "9691379e-03c5-4356-a84d-995a6f4284dd")
)
(junction
(at 243.84 200.66)
(diameter 0)
@@ -2504,6 +2338,12 @@
(color 0 0 0 0)
(uuid "c79d365a-7eb5-4e17-a150-46b714151130")
)
(junction
(at 36.83 77.47)
(diameter 0)
(color 0 0 0 0)
(uuid "c7b1301d-d308-4c17-a26d-b8bee9e65bbc")
)
(junction
(at 213.36 200.66)
(diameter 0)
@@ -2622,6 +2462,16 @@
)
(uuid "11ad0b0e-d605-4139-9c31-76050fb20950")
)
(wire
(pts
(xy 40.64 77.47) (xy 38.1 77.47)
)
(stroke
(width 0)
(type default)
)
(uuid "13d97c0b-b70b-49c9-aee8-997f1f330cae")
)
(wire
(pts
(xy 243.84 200.66) (xy 242.57 200.66)
@@ -2792,6 +2642,16 @@
)
(uuid "4a91f5f7-45a0-40bb-932c-88a1af0c72eb")
)
(wire
(pts
(xy 36.83 76.2) (xy 36.83 77.47)
)
(stroke
(width 0)
(type default)
)
(uuid "4d75715b-5c03-4f74-962e-65303cc18dcc")
)
(wire
(pts
(xy 158.75 106.68) (xy 161.29 106.68)
@@ -2962,6 +2822,16 @@
)
(uuid "6e6bde6b-710f-46fe-ae64-dcc1c9cb2654")
)
(wire
(pts
(xy 40.64 83.82) (xy 40.64 85.09)
)
(stroke
(width 0)
(type default)
)
(uuid "6e7fb4b0-87a6-438b-8d87-5f176f93d6c3")
)
(wire
(pts
(xy 270.51 213.36) (xy 265.43 213.36)
@@ -3012,6 +2882,16 @@
)
(uuid "760f68c3-55c4-4d63-b9d1-1242153327ac")
)
(wire
(pts
(xy 35.56 77.47) (xy 36.83 77.47)
)
(stroke
(width 0)
(type default)
)
(uuid "77bf4909-a56c-4f8d-a61a-7f5fa4f91e92")
)
(wire
(pts
(xy 213.36 200.66) (xy 212.09 200.66)
@@ -3182,6 +3062,36 @@
)
(uuid "a3f0835a-3b5b-47e1-8a75-e220b9b2620a")
)
(wire
(pts
(xy 36.83 85.09) (xy 38.1 85.09)
)
(stroke
(width 0)
(type default)
)
(uuid "a60e5812-7a8a-4db3-bf7a-9e5827aab165")
)
(wire
(pts
(xy 36.83 77.47) (xy 38.1 77.47)
)
(stroke
(width 0)
(type default)
)
(uuid "a74e0287-43ed-4a83-a2e5-58597c0b71ad")
)
(wire
(pts
(xy 40.64 78.74) (xy 40.64 77.47)
)
(stroke
(width 0)
(type default)
)
(uuid "a757fb93-eac8-4231-94e7-e6b64d10a357")
)
(wire
(pts
(xy 177.8 213.36) (xy 172.72 213.36)
@@ -3262,6 +3172,16 @@
)
(uuid "b1f63e57-8eca-4368-acdd-39768cad7ee8")
)
(wire
(pts
(xy 36.83 85.09) (xy 36.83 86.36)
)
(stroke
(width 0)
(type default)
)
(uuid "b4069663-4cfa-4aa6-97c3-bc3f1da5fcc2")
)
(wire
(pts
(xy 243.84 168.91) (xy 243.84 200.66)
@@ -3302,6 +3222,16 @@
)
(uuid "c8d6b6fe-9173-45b5-904c-d6a48c165dfb")
)
(wire
(pts
(xy 40.64 85.09) (xy 38.1 85.09)
)
(stroke
(width 0)
(type default)
)
(uuid "cb9c59ae-5f9e-4b3b-ba2b-b145eb8a3682")
)
(wire
(pts
(xy 156.21 106.68) (xy 153.67 106.68)
@@ -3402,6 +3332,16 @@
)
(uuid "e3e23ab7-a47a-492a-8f7f-b8876d21c5ce")
)
(wire
(pts
(xy 35.56 85.09) (xy 36.83 85.09)
)
(stroke
(width 0)
(type default)
)
(uuid "e45e4aa6-79d6-4533-8f3c-950a8c425958")
)
(wire
(pts
(xy 184.15 200.66) (xy 186.69 200.66)
@@ -5452,6 +5392,91 @@
)
)
)
(symbol
(lib_id "Device:R")
(at 35.56 81.28 180)
(unit 1)
(exclude_from_sim no)
(in_bom yes)
(on_board yes)
(dnp no)
(fields_autoplaced yes)
(uuid "06c66de4-05db-4145-a783-1ccae1cba266")
(property "Reference" "R22"
(at 29.21 81.28 90)
(effects
(font
(size 1.27 1.27)
)
)
)
(property "Value" "100mOhm"
(at 31.75 81.28 90)
(effects
(font
(size 1.27 1.27)
)
)
)
(property "Footprint" "Resistor_SMD:R_1206_3216Metric"
(at 37.338 81.28 90)
(effects
(font
(size 1.27 1.27)
)
(hide yes)
)
)
(property "Datasheet" "~"
(at 35.56 81.28 0)
(effects
(font
(size 1.27 1.27)
)
(hide yes)
)
)
(property "Description" "Resistor"
(at 35.56 81.28 0)
(effects
(font
(size 1.27 1.27)
)
(hide yes)
)
)
(property "Field5" ""
(at 35.56 81.28 90)
(effects
(font
(size 1.27 1.27)
)
(hide yes)
)
)
(property "LCSC_PART_NUMBER" "C25334"
(at 35.56 81.28 0)
(effects
(font
(size 1.27 1.27)
)
)
)
(pin "2"
(uuid "621f9ffa-bc53-4f17-8c67-70556274e5d5")
)
(pin "1"
(uuid "6c18da2f-eff2-4a53-8887-8bcae3c50e33")
)
(instances
(project "PumpOutput"
(path "/fa7c93d2-7670-4cd5-85ee-82ddfb7f2ba1"
(reference "R22")
(unit 1)
)
)
)
)
(symbol
(lib_id "Diode:BAS40-04")
(at 342.9 200.66 270)
@@ -8115,88 +8140,6 @@
)
)
)
(symbol
(lib_id "Device:R_Shunt")
(at 36.83 81.28 0)
(unit 1)
(exclude_from_sim no)
(in_bom yes)
(on_board yes)
(dnp no)
(uuid "a1bb7c77-426c-4d47-bf3d-412e55f41696")
(property "Reference" "R1"
(at 31.115 81.28 90)
(effects
(font
(size 1.27 1.27)
)
)
)
(property "Value" "0.06"
(at 33.655 81.28 90)
(effects
(font
(size 1.27 1.27)
)
)
)
(property "Footprint" "board:R_Shunt_Vishay_WSK2512_6332Metric_T1.19mm"
(at 35.052 81.28 90)
(effects
(font
(size 1.27 1.27)
)
(hide yes)
)
)
(property "Datasheet" "~"
(at 36.83 81.28 0)
(effects
(font
(size 1.27 1.27)
)
(hide yes)
)
)
(property "Description" ""
(at 36.83 81.28 0)
(effects
(font
(size 1.27 1.27)
)
(hide yes)
)
)
(property "LCSC_PART_NUMBER" "C252688"
(at 36.83 81.28 0)
(effects
(font
(size 1.27 1.27)
)
(hide yes)
)
)
(pin "1"
(uuid "e0a8ea80-0f81-4231-b874-03b9f5249a2e")
)
(pin "2"
(uuid "bcf029db-6ab1-45fd-8230-ea25cd4b8e39")
)
(pin "3"
(uuid "c3fc705b-3f3f-4a16-a58f-f5731609e069")
)
(pin "4"
(uuid "3f9ec63d-9486-46fd-80da-2e3026ee305c")
)
(instances
(project "PumpOutput"
(path "/fa7c93d2-7670-4cd5-85ee-82ddfb7f2ba1"
(reference "R1")
(unit 1)
)
)
)
)
(symbol
(lib_id "Device:R")
(at 107.95 209.55 0)
@@ -9567,6 +9510,91 @@
)
)
)
(symbol
(lib_id "Device:R")
(at 38.1 81.28 180)
(unit 1)
(exclude_from_sim no)
(in_bom yes)
(on_board yes)
(dnp no)
(fields_autoplaced yes)
(uuid "cd915e50-d838-4be3-a347-a21c637844fb")
(property "Reference" "R23"
(at 31.75 81.28 90)
(effects
(font
(size 1.27 1.27)
)
)
)
(property "Value" "100mOhm"
(at 34.29 81.28 90)
(effects
(font
(size 1.27 1.27)
)
)
)
(property "Footprint" "Resistor_SMD:R_1206_3216Metric"
(at 39.878 81.28 90)
(effects
(font
(size 1.27 1.27)
)
(hide yes)
)
)
(property "Datasheet" "~"
(at 38.1 81.28 0)
(effects
(font
(size 1.27 1.27)
)
(hide yes)
)
)
(property "Description" "Resistor"
(at 38.1 81.28 0)
(effects
(font
(size 1.27 1.27)
)
(hide yes)
)
)
(property "Field5" ""
(at 38.1 81.28 90)
(effects
(font
(size 1.27 1.27)
)
(hide yes)
)
)
(property "LCSC_PART_NUMBER" "C25334"
(at 38.1 81.28 0)
(effects
(font
(size 1.27 1.27)
)
)
)
(pin "2"
(uuid "7fe843c2-c5dd-443c-ae77-3b0b38ae2e61")
)
(pin "1"
(uuid "ff40a99b-897c-44a2-95ab-a9ce9db21ed7")
)
(instances
(project "PumpOutput"
(path "/fa7c93d2-7670-4cd5-85ee-82ddfb7f2ba1"
(reference "R23")
(unit 1)
)
)
)
)
(symbol
(lib_id "Device:LED")
(at 148.59 102.87 90)

View File

@@ -1,105 +1,124 @@
P CODE 00
P UNITS CUST 0
P arrayDim N
317GND VIA MD0118PA00X+039921Y-034803X0236Y0000R000S3
317GND VIA MD0118PA00X+073071Y-034803X0236Y0000R000S3
317GND VIA MD0118PA00X+075512Y-034803X0236Y0000R000S3
317GND VIA MD0118PA00X+070000Y-034803X0236Y0000R000S3
317GND VIA MD0118PA00X+061339Y-034803X0236Y0000R000S3
317GND VIA MD0118PA00X+048346Y-034803X0236Y0000R000S3
317GND VIA MD0118PA00X+057008Y-034803X0236Y0000R000S3
317VBAT VIA MD0118PA00X+070000Y-031653X0236Y0000R000S3
317VBAT VIA MD0118PA00X+068198Y-031624X0236Y0000R000S3
317GND VIA MD0118PA00X+044783Y-034803X0236Y0000R000S3
317GND VIA MD0118PA00X+043012Y-034843X0236Y0000R000S3
317GND VIA MD0118PA00X+048720Y-034803X0236Y0000R000S3
317GND VIA MD0118PA00X+056496Y-034803X0236Y0000R000S3
317GND VIA MD0118PA00X+071949Y-034803X0236Y0000R000S3
317GND VIA MD0118PA00X+057874Y-034803X0236Y0000R000S3
317GND VIA MD0118PA00X+071453Y-034485X0236Y0000R000S3
317GND VIA MD0118PA00X+065669Y-034803X0236Y0000R000S3
317GND VIA MD0118PA00X+050689Y-034803X0236Y0000R000S3
317GND VIA MD0118PA00X+062598Y-034803X0236Y0000R000S3
317GND VIA MD0118PA00X+068406Y-034803X0236Y0000R000S3
317GND VIA MD0118PA00X+064469Y-034803X0236Y0000R000S3
317GND VIA MD0118PA00X+042126Y-034843X0236Y0000R000S3
317GND VIA MD0118PA00X+046949Y-034803X0236Y0000R000S3
317GND VIA MD0118PA00X+060433Y-034803X0236Y0000R000S3
317GND VIA MD0118PA00X+046063Y-034803X0236Y0000R000S3
317GND VIA MD0118PA00X+069587Y-034803X0236Y0000R000S3
317GND VIA MD0118PA00X+058661Y-034803X0236Y0000R000S3
317GND VIA MD0118PA00X+068113Y-033900X0236Y0000R000S3
317GND VIA MD0118PA00X+052677Y-034803X0236Y0000R000S3
317GND VIA MD0118PA00X+044173Y-034803X0236Y0000R000S3
3175K_VBAT VIA MD0118PA00X+061122Y-032776X0236Y0000R000S3
3175K_VBAT VIA MD0118PA00X+065650Y-032874X0236Y0000R000S3
3175K_VBAT VIA MD0118PA00X+069726Y-033248X0236Y0000R000S3
317T-(P_FAULT1-K) VIA MD0118PA00X+062894Y-033563X0236Y0000R000S3
317T-(P_FAULT1-K) VIA MD0118PA00X+067283Y-033583X0236Y0000R000S3
317GND VIA MD0118PA00X+054724Y-034803X0236Y0000R000S3
317GND VIA MD0118PA00X+053937Y-034803X0236Y0000R000S3
317GND VIA MD0118PA00X+050000Y-034803X0236Y0000R000S3
317GND VIA MD0118PA00X+061811Y-034803X0236Y0000R000S3
3175K_VBAT VIA MD0118PA00X+070866Y-031969X0236Y0000R000S3
3175K_VBAT VIA MD0118PA00X+062598Y-032087X0236Y0000R000S3
3175K_VBAT VIA MD0118PA00X+064055Y-032087X0236Y0000R000S3
3175K_VBAT VIA MD0118PA00X+066378Y-032087X0236Y0000R000S3
317T-(P_FAULT1-K) VIA MD0118PA00X+063543Y-033504X0236Y0000R000S3
317T-(P_FAULT1-K) VIA MD0118PA00X+067126Y-033583X0236Y0000R000S3
317T-(P_FAULT1-K) VIA MD0118PA00X+071535Y-033622X0236Y0000R000S3
317T-(P_FAULT1-A) VIA MD0118PA00X+075577Y-032918X0236Y0000R000S3
317T-(P_FAULT1-A) VIA MD0118PA00X+041890Y-032480X0236Y0000R000S3
317T-(P_FAULT2-A) VIA MD0118PA00X+075263Y-032896X0236Y0000R000S3
317T-(P_FAULT2-A) VIA MD0118PA00X+046102Y-032948X0236Y0000R000S3
317T-(P_FAULT3-A) VIA MD0118PA00X+050236Y-032908X0236Y0000R000S3
317T-(P_FAULT3-A) VIA MD0118PA00X+075019Y-032697X0236Y0000R000S3
317T-(P_FAULT4-A) VIA MD0118PA00X+054646Y-032908X0236Y0000R000S3
317T-(P_FAULT4-A) VIA MD0118PA00X+074802Y-032468X0236Y0000R000S3
317T-(P_FAULT5-A) VIA MD0118PA00X+074539Y-032643X0236Y0000R000S3
317T-(P_FAULT5-A) VIA MD0118PA00X+059055Y-032908X0236Y0000R000S3
317T-(P_FAULT6-A) VIA MD0118PA00X+063268Y-032908X0236Y0000R000S3
317T-(P_FAULT6-A) VIA MD0118PA00X+074279Y-032819X0236Y0000R000S3
317T-(P_FAULT7-A) VIA MD0118PA00X+067598Y-032948X0236Y0000R000S3
317T-(P_FAULT7-A) VIA MD0118PA00X+074006Y-032977X0236Y0000R000S3
317T-(P_FAULT8-A) VIA MD0118PA00X+073740Y-034055X0236Y0000R000S3
317T-(P_FAULT8-A) VIA MD0118PA00X+071969Y-033268X0236Y0000R000S3
317PUMP1 VIA MD0118PA00X+041811Y-037638X0236Y0000R000S3
317PUMP1 VIA MD0118PA00X+073465Y-038898X0236Y0000R000S3
317PUMP3 VIA MD0118PA00X+073937Y-038583X0236Y0000R000S3
317PUMP3 VIA MD0118PA00X+050354Y-037638X0236Y0000R000S3
317PUMP4 VIA MD0118PA00X+074173Y-038819X0236Y0000R000S3
317PUMP4 VIA MD0118PA00X+054646Y-037638X0236Y0000R000S3
317PUMP5 VIA MD0118PA00X+074528Y-038661X0236Y0000R000S3
317PUMP5 VIA MD0118PA00X+058976Y-037658X0236Y0000R000S3
317PUMP6 VIA MD0118PA00X+074764Y-038386X0236Y0000R000S3
317PUMP6 VIA MD0118PA00X+063484Y-037598X0236Y0000R000S3
317PUMP7 VIA MD0118PA00X+067638Y-037598X0236Y0000R000S3
317PUMP7 VIA MD0118PA00X+075001Y-038179X0236Y0000R000S3
317PUMP8 VIA MD0118PA00X+075276Y-037953X0236Y0000R000S3
317PUMP8 VIA MD0118PA00X+071969Y-037953X0236Y0000R000S3
317PUMP2 VIA MD0118PA00X+046102Y-037598X0236Y0000R000S3
317PUMP2 VIA MD0118PA00X+073740Y-038307X0236Y0000R000S3
3173_3V VIA MD0118PA00X+072726Y-032903X0236Y0000R000S3
3173_3V VIA MD0118PA00X+067992Y-032953X0236Y0000R000S3
317SDA VIA MD0118PA00X+069685Y-034173X0236Y0000R000S3
317SDA VIA MD0118PA00X+072933Y-034173X0236Y0000R000S3
317SCL VIA MD0118PA00X+068363Y-034012X0236Y0000R000S3
317SCL VIA MD0118PA00X+073228Y-033957X0236Y0000R000S3
327PUMP3 R16 -1 A01X+050315Y-036073X0315Y0374R270S2
327NET-(Q3-G) R16 -2 A01X+050315Y-035423X0315Y0374R270S2
327NET-(Q3-G) Q3 -1 A01X+049621Y-035984X0354Y0315R180S2
327GND Q3 -2 A01X+049621Y-035236X0354Y0315R180S2
327-(PUMP3-PIN_1) Q3 -3 A01X+048834Y-035610X0354Y0315R180S2
327NET-(Q5-G) Q5 -1 A01X+058297Y-035984X0354Y0315R180S2
327GND Q5 -2 A01X+058297Y-035236X0354Y0315R180S2
327-(PUMP5-PIN_1) Q5 -3 A01X+057510Y-035610X0354Y0315R180S2
327NET-(Q1-G) Q1 -1 A01X+041132Y-035984X0354Y0315R180S2
327GND Q1 -2 A01X+041132Y-035236X0354Y0315R180S2
327-(PUMP1-PIN_1) Q1 -3 A01X+040344Y-035610X0354Y0315R180S2
317-(PUMP8-PIN_1) PUMP8 -1 D0394PA00X+069503Y-037362X0669Y0787R000S0
317VCC PUMP8 -2 D0394PA00X+070487Y-037362X0669Y0787R000S0
317-(PUMP7-PIN_1) PUMP7 -1 D0394PA00X+065172Y-037382X0669Y0787R000S0
317VCC PUMP7 -2 D0394PA00X+066156Y-037382X0669Y0787R000S0
327NET-(Q6-G) Q6 -1 A01X+062613Y-035984X0354Y0315R180S2
327GND Q6 -2 A01X+062613Y-035236X0354Y0315R180S2
327-(PUMP6-PIN_1) Q6 -3 A01X+061826Y-035610X0354Y0315R180S2
327PUMP8 R19 -1 A01X+071969Y-036033X0315Y0374R270S2
327NET-(Q8-G) R19 -2 A01X+071969Y-035384X0315Y0374R270S2
327NET-(Q5-G) R10 -1 A01X+058041Y-033898X0315Y0374R180S2
327GND R10 -2 A01X+057392Y-033898X0315Y0374R180S2
327T-(P_FAULT1-K) P_FAUL-1 A01X+058504Y-033164X0384Y0551R270S2
327T-(P_FAULT5-A) P_FAUL-2 A01X+058504Y-032426X0384Y0551R270S2
327NET-(Q6-G) R11 -1 A01X+062333Y-033898X0315Y0374R180S2
327GND R11 -2 A01X+061683Y-033898X0315Y0374R180S2
327-(PUMP6-PIN_1) PUMP_D-1 A01X+061811Y-033164X0384Y0551R270S2
3275K_VBAT PUMP_D-2 A01X+061811Y-032426X0384Y0551R270S2
327PUMP6 R21 -1 A01X+063484Y-037234X0315Y0374R270S2
327NET-(Q6-G) R21 -2 A01X+063484Y-036585X0315Y0374R270S2
327-(PUMP2-PIN_1) PUMP_D-1 A01X+044660Y-033204X0384Y0551R270S2
3275K_VBAT PUMP_D-2 A01X+044660Y-032466X0384Y0551R270S2
327PUMP1 R14 -1 A01X+041890Y-036073X0315Y0374R270S2
327NET-(Q1-G) R14 -2 A01X+041890Y-035423X0315Y0374R270S2
327NET-(Q2-G) R7 -1 A01X+045167Y-033898X0315Y0374R180S2
327GND R7 -2 A01X+044518Y-033898X0315Y0374R180S2
327T-(P_FAULT1-K) P_FAUL-1 A01X+054173Y-033164X0384Y0551R270S2
327T-(P_FAULT4-A) P_FAUL-2 A01X+054173Y-032426X0384Y0551R270S2
327NET-(Q7-G) R12 -1 A01X+066663Y-033937X0315Y0374R180S2
327GND R12 -2 A01X+066014Y-033937X0315Y0374R180S2
327NET-(Q4-G) Q4 -1 A01X+053967Y-035984X0354Y0315R180S2
327GND Q4 -2 A01X+053967Y-035236X0354Y0315R180S2
327-(PUMP4-PIN_1) Q4 -3 A01X+053179Y-035610X0354Y0315R180S2
327-(PUMP5-PIN_1) PUMP_D-1 A01X+057520Y-033164X0384Y0551R270S2
3275K_VBAT PUMP_D-2 A01X+057520Y-032426X0384Y0551R270S2
317T-(P_FAULT1-A) VIA MD0118PA00X+074134Y-033169X0236Y0000R000S3
317T-(P_FAULT1-A) VIA MD0118PA00X+044488Y-032599X0236Y0000R000S3
317T-(P_FAULT2-A) VIA MD0118PA00X+048386Y-032796X0236Y0000R000S3
317T-(P_FAULT2-A) VIA MD0118PA00X+073858Y-033150X0236Y0000R000S3
317T-(P_FAULT3-A) VIA MD0118PA00X+073543Y-033150X0236Y0000R000S3
317T-(P_FAULT3-A) VIA MD0118PA00X+052165Y-032875X0236Y0000R000S3
317T-(P_FAULT4-A) VIA MD0118PA00X+056260Y-032954X0236Y0000R000S3
317T-(P_FAULT4-A) VIA MD0118PA00X+073228Y-033150X0236Y0000R000S3
317T-(P_FAULT5-A) VIA MD0118PA00X+074724Y-031850X0236Y0000R000S3
317T-(P_FAULT5-A) VIA MD0118PA00X+060157Y-032441X0236Y0000R000S3
317T-(P_FAULT6-A) VIA MD0118PA00X+074409Y-031850X0236Y0000R000S3
317T-(P_FAULT6-A) VIA MD0118PA00X+064055Y-032362X0236Y0000R000S3
317T-(P_FAULT7-A) VIA MD0118PA00X+067126Y-032165X0236Y0000R000S3
317T-(P_FAULT7-A) VIA MD0118PA00X+074094Y-031850X0236Y0000R000S3
317T-(P_FAULT8-A) VIA MD0118PA00X+073780Y-031850X0236Y0000R000S3
317T-(P_FAULT8-A) VIA MD0118PA00X+071417Y-031850X0236Y0000R000S3
317PUMP1 VIA MD0118PA00X+073425Y-034646X0236Y0000R000S3
317PUMP1 VIA MD0118PA00X+045374Y-036713X0236Y0000R000S3
317PUMP3 VIA MD0118PA00X+053248Y-036319X0236Y0000R000S3
317PUMP3 VIA MD0118PA00X+074370Y-034449X0236Y0000R000S3
317PUMP4 VIA MD0118PA00X+074764Y-034331X0236Y0000R000S3
317PUMP4 VIA MD0118PA00X+057185Y-036122X0236Y0000R000S3
317PUMP5 VIA MD0118PA00X+061024Y-036024X0236Y0000R000S3
317PUMP5 VIA MD0118PA00X+075079Y-034252X0236Y0000R000S3
317PUMP6 VIA MD0118PA00X+075354Y-034134X0236Y0000R000S3
317PUMP6 VIA MD0118PA00X+064654Y-035649X0236Y0000R000S3
317PUMP7 VIA MD0118PA00X+068898Y-035630X0236Y0000R000S3
317PUMP7 VIA MD0118PA00X+075551Y-033937X0236Y0000R000S3
317PUMP2 VIA MD0118PA00X+073858Y-034567X0236Y0000R000S3
317PUMP2 VIA MD0118PA00X+049311Y-036516X0236Y0000R000S3
3173_3V VIA MD0118PA00X+068287Y-033051X0236Y0000R000S3
3173_3V VIA MD0118PA00X+071929Y-033268X0236Y0000R000S3
317SDA VIA MD0118PA00X+069796Y-033232X0236Y0000R000S3
317SDA VIA MD0118PA00X+072677Y-033150X0236Y0000R000S3
317SCL VIA MD0118PA00X+070039Y-033103X0236Y0000R000S3
317SCL VIA MD0118PA00X+072953Y-033150X0236Y0000R000S3
327VCC R23 -1 A01X+069281Y-031654X0443Y0689R000S2
327VBAT R23 -2 A01X+070433Y-031654X0443Y0689R000S2
327VCC R22 -1 A01X+067416Y-031654X0443Y0689R000S2
327VBAT R22 -2 A01X+068568Y-031654X0443Y0689R000S2
327PUMP3 R16 -1 A01X+052195Y-033858X0315Y0374R180S2
327NET-(Q3-G) R16 -2 A01X+051545Y-033858X0315Y0374R180S2
327NET-(Q3-G) Q3 -1 A01X+052657Y-035984X0354Y0315R180S2
327GND Q3 -2 A01X+052657Y-035236X0354Y0315R180S2
327-(PUMP3-PIN_1) Q3 -3 A01X+051870Y-035610X0354Y0315R180S2
327NET-(Q5-G) Q5 -1 A01X+060443Y-035984X0354Y0315R180S2
327GND Q5 -2 A01X+060443Y-035236X0354Y0315R180S2
327-(PUMP5-PIN_1) Q5 -3 A01X+059656Y-035610X0354Y0315R180S2
327NET-(Q1-G) Q1 -1 A01X+044783Y-035984X0354Y0315R180S2
327GND Q1 -2 A01X+044783Y-035236X0354Y0315R180S2
327-(PUMP1-PIN_1) Q1 -3 A01X+043996Y-035610X0354Y0315R180S2
317-(PUMP8-PIN_1) PUMP8 -1 D0394PA00X+070551Y-037362X0669Y0787R000S0
317VCC PUMP8 -2 D0394PA00X+071535Y-037362X0669Y0787R000S0
317-(PUMP7-PIN_1) PUMP7 -1 D0394PA00X+066535Y-037382X0669Y0787R000S0
317VCC PUMP7 -2 D0394PA00X+067520Y-037382X0669Y0787R000S0
327NET-(Q6-G) Q6 -1 A01X+064469Y-035984X0354Y0315R180S2
327GND Q6 -2 A01X+064469Y-035236X0354Y0315R180S2
327-(PUMP6-PIN_1) Q6 -3 A01X+063681Y-035610X0354Y0315R180S2
327PUMP8 R19 -1 A01X+073199Y-036024X0315Y0374R180S2
327NET-(Q8-G) R19 -2 A01X+072549Y-036024X0315Y0374R180S2
327NET-(Q5-G) R10 -1 A01X+058661Y-033829X0315Y0374R090S2
327GND R10 -2 A01X+058661Y-034478X0315Y0374R090S2
327T-(P_FAULT1-K) P_FAUL-1 A01X+059646Y-033164X0384Y0551R270S2
327T-(P_FAULT5-A) P_FAUL-2 A01X+059646Y-032426X0384Y0551R270S2
327NET-(Q6-G) R11 -1 A01X+062598Y-033829X0315Y0374R090S2
327GND R11 -2 A01X+062598Y-034478X0315Y0374R090S2
327-(PUMP6-PIN_1) PUMP_D-1 A01X+062598Y-033164X0384Y0551R270S2
3275K_VBAT PUMP_D-2 A01X+062598Y-032426X0384Y0551R270S2
327PUMP6 R21 -1 A01X+064104Y-033858X0315Y0374R180S2
327NET-(Q6-G) R21 -2 A01X+063455Y-033858X0315Y0374R180S2
327-(PUMP2-PIN_1) PUMP_D-1 A01X+046949Y-033145X0384Y0551R270S2
3275K_VBAT PUMP_D-2 A01X+046949Y-032406X0384Y0551R270S2
327PUMP1 R14 -1 A01X+044488Y-033858X0315Y0374R180S2
327NET-(Q1-G) R14 -2 A01X+043839Y-033858X0315Y0374R180S2
327NET-(Q2-G) R7 -1 A01X+046949Y-033829X0315Y0374R090S2
327GND R7 -2 A01X+046949Y-034478X0315Y0374R090S2
327T-(P_FAULT1-K) P_FAUL-1 A01X+055709Y-033164X0384Y0551R270S2
327T-(P_FAULT4-A) P_FAUL-2 A01X+055709Y-032426X0384Y0551R270S2
327NET-(Q7-G) R12 -1 A01X+065945Y-033829X0315Y0374R090S2
327GND R12 -2 A01X+065945Y-034478X0315Y0374R090S2
327NET-(Q4-G) Q4 -1 A01X+056521Y-035984X0354Y0315R180S2
327GND Q4 -2 A01X+056521Y-035236X0354Y0315R180S2
327-(PUMP4-PIN_1) Q4 -3 A01X+055733Y-035610X0354Y0315R180S2
327-(PUMP5-PIN_1) PUMP_D-1 A01X+058661Y-033164X0384Y0551R270S2
3275K_VBAT PUMP_D-2 A01X+058661Y-032426X0384Y0551R270S2
327GND C3 -1 A01X+067913Y-034099X0423Y0374R270S2
3273_3V C3 -2 A01X+067913Y-033420X0423Y0374R270S2
327NET-(U2-IN+) U2 -1 A01X+069380Y-033159X0522Y0197R090S2
@@ -112,133 +131,129 @@ P arrayDim N
327SDA U2 -8 A01X+069380Y-034055X0522Y0197R090S2
327-(PUMP8-PIN_1) PUMP_D-1 A01X+070472Y-033204X0384Y0551R270S2
3275K_VBAT PUMP_D-2 A01X+070472Y-032466X0384Y0551R270S2
327-(PUMP1-PIN_1) PUMP_D-1 A01X+040463Y-033204X0384Y0551R270S2
3275K_VBAT PUMP_D-2 A01X+040463Y-032466X0384Y0551R270S2
327-(PUMP1-PIN_1) PUMP_D-1 A01X+043012Y-033169X0384Y0551R270S2
3275K_VBAT PUMP_D-2 A01X+043012Y-032431X0384Y0551R270S2
317GND U1 -1 D0394PA00X+076024Y-031181X0669Y0669R090S0
317GND U1 -2 D0394PA00X+076181Y-039173X0669Y0669R000S0
317GND U1 -3 D0394PA00X+037933Y-039173X0669Y0669R000S0
317GND U1 -4 D0394PA00X+037933Y-031004X0669Y0669R000S0
317GND U1 -2 D0394PA00X+068937Y-039173X0669Y0669R000S0
317GND U1 -3 D0394PA00X+045315Y-039173X0669Y0669R000S0
317GND U1 -4 D0394PA00X+041969Y-031004X0669Y0669R000S0
317SCL U1 -5 D0394PA00X+075024Y-031181X0669Y0669R090S0
317SDA U1 -6 D0394PA00X+074024Y-031181X0669Y0669R090S0
3173_3V U1 -7 D0394PA00X+073024Y-031181X0669Y0669R090S0
317VBAT U1 -8 D0394PA00X+072024Y-031181X0669Y0669R090S0
3273-~{INT}-PAD1) U3 -1 A01X+072726Y-036260X0689Y0177R270S2
327GND U3 -2 A01X+072982Y-036260X0689Y0177R270S2
327GND U3 -3 A01X+073238Y-036260X0689Y0177R270S2
327PUMP1 U3 -4 A01X+073494Y-036260X0689Y0177R270S2
327PUMP2 U3 -5 A01X+073750Y-036260X0689Y0177R270S2
327PUMP3 U3 -6 A01X+074006Y-036260X0689Y0177R270S2
327PUMP4 U3 -7 A01X+074262Y-036260X0689Y0177R270S2
327PUMP5 U3 -8 A01X+074518Y-036260X0689Y0177R270S2
327PUMP6 U3 -9 A01X+074774Y-036260X0689Y0177R270S2
327PUMP7 U3 -10 A01X+075030Y-036260X0689Y0177R270S2
327PUMP8 U3 -11 A01X+075285Y-036260X0689Y0177R270S2
327GND U3 -12 A01X+075541Y-036260X0689Y0177R270S2
327T-(P_FAULT1-A) U3 -13 A01X+075541Y-033425X0689Y0177R270S2
327T-(P_FAULT2-A) U3 -14 A01X+075285Y-033425X0689Y0177R270S2
327T-(P_FAULT3-A) U3 -15 A01X+075030Y-033425X0689Y0177R270S2
327T-(P_FAULT4-A) U3 -16 A01X+074774Y-033425X0689Y0177R270S2
327T-(P_FAULT5-A) U3 -17 A01X+074518Y-033425X0689Y0177R270S2
327T-(P_FAULT6-A) U3 -18 A01X+074262Y-033425X0689Y0177R270S2
327T-(P_FAULT7-A) U3 -19 A01X+074006Y-033425X0689Y0177R270S2
327T-(P_FAULT8-A) U3 -20 A01X+073750Y-033425X0689Y0177R270S2
327GND U3 -21 A01X+073494Y-033425X0689Y0177R270S2
327SCL U3 -22 A01X+073238Y-033425X0689Y0177R270S2
327SDA U3 -23 A01X+072982Y-033425X0689Y0177R270S2
3273_3V U3 -24 A01X+072726Y-033425X0689Y0177R270S2
327VBAT R1 -1 A01X+070073Y-031148X0902Y0799R180S2
327NET-(R1-PAD2) R1 -2 A01X+070248Y-031898X0551Y0299R180S2
327NET-(R1-PAD3) R1 -3 A01X+067547Y-031898X0551Y0299R180S2
327VCC R1 -4 A01X+067722Y-031148X0902Y0799R180S2
317-(PUMP5-PIN_1) PUMP5 -1 D0394PA00X+056526Y-037382X0669Y0787R000S0
317VCC PUMP5 -2 D0394PA00X+057510Y-037382X0669Y0787R000S0
327-(PUMP3-PIN_1) PUMP_D-1 A01X+048819Y-033164X0384Y0551R270S2
3275K_VBAT PUMP_D-2 A01X+048819Y-032426X0384Y0551R270S2
327PUMP7 R20 -1 A01X+067638Y-037175X0315Y0374R270S2
327NET-(Q7-G) R20 -2 A01X+067638Y-036526X0315Y0374R270S2
3273-~{INT}-PAD1) U3 -1 A01X+072726Y-035197X0689Y0177R270S2
327GND U3 -2 A01X+072982Y-035197X0689Y0177R270S2
327GND U3 -3 A01X+073238Y-035197X0689Y0177R270S2
327PUMP1 U3 -4 A01X+073494Y-035197X0689Y0177R270S2
327PUMP2 U3 -5 A01X+073750Y-035197X0689Y0177R270S2
327PUMP3 U3 -6 A01X+074006Y-035197X0689Y0177R270S2
327PUMP4 U3 -7 A01X+074262Y-035197X0689Y0177R270S2
327PUMP5 U3 -8 A01X+074518Y-035197X0689Y0177R270S2
327PUMP6 U3 -9 A01X+074774Y-035197X0689Y0177R270S2
327PUMP7 U3 -10 A01X+075030Y-035197X0689Y0177R270S2
327PUMP8 U3 -11 A01X+075285Y-035197X0689Y0177R270S2
327GND U3 -12 A01X+075541Y-035197X0689Y0177R270S2
327T-(P_FAULT1-A) U3 -13 A01X+075541Y-032362X0689Y0177R270S2
327T-(P_FAULT2-A) U3 -14 A01X+075285Y-032362X0689Y0177R270S2
327T-(P_FAULT3-A) U3 -15 A01X+075030Y-032362X0689Y0177R270S2
327T-(P_FAULT4-A) U3 -16 A01X+074774Y-032362X0689Y0177R270S2
327T-(P_FAULT5-A) U3 -17 A01X+074518Y-032362X0689Y0177R270S2
327T-(P_FAULT6-A) U3 -18 A01X+074262Y-032362X0689Y0177R270S2
327T-(P_FAULT7-A) U3 -19 A01X+074006Y-032362X0689Y0177R270S2
327T-(P_FAULT8-A) U3 -20 A01X+073750Y-032362X0689Y0177R270S2
327GND U3 -21 A01X+073494Y-032362X0689Y0177R270S2
327SCL U3 -22 A01X+073238Y-032362X0689Y0177R270S2
327SDA U3 -23 A01X+072982Y-032362X0689Y0177R270S2
3273_3V U3 -24 A01X+072726Y-032362X0689Y0177R270S2
317-(PUMP5-PIN_1) PUMP5 -1 D0394PA00X+058661Y-037382X0669Y0787R000S0
317VCC PUMP5 -2 D0394PA00X+059646Y-037382X0669Y0787R000S0
327-(PUMP3-PIN_1) PUMP_D-1 A01X+050689Y-033164X0384Y0551R270S2
3275K_VBAT PUMP_D-2 A01X+050689Y-032426X0384Y0551R270S2
327PUMP7 R20 -1 A01X+067254Y-033957X0315Y0374R180S2
327NET-(Q7-G) R20 -2 A01X+066604Y-033957X0315Y0374R180S2
327T-(P_FAULT1-K) P_FAUL-1 A01X+067141Y-033204X0384Y0551R270S2
327T-(P_FAULT7-A) P_FAUL-2 A01X+067141Y-032466X0384Y0551R270S2
317-(PUMP6-PIN_1) PUMP6 -1 D0394PA00X+060842Y-037382X0669Y0787R000S0
317VCC PUMP6 -2 D0394PA00X+061826Y-037382X0669Y0787R000S0
327VCC C2 -1 A01X+055709Y-033858X0463Y0571R000S2
327GND C2 -2 A01X+056526Y-033858X0463Y0571R000S2
327NET-(R1-PAD2) R3 -1 A01X+069783Y-032480X0315Y0374R180S2
317-(PUMP6-PIN_1) PUMP6 -1 D0394PA00X+062598Y-037382X0669Y0787R000S0
317VCC PUMP6 -2 D0394PA00X+063583Y-037382X0669Y0787R000S0
327VCC C2 -1 A01X+065251Y-033071X0463Y0571R180S2
327GND C2 -2 A01X+064434Y-033071X0463Y0571R180S2
327VBAT R3 -1 A01X+069783Y-032480X0315Y0374R180S2
327NET-(U2-IN+) R3 -2 A01X+069134Y-032480X0315Y0374R180S2
327NET-(U2-IN-) R2 -1 A01X+068533Y-032480X0315Y0374R180S2
327NET-(R1-PAD3) R2 -2 A01X+067884Y-032480X0315Y0374R180S2
327VCC R2 -2 A01X+067884Y-032480X0315Y0374R180S2
327-(PUMP7-PIN_1) PUMP_D-1 A01X+066156Y-033204X0384Y0551R270S2
3275K_VBAT PUMP_D-2 A01X+066156Y-032466X0384Y0551R270S2
327NET-(Q2-G) Q2 -1 A01X+045354Y-036024X0354Y0315R180S2
327GND Q2 -2 A01X+045354Y-035276X0354Y0315R180S2
327-(PUMP2-PIN_1) Q2 -3 A01X+044567Y-035650X0354Y0315R180S2
327T-(P_FAULT1-K) P_FAUL-1 A01X+045645Y-033204X0384Y0551R270S2
327T-(P_FAULT2-A) P_FAUL-2 A01X+045645Y-032466X0384Y0551R270S2
327GND D3 -1 A01X+047062Y-035236X0581Y0236R000S2
327VCC D3 -2 A01X+047062Y-035984X0581Y0236R000S2
327-(PUMP3-PIN_1) D3 -3 A01X+047800Y-035610X0581Y0236R000S2
327GND D6 -1 A01X+060054Y-035217X0581Y0236R000S2
327VCC D6 -2 A01X+060054Y-035965X0581Y0236R000S2
327-(PUMP6-PIN_1) D6 -3 A01X+060792Y-035591X0581Y0236R000S2
327T-(P_FAULT1-K) P_FAUL-1 A01X+049764Y-033164X0384Y0551R270S2
327T-(P_FAULT3-A) P_FAUL-2 A01X+049764Y-032426X0384Y0551R270S2
327T-(P_FAULT1-K) P_FAUL-1 A01X+062795Y-033164X0384Y0551R270S2
327T-(P_FAULT6-A) P_FAUL-2 A01X+062795Y-032426X0384Y0551R270S2
317-(PUMP3-PIN_1) PUMP3 -1 D0394PA00X+047849Y-037382X0669Y0787R000S0
317VCC PUMP3 -2 D0394PA00X+048834Y-037382X0669Y0787R000S0
327GND C1 -1 A01X+072200Y-032382X0423Y0374R000S2
3273_3V C1 -2 A01X+072879Y-032382X0423Y0374R000S2
327NET-(Q8-G) R13 -1 A01X+070994Y-033937X0315Y0374R180S2
327GND R13 -2 A01X+070344Y-033937X0315Y0374R180S2
327GND D2 -1 A01X+042913Y-035256X0581Y0236R000S2
327VCC D2 -2 A01X+042913Y-036004X0581Y0236R000S2
327-(PUMP2-PIN_1) D2 -3 A01X+043652Y-035630X0581Y0236R000S2
327VCC R5 -1 A01X+038189Y-033120X0315Y0374R270S2
3275K_VBAT R5 -2 A01X+038189Y-032470X0315Y0374R270S2
327GND D5 -1 A01X+055738Y-035236X0581Y0236R000S2
327VCC D5 -2 A01X+055738Y-035984X0581Y0236R000S2
327-(PUMP5-PIN_1) D5 -3 A01X+056476Y-035610X0581Y0236R000S2
327PUMP4 R17 -1 A01X+054646Y-036073X0315Y0374R270S2
327NET-(Q4-G) R17 -2 A01X+054646Y-035423X0315Y0374R270S2
317-(PUMP2-PIN_1) PUMP2 -1 D0394PA00X+043676Y-037382X0669Y0787R000S0
317VCC PUMP2 -2 D0394PA00X+044660Y-037382X0669Y0787R000S0
327T-(P_FAULT1-K) P_FAUL-1 A01X+041447Y-033204X0384Y0551R270S2
327T-(P_FAULT1-A) P_FAUL-2 A01X+041447Y-032466X0384Y0551R270S2
327PUMP5 R18 -1 A01X+058976Y-036073X0315Y0374R270S2
327NET-(Q5-G) R18 -2 A01X+058976Y-035423X0315Y0374R270S2
327GND D1 -1 A01X+038691Y-035256X0581Y0236R000S2
327VCC D1 -2 A01X+038691Y-036004X0581Y0236R000S2
327-(PUMP1-PIN_1) D1 -3 A01X+039429Y-035630X0581Y0236R000S2
327-(PUMP4-PIN_1) PUMP_D-1 A01X+053189Y-033164X0384Y0551R270S2
3275K_VBAT PUMP_D-2 A01X+053189Y-032426X0384Y0551R270S2
327NET-(Q8-G) Q8 -1 A01X+071275Y-035965X0354Y0315R180S2
327GND Q8 -2 A01X+071275Y-035217X0354Y0315R180S2
327-(PUMP8-PIN_1) Q8 -3 A01X+070487Y-035591X0354Y0315R180S2
327GND D4 -1 A01X+051407Y-035236X0581Y0236R000S2
327VCC D4 -2 A01X+051407Y-035984X0581Y0236R000S2
327-(PUMP4-PIN_1) D4 -3 A01X+052146Y-035610X0581Y0236R000S2
317-(PUMP1-PIN_1) PUMP1 -1 D0394PA00X+039478Y-037402X0669Y0787R000S0
317VCC PUMP1 -2 D0394PA00X+040463Y-037402X0669Y0787R000S0
327NET-(Q2-G) Q2 -1 A01X+048720Y-035984X0354Y0315R180S2
327GND Q2 -2 A01X+048720Y-035236X0354Y0315R180S2
327-(PUMP2-PIN_1) Q2 -3 A01X+047933Y-035610X0354Y0315R180S2
327T-(P_FAULT1-K) P_FAUL-1 A01X+047933Y-033145X0384Y0551R270S2
327T-(P_FAULT2-A) P_FAUL-2 A01X+047933Y-032406X0384Y0551R270S2
327GND D3 -1 A01X+050000Y-035236X0581Y0236R000S2
327VCC D3 -2 A01X+050000Y-035984X0581Y0236R000S2
327-(PUMP3-PIN_1) D3 -3 A01X+050738Y-035610X0581Y0236R000S2
327GND D6 -1 A01X+061811Y-035217X0581Y0236R000S2
327VCC D6 -2 A01X+061811Y-035965X0581Y0236R000S2
327-(PUMP6-PIN_1) D6 -3 A01X+062549Y-035591X0581Y0236R000S2
327T-(P_FAULT1-K) P_FAUL-1 A01X+051673Y-033164X0384Y0551R270S2
327T-(P_FAULT3-A) P_FAUL-2 A01X+051673Y-032426X0384Y0551R270S2
327T-(P_FAULT1-K) P_FAUL-1 A01X+063583Y-033164X0384Y0551R270S2
327T-(P_FAULT6-A) P_FAUL-2 A01X+063583Y-032426X0384Y0551R270S2
317-(PUMP3-PIN_1) PUMP3 -1 D0394PA00X+050787Y-037382X0669Y0787R000S0
317VCC PUMP3 -2 D0394PA00X+051772Y-037382X0669Y0787R000S0
327GND C1 -1 A01X+071255Y-033937X0423Y0374R000S2
3273_3V C1 -2 A01X+071934Y-033937X0423Y0374R000S2
327NET-(Q8-G) R13 -1 A01X+069951Y-033937X0315Y0374R000S2
327GND R13 -2 A01X+070600Y-033937X0315Y0374R000S2
327GND D2 -1 A01X+046088Y-035256X0581Y0236R000S2
327VCC D2 -2 A01X+046088Y-036004X0581Y0236R000S2
327-(PUMP2-PIN_1) D2 -3 A01X+046826Y-035630X0581Y0236R000S2
327VCC R5 -1 A01X+065089Y-032283X0315Y0374R180S2
3275K_VBAT R5 -2 A01X+064439Y-032283X0315Y0374R180S2
327GND D5 -1 A01X+057884Y-035236X0581Y0236R000S2
327VCC D5 -2 A01X+057884Y-035984X0581Y0236R000S2
327-(PUMP5-PIN_1) D5 -3 A01X+058622Y-035610X0581Y0236R000S2
327PUMP4 R17 -1 A01X+056230Y-033858X0315Y0374R180S2
327NET-(Q4-G) R17 -2 A01X+055581Y-033858X0315Y0374R180S2
317-(PUMP2-PIN_1) PUMP2 -1 D0394PA00X+046850Y-037382X0669Y0787R000S0
317VCC PUMP2 -2 D0394PA00X+047835Y-037382X0669Y0787R000S0
327T-(P_FAULT1-K) P_FAUL-1 A01X+043996Y-033145X0384Y0551R270S2
327T-(P_FAULT1-A) P_FAUL-2 A01X+043996Y-032406X0384Y0551R270S2
327PUMP5 R18 -1 A01X+060167Y-033858X0315Y0374R180S2
327NET-(Q5-G) R18 -2 A01X+059518Y-033858X0315Y0374R180S2
327GND D1 -1 A01X+042077Y-035256X0581Y0236R000S2
327VCC D1 -2 A01X+042077Y-036004X0581Y0236R000S2
327-(PUMP1-PIN_1) D1 -3 A01X+042815Y-035630X0581Y0236R000S2
327-(PUMP4-PIN_1) PUMP_D-1 A01X+054724Y-033164X0384Y0551R270S2
3275K_VBAT PUMP_D-2 A01X+054724Y-032426X0384Y0551R270S2
327NET-(Q8-G) Q8 -1 A01X+071949Y-035965X0354Y0315R180S2
327GND Q8 -2 A01X+071949Y-035217X0354Y0315R180S2
327-(PUMP8-PIN_1) Q8 -3 A01X+071161Y-035591X0354Y0315R180S2
327GND D4 -1 A01X+053962Y-035236X0581Y0236R000S2
327VCC D4 -2 A01X+053962Y-035984X0581Y0236R000S2
327-(PUMP4-PIN_1) D4 -3 A01X+054700Y-035610X0581Y0236R000S2
317-(PUMP1-PIN_1) PUMP1 -1 D0394PA00X+042913Y-037402X0669Y0787R000S0
317VCC PUMP1 -2 D0394PA00X+043898Y-037402X0669Y0787R000S0
327T-(P_FAULT1-K) P_FAUL-1 A01X+071417Y-033204X0384Y0551R270S2
327T-(P_FAULT8-A) P_FAUL-2 A01X+071417Y-032466X0384Y0551R270S2
327GND R4 -1 A01X+042884Y-032677X0315Y0374R180S2
327T-(P_FAULT1-K) R4 -2 A01X+042234Y-032677X0315Y0374R180S2
327PUMP2 R15 -1 A01X+046063Y-036112X0315Y0374R270S2
327NET-(Q2-G) R15 -2 A01X+046063Y-035463X0315Y0374R270S2
317-(PUMP4-PIN_1) PUMP4 -1 D0394PA00X+052195Y-037382X0669Y0787R000S0
317VCC PUMP4 -2 D0394PA00X+053179Y-037382X0669Y0787R000S0
327NET-(Q3-G) R8 -1 A01X+049350Y-033858X0315Y0374R180S2
327GND R8 -2 A01X+048701Y-033858X0315Y0374R180S2
327NET-(Q1-G) R6 -1 A01X+040955Y-033858X0315Y0374R180S2
327GND R6 -2 A01X+040305Y-033858X0315Y0374R180S2
327NET-(Q7-G) Q7 -1 A01X+066944Y-035984X0354Y0315R180S2
327GND Q7 -2 A01X+066944Y-035236X0354Y0315R180S2
327-(PUMP7-PIN_1) Q7 -3 A01X+066156Y-035610X0354Y0315R180S2
327GND D7 -1 A01X+064385Y-035236X0581Y0236R000S2
327VCC D7 -2 A01X+064385Y-035984X0581Y0236R000S2
327-(PUMP7-PIN_1) D7 -3 A01X+065123Y-035610X0581Y0236R000S2
327GND D8 -1 A01X+068716Y-035236X0581Y0236R000S2
327VCC D8 -2 A01X+068716Y-035984X0581Y0236R000S2
327-(PUMP8-PIN_1) D8 -3 A01X+069454Y-035610X0581Y0236R000S2
327NET-(Q4-G) R9 -1 A01X+053711Y-033898X0315Y0374R180S2
327GND R9 -2 A01X+053061Y-033898X0315Y0374R180S2
327GND R4 -1 A01X+046260Y-034478X0315Y0374R270S2
327T-(P_FAULT1-K) R4 -2 A01X+046260Y-033829X0315Y0374R270S2
327PUMP2 R15 -1 A01X+048455Y-033858X0315Y0374R180S2
327NET-(Q2-G) R15 -2 A01X+047805Y-033858X0315Y0374R180S2
317-(PUMP4-PIN_1) PUMP4 -1 D0394PA00X+054724Y-037382X0669Y0787R000S0
317VCC PUMP4 -2 D0394PA00X+055709Y-037382X0669Y0787R000S0
327NET-(Q3-G) R8 -1 A01X+050689Y-033858X0315Y0374R090S2
327GND R8 -2 A01X+050689Y-034508X0315Y0374R090S2
327NET-(Q1-G) R6 -1 A01X+043012Y-033829X0315Y0374R090S2
327GND R6 -2 A01X+043012Y-034478X0315Y0374R090S2
327NET-(Q7-G) Q7 -1 A01X+068406Y-035984X0354Y0315R180S2
327GND Q7 -2 A01X+068406Y-035236X0354Y0315R180S2
327-(PUMP7-PIN_1) Q7 -3 A01X+067618Y-035610X0354Y0315R180S2
327GND D7 -1 A01X+065797Y-035236X0581Y0236R000S2
327VCC D7 -2 A01X+065797Y-035984X0581Y0236R000S2
327-(PUMP7-PIN_1) D7 -3 A01X+066535Y-035610X0581Y0236R000S2
327GND D8 -1 A01X+069636Y-035236X0581Y0236R000S2
327VCC D8 -2 A01X+069636Y-035984X0581Y0236R000S2
327-(PUMP8-PIN_1) D8 -3 A01X+070374Y-035610X0581Y0236R000S2
327NET-(Q4-G) R9 -1 A01X+054724Y-033829X0315Y0374R090S2
327GND R9 -2 A01X+054724Y-034478X0315Y0374R090S2
999

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -58,20 +58,7 @@
"width": 0.0
}
],
"drc_exclusions": [
[
"silk_over_copper|98836040|49235000|02eec4a9-10aa-4ffd-acfb-ce06a25de154|00000000-0000-0000-0000-000000000000",
""
],
[
"silk_over_copper|98836040|49235000|1e5edd9f-7842-4f7f-b4f4-7be08845a9d9|00000000-0000-0000-0000-000000000000",
""
],
[
"silk_over_copper|98836040|49235000|a2ae2bdf-d2ce-446d-9eca-7a31fd18eea4|00000000-0000-0000-0000-000000000000",
""
]
],
"drc_exclusions": [],
"meta": {
"version": 2
},
@@ -148,11 +135,11 @@
"min_silk_clearance": 0.0,
"min_text_height": 0.8,
"min_text_thickness": 0.08,
"min_through_hole_diameter": 0.15,
"min_through_hole_diameter": 0.25,
"min_track_width": 0.0,
"min_via_annular_width": 0.05,
"min_via_diameter": 0.25,
"solder_mask_to_copper_clearance": 0.0,
"solder_mask_to_copper_clearance": 0.005,
"use_height_for_length_calcs": true
},
"teardrop_options": [
@@ -237,8 +224,8 @@
"drill": 0.0
},
{
"diameter": 0.25,
"drill": 0.15
"diameter": 0.3,
"drill": 0.25
}
],
"zones_allow_external_fillets": false
@@ -591,7 +578,7 @@
"group_by": false,
"label": "LCSC_PART_NUMBER",
"name": "LCSC_PART_NUMBER",
"show": false
"show": true
},
{
"group_by": false,

View File

@@ -13415,7 +13415,7 @@
(exclude_from_sim no)
(in_bom yes)
(on_board yes)
(dnp yes)
(dnp no)
(fields_autoplaced yes)
(uuid "0e6bfc7c-a8ca-4acb-86b3-da7634fcf4cd")
(property "Reference" "CD1"
@@ -14637,7 +14637,7 @@
(exclude_from_sim no)
(in_bom yes)
(on_board yes)
(dnp yes)
(dnp no)
(uuid "259a9ea1-51b5-4996-afc6-3a5d2a7eae89")
(property "Reference" "SD7"
(at 380.238 167.894 0)
@@ -15496,7 +15496,7 @@
(exclude_from_sim no)
(in_bom yes)
(on_board yes)
(dnp yes)
(dnp no)
(uuid "38ddd4b3-ab82-4cf2-9ba9-1bbde465d66e")
(property "Reference" "SD2"
(at 241.808 166.624 0)
@@ -15907,7 +15907,7 @@
(exclude_from_sim no)
(in_bom yes)
(on_board yes)
(dnp yes)
(dnp no)
(uuid "44ad3365-09c5-4acc-a334-941b9df2f3eb")
(property "Reference" "SD9"
(at 241.808 241.554 0)
@@ -16624,7 +16624,7 @@
(exclude_from_sim no)
(in_bom yes)
(on_board yes)
(dnp yes)
(dnp no)
(uuid "559263d0-487d-489c-9d5e-c1ccbadeede2")
(property "Reference" "SD5"
(at 446.278 167.894 0)
@@ -16935,7 +16935,7 @@
(exclude_from_sim no)
(in_bom yes)
(on_board yes)
(dnp yes)
(dnp no)
(fields_autoplaced yes)
(uuid "5b64d75d-2004-49a4-b8d2-de19648ebfe7")
(property "Reference" "SIGNAL1"
@@ -17250,7 +17250,7 @@
(exclude_from_sim no)
(in_bom yes)
(on_board yes)
(dnp yes)
(dnp no)
(uuid "5f9b896c-7554-4260-a44c-1ec6e751f7df")
(property "Reference" "SD14"
(at 380.238 242.824 0)
@@ -17397,7 +17397,7 @@
(exclude_from_sim no)
(in_bom yes)
(on_board yes)
(dnp yes)
(dnp no)
(uuid "63e9631d-ca47-4964-89e4-7c459387d5a9")
(property "Reference" "SD3"
(at 311.658 169.164 0)
@@ -18829,7 +18829,7 @@
(exclude_from_sim no)
(in_bom yes)
(on_board yes)
(dnp yes)
(dnp no)
(uuid "8081e0c8-926f-45f6-b7a5-d43c8b9f78c7")
(property "Reference" "SD12"
(at 311.658 242.824 0)
@@ -19014,7 +19014,7 @@
(exclude_from_sim no)
(in_bom yes)
(on_board yes)
(dnp yes)
(dnp no)
(uuid "8332b305-364d-4759-9189-c5914c392da8")
(property "Reference" "SD15"
(at 413.258 242.824 0)
@@ -19359,7 +19359,7 @@
(exclude_from_sim no)
(in_bom yes)
(on_board yes)
(dnp yes)
(dnp no)
(uuid "866d3e46-9c45-4fae-aa67-7dab00a7b166")
(property "Reference" "SD4"
(at 345.948 169.164 0)
@@ -21582,7 +21582,7 @@
(exclude_from_sim no)
(in_bom yes)
(on_board yes)
(dnp yes)
(dnp no)
(uuid "af84268b-3582-43cd-bb08-e521691e5e49")
(property "Reference" "SD6"
(at 413.258 167.894 0)
@@ -21981,7 +21981,7 @@
(exclude_from_sim no)
(in_bom yes)
(on_board yes)
(dnp yes)
(dnp no)
(uuid "bf083f22-5823-4c87-a76d-aef17403b41a")
(property "Reference" "SD10"
(at 277.368 242.824 0)
@@ -22057,7 +22057,7 @@
(exclude_from_sim no)
(in_bom yes)
(on_board yes)
(dnp yes)
(dnp no)
(uuid "bf9a07a3-eb45-49b1-917e-8007235fdd95")
(property "Reference" "SD1"
(at 277.368 167.894 0)
@@ -23105,7 +23105,7 @@
(exclude_from_sim no)
(in_bom yes)
(on_board yes)
(dnp yes)
(dnp no)
(uuid "ddf1113a-50c6-4b9f-a704-a95f99ded398")
(property "Reference" "SD13"
(at 446.278 242.824 0)
@@ -23773,7 +23773,7 @@
(exclude_from_sim no)
(in_bom yes)
(on_board yes)
(dnp yes)
(dnp no)
(uuid "edc15427-b163-4349-be98-b42b20a3f5b7")
(property "Reference" "SD8"
(at 480.568 167.894 0)
@@ -24088,7 +24088,7 @@
(exclude_from_sim no)
(in_bom yes)
(on_board yes)
(dnp yes)
(dnp no)
(uuid "f425808c-0508-41b4-a31f-7be79ef999cd")
(property "Reference" "SD11"
(at 345.948 242.824 0)
@@ -24522,7 +24522,7 @@
(exclude_from_sim no)
(in_bom yes)
(on_board yes)
(dnp yes)
(dnp no)
(uuid "fb1f82f5-b488-4bfc-8aff-38fede4a4c6e")
(property "Reference" "SD16"
(at 480.568 242.824 0)

View File

@@ -0,0 +1,208 @@
(footprint "Sensor"
(version 20241229)
(generator "pcbnew")
(generator_version "9.0")
(layer "F.Cu")
(property "Reference" "REF**"
(at 0 -0.5 0)
(unlocked yes)
(layer "F.SilkS")
(uuid "f71e9c26-7923-4e5c-828e-153927862740")
(effects
(font
(size 1 1)
(thickness 0.1)
)
)
)
(property "Value" "Sensor"
(at 0 1 0)
(unlocked yes)
(layer "F.Fab")
(uuid "d40c7203-0c06-49e1-8672-dbd216694fc8")
(effects
(font
(size 1 1)
(thickness 0.15)
)
)
)
(property "Datasheet" ""
(at 0 0 0)
(unlocked yes)
(layer "F.Fab")
(hide yes)
(uuid "6720cb18-0687-4d55-a6ad-3ccf0819eac2")
(effects
(font
(size 1 1)
(thickness 0.15)
)
)
)
(property "Description" ""
(at 0 0 0)
(unlocked yes)
(layer "F.Fab")
(hide yes)
(uuid "43905e6e-773d-4d5e-8d72-57c092c3495a")
(effects
(font
(size 1 1)
(thickness 0.15)
)
)
)
(attr smd)
(fp_line
(start -45 -18)
(end 41 -18)
(stroke
(width 0.1)
(type default)
)
(layer "F.SilkS")
(uuid "e63ec799-95c4-407e-acc9-9c7e6d3e330c")
)
(fp_line
(start -45 24)
(end -45 -18)
(stroke
(width 0.1)
(type default)
)
(layer "F.SilkS")
(uuid "a66c286b-432b-493d-9619-2dd4fbfdb21c")
)
(fp_line
(start -44 24)
(end -45 24)
(stroke
(width 0.1)
(type default)
)
(layer "F.SilkS")
(uuid "71526e68-71d4-4b13-ab74-482657a06849")
)
(fp_line
(start 41 -18)
(end 41 24)
(stroke
(width 0.1)
(type default)
)
(layer "F.SilkS")
(uuid "55554342-bbff-4d1b-b931-67fb7eaa5895")
)
(fp_line
(start 41 24)
(end -44 24)
(stroke
(width 0.1)
(type default)
)
(layer "F.SilkS")
(uuid "2dc0ee59-36f6-407e-821a-c6acfe3ea887")
)
(fp_text user "${REFERENCE}"
(at 0 2.5 0)
(unlocked yes)
(layer "F.Fab")
(uuid "befc0725-b201-4f81-b0dc-b327becad9ba")
(effects
(font
(size 1 1)
(thickness 0.15)
)
)
)
(pad "1" thru_hole rect
(at -42.4 -15.3)
(size 1.7 1.7)
(drill 1)
(layers "*.Cu" "*.Mask")
(remove_unused_layers no)
(uuid "5538ac99-6e48-4111-a3df-8ff33be72ff3")
)
(pad "2" thru_hole circle
(at -42.4 -12.76)
(size 1.7 1.7)
(drill 1)
(layers "*.Cu" "*.Mask")
(remove_unused_layers no)
(uuid "0c1d4a6c-7871-46ae-a262-a8223571975e")
)
(pad "3" thru_hole circle
(at -42.4 -10.22)
(size 1.7 1.7)
(drill 1)
(layers "*.Cu" "*.Mask")
(remove_unused_layers no)
(uuid "70df4148-0415-45a2-8365-0977fcda45a1")
)
(pad "4" thru_hole circle
(at -42.4 -7.68)
(size 1.7 1.7)
(drill 1)
(layers "*.Cu" "*.Mask")
(remove_unused_layers no)
(uuid "179dd332-2ab2-4917-91ec-35f232b45ce3")
)
(pad "5" thru_hole circle
(at -42.4 -5.14)
(size 1.7 1.7)
(drill 1)
(layers "*.Cu" "*.Mask")
(remove_unused_layers no)
(uuid "17bc8f3a-4727-4e31-90f4-8252dff5ad1c")
)
(pad "6" thru_hole circle
(at -42.4 -2.6)
(size 1.7 1.7)
(drill 1)
(layers "*.Cu" "*.Mask")
(remove_unused_layers no)
(uuid "eee06414-e977-45a8-a5e0-618644284d45")
)
(pad "7" thru_hole rect
(at -0.5 -12)
(size 1.7 1.7)
(drill 1)
(layers "*.Cu" "*.Mask")
(remove_unused_layers no)
(uuid "536f0038-06c5-45e5-b1c8-898a364f6ec4")
)
(pad "8" thru_hole rect
(at 39.5 -16)
(size 1.7 1.7)
(drill 1)
(layers "*.Cu" "*.Mask")
(remove_unused_layers no)
(uuid "3e0cdbaa-8219-48c4-bb0a-fd4f0e78a390")
)
(pad "9" thru_hole rect
(at 39.5 22.5)
(size 1.7 1.7)
(drill 1)
(layers "*.Cu" "*.Mask")
(remove_unused_layers no)
(uuid "f01565c2-eadd-4451-9dea-2ebe28d872f0")
)
(pad "10" thru_hole rect
(at -0.5 15)
(size 1.7 1.7)
(drill 1)
(layers "*.Cu" "*.Mask")
(remove_unused_layers no)
(uuid "56ec7c50-069f-4f46-9b83-ac18e4928930")
)
(pad "11" thru_hole rect
(at -43 22.5)
(size 1.7 1.7)
(drill 1)
(layers "*.Cu" "*.Mask")
(remove_unused_layers no)
(uuid "f423be21-13b8-46de-8e19-e48325411a29")
)
(embedded_fonts no)
)

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,136 @@
{
"board": {
"active_layer": 0,
"active_layer_preset": "All Layers",
"auto_track_width": false,
"hidden_netclasses": [],
"hidden_nets": [],
"high_contrast_mode": 0,
"net_color_mode": 1,
"opacity": {
"images": 0.6,
"pads": 1.0,
"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",
"locked_item_shadows",
"conflict_shadows",
"shapes"
],
"visible_layers": "ffffffff_ffffffff_ffffffff_ffffffff",
"zone_display_mode": 1
},
"git": {
"repo_type": "",
"repo_username": "",
"ssh_key": ""
},
"meta": {
"filename": "Sensors.kicad_prl",
"version": 5
},
"net_inspector_panel": {
"col_hidden": [
false,
false,
false,
false,
false,
false,
false,
false,
false,
false,
false,
false
],
"col_order": [
0,
1,
2,
3,
4,
5,
6,
7,
8,
9,
10,
11
],
"col_widths": [
0,
0,
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

@@ -0,0 +1,674 @@
{
"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": [
{
"gap": 0.0,
"via_gap": 0.0,
"width": 0.0
}
],
"drc_exclusions": [],
"meta": {
"version": 2
},
"rule_severities": {
"annular_width": "error",
"clearance": "error",
"connection_width": "warning",
"copper_edge_clearance": "error",
"copper_sliver": "warning",
"courtyards_overlap": "error",
"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": "ignore",
"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": "warning",
"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.15,
"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.25,
"min_track_width": 0.0,
"min_via_annular_width": 0.05,
"min_via_diameter": 0.25,
"solder_mask_to_copper_clearance": 0.005,
"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": [
0.0,
0.1,
0.2,
0.5
],
"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": [
{
"diameter": 0.0,
"drill": 0.0
},
{
"diameter": 0.3,
"drill": 0.25
}
],
"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": "ignore",
"power_pin_not_driven": "ignore",
"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",
"unit_value_mismatch": "error",
"unresolved_variable": "error",
"wire_dangling": "error"
}
},
"libraries": {
"pinned_footprint_libs": [],
"pinned_symbol_libs": []
},
"meta": {
"filename": "Sensors.kicad_pro",
"version": 3
},
"net_settings": {
"classes": [
{
"bus_width": 12,
"clearance": 0.15,
"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": "Sensors.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": true,
"label": "Value",
"name": "Value",
"show": true
},
{
"group_by": true,
"label": "Footprint",
"name": "Footprint",
"show": true
},
{
"group_by": false,
"label": "Datasheet",
"name": "Datasheet",
"show": true
},
{
"group_by": false,
"label": "Description",
"name": "Description",
"show": false
},
{
"group_by": false,
"label": "Qty",
"name": "${QUANTITY}",
"show": true
},
{
"group_by": false,
"label": "#",
"name": "${ITEM_NUMBER}",
"show": false
},
{
"group_by": false,
"label": "LCSC_PART_NUMBER",
"name": "LCSC_PART_NUMBER",
"show": true
},
{
"group_by": false,
"label": "Sim.Device",
"name": "Sim.Device",
"show": false
},
{
"group_by": false,
"label": "Sim.Pins",
"name": "Sim.Pins",
"show": false
},
{
"group_by": false,
"label": "Sim.Type",
"name": "Sim.Type",
"show": false
},
{
"group_by": 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
}
],
"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": [
[
"46346c04-8bed-48b4-837b-9342dd403232",
"Root"
]
],
"text_variables": {}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

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

Binary file not shown.

File diff suppressed because it is too large Load Diff

Binary file not shown.

View File

@@ -5,22 +5,22 @@ target = "riscv32imac-esp-espidf"
[target.riscv32imac-esp-espidf]
linker = "ldproxy"
#runner = "espflash flash --monitor --baud 921600 --partition-table partitions.csv -b no-reset" # Select this runner in case of usb ttl
#runner = "espflash flash --monitor --baud 921600 --flash-size 16mb --partition-table partitions.csv"
runner = "cargo runner"
runner = "espflash flash --monitor"
#runner = "cargo runner"
#runner = "espflash flash --monitor --partition-table partitions.csv -b no-reset" # create upgrade image file for webupload
# runner = espflash erase-parts otadata //ensure flash is clean
rustflags = [ "--cfg", "espidf_time64"] # Extending time_t for ESP IDF 5: https://github.com/esp-rs/rust/issues/110
rustflags = ["--cfg", "espidf_time64"] # Extending time_t for ESP IDF 5: https://github.com/esp-rs/rust/issues/110
[unstable]
build-std = ["std", "panic_abort"]
[env]
MCU="esp32c6"
MCU = "esp32c6"
# Note: this variable is not used by the pio builder (`cargo build --features pio`)
ESP_IDF_VERSION = "v5.2.1"
CHRONO_TZ_TIMEZONE_FILTER="UTC|Europe/Berlin"
CHRONO_TZ_TIMEZONE_FILTER = "UTC|America/New_York|America/Chicago|America/Los_Angeles|Europe/London|Europe/Berlin|Europe/Paris|Asia/Tokyo|Asia/Shanghai|Asia/Kolkata|Australia/Sydney|America/Sao_Paulo|Africa/Johannesburg|Asia/Dubai|Pacific/Auckland"
CARGO_WORKSPACE_DIR = { value = "", relative = true }
RUST_BACKTRACE = "full"
RUST_BACKTRACE = "full"

14
rust/.idea/dictionaries/project.xml generated Normal file
View File

@@ -0,0 +1,14 @@
<component name="ProjectDictionaryState">
<dictionary name="project">
<words>
<w>buildtime</w>
<w>deepsleep</w>
<w>githash</w>
<w>lightstate</w>
<w>mppt</w>
<w>plantstate</w>
<w>sntp</w>
<w>vergen</w>
</words>
</dictionary>
</component>

View File

@@ -1,6 +1,11 @@
<component name="InspectionProjectProfileManager">
<profile version="1.0">
<option name="myName" value="Project Default" />
<inspection_tool class="DuplicatedCode" enabled="true" level="WEAK WARNING" enabled_by_default="true">
<Languages>
<language minSize="102" name="Rust" />
</Languages>
</inspection_tool>
<inspection_tool class="Eslint" enabled="true" level="WARNING" enabled_by_default="true" />
</profile>
</component>

View File

@@ -10,15 +10,12 @@ resolver = "2"
# Explicitly disable LTO which the Xtensa codegen backend has issues
lto = false
strip = false
debug = true
debug = true
overflow-checks = true
panic = "abort"
incremental = true
opt-level = "s"
opt-level = 2
[profile.dev.build-override]
opt-level = 1
incremental = true
[package.metadata.cargo_runner]
# The string `$TARGET_FILE` will be replaced with the path from cargo.
@@ -38,13 +35,13 @@ command = [
partition_table = "partitions.csv"
[features]
default = ["std", "embassy", "esp-idf-svc/native"]
default = ["std", "esp-idf-svc/native"]
pio = ["esp-idf-svc/pio"]
std = ["alloc", "esp-idf-svc/binstart", "esp-idf-svc/std"]
alloc = ["esp-idf-svc/alloc"]
nightly = ["esp-idf-svc/nightly"]
experimental = ["esp-idf-svc/experimental"]
embassy = ["esp-idf-svc/embassy-sync", "esp-idf-svc/critical-section", "esp-idf-svc/embassy-time-driver"]
#embassy = ["esp-idf-svc/embassy-sync", "esp-idf-svc/critical-section", "esp-idf-svc/embassy-time-driver"]
[dependencies]
#ESP stuff
@@ -54,7 +51,7 @@ esp-idf-sys = { version = "0.36.1", features = ["binstart", "native"] }
esp-idf-svc = { version = "0.51.0", default-features = false }
embedded-hal = "1.0.0"
heapless = { version = "0.8", features = ["serde"] }
embedded-hal-bus = { version = "0.2.0", features = ["std"] }
embedded-hal-bus = { version = "0.3.0", features = ["std"] }
#Hardware additional driver
ds18b20 = "0.1.1"
@@ -69,21 +66,27 @@ strum = { version = "0.27.0", features = ["derive"] }
measurements = "0.11.0"
#json
serde = { version = "1.0.192", features = ["derive"] }
serde = { version = "1.0.192", features = ["derive"] }
serde_json = "1.0.108"
#timezone
chrono = { version = "0.4.23", default-features = false , features = ["iana-time-zone" , "alloc"] }
chrono-tz = {version="0.8.0", default-features = false , features = [ "filter-by-regex" ]}
chrono = { version = "0.4.23", default-features = false, features = ["iana-time-zone", "alloc", "serde"] }
chrono-tz = { version = "0.10.3", default-features = false, features = ["filter-by-regex"] }
eeprom24x = "0.7.2"
url = "2.5.3"
crc = "3.2.1"
bincode = "1.3.3"
bincode = "2.0.1"
ringbuffer = "0.15.0"
text-template = "0.1.0"
strum_macros = "0.27.0"
esp-ota = { version = "0.2.2", features = ["log"] }
unit-enum = "1.4.1"
pca9535 = { version = "2.0.0", features = ["std"] }
ina219 = { version = "0.2.0", features = ["std"] }
embedded-storage = "=0.3.1"
ekv = "1.0.0"
embedded-can = "0.4.1"
[patch.crates-io]
@@ -96,5 +99,5 @@ unit-enum = "1.4.1"
[build-dependencies]
cc = "=1.1.30"
embuild = { version= "0.32.0", features = ["espidf"]}
embuild = { version = "0.32.0", features = ["espidf"] }
vergen = { version = "8.2.6", features = ["build", "git", "gitcl"] }

View File

@@ -22,6 +22,22 @@ fn main() {
println!("stdout: {}", String::from_utf8_lossy(&output.stdout));
println!("stderr: {}", String::from_utf8_lossy(&output.stderr));
assert!(output.status.success());
// move webpack results to rust webserver src
let _ = Command::new("cmd")
.arg("/K")
.arg("move")
.arg("./src_webpack/bundle.js")
.arg("./src/webserver")
.output()
.unwrap();
let _ = Command::new("cmd")
.arg("/K")
.arg("move")
.arg("./src_webpack/index.html")
.arg("./src/webserver")
.output()
.unwrap();
}
Err(_) => {
println!("Assuming build on linux");
@@ -34,6 +50,18 @@ fn main() {
println!("stdout: {}", String::from_utf8_lossy(&output.stdout));
println!("stderr: {}", String::from_utf8_lossy(&output.stderr));
assert!(output.status.success());
// move webpack results to rust webserver src
let _ = Command::new("mv")
.arg("./src_webpack/bundle.js")
.arg("./src/webserver")
.output()
.unwrap();
let _ = Command::new("mv")
.arg("./src_webpack/index.html")
.arg("./src/webserver")
.output()
.unwrap();
}
}

5
rust/espflash.toml Normal file
View File

@@ -0,0 +1,5 @@
partition_table = "partitions.csv"
[connection]
serial = "/dev/ttyACM0"
[flash]
size = "16MB"

View File

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

View File

@@ -1,9 +1,7 @@
use std::str::FromStr;
use chrono_tz::{Europe::Berlin, Tz};
use crate::hal::PLANT_COUNT;
use crate::plant_state::PlantWateringMode;
use serde::{Deserialize, Serialize};
use crate::PLANT_COUNT;
use std::str::FromStr;
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
#[serde(default)]
@@ -13,7 +11,7 @@ pub struct NetworkConfig {
pub password: Option<heapless::String<64>>,
pub mqtt_url: Option<heapless::String<128>>,
pub base_topic: Option<heapless::String<64>>,
pub timezone: heapless::String<64>,
pub max_wait: u32,
}
impl Default for NetworkConfig {
fn default() -> Self {
@@ -23,7 +21,7 @@ impl Default for NetworkConfig {
password: None,
mqtt_url: None,
base_topic: None,
timezone: heapless::String::from_str("Europe/Berlin").unwrap(),
max_wait: 10000,
}
}
}
@@ -60,6 +58,7 @@ pub struct TankConfig {
pub tank_warn_percent: u8,
pub tank_empty_percent: u8,
pub tank_full_percent: u8,
pub ml_per_pulse: f32
}
impl Default for TankConfig {
fn default() -> Self {
@@ -70,50 +69,81 @@ impl Default for TankConfig {
tank_warn_percent: 40,
tank_empty_percent: 5,
tank_full_percent: 95,
ml_per_pulse: 0.0,
}
}
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Default)]
pub enum BatteryBoardVersion {
#[default]
Disabled,
BQ34Z100G1,
WchI2cSlave,
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Default)]
pub enum BoardVersion {
#[default]
INITIAL,
V3,
V4,
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Default)]
pub struct BoardHardware {
pub board: BoardVersion,
pub battery: BatteryBoardVersion,
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Default)]
#[serde(default)]
pub struct PlantControllerConfig {
pub hardware: BoardHardware,
pub network: NetworkConfig,
pub tank: TankConfig,
pub night_lamp: NightLampConfig,
pub plants: [PlantConfig; PLANT_COUNT],
pub timezone: Option<String>,
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
#[serde(default)]
pub struct PlantConfig {
pub mode: Mode,
pub target_moisture: u8,
pub mode: PlantWateringMode,
pub target_moisture: f32,
pub pump_time_s: u16,
pub pump_limit_ml: u16,
pub pump_cooldown_min: u16,
pub pump_hour_start: u8,
pub pump_hour_end: u8,
pub sensor_a: bool,
pub sensor_b: bool,
pub max_consecutive_pump_count: u8,
pub moisture_sensor_min_frequency: Option<f32>, // Optional min frequency
pub moisture_sensor_max_frequency: Option<f32>, // Optional max frequency
pub min_pump_current_ma: u16,
pub max_pump_current_ma: u16,
pub ignore_current_error: bool,
}
impl Default for PlantConfig {
fn default() -> Self {
Self {
mode: Mode::OFF,
target_moisture: 40,
mode: PlantWateringMode::OFF,
target_moisture: 40.,
pump_time_s: 30,
pump_limit_ml: 5000,
pump_cooldown_min: 60,
pump_hour_start: 9,
pump_hour_end: 20,
sensor_a: true,
sensor_b: false,
max_consecutive_pump_count: 10,
moisture_sensor_min_frequency: None, // No override by default
moisture_sensor_max_frequency: None, // No override by default
min_pump_current_ma: 10,
max_pump_current_ma: 3000,
ignore_current_error: true,
}
}
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
pub enum Mode {
OFF,
TargetMoisture,
TimerOnly,
TimerAndDeadzone,
}

214
rust/src/hal/battery.rs Normal file
View File

@@ -0,0 +1,214 @@
use anyhow::anyhow;
use bq34z100::{Bq34Z100Error, Bq34z100g1, Bq34z100g1Driver};
use embedded_hal_bus::i2c::MutexDevice;
use esp_idf_hal::delay::Delay;
use esp_idf_hal::i2c::{I2cDriver, I2cError};
use measurements::Temperature;
use serde::Serialize;
pub trait BatteryInteraction {
fn state_charge_percent(&mut self) -> Result<f32, BatteryError>;
fn remaining_milli_ampere_hour(&mut self) -> Result<u16, BatteryError>;
fn max_milli_ampere_hour(&mut self) -> Result<u16, BatteryError>;
fn design_milli_ampere_hour(&mut self) -> Result<u16, BatteryError>;
fn voltage_milli_volt(&mut self) -> Result<u16, BatteryError>;
fn average_current_milli_ampere(&mut self) -> Result<i16, BatteryError>;
fn cycle_count(&mut self) -> Result<u16, BatteryError>;
fn state_health_percent(&mut self) -> Result<u16, BatteryError>;
fn bat_temperature(&mut self) -> Result<u16, BatteryError>;
fn get_battery_state(&mut self) -> Result<BatteryState, BatteryError>;
}
#[derive(Debug, Serialize)]
pub struct BatteryInfo {
pub voltage_milli_volt: u16,
pub average_current_milli_ampere: i16,
pub cycle_count: u16,
pub design_milli_ampere_hour: u16,
pub remaining_milli_ampere_hour: u16,
pub state_of_charge: f32,
pub state_of_health: u16,
pub temperature: u16,
}
#[derive(Debug, Serialize)]
pub enum BatteryError {
NoBatteryMonitor,
CommunicationError(String),
}
impl From<Bq34Z100Error<esp_idf_hal::i2c::I2cError>> for BatteryError {
fn from(err: Bq34Z100Error<esp_idf_hal::i2c::I2cError>) -> Self {
BatteryError::CommunicationError(
anyhow!("failed to communicate with battery monitor: {:?}", err).to_string(),
)
}
}
#[derive(Debug, Serialize)]
pub enum BatteryState {
Unknown,
Info(BatteryInfo),
}
/// If no battery monitor is installed this implementation will be used
pub struct NoBatteryMonitor {}
impl BatteryInteraction for NoBatteryMonitor {
fn state_charge_percent(&mut self) -> Result<f32, BatteryError> {
Err(BatteryError::NoBatteryMonitor)
}
fn remaining_milli_ampere_hour(&mut self) -> Result<u16, BatteryError> {
Err(BatteryError::NoBatteryMonitor)
}
fn max_milli_ampere_hour(&mut self) -> Result<u16, BatteryError> {
Err(BatteryError::NoBatteryMonitor)
}
fn design_milli_ampere_hour(&mut self) -> Result<u16, BatteryError> {
Err(BatteryError::NoBatteryMonitor)
}
fn voltage_milli_volt(&mut self) -> Result<u16, BatteryError> {
Err(BatteryError::NoBatteryMonitor)
}
fn average_current_milli_ampere(&mut self) -> Result<i16, BatteryError> {
Err(BatteryError::NoBatteryMonitor)
}
fn cycle_count(&mut self) -> Result<u16, BatteryError> {
Err(BatteryError::NoBatteryMonitor)
}
fn state_health_percent(&mut self) -> Result<u16, BatteryError> {
Err(BatteryError::NoBatteryMonitor)
}
fn bat_temperature(&mut self) -> Result<u16, BatteryError> {
Err(BatteryError::NoBatteryMonitor)
}
fn get_battery_state(&mut self) -> Result<BatteryState, BatteryError> {
Ok(BatteryState::Unknown)
}
}
//TODO implement this battery monitor kind once controller is complete
#[allow(dead_code)]
pub struct WchI2cSlave {}
pub struct BQ34Z100G1<'a> {
pub battery_driver: Bq34z100g1Driver<MutexDevice<'a, I2cDriver<'a>>, Delay>,
}
impl BatteryInteraction for BQ34Z100G1<'_> {
fn state_charge_percent(&mut self) -> Result<f32, BatteryError> {
Ok(self.battery_driver.state_of_charge().map(f32::from)?)
}
fn remaining_milli_ampere_hour(&mut self) -> Result<u16, BatteryError> {
Ok(self.battery_driver.remaining_capacity()?)
}
fn max_milli_ampere_hour(&mut self) -> Result<u16, BatteryError> {
Ok(self.battery_driver.full_charge_capacity()?)
}
fn design_milli_ampere_hour(&mut self) -> Result<u16, BatteryError> {
Ok(self.battery_driver.design_capacity()?)
}
fn voltage_milli_volt(&mut self) -> Result<u16, BatteryError> {
Ok(self.battery_driver.voltage()?)
}
fn average_current_milli_ampere(&mut self) -> Result<i16, BatteryError> {
Ok(self.battery_driver.average_current()?)
}
fn cycle_count(&mut self) -> Result<u16, BatteryError> {
Ok(self.battery_driver.cycle_count()?)
}
fn state_health_percent(&mut self) -> Result<u16, BatteryError> {
Ok(self.battery_driver.state_of_health()?)
}
fn bat_temperature(&mut self) -> Result<u16, BatteryError> {
Ok(self.battery_driver.temperature()?)
}
fn get_battery_state(&mut self) -> Result<BatteryState, BatteryError> {
Ok(BatteryState::Info(BatteryInfo {
voltage_milli_volt: self.voltage_milli_volt()?,
average_current_milli_ampere: self.average_current_milli_ampere()?,
cycle_count: self.cycle_count()?,
design_milli_ampere_hour: self.design_milli_ampere_hour()?,
remaining_milli_ampere_hour: self.remaining_milli_ampere_hour()?,
state_of_charge: self.state_charge_percent()?,
state_of_health: self.state_health_percent()?,
temperature: self.bat_temperature()?,
}))
}
}
pub fn print_battery_bq34z100(
battery_driver: &mut Bq34z100g1Driver<MutexDevice<I2cDriver<'_>>, Delay>,
) -> anyhow::Result<(), Bq34Z100Error<I2cError>> {
println!("Try communicating with battery");
let fwversion = battery_driver.fw_version().unwrap_or_else(|e| {
println!("Firmware {:?}", e);
0
});
println!("fw version is {}", fwversion);
let design_capacity = battery_driver.design_capacity().unwrap_or_else(|e| {
println!("Design capacity {:?}", e);
0
});
println!("Design Capacity {}", design_capacity);
if design_capacity == 1000 {
println!("Still stock configuring battery, readouts are likely to be wrong!");
}
let flags = battery_driver.get_flags_decoded()?;
println!("Flags {:?}", flags);
let chem_id = battery_driver.chem_id().unwrap_or_else(|e| {
println!("Chemid {:?}", e);
0
});
let bat_temp = battery_driver.internal_temperature().unwrap_or_else(|e| {
println!("Bat Temp {:?}", e);
0
});
let temp_c = Temperature::from_kelvin(bat_temp as f64 / 10_f64).as_celsius();
let voltage = battery_driver.voltage().unwrap_or_else(|e| {
println!("Bat volt {:?}", e);
0
});
let current = battery_driver.current().unwrap_or_else(|e| {
println!("Bat current {:?}", e);
0
});
let state = battery_driver.state_of_charge().unwrap_or_else(|e| {
println!("Bat Soc {:?}", e);
0
});
let charge_voltage = battery_driver.charge_voltage().unwrap_or_else(|e| {
println!("Bat Charge Volt {:?}", e);
0
});
let charge_current = battery_driver.charge_current().unwrap_or_else(|e| {
println!("Bat Charge Current {:?}", e);
0
});
println!("ChemId: {} Current voltage {} and current {} with charge {}% and temp {} CVolt: {} CCur {}", chem_id, voltage, current, state, temp_c, charge_voltage, charge_current);
let _ = battery_driver.unsealed();
let _ = battery_driver.it_enable();
anyhow::Result::Ok(())
}

600
rust/src/hal/esp.rs Normal file
View File

@@ -0,0 +1,600 @@
use crate::config::{NetworkConfig, PlantControllerConfig};
use crate::hal::PLANT_COUNT;
use crate::log::{log, LogMessage};
use crate::STAY_ALIVE;
use anyhow::{anyhow, bail, Context};
use chrono::{DateTime, Utc};
use embedded_svc::ipv4::IpInfo;
use embedded_svc::mqtt::client::QoS::{AtLeastOnce, ExactlyOnce};
use embedded_svc::wifi::{
AccessPointConfiguration, AccessPointInfo, AuthMethod, ClientConfiguration, Configuration,
};
use esp_idf_hal::delay::Delay;
use esp_idf_hal::gpio::{Level, PinDriver};
use esp_idf_svc::mqtt::client::{EspMqttClient, LwtConfiguration, MqttClientConfiguration};
use esp_idf_svc::sntp;
use esp_idf_svc::sntp::SyncStatus;
use esp_idf_svc::systime::EspSystemTime;
use esp_idf_svc::wifi::config::{ScanConfig, ScanType};
use esp_idf_svc::wifi::EspWifi;
use esp_idf_sys::{esp_spiffs_info, vTaskDelay};
use serde::Serialize;
use std::ffi::CString;
use std::fs;
use std::fs::File;
use std::path::Path;
use std::result::Result::Ok as OkStd;
use std::str::FromStr;
use std::sync::atomic::AtomicBool;
use std::sync::Arc;
use std::time::Duration;
#[link_section = ".rtc.data"]
static mut LAST_WATERING_TIMESTAMP: [i64; PLANT_COUNT] = [0; PLANT_COUNT];
#[link_section = ".rtc.data"]
static mut CONSECUTIVE_WATERING_PLANT: [u32; PLANT_COUNT] = [0; PLANT_COUNT];
#[link_section = ".rtc.data"]
static mut LOW_VOLTAGE_DETECTED: bool = false;
#[link_section = ".rtc.data"]
static mut RESTART_TO_CONF: bool = false;
#[derive(Serialize, Debug)]
pub struct FileInfo {
filename: String,
size: usize,
}
#[derive(Serialize, Debug)]
pub struct FileList {
total: usize,
used: usize,
files: Vec<FileInfo>,
file_system_corrupt: Option<String>,
iter_error: Option<String>,
}
pub struct FileSystemSizeInfo {
pub total_size: usize,
pub used_size: usize,
pub free_size: usize,
}
pub struct MqttClient<'a> {
mqtt_client: EspMqttClient<'a>,
base_topic: heapless::String<64>,
}
pub struct Esp<'a> {
pub(crate) mqtt_client: Option<MqttClient<'a>>,
pub(crate) wifi_driver: EspWifi<'a>,
pub(crate) boot_button: PinDriver<'a, esp_idf_hal::gpio::AnyIOPin, esp_idf_hal::gpio::Input>,
pub(crate) delay: Delay,
}
impl Esp<'_> {
const SPIFFS_PARTITION_NAME: &'static str = "storage";
const CONFIG_FILE: &'static str = "/spiffs/config.cfg";
const BASE_PATH: &'static str = "/spiffs";
pub(crate) fn mode_override_pressed(&mut self) -> bool {
self.boot_button.get_level() == Level::Low
}
pub(crate) fn sntp(&mut self, max_wait_ms: u32) -> anyhow::Result<DateTime<Utc>> {
let sntp = sntp::EspSntp::new_default()?;
let mut counter = 0;
while sntp.get_sync_status() != SyncStatus::Completed {
self.delay.delay_ms(100);
counter += 100;
if counter > max_wait_ms {
bail!("Reached sntp timeout, aborting")
}
}
self.time()
}
pub(crate) fn time(&mut self) -> anyhow::Result<DateTime<Utc>> {
let time = EspSystemTime {}.now().as_millis();
let smaller_time = time as i64;
let local_time = DateTime::from_timestamp_millis(smaller_time)
.ok_or(anyhow!("could not convert timestamp"))?;
anyhow::Ok(local_time)
}
pub(crate) fn wifi_scan(&mut self) -> anyhow::Result<Vec<AccessPointInfo>> {
self.wifi_driver.start_scan(
&ScanConfig {
scan_type: ScanType::Passive(Duration::from_secs(5)),
show_hidden: false,
..Default::default()
},
true,
)?;
anyhow::Ok(self.wifi_driver.get_scan_result()?)
}
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 = true;
}
}
pub(crate) fn clear_low_voltage_in_cycle(&mut self) {
unsafe {
LOW_VOLTAGE_DETECTED = false;
}
}
pub(crate) fn low_voltage_in_cycle(&mut self) -> bool {
unsafe { LOW_VOLTAGE_DETECTED }
}
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 }
}
pub(crate) fn set_restart_to_conf(&mut self, to_conf: bool) {
unsafe {
RESTART_TO_CONF = to_conf;
}
}
pub(crate) fn wifi_ap(&mut self) -> anyhow::Result<()> {
let ssid = match self.load_config() {
Ok(config) => config.network.ap_ssid.clone(),
Err(_) => heapless::String::from_str("PlantCtrl Emergency Mode").unwrap(),
};
let apconfig = AccessPointConfiguration {
ssid,
auth_method: AuthMethod::None,
ssid_hidden: false,
..Default::default()
};
self.wifi_driver
.set_configuration(&Configuration::AccessPoint(apconfig))?;
self.wifi_driver.start()?;
anyhow::Ok(())
}
pub(crate) fn wifi(&mut self, network_config: &NetworkConfig) -> anyhow::Result<IpInfo> {
let ssid = network_config
.ssid
.clone()
.ok_or(anyhow!("No ssid configured"))?;
let password = network_config.password.clone();
let max_wait = network_config.max_wait;
match password {
Some(pw) => {
//TODO expect error due to invalid pw or similar! //call this during configuration and check if works, revert to config mode if not
self.wifi_driver.set_configuration(&Configuration::Client(
ClientConfiguration {
ssid,
password: pw,
..Default::default()
},
))?;
}
None => {
self.wifi_driver.set_configuration(&Configuration::Client(
ClientConfiguration {
ssid,
auth_method: AuthMethod::None,
..Default::default()
},
))?;
}
}
self.wifi_driver.start()?;
self.wifi_driver.connect()?;
let delay = Delay::new_default();
let mut counter = 0_u32;
while !self.wifi_driver.is_connected()? {
delay.delay_ms(250);
counter += 250;
if counter > max_wait {
//ignore these errors, Wi-Fi will not be used this
self.wifi_driver.disconnect().unwrap_or(());
self.wifi_driver.stop().unwrap_or(());
bail!("Did not manage wifi connection within timeout");
}
}
println!("Should be connected now, waiting for link to be up");
while !self.wifi_driver.is_up()? {
delay.delay_ms(250);
counter += 250;
if counter > max_wait {
//ignore these errors, Wi-Fi will not be used this
self.wifi_driver.disconnect().unwrap_or(());
self.wifi_driver.stop().unwrap_or(());
bail!("Did not manage wifi connection within timeout");
}
}
//update freertos registers ;)
let address = self.wifi_driver.sta_netif().get_ip_info()?;
log(LogMessage::WifiInfo, 0, 0, "", &format!("{address:?}"));
anyhow::Ok(address)
}
pub(crate) fn load_config(&mut self) -> anyhow::Result<PlantControllerConfig> {
let cfg = File::open(Self::CONFIG_FILE)?;
let config: PlantControllerConfig = serde_json::from_reader(cfg)?;
anyhow::Ok(config)
}
pub(crate) fn save_config(&mut self, config: &PlantControllerConfig) -> anyhow::Result<()> {
let mut cfg = File::create(Self::CONFIG_FILE)?;
serde_json::to_writer(&mut cfg, &config)?;
println!("Wrote config config {:?}", config);
anyhow::Ok(())
}
pub(crate) fn mount_file_system(&mut self) -> anyhow::Result<()> {
log(LogMessage::MountingFilesystem, 0, 0, "", "");
let base_path = CString::new("/spiffs")?;
let storage = CString::new(Self::SPIFFS_PARTITION_NAME)?;
let conf = esp_idf_sys::esp_vfs_spiffs_conf_t {
base_path: base_path.as_ptr(),
partition_label: storage.as_ptr(),
max_files: 5,
format_if_mount_failed: true,
};
unsafe {
esp_idf_sys::esp!(esp_idf_sys::esp_vfs_spiffs_register(&conf))?;
}
let free_space = self.file_system_size()?;
log(
LogMessage::FilesystemMount,
free_space.free_size as u32,
free_space.total_size as u32,
&free_space.used_size.to_string(),
"",
);
anyhow::Ok(())
}
fn file_system_size(&mut self) -> anyhow::Result<FileSystemSizeInfo> {
let storage = CString::new(Self::SPIFFS_PARTITION_NAME)?;
let mut total_size = 0;
let mut used_size = 0;
unsafe {
esp_idf_sys::esp!(esp_spiffs_info(
storage.as_ptr(),
&mut total_size,
&mut used_size
))?;
}
anyhow::Ok(FileSystemSizeInfo {
total_size,
used_size,
free_size: total_size - used_size,
})
}
pub(crate) fn list_files(&self) -> FileList {
let storage = CString::new(Self::SPIFFS_PARTITION_NAME).unwrap();
let mut file_system_corrupt = None;
let mut iter_error = None;
let mut result = Vec::new();
let filepath = Path::new(Self::BASE_PATH);
let read_dir = fs::read_dir(filepath);
match read_dir {
OkStd(read_dir) => {
for item in read_dir {
match item {
OkStd(file) => {
let f = FileInfo {
filename: file.file_name().into_string().unwrap(),
size: file.metadata().map(|it| it.len()).unwrap_or_default()
as usize,
};
result.push(f);
}
Err(err) => {
iter_error = Some(format!("{err:?}"));
break;
}
}
}
}
Err(err) => {
file_system_corrupt = Some(format!("{err:?}"));
}
}
let mut total: usize = 0;
let mut used: usize = 0;
unsafe {
esp_spiffs_info(storage.as_ptr(), &mut total, &mut used);
}
FileList {
total,
used,
file_system_corrupt,
files: result,
iter_error,
}
}
pub(crate) fn delete_file(&self, filename: &str) -> anyhow::Result<()> {
let filepath = Path::new(Self::BASE_PATH).join(Path::new(filename));
match fs::remove_file(filepath) {
OkStd(_) => anyhow::Ok(()),
Err(err) => {
bail!(format!("{err:?}"))
}
}
}
pub(crate) fn get_file_handle(&self, filename: &str, write: bool) -> anyhow::Result<File> {
let filepath = Path::new(Self::BASE_PATH).join(Path::new(filename));
anyhow::Ok(if write {
File::create(filepath)?
} else {
File::open(filepath)?
})
}
pub(crate) 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 = false;
crate::log::init();
RESTART_TO_CONF = to_config_mode;
};
} else {
unsafe {
if to_config_mode {
RESTART_TO_CONF = true;
}
log(
LogMessage::RestartToConfig,
RESTART_TO_CONF as u32,
0,
"",
"",
);
log(
LogMessage::LowVoltage,
LOW_VOLTAGE_DETECTED as u32,
0,
"",
"",
);
for i in 0..PLANT_COUNT {
println!(
"LAST_WATERING_TIMESTAMP[{}] = UTC {}",
i, LAST_WATERING_TIMESTAMP[i]
);
}
for i in 0..PLANT_COUNT {
println!(
"CONSECUTIVE_WATERING_PLANT[{}] = {}",
i, CONSECUTIVE_WATERING_PLANT[i]
);
}
}
}
}
pub(crate) fn mqtt(&mut self, network_config: &NetworkConfig) -> anyhow::Result<()> {
let base_topic = network_config
.base_topic
.as_ref()
.context("missing base topic")?;
if base_topic.is_empty() {
bail!("Mqtt base_topic was empty")
}
let base_topic_copy = base_topic.clone();
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 mqtt_client_config = MqttClientConfiguration {
lwt: Some(LwtConfiguration {
topic: &last_will_topic,
payload: "lost".as_bytes(),
qos: AtLeastOnce,
retain: true,
}),
client_id: Some("plantctrl"),
keep_alive_interval: Some(Duration::from_secs(60 * 60 * 2)),
//room for improvement
..Default::default()
};
let mqtt_connected_event_received = Arc::new(AtomicBool::new(false));
let mqtt_connected_event_ok = Arc::new(AtomicBool::new(false));
let round_trip_ok = Arc::new(AtomicBool::new(false));
let round_trip_topic = format!("{}/internal/roundtrip", base_topic);
let stay_alive_topic = format!("{}/stay_alive", base_topic);
log(LogMessage::StayAlive, 0, 0, "", &stay_alive_topic);
let mqtt_connected_event_received_copy = mqtt_connected_event_received.clone();
let mqtt_connected_event_ok_copy = mqtt_connected_event_ok.clone();
let stay_alive_topic_copy = stay_alive_topic.clone();
let round_trip_topic_copy = round_trip_topic.clone();
let round_trip_ok_copy = round_trip_ok.clone();
let client_id = mqtt_client_config.client_id.unwrap_or("not set");
log(LogMessage::MqttInfo, 0, 0, client_id, mqtt_url);
let mut client = EspMqttClient::new_cb(mqtt_url, &mqtt_client_config, move |event| {
let payload = event.payload();
match payload {
embedded_svc::mqtt::client::EventPayload::Received {
id: _,
topic,
data,
details: _,
} => {
let data = String::from_utf8_lossy(data);
if let Some(topic) = topic {
//todo use enums
if topic.eq(round_trip_topic_copy.as_str()) {
round_trip_ok_copy.store(true, std::sync::atomic::Ordering::Relaxed);
} else if topic.eq(stay_alive_topic_copy.as_str()) {
let value =
data.eq_ignore_ascii_case("true") || data.eq_ignore_ascii_case("1");
log(LogMessage::MqttStayAliveRec, 0, 0, &data, "");
STAY_ALIVE.store(value, std::sync::atomic::Ordering::Relaxed);
} else {
log(LogMessage::UnknownTopic, 0, 0, "", topic);
}
}
}
esp_idf_svc::mqtt::client::EventPayload::Connected(_) => {
mqtt_connected_event_received_copy
.store(true, std::sync::atomic::Ordering::Relaxed);
mqtt_connected_event_ok_copy.store(true, std::sync::atomic::Ordering::Relaxed);
println!("Mqtt connected");
}
esp_idf_svc::mqtt::client::EventPayload::Disconnected => {
mqtt_connected_event_received_copy
.store(true, std::sync::atomic::Ordering::Relaxed);
mqtt_connected_event_ok_copy.store(false, std::sync::atomic::Ordering::Relaxed);
println!("Mqtt disconnected");
}
esp_idf_svc::mqtt::client::EventPayload::Error(esp_error) => {
println!("EspMqttError reported {:?}", esp_error);
mqtt_connected_event_received_copy
.store(true, std::sync::atomic::Ordering::Relaxed);
mqtt_connected_event_ok_copy.store(false, std::sync::atomic::Ordering::Relaxed);
println!("Mqtt error");
}
esp_idf_svc::mqtt::client::EventPayload::BeforeConnect => {
println!("Mqtt before connect")
}
esp_idf_svc::mqtt::client::EventPayload::Subscribed(_) => {
println!("Mqtt subscribed")
}
esp_idf_svc::mqtt::client::EventPayload::Unsubscribed(_) => {
println!("Mqtt unsubscribed")
}
esp_idf_svc::mqtt::client::EventPayload::Published(_) => {
println!("Mqtt published")
}
esp_idf_svc::mqtt::client::EventPayload::Deleted(_) => {
println!("Mqtt deleted")
}
}
})?;
let mut wait_for_connections_event = 0;
while wait_for_connections_event < 100 {
wait_for_connections_event += 1;
match mqtt_connected_event_received.load(std::sync::atomic::Ordering::Relaxed) {
true => {
println!("Mqtt connection callback received, progressing");
match mqtt_connected_event_ok.load(std::sync::atomic::Ordering::Relaxed) {
true => {
println!("Mqtt did callback as connected, testing with roundtrip now");
//subscribe to roundtrip
client.subscribe(round_trip_topic.as_str(), ExactlyOnce)?;
client.subscribe(stay_alive_topic.as_str(), ExactlyOnce)?;
//publish to roundtrip
client.publish(
round_trip_topic.as_str(),
ExactlyOnce,
false,
"online_test".as_bytes(),
)?;
let mut wait_for_roundtrip = 0;
while wait_for_roundtrip < 100 {
wait_for_roundtrip += 1;
match round_trip_ok.load(std::sync::atomic::Ordering::Relaxed) {
true => {
println!("Round trip registered, proceeding");
self.mqtt_client = Some(MqttClient {
mqtt_client: client,
base_topic: base_topic_copy,
});
return anyhow::Ok(());
}
false => {
unsafe { vTaskDelay(10) };
}
}
}
bail!("Mqtt did not complete roundtrip in time");
}
false => {
bail!("Mqtt did respond but with failure")
}
}
}
false => {
unsafe { vTaskDelay(10) };
}
}
}
bail!("Mqtt did not fire connection callback in time");
}
pub(crate) fn mqtt_publish(&mut self, subtopic: &str, message: &[u8]) -> anyhow::Result<()> {
if self.mqtt_client.is_none() {
return anyhow::Ok(());
}
if !subtopic.starts_with("/") {
println!("Subtopic without / at start {}", subtopic);
bail!("Subtopic without / at start {}", subtopic);
}
if subtopic.len() > 192 {
println!("Subtopic exceeds 192 chars {}", subtopic);
bail!("Subtopic exceeds 192 chars {}", subtopic);
}
let client = self.mqtt_client.as_mut().unwrap();
let mut full_topic: heapless::String<256> = heapless::String::new();
if full_topic.push_str(client.base_topic.as_str()).is_err() {
println!("Some error assembling full_topic 1");
bail!("Some error assembling full_topic 1")
};
if full_topic.push_str(subtopic).is_err() {
println!("Some error assembling full_topic 2");
bail!("Some error assembling full_topic 2")
};
let publish = client
.mqtt_client
.publish(&full_topic, ExactlyOnce, true, message);
Delay::new(10).delay_ms(50);
match publish {
OkStd(message_id) => {
println!(
"Published mqtt topic {} with message {:#?} msgid is {:?}",
full_topic,
String::from_utf8_lossy(message),
message_id
);
anyhow::Ok(())
}
Err(err) => {
println!(
"Error during mqtt send on topic {} with message {:#?} error is {:?}",
full_topic,
String::from_utf8_lossy(message),
err
);
Err(err)?
}
}
}
}

144
rust/src/hal/initial_hal.rs Normal file
View File

@@ -0,0 +1,144 @@
use crate::hal::esp::Esp;
use crate::hal::rtc::{BackupHeader, RTCModuleInteraction};
use crate::hal::water::TankSensor;
use crate::hal::{deep_sleep, BoardInteraction, FreePeripherals, Sensor};
use crate::{
config::PlantControllerConfig,
hal::battery::{BatteryInteraction, NoBatteryMonitor},
};
use anyhow::{bail, Result};
use chrono::{DateTime, Utc};
use embedded_hal::digital::OutputPin;
use esp_idf_hal::gpio::{IOPin, Pull};
use esp_idf_hal::gpio::{InputOutput, PinDriver};
use measurements::{Current, Voltage};
pub struct Initial<'a> {
pub(crate) general_fault: PinDriver<'a, esp_idf_hal::gpio::AnyIOPin, InputOutput>,
pub(crate) esp: Esp<'a>,
pub(crate) config: PlantControllerConfig,
pub(crate) battery: Box<dyn BatteryInteraction + Send>,
pub rtc: Box<dyn RTCModuleInteraction + Send>,
}
struct NoRTC {}
impl RTCModuleInteraction for NoRTC {
fn get_backup_info(&mut self) -> Result<BackupHeader> {
bail!("Please configure board revision")
}
fn get_backup_config(&mut self) -> Result<Vec<u8>> {
bail!("Please configure board revision")
}
fn backup_config(&mut self, _bytes: &[u8]) -> Result<()> {
bail!("Please configure board revision")
}
fn get_rtc_time(&mut self) -> Result<DateTime<Utc>> {
bail!("Please configure board revision")
}
fn set_rtc_time(&mut self, _time: &DateTime<Utc>) -> Result<()> {
bail!("Please configure board revision")
}
}
pub(crate) fn create_initial_board(
free_pins: FreePeripherals,
fs_mount_error: bool,
config: PlantControllerConfig,
esp: Esp<'static>,
) -> Result<Box<dyn BoardInteraction<'static> + Send>> {
println!("Start initial");
let mut general_fault = PinDriver::input_output(free_pins.gpio6.downgrade())?;
general_fault.set_pull(Pull::Floating)?;
general_fault.set_low()?;
if fs_mount_error {
general_fault.set_high()?
}
let v = Initial {
general_fault,
config,
esp,
battery: Box::new(NoBatteryMonitor {}),
rtc: Box::new(NoRTC {}),
};
Ok(Box::new(v))
}
impl<'a> BoardInteraction<'a> for Initial<'a> {
fn get_tank_sensor(&mut self) -> Option<&mut TankSensor<'a>> {
None
}
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
}
fn get_rtc_module(&mut self) -> &mut Box<dyn RTCModuleInteraction + Send> {
&mut self.rtc
}
fn set_charge_indicator(&mut self, _charging: bool) -> Result<()> {
bail!("Please configure board revision")
}
fn deep_sleep(&mut self, duration_in_ms: u64) -> ! {
deep_sleep(duration_in_ms)
}
fn is_day(&self) -> bool {
false
}
fn light(&mut self, _enable: bool) -> Result<()> {
bail!("Please configure board revision")
}
fn pump(&mut self, _plant: usize, _enable: bool) -> Result<()> {
bail!("Please configure board revision")
}
fn pump_current(&mut self, _plant: usize) -> Result<Current> {
bail!("Please configure board revision")
}
fn fault(&mut self, _plant: usize, _enable: bool) -> Result<()> {
bail!("Please configure board revision")
}
fn measure_moisture_hz(&mut self, _plant: usize, _sensor: Sensor) -> Result<f32> {
bail!("Please configure board revision")
}
fn general_fault(&mut self, enable: bool) {
let _ = self.general_fault.set_state(enable.into());
}
fn test(&mut self) -> Result<()> {
bail!("Please configure board revision")
}
fn set_config(&mut self, config: PlantControllerConfig) -> anyhow::Result<()> {
self.config = config;
self.esp.save_config(&self.config)?;
anyhow::Ok(())
}
fn get_mptt_voltage(&mut self) -> Result<Voltage> {
bail!("Please configure board revision")
}
fn get_mptt_current(&mut self) -> Result<Current> {
bail!("Please configure board revision")
}
}

361
rust/src/hal/mod.rs Normal file
View File

@@ -0,0 +1,361 @@
pub(crate) mod battery;
mod esp;
mod initial_hal;
mod rtc;
mod v3_hal;
mod v4_hal;
mod water;
mod v4_sensor;
use crate::hal::rtc::{DS3231Module, RTCModuleInteraction};
use crate::hal::water::TankSensor;
use crate::{
config::{BatteryBoardVersion, BoardVersion, PlantControllerConfig},
hal::{
battery::{print_battery_bq34z100, BatteryInteraction, NoBatteryMonitor},
esp::Esp,
},
log::{log, LogMessage},
};
use anyhow::{Ok, Result};
use battery::BQ34Z100G1;
use bq34z100::Bq34z100g1Driver;
use ds323x::{DateTimeAccess, Ds323x};
use eeprom24x::{Eeprom24x, SlaveAddr, Storage};
use embedded_hal_bus::i2c::MutexDevice;
use esp_idf_hal::{
adc::ADC1,
delay::Delay,
gpio::{
Gpio0, Gpio1, Gpio10, Gpio11, Gpio12, Gpio13, Gpio14, Gpio15, Gpio16, Gpio17, Gpio18,
Gpio2, Gpio21, Gpio22, Gpio23, Gpio24, Gpio25, Gpio26, Gpio27, Gpio28, Gpio29, Gpio3,
Gpio30, Gpio4, Gpio5, Gpio6, Gpio7, Gpio8, IOPin, PinDriver, Pull,
},
i2c::{APBTickType, I2cConfig, I2cDriver},
pcnt::PCNT0,
prelude::Peripherals,
reset::ResetReason,
units::FromValueType,
};
use esp_idf_svc::{eventloop::EspSystemEventLoop, nvs::EspDefaultNvsPartition, wifi::EspWifi};
use esp_idf_sys::{
esp_deep_sleep, esp_restart, esp_sleep_enable_ext1_wakeup,
esp_sleep_ext1_wakeup_mode_t_ESP_EXT1_WAKEUP_ANY_LOW,
};
use esp_ota::mark_app_valid;
use measurements::{Current, Voltage};
use once_cell::sync::Lazy;
use std::result::Result::Ok as OkStd;
use std::sync::Mutex;
use std::time::Duration;
use esp_idf_hal::can::CAN;
use esp_idf_hal::pcnt::PCNT1;
//Only support for 8 right now!
pub const PLANT_COUNT: usize = 8;
const TANK_MULTI_SAMPLE: usize = 11;
pub static I2C_DRIVER: Lazy<Mutex<I2cDriver<'static>>> = Lazy::new(PlantHal::create_i2c);
fn deep_sleep(duration_in_ms: u64) -> ! {
unsafe {
//if we don't do this here, we might just revert newly flashed firmware
mark_app_valid();
//allow early wakeup by pressing the boot button
if duration_in_ms == 0 {
esp_restart();
} else {
//configure gpio 1 to wakeup on low, reused boot button for this
esp_sleep_enable_ext1_wakeup(
0b10u64,
esp_sleep_ext1_wakeup_mode_t_ESP_EXT1_WAKEUP_ANY_LOW,
);
esp_deep_sleep(duration_in_ms);
}
};
}
#[derive(Debug, PartialEq)]
pub enum Sensor {
A,
B,
}
pub struct PlantHal {}
pub struct HAL<'a> {
pub board_hal: Box<dyn BoardInteraction<'a> + Send>,
}
pub trait BoardInteraction<'a> {
fn get_tank_sensor(&mut self) -> Option<&mut TankSensor<'a>>;
fn get_esp(&mut self) -> &mut Esp<'a>;
fn get_config(&mut self) -> &PlantControllerConfig;
fn get_battery_monitor(&mut self) -> &mut Box<dyn BatteryInteraction + Send>;
fn get_rtc_module(&mut self) -> &mut Box<dyn RTCModuleInteraction + Send>;
fn set_charge_indicator(&mut self, charging: bool) -> Result<()>;
fn deep_sleep(&mut self, duration_in_ms: u64) -> !;
fn is_day(&self) -> bool;
//should be multsampled
fn light(&mut self, enable: bool) -> Result<()>;
fn pump(&mut self, plant: usize, enable: bool) -> Result<()>;
fn pump_current(&mut self, plant: usize) -> Result<Current>;
fn fault(&mut self, plant: usize, enable: bool) -> Result<()>;
fn measure_moisture_hz(&mut self, plant: usize, sensor: Sensor) -> Result<f32>;
fn general_fault(&mut self, enable: bool);
fn test(&mut self) -> Result<()>;
fn set_config(&mut self, config: PlantControllerConfig) -> Result<()>;
fn get_mptt_voltage(&mut self) -> anyhow::Result<Voltage>;
fn get_mptt_current(&mut self) -> anyhow::Result<Current>;
}
impl dyn BoardInteraction<'_> {
//the counter is just some arbitrary number that increases whenever some progress was made, try to keep the updates < 10 per second for ux reasons
fn progress(&mut self, counter: u32) {
let even = counter % 2 == 0;
let current = counter / (PLANT_COUNT as u32);
for led in 0..PLANT_COUNT {
self.fault(led, current == led as u32).unwrap();
}
let _ = self.general_fault(even.into());
}
}
#[allow(dead_code)]
pub struct FreePeripherals {
pub gpio0: Gpio0,
pub gpio1: Gpio1,
pub gpio2: Gpio2,
pub gpio3: Gpio3,
pub gpio4: Gpio4,
pub gpio5: Gpio5,
pub gpio6: Gpio6,
pub gpio7: Gpio7,
pub gpio8: Gpio8,
//config button here
pub gpio10: Gpio10,
pub gpio11: Gpio11,
pub gpio12: Gpio12,
pub gpio13: Gpio13,
pub gpio14: Gpio14,
pub gpio15: Gpio15,
pub gpio16: Gpio16,
pub gpio17: Gpio17,
pub gpio18: Gpio18,
//i2c here
pub gpio21: Gpio21,
pub gpio22: Gpio22,
pub gpio23: Gpio23,
pub gpio24: Gpio24,
pub gpio25: Gpio25,
pub gpio26: Gpio26,
pub gpio27: Gpio27,
pub gpio28: Gpio28,
pub gpio29: Gpio29,
pub gpio30: Gpio30,
pub pcnt0: PCNT0,
pub pcnt1: PCNT1,
pub adc1: ADC1,
pub can: CAN,
}
impl PlantHal {
fn create_i2c() -> Mutex<I2cDriver<'static>> {
let peripherals = unsafe { Peripherals::new() };
let config = I2cConfig::new()
.scl_enable_pullup(true)
.sda_enable_pullup(true)
.baudrate(100_u32.kHz().into())
.timeout(APBTickType::from(Duration::from_millis(100)));
let i2c = peripherals.i2c0;
let scl = peripherals.pins.gpio19.downgrade();
let sda = peripherals.pins.gpio20.downgrade();
Mutex::new(I2cDriver::new(i2c, sda, scl, &config).unwrap())
}
pub fn create() -> Result<Mutex<HAL<'static>>> {
let peripherals = Peripherals::take()?;
let sys_loop = EspSystemEventLoop::take()?;
let nvs = EspDefaultNvsPartition::take()?;
let wifi_driver = EspWifi::new(peripherals.modem, sys_loop, Some(nvs))?;
let mut boot_button = PinDriver::input(peripherals.pins.gpio9.downgrade())?;
boot_button.set_pull(Pull::Floating)?;
let free_pins = FreePeripherals {
can: peripherals.can,
adc1: peripherals.adc1,
pcnt0: peripherals.pcnt0,
pcnt1: peripherals.pcnt1,
gpio0: peripherals.pins.gpio0,
gpio1: peripherals.pins.gpio1,
gpio2: peripherals.pins.gpio2,
gpio3: peripherals.pins.gpio3,
gpio4: peripherals.pins.gpio4,
gpio5: peripherals.pins.gpio5,
gpio6: peripherals.pins.gpio6,
gpio7: peripherals.pins.gpio7,
gpio8: peripherals.pins.gpio8,
gpio10: peripherals.pins.gpio10,
gpio11: peripherals.pins.gpio11,
gpio12: peripherals.pins.gpio12,
gpio13: peripherals.pins.gpio13,
gpio14: peripherals.pins.gpio14,
gpio15: peripherals.pins.gpio15,
gpio16: peripherals.pins.gpio16,
gpio17: peripherals.pins.gpio17,
gpio18: peripherals.pins.gpio18,
gpio21: peripherals.pins.gpio21,
gpio22: peripherals.pins.gpio22,
gpio23: peripherals.pins.gpio23,
gpio24: peripherals.pins.gpio24,
gpio25: peripherals.pins.gpio25,
gpio26: peripherals.pins.gpio26,
gpio27: peripherals.pins.gpio27,
gpio28: peripherals.pins.gpio28,
gpio29: peripherals.pins.gpio29,
gpio30: peripherals.pins.gpio30,
};
let mut esp = Esp {
mqtt_client: None,
wifi_driver,
boot_button,
delay: Delay::new(1000),
};
//init,reset rtc memory depending on cause
let mut init_rtc_store: bool = false;
let mut to_config_mode: bool = false;
let reasons = ResetReason::get();
match reasons {
ResetReason::Software => {}
ResetReason::ExternalPin => {}
ResetReason::Watchdog => {
init_rtc_store = true;
}
ResetReason::Sdio => init_rtc_store = true,
ResetReason::Panic => init_rtc_store = true,
ResetReason::InterruptWatchdog => init_rtc_store = true,
ResetReason::PowerOn => init_rtc_store = true,
ResetReason::Unknown => init_rtc_store = true,
ResetReason::Brownout => init_rtc_store = true,
ResetReason::TaskWatchdog => init_rtc_store = true,
ResetReason::DeepSleep => {}
ResetReason::USBPeripheral => {
init_rtc_store = true;
to_config_mode = true;
}
ResetReason::JTAG => init_rtc_store = true,
};
log(
LogMessage::ResetReason,
init_rtc_store as u32,
to_config_mode as u32,
"",
&format!("{reasons:?}"),
);
esp.init_rtc_deepsleep_memory(init_rtc_store, to_config_mode);
let fs_mount_error = esp.mount_file_system().is_err();
let config = esp.load_config();
println!("Init rtc driver");
let mut rtc = Ds323x::new_ds3231(MutexDevice::new(&I2C_DRIVER));
println!("Init rtc eeprom driver");
let eeprom = {
Eeprom24x::new_24x32(
MutexDevice::new(&I2C_DRIVER),
SlaveAddr::Alternative(true, true, true),
)
};
let rtc_time = rtc.datetime();
match rtc_time {
OkStd(tt) => {
println!("Rtc Module reports time at UTC {}", tt);
}
Err(err) => {
println!("Rtc Module could not be read {:?}", err);
}
}
let storage = Storage::new(eeprom, Delay::new(1000));
let rtc_module: Box<dyn RTCModuleInteraction + Send> =
Box::new(DS3231Module { rtc, storage }) as Box<dyn RTCModuleInteraction + Send>;
let hal = match config {
Result::Ok(config) => {
let battery_interaction: Box<dyn BatteryInteraction + Send> =
match config.hardware.battery {
BatteryBoardVersion::Disabled => Box::new(NoBatteryMonitor {}),
BatteryBoardVersion::BQ34Z100G1 => {
let mut battery_driver = Bq34z100g1Driver {
i2c: MutexDevice::new(&I2C_DRIVER),
delay: Delay::new(0),
flash_block_data: [0; 32],
};
let status = print_battery_bq34z100(&mut battery_driver);
match status {
OkStd(_) => {}
Err(err) => {
log(
LogMessage::BatteryCommunicationError,
0u32,
0,
"",
&format!("{err:?})"),
);
}
}
Box::new(BQ34Z100G1 { battery_driver })
}
BatteryBoardVersion::WchI2cSlave => {
// TODO use correct implementation once availible
Box::new(NoBatteryMonitor {})
}
};
let board_hal: Box<dyn BoardInteraction + Send> = match config.hardware.board {
BoardVersion::INITIAL => {
initial_hal::create_initial_board(free_pins, fs_mount_error, config, esp)?
}
BoardVersion::V3 => {
v3_hal::create_v3(free_pins, esp, config, battery_interaction, rtc_module)?
}
BoardVersion::V4 => {
v4_hal::create_v4(free_pins, esp, config, battery_interaction, rtc_module)?
}
};
HAL { board_hal }
}
Err(err) => {
log(
LogMessage::ConfigModeMissingConfig,
0,
0,
"",
&err.to_string(),
);
HAL {
board_hal: initial_hal::create_initial_board(
free_pins,
fs_mount_error,
PlantControllerConfig::default(),
esp,
)?,
}
}
};
Ok(Mutex::new(hal))
}
}

132
rust/src/hal/rtc.rs Normal file
View File

@@ -0,0 +1,132 @@
use anyhow::{anyhow, bail};
use bincode::config::Configuration;
use bincode::{config, Decode, Encode};
use chrono::{DateTime, Utc};
use ds323x::{DateTimeAccess, Ds323x};
use eeprom24x::addr_size::TwoBytes;
use eeprom24x::page_size::B32;
use eeprom24x::unique_serial::No;
use eeprom24x::Storage;
use embedded_hal_bus::i2c::MutexDevice;
use embedded_storage::ReadStorage as embedded_storage_ReadStorage;
use embedded_storage::Storage as embedded_storage_Storage;
use esp_idf_hal::delay::Delay;
use esp_idf_hal::i2c::I2cDriver;
use serde::{Deserialize, Serialize};
use std::result::Result::Ok as OkStd;
const X25: crc::Crc<u16> = crc::Crc::<u16>::new(&crc::CRC_16_IBM_SDLC);
const CONFIG: Configuration = config::standard();
pub trait RTCModuleInteraction {
fn get_backup_info(&mut self) -> anyhow::Result<BackupHeader>;
fn get_backup_config(&mut self) -> anyhow::Result<Vec<u8>>;
fn backup_config(&mut self, bytes: &[u8]) -> anyhow::Result<()>;
fn get_rtc_time(&mut self) -> anyhow::Result<DateTime<Utc>>;
fn set_rtc_time(&mut self, time: &DateTime<Utc>) -> anyhow::Result<()>;
}
const BACKUP_HEADER_MAX_SIZE: usize = 64;
#[derive(Serialize, Deserialize, PartialEq, Debug, Default, Encode, Decode)]
pub struct BackupHeader {
pub timestamp: i64,
crc16: u16,
pub size: u16,
}
pub struct DS3231Module<'a> {
pub(crate) rtc:
Ds323x<ds323x::interface::I2cInterface<MutexDevice<'a, I2cDriver<'a>>>, ds323x::ic::DS3231>,
pub(crate) storage: Storage<MutexDevice<'a, I2cDriver<'a>>, B32, TwoBytes, No, Delay>,
}
impl RTCModuleInteraction for DS3231Module<'_> {
fn get_backup_info(&mut self) -> anyhow::Result<BackupHeader> {
let mut header_page_buffer = [0_u8; BACKUP_HEADER_MAX_SIZE];
self.storage
.read(0, &mut header_page_buffer)
.map_err(|err| anyhow!("Error reading eeprom header {:?}", err))?;
let (header, len): (BackupHeader, usize) =
bincode::decode_from_slice(&header_page_buffer[..], CONFIG)?;
println!("Raw header is {:?} with size {}", header_page_buffer, len);
anyhow::Ok(header)
}
fn get_backup_config(&mut self) -> anyhow::Result<Vec<u8>> {
let mut header_page_buffer = [0_u8; BACKUP_HEADER_MAX_SIZE];
self.storage
.read(0, &mut header_page_buffer)
.map_err(|err| anyhow!("Error reading eeprom header {:?}", err))?;
let (header, _header_size): (BackupHeader, usize) =
bincode::decode_from_slice(&header_page_buffer[..], CONFIG)?;
let mut data_buffer = vec![0_u8; header.size as usize];
//read the specified number of bytes after the header
self.storage
.read(BACKUP_HEADER_MAX_SIZE as u32, &mut data_buffer)
.map_err(|err| anyhow!("Error reading eeprom data {:?}", err))?;
let checksum = X25.checksum(&data_buffer);
if checksum != header.crc16 {
bail!(
"Invalid checksum, got {} but expected {}",
checksum,
header.crc16
);
}
anyhow::Ok(data_buffer)
}
fn backup_config(&mut self, bytes: &[u8]) -> anyhow::Result<()> {
let mut header_page_buffer = [0_u8; BACKUP_HEADER_MAX_SIZE];
let time = self.get_rtc_time()?.timestamp_millis();
let checksum = X25.checksum(bytes);
let header = BackupHeader {
crc16: checksum,
timestamp: time,
size: bytes.len() as u16,
};
let config = config::standard();
let encoded = bincode::encode_into_slice(&header, &mut header_page_buffer, config)?;
println!(
"Raw header is {:?} with size {}",
header_page_buffer, encoded
);
self.storage
.write(0, &header_page_buffer)
.map_err(|err| anyhow!("Error writing header {:?}", err))?;
//write rest after the header
self.storage
.write(BACKUP_HEADER_MAX_SIZE as u32, &bytes)
.map_err(|err| anyhow!("Error writing body {:?}", err))?;
anyhow::Ok(())
}
fn get_rtc_time(&mut self) -> anyhow::Result<DateTime<Utc>> {
match self.rtc.datetime() {
OkStd(rtc_time) => anyhow::Ok(rtc_time.and_utc()),
Err(err) => {
bail!("Error getting rtc time {:?}", err)
}
}
}
fn set_rtc_time(&mut self, time: &DateTime<Utc>) -> anyhow::Result<()> {
let naive_time = time.naive_utc();
match self.rtc.set_datetime(&naive_time) {
OkStd(_) => anyhow::Ok(()),
Err(err) => {
bail!("Error getting rtc time {:?}", err)
}
}
}
}

436
rust/src/hal/v3_hal.rs Normal file
View File

@@ -0,0 +1,436 @@
use crate::hal::rtc::RTCModuleInteraction;
use crate::hal::water::TankSensor;
use crate::hal::{
deep_sleep, BoardInteraction, FreePeripherals, Sensor, PLANT_COUNT,
};
use crate::log::{log, LogMessage};
use crate::{
config::PlantControllerConfig,
hal::{battery::BatteryInteraction, esp::Esp},
};
use anyhow::{bail, Ok, Result};
use embedded_hal::digital::OutputPin;
use esp_idf_hal::{
gpio::{AnyInputPin, IOPin, InputOutput, PinDriver, Pull},
pcnt::{PcntChannel, PcntChannelConfig, PcntControlMode, PcntCountMode, PcntDriver, PinIndex},
};
use esp_idf_sys::{gpio_hold_dis, gpio_hold_en};
use measurements::{Current, Voltage};
use plant_ctrl2::sipo::ShiftRegister40;
use std::result::Result::Ok as OkStd;
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: ShiftRegister40<
PinDriver<'a, esp_idf_hal::gpio::AnyIOPin, InputOutput>,
PinDriver<'a, esp_idf_hal::gpio::AnyIOPin, InputOutput>,
PinDriver<'a, esp_idf_hal::gpio::AnyIOPin, InputOutput>,
>,
_shift_register_enable_invert:
PinDriver<'a, esp_idf_hal::gpio::AnyIOPin, esp_idf_hal::gpio::Output>,
tank_sensor: TankSensor<'a>,
solar_is_day: PinDriver<'a, esp_idf_hal::gpio::AnyIOPin, esp_idf_hal::gpio::Input>,
light: PinDriver<'a, esp_idf_hal::gpio::AnyIOPin, InputOutput>,
main_pump: PinDriver<'a, esp_idf_hal::gpio::AnyIOPin, InputOutput>,
general_fault: PinDriver<'a, esp_idf_hal::gpio::AnyIOPin, InputOutput>,
signal_counter: PcntDriver<'a>,
}
pub(crate) fn create_v3(
peripherals: FreePeripherals,
esp: Esp<'static>,
config: PlantControllerConfig,
battery_monitor: Box<dyn BatteryInteraction + Send>,
rtc_module: Box<dyn RTCModuleInteraction + Send>,
) -> Result<Box<dyn BoardInteraction<'static> + Send>> {
println!("Start v3");
let mut clock = PinDriver::input_output(peripherals.gpio15.downgrade())?;
clock.set_pull(Pull::Floating)?;
let mut latch = PinDriver::input_output(peripherals.gpio3.downgrade())?;
latch.set_pull(Pull::Floating)?;
let mut data = PinDriver::input_output(peripherals.gpio23.downgrade())?;
data.set_pull(Pull::Floating)?;
let shift_register = ShiftRegister40::new(clock, latch, data);
//disable all
for mut pin in shift_register.decompose() {
pin.set_low()?;
}
let awake = &mut shift_register.decompose()[AWAKE];
awake.set_high()?;
let charging = &mut shift_register.decompose()[CHARGING];
charging.set_high()?;
let ms0 = &mut shift_register.decompose()[MS_0];
ms0.set_low()?;
let ms1 = &mut shift_register.decompose()[MS_1];
ms1.set_low()?;
let ms2 = &mut shift_register.decompose()[MS_2];
ms2.set_low()?;
let ms3 = &mut shift_register.decompose()[MS_3];
ms3.set_low()?;
let ms4 = &mut shift_register.decompose()[MS_4];
ms4.set_high()?;
let one_wire_pin = peripherals.gpio18.downgrade();
let tank_power_pin = peripherals.gpio11.downgrade();
let flow_sensor_pin = peripherals.gpio4.downgrade();
let tank_sensor = TankSensor::create(
one_wire_pin,
peripherals.adc1,
peripherals.gpio5,
tank_power_pin,
flow_sensor_pin,
peripherals.pcnt1
)?;
let mut signal_counter = PcntDriver::new(
peripherals.pcnt0,
Some(peripherals.gpio22),
Option::<AnyInputPin>::None,
Option::<AnyInputPin>::None,
Option::<AnyInputPin>::None,
)?;
signal_counter.channel_config(
PcntChannel::Channel0,
PinIndex::Pin0,
PinIndex::Pin1,
&PcntChannelConfig {
lctrl_mode: PcntControlMode::Keep,
hctrl_mode: PcntControlMode::Keep,
pos_mode: PcntCountMode::Increment,
neg_mode: PcntCountMode::Hold,
counter_h_lim: i16::MAX,
counter_l_lim: 0,
},
)?;
let mut solar_is_day = PinDriver::input(peripherals.gpio7.downgrade())?;
solar_is_day.set_pull(Pull::Floating)?;
let mut light = PinDriver::input_output(peripherals.gpio10.downgrade())?;
light.set_pull(Pull::Floating)?;
let mut main_pump = PinDriver::input_output(peripherals.gpio2.downgrade())?;
main_pump.set_pull(Pull::Floating)?;
main_pump.set_low()?;
let mut general_fault = PinDriver::input_output(peripherals.gpio6.downgrade())?;
general_fault.set_pull(Pull::Floating)?;
general_fault.set_low()?;
let mut shift_register_enable_invert = PinDriver::output(peripherals.gpio21.downgrade())?;
unsafe { gpio_hold_dis(shift_register_enable_invert.pin()) };
shift_register_enable_invert.set_low()?;
unsafe { gpio_hold_en(shift_register_enable_invert.pin()) };
Ok(Box::new(V3 {
config,
battery_monitor,
rtc_module,
esp,
shift_register,
_shift_register_enable_invert: shift_register_enable_invert,
tank_sensor,
solar_is_day,
light,
main_pump,
general_fault,
signal_counter,
}))
}
impl<'a> BoardInteraction<'a> for V3<'a> {
fn get_tank_sensor(&mut self) -> Option<&mut TankSensor<'a>> {
Some(&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 + 'static)> {
&mut self.battery_monitor
}
fn get_rtc_module(&mut self) -> &mut Box<dyn RTCModuleInteraction + Send> {
&mut self.rtc_module
}
fn set_charge_indicator(&mut self, charging: bool) -> Result<()> {
Ok(self.shift_register.decompose()[CHARGING].set_state(charging.into())?)
}
fn deep_sleep(&mut self, duration_in_ms: u64) -> ! {
let _ = self.shift_register.decompose()[AWAKE].set_low();
deep_sleep(duration_in_ms)
}
fn is_day(&self) -> bool {
self.solar_is_day.get_level().into()
}
fn light(&mut self, enable: bool) -> Result<()> {
unsafe { gpio_hold_dis(self.light.pin()) };
self.light.set_state(enable.into())?;
unsafe { gpio_hold_en(self.light.pin()) };
Ok(())
}
fn pump(&mut self, plant: usize, enable: bool) -> Result<()> {
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}",),
};
self.shift_register.decompose()[index].set_state(enable.into())?;
if !enable {
self.main_pump.set_low()?;
}
Ok(())
}
fn pump_current(&mut self, _plant: usize) -> Result<Current> {
bail!("Not implemented in v3")
}
fn fault(&mut self, plant: usize, enable: bool) -> Result<()> {
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),
};
self.shift_register.decompose()[index].set_state(enable.into())?;
Ok(())
}
fn measure_moisture_hz(&mut self, plant: usize, sensor: Sensor) -> Result<f32> {
let mut results = [0_f32; REPEAT_MOIST_MEASURE];
for repeat in 0..REPEAT_MOIST_MEASURE {
self.signal_counter.counter_pause()?;
self.signal_counter.counter_clear()?;
//Disable all
self.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 pin_0 = &mut self.shift_register.decompose()[MS_0];
let pin_1 = &mut self.shift_register.decompose()[MS_1];
let pin_2 = &mut self.shift_register.decompose()[MS_2];
let pin_3 = &mut self.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()?;
}
self.shift_register.decompose()[MS_4].set_low()?;
self.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
self.esp.delay.delay_ms(10);
self.signal_counter.counter_resume()?;
self.esp.delay.delay_ms(measurement);
self.signal_counter.counter_pause()?;
self.shift_register.decompose()[MS_4].set_high()?;
self.shift_register.decompose()[SENSOR_ON].set_low()?;
self.esp.delay.delay_ms(10);
let unscaled = self.signal_counter.get_counter_value()? as i32;
let hz = unscaled as f32 * factor;
log(
LogMessage::RawMeasure,
unscaled as u32,
hz as u32,
&plant.to_string(),
&format!("{sensor:?}"),
);
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)
}
fn general_fault(&mut self, enable: bool) {
unsafe { gpio_hold_dis(self.general_fault.pin()) };
self.general_fault.set_state(enable.into()).unwrap();
unsafe { gpio_hold_en(self.general_fault.pin()) };
}
fn test(&mut self) -> Result<()> {
self.general_fault(true);
self.esp.delay.delay_ms(100);
self.general_fault(false);
self.esp.delay.delay_ms(100);
self.light(true)?;
self.esp.delay.delay_ms(500);
self.light(false)?;
self.esp.delay.delay_ms(500);
for i in 0..PLANT_COUNT {
self.fault(i, true)?;
self.esp.delay.delay_ms(500);
self.fault(i, false)?;
self.esp.delay.delay_ms(500);
}
for i in 0..PLANT_COUNT {
self.pump(i, true)?;
self.esp.delay.delay_ms(100);
self.pump(i, false)?;
self.esp.delay.delay_ms(100);
}
for plant in 0..PLANT_COUNT {
let a = self.measure_moisture_hz(plant, Sensor::A);
let b = self.measure_moisture_hz(plant, Sensor::B);
let aa = match a {
OkStd(a) => a as u32,
Err(_) => u32::MAX,
};
let bb = match b {
OkStd(b) => b as u32,
Err(_) => u32::MAX,
};
log(LogMessage::TestSensor, aa, bb, &plant.to_string(), "");
}
self.esp.delay.delay_ms(10);
Ok(())
}
fn set_config(&mut self, config: PlantControllerConfig) -> Result<()> {
self.config = config;
self.esp.save_config(&self.config)?;
anyhow::Ok(())
}
fn get_mptt_voltage(&mut self) -> Result<Voltage> {
//assuming module to work, these are the hardware set values
if self.is_day() {
Ok(Voltage::from_volts(15_f64))
} else {
Ok(Voltage::from_volts(0_f64))
}
}
fn get_mptt_current(&mut self) -> Result<Current> {
bail!("Board does not have current sensor")
}
}

446
rust/src/hal/v4_hal.rs Normal file
View File

@@ -0,0 +1,446 @@
use crate::config::PlantControllerConfig;
use crate::hal::battery::BatteryInteraction;
use crate::hal::esp::Esp;
use crate::hal::rtc::RTCModuleInteraction;
use crate::hal::v4_sensor::SensorImpl;
use crate::hal::v4_sensor::SensorInteraction;
use crate::hal::water::TankSensor;
use crate::hal::{
deep_sleep, BoardInteraction, FreePeripherals, Sensor, I2C_DRIVER, PLANT_COUNT
};
use crate::log::{log, LogMessage};
use anyhow::bail;
use embedded_hal::digital::OutputPin;
use embedded_hal_bus::i2c::MutexDevice;
use esp_idf_hal::gpio::{AnyInputPin, IOPin, InputOutput, Output, PinDriver, Pull};
use esp_idf_hal::i2c::{I2cDriver, I2cError};
use esp_idf_hal::pcnt::{
PcntChannel, PcntChannelConfig, PcntControlMode, PcntCountMode, PcntDriver, PinIndex,
};
use esp_idf_sys::{gpio_hold_dis, gpio_hold_en};
use ina219::address::{Address, Pin};
use ina219::calibration::UnCalibrated;
use ina219::configuration::{Configuration, OperatingMode};
use ina219::SyncIna219;
use measurements::{Current, Resistance, Voltage};
use pca9535::{ExpanderError, GPIOBank, Pca9535Immediate, StandardExpanderInterface};
use std::result::Result::Ok as OkStd;
use embedded_can::nb::Can;
use embedded_can::Frame;
use embedded_can::StandardId;
use esp_idf_hal::prelude::*;
use esp_idf_hal::can;
pub enum Charger<'a> {
SolarMpptV1 {
mppt_ina: SyncIna219<MutexDevice<'a, I2cDriver<'a>>, UnCalibrated>,
solar_is_day: PinDriver<'a, esp_idf_hal::gpio::AnyIOPin, esp_idf_hal::gpio::Input>,
charge_indicator: PinDriver<'a, esp_idf_hal::gpio::AnyIOPin, InputOutput>,
},
ErrorInit {},
}
impl Charger<'_> {
pub(crate) fn power_save(&mut self) {
match self {
Charger::SolarMpptV1 { mppt_ina, .. } => {
let _ = mppt_ina
.set_configuration(Configuration {
reset: Default::default(),
bus_voltage_range: Default::default(),
shunt_voltage_range: Default::default(),
bus_resolution: Default::default(),
shunt_resolution: Default::default(),
operating_mode: OperatingMode::PowerDown,
})
.map_err(|e| {
println!(
"Error setting ina mppt configuration during deep sleep preparation{:?}",
e
);
});
}
_ => {}
}
}
fn set_charge_indicator(&mut self, charging: bool) -> anyhow::Result<()> {
match self {
Self::SolarMpptV1 {
charge_indicator, ..
} => {
charge_indicator.set_state(charging.into())?;
}
_ => {}
}
Ok(())
}
fn is_day(&self) -> bool {
match self {
Charger::SolarMpptV1 { solar_is_day, .. } => solar_is_day.get_level().into(),
_ => true,
}
}
fn get_mptt_voltage(&mut self) -> anyhow::Result<Voltage> {
let voltage = match self {
Charger::SolarMpptV1 { mppt_ina, .. } => mppt_ina
.bus_voltage()
.map(|v| Voltage::from_millivolts(v.voltage_mv() as f64))?,
_ => {
bail!("hardware error during init")
}
};
Ok(voltage)
}
fn get_mptt_current(&mut self) -> anyhow::Result<Current> {
let current = match self {
Charger::SolarMpptV1 { mppt_ina, .. } => mppt_ina.shunt_voltage().map(|v| {
let shunt_voltage = Voltage::from_microvolts(v.shunt_voltage_uv().abs() as f64);
let shut_value = Resistance::from_ohms(0.05_f64);
let current = shunt_voltage.as_volts() / shut_value.as_ohms();
Current::from_amperes(current)
})?,
_ => {
bail!("hardware error during init")
}
};
Ok(current)
}
}
pub struct V4<'a> {
esp: Esp<'a>,
tank_sensor: TankSensor<'a>,
charger: Charger<'a>,
rtc_module: Box<dyn RTCModuleInteraction + Send>,
battery_monitor: Box<dyn BatteryInteraction + Send>,
config: PlantControllerConfig,
awake: PinDriver<'a, esp_idf_hal::gpio::AnyIOPin, Output>,
light: PinDriver<'a, esp_idf_hal::gpio::AnyIOPin, InputOutput>,
general_fault: PinDriver<'a, esp_idf_hal::gpio::AnyIOPin, InputOutput>,
pump_expander: Pca9535Immediate<MutexDevice<'a, I2cDriver<'a>>>,
pump_ina: Option<SyncIna219<MutexDevice<'a, I2cDriver<'a>>, UnCalibrated>>,
sensor: SensorImpl<'a>,
extra1: PinDriver<'a, esp_idf_hal::gpio::AnyIOPin, Output>,
extra2: PinDriver<'a, esp_idf_hal::gpio::AnyIOPin, Output>,
}
pub(crate) fn create_v4(
peripherals: FreePeripherals,
esp: Esp<'static>,
config: PlantControllerConfig,
battery_monitor: Box<dyn BatteryInteraction + Send>,
rtc_module: Box<dyn RTCModuleInteraction + Send>,
) -> anyhow::Result<Box<dyn BoardInteraction<'static> + Send + 'static>> {
println!("Start v4");
let mut awake = PinDriver::output(peripherals.gpio21.downgrade())?;
awake.set_high()?;
let mut general_fault = PinDriver::input_output(peripherals.gpio23.downgrade())?;
general_fault.set_pull(Pull::Floating)?;
general_fault.set_low()?;
let mut extra1 = PinDriver::output(peripherals.gpio6.downgrade())?;
extra1.set_low()?;
let mut extra2 = PinDriver::output(peripherals.gpio15.downgrade())?;
extra2.set_low()?;
let one_wire_pin = peripherals.gpio18.downgrade();
let tank_power_pin = peripherals.gpio11.downgrade();
let flow_sensor_pin = peripherals.gpio4.downgrade();
let tank_sensor = TankSensor::create(
one_wire_pin,
peripherals.adc1,
peripherals.gpio5,
tank_power_pin,
flow_sensor_pin,
peripherals.pcnt1
)?;
let mut sensor_expander = Pca9535Immediate::new(MutexDevice::new(&I2C_DRIVER), 34);
let sensor = match sensor_expander.pin_into_output(GPIOBank::Bank0, 0) {
Ok(_) => {
println!("SensorExpander answered");
//pulse counter version
let mut signal_counter = PcntDriver::new(
peripherals.pcnt0,
Some(peripherals.gpio22),
Option::<AnyInputPin>::None,
Option::<AnyInputPin>::None,
Option::<AnyInputPin>::None,
)?;
signal_counter.channel_config(
PcntChannel::Channel0,
PinIndex::Pin0,
PinIndex::Pin1,
&PcntChannelConfig {
lctrl_mode: PcntControlMode::Keep,
hctrl_mode: PcntControlMode::Keep,
pos_mode: PcntCountMode::Increment,
neg_mode: PcntCountMode::Hold,
counter_h_lim: i16::MAX,
counter_l_lim: 0,
},
)?;
for pin in 0..8 {
let _ = sensor_expander.pin_into_output(GPIOBank::Bank0, pin);
let _ = sensor_expander.pin_into_output(GPIOBank::Bank1, pin);
let _ = sensor_expander.pin_set_low(GPIOBank::Bank0, pin);
let _ = sensor_expander.pin_set_low(GPIOBank::Bank1, pin);
}
SensorImpl::PulseCounter {
signal_counter,
sensor_expander,
}
}
Err(_) => {
println!("Can bus mode ");
let timing = can::config::Timing::B25K;
let config = can::config::Config::new().timing(timing);
let mut can = can::CanDriver::new(peripherals.can, peripherals.gpio0, peripherals.gpio2, &config).unwrap();
let frame = StandardId::new(0x042).unwrap();
let tx_frame = Frame::new(frame, &[0, 1, 2, 3, 4, 5, 6, 7]).unwrap();
can.transmit(&tx_frame, 1000).unwrap();
if let Ok(rx_frame) = can.receive(1000) {
println!("rx {:}:", rx_frame);
}
//can bus version
SensorImpl::CanBus {
can
}
}
};
let mut solar_is_day = PinDriver::input(peripherals.gpio7.downgrade())?;
solar_is_day.set_pull(Pull::Floating)?;
let mut light = PinDriver::input_output(peripherals.gpio10.downgrade())?;
light.set_pull(Pull::Floating)?;
let mut charge_indicator = PinDriver::input_output(peripherals.gpio3.downgrade())?;
charge_indicator.set_pull(Pull::Floating)?;
charge_indicator.set_low()?;
let mut pump_expander = Pca9535Immediate::new(MutexDevice::new(&I2C_DRIVER), 32);
for pin in 0..8 {
let _ = pump_expander.pin_into_output(GPIOBank::Bank0, pin);
let _ = pump_expander.pin_into_output(GPIOBank::Bank1, pin);
let _ = pump_expander.pin_set_low(GPIOBank::Bank0, pin);
let _ = pump_expander.pin_set_low(GPIOBank::Bank1, pin);
}
let mppt_ina = SyncIna219::new(
MutexDevice::new(&I2C_DRIVER),
Address::from_pins(Pin::Vcc, Pin::Gnd),
);
let charger = match mppt_ina {
Ok(mut mppt_ina) => {
mppt_ina.set_configuration(Configuration {
reset: Default::default(),
bus_voltage_range: Default::default(),
shunt_voltage_range: Default::default(),
bus_resolution: Default::default(),
shunt_resolution: ina219::configuration::Resolution::Avg128,
operating_mode: Default::default(),
})?;
Charger::SolarMpptV1 {
mppt_ina,
solar_is_day,
charge_indicator,
}
}
Err(_) => Charger::ErrorInit {},
};
let pump_ina = match SyncIna219::new(
MutexDevice::new(&I2C_DRIVER),
Address::from_pins(Pin::Gnd, Pin::Sda),
) {
Ok(pump_ina) => Some(pump_ina),
Err(err) => {
println!("Error creating pump ina: {:?}", err);
None
}
};
let v = V4 {
rtc_module,
esp,
awake,
tank_sensor,
light,
general_fault,
pump_ina,
pump_expander,
config,
battery_monitor,
charger,
extra1,
extra2,
sensor,
};
Ok(Box::new(v))
}
impl<'a> BoardInteraction<'a> for V4<'a> {
fn get_tank_sensor(&mut self) -> Option<&mut TankSensor<'a>> {
Some(&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
}
fn set_charge_indicator(&mut self, charging: bool) -> anyhow::Result<()> {
self.charger.set_charge_indicator(charging)
}
fn deep_sleep(&mut self, duration_in_ms: u64) -> ! {
self.awake.set_low().unwrap();
self.charger.power_save();
deep_sleep(duration_in_ms);
}
fn is_day(&self) -> bool {
self.charger.is_day()
}
fn light(&mut self, enable: bool) -> anyhow::Result<()> {
unsafe { gpio_hold_dis(self.light.pin()) };
self.light.set_state(enable.into())?;
unsafe { gpio_hold_en(self.light.pin()) };
anyhow::Ok(())
}
fn pump(&mut self, plant: usize, enable: bool) -> anyhow::Result<()> {
if enable {
self.pump_expander
.pin_set_high(GPIOBank::Bank0, plant.try_into()?)?;
} else {
self.pump_expander
.pin_set_low(GPIOBank::Bank0, plant.try_into()?)?;
}
anyhow::Ok(())
}
fn pump_current(&mut self, _plant: usize) -> anyhow::Result<Current> {
//sensore is shared for all pumps, ignore plant id
match self.pump_ina.as_mut() {
None => {
bail!("pump current sensor not available");
}
Some(pump_ina) => {
let v = pump_ina.shunt_voltage().map(|v| {
let shunt_voltage = Voltage::from_microvolts(v.shunt_voltage_uv().abs() as f64);
let shut_value = Resistance::from_ohms(0.05_f64);
let current = shunt_voltage.as_volts() / shut_value.as_ohms();
Current::from_amperes(current)
})?;
Ok(v)
}
}
}
fn fault(&mut self, plant: usize, enable: bool) -> anyhow::Result<()> {
if enable {
self.pump_expander
.pin_set_high(GPIOBank::Bank1, plant.try_into()?)?
} else {
self.pump_expander
.pin_set_low(GPIOBank::Bank1, plant.try_into()?)?
}
anyhow::Ok(())
}
fn measure_moisture_hz(&mut self, plant: usize, sensor: Sensor) -> anyhow::Result<f32> {
self.sensor.measure_moisture_hz(plant, sensor)
}
fn general_fault(&mut self, enable: bool) {
unsafe { gpio_hold_dis(self.general_fault.pin()) };
self.general_fault.set_state(enable.into()).unwrap();
unsafe { gpio_hold_en(self.general_fault.pin()) };
}
fn test(&mut self) -> anyhow::Result<()> {
self.general_fault(true);
self.esp.delay.delay_ms(100);
self.general_fault(false);
self.esp.delay.delay_ms(500);
self.light(true)?;
self.esp.delay.delay_ms(500);
self.light(false)?;
self.esp.delay.delay_ms(500);
for i in 0..PLANT_COUNT {
self.fault(i, true)?;
self.esp.delay.delay_ms(500);
self.fault(i, false)?;
self.esp.delay.delay_ms(500);
}
for i in 0..PLANT_COUNT {
self.pump(i, true)?;
self.esp.delay.delay_ms(100);
self.pump(i, false)?;
self.esp.delay.delay_ms(100);
}
for plant in 0..PLANT_COUNT {
let a = self.measure_moisture_hz(plant, Sensor::A);
let b = self.measure_moisture_hz(plant, Sensor::B);
let aa = match a {
OkStd(a) => a as u32,
Err(_) => u32::MAX,
};
let bb = match b {
OkStd(b) => b as u32,
Err(_) => u32::MAX,
};
log(LogMessage::TestSensor, aa, bb, &plant.to_string(), "");
}
self.esp.delay.delay_ms(10);
anyhow::Ok(())
}
fn set_config(&mut self, config: PlantControllerConfig) -> anyhow::Result<()> {
self.config = config;
self.esp.save_config(&self.config)?;
anyhow::Ok(())
}
fn get_mptt_voltage(&mut self) -> anyhow::Result<Voltage> {
self.charger.get_mptt_voltage()
}
fn get_mptt_current(&mut self) -> anyhow::Result<Current> {
self.charger.get_mptt_current()
}
}

115
rust/src/hal/v4_sensor.rs Normal file
View File

@@ -0,0 +1,115 @@
use embedded_hal_bus::i2c::MutexDevice;
use esp_idf_hal::can::CanDriver;
use esp_idf_hal::delay::Delay;
use esp_idf_hal::i2c::I2cDriver;
use esp_idf_hal::pcnt::PcntDriver;
use pca9535::{GPIOBank, Pca9535Immediate, StandardExpanderInterface};
use crate::hal::Sensor;
use crate::log::{log, LogMessage};
const REPEAT_MOIST_MEASURE: usize = 10;
pub trait SensorInteraction {
fn measure_moisture_hz(&mut self, plant: usize, sensor: Sensor) -> anyhow::Result<f32>;
}
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<'a> {
PulseCounter{
signal_counter: PcntDriver<'a>,
sensor_expander: Pca9535Immediate<MutexDevice<'a, I2cDriver<'a>>>,
},
CanBus{
can: CanDriver<'a>
}
}
impl SensorInteraction for SensorImpl<'_> {
fn measure_moisture_hz(&mut self, plant: usize, sensor: Sensor) -> anyhow::Result<f32> {
match self {
SensorImpl::PulseCounter { signal_counter, sensor_expander, .. } => {
let mut results = [0_f32; REPEAT_MOIST_MEASURE];
for repeat in 0..REPEAT_MOIST_MEASURE {
signal_counter.counter_pause()?;
signal_counter.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 delay = Delay::new_default();
let measurement = 100; // TODO what is this scaling factor? what is its purpose?
let factor = 1000f32 / measurement as f32;
//give some time to stabilize
delay.delay_ms(10);
signal_counter.counter_resume()?;
delay.delay_ms(measurement);
signal_counter.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)?;
delay.delay_ms(10);
let unscaled = signal_counter.get_counter_value()? as i32;
let hz = unscaled as f32 * factor;
log(
LogMessage::RawMeasure,
unscaled as u32,
hz as u32,
&plant.to_string(),
&format!("{sensor:?}"),
);
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];
anyhow::Ok(median)
}
SensorImpl::CanBus { .. } => {
todo!()
}
}
}
}

170
rust/src/hal/water.rs Normal file
View File

@@ -0,0 +1,170 @@
use crate::hal::TANK_MULTI_SAMPLE;
use anyhow::{anyhow, bail};
use ds18b20::Ds18b20;
use esp_idf_hal::adc::oneshot::config::AdcChannelConfig;
use esp_idf_hal::adc::oneshot::{AdcChannelDriver, AdcDriver};
use esp_idf_hal::adc::{attenuation, Resolution, ADC1};
use esp_idf_hal::delay::Delay;
use esp_idf_hal::gpio::{AnyIOPin, AnyInputPin, Gpio5, InputOutput, PinDriver, Pull};
use esp_idf_hal::pcnt::{PcntChannel, PcntChannelConfig, PcntControlMode, PcntCountMode, PcntDriver, PinIndex, PCNT1};
use esp_idf_sys::EspError;
use one_wire_bus::OneWire;
pub struct TankSensor<'a> {
one_wire_bus: OneWire<PinDriver<'a, AnyIOPin, InputOutput>>,
tank_channel: AdcChannelDriver<'a, Gpio5, AdcDriver<'a, ADC1>>,
tank_power: PinDriver<'a, AnyIOPin, InputOutput>,
flow_counter: PcntDriver<'a>,
delay: Delay,
}
impl<'a> TankSensor<'a> {
pub(crate) fn create(
one_wire_pin: AnyIOPin,
adc1: ADC1,
gpio5: Gpio5,
tank_power_pin: AnyIOPin,
flow_sensor_pin: AnyIOPin,
pcnt1: PCNT1
) -> anyhow::Result<TankSensor<'a>> {
let mut one_wire_pin =
PinDriver::input_output_od(one_wire_pin).expect("Failed to configure pin");
one_wire_pin
.set_pull(Pull::Floating)
.expect("Failed to set pull");
let adc_config = AdcChannelConfig {
attenuation: attenuation::DB_11,
resolution: Resolution::Resolution12Bit,
calibration: esp_idf_hal::adc::oneshot::config::Calibration::Curve,
};
let tank_driver = AdcDriver::new(adc1).expect("Failed to configure ADC");
let tank_channel = AdcChannelDriver::new(tank_driver, gpio5, &adc_config)
.expect("Failed to configure ADC channel");
let mut tank_power =
PinDriver::input_output(tank_power_pin).expect("Failed to configure pin");
tank_power
.set_pull(Pull::Floating)
.expect("Failed to set pull");
let one_wire_bus =
OneWire::new(one_wire_pin).expect("OneWire bus did not pull up after release");
let mut flow_counter = PcntDriver::new(
pcnt1,
Some(flow_sensor_pin),
Option::<AnyInputPin>::None,
Option::<AnyInputPin>::None,
Option::<AnyInputPin>::None,
)?;
flow_counter.channel_config(
PcntChannel::Channel1,
PinIndex::Pin0,
PinIndex::Pin1,
&PcntChannelConfig {
lctrl_mode: PcntControlMode::Keep,
hctrl_mode: PcntControlMode::Keep,
pos_mode: PcntCountMode::Increment,
neg_mode: PcntCountMode::Hold,
counter_h_lim: i16::MAX,
counter_l_lim: 0,
},
)?;
Ok(TankSensor {
one_wire_bus,
tank_channel,
tank_power,
flow_counter,
delay: Default::default(),
})
}
pub fn reset_flow_meter(&mut self) {
self.flow_counter.counter_pause().unwrap();
self.flow_counter.counter_clear().unwrap();
}
pub fn start_flow_meter(&mut self) {
self.flow_counter.counter_resume().unwrap();
}
pub fn get_flow_meter_value(&mut self) -> i16 {
self.flow_counter.get_counter_value().unwrap()
}
pub fn stop_flow_meter(&mut self) -> i16 {
self.flow_counter.counter_pause().unwrap();
self.get_flow_meter_value()
}
pub fn water_temperature_c(&mut self) -> anyhow::Result<f32> {
//multisample should be moved to water_temperature_c
let mut attempt = 1;
let water_temp: Result<f32, anyhow::Error> = loop {
let temp = self.single_temperature_c();
match &temp {
Ok(res) => {
println!("Water temp is {}", res);
break temp;
}
Err(err) => {
println!("Could not get water temp {} attempt {}", err, attempt)
}
}
if attempt == 5 {
break temp;
}
attempt += 1;
};
water_temp
}
fn single_temperature_c(&mut self) -> anyhow::Result<f32> {
self.one_wire_bus
.reset(&mut self.delay)
.map_err(|err| -> anyhow::Error { anyhow!("Missing attribute: {:?}", err) })?;
let first = self.one_wire_bus.devices(false, &mut self.delay).next();
if first.is_none() {
bail!("Not found any one wire Ds18b20");
}
let device_address = first
.unwrap()
.map_err(|err| -> anyhow::Error { anyhow!("Missing attribute: {:?}", err) })?;
let water_temp_sensor = Ds18b20::new::<EspError>(device_address)
.map_err(|err| -> anyhow::Error { anyhow!("Missing attribute: {:?}", err) })?;
water_temp_sensor
.start_temp_measurement(&mut self.one_wire_bus, &mut self.delay)
.map_err(|err| -> anyhow::Error { anyhow!("Missing attribute: {:?}", err) })?;
ds18b20::Resolution::Bits12.delay_for_measurement_time(&mut self.delay);
let sensor_data = water_temp_sensor
.read_data(&mut self.one_wire_bus, &mut self.delay)
.map_err(|err| -> anyhow::Error { anyhow!("Missing attribute: {:?}", err) })?;
if sensor_data.temperature == 85_f32 {
bail!("Ds18b20 dummy temperature returned");
}
anyhow::Ok(sensor_data.temperature / 10_f32)
}
pub fn tank_sensor_voltage(&mut self) -> anyhow::Result<f32> {
self.tank_power.set_high()?;
//let stabilize
self.delay.delay_ms(100);
let mut store = [0_u16; TANK_MULTI_SAMPLE];
for multisample in 0..TANK_MULTI_SAMPLE {
let value = self.tank_channel.read()?;
store[multisample] = value;
}
self.tank_power.set_low()?;
store.sort();
let median_mv = store[6] as f32 / 1000_f32;
anyhow::Ok(median_mv)
}
}

View File

@@ -65,7 +65,7 @@ pub fn get_log() -> Vec<LogEntry> {
read_copy.push(copy);
}
drop(buffer);
return read_copy;
read_copy
}
pub fn log(message_key: LogMessage, number_a: u32, number_b: u32, txt_short: &str, txt_long: &str) {
@@ -93,6 +93,7 @@ pub fn log(message_key: LogMessage, number_a: u32, number_b: u32, txt_short: &st
let serial_entry = template.fill_in(&values);
println!("{serial_entry}");
//TODO push to mqtt?
let entry = LogEntry {
timestamp: time,
@@ -193,6 +194,16 @@ pub enum LogMessage {
serialize = "Pumped multiple times, but plant is still to try attempt: ${number_a} limit :: ${number_b} plant: ${txt_short}"
)]
ConsecutivePumpCountLimit,
#[strum(
serialize = "Pump Overcurrent error, pump: ${number_a} tripped overcurrent ${number_b} limit was ${txt_short} @s ${txt_long}"
)]
PumpOverCurrent,
#[strum(
serialize = "Pump Open loop error, pump: ${number_a} is low, ${number_b} limit was ${txt_short} @s ${txt_long}"
)]
PumpOpenLoopCurrent,
#[strum(serialize = "Pump Open current sensor required but did not work: ${number_a}")]
PumpMissingSensorCurrent,
}
#[derive(Serialize)]

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

309
rust/src/plant_state.rs Normal file
View File

@@ -0,0 +1,309 @@
use crate::{
config::PlantConfig,
hal::{Sensor, HAL},
in_time_range,
};
use chrono::{DateTime, TimeDelta, Utc};
use chrono_tz::Tz;
use serde::{Deserialize, Serialize};
const MOIST_SENSOR_MAX_FREQUENCY: f32 = 7500.; // 60kHz (500Hz margin)
const MOIST_SENSOR_MIN_FREQUENCY: f32 = 150.; // this is really, really dry, think like cactus levels
#[derive(Debug, PartialEq, Serialize)]
pub enum MoistureSensorError {
ShortCircuit { hz: f32, max: f32 },
OpenLoop { hz: f32, min: f32 },
BoardError(String),
}
#[derive(Debug, PartialEq, Serialize)]
pub enum MoistureSensorState {
Disabled,
MoistureValue { raw_hz: f32, moisture_percent: f32 },
SensorError(MoistureSensorError),
}
impl MoistureSensorState {
pub fn is_err(&self) -> Option<&MoistureSensorError> {
match self {
MoistureSensorState::SensorError(moisture_sensor_error) => Some(moisture_sensor_error),
_ => None,
}
}
pub fn moisture_percent(&self) -> Option<f32> {
if let MoistureSensorState::MoistureValue {
raw_hz: _,
moisture_percent,
} = self
{
Some(*moisture_percent)
} else {
None
}
}
}
impl MoistureSensorState {}
#[derive(Debug, PartialEq, Serialize)]
pub enum PumpError {
PumpNotWorking {
failed_attempts: usize,
max_allowed_failures: usize,
},
}
#[derive(Debug, Serialize)]
pub struct PumpState {
consecutive_pump_count: u32,
previous_pump: Option<DateTime<Utc>>,
}
impl PumpState {
fn is_err(&self, plant_config: &PlantConfig) -> Option<PumpError> {
if self.consecutive_pump_count > plant_config.max_consecutive_pump_count as u32 {
Some(PumpError::PumpNotWorking {
failed_attempts: self.consecutive_pump_count as usize,
max_allowed_failures: plant_config.max_consecutive_pump_count as usize,
})
} else {
None
}
}
}
#[derive(Serialize, Deserialize, Clone, Copy, Debug, PartialEq)]
pub enum PlantWateringMode {
OFF,
TargetMoisture,
TimerOnly,
}
pub struct PlantState {
pub sensor_a: MoistureSensorState,
pub sensor_b: MoistureSensorState,
pub pump: PumpState,
}
fn map_range_moisture(
s: f32,
min_frequency: Option<f32>,
max_frequency: Option<f32>,
) -> Result<f32, MoistureSensorError> {
// Use overrides if provided, otherwise fallback to defaults
let min_freq = min_frequency.unwrap_or(MOIST_SENSOR_MIN_FREQUENCY);
let max_freq = max_frequency.unwrap_or(MOIST_SENSOR_MAX_FREQUENCY);
if s < min_freq {
return Err(MoistureSensorError::OpenLoop {
hz: s,
min: min_freq,
});
}
if s > max_freq {
return Err(MoistureSensorError::ShortCircuit {
hz: s,
max: max_freq,
});
}
let moisture_percent = (s - min_freq) * 100.0 / (max_freq - min_freq);
Ok(moisture_percent)
}
impl PlantState {
pub fn read_hardware_state(plant_id: usize, board: &mut HAL) -> Self {
let sensor_a = if board.board_hal.get_config().plants[plant_id].sensor_a {
match board.board_hal.measure_moisture_hz(plant_id, Sensor::A) {
Ok(raw) => match map_range_moisture(
raw,
board.board_hal.get_config().plants[plant_id].moisture_sensor_min_frequency,
board.board_hal.get_config().plants[plant_id].moisture_sensor_max_frequency,
) {
Ok(moisture_percent) => MoistureSensorState::MoistureValue {
raw_hz: raw,
moisture_percent,
},
Err(err) => MoistureSensorState::SensorError(err),
},
Err(err) => MoistureSensorState::SensorError(MoistureSensorError::BoardError(
err.to_string(),
)),
}
} else {
MoistureSensorState::Disabled
};
let sensor_b = if board.board_hal.get_config().plants[plant_id].sensor_b {
match board.board_hal.measure_moisture_hz(plant_id, Sensor::B) {
Ok(raw) => match map_range_moisture(
raw,
board.board_hal.get_config().plants[plant_id].moisture_sensor_min_frequency,
board.board_hal.get_config().plants[plant_id].moisture_sensor_max_frequency,
) {
Ok(moisture_percent) => MoistureSensorState::MoistureValue {
raw_hz: raw,
moisture_percent,
},
Err(err) => MoistureSensorState::SensorError(err),
},
Err(err) => MoistureSensorState::SensorError(MoistureSensorError::BoardError(
err.to_string(),
)),
}
} else {
MoistureSensorState::Disabled
};
let previous_pump = board.board_hal.get_esp().last_pump_time(plant_id);
let consecutive_pump_count = board.board_hal.get_esp().consecutive_pump_count(plant_id);
let state = Self {
sensor_a,
sensor_b,
pump: PumpState {
consecutive_pump_count,
previous_pump,
},
};
if state.is_err() {
let _ = board.board_hal.fault(plant_id, true);
}
state
}
pub fn pump_in_timeout(&self, plant_conf: &PlantConfig, current_time: &DateTime<Tz>) -> bool {
if matches!(plant_conf.mode, PlantWateringMode::OFF) {
return false;
}
self.pump.previous_pump.is_some_and(|last_pump| {
last_pump
.checked_add_signed(TimeDelta::minutes(plant_conf.pump_cooldown_min.into()))
.is_some_and(|earliest_next_allowed_pump| {
earliest_next_allowed_pump > *current_time
})
})
}
pub fn is_err(&self) -> bool {
self.sensor_a.is_err().is_some() || self.sensor_b.is_err().is_some()
}
pub fn plant_moisture(
&self,
) -> (
Option<f32>,
(Option<&MoistureSensorError>, Option<&MoistureSensorError>),
) {
match (
self.sensor_a.moisture_percent(),
self.sensor_b.moisture_percent(),
) {
(Some(moisture_a), Some(moisture_b)) => {
(Some((moisture_a + moisture_b) / 2.), (None, None))
}
(Some(moisture_percent), _) => (Some(moisture_percent), (None, self.sensor_b.is_err())),
(_, Some(moisture_percent)) => (Some(moisture_percent), (self.sensor_a.is_err(), None)),
_ => (None, (self.sensor_a.is_err(), self.sensor_b.is_err())),
}
}
pub fn needs_to_be_watered(
&self,
plant_conf: &PlantConfig,
current_time: &DateTime<Tz>,
) -> bool {
match plant_conf.mode {
PlantWateringMode::OFF => false,
PlantWateringMode::TargetMoisture => {
let (moisture_percent, _) = self.plant_moisture();
if let Some(moisture_percent) = moisture_percent {
if self.pump_in_timeout(plant_conf, current_time) {
false
} else if moisture_percent < plant_conf.target_moisture {
in_time_range(
current_time,
plant_conf.pump_hour_start,
plant_conf.pump_hour_end,
)
} else {
false
}
} else {
// in case no moisture can be determined, do not water the plant
false
}
}
PlantWateringMode::TimerOnly => !self.pump_in_timeout(plant_conf, current_time),
}
}
pub fn to_mqtt_info(
&self,
plant_conf: &PlantConfig,
current_time: &DateTime<Tz>,
) -> PlantInfo<'_> {
PlantInfo {
sensor_a: &self.sensor_a,
sensor_b: &self.sensor_b,
mode: plant_conf.mode,
do_water: self.needs_to_be_watered(plant_conf, current_time),
dry: if let Some(moisture_percent) = self.plant_moisture().0 {
moisture_percent < plant_conf.target_moisture
} else {
false
},
cooldown: self.pump_in_timeout(plant_conf, current_time),
out_of_work_hour: in_time_range(
current_time,
plant_conf.pump_hour_start,
plant_conf.pump_hour_end,
),
consecutive_pump_count: self.pump.consecutive_pump_count,
pump_error: self.pump.is_err(plant_conf),
last_pump: self
.pump
.previous_pump
.map(|t| t.with_timezone(&current_time.timezone())),
next_pump: if matches!(
plant_conf.mode,
PlantWateringMode::TimerOnly | PlantWateringMode::TargetMoisture
) {
self.pump.previous_pump.and_then(|last_pump| {
last_pump
.checked_add_signed(TimeDelta::minutes(plant_conf.pump_cooldown_min.into()))
.map(|t| t.with_timezone(&current_time.timezone()))
})
} else {
None
},
}
}
}
#[derive(Debug, PartialEq, Serialize)]
/// State of a single plant to be tracked
pub struct PlantInfo<'a> {
/// state of humidity sensor on bank a
sensor_a: &'a MoistureSensorState,
/// state of humidity sensor on bank b
sensor_b: &'a MoistureSensorState,
/// configured plant watering mode
mode: PlantWateringMode,
/// the plant needs to be watered
do_water: bool,
/// plant is considered to be dry according to settings
dry: bool,
/// plant irrigation cooldown is active
cooldown: bool,
/// plant should not be watered at this time of day TODO: does this really belong here? Isn't this a global setting?
out_of_work_hour: bool,
/// how often has the pump been watered without reaching target moisture
consecutive_pump_count: u32,
pump_error: Option<PumpError>,
/// last time when the pump was active
last_pump: Option<DateTime<Tz>>,
/// next time when pump should activate
next_pump: Option<DateTime<Tz>>,
}

View File

@@ -100,7 +100,7 @@ macro_rules! ShiftRegisterBuilder {
}
/// Get embedded-hal output pins to control the shift register outputs
pub fn decompose(&self) -> [ShiftRegisterPin; $size] {
pub fn decompose(&self) -> [ShiftRegisterPin<'_>; $size] {
// Create an uninitialized array of `MaybeUninit`. The `assume_init` is
// safe because the type we are claiming to have initialized here is a
// bunch of `MaybeUninit`s, which do not require initialization.

View File

@@ -1,10 +1,7 @@
use crate::{config::TankConfig, hal::HAL};
use anyhow::Context;
use serde::Serialize;
use crate::{
config::{PlantControllerConfig, TankConfig},
plant_hal::PlantCtrlBoard,
};
const OPEN_TANK_VOLTAGE: f32 = 3.0;
pub const WATER_FROZEN_THRESH: f32 = 4.0;
@@ -17,12 +14,12 @@ pub enum TankError {
}
pub enum TankState {
TankSensorPresent(f32),
TankSensorError(TankError),
TankSensorDisabled,
Present(f32),
Error(TankError),
Disabled,
}
fn raw_volatge_to_divider_percent(raw_value_mv: f32) -> Result<f32, TankError> {
fn raw_voltage_to_divider_percent(raw_value_mv: f32) -> Result<f32, TankError> {
if raw_value_mv > OPEN_TANK_VOLTAGE {
return Err(TankError::SensorMissing(raw_value_mv));
}
@@ -37,7 +34,7 @@ fn raw_voltage_to_tank_fill_percent(
raw_value_mv: f32,
config: &TankConfig,
) -> Result<f32, TankError> {
let divider_percent = raw_volatge_to_divider_percent(raw_value_mv)?;
let divider_percent = raw_voltage_to_divider_percent(raw_value_mv)?;
if divider_percent < config.tank_empty_percent.into()
|| divider_percent > config.tank_full_percent.into()
{
@@ -56,9 +53,9 @@ fn raw_voltage_to_tank_fill_percent(
impl TankState {
pub fn left_ml(&self, config: &TankConfig) -> Result<f32, TankError> {
match self {
TankState::TankSensorDisabled => Err(TankError::SensorDisabled),
TankState::TankSensorError(err) => Err(err.clone()),
TankState::TankSensorPresent(raw_value_mv) => {
TankState::Disabled => Err(TankError::SensorDisabled),
TankState::Error(err) => Err(err.clone()),
TankState::Present(raw_value_mv) => {
let tank_fill_percent = raw_voltage_to_tank_fill_percent(*raw_value_mv, config)?;
Ok(config.tank_useable_ml as f32 * tank_fill_percent / 100.)
}
@@ -66,9 +63,9 @@ impl TankState {
}
pub fn enough_water(&self, config: &TankConfig) -> Result<bool, TankError> {
match self {
TankState::TankSensorDisabled => Err(TankError::SensorDisabled),
TankState::TankSensorError(err) => Err(err.clone()),
TankState::TankSensorPresent(raw_value_mv) => {
TankState::Disabled => Err(TankError::SensorDisabled),
TankState::Error(err) => Err(err.clone()),
TankState::Present(raw_value_mv) => {
let tank_fill_percent = raw_voltage_to_tank_fill_percent(*raw_value_mv, config)?;
if tank_fill_percent > config.tank_empty_percent.into() {
Ok(true)
@@ -80,14 +77,14 @@ impl TankState {
}
pub fn is_enabled(&self) -> bool {
! matches!(self, TankState::TankSensorDisabled)
!matches!(self, TankState::Disabled)
}
pub fn warn_level(&self, config: &TankConfig) -> Result<bool, TankError> {
match self {
TankState::TankSensorDisabled => Err(TankError::SensorDisabled),
TankState::TankSensorError(err) => Err(err.clone()),
TankState::TankSensorPresent(raw_value_mv) => {
TankState::Disabled => Err(TankError::SensorDisabled),
TankState::Error(err) => Err(err.clone()),
TankState::Present(raw_value_mv) => {
let tank_fill_percent = raw_voltage_to_tank_fill_percent(*raw_value_mv, config);
match tank_fill_percent {
Ok(value) => {
@@ -108,19 +105,15 @@ impl TankState {
pub fn got_error(&self, config: &TankConfig) -> Option<TankError> {
match self {
TankState::TankSensorPresent(raw_value_mv) => {
TankState::Present(raw_value_mv) => {
raw_voltage_to_tank_fill_percent(*raw_value_mv, config).err()
}
TankState::TankSensorError(err) => Some(err.clone()),
TankState::TankSensorDisabled => Some(TankError::SensorDisabled),
TankState::Error(err) => Some(err.clone()),
TankState::Disabled => Some(TankError::SensorDisabled),
}
}
pub fn as_mqtt_info(
&self,
config: &TankConfig,
water_temp: Result<f32, anyhow::Error>,
) -> TankInfo {
pub fn as_mqtt_info(&self, config: &TankConfig, water_temp: &anyhow::Result<f32>) -> TankInfo {
let mut tank_err: Option<TankError> = None;
let left_ml = match self.left_ml(config) {
Err(err) => {
@@ -130,10 +123,10 @@ impl TankState {
Ok(left_ml) => Some(left_ml),
};
let enough_water = self.enough_water(config).unwrap_or(false); //NOTE: is this correct if there is an error assume not enough water?
let warn_level = self.warn_level(config).unwrap_or(false); //NOTE: should no warn level be triggered if there is an error?
let warn_level = self.warn_level(config).unwrap_or(false); //NOTE: should warn level be triggered if there is an error?
let raw = match self {
TankState::TankSensorDisabled | TankState::TankSensorError(_) => None,
TankState::TankSensorPresent(raw_value_mv) => Some(*raw_value_mv),
TankState::Disabled | TankState::Error(_) => None,
TankState::Present(raw_value_mv) => Some(*raw_value_mv),
};
let percent = match raw {
@@ -151,42 +144,44 @@ impl TankState {
.as_ref()
.is_ok_and(|temp| *temp < WATER_FROZEN_THRESH),
water_temp: water_temp.as_ref().copied().ok(),
temp_sensor_error: water_temp.err().map(|err| err.to_string()),
temp_sensor_error: water_temp.as_ref().err().map(|err| err.to_string()),
percent,
}
}
}
pub fn determine_tank_state(
board: &mut std::sync::MutexGuard<'_, PlantCtrlBoard<'_>>,
config: &PlantControllerConfig,
) -> TankState {
if config.tank.tank_sensor_enabled {
match board.tank_sensor_voltage() {
Ok(raw_sensor_value_mv) => TankState::TankSensorPresent(raw_sensor_value_mv),
Err(err) => TankState::TankSensorError(TankError::BoardError(err.to_string())),
pub fn determine_tank_state(board: &mut std::sync::MutexGuard<'_, HAL<'_>>) -> TankState {
if board.board_hal.get_config().tank.tank_sensor_enabled {
match board
.board_hal
.get_tank_sensor()
.context("no sensor")
.and_then(|f| f.tank_sensor_voltage())
{
Ok(raw_sensor_value_mv) => TankState::Present(raw_sensor_value_mv),
Err(err) => TankState::Error(TankError::BoardError(err.to_string())),
}
} else {
TankState::TankSensorDisabled
TankState::Disabled
}
}
#[derive(Debug, Serialize)]
/// Information structure send to mqtt for monitoring purposes
pub struct TankInfo {
/// is there enough water in the tank
/// there is enough water in the tank
enough_water: bool,
/// warning that water needs to be refilled soon
warn_level: bool,
/// estimation how many ml are still in tank
/// estimation how many ml are still in the tank
left_ml: Option<f32>,
/// if there is was an issue with the water level sensor
/// if there is an issue with the water level sensor
sensor_error: Option<TankError>,
/// raw water sensor value
raw: Option<f32>,
/// percent value
percent: Option<f32>,
/// water in tank might be frozen
/// water in the tank might be frozen
water_frozen: bool,
/// water temperature
water_temp: Option<f32>,

View File

@@ -1,14 +1,17 @@
//offer ota and config mode
use crate::{
determine_tank_state, get_version, log::LogMessage, map_range_moisture, plant_hal::PLANT_COUNT,
config::PlantControllerConfig,
determine_tank_state, do_secure_pump, get_version,
hal::PLANT_COUNT,
log::LogMessage,
plant_state::{MoistureSensorState, PlantState},
BOARD_ACCESS,
};
use anyhow::bail;
use anyhow::{bail, Context};
use chrono::DateTime;
use core::result::Result::Ok;
use embedded_svc::http::Method;
use esp_idf_hal::delay::Delay;
use esp_idf_svc::http::server::{Configuration, EspHttpConnection, EspHttpServer, Request};
use esp_idf_sys::{settimeofday, timeval, vTaskDelay};
use esp_ota::OtaUpdate;
@@ -20,8 +23,6 @@ use std::{
};
use url::Url;
use crate::config::PlantControllerConfig;
#[derive(Serialize, Debug)]
struct SSIDList<'a> {
ssids: Vec<&'a String<32>>,
@@ -35,8 +36,15 @@ struct LoadData<'a> {
#[derive(Serialize, Debug)]
struct Moistures {
moisture_a: Vec<u8>,
moisture_b: Vec<u8>,
moisture_a: Vec<std::string::String>,
moisture_b: Vec<std::string::String>,
}
#[derive(Serialize, Debug)]
struct SolarState {
mppt_voltage: f32,
mppt_current: f32,
is_day: bool,
}
#[derive(Deserialize, Debug)]
@@ -52,7 +60,7 @@ pub struct TestPump {
#[derive(Serialize, Deserialize, PartialEq, Debug)]
pub struct WebBackupHeader {
timestamp: std::string::String,
size: usize,
size: u16,
}
#[derive(Deserialize)]
@@ -66,69 +74,35 @@ fn write_time(
let actual_data = read_up_to_bytes_from_request(request, None)?;
let time: SetTime = serde_json::from_slice(&actual_data)?;
let parsed = DateTime::parse_from_rfc3339(time.time).map_err(|err| anyhow::anyhow!(err))?;
let mut board = BOARD_ACCESS.lock().unwrap();
let mut board = BOARD_ACCESS.lock().expect("board access");
let now = timeval {
tv_sec: parsed.to_utc().timestamp(),
tv_usec: 0,
};
unsafe { settimeofday(&now, core::ptr::null_mut()) };
board.set_rtc_time(&parsed.to_utc())?;
board
.board_hal
.get_rtc_module()
.set_rtc_time(&parsed.to_utc())?;
anyhow::Ok(None)
}
fn get_live_moisture(
fn get_time(
_request: &mut Request<&mut EspHttpConnection>,
) -> Result<Option<std::string::String>, anyhow::Error> {
let mut board = BOARD_ACCESS.lock().unwrap();
let mut a: Vec<u8> = Vec::new();
let mut b: Vec<u8> = Vec::new();
for plant in 0..8 {
let a_hz = board.measure_moisture_hz(plant, crate::plant_hal::Sensor::A)?;
let b_hz = board.measure_moisture_hz(plant, crate::plant_hal::Sensor::B)?;
let a_pct = map_range_moisture(a_hz as f32);
match a_pct {
Ok(result) => {
a.push(result);
}
Err(_) => {
a.push(200);
}
}
let b_pct = map_range_moisture(b_hz as f32);
match b_pct {
Ok(result) => {
b.push(result);
}
Err(_) => {
b.push(200);
}
}
}
let data = Moistures {
moisture_a: a,
moisture_b: b,
};
let json = serde_json::to_string(&data)?;
anyhow::Ok(Some(json))
}
fn get_data(
_request: &mut Request<&mut EspHttpConnection>,
) -> Result<Option<std::string::String>, anyhow::Error> {
let mut board = BOARD_ACCESS.lock().unwrap();
let mut board = BOARD_ACCESS.lock().expect("board access");
let native = board
.board_hal
.get_esp()
.time()
.and_then(|t| Ok(t.to_rfc3339()))
.map(|t| t.to_rfc3339())
.unwrap_or("error".to_string());
let rtc = board
.board_hal
.get_rtc_module()
.get_rtc_time()
.and_then(|t| Ok(t.to_rfc3339()))
.map(|t| t.to_rfc3339())
.unwrap_or("error".to_string());
let data = LoadData {
@@ -140,14 +114,58 @@ fn get_data(
anyhow::Ok(Some(json))
}
fn get_timezones(
_request: &mut Request<&mut EspHttpConnection>,
) -> Result<Option<std::string::String>, anyhow::Error> {
// Get all timezones using chrono-tz
let timezones: Vec<&'static str> = chrono_tz::TZ_VARIANTS.iter().map(|tz| tz.name()).collect();
// Convert to JSON
let json = serde_json::to_string(&timezones)?;
anyhow::Ok(Some(json))
}
fn get_live_moisture(
_request: &mut Request<&mut EspHttpConnection>,
) -> Result<Option<std::string::String>, anyhow::Error> {
let mut board = BOARD_ACCESS.lock().expect("Should never fail");
let plant_state =
Vec::from_iter((0..PLANT_COUNT).map(|i| PlantState::read_hardware_state(i, &mut board)));
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)?;
anyhow::Ok(Some(json))
}
fn get_config(
_request: &mut Request<&mut EspHttpConnection>,
) -> Result<Option<std::string::String>, anyhow::Error> {
let mut board = BOARD_ACCESS.lock().unwrap();
let json = match board.get_config() {
Ok(config) => serde_json::to_string(&config)?,
Err(_) => serde_json::to_string(&PlantControllerConfig::default())?,
};
let mut board = BOARD_ACCESS.lock().expect("Should never fail");
let json = serde_json::to_string(&board.board_hal.get_config())?;
anyhow::Ok(Some(json))
}
@@ -155,17 +173,19 @@ fn backup_config(
request: &mut Request<&mut EspHttpConnection>,
) -> Result<Option<std::string::String>, anyhow::Error> {
let all = read_up_to_bytes_from_request(request, Some(3072))?;
let mut board = BOARD_ACCESS.lock().unwrap();
board.backup_config(&all)?;
let mut board = BOARD_ACCESS.lock().expect("board access");
//TODO how to handle progress here? prior versions animated the fault leds while running
board.board_hal.get_rtc_module().backup_config(&all)?;
anyhow::Ok(Some("saved".to_owned()))
}
fn get_backup_config(
_request: &mut Request<&mut EspHttpConnection>,
) -> Result<Option<std::string::String>, anyhow::Error> {
let mut board = BOARD_ACCESS.lock().unwrap();
let json = match board.get_backup_config() {
Ok(config) => std::str::from_utf8(&config)?.to_owned(),
let mut board = BOARD_ACCESS.lock().expect("board access");
let json = match board.board_hal.get_rtc_module().get_backup_config() {
Ok(config) => from_utf8(&config)?.to_owned(),
Err(err) => {
println!("Error get backup config {:?}", err);
err.to_string()
@@ -177,8 +197,8 @@ fn get_backup_config(
fn backup_info(
_request: &mut Request<&mut EspHttpConnection>,
) -> Result<Option<std::string::String>, anyhow::Error> {
let mut board = BOARD_ACCESS.lock().unwrap();
let header = board.get_backup_info();
let mut board = BOARD_ACCESS.lock().expect("Should never fail");
let header = board.board_hal.get_rtc_module().get_backup_info();
let json = match header {
Ok(h) => {
let timestamp = DateTime::from_timestamp_millis(h.timestamp).unwrap();
@@ -188,10 +208,9 @@ fn backup_info(
};
serde_json::to_string(&wbh)?
}
Err(_) => {
//TODO make better
Err(err) => {
let wbh = WebBackupHeader {
timestamp: "no backup".to_owned(),
timestamp: err.to_string(),
size: 0,
};
serde_json::to_string(&wbh)?
@@ -203,20 +222,32 @@ fn backup_info(
fn set_config(
request: &mut Request<&mut EspHttpConnection>,
) -> Result<Option<std::string::String>, anyhow::Error> {
let all = read_up_to_bytes_from_request(request, Some(3072))?;
let all = read_up_to_bytes_from_request(request, Some(4096))?;
let config: PlantControllerConfig = serde_json::from_slice(&all)?;
let mut board = BOARD_ACCESS.lock().unwrap();
board.set_config(&config)?;
let mut board = BOARD_ACCESS.lock().expect("board access");
board.board_hal.set_config(config)?;
anyhow::Ok(Some("saved".to_owned()))
}
fn get_solar_state(
_request: &mut Request<&mut EspHttpConnection>,
) -> Result<Option<std::string::String>, anyhow::Error> {
let mut board = BOARD_ACCESS.lock().expect("board access");
let state = SolarState {
mppt_voltage: board.board_hal.get_mptt_voltage()?.as_millivolts() as f32,
mppt_current: board.board_hal.get_mptt_current()?.as_milliamperes() as f32,
is_day: board.board_hal.is_day(),
};
anyhow::Ok(Some(serde_json::to_string(&state)?))
}
fn get_battery_state(
_request: &mut Request<&mut EspHttpConnection>,
) -> Result<Option<std::string::String>, anyhow::Error> {
let mut board = BOARD_ACCESS.lock().unwrap();
let battery_state = board.get_battery_state();
let battery_json = serde_json::to_string(&battery_state)?;
anyhow::Ok(Some(battery_json))
let mut board = BOARD_ACCESS.lock().expect("board access");
let battery_state = board.board_hal.get_battery_monitor().get_battery_state();
anyhow::Ok(Some(serde_json::to_string(&battery_state)?))
}
fn get_log(
@@ -244,21 +275,29 @@ fn pump_test(
let actual_data = read_up_to_bytes_from_request(request, None)?;
let pump_test: TestPump = serde_json::from_slice(&actual_data)?;
let mut board = BOARD_ACCESS.lock().unwrap();
board.test_pump(pump_test.pump)?;
anyhow::Ok(None)
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)?;
board.board_hal.pump(pump_test.pump, false)?;
anyhow::Ok(Some(serde_json::to_string(&pump_result)?))
}
fn tank_info(
_request: &mut Request<&mut EspHttpConnection>,
) -> Result<Option<std::string::String>, anyhow::Error> {
let mut board = BOARD_ACCESS.lock().unwrap();
let config = board.get_config()?;
let tank_info = determine_tank_state(&mut board, &config);
let tank_info = determine_tank_state(&mut board);
//should be multsampled
let water_temp = board.water_temperature_c();
Ok(Some(serde_json::to_string(
&tank_info.as_mqtt_info(&config.tank, water_temp),
)?))
let water_temp = board
.board_hal
.get_tank_sensor()
.context("no sensor")
.and_then(|f| f.water_temperature_c());
Ok(Some(serde_json::to_string(&tank_info.as_mqtt_info(
&board.board_hal.get_config().tank,
&water_temp,
))?))
}
fn night_lamp_test(
@@ -267,7 +306,7 @@ fn night_lamp_test(
let actual_data = read_up_to_bytes_from_request(request, None)?;
let light_command: NightLampCommand = serde_json::from_slice(&actual_data)?;
let mut board = BOARD_ACCESS.lock().unwrap();
board.light(light_command.active)?;
board.board_hal.light(light_command.active)?;
anyhow::Ok(None)
}
@@ -275,7 +314,7 @@ fn wifi_scan(
_request: &mut Request<&mut EspHttpConnection>,
) -> Result<Option<std::string::String>, anyhow::Error> {
let mut board = BOARD_ACCESS.lock().unwrap();
let scan_result = board.wifi_scan()?;
let scan_result = board.board_hal.get_esp().wifi_scan()?;
let mut ssids: Vec<&String<32>> = Vec::new();
scan_result.iter().for_each(|s| ssids.push(&s.ssid));
let ssid_json = serde_json::to_string(&SSIDList { ssids })?;
@@ -286,10 +325,12 @@ fn wifi_scan(
fn list_files(
_request: &mut Request<&mut EspHttpConnection>,
) -> Result<Option<std::string::String>, anyhow::Error> {
let board = BOARD_ACCESS.lock().unwrap();
let result = board.list_files();
let mut board = BOARD_ACCESS
.lock()
.expect("It should be possible to lock the board for exclusive fs access");
let result = board.board_hal.get_esp().list_files();
let file_list_json = serde_json::to_string(&result)?;
return anyhow::Ok(Some(file_list_json));
anyhow::Ok(Some(file_list_json))
}
fn ota(
@@ -309,12 +350,13 @@ fn ota(
total_read += read;
let to_write = &buffer[0..read];
//delay for watchdog and wifi stuff
Delay::new_default().delay_ms(1);
board.board_hal.get_esp().delay.delay_ms(1);
let iter = (total_read / 1024) % 8;
if iter != lastiter {
board.board_hal.general_fault(iter % 5 == 0);
for i in 0..PLANT_COUNT {
board.fault(i, iter == i);
let _ = board.board_hal.fault(i, iter == i);
}
lastiter = iter;
}
@@ -331,60 +373,19 @@ fn ota(
let mut finalizer = ota.finalize()?;
println!("changing boot partition");
board.set_restart_to_conf(true);
board.board_hal.get_esp().set_restart_to_conf(true);
drop(board);
finalizer.set_as_boot_partition()?;
return anyhow::Ok(None);
}
fn flash_bq(filename: &str, dryrun: bool) -> anyhow::Result<()> {
let mut board = BOARD_ACCESS.lock().unwrap();
let mut toggle = true;
let delay = Delay::new(1);
let file_handle = board.get_file_handle(filename, false)?;
let mut reader = std::io::BufRead::lines(std::io::BufReader::with_capacity(512, file_handle));
let mut line = 0;
loop {
board.general_fault(toggle);
toggle = !toggle;
delay.delay_us(2);
line += 1;
match reader.next() {
Some(next) => {
let input = next?;
println!("flashing bq34z100 dryrun:{dryrun} line {line} payload: {input}");
match board.flash_bq34_z100(&input, dryrun) {
Ok(_) => {
println!("ok")
}
Err(err) => {
bail!(
"Error flashing bq34z100 in dryrun: {dryrun} line: {line} error: {err}"
)
}
}
}
None => break,
}
}
println!("Finished flashing file {line} lines processed");
board.general_fault(false);
return anyhow::Ok(());
anyhow::Ok(None)
}
fn query_param(uri: &str, param_name: &str) -> Option<std::string::String> {
println!("{uri} get {param_name}");
let parsed = Url::parse(&format!("http://127.0.0.1/{uri}")).unwrap();
let value = parsed.query_pairs().filter(|it| it.0 == param_name).next();
let value = parsed.query_pairs().find(|it| it.0 == param_name);
match value {
Some(found) => {
return Some(found.1.into_owned());
}
None => return None,
Some(found) => Some(found.1.into_owned()),
None => None,
}
}
@@ -415,9 +416,14 @@ pub fn httpd(reboot_now: Arc<AtomicBool>) -> Box<EspHttpServer<'static>> {
handle_error_to500(request, get_battery_state)
})
.unwrap();
server
.fn_handler("/solar", Method::Get, |request| {
handle_error_to500(request, get_solar_state)
})
.unwrap();
server
.fn_handler("/time", Method::Get, |request| {
handle_error_to500(request, get_data)
handle_error_to500(request, get_time)
})
.unwrap();
server
@@ -447,7 +453,7 @@ pub fn httpd(reboot_now: Arc<AtomicBool>) -> Box<EspHttpServer<'static>> {
.unwrap();
server
.fn_handler("/boardtest", Method::Post, move |_| {
BOARD_ACCESS.lock().unwrap().test()
BOARD_ACCESS.lock().unwrap().board_hal.test()
})
.unwrap();
server
@@ -499,7 +505,12 @@ pub fn httpd(reboot_now: Arc<AtomicBool>) -> Box<EspHttpServer<'static>> {
let reboot_now_for_reboot = reboot_now.clone();
server
.fn_handler("/reboot", Method::Post, move |_| {
BOARD_ACCESS.lock().unwrap().set_restart_to_conf(true);
BOARD_ACCESS
.lock()
.unwrap()
.board_hal
.get_esp()
.set_restart_to_conf(true);
reboot_now_for_reboot.store(true, std::sync::atomic::Ordering::Relaxed);
anyhow::Ok(())
})
@@ -520,6 +531,8 @@ pub fn httpd(reboot_now: Arc<AtomicBool>) -> Box<EspHttpServer<'static>> {
let file_handle = BOARD_ACCESS
.lock()
.unwrap()
.board_hal
.get_esp()
.get_file_handle(&filename, false);
match file_handle {
Ok(mut file_handle) => {
@@ -555,8 +568,8 @@ pub fn httpd(reboot_now: Arc<AtomicBool>) -> Box<EspHttpServer<'static>> {
server
.fn_handler("/file", Method::Post, move |mut request| {
let filename = query_param(request.uri(), "filename").unwrap();
let lock = BOARD_ACCESS.lock().unwrap();
let file_handle = lock.get_file_handle(&filename, true);
let mut board = BOARD_ACCESS.lock().unwrap();
let file_handle = board.board_hal.get_esp().get_file_handle(&filename, true);
match file_handle {
//TODO get free filesystem size, check against during write if not to large
Ok(mut file_handle) => {
@@ -568,7 +581,7 @@ pub fn httpd(reboot_now: Arc<AtomicBool>) -> Box<EspHttpServer<'static>> {
let iter = (total_read / 1024) % 8;
if iter != lastiter {
for i in 0..PLANT_COUNT {
lock.fault(i, iter == i);
let _ = board.board_hal.fault(i, iter == i);
}
lastiter = iter;
}
@@ -590,7 +603,7 @@ pub fn httpd(reboot_now: Arc<AtomicBool>) -> Box<EspHttpServer<'static>> {
cors_response(request, 500, &error_text)?;
}
}
drop(lock);
drop(board);
anyhow::Ok(())
})
.unwrap();
@@ -599,8 +612,8 @@ pub fn httpd(reboot_now: Arc<AtomicBool>) -> Box<EspHttpServer<'static>> {
.fn_handler("/file", Method::Delete, move |request| {
let filename = query_param(request.uri(), "filename").unwrap();
let copy = filename.clone();
let board = BOARD_ACCESS.lock().unwrap();
match board.delete_file(&filename) {
let mut board = BOARD_ACCESS.lock().unwrap();
match board.board_hal.get_esp().delete_file(&filename) {
Ok(_) => {
let info = format!("Deleted file {copy}");
cors_response(request, 200, &info)?;
@@ -618,35 +631,6 @@ pub fn httpd(reboot_now: Arc<AtomicBool>) -> Box<EspHttpServer<'static>> {
cors_response(request, 200, "")
})
.unwrap();
server
.fn_handler("/flashbattery", Method::Post, move |request| {
let filename = query_param(request.uri(),"filename").unwrap();
let dryrun = true;
match flash_bq(&filename, false) {
Ok(_) => {
if !dryrun {
match flash_bq(&filename, true) {
Ok(_) => {
cors_response(request, 200, "Sucessfully flashed bq34z100")?;
},
Err(err) => {
let info = format!("Could not flash bq34z100, could be bricked now! {filename} {err:?}");
cors_response(request, 500, &info)?;
},
}
} else {
cors_response(request, 200, "Sucessfully processed bq34z100")?;
}
},
Err(err) => {
let info = format!("Could not process firmware file for, bq34z100, refusing to flash! {filename} {err:?}");
cors_response(request, 500, &info)?;
},
};
anyhow::Ok(())
})
.unwrap();
unsafe { vTaskDelay(1) };
server
.fn_handler("/", Method::Get, move |request| {
@@ -672,6 +656,11 @@ pub fn httpd(reboot_now: Arc<AtomicBool>) -> Box<EspHttpServer<'static>> {
})
.unwrap();
server
.fn_handler("/timezones", Method::Get, move |request| {
handle_error_to500(request, get_timezones)
})
.unwrap();
server
}
fn cors_response(
@@ -687,7 +676,7 @@ fn cors_response(
let mut response = request.into_response(status, None, &headers)?;
response.write(body.as_bytes())?;
response.flush()?;
return anyhow::Ok(());
anyhow::Ok(())
}
type AnyhowHandler =
@@ -712,7 +701,7 @@ fn handle_error_to500(
cors_response(request, 500, &error_text)?;
}
}
return anyhow::Ok(());
anyhow::Ok(())
}
fn read_up_to_bytes_from_request(

View File

@@ -1,143 +1,189 @@
interface LogArray extends Array<LogEntry>{}
interface LogEntry {
timestamp: string,
message_id: number,
a: number,
b: number,
txt_short: string,
txt_long: string
export interface LogArray extends Array<LogEntry> {
}
interface LogLocalisation extends Array<LogLocalisationEntry>{}
interface LogLocalisationEntry {
msg_type: string,
message: string
export interface LogEntry {
timestamp: string,
message_id: number,
a: number,
b: number,
txt_short: string,
txt_long: string
}
interface BackupHeader {
timestamp: string,
size: number
export interface LogLocalisation extends Array<LogLocalisationEntry> {
}
interface NetworkConfig {
ap_ssid: string,
ssid: string,
password: string,
mqtt_url: string,
base_topic: string
export interface LogLocalisationEntry {
msg_type: string,
message: string
}
interface FileList {
total: number,
used: number,
files: FileInfo[],
file_system_corrupt: string,
iter_error: string,
export interface BackupHeader {
timestamp: string,
size: number
}
interface FileInfo{
filename: string,
size: number,
export interface NetworkConfig {
ap_ssid: string,
ssid: string,
password: string,
mqtt_url: string,
base_topic: string,
max_wait: number
}
interface NightLampConfig {
enabled: boolean,
night_lamp_hour_start: number,
night_lamp_hour_end: number,
night_lamp_only_when_dark: boolean,
low_soc_cutoff: number,
low_soc_restore: number
export interface FileList {
total: number,
used: number,
files: FileInfo[],
file_system_corrupt: string,
iter_error: string,
}
interface NightLampCommand {
active: boolean
export interface SolarState {
mppt_voltage: number,
mppt_current: number,
is_day: boolean
}
interface TankConfig {
tank_sensor_enabled: boolean,
tank_allow_pumping_if_sensor_error: boolean,
tank_useable_ml: number,
tank_warn_percent: number,
tank_empty_percent: number,
tank_full_percent: number,
export interface FileInfo {
filename: string,
size: number,
}
interface PlantControllerConfig {
network: NetworkConfig,
tank: TankConfig,
night_lamp: NightLampConfig,
plants: PlantConfig[]
export interface NightLampConfig {
enabled: boolean,
night_lamp_hour_start: number,
night_lamp_hour_end: number,
night_lamp_only_when_dark: boolean,
low_soc_cutoff: number,
low_soc_restore: number
}
interface PlantConfig {
mode: string,
target_moisture: number,
pump_time_s: number,
pump_cooldown_min: number,
pump_hour_start: number,
pump_hour_end: number,
sensor_b: boolean,
max_consecutive_pump_count: number,
export interface NightLampCommand {
active: boolean
}
export interface TankConfig {
tank_sensor_enabled: boolean,
tank_allow_pumping_if_sensor_error: boolean,
tank_useable_ml: number,
tank_warn_percent: number,
tank_empty_percent: number,
tank_full_percent: number,
ml_per_pulse: number
}
interface SSIDList {
ssids: [string]
export enum BatteryBoardVersion {
Disabled = "Disabled",
BQ34Z100G1 = "BQ34Z100G1",
WchI2cSlave = "WchI2cSlave"
}
interface TestPump {
pump: number
export enum BoardVersion {
INITIAL = "INITIAL",
V3 = "V3",
V4 = "V4"
}
interface SetTime {
time: string
export interface BoardHardware {
board: BoardVersion,
battery: BatteryBoardVersion,
}
interface GetTime {
rtc: string,
native: string
export interface PlantControllerConfig {
hardware: BoardHardware,
network: NetworkConfig,
tank: TankConfig,
night_lamp: NightLampConfig,
plants: PlantConfig[]
timezone?: string,
}
interface Moistures {
moisture_a: [number],
moisture_b: [number],
export interface PlantConfig {
mode: string,
target_moisture: number,
pump_time_s: number,
pump_cooldown_min: number,
pump_hour_start: number,
pump_hour_end: number,
sensor_a: boolean,
sensor_b: boolean,
max_consecutive_pump_count: number,
moisture_sensor_min_frequency: number | null;
moisture_sensor_max_frequency: number | null;
min_pump_current_ma: number,
max_pump_current_ma: number,
ignore_current_error: boolean,
}
interface VersionInfo {
git_hash: string,
build_time: string,
partition: string
export interface PumpTestResult {
median_current_ma: number,
max_current_ma: number,
min_current_ma: number,
flow_value_ml: number,
flow_value_count: number,
pump_time_s: number,
error: boolean,
}
interface BatteryState {
temperature: string
voltage_milli_volt: string,
current_milli_ampere: string,
cycle_count: string,
design_milli_ampere: string,
remaining_milli_ampere: string,
state_of_charge: string,
state_of_health: string
export interface SSIDList {
ssids: [string]
}
interface TankInfo {
/// is there enough water in the tank
enough_water: boolean,
/// warning that water needs to be refilled soon
warn_level: boolean,
/// estimation how many ml are still in tank
left_ml: number | null,
/// if there is was an issue with the water level sensor
sensor_error: string | null,
/// raw water sensor value
raw: number | null,
/// percent value
percent: number | null,
/// water in tank might be frozen
water_frozen: boolean,
/// water temperature
water_temp: number | null,
temp_sensor_error: string | null
export interface TestPump {
pump: number
}
export interface SetTime {
time: string
}
export interface GetTime {
rtc: string,
native: string
}
export interface Moistures {
moisture_a: [string],
moisture_b: [string],
}
export interface VersionInfo {
git_hash: string,
build_time: string,
partition: string
}
export interface BatteryState {
temperature: string
voltage_milli_volt: string,
current_milli_ampere: string,
cycle_count: string,
design_milli_ampere: string,
remaining_milli_ampere: string,
state_of_charge: string,
state_of_health: string
}
export interface TankInfo {
/// is there enough water in the tank
enough_water: boolean,
/// warning that water needs to be refilled soon
warn_level: boolean,
/// estimation how many ml are still in tank
left_ml: number | null,
/// if there is was an issue with the water level sensor
sensor_error: string | null,
/// raw water sensor value
raw: number | null,
/// percent value
percent: number | null,
/// water in tank might be frozen
water_frozen: boolean,
/// water temperature
water_temp: number | null,
temp_sensor_error: string | null
}

View File

@@ -1,4 +1,5 @@
import { Controller } from "./main";
import {BatteryState} from "./api";
export class BatteryView{
voltage_milli_volt: HTMLSpanElement;

View File

@@ -1,5 +1,5 @@
import { Controller } from "./main";
import {Controller} from "./main";
import {FileInfo, FileList} from "./api";
const regex = /[^a-zA-Z0-9_.]/g;
function sanitize(str:string){
@@ -24,7 +24,7 @@ export class FileView {
let fileuploadname = document.getElementById("fileuploadname") as HTMLInputElement
let fileuploadbtn = document.getElementById("fileuploadbtn") as HTMLInputElement
fileuploadfile.onchange = () => {
var selectedFile = fileuploadfile.files?.[0];
const selectedFile = fileuploadfile.files?.[0];
if (selectedFile == null) {
//TODO error dialog here
return
@@ -42,7 +42,7 @@ export class FileView {
}
fileuploadbtn.onclick = () => {
var selectedFile = fileuploadfile.files?.[0];
const selectedFile = fileuploadfile.files?.[0];
if (selectedFile == null) {
//TODO error dialog here
return
@@ -77,8 +77,7 @@ class FileEntry {
this.view.classList.add("fileentryouter")
const template = require('./fileviewentry.html') as string;
const fileRaw = template.replaceAll("${fileid}", String(fileid));
this.view.innerHTML = fileRaw
this.view.innerHTML = template.replaceAll("${fileid}", String(fileid))
let name = document.getElementById("file_" + fileid + "_name") as HTMLElement;
let size = document.getElementById("file_" + fileid + "_size") as HTMLElement;

View File

@@ -0,0 +1,20 @@
<style>
.boardkey{
min-width: 200px;
}
.boardvalue{
flex-grow: 1;
}
</style>
<div class="subtitle">Hardware:</div>
<div class="flexcontainer">
<div class="boardkey">BoardRevision</div>
<select class="boardvalue" id="hardware_board_value">
</select>
</div>
<div class="flexcontainer">
<div class="boardkey">BatteryMonitor</div>
<select class="boardvalue" id="hardware_battery_value">
</select>
</div>

View File

@@ -0,0 +1,45 @@
import { Controller } from "./main";
import {BatteryBoardVersion, BoardHardware, BoardVersion} from "./api";
export class HardwareConfigView {
private readonly hardware_board_value: HTMLSelectElement;
private readonly hardware_battery_value: HTMLSelectElement;
constructor(controller:Controller){
(document.getElementById("hardwareview") as HTMLElement).innerHTML = require('./hardware.html') as string;
this.hardware_board_value = document.getElementById("hardware_board_value") as HTMLSelectElement;
this.hardware_board_value.onchange = controller.configChanged
Object.keys(BoardVersion).forEach(version => {
let option = document.createElement("option");
if (version == BoardVersion.INITIAL.toString()){
option.selected = true
}
option.innerText = version.toString();
this.hardware_board_value.appendChild(option);
})
this.hardware_battery_value = document.getElementById("hardware_battery_value") as HTMLSelectElement;
this.hardware_battery_value.onchange = controller.configChanged
Object.keys(BatteryBoardVersion).forEach(version => {
let option = document.createElement("option");
if (version == BatteryBoardVersion.Disabled.toString()){
option.selected = true
}
option.innerText = version.toString();
this.hardware_battery_value.appendChild(option);
})
}
setConfig(hardware: BoardHardware) {
this.hardware_board_value.value = hardware.board.toString()
this.hardware_battery_value.value = hardware.battery.toString()
}
getConfig(): BoardHardware {
return {
board : BoardVersion[this.hardware_board_value.value as keyof typeof BoardVersion],
battery : BatteryBoardVersion[this.hardware_battery_value.value as keyof typeof BatteryBoardVersion],
}
}
}

View File

@@ -1,4 +1,5 @@
import { Controller } from "./main";
import {LogArray, LogLocalisation} from "./api";
export class LogView {
private readonly logpanel: HTMLElement;

View File

@@ -14,7 +14,7 @@
}
.progress {
height: 1.5em;
height: 2.5em;
width: 100%;
background-color: #555;
position: relative;
@@ -29,7 +29,7 @@
font-size: 0.8em;
position: absolute;
text-align: center;
top: 5px;
top: 10px;
left: 0;
right: 0;
}
@@ -45,7 +45,7 @@
display: inline-block;
height: 100%;
animation: indeterminateAnimation 1s infinite linear;
transform-origin: 0% 50%;
transform-origin: 0 50%;
}
@@ -75,15 +75,15 @@
.subcontainer {
min-width: 300px;
max-width: 900px;
flex-grow: 1;
border-style: solid;
border-width: 1px;
flex-grow: 1;
border-style: solid;
border-width: 1px;
padding: 8px;
}
.subcontainercontainer{
flex-grow: 1;
}
.plantcontainer {
flex-grow: 1;
min-width: 100%;
@@ -120,15 +120,15 @@
}
.plantlist {
display: flex;
display: flex;
flex-wrap: wrap;
}
.subtitle {
flex-grow: 1;
text-align: center;
flex-grow: 1;
text-align: center;
font-weight: bold;
}
@@ -138,6 +138,10 @@
<div class="container-xl">
<div style="display:flex; flex-wrap: wrap;">
<div id="hardwareview" class="subcontainer"></div>
</div>
<div style="display:flex; flex-wrap: wrap;">
<div id="firmwareview" class="subcontainer">
</div>
@@ -145,6 +149,8 @@
</div>
<div id="batteryview" class="subcontainer">
</div>
<div id="solarview" class="subcontainer">
</div>
</div>
<div class="flexcontainer">

File diff suppressed because it is too large Load Diff

View File

@@ -42,6 +42,11 @@
</datalist>
<input class="basicnetworkkeyssid2" type="button" id="scan" value="Scan">
</div>
<div class="flexcontainer">
<label class="basicnetworkkey" for="max_wait">Max wait:</label>
<input class="basicnetworkvalue" type="number" id="max_wait">
</div>
<div class="flexcontainer">
<label class="basicnetworkkey" for="ssid">Password:</label>

View File

@@ -1,11 +1,12 @@
import { Controller } from "./main";
import {NetworkConfig, SSIDList} from "./api";
export class NetworkConfigView {
setScanResult(ssidList: SSIDList) {
this.ssidlist.innerHTML = ''
for (var ssid of ssidList.ssids) {
var wi = document.createElement("option");
wi.value = ssid;
for (const ssid of ssidList.ssids) {
const wi = document.createElement("option");
wi.value = ssid;
this.ssidlist.appendChild(wi);
}
}
@@ -14,6 +15,7 @@ export class NetworkConfigView {
private readonly password: HTMLInputElement;
private readonly mqtt_url: HTMLInputElement;
private readonly base_topic: HTMLInputElement;
private readonly max_wait: HTMLInputElement;
private readonly ssidlist: HTMLElement;
constructor(controller: Controller, publicIp: string) {
@@ -28,6 +30,9 @@ export class NetworkConfigView {
this.ssid.onchange = controller.configChanged
this.password = (document.getElementById("password") as HTMLInputElement);
this.password.onchange = controller.configChanged
this.max_wait = (document.getElementById("max_wait") as HTMLInputElement);
this.max_wait.onchange = controller.configChanged
this.mqtt_url = document.getElementById("mqtt_url") as HTMLInputElement;
this.mqtt_url.onchange = controller.configChanged
this.base_topic = document.getElementById("base_topic") as HTMLInputElement;
@@ -47,10 +52,12 @@ export class NetworkConfigView {
this.password.value = network.password;
this.mqtt_url.value = network.mqtt_url;
this.base_topic.value = network.base_topic;
this.max_wait.value = network.max_wait.toString();
}
getConfig(): NetworkConfig {
return {
max_wait: +this.max_wait.value,
ap_ssid: this.ap_ssid.value,
ssid: this.ssid.value ?? null,
password: this.password.value ?? null,

View File

@@ -20,7 +20,7 @@
<div class="lightkey">Test Nightlight</div>
<input class="lightcheckbox" type="checkbox" id="night_lamp_test">
</div>
<div class="flexcontainer" style="text-decoration-line: line-through;">
<div class="flexcontainer">
<div class="lightkey">Enable Nightlight</div>
<input class="lightcheckbox" type="checkbox" id="night_lamp_enabled">
</div>

View File

@@ -1,4 +1,5 @@
import { Controller } from "./main";
import {NightLampConfig} from "./api";
export class NightLampView {
private readonly night_lamp_only_when_dark: HTMLInputElement;

View File

@@ -37,5 +37,5 @@
</form>
</div>
<div class="display:flex">
<input style="margin-left: 16px; margin-top: 8px;" class="col-6" type="button" id="test" value="Self-Test">
<button style="margin-left: 16px; margin-top: 8px;" class="col-6" type="button" id="test">Self-Test</button>
</div>

View File

@@ -1,4 +1,5 @@
import { Controller } from "./main";
import {VersionInfo} from "./api";
export class OTAView {
readonly file1Upload: HTMLInputElement;
@@ -9,6 +10,8 @@ export class OTAView {
constructor(controller: Controller) {
(document.getElementById("firmwareview") as HTMLElement).innerHTML = require("./ota.html")
let test = document.getElementById("test") as HTMLButtonElement;
this.firmware_buildtime = document.getElementById("firmware_buildtime") as HTMLDivElement;
this.firmware_githash = document.getElementById("firmware_githash") as HTMLDivElement;
this.firmware_partition = document.getElementById("firmware_partition") as HTMLDivElement;
@@ -17,13 +20,17 @@ export class OTAView {
const file = document.getElementById("firmware_file") as HTMLInputElement;
this.file1Upload = file
this.file1Upload.onchange = () => {
var selectedFile = file.files?.[0];
const selectedFile = file.files?.[0];
if (selectedFile == null) {
//TODO error dialog here
return
}
controller.uploadNewFirmware(selectedFile);
};
test.onclick = () => {
controller.selfTest();
}
}
setVersion(versionInfo: VersionInfo) {

View File

@@ -1,30 +1,53 @@
<style>
.plantsensorkey{
min-width: 100px;
}
.plantsensorvalue{
flex-grow: 1;
}
.plantsensorkey {
min-width: 100px;
}
.plantkey{
min-width: 175px;
}
.plantvalue{
flex-grow: 1;
}
.plantcheckbox{
min-width: 20px;
margin: 0px;
}
.plantsensorvalue {
flex-grow: 1;
}
.plantkey {
min-width: 195px;
}
.plantvalue {
flex-grow: 1;
}
.plantcheckbox {
min-width: 20px;
margin: 0;
}
.plantTargetEnabledOnly_ ${plantId} {
}
.plantPumpEnabledOnly_ ${plantId} {
}
.plantSensorEnabledOnly_ ${plantId} {
}
.plantHidden_ ${plantId} {
display: none;
}
</style>
<div>
<div class="subtitle"
id="plant_${plantId}_header">
id="plant_${plantId}_header">
Plant ${plantId}
</div>
<div class="flexcontainer">
<div class="plantkey">Sensor A installed:</div>
<input class="plantcheckbox" id="plant_${plantId}_sensor_a" type="checkbox">
</div>
<div class="flexcontainer">
<div class="plantkey">Sensor B installed:</div>
<input class="plantcheckbox" id="plant_${plantId}_sensor_b" type="checkbox">
</div>
<div class="flexcontainer">
<div class="plantkey">
Mode:
@@ -36,52 +59,98 @@
</select>
</div>
<div class="flexcontainer">
<div class="flexcontainer plantTargetEnabledOnly_${plantId}">
<div class="plantkey">Target Moisture:</div>
<input class="plantvalue" id="plant_${plantId}_target_moisture" type="number" min="0" max="100" placeholder="0">
</div>
<div class="flexcontainer">
<div class="flexcontainer plantPumpEnabledOnly_${plantId}">
<div class="plantkey">Pump Time (s):</div>
<input class="plantvalue" id="plant_${plantId}_pump_time_s" type="number" min="0" max="600" placeholder="30">
</div>
<div class="flexcontainer">
<div class="flexcontainer plantPumpEnabledOnly_${plantId}">
<div class="plantkey">Pump Cooldown (m):</div>
<input class="plantvalue" id="plant_${plantId}_pump_cooldown_min" type="number" min="0" max="600" placeholder="30">
<input class="plantvalue" id="plant_${plantId}_pump_cooldown_min" type="number" min="0" max="600"
placeholder="30">
</div>
<div class="flexcontainer">
<div class="flexcontainer plantPumpEnabledOnly_${plantId}">
<div class="plantkey">"Pump Hour Start":</div>
<select class="plantvalue" id="plant_${plantId}_pump_hour_start">10</select>
</div>
<div class="flexcontainer">
<div class="flexcontainer plantPumpEnabledOnly_${plantId}">
<div class="plantkey">"Pump Hour End":</div>
<select class="plantvalue" id="plant_${plantId}_pump_hour_end">19</select>
</div>
<div class="flexcontainer">
<div class="flexcontainer plantTargetEnabledOnly_${plantId}">
<div class="plantkey">Warn Pump Count:</div>
<input class="plantvalue" id="plant_${plantId}_max_consecutive_pump_count" type="number" min="1" , max="50" ,
placeholder="10">
<input class="plantvalue" id="plant_${plantId}_max_consecutive_pump_count" type="number" min="1" max="50"
placeholder="10">
</div>
<div class="flexcontainer plantSensorEnabledOnly_${plantId}">
<div class="plantkey">Min Frequency Override</div>
<input class="plantvalue" id="plant_${plantId}_min_frequency" type="number" min="1000" max="25000">
</div>
<div class="flexcontainer plantSensorEnabledOnly_${plantId}">
<div class="plantkey">Max Frequency Override</div>
<input class="plantvalue" id="plant_${plantId}_max_frequency" type="number" min="1000" max="25000">
</div>
<div class="flexcontainer">
<div class="plantkey">Sensor B installed:</div>
<input class="plantcheckbox" id="plant_${plantId}_sensor_b" type="checkbox">
<div class="flexcontainer plantPumpEnabledOnly_${plantId}">
<h2 class="plantkey">Current config:</h2>
</div>
<div class="flexcontainer plantPumpEnabledOnly_${plantId}">
<div class="plantkey">Min current</div>
<input class="plantvalue" id="plant_${plantId}_min_pump_current_ma" type="number" min="0" max="4500">
</div>
<div class="flexcontainer plantPumpEnabledOnly_${plantId}">
<div class="plantkey">Max current</div>
<input class="plantvalue" id="plant_${plantId}_max_pump_current_ma" type="number" min="0" max="4500">
</div>
<div class="flexcontainer plantPumpEnabledOnly_${plantId}">
<div class="plantkey">Ignore current sensor error</div>
<input class="plantcheckbox" id="plant_${plantId}_ignore_current_error" type="checkbox">
</div>
<div class="flexcontainer">
<div class="flexcontainer plantPumpEnabledOnly_${plantId}">
<button class="subtitle" id="plant_${plantId}_test">Test Pump</button>
</div>
<div class="flexcontainer">
<div class="flexcontainer plantSensorEnabledOnly_${plantId}">
<div class="subtitle">Live:</div>
</div>
<div class="flexcontainer">
<div class="flexcontainer plantSensorEnabledOnly_${plantId}">
<span class="plantsensorkey">Sensor A:</span>
<span class="plantsensorvalue" id="plant_${plantId}_moisture_a">loading</span>
<span class="plantsensorvalue" id="plant_${plantId}_moisture_a">not measured</span>
</div>
<div class="flexcontainer">
<div class="flexcontainer plantSensorEnabledOnly_${plantId}">
<div class="plantsensorkey">Sensor B:</div>
<span class="plantsensorvalue" id="plant_${plantId}_moisture_b">loading</span>
<span class="plantsensorvalue" id="plant_${plantId}_moisture_b">not measured</span>
</div>
<div class="flexcontainer plantPumpEnabledOnly_${plantId}">
<div class="plantsensorkey">Max Current</div>
<span class="plantsensorvalue" id="plant_${plantId}_pump_test_current_max">not_tested</span>
</div>
<div class="flexcontainer plantPumpEnabledOnly_${plantId}">
<div class="plantsensorkey">Min Current</div>
<span class="plantsensorvalue" id="plant_${plantId}_pump_test_current_min">not_tested</span>
</div>
<div class="flexcontainer plantPumpEnabledOnly_${plantId}">
<div class="plantsensorkey">Average</div>
<span class="plantsensorvalue" id="plant_${plantId}_pump_test_current_average">not_tested</span>
</div>
<div class="flexcontainer plantPumpEnabledOnly_${plantId}">
<div class="plantsensorkey">Pump Time</div>
<span class="plantsensorvalue" id="plant_${plantId}_pump_test_pump_time">not_tested</span>
</div>
<div class="flexcontainer plantPumpEnabledOnly_${plantId}">
<div class="plantsensorkey">Flow ml</div>
<span class="plantsensorvalue" id="plant_${plantId}_pump_test_flow_ml">not_tested</span>
</div>
<div class="flexcontainer plantPumpEnabledOnly_${plantId}">
<div class="plantsensorkey">Flow raw</div>
<span class="plantsensorvalue" id="plant_${plantId}_pump_test_flow_raw">not_tested</span>
</div>

View File

@@ -1,48 +1,59 @@
import {PlantConfig, PumpTestResult} from "./api";
const PLANT_COUNT = 8;
import { Controller } from "./main";
import {Controller} from "./main";
export class PlantViews {
private readonly measure_moisture: HTMLButtonElement;
private readonly plants: PlantView[] = []
private readonly plantsDiv: HTMLDivElement
constructor(syncConfig:Controller) {
this.measure_moisture = document.getElementById("measure_moisture") as HTMLButtonElement
this.measure_moisture.onclick = syncConfig.measure_moisture
this.plantsDiv = document.getElementById("plants") as HTMLDivElement;
for (let plantId = 0; plantId < PLANT_COUNT; plantId++) {
this.plants[plantId] = new PlantView(plantId, this.plantsDiv, syncConfig);
}
constructor(syncConfig: Controller) {
this.measure_moisture = document.getElementById("measure_moisture") as HTMLButtonElement
this.measure_moisture.onclick = syncConfig.measure_moisture
this.plantsDiv = document.getElementById("plants") as HTMLDivElement;
for (let plantId = 0; plantId < PLANT_COUNT; plantId++) {
this.plants[plantId] = new PlantView(plantId, this.plantsDiv, syncConfig);
}
}
getConfig(): PlantConfig[] {
const rv: PlantConfig[] = [];
for (let i = 0; i < PLANT_COUNT; i++) {
rv[i] = this.plants[i].getConfig();
}
return rv
const rv: PlantConfig[] = [];
for (let i = 0; i < PLANT_COUNT; i++) {
rv[i] = this.plants[i].getConfig();
}
return rv
}
update(moisture_a: [number], moisture_b: [number]) {
for (let plantId = 0; plantId < PLANT_COUNT; plantId++) {
const a = moisture_a[plantId]
const b = moisture_b[plantId]
this.plants[plantId].update(a,b)
}
update(moisture_a: [string], moisture_b: [string]) {
for (let plantId = 0; plantId < PLANT_COUNT; plantId++) {
const a = moisture_a[plantId]
const b = moisture_b[plantId]
this.plants[plantId].setMeasurementResult(a, b)
}
}
setConfig(plants: PlantConfig[]) {
for (let plantId = 0; plantId < PLANT_COUNT; plantId++) {
const plantConfig = plants[plantId];
const plantView = this.plants[plantId];
plantView.setConfig(plantConfig)
}
for (let plantId = 0; plantId < PLANT_COUNT; plantId++) {
const plantConfig = plants[plantId];
const plantView = this.plants[plantId];
plantView.setConfig(plantConfig)
}
}
}
setPumpTestCurrent(plantId: number, response: PumpTestResult) {
const plantView = this.plants[plantId];
plantView.setTestResult(response)
}
}
export class PlantView {
private readonly moistureSensorMinFrequency: HTMLInputElement;
private readonly moistureSensorMaxFrequency: HTMLInputElement;
private readonly plantId: number;
private readonly plantDiv: HTMLDivElement;
private readonly header: HTMLElement;
@@ -52,106 +63,195 @@ export class PlantView {
private readonly pumpCooldown: HTMLInputElement;
private readonly pumpHourStart: HTMLSelectElement;
private readonly pumpHourEnd: HTMLSelectElement;
private readonly sensorAInstalled: HTMLInputElement;
private readonly sensorBInstalled: HTMLInputElement;
private readonly mode: HTMLSelectElement;
private readonly moistureA: HTMLElement;
private readonly moistureB: HTMLElement;
private readonly maxConsecutivePumpCount: HTMLInputElement;
constructor(plantId: number, parent:HTMLDivElement, controller:Controller) {
const dummy = this;
this.plantId = plantId;
this.plantDiv = document.createElement("div")! as HTMLDivElement
const template = require('./plant.html') as string;
const plantRaw = template.replaceAll("${plantId}", String(plantId));
this.plantDiv.innerHTML = plantRaw
this.plantDiv.classList.add("plantcontainer")
parent.appendChild(this.plantDiv)
this.header = document.getElementById("plant_"+plantId+"_header")!
this.header.innerText = "Plant "+ (this.plantId+1)
this.moistureA = document.getElementById("plant_"+plantId+"_moisture_a")! as HTMLElement;
this.moistureB = document.getElementById("plant_"+plantId+"_moisture_b")! as HTMLElement;
private readonly minPumpCurrentMa: HTMLInputElement;
private readonly maxPumpCurrentMa: HTMLInputElement;
private readonly ignoreCurrentError: HTMLInputElement;
this.testButton = document.getElementById("plant_"+plantId+"_test")! as HTMLButtonElement;
this.testButton.onclick = function(){
controller.testPlant(plantId)
}
private readonly pump_test_current_max: HTMLElement;
private readonly pump_test_current_min: HTMLElement;
private readonly pump_test_current_average: HTMLElement;
private readonly pump_test_pump_time: HTMLElement;
private readonly pump_test_flow_ml: HTMLElement;
private readonly pump_test_flow_raw: HTMLElement;
this.mode = document.getElementById("plant_"+plantId+"_mode") as HTMLSelectElement
this.mode.onchange = function(){
controller.configChanged()
}
this.targetMoisture = document.getElementById("plant_"+plantId+"_target_moisture")! as HTMLInputElement;
this.targetMoisture.onchange = function(){
controller.configChanged()
}
constructor(plantId: number, parent: HTMLDivElement, controller: Controller) {
this.plantId = plantId;
this.plantDiv = document.createElement("div")! as HTMLDivElement
const template = require('./plant.html') as string;
this.plantDiv.innerHTML = template.replaceAll("${plantId}", String(plantId))
this.pumpTimeS = document.getElementById("plant_"+plantId+"_pump_time_s") as HTMLInputElement;
this.pumpTimeS.onchange = function(){
controller.configChanged()
}
this.plantDiv.classList.add("plantcontainer")
parent.appendChild(this.plantDiv)
this.pumpCooldown = document.getElementById("plant_"+plantId+"_pump_cooldown_min") as HTMLInputElement;
this.pumpCooldown.onchange = function(){
controller.configChanged()
}
this.header = document.getElementById("plant_" + plantId + "_header")!
this.header.innerText = "Plant " + (this.plantId + 1)
this.pumpHourStart = document.getElementById("plant_"+plantId+"_pump_hour_start") as HTMLSelectElement;
this.pumpHourStart.onchange = function(){
controller.configChanged()
}
for (let i = 0; i < 24; i++) {
let option = document.createElement("option");
if (i == 10){
option.selected = true
this.moistureA = document.getElementById("plant_" + plantId + "_moisture_a")! as HTMLElement;
this.moistureB = document.getElementById("plant_" + plantId + "_moisture_b")! as HTMLElement;
this.pump_test_current_max = document.getElementById("plant_" + plantId + "_pump_test_current_max")! as HTMLElement;
this.pump_test_current_min = document.getElementById("plant_" + plantId + "_pump_test_current_min")! as HTMLElement;
this.pump_test_current_average = document.getElementById("plant_" + plantId + "_pump_test_current_average")! as HTMLElement;
this.pump_test_pump_time = document.getElementById("plant_" + plantId + "_pump_test_pump_time")! as HTMLElement;
this.pump_test_flow_ml = document.getElementById("plant_" + plantId + "_pump_test_flow_ml")! as HTMLElement;
this.pump_test_flow_raw = document.getElementById("plant_" + plantId + "_pump_test_flow_raw")! as HTMLElement;
this.testButton = document.getElementById("plant_" + plantId + "_test")! as HTMLButtonElement;
this.testButton.onclick = function () {
controller.testPlant(plantId)
}
option.innerText = i.toString();
this.pumpHourStart.appendChild(option);
}
this.pumpHourEnd = document.getElementById("plant_"+plantId+"_pump_hour_end") as HTMLSelectElement;
this.pumpHourEnd.onchange = function(){
controller.configChanged()
}
for (let i = 0; i < 24; i++) {
let option = document.createElement("option");
if (i == 19){
option.selected = true
this.mode = document.getElementById("plant_" + plantId + "_mode") as HTMLSelectElement
this.mode.onchange = function () {
controller.configChanged()
}
option.innerText = i.toString();
this.pumpHourEnd.appendChild(option);
}
this.sensorBInstalled = document.getElementById("plant_"+plantId+"_sensor_b") as HTMLInputElement;
this.sensorBInstalled.onchange = function(){
controller.configChanged()
}
this.targetMoisture = document.getElementById("plant_" + plantId + "_target_moisture")! as HTMLInputElement;
this.targetMoisture.onchange = function () {
controller.configChanged()
}
this.maxConsecutivePumpCount = document.getElementById("plant_"+plantId+"_max_consecutive_pump_count") as HTMLInputElement;
this.maxConsecutivePumpCount.onchange = function(){
controller.configChanged()
}
this.pumpTimeS = document.getElementById("plant_" + plantId + "_pump_time_s") as HTMLInputElement;
this.pumpTimeS.onchange = function () {
controller.configChanged()
}
this.pumpCooldown = document.getElementById("plant_" + plantId + "_pump_cooldown_min") as HTMLInputElement;
this.pumpCooldown.onchange = function () {
controller.configChanged()
}
this.pumpHourStart = document.getElementById("plant_" + plantId + "_pump_hour_start") as HTMLSelectElement;
this.pumpHourStart.onchange = function () {
controller.configChanged()
}
for (let i = 0; i < 24; i++) {
let option = document.createElement("option");
if (i == 10) {
option.selected = true
}
option.innerText = i.toString();
this.pumpHourStart.appendChild(option);
}
this.pumpHourEnd = document.getElementById("plant_" + plantId + "_pump_hour_end") as HTMLSelectElement;
this.pumpHourEnd.onchange = function () {
controller.configChanged()
}
for (let i = 0; i < 24; i++) {
let option = document.createElement("option");
if (i == 19) {
option.selected = true
}
option.innerText = i.toString();
this.pumpHourEnd.appendChild(option);
}
this.sensorAInstalled = document.getElementById("plant_" + plantId + "_sensor_a") as HTMLInputElement;
this.sensorAInstalled.onchange = function () {
controller.configChanged()
}
this.sensorBInstalled = document.getElementById("plant_" + plantId + "_sensor_b") as HTMLInputElement;
this.sensorBInstalled.onchange = function () {
controller.configChanged()
}
this.minPumpCurrentMa = document.getElementById("plant_" + plantId + "_min_pump_current_ma") as HTMLInputElement;
this.minPumpCurrentMa.onchange = function () {
controller.configChanged()
}
this.maxPumpCurrentMa = document.getElementById("plant_" + plantId + "_max_pump_current_ma") as HTMLInputElement;
this.maxPumpCurrentMa.onchange = function () {
controller.configChanged()
}
this.ignoreCurrentError = document.getElementById("plant_" + plantId + "_ignore_current_error") as HTMLInputElement;
this.ignoreCurrentError.onchange = function () {
controller.configChanged()
}
this.maxConsecutivePumpCount = document.getElementById("plant_" + plantId + "_max_consecutive_pump_count") as HTMLInputElement;
this.maxConsecutivePumpCount.onchange = function () {
controller.configChanged()
}
this.moistureSensorMinFrequency = document.getElementById("plant_" + plantId + "_min_frequency") as HTMLInputElement;
this.moistureSensorMinFrequency.onchange = function () {
controller.configChanged()
}
this.moistureSensorMinFrequency.onchange = () => {
controller.configChanged();
};
this.moistureSensorMaxFrequency = document.getElementById("plant_" + plantId + "_max_frequency") as HTMLInputElement;
this.moistureSensorMaxFrequency.onchange = () => {
controller.configChanged();
};
}
update(a: number, b: number) {
if (a == 200){
this.moistureA.innerText = "error"
} else {
this.moistureA.innerText = String(a)
}
if (b == 200){
this.moistureB.innerText = "error"
} else {
this.moistureB.innerText = String(b)
}
updateVisibility(plantConfig: PlantConfig) {
let sensorOnly = document.getElementsByClassName("plantSensorEnabledOnly_"+ this.plantId)
let pumpOnly = document.getElementsByClassName("plantPumpEnabledOnly_"+ this.plantId)
let targetOnly = document.getElementsByClassName("plantTargetEnabledOnly_"+ this.plantId)
console.log("updateVisibility plantConfig: " + plantConfig.mode)
let showSensor = plantConfig.sensor_a || plantConfig.sensor_b
let showPump = plantConfig.mode !== "OFF"
let showTarget = plantConfig.mode === "TargetMoisture"
console.log("updateVisibility showsensor: " + showSensor + " pump " + showPump + " target " +showTarget)
for (const element of Array.from(sensorOnly)) {
if (showSensor) {
element.classList.remove("plantHidden_" + this.plantId)
} else {
element.classList.add("plantHidden_" + this.plantId)
}
}
for (const element of Array.from(pumpOnly)) {
if (showPump) {
element.classList.remove("plantHidden_" + this.plantId)
} else {
element.classList.add("plantHidden_" + this.plantId)
}
}
for (const element of Array.from(targetOnly)) {
if (showTarget) {
element.classList.remove("plantHidden_" + this.plantId)
} else {
element.classList.add("plantHidden_" + this.plantId)
}
}
}
setTestResult(result: PumpTestResult) {
this.pump_test_current_max.innerText = result.max_current_ma.toString()
this.pump_test_current_min.innerText = result.min_current_ma.toString()
this.pump_test_current_average.innerText = result.median_current_ma.toString()
this.pump_test_flow_raw.innerText = result.flow_value_count.toString()
this.pump_test_flow_ml.innerText = result.flow_value_ml.toString()
this.pump_test_pump_time.innerText = result.pump_time_s.toString()
}
setMeasurementResult(a: string, b: string) {
this.moistureA.innerText = a
this.moistureB.innerText = b
}
setConfig(plantConfig: PlantConfig) {
this.mode.value = plantConfig.mode;
this.targetMoisture.value = plantConfig.target_moisture.toString();
@@ -159,29 +259,41 @@ export class PlantView {
this.pumpCooldown.value = plantConfig.pump_cooldown_min.toString();
this.pumpHourStart.value = plantConfig.pump_hour_start.toString();
this.pumpHourEnd.value = plantConfig.pump_hour_end.toString();
this.sensorBInstalled.checked = plantConfig.sensor_b
this.sensorBInstalled.checked = plantConfig.sensor_b;
this.sensorAInstalled.checked = plantConfig.sensor_a;
this.maxConsecutivePumpCount.value = plantConfig.max_consecutive_pump_count.toString();
this.minPumpCurrentMa.value = plantConfig.min_pump_current_ma.toString();
this.maxPumpCurrentMa.value = plantConfig.max_pump_current_ma.toString();
this.ignoreCurrentError.checked = plantConfig.ignore_current_error;
// Set new fields
this.moistureSensorMinFrequency.value =
plantConfig.moisture_sensor_min_frequency?.toString() || "";
this.moistureSensorMaxFrequency.value =
plantConfig.moisture_sensor_max_frequency?.toString() || "";
this.updateVisibility(plantConfig);
}
getConfig() :PlantConfig {
const rv:PlantConfig = {
mode: this.mode.value,
target_moisture: this.targetMoisture.valueAsNumber,
pump_time_s: this.pumpTimeS.valueAsNumber,
pump_cooldown_min: this.pumpCooldown.valueAsNumber,
pump_hour_start: +this.pumpHourStart.value,
pump_hour_end: +this.pumpHourEnd.value,
sensor_b: this.sensorBInstalled.checked,
max_consecutive_pump_count: this.maxConsecutivePumpCount.valueAsNumber
}
return rv
}
getConfig(): PlantConfig {
setMoistureA(a: number) {
this.moistureA.innerText = String(a);
let conv: PlantConfig = {
mode: this.mode.value,
target_moisture: this.targetMoisture.valueAsNumber,
pump_time_s: this.pumpTimeS.valueAsNumber,
pump_cooldown_min: this.pumpCooldown.valueAsNumber,
pump_hour_start: +this.pumpHourStart.value,
pump_hour_end: +this.pumpHourEnd.value,
sensor_b: this.sensorBInstalled.checked,
sensor_a: this.sensorAInstalled.checked,
max_consecutive_pump_count: this.maxConsecutivePumpCount.valueAsNumber,
moisture_sensor_min_frequency: this.moistureSensorMinFrequency.valueAsNumber || null,
moisture_sensor_max_frequency: this.moistureSensorMaxFrequency.valueAsNumber || null,
min_pump_current_ma: this.minPumpCurrentMa.valueAsNumber,
max_pump_current_ma: this.maxPumpCurrentMa.valueAsNumber,
ignore_current_error: this.ignoreCurrentError.checked,
};
this.updateVisibility(conv);
return conv;
}
setMoistureB(b: number) {
this.moistureB.innerText = String(b);
}
}
}

View File

@@ -0,0 +1,29 @@
<style>
.solarflexkey {
min-width: 150px;
}
.solarflexvalue {
text-wrap: nowrap;
flex-grow: 1;
}
</style>
<div class="flexcontainer">
<div class="subtitle">
Mppt:
</div>
<input id="solar_auto_refresh" type="checkbox">
</div>
<div class="flexcontainer">
<span class="solarflexkey">Mppt mV:</span>
<span class="solarflexvalue" id="solar_voltage_milli_volt"></span>
</div>
<div class="flexcontainer">
<span class="solarflexkey">Mppt mA:</span>
<span class="solarflexvalue" id="solar_current_milli_ampere" ></span>
</div>
<div class="flexcontainer">
<span class="solarflexkey">is Day:</span>
<span class="solarflexvalue" id="solar_is_day" ></span>
</div>

View File

@@ -0,0 +1,49 @@
import { Controller } from "./main";
import {BatteryState, SolarState} from "./api";
export class SolarView{
solar_voltage_milli_volt: HTMLSpanElement;
solar_current_milli_ampere: HTMLSpanElement;
solar_is_day: HTMLSpanElement;
solar_auto_refresh: HTMLInputElement;
timer: NodeJS.Timeout | undefined;
controller: Controller;
constructor (controller:Controller) {
(document.getElementById("solarview") as HTMLElement).innerHTML = require("./solarview.html")
this.solar_voltage_milli_volt = document.getElementById("solar_voltage_milli_volt") as HTMLSpanElement;
this.solar_current_milli_ampere = document.getElementById("solar_current_milli_ampere") as HTMLSpanElement;
this.solar_is_day = document.getElementById("solar_is_day") as HTMLSpanElement;
this.solar_auto_refresh = document.getElementById("solar_auto_refresh") as HTMLInputElement;
this.controller = controller
this.solar_auto_refresh.onchange = () => {
if(this.timer){
clearTimeout(this.timer)
}
if(this.solar_auto_refresh.checked){
controller.updateSolarData()
}
}
}
update(solarState: SolarState|null){
if (solarState == null) {
this.solar_voltage_milli_volt.innerText = "N/A"
this.solar_current_milli_ampere.innerText = "N/A"
this.solar_is_day.innerText = "N/A"
} else {
this.solar_voltage_milli_volt.innerText = solarState.mppt_voltage.toFixed(0)
this.solar_current_milli_ampere.innerText = solarState.mppt_current.toFixed(0)
this.solar_is_day.innerText = solarState.is_day?"🌞":"🌙"
}
if(this.solar_auto_refresh.checked){
this.timer = setTimeout(this.controller.updateSolarData, 1000);
} else {
if(this.timer){
clearTimeout(this.timer)
}
}
}
}

View File

@@ -1,60 +1,65 @@
import { Controller } from "./main";
import {Controller} from "./main";
import {BackupHeader} from "./api";
export class SubmitView {
json: HTMLDivElement;
submitFormBtn: HTMLButtonElement;
submit_status: HTMLElement;
backupBtn: HTMLButtonElement;
restoreBackupBtn: HTMLButtonElement;
backuptimestamp: HTMLElement;
backupsize: HTMLElement;
backupjson: HTMLElement;
json: HTMLDivElement;
submitFormBtn: HTMLButtonElement;
submit_status: HTMLElement;
backupBtn: HTMLButtonElement;
restoreBackupBtn: HTMLButtonElement;
backuptimestamp: HTMLElement;
backupsize: HTMLElement;
backupjson: HTMLElement;
constructor(controller: Controller) {
(document.getElementById("submitview") as HTMLElement).innerHTML = require("./submitview.html")
constructor(controller: Controller) {
(document.getElementById("submitview") as HTMLElement).innerHTML = require("./submitview.html")
let showJson = document.getElementById('showJson') as HTMLButtonElement
let rawdata = document.getElementById('rawdata') as HTMLElement
this.json = document.getElementById('json') as HTMLDivElement
this.backupjson = document.getElementById('backupjson') as HTMLDivElement
this.submitFormBtn = document.getElementById("submit") as HTMLButtonElement
this.backupBtn = document.getElementById("backup") as HTMLButtonElement
this.restoreBackupBtn = document.getElementById("restorebackup") as HTMLButtonElement
this.backuptimestamp = document.getElementById("backuptimestamp") as HTMLElement
this.backupsize = document.getElementById("backupsize") as HTMLElement
this.submit_status = document.getElementById("submit_status") as HTMLElement
this.submitFormBtn.onclick = () => {
controller.uploadConfig(this.json.textContent as string, (status: string) => {
this.submit_status.innerHTML = status;
});
let showJson = document.getElementById('showJson') as HTMLButtonElement
let rawdata = document.getElementById('rawdata') as HTMLElement
this.json = document.getElementById('json') as HTMLDivElement
this.backupjson = document.getElementById('backupjson') as HTMLDivElement
this.submitFormBtn = document.getElementById("submit") as HTMLButtonElement
this.backupBtn = document.getElementById("backup") as HTMLButtonElement
this.restoreBackupBtn = document.getElementById("restorebackup") as HTMLButtonElement
this.backuptimestamp = document.getElementById("backuptimestamp") as HTMLElement
this.backupsize = document.getElementById("backupsize") as HTMLElement
this.submit_status = document.getElementById("submit_status") as HTMLElement
this.submitFormBtn.onclick = () => {
controller.uploadConfig(this.json.textContent as string, (status: string) => {
this.submit_status.innerHTML = status;
});
}
this.backupBtn.onclick = () => {
controller.progressview.addIndeterminate("backup", "Backup to EEPROM running")
controller.backupConfig(this.json.textContent as string).then(saveStatus => {
controller.getBackupInfo().then(r => {
controller.progressview.removeProgress("backup")
this.submit_status.innerHTML = saveStatus;
});
});
}
this.restoreBackupBtn.onclick = () => {
controller.getBackupConfig();
}
showJson.onclick = () => {
if (rawdata.style.display == "none") {
rawdata.style.display = "flex";
} else {
rawdata.style.display = "none";
}
}
}
this.backupBtn.onclick = () => {
controller.backupConfig(this.json.textContent as string, (status: string) => {
this.submit_status.innerHTML = status;
});
}
this.restoreBackupBtn.onclick = () => {
controller.getBackupConfig();
}
showJson.onclick = () => {
if (rawdata.style.display == "none"){
rawdata.style.display = "flex";
} else {
rawdata.style.display = "none";
}
}
}
setBackupInfo(header: BackupHeader) {
this.backuptimestamp.innerText = header.timestamp
this.backupsize.innerText = header.size.toString()
}
setBackupInfo(header: BackupHeader) {
this.backuptimestamp.innerText = header.timestamp
this.backupsize.innerText = header.size.toString()
}
setJson(pretty: string) {
this.json.textContent = pretty
}
setJson(pretty: string) {
this.json.textContent = pretty
}
setBackupJson(pretty: string) {
this.backupjson.textContent = pretty
}
setBackupJson(pretty: string) {
this.backupjson.textContent = pretty
}
}

View File

@@ -7,14 +7,31 @@
word-wrap: break-word;
overflow: scroll;
}
.submitbutton{
padding: 1em 1em;
background: #667eea;
color: white;
border: none;
border-radius: 8px;
font-size: 1.1em;
font-weight: bold;
cursor: pointer;
transition: all 0.3s ease;
letter-spacing: 1px;
margin: 1em 0;
}
.submitbutton:hover {
background: #1c4e63;
}
</style>
<button class="submitbutton" id="submit">Submit</button>
<br>
<button id="showJson">Show Json</button>
<div id="rawdata" class="flexcontainer" style="display: none;">
<div class="submitarea" id="json" contenteditable="true"></div>
<div class="submitarea" id="backupjson">backup will be here</div>
</div>
<button id="submit">Submit</button>
<div>BackupStatus:</div>
<div id="backuptimestamp"></div>
<div id="backupsize"></div>

View File

@@ -1,14 +1,14 @@
<style>
.tankcheckbox {
min-width: 20px;
margin: 0px;
margin: 0;
}
.tankkey{
min-width: 250px;
}
.tankvalue{
flex-grow: 1;
margin: 0px;
margin: 0;
}
.hidden {
display: none;
@@ -48,6 +48,10 @@
<div class="tankkey">Full at %</div>
<input class="tankvalue" type="number" min="0" max="100" id="tank_full_percent">
</div>
<div class="flexcontainer">
<div class="tankkey">Flow Sensor ml per pulse</div>
<input class="tankvalue" type="number" min="0" max="1000" step="0.01" id="ml_per_pulse">
</div>
<button id="tank_update">Update Tank</button>
@@ -82,4 +86,4 @@
<div class="flexcontainer">
<div class="tankkey">Warn Level</div>
<label class="tankvalue" id="tank_measure_warnlevel"></label>
</div>
</div>

View File

@@ -1,4 +1,5 @@
import { Controller } from "./main";
import {TankConfig, TankInfo} from "./api";
export class TankConfigView {
private readonly tank_useable_ml: HTMLInputElement;
@@ -7,6 +8,7 @@ export class TankConfigView {
private readonly tank_warn_percent: HTMLInputElement;
private readonly tank_sensor_enabled: HTMLInputElement;
private readonly tank_allow_pumping_if_sensor_error: HTMLInputElement;
private readonly ml_per_pulse: HTMLInputElement;
private readonly tank_measure_error: HTMLLabelElement;
private readonly tank_measure_ml: HTMLLabelElement;
private readonly tank_measure_percent: HTMLLabelElement;
@@ -53,6 +55,8 @@ export class TankConfigView {
this.tank_sensor_enabled.onchange = controller.configChanged
this.tank_allow_pumping_if_sensor_error = document.getElementById("tank_allow_pumping_if_sensor_error") as HTMLInputElement;
this.tank_allow_pumping_if_sensor_error.onchange = controller.configChanged
this.ml_per_pulse = document.getElementById("ml_per_pulse") as HTMLInputElement;
this.ml_per_pulse.onchange = controller.configChanged
let tank_update = document.getElementById("tank_update") as HTMLInputElement;
tank_update.onclick = () => {
@@ -140,6 +144,7 @@ export class TankConfigView {
this.tank_full_percent.value = String(tank.tank_full_percent)
this.tank_sensor_enabled.checked = tank.tank_sensor_enabled
this.tank_useable_ml.value = String(tank.tank_useable_ml)
this.ml_per_pulse.value = String(tank.ml_per_pulse)
}
getConfig(): TankConfig {
return {
@@ -148,7 +153,8 @@ export class TankConfigView {
tank_full_percent: this.tank_full_percent.valueAsNumber,
tank_sensor_enabled: this.tank_sensor_enabled.checked,
tank_useable_ml: this.tank_useable_ml.valueAsNumber,
tank_warn_percent: this.tank_warn_percent.valueAsNumber
tank_warn_percent: this.tank_warn_percent.valueAsNumber,
ml_per_pulse: this.ml_per_pulse.valueAsNumber
}
}
}

View File

@@ -18,4 +18,11 @@
<div id="timeview_browser_time" style="text-wrap: nowrap; flex-grow: 1;">Local time</div>
</div>
<div style="display:flex">
<span style="min-width: 50px;">Timezone:</span>
<select id="timezone_select" style="text-wrap: nowrap; flex-grow: 1;">
<option value="" disabled selected>Select Timezone</option>
</select>
</div>
<button id="timeview_time_upload">Store Browser time into esp and rtc</button>

View File

@@ -8,9 +8,14 @@ export class TimeView {
auto_refresh: HTMLInputElement;
controller: Controller;
timer: NodeJS.Timeout | undefined;
timezoneSelect: HTMLSelectElement;
constructor(controller:Controller) {
(document.getElementById("timeview") as HTMLElement).innerHTML = require("./timeview.html")
this.timezoneSelect = document.getElementById('timezone_select') as HTMLSelectElement;
this.timezoneSelect.onchange = function(){
controller.configChanged()
}
this.auto_refresh = document.getElementById("timeview_auto_refresh") as HTMLInputElement;
this.esp_time = document.getElementById("timeview_esp_time") as HTMLDivElement;
@@ -33,7 +38,7 @@ export class TimeView {
update(native: string, rtc: string) {
this.esp_time.innerText = native;
this.rtc_time.innerText = rtc;
var date = new Date();
const date = new Date();
this.browser_time.innerText = date.toISOString();
if(this.auto_refresh.checked){
this.timer = setTimeout(this.controller.updateRTCData, 1000);
@@ -44,4 +49,26 @@ export class TimeView {
}
}
}
timezones(timezones: string[]) {
timezones.forEach(tz => {
const option = document.createElement('option');
option.value = tz;
option.textContent = tz;
this.timezoneSelect.appendChild(option);
});
}
getTimeZone() {
return this.timezoneSelect.value;
}
setTimeZone(timezone: string | undefined) {
if (timezone != undefined) {
this.timezoneSelect.value = timezone;
} else {
this.timezoneSelect.value = "UTC";
}
}
}

View File

@@ -9,7 +9,7 @@ console.log("Dev server is " + isDevServer);
var host;
if (isDevServer){
//ensure no trailing /
host = 'http://192.168.251.37';
host = 'http://10.23.44.186';
} else {
host = '';
}
@@ -49,7 +49,7 @@ module.exports = {
},
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, '../src/webserver'),
path: path.resolve(__dirname, '.'),
},
devServer: {
}

9
website/.idea/website.iml generated Normal file
View File

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