23 Commits

Author SHA1 Message Date
b0f8bcc9da Merge remote-tracking branch 'origin/develop' into develop
# Conflicts:
#	Software/MainBoard/rust/src/hal/water.rs
2026-05-06 09:29:25 +02:00
103859120c Add initial TODO file with pending tasks for One Wire, Flow Sensor, and PlantProfiles implementation 2026-05-06 09:27:19 +02:00
403517fdb4 Suppress EMI noise on water flow sensor by filtering short pulses 2026-05-06 09:26:14 +02:00
11eb8713bf more startup debugging 2026-05-05 18:04:00 +02:00
d903c2bf52 Refactor flow meter logic: replace global mutex with per-instance flow_unit and use critical-section for thread safety. 2026-05-05 16:27:21 +02:00
f8f76674ce Refactor flow meter handling: switch get_flow_meter_value to get_full_flow_count, update related structs and logic to use u32 for flow values. 2026-05-05 01:14:54 +02:00
3cc5a0d2bd dependency lock upgrade 2026-05-05 00:50:18 +02:00
3be585ecbf Refactor flow meter handling with interrupt-based logic and global state
- Added `flow_interrupt_handler` for efficient interrupt processing.
- Replaced per-instance `flow_counter` with global atomic and mutex-based state (`FLOW_OVERFLOW_COUNTER`, `FLOW_UNIT`).
- Updated flow meter functions to leverage the new architecture for better modularity and thread safety.
- Switched debugging output from `println!` to `log` for improved logging consistency.
2026-05-05 00:50:18 +02:00
5b1a945ac3 Replace blocking http_server call with async task using spawner 2026-05-05 00:50:18 +02:00
f4e050d413 Add ChecksumError handling to FatError conversion 2026-05-05 00:50:18 +02:00
776db785c4 Update hardware and firmware documentation for new modules and features
- Removed outdated TODOs and legacy references in hardware documentation.
- Added details on the new CH32V203-based Sensor Module for CAN bus soil moisture sensors.
- Documented updates to the Battery Management System (CH32V203-based) replacing the older bq34z100 design.
- Refined sensor, pump, and power module descriptions with updated specifications.
- Expanded firmware documentation to include Rust-based ESP32-C6 platform details, new OTA procedure, and MQTT telemetry topics.
- Simplified toolchain setup and compilation process with updated scripts and instructions.
2026-05-05 00:50:18 +02:00
Kai Börnert
ef0ec47d92 Improve CAN bus robustness: adjust NART, add transmission delays for error recovery. 2026-05-04 17:44:54 +02:00
0ed9d6bb57 Adjust timeouts and constants for improved moisture sensor and backup management accuracy
- Increased CAN measurement timeout to 5000ms for reliability.
- Updated `SAVEGAME_SLOT_SIZE` usage in backup handling for consistency.
- Refined moisture sensor frequency thresholds for better sensor calibration (400Hz-70kHz).
2026-05-03 21:03:13 +02:00
4771a77686 Merge branch 'test_new_storage' into develop
# Conflicts:
#	Software/MainBoard/rust/src/main.rs
2026-05-03 14:42:08 +02:00
eef165b6de Track overcurrent issues during pump operation
- Added `overcurrent_ma` field to pump results for capturing overcurrent data.
- Enhanced pumping logic to record and propagate overcurrent issues per plant.
- Updated `PumpState` and `PlantState` to handle overcurrent errors.
2026-05-02 01:38:30 +02:00
1ace878488 Refactor extra1 to fertilizer_pump in HAL and main logic
- Renamed `extra1` method and related calls to `fertilizer_pump` for clarity and better domain alignment.
- Updated HAL implementation to control `extra2` GPIO for fertilizer pump operations.
- Added build script trigger to refresh `VERGEN_BUILD_TIMESTAMP` on each build.
2026-05-01 13:11:47 +02:00
a30d59605d Improve CAN bus error handling and logging
- Enhanced error detection with detailed status logging for bus-off, error warning, and passive errors.
- Added line breaks to CAN and RX error logs for better readability.
- Refined CAN transmission logic and error feedback, including buffer overflow handling.
- Simplified firmware timestamp frame creation and ensured successful sending.
2026-05-01 13:11:37 +02:00
2ee3615dcd switch fertilizer to extra 1 2026-05-01 10:45:54 +02:00
db0f7daa4c feat: add fertilizer cooldown functionality with web UI, HAL integration, and configuration support 2026-04-30 22:09:04 +02:00
6809a37d9d feat: add fertilizer pump functionality with configuration, web UI, and HAL integration 2026-04-30 20:44:07 +02:00
0ca09ed498 feat: add fertilizer pump test functionality with web integration and HAL support 2026-04-30 20:37:07 +02:00
07aed02fe7 fix mqtt not starting webserver 2026-04-06 12:54:18 +02:00
aef0ffd5a1 add v1 revisio of bms 2026-04-06 12:40:52 +02:00
46 changed files with 29789 additions and 2699 deletions

View File

@@ -15,6 +15,7 @@
"vias": 1.0,
"zones": 0.6
},
"prototype_zone_fills": false,
"selection_filter": {
"dimensions": true,
"footprints": true,
@@ -53,6 +54,7 @@
"zone_display_mode": 1
},
"git": {
"integration_disabled": false,
"repo_type": "",
"repo_username": "",
"ssh_key": ""
@@ -105,6 +107,7 @@
"filter_text": "",
"group_by_constraint": false,
"group_by_netclass": false,
"show_time_domain_details": false,
"show_unconnected_nets": false,
"show_zero_pad_nets": false,
"sort_ascending": true,
@@ -115,6 +118,7 @@
"files": []
},
"schematic": {
"hierarchy_collapsed": [],
"selection_filter": {
"graphics": true,
"images": true,
@@ -122,6 +126,7 @@
"lockedItems": false,
"otherItems": true,
"pins": true,
"ruleAreas": true,
"symbols": true,
"text": true,
"wires": true

View File

@@ -3,6 +3,8 @@
"3dviewports": [],
"design_settings": {
"defaults": {
"apply_defaults_to_fp_barcodes": false,
"apply_defaults_to_fp_dimensions": false,
"apply_defaults_to_fp_fields": false,
"apply_defaults_to_fp_shapes": false,
"apply_defaults_to_fp_text": false,
@@ -82,6 +84,7 @@
"extra_footprint": "warning",
"footprint": "error",
"footprint_filters_mismatch": "ignore",
"footprint_symbol_field_mismatch": "warning",
"footprint_symbol_mismatch": "warning",
"footprint_type_mismatch": "ignore",
"hole_clearance": "error",
@@ -99,6 +102,7 @@
"mirrored_text_on_front_layer": "warning",
"missing_courtyard": "ignore",
"missing_footprint": "warning",
"missing_tuning_profile": "warning",
"net_conflict": "warning",
"nonmirrored_text_on_back_layer": "warning",
"npth_inside_courtyard": "ignore",
@@ -118,9 +122,12 @@
"too_many_vias": "error",
"track_angle": "error",
"track_dangling": "warning",
"track_not_centered_on_via": "ignore",
"track_on_post_machined_layer": "error",
"track_segment_length": "error",
"track_width": "error",
"tracks_crossing": "error",
"tuning_profile_track_geometries": "ignore",
"unconnected_items": "error",
"unresolved_variable": "error",
"via_dangling": "warning",
@@ -235,17 +242,28 @@
"zones_allow_external_fillets": false
},
"ipc2581": {
"bom_rev": "",
"dist": "",
"distpn": "",
"internal_id": "",
"mfg": "",
"mpn": ""
"mpn": "",
"sch_revision": ""
},
"layer_pairs": [],
"layer_presets": [],
"viewports": []
},
"boards": [],
"component_class_settings": {
"assignments": [],
"meta": {
"version": 0
},
"sheet_component_classes": {
"enabled": false
}
},
"cvpcb": {
"equivalence_files": []
},
@@ -494,13 +512,14 @@
"priority": 2147483647,
"schematic_color": "rgba(0, 0, 0, 0.000)",
"track_width": 0.2,
"tuning_profile": "",
"via_diameter": 0.6,
"via_drill": 0.3,
"wire_width": 6
}
],
"meta": {
"version": 4
"version": 5
},
"net_colors": null,
"netclass_assignments": null,
@@ -683,6 +702,7 @@
"sort_asc": true,
"sort_field": "Reference"
},
"bus_aliases": {},
"connection_grid_size": 50.0,
"drawing": {
"dashed_lines_dash_length_ratio": 12.0,
@@ -721,7 +741,14 @@
"spice_save_all_dissipations": false,
"spice_save_all_voltages": false,
"subpart_first_id": 65,
"subpart_id_separator": 0
"subpart_id_separator": 0,
"top_level_sheets": [
{
"filename": "MPPT.kicad_sch",
"name": "MPPT",
"uuid": "00000000-0000-0000-0000-000000000000"
}
]
},
"sheets": [
[
@@ -729,5 +756,11 @@
"Root"
]
],
"text_variables": {}
"text_variables": {},
"tuning_profiles": {
"meta": {
"version": 0
},
"tuning_profiles_impedance_geometric": []
}
}

View File

@@ -15,6 +15,7 @@
"vias": 1.0,
"zones": 0.6
},
"prototype_zone_fills": false,
"ratsnest_display_mode": 0,
"selection_filter": {
"dimensions": true,
@@ -54,6 +55,7 @@
"zone_display_mode": 1
},
"git": {
"integration_disabled": false,
"repo_password": "",
"repo_type": "",
"repo_username": "",
@@ -113,6 +115,7 @@
"filter_text": "",
"group_by_constraint": false,
"group_by_netclass": false,
"show_time_domain_details": false,
"show_unconnected_nets": false,
"show_zero_pad_nets": false,
"sort_ascending": true,
@@ -123,6 +126,7 @@
"files": []
},
"schematic": {
"hierarchy_collapsed": [],
"selection_filter": {
"graphics": true,
"images": true,
@@ -130,6 +134,7 @@
"lockedItems": false,
"otherItems": true,
"pins": true,
"ruleAreas": true,
"symbols": true,
"text": true,
"wires": true

View File

@@ -3,6 +3,8 @@
"3dviewports": [],
"design_settings": {
"defaults": {
"apply_defaults_to_fp_barcodes": false,
"apply_defaults_to_fp_dimensions": false,
"apply_defaults_to_fp_fields": false,
"apply_defaults_to_fp_shapes": false,
"apply_defaults_to_fp_text": false,
@@ -78,6 +80,7 @@
"extra_footprint": "warning",
"footprint": "error",
"footprint_filters_mismatch": "ignore",
"footprint_symbol_field_mismatch": "warning",
"footprint_symbol_mismatch": "warning",
"footprint_type_mismatch": "warning",
"hole_clearance": "error",
@@ -96,6 +99,7 @@
"mirrored_text_on_front_layer": "warning",
"missing_courtyard": "ignore",
"missing_footprint": "warning",
"missing_tuning_profile": "warning",
"net_conflict": "warning",
"nonmirrored_text_on_back_layer": "warning",
"npth_inside_courtyard": "ignore",
@@ -115,9 +119,12 @@
"too_many_vias": "error",
"track_angle": "error",
"track_dangling": "warning",
"track_not_centered_on_via": "ignore",
"track_on_post_machined_layer": "error",
"track_segment_length": "error",
"track_width": "error",
"tracks_crossing": "error",
"tuning_profile_track_geometries": "ignore",
"unconnected_items": "error",
"unresolved_variable": "error",
"via_dangling": "warning",
@@ -242,17 +249,28 @@
"zones_use_no_outline": true
},
"ipc2581": {
"bom_rev": "",
"dist": "",
"distpn": "",
"internal_id": "",
"mfg": "",
"mpn": ""
"mpn": "",
"sch_revision": ""
},
"layer_pairs": [],
"layer_presets": [],
"viewports": []
},
"boards": [],
"component_class_settings": {
"assignments": [],
"meta": {
"version": 0
},
"sheet_component_classes": {
"enabled": false
}
},
"cvpcb": {
"equivalence_files": []
},
@@ -443,11 +461,14 @@
"duplicate_sheet_names": "error",
"endpoint_off_grid": "ignore",
"extra_units": "error",
"field_name_whitespace": "warning",
"footprint_filter": "ignore",
"footprint_link_issues": "warning",
"four_way_junction": "ignore",
"global_label_dangling": "warning",
"ground_pin_not_ground": "warning",
"hier_label_mismatch": "error",
"isolated_pin_label": "warning",
"label_dangling": "error",
"label_multiple_wires": "warning",
"lib_symbol_issues": "warning",
@@ -470,6 +491,7 @@
"similar_power": "warning",
"simulation_model_issue": "ignore",
"single_global_label": "warning",
"stacked_pin_name": "warning",
"unannotated": "error",
"unconnected_wire_endpoint": "warning",
"undefined_netclass": "error",
@@ -502,6 +524,7 @@
"priority": 2147483647,
"schematic_color": "rgba(0, 0, 0, 0.000)",
"track_width": 1.2,
"tuning_profile": "",
"via_diameter": 0.8,
"via_drill": 0.4,
"wire_width": 6
@@ -520,6 +543,7 @@
"priority": 0,
"schematic_color": "rgb(255, 4, 6)",
"track_width": 1.0,
"tuning_profile": "",
"via_diameter": 0.8,
"via_drill": 0.4,
"wire_width": 12
@@ -538,6 +562,7 @@
"priority": 1,
"schematic_color": "rgb(255, 153, 0)",
"track_width": 0.2,
"tuning_profile": "",
"via_diameter": 0.8,
"via_drill": 0.4,
"wire_width": 12
@@ -556,6 +581,7 @@
"priority": 2,
"schematic_color": "rgb(81, 255, 3)",
"track_width": 1.2,
"tuning_profile": "",
"via_diameter": 0.8,
"via_drill": 0.4,
"wire_width": 12
@@ -574,6 +600,7 @@
"priority": 3,
"schematic_color": "rgb(130, 130, 130)",
"track_width": 1.2,
"tuning_profile": "",
"via_diameter": 0.8,
"via_drill": 0.4,
"wire_width": 12
@@ -592,13 +619,14 @@
"priority": 4,
"schematic_color": "rgb(0, 0, 0)",
"track_width": 0.5,
"tuning_profile": "",
"via_diameter": 0.8,
"via_drill": 0.4,
"wire_width": 12
}
],
"meta": {
"version": 4
"version": 5
},
"net_colors": null,
"netclass_assignments": null,
@@ -1077,6 +1105,10 @@
},
"schematic": {
"annotate_start_num": 0,
"annotation": {
"method": 0,
"sort_order": 0
},
"bom_export_filename": "PlantCtrlESP32.csv",
"bom_fmt_presets": [],
"bom_fmt_settings": {
@@ -1256,6 +1288,7 @@
"sort_asc": true,
"sort_field": "LCSC_PART_NUMBER"
},
"bus_aliases": {},
"connection_grid_size": 50.0,
"drawing": {
"dashed_lines_dash_length_ratio": 12.0,
@@ -1263,6 +1296,7 @@
"default_line_thickness": 6.0,
"default_text_size": 50.0,
"field_names": [],
"hop_over_size_choice": 0,
"intersheets_ref_own_page": false,
"intersheets_ref_prefix": "",
"intersheets_ref_short": false,
@@ -1295,6 +1329,7 @@
},
"page_layout_descr_file": "",
"plot_directory": "/tmp/",
"reuse_designators": true,
"space_save_all_events": true,
"spice_adjust_passive_values": false,
"spice_current_sheet_as_root": false,
@@ -1304,7 +1339,16 @@
"spice_save_all_dissipations": false,
"spice_save_all_voltages": false,
"subpart_first_id": 65,
"subpart_id_separator": 0
"subpart_id_separator": 0,
"top_level_sheets": [
{
"filename": "PlantCtrlESP32.kicad_sch",
"name": "PlantCtrlESP32",
"uuid": "00000000-0000-0000-0000-000000000000"
}
],
"used_designators": "",
"variants": []
},
"sheets": [
[
@@ -1312,5 +1356,11 @@
"Root"
]
],
"text_variables": {}
"text_variables": {},
"tuning_profiles": {
"meta": {
"version": 0
},
"tuning_profiles_impedance_geometric": []
}
}

View File

@@ -15,6 +15,7 @@
"vias": 1.0,
"zones": 0.6
},
"prototype_zone_fills": false,
"selection_filter": {
"dimensions": true,
"footprints": true,
@@ -53,6 +54,7 @@
"zone_display_mode": 0
},
"git": {
"integration_disabled": false,
"repo_type": "",
"repo_username": "",
"ssh_key": ""
@@ -105,6 +107,7 @@
"filter_text": "",
"group_by_constraint": false,
"group_by_netclass": false,
"show_time_domain_details": false,
"show_unconnected_nets": false,
"show_zero_pad_nets": false,
"sort_ascending": true,
@@ -115,6 +118,7 @@
"files": []
},
"schematic": {
"hierarchy_collapsed": [],
"selection_filter": {
"graphics": true,
"images": true,
@@ -122,6 +126,7 @@
"lockedItems": false,
"otherItems": true,
"pins": true,
"ruleAreas": true,
"symbols": true,
"text": true,
"wires": true

View File

@@ -3,6 +3,8 @@
"3dviewports": [],
"design_settings": {
"defaults": {
"apply_defaults_to_fp_barcodes": false,
"apply_defaults_to_fp_dimensions": false,
"apply_defaults_to_fp_fields": false,
"apply_defaults_to_fp_shapes": false,
"apply_defaults_to_fp_text": false,
@@ -82,6 +84,7 @@
"extra_footprint": "warning",
"footprint": "error",
"footprint_filters_mismatch": "ignore",
"footprint_symbol_field_mismatch": "warning",
"footprint_symbol_mismatch": "warning",
"footprint_type_mismatch": "warning",
"hole_clearance": "error",
@@ -99,6 +102,7 @@
"mirrored_text_on_front_layer": "warning",
"missing_courtyard": "ignore",
"missing_footprint": "warning",
"missing_tuning_profile": "warning",
"net_conflict": "warning",
"nonmirrored_text_on_back_layer": "warning",
"npth_inside_courtyard": "ignore",
@@ -118,9 +122,12 @@
"too_many_vias": "error",
"track_angle": "error",
"track_dangling": "warning",
"track_not_centered_on_via": "ignore",
"track_on_post_machined_layer": "error",
"track_segment_length": "error",
"track_width": "error",
"tracks_crossing": "error",
"tuning_profile_track_geometries": "ignore",
"unconnected_items": "error",
"unresolved_variable": "error",
"via_dangling": "warning",
@@ -236,17 +243,28 @@
"zones_allow_external_fillets": false
},
"ipc2581": {
"bom_rev": "",
"dist": "",
"distpn": "",
"internal_id": "",
"mfg": "",
"mpn": ""
"mpn": "",
"sch_revision": ""
},
"layer_pairs": [],
"layer_presets": [],
"viewports": []
},
"boards": [],
"component_class_settings": {
"assignments": [],
"meta": {
"version": 0
},
"sheet_component_classes": {
"enabled": false
}
},
"cvpcb": {
"equivalence_files": []
},
@@ -495,13 +513,14 @@
"priority": 2147483647,
"schematic_color": "rgba(0, 0, 0, 0.000)",
"track_width": 0.2,
"tuning_profile": "",
"via_diameter": 0.6,
"via_drill": 0.3,
"wire_width": 6
}
],
"meta": {
"version": 4
"version": 5
},
"net_colors": null,
"netclass_assignments": null,
@@ -629,6 +648,7 @@
"sort_asc": true,
"sort_field": "Reference"
},
"bus_aliases": {},
"connection_grid_size": 50.0,
"drawing": {
"dashed_lines_dash_length_ratio": 12.0,
@@ -667,7 +687,14 @@
"spice_save_all_dissipations": false,
"spice_save_all_voltages": false,
"subpart_first_id": 65,
"subpart_id_separator": 0
"subpart_id_separator": 0,
"top_level_sheets": [
{
"filename": "PumpOutput.kicad_sch",
"name": "PumpOutput",
"uuid": "00000000-0000-0000-0000-000000000000"
}
]
},
"sheets": [
[
@@ -675,5 +702,11 @@
"Root"
]
],
"text_variables": {}
"text_variables": {},
"tuning_profiles": {
"meta": {
"version": 0
},
"tuning_profiles_impedance_geometric": []
}
}

View File

@@ -15,6 +15,7 @@
"vias": 1.0,
"zones": 0.6
},
"prototype_zone_fills": false,
"selection_filter": {
"dimensions": true,
"footprints": true,
@@ -54,6 +55,7 @@
"zone_display_mode": 0
},
"git": {
"integration_disabled": false,
"repo_type": "",
"repo_username": "",
"ssh_key": ""
@@ -106,6 +108,7 @@
"filter_text": "",
"group_by_constraint": false,
"group_by_netclass": false,
"show_time_domain_details": false,
"show_unconnected_nets": false,
"show_zero_pad_nets": false,
"sort_ascending": true,
@@ -116,6 +119,7 @@
"files": []
},
"schematic": {
"hierarchy_collapsed": [],
"selection_filter": {
"graphics": true,
"images": true,
@@ -123,6 +127,7 @@
"lockedItems": false,
"otherItems": true,
"pins": true,
"ruleAreas": true,
"symbols": true,
"text": true,
"wires": true

View File

@@ -3,6 +3,8 @@
"3dviewports": [],
"design_settings": {
"defaults": {
"apply_defaults_to_fp_barcodes": false,
"apply_defaults_to_fp_dimensions": false,
"apply_defaults_to_fp_fields": false,
"apply_defaults_to_fp_shapes": false,
"apply_defaults_to_fp_text": false,
@@ -77,6 +79,7 @@
"extra_footprint": "warning",
"footprint": "error",
"footprint_filters_mismatch": "warning",
"footprint_symbol_field_mismatch": "warning",
"footprint_symbol_mismatch": "warning",
"footprint_type_mismatch": "warning",
"hole_clearance": "error",
@@ -94,6 +97,7 @@
"mirrored_text_on_front_layer": "warning",
"missing_courtyard": "warning",
"missing_footprint": "warning",
"missing_tuning_profile": "warning",
"net_conflict": "warning",
"nonmirrored_text_on_back_layer": "warning",
"npth_inside_courtyard": "warning",
@@ -113,9 +117,12 @@
"too_many_vias": "error",
"track_angle": "error",
"track_dangling": "warning",
"track_not_centered_on_via": "ignore",
"track_on_post_machined_layer": "error",
"track_segment_length": "error",
"track_width": "error",
"tracks_crossing": "error",
"tuning_profile_track_geometries": "ignore",
"unconnected_items": "error",
"unresolved_variable": "error",
"via_dangling": "warning",
@@ -227,17 +234,28 @@
"zones_allow_external_fillets": false
},
"ipc2581": {
"bom_rev": "",
"dist": "",
"distpn": "",
"internal_id": "",
"mfg": "",
"mpn": ""
"mpn": "",
"sch_revision": ""
},
"layer_pairs": [],
"layer_presets": [],
"viewports": []
},
"boards": [],
"component_class_settings": {
"assignments": [],
"meta": {
"version": 0
},
"sheet_component_classes": {
"enabled": false
}
},
"cvpcb": {
"equivalence_files": []
},
@@ -486,13 +504,14 @@
"priority": 2147483647,
"schematic_color": "rgba(0, 0, 0, 0.000)",
"track_width": 0.2,
"tuning_profile": "",
"via_diameter": 0.6,
"via_drill": 0.3,
"wire_width": 6
}
],
"meta": {
"version": 4
"version": 5
},
"net_colors": null,
"netclass_assignments": null,
@@ -861,6 +880,7 @@
"sort_asc": true,
"sort_field": "Reference"
},
"bus_aliases": {},
"connection_grid_size": 50.0,
"drawing": {
"dashed_lines_dash_length_ratio": 12.0,
@@ -899,7 +919,14 @@
"spice_save_all_dissipations": false,
"spice_save_all_voltages": false,
"subpart_first_id": 65,
"subpart_id_separator": 0
"subpart_id_separator": 0,
"top_level_sheets": [
{
"filename": "sensor.kicad_sch",
"name": "sensor",
"uuid": "00000000-0000-0000-0000-000000000000"
}
]
},
"sheets": [
[
@@ -907,5 +934,11 @@
"Root"
]
],
"text_variables": {}
"text_variables": {},
"tuning_profiles": {
"meta": {
"version": 0
},
"tuning_profiles_impedance_geometric": []
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -2,7 +2,7 @@
"board": {
"active_layer": 0,
"active_layer_preset": "",
"auto_track_width": true,
"auto_track_width": false,
"hidden_netclasses": [],
"hidden_nets": [],
"high_contrast_mode": 0,
@@ -15,6 +15,7 @@
"vias": 1.0,
"zones": 0.6
},
"prototype_zone_fills": false,
"selection_filter": {
"dimensions": true,
"footprints": true,
@@ -54,6 +55,7 @@
"zone_display_mode": 0
},
"git": {
"integration_disabled": false,
"repo_type": "",
"repo_username": "",
"ssh_key": ""
@@ -63,8 +65,30 @@
"version": 5
},
"net_inspector_panel": {
"col_hidden": [],
"col_order": [],
"col_hidden": [
false,
false,
false,
false,
false,
false,
false,
false,
false,
false
],
"col_order": [
0,
1,
2,
3,
4,
5,
6,
7,
8,
9
],
"col_widths": [],
"custom_group_rules": [],
"expanded_rows": [],
@@ -73,6 +97,7 @@
"filter_text": "",
"group_by_constraint": false,
"group_by_netclass": false,
"show_time_domain_details": false,
"show_unconnected_nets": false,
"show_zero_pad_nets": false,
"sort_ascending": true,
@@ -83,6 +108,7 @@
"files": []
},
"schematic": {
"hierarchy_collapsed": [],
"selection_filter": {
"graphics": true,
"images": true,
@@ -90,6 +116,7 @@
"lockedItems": false,
"otherItems": true,
"pins": true,
"ruleAreas": true,
"symbols": true,
"text": true,
"wires": true

View File

@@ -2,25 +2,262 @@
"board": {
"3dviewports": [],
"design_settings": {
"defaults": {},
"diff_pair_dimensions": [],
"defaults": {
"apply_defaults_to_fp_barcodes": false,
"apply_defaults_to_fp_dimensions": false,
"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": [],
"rules": {},
"track_widths": [],
"via_dimensions": []
"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_field_mismatch": "warning",
"footprint_symbol_mismatch": "warning",
"footprint_type_mismatch": "ignore",
"hole_clearance": "error",
"hole_to_hole": "warning",
"holes_co_located": "warning",
"invalid_outline": "error",
"isolated_copper": "warning",
"item_on_disabled_layer": "error",
"items_not_allowed": "error",
"length_out_of_range": "error",
"lib_footprint_issues": "warning",
"lib_footprint_mismatch": "warning",
"malformed_courtyard": "error",
"microvia_drill_out_of_range": "error",
"mirrored_text_on_front_layer": "warning",
"missing_courtyard": "ignore",
"missing_footprint": "warning",
"missing_tuning_profile": "warning",
"net_conflict": "warning",
"nonmirrored_text_on_back_layer": "warning",
"npth_inside_courtyard": "error",
"padstack": "warning",
"pth_inside_courtyard": "error",
"shorting_items": "error",
"silk_edge_clearance": "ignore",
"silk_over_copper": "warning",
"silk_overlap": "warning",
"skew_out_of_range": "error",
"solder_mask_bridge": "error",
"starved_thermal": "error",
"text_height": "warning",
"text_on_edge_cuts": "error",
"text_thickness": "warning",
"through_hole_pad_without_hole": "error",
"too_many_vias": "error",
"track_angle": "error",
"track_dangling": "warning",
"track_not_centered_on_via": "ignore",
"track_on_post_machined_layer": "error",
"track_segment_length": "error",
"track_width": "error",
"tracks_crossing": "error",
"tuning_profile_track_geometries": "ignore",
"unconnected_items": "error",
"unresolved_variable": "error",
"via_dangling": "warning",
"zones_intersect": "error"
},
"rules": {
"max_error": 0.005,
"min_clearance": 0.0,
"min_connection": 0.0,
"min_copper_edge_clearance": 0.5,
"min_groove_width": 0.0,
"min_hole_clearance": 0.25,
"min_hole_to_hole": 0.25,
"min_microvia_diameter": 0.2,
"min_microvia_drill": 0.1,
"min_resolved_spokes": 2,
"min_silk_clearance": 0.0,
"min_text_height": 0.8,
"min_text_thickness": 0.08,
"min_through_hole_diameter": 0.3,
"min_track_width": 0.2,
"min_via_annular_width": 0.1,
"min_via_diameter": 0.5,
"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.2,
0.5,
1.0,
2.0,
5.0
],
"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
}
],
"zones_allow_external_fillets": false
},
"ipc2581": {
"bom_rev": "",
"dist": "",
"distpn": "",
"internal_id": "",
"mfg": "",
"mpn": ""
"mpn": "",
"sch_revision": ""
},
"layer_pairs": [],
"layer_presets": [],
"viewports": []
},
"boards": [],
"component_class_settings": {
"assignments": [],
"meta": {
"version": 0
},
"sheet_component_classes": {
"enabled": false
}
},
"cvpcb": {
"equivalence_files": []
},
@@ -210,11 +447,14 @@
"duplicate_sheet_names": "error",
"endpoint_off_grid": "warning",
"extra_units": "error",
"field_name_whitespace": "warning",
"footprint_filter": "ignore",
"footprint_link_issues": "warning",
"four_way_junction": "ignore",
"global_label_dangling": "warning",
"ground_pin_not_ground": "warning",
"hier_label_mismatch": "error",
"isolated_pin_label": "warning",
"label_dangling": "error",
"label_multiple_wires": "warning",
"lib_symbol_issues": "warning",
@@ -237,6 +477,7 @@
"similar_power": "warning",
"simulation_model_issue": "ignore",
"single_global_label": "ignore",
"stacked_pin_name": "warning",
"unannotated": "error",
"unconnected_wire_endpoint": "warning",
"undefined_netclass": "error",
@@ -269,13 +510,14 @@
"priority": 2147483647,
"schematic_color": "rgba(0, 0, 0, 0.000)",
"track_width": 0.2,
"tuning_profile": "",
"via_diameter": 0.6,
"via_drill": 0.3,
"wire_width": 6
}
],
"meta": {
"version": 4
"version": 5
},
"net_colors": null,
"netclass_assignments": null,
@@ -297,6 +539,10 @@
},
"schematic": {
"annotate_start_num": 0,
"annotation": {
"method": 0,
"sort_order": 0
},
"bom_export_filename": "${PROJECTNAME}.csv",
"bom_fmt_presets": [],
"bom_fmt_settings": {
@@ -359,15 +605,298 @@
"label": "Datasheet",
"name": "Datasheet",
"show": true
},
{
"group_by": false,
"label": "Actuator/Cap Color",
"name": "Actuator/Cap Color",
"show": false
},
{
"group_by": false,
"label": "Attrition Qty",
"name": "Attrition Qty",
"show": false
},
{
"group_by": false,
"label": "Capacitance",
"name": "Capacitance",
"show": false
},
{
"group_by": false,
"label": "Category",
"name": "Category",
"show": false
},
{
"group_by": false,
"label": "Circuit",
"name": "Circuit",
"show": false
},
{
"group_by": false,
"label": "Class",
"name": "Class",
"show": false
},
{
"group_by": false,
"label": "Contact Current",
"name": "Contact Current",
"show": false
},
{
"group_by": false,
"label": "Diode Configuration",
"name": "Diode Configuration",
"show": false
},
{
"group_by": false,
"label": "Field5",
"name": "Field5",
"show": false
},
{
"group_by": false,
"label": "Forward Voltage (Vf@If)",
"name": "Forward Voltage (Vf@If)",
"show": false
},
{
"group_by": false,
"label": "Insulation Resistance",
"name": "Insulation Resistance",
"show": false
},
{
"group_by": false,
"label": "LCSC",
"name": "LCSC",
"show": false
},
{
"group_by": false,
"label": "LCSC_PART_NUMBER",
"name": "LCSC_PART_NUMBER",
"show": false
},
{
"group_by": false,
"label": "Manufacturer",
"name": "Manufacturer",
"show": false
},
{
"group_by": false,
"label": "Mechanical Life",
"name": "Mechanical Life",
"show": false
},
{
"group_by": false,
"label": "Minimum Qty",
"name": "Minimum Qty",
"show": false
},
{
"group_by": false,
"label": "Mounting Style",
"name": "Mounting Style",
"show": false
},
{
"group_by": false,
"label": "Operating Force",
"name": "Operating Force",
"show": false
},
{
"group_by": false,
"label": "Operating Temperature",
"name": "Operating Temperature",
"show": false
},
{
"group_by": false,
"label": "Operating Temperature Range",
"name": "Operating Temperature Range",
"show": false
},
{
"group_by": false,
"label": "Overload Voltage (Max)",
"name": "Overload Voltage (Max)",
"show": false
},
{
"group_by": false,
"label": "Part",
"name": "Part",
"show": false
},
{
"group_by": false,
"label": "Pin Style",
"name": "Pin Style",
"show": false
},
{
"group_by": false,
"label": "Power(Watts)",
"name": "Power(Watts)",
"show": false
},
{
"group_by": false,
"label": "Price",
"name": "Price",
"show": false
},
{
"group_by": false,
"label": "Process",
"name": "Process",
"show": false
},
{
"group_by": false,
"label": "Rectified Current",
"name": "Rectified Current",
"show": false
},
{
"group_by": false,
"label": "Resistance",
"name": "Resistance",
"show": false
},
{
"group_by": false,
"label": "Reverse Leakage Current",
"name": "Reverse Leakage Current",
"show": false
},
{
"group_by": false,
"label": "Reverse Voltage (Vr)",
"name": "Reverse Voltage (Vr)",
"show": false
},
{
"group_by": false,
"label": "Sim.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": "Stock",
"name": "Stock",
"show": false
},
{
"group_by": false,
"label": "Strike Gundam",
"name": "Strike Gundam",
"show": false
},
{
"group_by": false,
"label": "Switch Height",
"name": "Switch Height",
"show": false
},
{
"group_by": false,
"label": "Switch Length",
"name": "Switch Length",
"show": false
},
{
"group_by": false,
"label": "Switch Width",
"name": "Switch Width",
"show": false
},
{
"group_by": false,
"label": "Temperature Coefficient",
"name": "Temperature Coefficient",
"show": false
},
{
"group_by": false,
"label": "Tolerance",
"name": "Tolerance",
"show": false
},
{
"group_by": false,
"label": "Type",
"name": "Type",
"show": false
},
{
"group_by": false,
"label": "Voltage Rated",
"name": "Voltage Rated",
"show": false
},
{
"group_by": false,
"label": "Voltage Rating (Dc)",
"name": "Voltage Rating (Dc)",
"show": false
},
{
"group_by": false,
"label": "With Lamp",
"name": "With Lamp",
"show": false
},
{
"group_by": false,
"label": "Actuator Style",
"name": "Actuator Style",
"show": false
},
{
"group_by": false,
"label": "Description",
"name": "Description",
"show": false
},
{
"group_by": false,
"label": "#",
"name": "${ITEM_NUMBER}",
"show": false
}
],
"filter_string": "",
"group_symbols": true,
"include_excluded_from_bom": true,
"name": "Default Editing",
"name": "",
"sort_asc": true,
"sort_field": "Reference"
},
"bus_aliases": {},
"connection_grid_size": 50.0,
"drawing": {
"dashed_lines_dash_length_ratio": 12.0,
@@ -375,6 +904,7 @@
"default_line_thickness": 6.0,
"default_text_size": 50.0,
"field_names": [],
"hop_over_size_choice": 0,
"intersheets_ref_own_page": false,
"intersheets_ref_prefix": "",
"intersheets_ref_short": false,
@@ -398,6 +928,7 @@
"net_format_name": "",
"page_layout_descr_file": "",
"plot_directory": "",
"reuse_designators": true,
"space_save_all_events": true,
"spice_current_sheet_as_root": false,
"spice_external_command": "spice \"%I\"",
@@ -406,13 +937,28 @@
"spice_save_all_dissipations": false,
"spice_save_all_voltages": false,
"subpart_first_id": 65,
"subpart_id_separator": 0
"subpart_id_separator": 0,
"top_level_sheets": [
{
"filename": "bms.kicad_sch",
"name": "bms",
"uuid": "7972d0e7-2611-420d-b298-ef8307db6186"
}
],
"used_designators": "",
"variants": []
},
"sheets": [
[
"7972d0e7-2611-420d-b298-ef8307db6186",
"Root"
"bms"
]
],
"text_variables": {}
"text_variables": {},
"tuning_profiles": {
"meta": {
"version": 0
},
"tuning_profiles_impedance_geometric": []
}
}

File diff suppressed because it is too large Load Diff

View File

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

View File

@@ -0,0 +1,337 @@
(footprint "AMASS_XT30UPB+DATA-M_1x02_P5.0mm_Vertical"
(version 20240108)
(generator "pcbnew")
(generator_version "8.0")
(layer "F.Cu")
(descr "Connector XT30 Vertical PCB Male, https://www.tme.eu/en/Document/4acc913878197f8c2e30d4b8cdc47230/XT30UPB%20SPEC.pdf")
(tags "RC Connector XT30")
(property "Reference" "REF**"
(at 2.5 -4 0)
(layer "F.SilkS")
(uuid "f7510d54-dcb1-4c3b-b842-cd250a98370c")
(effects
(font
(size 1 1)
(thickness 0.15)
)
)
)
(property "Value" "AMASS_XT30UPB+DATA-M_1x02_P5.0mm_Vertical"
(at 2.5 4 0)
(layer "F.Fab")
(uuid "c5a8a60c-4ea1-4401-a30c-34d36be61c07")
(effects
(font
(size 1 1)
(thickness 0.15)
)
)
)
(property "Footprint" ""
(at 0 0 0)
(unlocked yes)
(layer "F.Fab")
(hide yes)
(uuid "8fb27306-c085-4316-b554-4ba9be794054")
(effects
(font
(size 1.27 1.27)
(thickness 0.15)
)
)
)
(property "Datasheet" ""
(at 0 0 0)
(unlocked yes)
(layer "F.Fab")
(hide yes)
(uuid "74b71861-05d2-4229-8e81-25952aaaef7e")
(effects
(font
(size 1.27 1.27)
(thickness 0.15)
)
)
)
(property "Description" ""
(at 0 0 0)
(unlocked yes)
(layer "F.Fab")
(hide yes)
(uuid "a1d16ecc-7e64-48c4-b772-a9255380960d")
(effects
(font
(size 1.27 1.27)
(thickness 0.15)
)
)
)
(attr through_hole)
(fp_line
(start -2.71 -1.41)
(end -2.71 1.41)
(stroke
(width 0.12)
(type solid)
)
(layer "F.SilkS")
(uuid "e96c6ad2-9ca6-4df1-b35b-76e090d7ff4e")
)
(fp_line
(start -2.71 -1.41)
(end -1.01 -2.71)
(stroke
(width 0.12)
(type solid)
)
(layer "F.SilkS")
(uuid "0784d204-0a48-4a2b-8085-50e1ff7a1493")
)
(fp_line
(start -2.71 1.41)
(end -1.01 2.71)
(stroke
(width 0.12)
(type solid)
)
(layer "F.SilkS")
(uuid "db750970-e424-4a8e-a882-20a90baabffc")
)
(fp_line
(start -1.01 -2.71)
(end 7.71 -2.71)
(stroke
(width 0.12)
(type solid)
)
(layer "F.SilkS")
(uuid "9f23420e-db87-438c-a708-676a0616966e")
)
(fp_line
(start -1.01 2.71)
(end 7.71 2.71)
(stroke
(width 0.12)
(type solid)
)
(layer "F.SilkS")
(uuid "2ba574e3-9de5-4d7d-9777-e19e6fa702e7")
)
(fp_line
(start 7.71 -2.71)
(end 7.71 2.71)
(stroke
(width 0.12)
(type solid)
)
(layer "F.SilkS")
(uuid "2018cc6b-6115-4763-8b7c-54b60affbda7")
)
(fp_rect
(start -6.3 -2.71)
(end 7.71 2.7)
(stroke
(width 0.1)
(type default)
)
(fill none)
(layer "F.SilkS")
(uuid "11aac399-c862-4b67-9828-087abeea5b1b")
)
(fp_line
(start -3.1 -1.8)
(end -3.1 1.8)
(stroke
(width 0.05)
(type solid)
)
(layer "F.CrtYd")
(uuid "06ae69d8-1372-4524-8b1a-27a2f062f1c5")
)
(fp_line
(start -3.1 -1.8)
(end -1.4 -3.1)
(stroke
(width 0.05)
(type solid)
)
(layer "F.CrtYd")
(uuid "ae869a92-c688-4f2b-82ca-0578106a035a")
)
(fp_line
(start -3.1 1.8)
(end -1.4 3.1)
(stroke
(width 0.05)
(type solid)
)
(layer "F.CrtYd")
(uuid "cac6d927-6ba1-4095-825b-f94ee0d7abe9")
)
(fp_line
(start -1.4 -3.1)
(end 8.1 -3.1)
(stroke
(width 0.05)
(type solid)
)
(layer "F.CrtYd")
(uuid "fb4fa373-5492-4717-a9fe-7b69f4c53ba0")
)
(fp_line
(start -1.4 3.1)
(end 8.1 3.1)
(stroke
(width 0.05)
(type solid)
)
(layer "F.CrtYd")
(uuid "70296c77-546d-44a2-b5d3-e6dc58cf713b")
)
(fp_line
(start 8.1 -3.1)
(end 8.1 3.1)
(stroke
(width 0.05)
(type solid)
)
(layer "F.CrtYd")
(uuid "64764a09-de32-4f35-b54a-17e44810370f")
)
(fp_line
(start -2.6 -1.3)
(end -2.6 1.3)
(stroke
(width 0.1)
(type solid)
)
(layer "F.Fab")
(uuid "8d7ee7cb-5dda-453f-aa9a-6420c87f1b8e")
)
(fp_line
(start -2.6 -1.3)
(end -0.9 -2.6)
(stroke
(width 0.1)
(type solid)
)
(layer "F.Fab")
(uuid "2fa3ad90-36bb-4374-95ed-e44e50c7e385")
)
(fp_line
(start -2.6 1.3)
(end -0.9 2.6)
(stroke
(width 0.1)
(type solid)
)
(layer "F.Fab")
(uuid "2e4f8556-ffc2-4791-91da-e68c3513337e")
)
(fp_line
(start -0.9 -2.6)
(end 7.6 -2.6)
(stroke
(width 0.1)
(type solid)
)
(layer "F.Fab")
(uuid "cb9cd8af-1997-41db-b9fe-8982960ac6db")
)
(fp_line
(start -0.9 2.6)
(end 7.6 2.6)
(stroke
(width 0.1)
(type solid)
)
(layer "F.Fab")
(uuid "ea8a6c02-e974-4677-a854-a0891c323245")
)
(fp_line
(start 7.6 -2.6)
(end 7.6 2.6)
(stroke
(width 0.1)
(type solid)
)
(layer "F.Fab")
(uuid "c66f305c-0d56-4591-bc02-23252ad20321")
)
(fp_text user "-"
(at -4 0 0)
(layer "F.SilkS")
(uuid "c119570a-6846-48dc-9422-0b5665ab2df6")
(effects
(font
(size 1.5 1.5)
(thickness 0.15)
)
)
)
(fp_text user "+"
(at 9 0 0)
(layer "F.SilkS")
(uuid "d6ab678c-47f7-47e0-869b-ef3b9dbd1ba9")
(effects
(font
(size 1.5 1.5)
(thickness 0.15)
)
)
)
(fp_text user "${REFERENCE}"
(at 2.5 0 0)
(layer "F.Fab")
(uuid "a70efd12-1491-4664-ae98-5b2b7f52a502")
(effects
(font
(size 1 1)
(thickness 0.15)
)
)
)
(pad "1" thru_hole rect
(at 0 0)
(size 3 3)
(drill 1.8)
(layers "*.Cu" "*.Mask")
(remove_unused_layers no)
(uuid "3a0f3b23-814b-4df9-a02c-3d9fed9e23c9")
)
(pad "2" thru_hole circle
(at 5 0)
(size 3 3)
(drill 1.8)
(layers "*.Cu" "*.Mask")
(remove_unused_layers no)
(uuid "d897d74a-a13b-47cf-9806-eb8a75fe8d08")
)
(pad "3" thru_hole circle
(at -3.9 -1)
(size 1.524 1.524)
(drill 1)
(layers "*.Cu" "*.Mask")
(remove_unused_layers no)
(uuid "02a8c3fc-d75c-47a4-a907-f9191ff19e2c")
)
(pad "4" thru_hole circle
(at -3.9 1)
(size 1.524 1.524)
(drill 1)
(layers "*.Cu" "*.Mask")
(remove_unused_layers no)
(uuid "2000b5b8-f7c9-40a8-9010-65c16d2aefce")
)
(model "${KICAD8_3DMODEL_DIR}/Connector_AMASS.3dshapes/AMASS_XT30UPB-M_1x02_P5.0mm_Vertical.wrl"
(offset
(xyz 0 0 0)
)
(scale
(xyz 1 1 1)
)
(rotate
(xyz 0 0 0)
)
)
)

View File

@@ -0,0 +1,4 @@
(fp_lib_table
(version 7)
(lib (name "amass") (type "KiCad") (uri "${KIPRJMOD}/footprints/amass") (options "") (descr ""))
)

View File

@@ -256,12 +256,12 @@ async fn main(spawner: Spawner) {
// Improve CAN robustness for longer cables:
// 1. Enable Automatic Bus-Off Management (ABOM)
// 2. Disable Automatic Retransmission (NART) as we send regular measurements anyway
// 2. Enable Automatic Retransmission (NART) to recover from transient errors
// 3. Enable Receive FIFO Overwrite Mode (RFLM = 0, default)
// 4. Increase Resync Jump Width (SJW) if possible by patching BTIMR
hal::pac::CAN1.ctlr().modify(|w| {
w.set_abom(false);
w.set_nart(true);
w.set_nart(false);
});
// SJW is bits 24-25 of BTIMR. HAL sets it to 0 (SJW=1).
@@ -415,7 +415,7 @@ async fn can_task(
Timer::after_millis(100).await;
}
let mut msg: heapless::String<128> = heapless::String::new();
let _ = write!(&mut msg, "rx err {:?}", err);
let _ = write!(&mut msg, "rx err {:?} \r\n", err);
log(msg);
}
}
@@ -429,14 +429,25 @@ async fn can_task(
}
}
// Check CAN error status register for bus-off condition
if hal::pac::CAN1.errsr().read().boff() {
blink_error_loop(info, warn, 3, 3).await; // Bus-off error
}
while let Ok(mut frame) = CAN_TX_CH.try_receive() {
match can.transmit(&mut frame) {
Ok(..) => {
Ok(_ok) => {
let status = hal::pac::CAN1.errsr().read();
// Check CAN error status register for bus-off condition
if status.boff() || status.ewgf() || status.epvf() {
let mut msg: heapless::String<128> = heapless::String::new();
let _ = write!(&mut msg, "canbus status {} {} {} \r\n", status.boff(), status.ewgf(), status.epvf());
log(msg);
for _ in 0..2 {
warn.set_high();
Timer::after_millis(100).await;
warn.set_low();
Timer::after_millis(100).await;
}
}
}
Err(nb::Error::WouldBlock) => {
for _ in 0..2 {
@@ -446,7 +457,7 @@ async fn can_task(
Timer::after_millis(100).await;
}
let mut msg: heapless::String<128> = heapless::String::new();
let _ = write!(&mut msg, "canbus out buffer full");
let _ = write!(&mut msg, "canbus out buffer full \r\n");
log(msg);
}
Err(nb::Error::Other(err)) => {
@@ -457,7 +468,7 @@ async fn can_task(
Timer::after_millis(100).await;
}
let mut msg: heapless::String<128> = heapless::String::new();
let _ = write!(&mut msg, "tx err {:?}", err);
let _ = write!(&mut msg, "tx err {:?} \r\n", err);
log(msg);
}
}
@@ -516,7 +527,7 @@ async fn worker(
loop {
let mut total_pulses: u32 = 0;
for _ in 0..AVG_WINDOWS {
for _window in 0..AVG_WINDOWS {
// Count rising edges of Q in a 100 ms window
let start = Instant::now();
let mut pulses: u32 = 0;
@@ -582,13 +593,16 @@ async fn worker(
log(msg);
let moisture = CanFrame::new(moisture_id, &(freq_hz as u32).to_be_bytes()).unwrap();
let delay_ms = moisture_id.as_raw() as u64 % 50;
Timer::after(Duration::from_millis(delay_ms)).await;
CAN_TX_CH.send(moisture).await;
// Send firmware build timestamp after each measurement so the controller
// always has up-to-date build info without requiring an identify request.
if let Some(build_frame) = CanFrame::new(firmware_build_id, &FIRMWARE_BUILD_MINUTES.to_be_bytes()) {
CAN_TX_CH.send(build_frame).await;
}
let firmware = CanFrame::new(firmware_build_id, &FIRMWARE_BUILD_MINUTES.to_be_bytes()).unwrap();
let delay_ms = firmware_build_id.as_raw() as u64 % 50;
Timer::after(Duration::from_millis(delay_ms)).await;
CAN_TX_CH.send(firmware).await;
// Wait for the other slot to measure, plus gaps to ensure no overlap
// After A finishes measuring: wait 50ms (gap) + 400ms (B measures) + 50ms (gap) = 500ms

File diff suppressed because it is too large Load Diff

View File

@@ -101,6 +101,7 @@ chrono-tz = { version = "0.10.4", default-features = false, features = ["filter-
heapless = { version = "0.7.17", features = ["serde"] } # stay in sync with mcutie version
static_cell = "2.1.1"
portable-atomic = "1.11.1"
critical-section = "1"
crc = "3.3.0"
bytemuck = { version = "1.24.0", features = ["derive", "min_const_generics", "pod_saturating", "extern_crate_alloc"] }
deranged = "0.5.5"

View File

@@ -0,0 +1,3 @@
One Wire does not seem to work.
Flow Sensor does not seem to work.
PlantProfiles with a dry out phase needs to be implemented + Memory for this

View File

@@ -49,5 +49,8 @@ fn linker_be_nice() {
fn main() {
linker_be_nice();
// Non-existent path causes Cargo to always re-run this script,
// keeping VERGEN_BUILD_TIMESTAMP fresh on every build.
println!("cargo:rerun-if-changed=ALWAYS_REBUILD_SENTINEL");
let _ = EmitBuilder::builder().all_git().all_build().emit();
}

View File

@@ -129,6 +129,8 @@ pub struct PlantConfig {
pub min_pump_current_ma: u16,
pub max_pump_current_ma: u16,
pub ignore_current_error: bool,
pub fertilizer_s: u16,
pub fertilizer_cooldown_min: u16,
}
impl Default for PlantConfig {
@@ -150,6 +152,8 @@ impl Default for PlantConfig {
min_pump_current_ma: 10,
max_pump_current_ma: 3000,
ignore_current_error: true,
fertilizer_s: 0,
fertilizer_cooldown_min: 1440, // 1 day default
}
}
}

View File

@@ -316,9 +316,12 @@ impl From<sntpc::Error> for FatError {
impl From<BmsProtocolError> for FatError {
fn from(value: BmsProtocolError) -> Self {
match value {
BmsProtocolError::I2cCommunicationError => FatError::String {
BmsProtocolError::I2cCommunicationError =>FatError::String {
error: "I2C communication error".to_string(),
},
BmsProtocolError::ChecksumError => FatError::String {
error: "BMS checksum error".to_string(),
},
}
}
}

View File

@@ -51,6 +51,8 @@ static mut LAST_WATERING_TIMESTAMP: [i64; PLANT_COUNT] = [0; PLANT_COUNT];
#[esp_hal::ram(unstable(rtc_fast), unstable(persistent))]
static mut CONSECUTIVE_WATERING_PLANT: [u32; PLANT_COUNT] = [0; PLANT_COUNT];
#[esp_hal::ram(unstable(rtc_fast), unstable(persistent))]
static mut LAST_FERTILIZER_TIMESTAMP: [i64; PLANT_COUNT] = [0; PLANT_COUNT];
#[esp_hal::ram(unstable(rtc_fast), unstable(persistent))]
static mut LOW_VOLTAGE_DETECTED: i8 = 0;
#[esp_hal::ram(unstable(rtc_fast), unstable(persistent))]
static mut RESTART_TO_CONF: i8 = 0;
@@ -342,6 +344,14 @@ impl Esp<'_> {
LAST_WATERING_TIMESTAMP[plant] = time.timestamp_millis();
}
}
pub(crate) fn last_fertilizer_time(&self, plant: usize) -> i64 {
unsafe { LAST_FERTILIZER_TIMESTAMP[plant] }
}
pub(crate) fn store_last_fertilizer_time(&mut self, plant: usize, time: DateTime<Utc>) {
unsafe {
LAST_FERTILIZER_TIMESTAMP[plant] = time.timestamp_millis();
}
}
pub(crate) fn set_low_voltage_in_cycle(&mut self) {
unsafe {
LOW_VOLTAGE_DETECTED = 1;

View File

@@ -164,6 +164,7 @@ pub trait BoardInteraction<'a> {
async fn get_mptt_voltage(&mut self) -> FatResult<Voltage>;
async fn get_mptt_current(&mut self) -> FatResult<Current>;
async fn can_power(&mut self, state: bool) -> FatResult<()>;
async fn fertilizer_pump(&mut self, enable: bool) -> FatResult<()>;
async fn backup_config(&mut self, config: &PlantControllerConfig) -> FatResult<()>;
async fn read_backup(&mut self) -> FatResult<PlantControllerConfig>;
@@ -175,12 +176,7 @@ pub trait BoardInteraction<'a> {
}
/// Return the last known firmware build timestamps per sensor, set during detect_sensors.
fn get_sensor_build_minutes(
&self,
) -> (
[Option<u32>; PLANT_COUNT],
[Option<u32>; PLANT_COUNT],
) {
fn get_sensor_build_minutes(&self) -> ([Option<u32>; PLANT_COUNT], [Option<u32>; PLANT_COUNT]) {
([None; PLANT_COUNT], [None; PLANT_COUNT])
}
@@ -294,7 +290,8 @@ impl PlantHal {
error: format!("Could not init wifi: {:?}", e),
})?;
let pcnt_module = Pcnt::new(peripherals.PCNT);
let mut pcnt_module = Pcnt::new(peripherals.PCNT);
pcnt_module.set_interrupt_handler(water::flow_interrupt_handler);
let free_pins = FreePeripherals {
gpio0: peripherals.GPIO0,

View File

@@ -161,9 +161,11 @@ pub(crate) async fn create_v4(
info!("Start v4");
let mut awake = Output::new(peripherals.gpio21, Level::High, OutputConfig::default());
awake.set_high();
info!("v4: gpio21 awake ok");
let mut general_fault = Output::new(peripherals.gpio23, Level::Low, OutputConfig::default());
general_fault.set_low();
info!("v4: gpio23 general_fault ok");
let twai_config = Some(TwaiConfiguration::new(
peripherals.twai,
@@ -172,17 +174,24 @@ pub(crate) async fn create_v4(
TWAI_BAUDRATE,
TwaiMode::Normal,
));
info!("v4: twai config ok");
let extra1 = Output::new(peripherals.gpio6, Level::Low, OutputConfig::default());
info!("v4: gpio6 extra1 ok");
let extra2 = Output::new(peripherals.gpio15, Level::Low, OutputConfig::default());
info!("v4: gpio15 extra2 ok");
let one_wire_pin = Flex::new(peripherals.gpio18);
info!("v4: gpio18 one_wire ok");
let tank_power_pin = Output::new(peripherals.gpio11, Level::Low, OutputConfig::default());
info!("v4: gpio11 tank_power ok");
let flow_sensor_pin = Input::new(
peripherals.gpio4,
InputConfig::default().with_pull(Pull::Up),
);
info!("v4: gpio4 flow_sensor ok");
info!("v4: creating tank sensor");
let tank_sensor = TankSensor::create(
one_wire_pin,
peripherals.adc1,
@@ -191,12 +200,17 @@ pub(crate) async fn create_v4(
flow_sensor_pin,
peripherals.pcnt1,
)?;
info!("v4: tank sensor ok");
let can_power = Output::new(peripherals.gpio22, Level::Low, OutputConfig::default());
info!("v4: gpio22 can_power ok");
let solar_is_day = Input::new(peripherals.gpio7, InputConfig::default());
info!("v4: gpio7 solar_is_day ok");
let light = Output::new(peripherals.gpio10, Level::Low, Default::default());
info!("v4: gpio10 light ok");
let charge_indicator = Output::new(peripherals.gpio3, Level::Low, Default::default());
info!("v4: gpio3 charge_indicator ok");
info!("Start pump expander");
let pump_device = I2cDevice::new(I2C_DRIVER.get().await);
@@ -391,7 +405,7 @@ impl<'a> BoardInteraction<'a> for V4<'a> {
let mut moistures = Moistures::default();
let _ = wait_for_can_measurements(&mut twai, &mut moistures)
.with_timeout(Duration::from_millis(1000))
.with_timeout(Duration::from_millis(5000))
.await;
Ok(moistures)
})
@@ -484,6 +498,15 @@ impl<'a> BoardInteraction<'a> for V4<'a> {
Ok(())
}
async fn fertilizer_pump(&mut self, enable: bool) -> FatResult<()> {
if enable {
self.extra2.set_high();
} else {
self.extra2.set_low();
}
Ok(())
}
async fn backup_config(&mut self, controller_config: &PlantControllerConfig) -> FatResult<()> {
let mut buffer: [u8; 4096 - BACKUP_HEADER_MAX_SIZE] = [0; 4096 - BACKUP_HEADER_MAX_SIZE];
let length = postcard::to_slice(controller_config, &mut buffer)?.len();
@@ -643,9 +666,7 @@ impl<'a> BoardInteraction<'a> for V4<'a> {
}
}
fn get_sensor_build_minutes(
&self,
) -> ([Option<u32>; PLANT_COUNT], [Option<u32>; PLANT_COUNT]) {
fn get_sensor_build_minutes(&self) -> ([Option<u32>; PLANT_COUNT], [Option<u32>; PLANT_COUNT]) {
(self.sensor_a_build_minutes, self.sensor_b_build_minutes)
}
}

View File

@@ -10,17 +10,20 @@ use esp_hal::pcnt::channel::EdgeMode::{Hold, Increment};
use esp_hal::pcnt::unit::Unit;
use esp_hal::peripherals::GPIO5;
use esp_hal::Async;
use esp_println::println;
use log::info;
use onewire::{ds18b20, Device, DeviceSearch, OneWire, DS18B20};
use portable_atomic::{AtomicUsize, Ordering};
unsafe impl Send for TankSensor<'_> {}
static FLOW_OVERFLOW_COUNTER: AtomicUsize = AtomicUsize::new(0);
pub struct TankSensor<'a> {
one_wire_bus: OneWire<Flex<'a>>,
tank_channel: Adc<'a, ADC1<'a>, Async>,
tank_power: Output<'a>,
tank_pin: AdcPin<GPIO5<'a>, ADC1<'a>, AdcCalLine<ADC1<'a>>>,
flow_counter: Unit<'a, 1>,
flow_unit: Unit<'static, 1>,
}
impl<'a> TankSensor<'a> {
@@ -30,7 +33,7 @@ impl<'a> TankSensor<'a> {
gpio5: GPIO5<'a>,
tank_power: Output<'a>,
flow_sensor: Input,
pcnt1: Unit<'a, 1>,
pcnt1: Unit<'static, 1>,
) -> Result<TankSensor<'a>, FatError> {
one_wire_pin.apply_output_config(
&OutputConfig::default()
@@ -41,47 +44,76 @@ impl<'a> TankSensor<'a> {
one_wire_pin.set_high();
one_wire_pin.set_input_enable(true);
one_wire_pin.set_output_enable(true);
info!("tank: one_wire pin config ok");
let mut adc1_config = AdcConfig::new();
info!("tank: adc config created");
let tank_pin =
adc1_config.enable_pin_with_cal::<_, AdcCalLine<_>>(gpio5, Attenuation::_11dB);
info!("tank: adc pin cal ok");
let tank_channel = Adc::new(adc1, adc1_config).into_async();
info!("tank: adc channel ok");
let one_wire_bus = OneWire::new(one_wire_pin, false);
info!("tank: one_wire bus ok");
pcnt1.set_high_limit(Some(i16::MAX))?;
info!("tank: pcnt high limit ok");
// Reject pulses shorter than ~12.8 µs (1023 APB cycles @ 80 MHz) to suppress EMI noise
// on the sensor cable. Real flow pulses are in the millisecond range.
pcnt1.set_filter(Some(1023)).unwrap();
let ch0 = &pcnt1.channel0;
ch0.set_edge_signal(flow_sensor.peripheral_input());
info!("tank: pcnt edge signal ok");
ch0.set_input_mode(Hold, Increment);
ch0.set_ctrl_mode(Keep, Keep);
info!("tank: pcnt input/ctrl mode ok");
pcnt1.listen();
info!("tank: pcnt listen ok");
Ok(TankSensor {
one_wire_bus,
tank_channel,
tank_power,
tank_pin,
flow_counter: pcnt1,
flow_unit: pcnt1,
})
}
pub fn reset_flow_meter(&mut self) {
self.flow_counter.pause();
self.flow_counter.clear();
// Pause, clear counter, clear any pending interrupt, then reset the overflow counter
// all inside a single critical section to prevent a race where the interrupt fires
// between the overflow reset and the pause.
critical_section::with(|_| {
self.flow_unit.pause();
self.flow_unit.clear();
self.flow_unit.reset_interrupt();
FLOW_OVERFLOW_COUNTER.store(0, Ordering::SeqCst);
});
}
pub fn start_flow_meter(&mut self) {
self.flow_counter.resume();
}
pub fn get_flow_meter_value(&mut self) -> i16 {
self.flow_counter.value()
self.flow_unit.resume();
}
pub fn stop_flow_meter(&mut self) -> i16 {
self.flow_counter.pause();
self.get_flow_meter_value()
critical_section::with(|_| {
let val = self.flow_unit.value();
self.flow_unit.pause();
val
})
}
pub fn get_full_flow_count(&self) -> u32 {
// Read both values inside a single critical section so an overflow interrupt cannot
// fire between the two reads and produce an inconsistent result.
critical_section::with(|_| {
let overflowed = FLOW_OVERFLOW_COUNTER.load(Ordering::SeqCst) as u32;
let current = self.flow_unit.value() as u32;
overflowed * (i16::MAX as u32 + 1) + current
})
}
pub async fn water_temperature_c(&mut self) -> Result<f32, FatError> {
@@ -90,9 +122,9 @@ impl<'a> TankSensor<'a> {
let mut delay = Delay::new();
let presence = self.one_wire_bus.reset(&mut delay)?;
println!("OneWire: reset presence pulse = {}", presence);
info!("OneWire: reset presence pulse = {}", presence);
if !presence {
println!("OneWire: no device responded to reset — check pull-up resistor and wiring");
info!("OneWire: no device responded to reset — check pull-up resistor and wiring");
}
let mut search = DeviceSearch::new();
@@ -100,7 +132,7 @@ impl<'a> TankSensor<'a> {
let mut devices_found = 0u8;
while let Some(device) = self.one_wire_bus.search_next(&mut search, &mut delay)? {
devices_found += 1;
println!(
info!(
"OneWire: found device #{} family=0x{:02X} addr={:02X?}",
devices_found, device.address[0], device.address
);
@@ -108,16 +140,16 @@ impl<'a> TankSensor<'a> {
water_temp_sensor = Some(device);
break;
} else {
println!("OneWire: skipping device — not a DS18B20 (family 0x{:02X} != 0x{:02X})", device.address[0], ds18b20::FAMILY_CODE);
info!("OneWire: skipping device — not a DS18B20 (family 0x{:02X} != 0x{:02X})", device.address[0], ds18b20::FAMILY_CODE);
}
}
if devices_found == 0 {
println!("OneWire: search found zero devices on the bus");
info!("OneWire: search found zero devices on the bus");
}
match water_temp_sensor {
Some(device) => {
println!("Found one wire device: {:?}", device);
info!("Found one wire device: {:?}", device);
let mut water_temp_sensor = DS18B20::new(device)?;
let water_temp: Result<f32, FatError> = loop {
@@ -126,11 +158,11 @@ impl<'a> TankSensor<'a> {
.await;
match &temp {
Ok(res) => {
println!("Water temp is {}", res);
info!("Water temp is {}", res);
break temp;
}
Err(err) => {
println!("Could not get water temp {} attempt {}", err, attempt)
info!("Could not get water temp {} attempt {}", err, attempt)
}
}
if attempt == 5 {
@@ -178,3 +210,15 @@ impl<'a> TankSensor<'a> {
Ok(median_mv / 1000.0)
}
}
#[esp_hal::handler]
pub fn flow_interrupt_handler() {
use esp_hal::peripherals::PCNT;
let pcnt = PCNT::regs();
if pcnt.int_raw().read().cnt_thr_event_u(1).bit() {
if pcnt.u_status(1).read().h_lim().bit() {
FLOW_OVERFLOW_COUNTER.fetch_add(1, Ordering::SeqCst);
}
pcnt.int_clr().write(|w| w.cnt_thr_event_u(1).set_bit());
}
}

View File

@@ -4,7 +4,7 @@ use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex;
use embassy_sync::blocking_mutex::Mutex as BlockingMutex;
use log::{LevelFilter, Log, Metadata, Record};
const MAX_LIVE_LOG_ENTRIES: usize = 64;
const MAX_LIVE_LOG_ENTRIES: usize = 128;
struct LiveLogBuffer {
entries: Vec<(u64, String)>,

View File

@@ -311,6 +311,10 @@ pub enum LogMessage {
PumpOpenLoopCurrent,
#[strum(serialize = "Pump Open current sensor required but did not work: ${number_a}")]
PumpMissingSensorCurrent,
#[strum(
serialize = "Fertilizer applied for ${number_a}s on plant ${number_b} (last application ${txt_short} minutes ago)"
)]
FertilizerApplied,
#[strum(serialize = "MPPT Current sensor could not be reached")]
MPPTError,
#[strum(

View File

@@ -123,6 +123,8 @@ struct PumpInfo {
max_current_ma: u16,
min_current_ma: u16,
error: String,
flow_raw: u32,
flow_ml: f32,
}
#[derive(Serialize)]
@@ -132,8 +134,9 @@ pub struct PumpResult {
min_current_ma: u16,
error: String,
flow_value_ml: f32,
flow_value_count: i16,
flow_value_count: u32,
pump_time_s: u16,
overcurrent_ma: Option<u16>,
}
#[derive(Serialize, Debug, PartialEq)]
@@ -239,7 +242,7 @@ async fn safe_main(spawner: Spawner) -> FatResult<()> {
let reboot_now = Arc::new(AtomicBool::new(false));
println!("starting webserver");
let _ = http_server(reboot_now.clone(), stack);
spawner.spawn(http_server(reboot_now.clone(), stack)?);
wait_infinity(board, WaitType::MissingConfig, reboot_now.clone(), UTC).await;
}
@@ -384,7 +387,7 @@ async fn safe_main(spawner: Spawner) -> FatResult<()> {
let moisture = board.board_hal.measure_moisture_hz().await?;
let plantstate: [PlantState; PLANT_COUNT] = [
let mut plantstate: [PlantState; PLANT_COUNT] = [
PlantState::interpret_raw_values(moisture, 0, &mut board).await,
PlantState::interpret_raw_values(moisture, 1, &mut board).await,
PlantState::interpret_raw_values(moisture, 2, &mut board).await,
@@ -408,6 +411,7 @@ async fn safe_main(spawner: Spawner) -> FatResult<()> {
&& !water_frozen;
if pump_required {
log(LogMessage::EnableMain, dry_run as u32, 0, "", "");
let mut overcurrent_results: [Option<u16>; PLANT_COUNT] = [None; PLANT_COUNT];
for (plant_id, (state, plant_config)) in plantstate
.iter()
.zip(&board.board_hal.get_config().plants.clone())
@@ -454,12 +458,15 @@ async fn safe_main(spawner: Spawner) -> FatResult<()> {
0,
0,
String::new(),
0,
0.0,
)
.await;
let result = do_secure_pump(&mut board, plant_id, plant_config, dry_run).await;
match result {
Ok(state) => {
overcurrent_results[plant_id] = state.overcurrent_ma;
pump_info(
&mut board,
plant_id,
@@ -469,6 +476,8 @@ async fn safe_main(spawner: Spawner) -> FatResult<()> {
state.max_current_ma,
state.min_current_ma,
state.error,
state.flow_value_count,
state.flow_value_ml,
)
.await;
}
@@ -482,6 +491,8 @@ async fn safe_main(spawner: Spawner) -> FatResult<()> {
0,
0,
format!("{err:?}"),
0,
0.0,
)
.await;
}
@@ -497,6 +508,16 @@ async fn safe_main(spawner: Spawner) -> FatResult<()> {
.store_consecutive_pump_count(plant_id, 0);
}
}
for (plant_id, overcurrent) in overcurrent_results.iter().enumerate() {
if let Some(current_ma) = *overcurrent {
plantstate[plant_id].pump.overcurrent_error = Some(current_ma);
}
}
publish_plant_states(&mut board, &timezone_time.clone(), &plantstate)
.await
.unwrap_or_else(|e| {
error!("Error publishing plant states after pumping {e}");
});
} else {
// Pump corrosion protection: pulses each pump once a week for 2s around midday.
let last_check_day = board
@@ -709,19 +730,58 @@ pub async fn do_secure_pump(
let steps_in_50ms = plant_config.pump_time_s as usize * 20;
let mut current_collector = vec![0_u16; steps_in_50ms];
let mut flow_collector = vec![0_i16; steps_in_50ms];
let mut flow_collector = vec![0_u32; steps_in_50ms];
let mut error = String::new();
let mut first_error = true;
let mut pump_time_ms: u32 = 0;
let mut overcurrent_ma: Option<u16> = None;
if !dry_run {
// Run fertilizer pump first if configured and not in cooldown
if plant_config.fertilizer_s > 0 {
let current_time = board.board_hal.get_time().await;
let last_fertilizer = board.board_hal.get_esp().last_fertilizer_time(plant_id);
let elapsed_minutes = (current_time.timestamp() - last_fertilizer) / 60;
if elapsed_minutes >= plant_config.fertilizer_cooldown_min as i64 {
info!(
"Starting fertilizer pump for {} seconds (last fertilizer was {} minutes ago)",
plant_config.fertilizer_s, elapsed_minutes
);
log(
LogMessage::FertilizerApplied,
plant_config.fertilizer_s as u32,
(plant_id + 1) as u32,
&elapsed_minutes.to_string(),
"",
);
board.board_hal.fertilizer_pump(true).await?;
Timer::after_millis(plant_config.fertilizer_s as u64 * 1000).await;
board.board_hal.fertilizer_pump(false).await?;
info!("Fertilizer pump stopped");
// Store the current time as last fertilizer time
board
.board_hal
.get_esp()
.store_last_fertilizer_time(plant_id, current_time);
} else {
let remaining_minutes =
plant_config.fertilizer_cooldown_min as i64 - elapsed_minutes;
info!(
"Skipping fertilizer (cooldown: {} minutes remaining)",
remaining_minutes
);
}
}
board.board_hal.get_tank_sensor()?.reset_flow_meter();
board.board_hal.get_tank_sensor()?.start_flow_meter();
board.board_hal.pump(plant_id, true).await?;
for step in 0..steps_in_50ms {
let step_start = Instant::now();
let flow_value = board.board_hal.get_tank_sensor()?.get_flow_meter_value();
let flow_value = board.board_hal.get_tank_sensor()?.get_full_flow_count();
flow_collector[step] = flow_value;
let flow_value_ml = flow_value as f32 * board.board_hal.get_config().tank.ml_per_pulse;
@@ -752,6 +812,7 @@ pub async fn do_secure_pump(
plant_config.max_pump_current_ma.to_string().as_str(),
step.to_string().as_str(),
);
overcurrent_ma = Some(current_ma);
error = err_msg;
} else if high_current && first_error {
let err_msg = format!("OverCurrent: {}mA", current_ma);
@@ -762,6 +823,7 @@ pub async fn do_secure_pump(
plant_config.max_pump_current_ma.to_string().as_str(),
step.to_string().as_str(),
);
overcurrent_ma = Some(current_ma);
board.board_hal.general_fault(true).await;
board.board_hal.fault(plant_id, true).await?;
if !plant_config.ignore_current_error {
@@ -831,7 +893,7 @@ pub async fn do_secure_pump(
pump_time_ms = 1337;
}
board.board_hal.get_tank_sensor()?.stop_flow_meter();
let final_flow_value = board.board_hal.get_tank_sensor()?.get_flow_meter_value();
let final_flow_value = board.board_hal.get_tank_sensor()?.get_full_flow_count();
let flow_value_ml = final_flow_value as f32 * board.board_hal.get_config().tank.ml_per_pulse;
info!("Final flow value is {final_flow_value} with {flow_value_ml} ml");
current_collector.sort();
@@ -843,6 +905,7 @@ pub async fn do_secure_pump(
flow_value_count: final_flow_value,
pump_time_s: (pump_time_ms / 1000) as u16,
error,
overcurrent_ma,
})
}
@@ -997,6 +1060,8 @@ async fn pump_info(
max_current_ma: u16,
min_current_ma: u16,
error: String,
flow_raw: u32,
flow_ml: f32,
) {
let pump_info = PumpInfo {
enabled: pump_active,
@@ -1005,6 +1070,8 @@ async fn pump_info(
max_current_ma,
min_current_ma,
error,
flow_raw,
flow_ml,
};
let pump_topic = format!("/pump{}", plant_id + 1);

View File

@@ -220,7 +220,7 @@ impl<'t, T: Deref<Target = str> + 't, L: Publishable + 't, const S: usize>
username: self.username,
password: self.password,
subscriptions: self.subscriptions,
keep_alive
keep_alive,
},
)
}

View File

@@ -4,8 +4,8 @@ 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
const MOIST_SENSOR_MAX_FREQUENCY: f32 = 70000.; // 70kHz
const MOIST_SENSOR_MIN_FREQUENCY: f32 = 400.; // this is really, really dry, think like cactus levels
#[derive(Debug, PartialEq, Serialize)]
pub enum MoistureSensorError {
@@ -51,16 +51,27 @@ pub enum PumpError {
failed_attempts: usize,
max_allowed_failures: usize,
},
OverCurrent {
current_ma: u16,
max_allowed_ma: u16,
},
}
#[derive(Debug, Serialize)]
pub struct PumpState {
consecutive_pump_count: u32,
previous_pump: Option<DateTime<Utc>>,
pub overcurrent_error: Option<u16>,
}
impl PumpState {
fn is_err(&self, plant_config: &PlantConfig) -> Option<PumpError> {
if let Some(current_ma) = self.overcurrent_error {
return Some(PumpError::OverCurrent {
current_ma,
max_allowed_ma: plant_config.max_pump_current_ma,
});
}
if self.consecutive_pump_count > plant_config.max_consecutive_pump_count as u32 {
Some(PumpError::PumpNotWorking {
failed_attempts: self.consecutive_pump_count as usize,
@@ -89,6 +100,8 @@ pub struct PlantState {
pub sensor_a_firmware_build_minutes: Option<u32>,
/// Last known firmware build timestamp for sensor B.
pub sensor_b_firmware_build_minutes: Option<u32>,
/// Last time fertilizer was applied (Unix timestamp in seconds).
pub last_fertilizer_time: i64,
}
fn map_range_moisture(
@@ -162,6 +175,7 @@ impl PlantState {
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 last_fertilizer_time = board.board_hal.get_esp().last_fertilizer_time(plant_id);
let (a_builds, b_builds) = board.board_hal.get_sensor_build_minutes();
let state = Self {
sensor_a,
@@ -169,9 +183,11 @@ impl PlantState {
pump: PumpState {
consecutive_pump_count,
previous_pump,
overcurrent_error: None,
},
sensor_a_firmware_build_minutes: a_builds[plant_id],
sensor_b_firmware_build_minutes: b_builds[plant_id],
last_fertilizer_time,
};
if state.is_err() {
let _ = board.board_hal.fault(plant_id, true).await;
@@ -296,6 +312,7 @@ impl PlantState {
},
sensor_a_firmware_build_minutes: self.sensor_a_firmware_build_minutes,
sensor_b_firmware_build_minutes: self.sensor_b_firmware_build_minutes,
last_fertilizer_time: self.last_fertilizer_time,
}
}
}
@@ -328,4 +345,6 @@ pub struct PlantInfo<'a> {
sensor_a_firmware_build_minutes: Option<u32>,
/// firmware build timestamp of sensor B (minutes since Unix epoch); None if unknown
sensor_b_firmware_build_minutes: Option<u32>,
/// last time when fertilizer was applied
last_fertilizer_time: i64,
}

View File

@@ -1,4 +1,5 @@
use crate::fat_error::{FatError, FatResult};
use crate::hal::savegame_manager::SAVEGAME_SLOT_SIZE;
use crate::webserver::read_up_to_bytes_from_request;
use crate::BOARD_ACCESS;
use alloc::borrow::ToOwned;
@@ -46,7 +47,7 @@ pub(crate) async fn backup_config<T, const N: usize>(
where
T: Read + Write,
{
let input = read_up_to_bytes_from_request(conn, Some(4096)).await?;
let input = read_up_to_bytes_from_request(conn, Some(SAVEGAME_SLOT_SIZE)).await?;
info!("Read input with length {}", input.len());
let mut board = BOARD_ACCESS.get().await.lock().await;
let config_to_backup = serde_json::from_slice(&input)?;

View File

@@ -17,8 +17,8 @@ use crate::webserver::get_log::{get_live_log, get_log};
use crate::webserver::get_static::{serve_bundle, serve_favicon, serve_index};
use crate::webserver::ota::ota_operations;
use crate::webserver::post_json::{
board_test, can_power, detect_sensors, night_lamp_test, pump_test, set_config, wifi_scan,
write_time,
board_test, can_power, detect_sensors, fertilizer_pump_test, night_lamp_test, pump_test,
set_config, wifi_scan, write_time,
};
use crate::{bail, BOARD_ACCESS};
use alloc::borrow::ToOwned;
@@ -116,6 +116,7 @@ impl Handler for HTTPRequestRouter {
"/pumptest" => Some(pump_test(conn).await),
"/can_power" => Some(can_power(conn).await),
"/lamptest" => Some(night_lamp_test(conn).await),
"/fertilizerpumptest" => Some(fertilizer_pump_test(conn).await),
"/boardtest" => Some(board_test().await),
"/detect_sensors" => Some(detect_sensors(conn).await),
"/reboot" => {

View File

@@ -102,6 +102,19 @@ where
Ok(None)
}
pub(crate) async fn fertilizer_pump_test<T, const N: usize>(
_request: &mut Connection<'_, T, N>,
) -> FatResult<Option<String>>
where
T: Read + Write,
{
let mut board = BOARD_ACCESS.get().await.lock().await;
board.board_hal.fertilizer_pump(true).await?;
embassy_time::Timer::after_millis(1000).await;
board.board_hal.fertilizer_pump(false).await?;
Ok(None)
}
pub(crate) async fn write_time<T, const N: usize>(
request: &mut Connection<'_, T, N>,
) -> FatResult<Option<String>>

View File

@@ -128,6 +128,8 @@ export interface PlantConfig {
min_moisture: number,
pump_time_s: number,
pump_cooldown_min: number,
fertilizer_s: number,
fertilizer_cooldown_min: number,
pump_hour_start: number,
pump_hour_end: number,
pump_limit_ml: number,

View File

@@ -22,3 +22,8 @@
<div class="boardkey">Pump corrosion protection (weekly)</div>
<input type="checkbox" id="hardware_pump_corrosion_protection">
</div>
<div class="subtitle">Fertilizer Pump:</div>
<div class="flexcontainer">
<button class="subtitle" id="fertilizer_pump_test">Test Fertilizer Pump</button>
</div>

View File

@@ -5,6 +5,7 @@ export class HardwareConfigView {
private readonly hardware_board_value: HTMLSelectElement;
private readonly hardware_battery_value: HTMLSelectElement;
private readonly hardware_pump_corrosion_protection: HTMLInputElement;
private readonly fertilizer_pump_test: HTMLButtonElement;
constructor(controller:Controller){
(document.getElementById("hardwareview") as HTMLElement).innerHTML = require('./hardware.html') as string;
@@ -33,6 +34,11 @@ export class HardwareConfigView {
this.hardware_pump_corrosion_protection = document.getElementById("hardware_pump_corrosion_protection") as HTMLInputElement;
this.hardware_pump_corrosion_protection.onchange = controller.configChanged
this.fertilizer_pump_test = document.getElementById("fertilizer_pump_test") as HTMLButtonElement;
this.fertilizer_pump_test.onclick = () => {
controller.testFertilizerPump();
}
}
setConfig(hardware: BoardHardware) {

View File

@@ -304,6 +304,12 @@ export class Controller {
})
}
testFertilizerPump() {
fetch(PUBLIC_URL + "/fertilizerpumptest", {
method: "POST"
})
}
testPlant(plantId: number) {
let counter = 0
let limit = 30

View File

@@ -78,6 +78,16 @@
<input class="plantvalue" id="plant_${plantId}_pump_cooldown_min" type="number" min="0" max="600"
placeholder="30">
</div>
<div class="flexcontainer plantPumpEnabledOnly_${plantId}">
<div class="plantkey">Fertilizer (s):</div>
<input class="plantvalue" id="plant_${plantId}_fertilizer_s" type="number" min="0" max="60"
placeholder="0">
</div>
<div class="flexcontainer plantPumpEnabledOnly_${plantId}">
<div class="plantkey">Fertilizer Cooldown (m):</div>
<input class="plantvalue" id="plant_${plantId}_fertilizer_cooldown_min" type="number" min="0" max="20160"
placeholder="1440">
</div>
<div class="flexcontainer plantPumpEnabledOnly_${plantId}">
<div class="plantkey">"Pump Hour Start":</div>
<select class="plantvalue" id="plant_${plantId}_pump_hour_start">10</select>

View File

@@ -79,6 +79,8 @@ export class PlantView {
private readonly minMoisture: HTMLInputElement;
private readonly pumpTimeS: HTMLInputElement;
private readonly pumpCooldown: HTMLInputElement;
private readonly fertilizerS: HTMLInputElement;
private readonly fertilizerCooldownMin: HTMLInputElement;
private readonly pumpHourStart: HTMLSelectElement;
private readonly pumpHourEnd: HTMLSelectElement;
private readonly sensorAInstalled: HTMLInputElement;
@@ -180,6 +182,16 @@ export class PlantView {
controller.configChanged()
}
this.fertilizerS = document.getElementById("plant_" + plantId + "_fertilizer_s") as HTMLInputElement;
this.fertilizerS.onchange = function () {
controller.configChanged()
}
this.fertilizerCooldownMin = document.getElementById("plant_" + plantId + "_fertilizer_cooldown_min") as HTMLInputElement;
this.fertilizerCooldownMin.onchange = function () {
controller.configChanged()
}
this.pumpHourStart = document.getElementById("plant_" + plantId + "_pump_hour_start") as HTMLSelectElement;
this.pumpHourStart.onchange = function () {
controller.configChanged()
@@ -328,6 +340,8 @@ export class PlantView {
this.minMoisture.value = plantConfig.min_moisture?.toString() || "";
this.pumpTimeS.value = plantConfig.pump_time_s.toString();
this.pumpCooldown.value = plantConfig.pump_cooldown_min.toString();
this.fertilizerS.value = plantConfig.fertilizer_s?.toString() || "0";
this.fertilizerCooldownMin.value = plantConfig.fertilizer_cooldown_min?.toString() || "1440";
this.pumpHourStart.value = plantConfig.pump_hour_start.toString();
this.pumpHourEnd.value = plantConfig.pump_hour_end.toString();
this.sensorBInstalled.checked = plantConfig.sensor_b;
@@ -355,6 +369,8 @@ export class PlantView {
pump_time_s: this.pumpTimeS.valueAsNumber,
pump_limit_ml: 5000,
pump_cooldown_min: this.pumpCooldown.valueAsNumber,
fertilizer_s: this.fertilizerS.valueAsNumber || 0,
fertilizer_cooldown_min: this.fertilizerCooldownMin.valueAsNumber || 1440,
pump_hour_start: +this.pumpHourStart.value,
pump_hour_end: +this.pumpHourEnd.value,
sensor_b: this.sensorBInstalled.checked,

View File

@@ -3,39 +3,55 @@ title: "BatteryManagement"
date: 2025-01-27
draft: false
description: "a description"
tags: ["battery", "bq34z100"]
tags: ["battery", "bms"]
---
# Battery Management Module
The project contains an additional companion board (Fuel Gauge), with a bq34z100 battery management IC.
It allows to track the health and charge for an external battery and is supposed to be soldered directly to the battery.
The MainBoard contains a connector for power, and additionally a two-pin I2C bus to communicate with the Battery Management module.
The PlantCtrl system uses an external **Battery Management System (BMS)** board that connects to the MainBoard. This module monitors battery voltage, current, and health metrics and communicates with the ESP32-C6 via I2C.
<!-- TODO: Add photo of the new modular Battery Management board -->
# Setup
{{< alert >}}
A protected Battery is required. There is only a very simplistic output voltage adjustment for the MPPT system and no charge termination. It is expected that the battery itself protects against overcharging and deep discharges!
The open-bms is a custom battery management board designed for this project. It uses a CH32V203 microcontroller to handle battery monitoring and protection. The older bq34z100-based battery management board is deprecated and located in the `__Legay_Unused` folder.
{{< /alert >}}
* BatteryManagement is purely optional, but recommended for solar power.
* If available it will be used for an extended low power deep sleep in case of critical charge.
* If available it will also be used, to reduce the nightlight, if the charge drops to a predefined level, so the nightlight cannot drain to much battery
* If available, all relevant battery metrics will be published via mqtt
Currently the setup requires a custom Ev2400 flasher and the properitary windows software from texas instruments.
{{< alert >}}
Before soldering to the battery
{{< /alert >}}
1. The voltage devider high side must be bridged, while being connected to the computer and being supplied with around 4.2 V from the battery solder leads.
2. Then the data/register for low voltage flash write protection should be set to 0V, as else with the voltage divider and no further configuration, the IC will refuse all write requests.
3. After this the supplied golden image can be used, it will setup the battery for 6Ah and a 4S lifepo. Different values can be adjusted after this to the users liking.
## Hardware
The Battery Management Board features:
* CH32V203 RISC-V microcontroller for battery monitoring
* I2C interface for communication with the MainBoard
* Battery voltage and current sensing
{{< alert >}}
The main board, does not care or process any of the charge discharge limits that can be set. Ensure that the battery can supply enough current as well as accept a 2.4A charging current from the MPPT system.
The open-bms board does not use the bq34z100 fuel gauge IC. That component was used in an older legacy design now located in the `__Legay_Unused` folder.
{{< /alert >}}
The golden image sets the statups led up, to be in blinky mode. one very long interval means, that the battery is pretty much full. A few very short flashes mean that the battery is nearly empty. No light means, that the battery is in discharge protection and shut down.
## Integration with MainBoard
If the red error led lights, something is wrong with the battery. This can be abnormal voltages or a very low health state.
The battery management board:
* Connects to the MainBoard via a two-pin I2C bus
* Provides power connection to the battery
* Reports battery metrics via MQTT (if configured)
# Todo?
If the battery reports that no discharging should occure, report this and then shutdown without using pumps
## Usage
* If available, the system will use battery metrics for deep sleep management when charge is critical
* The nightlight can be automatically disabled if battery level drops below a predefined threshold
* All battery metrics are published via MQTT when configured
* The system includes safety mechanisms to prevent overcharging and deep discharges through the battery's built-in protection circuitry
## Safety Notes
{{< alert >}}
The system requires a battery with built-in protection circuitry. The MPPT system does not include charge termination or overcharge protection - the battery itself must provide these safety features.
{{< /alert >}}
The CH32V203-based BMS monitors battery health and provides status information but does not control the charge/discharge limits. Ensure your battery can handle the maximum charging current from the MPPT system (up to 2.4A).
## Setup
1. **Connect Battery:** Connect your protected battery to the BMS board
2. **_connect MainBoard:** Connect the Battery Management Board to the MainBoard via the I2C bus connector
3. **Power On:** Power on the system and verify communication via MQTT
## Status Indicators
The BMS board includes status LEDs, they behave like every normal powerbank (1-5 lights, animted if charging)

View File

@@ -65,13 +65,9 @@ Software and Hardware may fail: It is your responsibility to ensure that a stuck
{{< /alert >}}
# Todo
## Flow Sensor
There is a input for a flow sensor, currently it is not used as the software is missing.
* Allow monitoring if pumps are actually moving water
* Allow to set limits for how much ml are allowed additinally to the current time limit per watering run
Currently it cannot be set how two sensor should be interpreted and they are only averaged. More complex functions would be nice here, eg. allowing a user settable interpolation (0.8*a+0.2*b)/2 and Min(a,b) as well as max(a,b)

View File

@@ -11,8 +11,6 @@ tags: ["esp32", "hardware"]
<img src="pcb_back.png" class="grid-w50" />
{{< /gallery >}}
<!-- TODO: Add new screenshots of the modular PCB setup -->
{{< gitea server="https://git.mannheim.ccc.de/" repo="C3MA/PlantCtrl" >}}
## Modular Design
@@ -27,17 +25,25 @@ The system now consists of a **MainBoard** which acts as the controller and seve
* **Fully Open Source:** Designed in KiCad
## Available Modules
* **MPPT Charger:** Efficient solar charging for batteries.
* **Pump Driver:** High-current outputs for pumps and valves.
* **Sensor Interface:** Support for multiple moisture sensors.
* **Light Controller:** For LED nightlights or growth lights.
* **MPPT Charger:** Efficient solar charging for batteries using CN3795.
* **Pump Driver:** High-current outputs (up to 3A) for pumps and valves.
* **Sensor Module:** CAN bus-based moisture sensors using CH32V203 microcontroller.
* **Battery Management:** External BMS board with CH32V203 for battery monitoring.
* **Light Controller:** For LED nightlights or growth lights using AP63200.
## Sensor Module (CAN bus)
The standard sensor module features its own **CH32V203 RISC-V microcontroller**, which handles the measurement of soil moisture and communicates the results back to the MainBoard via the CAN bus.
* **Capacity:** Supports up to 16 sensors (typically 8 plants with an A and B sensor each).
* **Reliability:** Digital communication via CAN bus ensures data integrity even over longer cable runs and in electrically noisy environments.
* **Addressing:** The A sensor is always used; the B sensor is optional and suggested for larger planters to provide a better average of the soil moisture.
## Capabilities
* **Moisture Sensors:** Supports multiple capacitive or resistive sensors via expansion modules.
* **Moisture Sensors:** Supports multiple capacitive or resistive sensors via CAN bus-based Sensor Modules.
* **Pumps/Valves:** Support for multiple independent watering zones.
* **Power:**
* Solar powered with MPPT
* Battery powered with optional Battery Management (Fuel Gauge)
* Battery powered with optional Battery Management System (BMS)
* Can also be used with a standard power supply (7-24V)
* **Efficient Power:** Use of high-efficiency DC-DC converters for 3.3V and peripherals.

View File

@@ -6,9 +6,12 @@ description: "a description"
tags: ["firmeware", "upload"]
---
# From Source
The PlantCtrl firmware is written in Rust for the ESP32-C6 RISC-V microcontroller.
## Preconditions
* **Rust:** Current version of `rustup`.
* **ESP32 Toolchain:** `espup` installed and configured.
* **ESP32 Toolchain:** `espup` installed and configured for ESP32-C6.
* **espflash:** Installed via `cargo install espflash`.
* **Node.js:** `npm` installed (for the web interface).
@@ -37,10 +40,8 @@ You can use the provided bash scripts to automate the build and flash process:
You can also update the firmware wirelessly if the system is already running and connected to your network.
1. Generate the OTA binary:
```bash
cargo build --release
```
2. The binary will be at `target/riscv32imac-unknown-none-elf/release/plant-ctrl2`.
**`./image.sh`**
2. The binary will be `image.bin`.
3. Open the PlantCtrl web interface in your browser.
4. Navigate to the **OTA** section.
5. Upload the `plant-ctrl2` file.

View File

@@ -6,23 +6,26 @@ description: "a description"
tags: ["mqtt", "esp"]
---
# MQTT
A configured MQTT server will receive statistical and status data from the controller.
The PlantCtrl firmware publishes comprehensive status and telemetry data via MQTT when configured. The system uses the **mcutie** crate for Home Assistant integration and standard MQTT topics.
### Topics
| Topic | Example | Description |
|-------|---------|-------------|
| `firmware/address` | `192.168.1.2` | IP address in station mode |
| `firmware/state` | `VersionInfo { ... }` | Debug information about the current firmware and OTA slots |
| `firmware/state` | `{...}` | Debug information about the current firmware and OTA slots |
| `firmware/last_online` | `2025-01-22T08:56:46.664+01:00` | Last time the board was online |
| `state` | `online` | Current state of the controller |
| `mppt` | `{"current_ma":1200,"voltage_ma":18500}` | MPPT charging metrics |
| `battery` | `{"Info":{"voltage_milli_volt":12860,"average_current_milli_ampere":-16,...}}` | Battery health and charge data |
| `water` | `{"enough_water":true,"warn_level":false,"left_ml":1337,...}` | Water tank status |
| `plant{1-8}` | `{"sensor_a":...,"sensor_b":...,"mode":"TargetMoisture",...}` | Detailed status for each plant slot |
| `pump{1-8}` | `{"enabled":true,"pump_ineffective":false,...}` | Metrics for the last pump activity |
| `mppt` | `{"current_ma":1200,"voltage_ma":18500}` | MPPT charging metrics (current and voltage from solar panel) |
| `battery` | `{"Info":{"voltage_milli_volt":12860,"state_of_charge":95,...}}` | Battery health and charge data from the BMS |
| `water` | `{"enough_water":true,"warn_level":false,"left_ml":1337,...}` | Water tank status (level, temperature, frozen detection) |
| `plant{1-8}` | `{"sensor_a":...,"sensor_b":...,"mode":"TargetMoisture",...}` | Detailed status for each plant slot including moisture sensors |
| `pump{1-8}` | `{"enabled":true,"median_current_ma":500,...}` | Metrics for each pump output |
| `light` | `{"enabled":true,"active":true,...}` | Night light status |
| `deepsleep` | `night 1h` | Why and how long the ESP will sleep |
| `deepsleep` | `night 1h` | Reason and duration of deep sleep |
Note: The batteries `average_current_milli_ampere` field uses a placeholder value (1337) and should be updated with actual current sensor readings when available.
### Data Structures
@@ -39,14 +42,15 @@ Contains a debug dump of the `VersionInfo` struct:
- `voltage_ma`: Solar panel voltage in mV
#### Battery (`battery`)
Can be `"Unknown"` or an `Info` object:
- `voltage_milli_volt`: Battery voltage
- `average_current_milli_ampere`: Current draw/charge
- `design_milli_ampere_hour`: Battery capacity
- `remaining_milli_ampere_hour`: Remaining capacity
Can be `"Unknown"` or an `Info` object. The battery data comes from a custom BMS (Battery Management System) board that uses the CH32V203 microcontroller with I2C communication.
- `voltage_milli_volt`: Battery voltage in millivolts
- `average_current_milli_ampere`: Current draw/charge in milliamperes (placeholder: 1337)
- `design_milli_ampere_hour`: Battery design capacity in milliampere-hours
- `remaining_milli_ampere_hour`: Remaining capacity in milliampere-hours
- `state_of_charge`: Charge percentage (0-100)
- `state_of_health`: Health percentage (0-100)
- `temperature`: Temperature in degrees Celsius
- `state_of_health`: Health percentage (0-100) based onLifetime capacity vs design capacity
- `temperature`: Battery temperature in degrees Celsius
#### Water (`water`)
- `enough_water`: Boolean, true if level is above empty threshold

View File

@@ -6,9 +6,9 @@ description: "How to compile the project"
tags: ["clone", "compile"]
---
# Preconditions:
* **Rust:** `rustup` installed.
* **ESP32 Toolchain:** `espup` installed.
* **Build Utilities:** `ldproxy` and `espflash` installed.
* **Rust:** `rustup` installed with the Rust toolchain.
* **ESP32 Toolchain:** `espup` installed for ESP32 support.
* **Build Utilities:** `ldproxy` and `espflash` installed via cargo.
* **Node.js:** `npm` installed (for the web interface).
# Cloning the Repository
@@ -19,24 +19,16 @@ cd PlantCtrl/Software/MainBoard/rust
```
# Toolchain Setup
1. **Install Rust:** If not already done, visit [rustup.rs](https://rustup.rs/).
2. **Install ldproxy:**
The project uses Rust with ESP32-C6 support. The toolchain setup involves installing the necessary components:
1. **Rust Toolchain:**
```bash
cargo install ldproxy
```
3. **Install espup:**
```bash
cargo install espup
```
4. **Install ESP toolchain:**
```bash
espup install
```
5. **Install espflash:**
```bash
cargo install espflash
rustup toolchain install stable
rustup default stable
```
# Building the Web Interface
The configuration website is built using TypeScript and Webpack, then embedded into the Rust binary.
```bash
@@ -46,14 +38,7 @@ npx webpack
cd ..
```
# Compiling the Firmware
Build the project using Cargo:
```bash
cargo build --release
```
The resulting binary will be located in `target/riscv32imac-unknown-none-elf/release/plant-ctrl2`.
# Using Build Scripts
# Compiling the Firmware using Build Scripts
To simplify the process, several bash scripts are provided in the `Software/MainBoard/rust` directory:
* **`image_build.sh`**: Automatically builds the web interface, compiles the Rust firmware in release mode, and creates a flashable `image.bin`.