Compare commits
2 Commits
test_new_s
...
develop
| Author | SHA1 | Date | |
|---|---|---|---|
| 07aed02fe7 | |||
| aef0ffd5a1 |
@@ -15,6 +15,7 @@
|
|||||||
"vias": 1.0,
|
"vias": 1.0,
|
||||||
"zones": 0.6
|
"zones": 0.6
|
||||||
},
|
},
|
||||||
|
"prototype_zone_fills": false,
|
||||||
"selection_filter": {
|
"selection_filter": {
|
||||||
"dimensions": true,
|
"dimensions": true,
|
||||||
"footprints": true,
|
"footprints": true,
|
||||||
@@ -53,6 +54,7 @@
|
|||||||
"zone_display_mode": 1
|
"zone_display_mode": 1
|
||||||
},
|
},
|
||||||
"git": {
|
"git": {
|
||||||
|
"integration_disabled": false,
|
||||||
"repo_type": "",
|
"repo_type": "",
|
||||||
"repo_username": "",
|
"repo_username": "",
|
||||||
"ssh_key": ""
|
"ssh_key": ""
|
||||||
@@ -105,6 +107,7 @@
|
|||||||
"filter_text": "",
|
"filter_text": "",
|
||||||
"group_by_constraint": false,
|
"group_by_constraint": false,
|
||||||
"group_by_netclass": false,
|
"group_by_netclass": false,
|
||||||
|
"show_time_domain_details": false,
|
||||||
"show_unconnected_nets": false,
|
"show_unconnected_nets": false,
|
||||||
"show_zero_pad_nets": false,
|
"show_zero_pad_nets": false,
|
||||||
"sort_ascending": true,
|
"sort_ascending": true,
|
||||||
@@ -115,6 +118,7 @@
|
|||||||
"files": []
|
"files": []
|
||||||
},
|
},
|
||||||
"schematic": {
|
"schematic": {
|
||||||
|
"hierarchy_collapsed": [],
|
||||||
"selection_filter": {
|
"selection_filter": {
|
||||||
"graphics": true,
|
"graphics": true,
|
||||||
"images": true,
|
"images": true,
|
||||||
@@ -122,6 +126,7 @@
|
|||||||
"lockedItems": false,
|
"lockedItems": false,
|
||||||
"otherItems": true,
|
"otherItems": true,
|
||||||
"pins": true,
|
"pins": true,
|
||||||
|
"ruleAreas": true,
|
||||||
"symbols": true,
|
"symbols": true,
|
||||||
"text": true,
|
"text": true,
|
||||||
"wires": true
|
"wires": true
|
||||||
|
|||||||
@@ -3,6 +3,8 @@
|
|||||||
"3dviewports": [],
|
"3dviewports": [],
|
||||||
"design_settings": {
|
"design_settings": {
|
||||||
"defaults": {
|
"defaults": {
|
||||||
|
"apply_defaults_to_fp_barcodes": false,
|
||||||
|
"apply_defaults_to_fp_dimensions": false,
|
||||||
"apply_defaults_to_fp_fields": false,
|
"apply_defaults_to_fp_fields": false,
|
||||||
"apply_defaults_to_fp_shapes": false,
|
"apply_defaults_to_fp_shapes": false,
|
||||||
"apply_defaults_to_fp_text": false,
|
"apply_defaults_to_fp_text": false,
|
||||||
@@ -82,6 +84,7 @@
|
|||||||
"extra_footprint": "warning",
|
"extra_footprint": "warning",
|
||||||
"footprint": "error",
|
"footprint": "error",
|
||||||
"footprint_filters_mismatch": "ignore",
|
"footprint_filters_mismatch": "ignore",
|
||||||
|
"footprint_symbol_field_mismatch": "warning",
|
||||||
"footprint_symbol_mismatch": "warning",
|
"footprint_symbol_mismatch": "warning",
|
||||||
"footprint_type_mismatch": "ignore",
|
"footprint_type_mismatch": "ignore",
|
||||||
"hole_clearance": "error",
|
"hole_clearance": "error",
|
||||||
@@ -99,6 +102,7 @@
|
|||||||
"mirrored_text_on_front_layer": "warning",
|
"mirrored_text_on_front_layer": "warning",
|
||||||
"missing_courtyard": "ignore",
|
"missing_courtyard": "ignore",
|
||||||
"missing_footprint": "warning",
|
"missing_footprint": "warning",
|
||||||
|
"missing_tuning_profile": "warning",
|
||||||
"net_conflict": "warning",
|
"net_conflict": "warning",
|
||||||
"nonmirrored_text_on_back_layer": "warning",
|
"nonmirrored_text_on_back_layer": "warning",
|
||||||
"npth_inside_courtyard": "ignore",
|
"npth_inside_courtyard": "ignore",
|
||||||
@@ -118,9 +122,12 @@
|
|||||||
"too_many_vias": "error",
|
"too_many_vias": "error",
|
||||||
"track_angle": "error",
|
"track_angle": "error",
|
||||||
"track_dangling": "warning",
|
"track_dangling": "warning",
|
||||||
|
"track_not_centered_on_via": "ignore",
|
||||||
|
"track_on_post_machined_layer": "error",
|
||||||
"track_segment_length": "error",
|
"track_segment_length": "error",
|
||||||
"track_width": "error",
|
"track_width": "error",
|
||||||
"tracks_crossing": "error",
|
"tracks_crossing": "error",
|
||||||
|
"tuning_profile_track_geometries": "ignore",
|
||||||
"unconnected_items": "error",
|
"unconnected_items": "error",
|
||||||
"unresolved_variable": "error",
|
"unresolved_variable": "error",
|
||||||
"via_dangling": "warning",
|
"via_dangling": "warning",
|
||||||
@@ -235,17 +242,28 @@
|
|||||||
"zones_allow_external_fillets": false
|
"zones_allow_external_fillets": false
|
||||||
},
|
},
|
||||||
"ipc2581": {
|
"ipc2581": {
|
||||||
|
"bom_rev": "",
|
||||||
"dist": "",
|
"dist": "",
|
||||||
"distpn": "",
|
"distpn": "",
|
||||||
"internal_id": "",
|
"internal_id": "",
|
||||||
"mfg": "",
|
"mfg": "",
|
||||||
"mpn": ""
|
"mpn": "",
|
||||||
|
"sch_revision": ""
|
||||||
},
|
},
|
||||||
"layer_pairs": [],
|
"layer_pairs": [],
|
||||||
"layer_presets": [],
|
"layer_presets": [],
|
||||||
"viewports": []
|
"viewports": []
|
||||||
},
|
},
|
||||||
"boards": [],
|
"boards": [],
|
||||||
|
"component_class_settings": {
|
||||||
|
"assignments": [],
|
||||||
|
"meta": {
|
||||||
|
"version": 0
|
||||||
|
},
|
||||||
|
"sheet_component_classes": {
|
||||||
|
"enabled": false
|
||||||
|
}
|
||||||
|
},
|
||||||
"cvpcb": {
|
"cvpcb": {
|
||||||
"equivalence_files": []
|
"equivalence_files": []
|
||||||
},
|
},
|
||||||
@@ -494,13 +512,14 @@
|
|||||||
"priority": 2147483647,
|
"priority": 2147483647,
|
||||||
"schematic_color": "rgba(0, 0, 0, 0.000)",
|
"schematic_color": "rgba(0, 0, 0, 0.000)",
|
||||||
"track_width": 0.2,
|
"track_width": 0.2,
|
||||||
|
"tuning_profile": "",
|
||||||
"via_diameter": 0.6,
|
"via_diameter": 0.6,
|
||||||
"via_drill": 0.3,
|
"via_drill": 0.3,
|
||||||
"wire_width": 6
|
"wire_width": 6
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"meta": {
|
"meta": {
|
||||||
"version": 4
|
"version": 5
|
||||||
},
|
},
|
||||||
"net_colors": null,
|
"net_colors": null,
|
||||||
"netclass_assignments": null,
|
"netclass_assignments": null,
|
||||||
@@ -683,6 +702,7 @@
|
|||||||
"sort_asc": true,
|
"sort_asc": true,
|
||||||
"sort_field": "Reference"
|
"sort_field": "Reference"
|
||||||
},
|
},
|
||||||
|
"bus_aliases": {},
|
||||||
"connection_grid_size": 50.0,
|
"connection_grid_size": 50.0,
|
||||||
"drawing": {
|
"drawing": {
|
||||||
"dashed_lines_dash_length_ratio": 12.0,
|
"dashed_lines_dash_length_ratio": 12.0,
|
||||||
@@ -721,7 +741,14 @@
|
|||||||
"spice_save_all_dissipations": false,
|
"spice_save_all_dissipations": false,
|
||||||
"spice_save_all_voltages": false,
|
"spice_save_all_voltages": false,
|
||||||
"subpart_first_id": 65,
|
"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": [
|
"sheets": [
|
||||||
[
|
[
|
||||||
@@ -729,5 +756,11 @@
|
|||||||
"Root"
|
"Root"
|
||||||
]
|
]
|
||||||
],
|
],
|
||||||
"text_variables": {}
|
"text_variables": {},
|
||||||
|
"tuning_profiles": {
|
||||||
|
"meta": {
|
||||||
|
"version": 0
|
||||||
|
},
|
||||||
|
"tuning_profiles_impedance_geometric": []
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,6 +15,7 @@
|
|||||||
"vias": 1.0,
|
"vias": 1.0,
|
||||||
"zones": 0.6
|
"zones": 0.6
|
||||||
},
|
},
|
||||||
|
"prototype_zone_fills": false,
|
||||||
"ratsnest_display_mode": 0,
|
"ratsnest_display_mode": 0,
|
||||||
"selection_filter": {
|
"selection_filter": {
|
||||||
"dimensions": true,
|
"dimensions": true,
|
||||||
@@ -54,6 +55,7 @@
|
|||||||
"zone_display_mode": 1
|
"zone_display_mode": 1
|
||||||
},
|
},
|
||||||
"git": {
|
"git": {
|
||||||
|
"integration_disabled": false,
|
||||||
"repo_password": "",
|
"repo_password": "",
|
||||||
"repo_type": "",
|
"repo_type": "",
|
||||||
"repo_username": "",
|
"repo_username": "",
|
||||||
@@ -113,6 +115,7 @@
|
|||||||
"filter_text": "",
|
"filter_text": "",
|
||||||
"group_by_constraint": false,
|
"group_by_constraint": false,
|
||||||
"group_by_netclass": false,
|
"group_by_netclass": false,
|
||||||
|
"show_time_domain_details": false,
|
||||||
"show_unconnected_nets": false,
|
"show_unconnected_nets": false,
|
||||||
"show_zero_pad_nets": false,
|
"show_zero_pad_nets": false,
|
||||||
"sort_ascending": true,
|
"sort_ascending": true,
|
||||||
@@ -123,6 +126,7 @@
|
|||||||
"files": []
|
"files": []
|
||||||
},
|
},
|
||||||
"schematic": {
|
"schematic": {
|
||||||
|
"hierarchy_collapsed": [],
|
||||||
"selection_filter": {
|
"selection_filter": {
|
||||||
"graphics": true,
|
"graphics": true,
|
||||||
"images": true,
|
"images": true,
|
||||||
@@ -130,6 +134,7 @@
|
|||||||
"lockedItems": false,
|
"lockedItems": false,
|
||||||
"otherItems": true,
|
"otherItems": true,
|
||||||
"pins": true,
|
"pins": true,
|
||||||
|
"ruleAreas": true,
|
||||||
"symbols": true,
|
"symbols": true,
|
||||||
"text": true,
|
"text": true,
|
||||||
"wires": true
|
"wires": true
|
||||||
|
|||||||
@@ -3,6 +3,8 @@
|
|||||||
"3dviewports": [],
|
"3dviewports": [],
|
||||||
"design_settings": {
|
"design_settings": {
|
||||||
"defaults": {
|
"defaults": {
|
||||||
|
"apply_defaults_to_fp_barcodes": false,
|
||||||
|
"apply_defaults_to_fp_dimensions": false,
|
||||||
"apply_defaults_to_fp_fields": false,
|
"apply_defaults_to_fp_fields": false,
|
||||||
"apply_defaults_to_fp_shapes": false,
|
"apply_defaults_to_fp_shapes": false,
|
||||||
"apply_defaults_to_fp_text": false,
|
"apply_defaults_to_fp_text": false,
|
||||||
@@ -78,6 +80,7 @@
|
|||||||
"extra_footprint": "warning",
|
"extra_footprint": "warning",
|
||||||
"footprint": "error",
|
"footprint": "error",
|
||||||
"footprint_filters_mismatch": "ignore",
|
"footprint_filters_mismatch": "ignore",
|
||||||
|
"footprint_symbol_field_mismatch": "warning",
|
||||||
"footprint_symbol_mismatch": "warning",
|
"footprint_symbol_mismatch": "warning",
|
||||||
"footprint_type_mismatch": "warning",
|
"footprint_type_mismatch": "warning",
|
||||||
"hole_clearance": "error",
|
"hole_clearance": "error",
|
||||||
@@ -96,6 +99,7 @@
|
|||||||
"mirrored_text_on_front_layer": "warning",
|
"mirrored_text_on_front_layer": "warning",
|
||||||
"missing_courtyard": "ignore",
|
"missing_courtyard": "ignore",
|
||||||
"missing_footprint": "warning",
|
"missing_footprint": "warning",
|
||||||
|
"missing_tuning_profile": "warning",
|
||||||
"net_conflict": "warning",
|
"net_conflict": "warning",
|
||||||
"nonmirrored_text_on_back_layer": "warning",
|
"nonmirrored_text_on_back_layer": "warning",
|
||||||
"npth_inside_courtyard": "ignore",
|
"npth_inside_courtyard": "ignore",
|
||||||
@@ -115,9 +119,12 @@
|
|||||||
"too_many_vias": "error",
|
"too_many_vias": "error",
|
||||||
"track_angle": "error",
|
"track_angle": "error",
|
||||||
"track_dangling": "warning",
|
"track_dangling": "warning",
|
||||||
|
"track_not_centered_on_via": "ignore",
|
||||||
|
"track_on_post_machined_layer": "error",
|
||||||
"track_segment_length": "error",
|
"track_segment_length": "error",
|
||||||
"track_width": "error",
|
"track_width": "error",
|
||||||
"tracks_crossing": "error",
|
"tracks_crossing": "error",
|
||||||
|
"tuning_profile_track_geometries": "ignore",
|
||||||
"unconnected_items": "error",
|
"unconnected_items": "error",
|
||||||
"unresolved_variable": "error",
|
"unresolved_variable": "error",
|
||||||
"via_dangling": "warning",
|
"via_dangling": "warning",
|
||||||
@@ -242,17 +249,28 @@
|
|||||||
"zones_use_no_outline": true
|
"zones_use_no_outline": true
|
||||||
},
|
},
|
||||||
"ipc2581": {
|
"ipc2581": {
|
||||||
|
"bom_rev": "",
|
||||||
"dist": "",
|
"dist": "",
|
||||||
"distpn": "",
|
"distpn": "",
|
||||||
"internal_id": "",
|
"internal_id": "",
|
||||||
"mfg": "",
|
"mfg": "",
|
||||||
"mpn": ""
|
"mpn": "",
|
||||||
|
"sch_revision": ""
|
||||||
},
|
},
|
||||||
"layer_pairs": [],
|
"layer_pairs": [],
|
||||||
"layer_presets": [],
|
"layer_presets": [],
|
||||||
"viewports": []
|
"viewports": []
|
||||||
},
|
},
|
||||||
"boards": [],
|
"boards": [],
|
||||||
|
"component_class_settings": {
|
||||||
|
"assignments": [],
|
||||||
|
"meta": {
|
||||||
|
"version": 0
|
||||||
|
},
|
||||||
|
"sheet_component_classes": {
|
||||||
|
"enabled": false
|
||||||
|
}
|
||||||
|
},
|
||||||
"cvpcb": {
|
"cvpcb": {
|
||||||
"equivalence_files": []
|
"equivalence_files": []
|
||||||
},
|
},
|
||||||
@@ -443,11 +461,14 @@
|
|||||||
"duplicate_sheet_names": "error",
|
"duplicate_sheet_names": "error",
|
||||||
"endpoint_off_grid": "ignore",
|
"endpoint_off_grid": "ignore",
|
||||||
"extra_units": "error",
|
"extra_units": "error",
|
||||||
|
"field_name_whitespace": "warning",
|
||||||
"footprint_filter": "ignore",
|
"footprint_filter": "ignore",
|
||||||
"footprint_link_issues": "warning",
|
"footprint_link_issues": "warning",
|
||||||
"four_way_junction": "ignore",
|
"four_way_junction": "ignore",
|
||||||
"global_label_dangling": "warning",
|
"global_label_dangling": "warning",
|
||||||
|
"ground_pin_not_ground": "warning",
|
||||||
"hier_label_mismatch": "error",
|
"hier_label_mismatch": "error",
|
||||||
|
"isolated_pin_label": "warning",
|
||||||
"label_dangling": "error",
|
"label_dangling": "error",
|
||||||
"label_multiple_wires": "warning",
|
"label_multiple_wires": "warning",
|
||||||
"lib_symbol_issues": "warning",
|
"lib_symbol_issues": "warning",
|
||||||
@@ -470,6 +491,7 @@
|
|||||||
"similar_power": "warning",
|
"similar_power": "warning",
|
||||||
"simulation_model_issue": "ignore",
|
"simulation_model_issue": "ignore",
|
||||||
"single_global_label": "warning",
|
"single_global_label": "warning",
|
||||||
|
"stacked_pin_name": "warning",
|
||||||
"unannotated": "error",
|
"unannotated": "error",
|
||||||
"unconnected_wire_endpoint": "warning",
|
"unconnected_wire_endpoint": "warning",
|
||||||
"undefined_netclass": "error",
|
"undefined_netclass": "error",
|
||||||
@@ -502,6 +524,7 @@
|
|||||||
"priority": 2147483647,
|
"priority": 2147483647,
|
||||||
"schematic_color": "rgba(0, 0, 0, 0.000)",
|
"schematic_color": "rgba(0, 0, 0, 0.000)",
|
||||||
"track_width": 1.2,
|
"track_width": 1.2,
|
||||||
|
"tuning_profile": "",
|
||||||
"via_diameter": 0.8,
|
"via_diameter": 0.8,
|
||||||
"via_drill": 0.4,
|
"via_drill": 0.4,
|
||||||
"wire_width": 6
|
"wire_width": 6
|
||||||
@@ -520,6 +543,7 @@
|
|||||||
"priority": 0,
|
"priority": 0,
|
||||||
"schematic_color": "rgb(255, 4, 6)",
|
"schematic_color": "rgb(255, 4, 6)",
|
||||||
"track_width": 1.0,
|
"track_width": 1.0,
|
||||||
|
"tuning_profile": "",
|
||||||
"via_diameter": 0.8,
|
"via_diameter": 0.8,
|
||||||
"via_drill": 0.4,
|
"via_drill": 0.4,
|
||||||
"wire_width": 12
|
"wire_width": 12
|
||||||
@@ -538,6 +562,7 @@
|
|||||||
"priority": 1,
|
"priority": 1,
|
||||||
"schematic_color": "rgb(255, 153, 0)",
|
"schematic_color": "rgb(255, 153, 0)",
|
||||||
"track_width": 0.2,
|
"track_width": 0.2,
|
||||||
|
"tuning_profile": "",
|
||||||
"via_diameter": 0.8,
|
"via_diameter": 0.8,
|
||||||
"via_drill": 0.4,
|
"via_drill": 0.4,
|
||||||
"wire_width": 12
|
"wire_width": 12
|
||||||
@@ -556,6 +581,7 @@
|
|||||||
"priority": 2,
|
"priority": 2,
|
||||||
"schematic_color": "rgb(81, 255, 3)",
|
"schematic_color": "rgb(81, 255, 3)",
|
||||||
"track_width": 1.2,
|
"track_width": 1.2,
|
||||||
|
"tuning_profile": "",
|
||||||
"via_diameter": 0.8,
|
"via_diameter": 0.8,
|
||||||
"via_drill": 0.4,
|
"via_drill": 0.4,
|
||||||
"wire_width": 12
|
"wire_width": 12
|
||||||
@@ -574,6 +600,7 @@
|
|||||||
"priority": 3,
|
"priority": 3,
|
||||||
"schematic_color": "rgb(130, 130, 130)",
|
"schematic_color": "rgb(130, 130, 130)",
|
||||||
"track_width": 1.2,
|
"track_width": 1.2,
|
||||||
|
"tuning_profile": "",
|
||||||
"via_diameter": 0.8,
|
"via_diameter": 0.8,
|
||||||
"via_drill": 0.4,
|
"via_drill": 0.4,
|
||||||
"wire_width": 12
|
"wire_width": 12
|
||||||
@@ -592,13 +619,14 @@
|
|||||||
"priority": 4,
|
"priority": 4,
|
||||||
"schematic_color": "rgb(0, 0, 0)",
|
"schematic_color": "rgb(0, 0, 0)",
|
||||||
"track_width": 0.5,
|
"track_width": 0.5,
|
||||||
|
"tuning_profile": "",
|
||||||
"via_diameter": 0.8,
|
"via_diameter": 0.8,
|
||||||
"via_drill": 0.4,
|
"via_drill": 0.4,
|
||||||
"wire_width": 12
|
"wire_width": 12
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"meta": {
|
"meta": {
|
||||||
"version": 4
|
"version": 5
|
||||||
},
|
},
|
||||||
"net_colors": null,
|
"net_colors": null,
|
||||||
"netclass_assignments": null,
|
"netclass_assignments": null,
|
||||||
@@ -1077,6 +1105,10 @@
|
|||||||
},
|
},
|
||||||
"schematic": {
|
"schematic": {
|
||||||
"annotate_start_num": 0,
|
"annotate_start_num": 0,
|
||||||
|
"annotation": {
|
||||||
|
"method": 0,
|
||||||
|
"sort_order": 0
|
||||||
|
},
|
||||||
"bom_export_filename": "PlantCtrlESP32.csv",
|
"bom_export_filename": "PlantCtrlESP32.csv",
|
||||||
"bom_fmt_presets": [],
|
"bom_fmt_presets": [],
|
||||||
"bom_fmt_settings": {
|
"bom_fmt_settings": {
|
||||||
@@ -1256,6 +1288,7 @@
|
|||||||
"sort_asc": true,
|
"sort_asc": true,
|
||||||
"sort_field": "LCSC_PART_NUMBER"
|
"sort_field": "LCSC_PART_NUMBER"
|
||||||
},
|
},
|
||||||
|
"bus_aliases": {},
|
||||||
"connection_grid_size": 50.0,
|
"connection_grid_size": 50.0,
|
||||||
"drawing": {
|
"drawing": {
|
||||||
"dashed_lines_dash_length_ratio": 12.0,
|
"dashed_lines_dash_length_ratio": 12.0,
|
||||||
@@ -1263,6 +1296,7 @@
|
|||||||
"default_line_thickness": 6.0,
|
"default_line_thickness": 6.0,
|
||||||
"default_text_size": 50.0,
|
"default_text_size": 50.0,
|
||||||
"field_names": [],
|
"field_names": [],
|
||||||
|
"hop_over_size_choice": 0,
|
||||||
"intersheets_ref_own_page": false,
|
"intersheets_ref_own_page": false,
|
||||||
"intersheets_ref_prefix": "",
|
"intersheets_ref_prefix": "",
|
||||||
"intersheets_ref_short": false,
|
"intersheets_ref_short": false,
|
||||||
@@ -1295,6 +1329,7 @@
|
|||||||
},
|
},
|
||||||
"page_layout_descr_file": "",
|
"page_layout_descr_file": "",
|
||||||
"plot_directory": "/tmp/",
|
"plot_directory": "/tmp/",
|
||||||
|
"reuse_designators": true,
|
||||||
"space_save_all_events": true,
|
"space_save_all_events": true,
|
||||||
"spice_adjust_passive_values": false,
|
"spice_adjust_passive_values": false,
|
||||||
"spice_current_sheet_as_root": false,
|
"spice_current_sheet_as_root": false,
|
||||||
@@ -1304,7 +1339,16 @@
|
|||||||
"spice_save_all_dissipations": false,
|
"spice_save_all_dissipations": false,
|
||||||
"spice_save_all_voltages": false,
|
"spice_save_all_voltages": false,
|
||||||
"subpart_first_id": 65,
|
"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": [
|
"sheets": [
|
||||||
[
|
[
|
||||||
@@ -1312,5 +1356,11 @@
|
|||||||
"Root"
|
"Root"
|
||||||
]
|
]
|
||||||
],
|
],
|
||||||
"text_variables": {}
|
"text_variables": {},
|
||||||
|
"tuning_profiles": {
|
||||||
|
"meta": {
|
||||||
|
"version": 0
|
||||||
|
},
|
||||||
|
"tuning_profiles_impedance_geometric": []
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,6 +15,7 @@
|
|||||||
"vias": 1.0,
|
"vias": 1.0,
|
||||||
"zones": 0.6
|
"zones": 0.6
|
||||||
},
|
},
|
||||||
|
"prototype_zone_fills": false,
|
||||||
"selection_filter": {
|
"selection_filter": {
|
||||||
"dimensions": true,
|
"dimensions": true,
|
||||||
"footprints": true,
|
"footprints": true,
|
||||||
@@ -53,6 +54,7 @@
|
|||||||
"zone_display_mode": 0
|
"zone_display_mode": 0
|
||||||
},
|
},
|
||||||
"git": {
|
"git": {
|
||||||
|
"integration_disabled": false,
|
||||||
"repo_type": "",
|
"repo_type": "",
|
||||||
"repo_username": "",
|
"repo_username": "",
|
||||||
"ssh_key": ""
|
"ssh_key": ""
|
||||||
@@ -105,6 +107,7 @@
|
|||||||
"filter_text": "",
|
"filter_text": "",
|
||||||
"group_by_constraint": false,
|
"group_by_constraint": false,
|
||||||
"group_by_netclass": false,
|
"group_by_netclass": false,
|
||||||
|
"show_time_domain_details": false,
|
||||||
"show_unconnected_nets": false,
|
"show_unconnected_nets": false,
|
||||||
"show_zero_pad_nets": false,
|
"show_zero_pad_nets": false,
|
||||||
"sort_ascending": true,
|
"sort_ascending": true,
|
||||||
@@ -115,6 +118,7 @@
|
|||||||
"files": []
|
"files": []
|
||||||
},
|
},
|
||||||
"schematic": {
|
"schematic": {
|
||||||
|
"hierarchy_collapsed": [],
|
||||||
"selection_filter": {
|
"selection_filter": {
|
||||||
"graphics": true,
|
"graphics": true,
|
||||||
"images": true,
|
"images": true,
|
||||||
@@ -122,6 +126,7 @@
|
|||||||
"lockedItems": false,
|
"lockedItems": false,
|
||||||
"otherItems": true,
|
"otherItems": true,
|
||||||
"pins": true,
|
"pins": true,
|
||||||
|
"ruleAreas": true,
|
||||||
"symbols": true,
|
"symbols": true,
|
||||||
"text": true,
|
"text": true,
|
||||||
"wires": true
|
"wires": true
|
||||||
|
|||||||
@@ -3,6 +3,8 @@
|
|||||||
"3dviewports": [],
|
"3dviewports": [],
|
||||||
"design_settings": {
|
"design_settings": {
|
||||||
"defaults": {
|
"defaults": {
|
||||||
|
"apply_defaults_to_fp_barcodes": false,
|
||||||
|
"apply_defaults_to_fp_dimensions": false,
|
||||||
"apply_defaults_to_fp_fields": false,
|
"apply_defaults_to_fp_fields": false,
|
||||||
"apply_defaults_to_fp_shapes": false,
|
"apply_defaults_to_fp_shapes": false,
|
||||||
"apply_defaults_to_fp_text": false,
|
"apply_defaults_to_fp_text": false,
|
||||||
@@ -82,6 +84,7 @@
|
|||||||
"extra_footprint": "warning",
|
"extra_footprint": "warning",
|
||||||
"footprint": "error",
|
"footprint": "error",
|
||||||
"footprint_filters_mismatch": "ignore",
|
"footprint_filters_mismatch": "ignore",
|
||||||
|
"footprint_symbol_field_mismatch": "warning",
|
||||||
"footprint_symbol_mismatch": "warning",
|
"footprint_symbol_mismatch": "warning",
|
||||||
"footprint_type_mismatch": "warning",
|
"footprint_type_mismatch": "warning",
|
||||||
"hole_clearance": "error",
|
"hole_clearance": "error",
|
||||||
@@ -99,6 +102,7 @@
|
|||||||
"mirrored_text_on_front_layer": "warning",
|
"mirrored_text_on_front_layer": "warning",
|
||||||
"missing_courtyard": "ignore",
|
"missing_courtyard": "ignore",
|
||||||
"missing_footprint": "warning",
|
"missing_footprint": "warning",
|
||||||
|
"missing_tuning_profile": "warning",
|
||||||
"net_conflict": "warning",
|
"net_conflict": "warning",
|
||||||
"nonmirrored_text_on_back_layer": "warning",
|
"nonmirrored_text_on_back_layer": "warning",
|
||||||
"npth_inside_courtyard": "ignore",
|
"npth_inside_courtyard": "ignore",
|
||||||
@@ -118,9 +122,12 @@
|
|||||||
"too_many_vias": "error",
|
"too_many_vias": "error",
|
||||||
"track_angle": "error",
|
"track_angle": "error",
|
||||||
"track_dangling": "warning",
|
"track_dangling": "warning",
|
||||||
|
"track_not_centered_on_via": "ignore",
|
||||||
|
"track_on_post_machined_layer": "error",
|
||||||
"track_segment_length": "error",
|
"track_segment_length": "error",
|
||||||
"track_width": "error",
|
"track_width": "error",
|
||||||
"tracks_crossing": "error",
|
"tracks_crossing": "error",
|
||||||
|
"tuning_profile_track_geometries": "ignore",
|
||||||
"unconnected_items": "error",
|
"unconnected_items": "error",
|
||||||
"unresolved_variable": "error",
|
"unresolved_variable": "error",
|
||||||
"via_dangling": "warning",
|
"via_dangling": "warning",
|
||||||
@@ -236,17 +243,28 @@
|
|||||||
"zones_allow_external_fillets": false
|
"zones_allow_external_fillets": false
|
||||||
},
|
},
|
||||||
"ipc2581": {
|
"ipc2581": {
|
||||||
|
"bom_rev": "",
|
||||||
"dist": "",
|
"dist": "",
|
||||||
"distpn": "",
|
"distpn": "",
|
||||||
"internal_id": "",
|
"internal_id": "",
|
||||||
"mfg": "",
|
"mfg": "",
|
||||||
"mpn": ""
|
"mpn": "",
|
||||||
|
"sch_revision": ""
|
||||||
},
|
},
|
||||||
"layer_pairs": [],
|
"layer_pairs": [],
|
||||||
"layer_presets": [],
|
"layer_presets": [],
|
||||||
"viewports": []
|
"viewports": []
|
||||||
},
|
},
|
||||||
"boards": [],
|
"boards": [],
|
||||||
|
"component_class_settings": {
|
||||||
|
"assignments": [],
|
||||||
|
"meta": {
|
||||||
|
"version": 0
|
||||||
|
},
|
||||||
|
"sheet_component_classes": {
|
||||||
|
"enabled": false
|
||||||
|
}
|
||||||
|
},
|
||||||
"cvpcb": {
|
"cvpcb": {
|
||||||
"equivalence_files": []
|
"equivalence_files": []
|
||||||
},
|
},
|
||||||
@@ -495,13 +513,14 @@
|
|||||||
"priority": 2147483647,
|
"priority": 2147483647,
|
||||||
"schematic_color": "rgba(0, 0, 0, 0.000)",
|
"schematic_color": "rgba(0, 0, 0, 0.000)",
|
||||||
"track_width": 0.2,
|
"track_width": 0.2,
|
||||||
|
"tuning_profile": "",
|
||||||
"via_diameter": 0.6,
|
"via_diameter": 0.6,
|
||||||
"via_drill": 0.3,
|
"via_drill": 0.3,
|
||||||
"wire_width": 6
|
"wire_width": 6
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"meta": {
|
"meta": {
|
||||||
"version": 4
|
"version": 5
|
||||||
},
|
},
|
||||||
"net_colors": null,
|
"net_colors": null,
|
||||||
"netclass_assignments": null,
|
"netclass_assignments": null,
|
||||||
@@ -629,6 +648,7 @@
|
|||||||
"sort_asc": true,
|
"sort_asc": true,
|
||||||
"sort_field": "Reference"
|
"sort_field": "Reference"
|
||||||
},
|
},
|
||||||
|
"bus_aliases": {},
|
||||||
"connection_grid_size": 50.0,
|
"connection_grid_size": 50.0,
|
||||||
"drawing": {
|
"drawing": {
|
||||||
"dashed_lines_dash_length_ratio": 12.0,
|
"dashed_lines_dash_length_ratio": 12.0,
|
||||||
@@ -667,7 +687,14 @@
|
|||||||
"spice_save_all_dissipations": false,
|
"spice_save_all_dissipations": false,
|
||||||
"spice_save_all_voltages": false,
|
"spice_save_all_voltages": false,
|
||||||
"subpart_first_id": 65,
|
"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": [
|
"sheets": [
|
||||||
[
|
[
|
||||||
@@ -675,5 +702,11 @@
|
|||||||
"Root"
|
"Root"
|
||||||
]
|
]
|
||||||
],
|
],
|
||||||
"text_variables": {}
|
"text_variables": {},
|
||||||
|
"tuning_profiles": {
|
||||||
|
"meta": {
|
||||||
|
"version": 0
|
||||||
|
},
|
||||||
|
"tuning_profiles_impedance_geometric": []
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,6 +15,7 @@
|
|||||||
"vias": 1.0,
|
"vias": 1.0,
|
||||||
"zones": 0.6
|
"zones": 0.6
|
||||||
},
|
},
|
||||||
|
"prototype_zone_fills": false,
|
||||||
"selection_filter": {
|
"selection_filter": {
|
||||||
"dimensions": true,
|
"dimensions": true,
|
||||||
"footprints": true,
|
"footprints": true,
|
||||||
@@ -54,6 +55,7 @@
|
|||||||
"zone_display_mode": 0
|
"zone_display_mode": 0
|
||||||
},
|
},
|
||||||
"git": {
|
"git": {
|
||||||
|
"integration_disabled": false,
|
||||||
"repo_type": "",
|
"repo_type": "",
|
||||||
"repo_username": "",
|
"repo_username": "",
|
||||||
"ssh_key": ""
|
"ssh_key": ""
|
||||||
@@ -106,6 +108,7 @@
|
|||||||
"filter_text": "",
|
"filter_text": "",
|
||||||
"group_by_constraint": false,
|
"group_by_constraint": false,
|
||||||
"group_by_netclass": false,
|
"group_by_netclass": false,
|
||||||
|
"show_time_domain_details": false,
|
||||||
"show_unconnected_nets": false,
|
"show_unconnected_nets": false,
|
||||||
"show_zero_pad_nets": false,
|
"show_zero_pad_nets": false,
|
||||||
"sort_ascending": true,
|
"sort_ascending": true,
|
||||||
@@ -116,6 +119,7 @@
|
|||||||
"files": []
|
"files": []
|
||||||
},
|
},
|
||||||
"schematic": {
|
"schematic": {
|
||||||
|
"hierarchy_collapsed": [],
|
||||||
"selection_filter": {
|
"selection_filter": {
|
||||||
"graphics": true,
|
"graphics": true,
|
||||||
"images": true,
|
"images": true,
|
||||||
@@ -123,6 +127,7 @@
|
|||||||
"lockedItems": false,
|
"lockedItems": false,
|
||||||
"otherItems": true,
|
"otherItems": true,
|
||||||
"pins": true,
|
"pins": true,
|
||||||
|
"ruleAreas": true,
|
||||||
"symbols": true,
|
"symbols": true,
|
||||||
"text": true,
|
"text": true,
|
||||||
"wires": true
|
"wires": true
|
||||||
|
|||||||
@@ -3,6 +3,8 @@
|
|||||||
"3dviewports": [],
|
"3dviewports": [],
|
||||||
"design_settings": {
|
"design_settings": {
|
||||||
"defaults": {
|
"defaults": {
|
||||||
|
"apply_defaults_to_fp_barcodes": false,
|
||||||
|
"apply_defaults_to_fp_dimensions": false,
|
||||||
"apply_defaults_to_fp_fields": false,
|
"apply_defaults_to_fp_fields": false,
|
||||||
"apply_defaults_to_fp_shapes": false,
|
"apply_defaults_to_fp_shapes": false,
|
||||||
"apply_defaults_to_fp_text": false,
|
"apply_defaults_to_fp_text": false,
|
||||||
@@ -77,6 +79,7 @@
|
|||||||
"extra_footprint": "warning",
|
"extra_footprint": "warning",
|
||||||
"footprint": "error",
|
"footprint": "error",
|
||||||
"footprint_filters_mismatch": "warning",
|
"footprint_filters_mismatch": "warning",
|
||||||
|
"footprint_symbol_field_mismatch": "warning",
|
||||||
"footprint_symbol_mismatch": "warning",
|
"footprint_symbol_mismatch": "warning",
|
||||||
"footprint_type_mismatch": "warning",
|
"footprint_type_mismatch": "warning",
|
||||||
"hole_clearance": "error",
|
"hole_clearance": "error",
|
||||||
@@ -94,6 +97,7 @@
|
|||||||
"mirrored_text_on_front_layer": "warning",
|
"mirrored_text_on_front_layer": "warning",
|
||||||
"missing_courtyard": "warning",
|
"missing_courtyard": "warning",
|
||||||
"missing_footprint": "warning",
|
"missing_footprint": "warning",
|
||||||
|
"missing_tuning_profile": "warning",
|
||||||
"net_conflict": "warning",
|
"net_conflict": "warning",
|
||||||
"nonmirrored_text_on_back_layer": "warning",
|
"nonmirrored_text_on_back_layer": "warning",
|
||||||
"npth_inside_courtyard": "warning",
|
"npth_inside_courtyard": "warning",
|
||||||
@@ -113,9 +117,12 @@
|
|||||||
"too_many_vias": "error",
|
"too_many_vias": "error",
|
||||||
"track_angle": "error",
|
"track_angle": "error",
|
||||||
"track_dangling": "warning",
|
"track_dangling": "warning",
|
||||||
|
"track_not_centered_on_via": "ignore",
|
||||||
|
"track_on_post_machined_layer": "error",
|
||||||
"track_segment_length": "error",
|
"track_segment_length": "error",
|
||||||
"track_width": "error",
|
"track_width": "error",
|
||||||
"tracks_crossing": "error",
|
"tracks_crossing": "error",
|
||||||
|
"tuning_profile_track_geometries": "ignore",
|
||||||
"unconnected_items": "error",
|
"unconnected_items": "error",
|
||||||
"unresolved_variable": "error",
|
"unresolved_variable": "error",
|
||||||
"via_dangling": "warning",
|
"via_dangling": "warning",
|
||||||
@@ -227,17 +234,28 @@
|
|||||||
"zones_allow_external_fillets": false
|
"zones_allow_external_fillets": false
|
||||||
},
|
},
|
||||||
"ipc2581": {
|
"ipc2581": {
|
||||||
|
"bom_rev": "",
|
||||||
"dist": "",
|
"dist": "",
|
||||||
"distpn": "",
|
"distpn": "",
|
||||||
"internal_id": "",
|
"internal_id": "",
|
||||||
"mfg": "",
|
"mfg": "",
|
||||||
"mpn": ""
|
"mpn": "",
|
||||||
|
"sch_revision": ""
|
||||||
},
|
},
|
||||||
"layer_pairs": [],
|
"layer_pairs": [],
|
||||||
"layer_presets": [],
|
"layer_presets": [],
|
||||||
"viewports": []
|
"viewports": []
|
||||||
},
|
},
|
||||||
"boards": [],
|
"boards": [],
|
||||||
|
"component_class_settings": {
|
||||||
|
"assignments": [],
|
||||||
|
"meta": {
|
||||||
|
"version": 0
|
||||||
|
},
|
||||||
|
"sheet_component_classes": {
|
||||||
|
"enabled": false
|
||||||
|
}
|
||||||
|
},
|
||||||
"cvpcb": {
|
"cvpcb": {
|
||||||
"equivalence_files": []
|
"equivalence_files": []
|
||||||
},
|
},
|
||||||
@@ -486,13 +504,14 @@
|
|||||||
"priority": 2147483647,
|
"priority": 2147483647,
|
||||||
"schematic_color": "rgba(0, 0, 0, 0.000)",
|
"schematic_color": "rgba(0, 0, 0, 0.000)",
|
||||||
"track_width": 0.2,
|
"track_width": 0.2,
|
||||||
|
"tuning_profile": "",
|
||||||
"via_diameter": 0.6,
|
"via_diameter": 0.6,
|
||||||
"via_drill": 0.3,
|
"via_drill": 0.3,
|
||||||
"wire_width": 6
|
"wire_width": 6
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"meta": {
|
"meta": {
|
||||||
"version": 4
|
"version": 5
|
||||||
},
|
},
|
||||||
"net_colors": null,
|
"net_colors": null,
|
||||||
"netclass_assignments": null,
|
"netclass_assignments": null,
|
||||||
@@ -861,6 +880,7 @@
|
|||||||
"sort_asc": true,
|
"sort_asc": true,
|
||||||
"sort_field": "Reference"
|
"sort_field": "Reference"
|
||||||
},
|
},
|
||||||
|
"bus_aliases": {},
|
||||||
"connection_grid_size": 50.0,
|
"connection_grid_size": 50.0,
|
||||||
"drawing": {
|
"drawing": {
|
||||||
"dashed_lines_dash_length_ratio": 12.0,
|
"dashed_lines_dash_length_ratio": 12.0,
|
||||||
@@ -899,7 +919,14 @@
|
|||||||
"spice_save_all_dissipations": false,
|
"spice_save_all_dissipations": false,
|
||||||
"spice_save_all_voltages": false,
|
"spice_save_all_voltages": false,
|
||||||
"subpart_first_id": 65,
|
"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": [
|
"sheets": [
|
||||||
[
|
[
|
||||||
@@ -907,5 +934,11 @@
|
|||||||
"Root"
|
"Root"
|
||||||
]
|
]
|
||||||
],
|
],
|
||||||
"text_variables": {}
|
"text_variables": {},
|
||||||
|
"tuning_profiles": {
|
||||||
|
"meta": {
|
||||||
|
"version": 0
|
||||||
|
},
|
||||||
|
"tuning_profiles_impedance_geometric": []
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -2,7 +2,7 @@
|
|||||||
"board": {
|
"board": {
|
||||||
"active_layer": 0,
|
"active_layer": 0,
|
||||||
"active_layer_preset": "",
|
"active_layer_preset": "",
|
||||||
"auto_track_width": true,
|
"auto_track_width": false,
|
||||||
"hidden_netclasses": [],
|
"hidden_netclasses": [],
|
||||||
"hidden_nets": [],
|
"hidden_nets": [],
|
||||||
"high_contrast_mode": 0,
|
"high_contrast_mode": 0,
|
||||||
@@ -15,6 +15,7 @@
|
|||||||
"vias": 1.0,
|
"vias": 1.0,
|
||||||
"zones": 0.6
|
"zones": 0.6
|
||||||
},
|
},
|
||||||
|
"prototype_zone_fills": false,
|
||||||
"selection_filter": {
|
"selection_filter": {
|
||||||
"dimensions": true,
|
"dimensions": true,
|
||||||
"footprints": true,
|
"footprints": true,
|
||||||
@@ -54,6 +55,7 @@
|
|||||||
"zone_display_mode": 0
|
"zone_display_mode": 0
|
||||||
},
|
},
|
||||||
"git": {
|
"git": {
|
||||||
|
"integration_disabled": false,
|
||||||
"repo_type": "",
|
"repo_type": "",
|
||||||
"repo_username": "",
|
"repo_username": "",
|
||||||
"ssh_key": ""
|
"ssh_key": ""
|
||||||
@@ -63,8 +65,30 @@
|
|||||||
"version": 5
|
"version": 5
|
||||||
},
|
},
|
||||||
"net_inspector_panel": {
|
"net_inspector_panel": {
|
||||||
"col_hidden": [],
|
"col_hidden": [
|
||||||
"col_order": [],
|
false,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
false
|
||||||
|
],
|
||||||
|
"col_order": [
|
||||||
|
0,
|
||||||
|
1,
|
||||||
|
2,
|
||||||
|
3,
|
||||||
|
4,
|
||||||
|
5,
|
||||||
|
6,
|
||||||
|
7,
|
||||||
|
8,
|
||||||
|
9
|
||||||
|
],
|
||||||
"col_widths": [],
|
"col_widths": [],
|
||||||
"custom_group_rules": [],
|
"custom_group_rules": [],
|
||||||
"expanded_rows": [],
|
"expanded_rows": [],
|
||||||
@@ -73,6 +97,7 @@
|
|||||||
"filter_text": "",
|
"filter_text": "",
|
||||||
"group_by_constraint": false,
|
"group_by_constraint": false,
|
||||||
"group_by_netclass": false,
|
"group_by_netclass": false,
|
||||||
|
"show_time_domain_details": false,
|
||||||
"show_unconnected_nets": false,
|
"show_unconnected_nets": false,
|
||||||
"show_zero_pad_nets": false,
|
"show_zero_pad_nets": false,
|
||||||
"sort_ascending": true,
|
"sort_ascending": true,
|
||||||
@@ -83,6 +108,7 @@
|
|||||||
"files": []
|
"files": []
|
||||||
},
|
},
|
||||||
"schematic": {
|
"schematic": {
|
||||||
|
"hierarchy_collapsed": [],
|
||||||
"selection_filter": {
|
"selection_filter": {
|
||||||
"graphics": true,
|
"graphics": true,
|
||||||
"images": true,
|
"images": true,
|
||||||
@@ -90,6 +116,7 @@
|
|||||||
"lockedItems": false,
|
"lockedItems": false,
|
||||||
"otherItems": true,
|
"otherItems": true,
|
||||||
"pins": true,
|
"pins": true,
|
||||||
|
"ruleAreas": true,
|
||||||
"symbols": true,
|
"symbols": true,
|
||||||
"text": true,
|
"text": true,
|
||||||
"wires": true
|
"wires": true
|
||||||
|
|||||||
@@ -2,25 +2,262 @@
|
|||||||
"board": {
|
"board": {
|
||||||
"3dviewports": [],
|
"3dviewports": [],
|
||||||
"design_settings": {
|
"design_settings": {
|
||||||
"defaults": {},
|
"defaults": {
|
||||||
"diff_pair_dimensions": [],
|
"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": [],
|
"drc_exclusions": [],
|
||||||
"rules": {},
|
"meta": {
|
||||||
"track_widths": [],
|
"version": 2
|
||||||
"via_dimensions": []
|
},
|
||||||
|
"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": {
|
"ipc2581": {
|
||||||
|
"bom_rev": "",
|
||||||
"dist": "",
|
"dist": "",
|
||||||
"distpn": "",
|
"distpn": "",
|
||||||
"internal_id": "",
|
"internal_id": "",
|
||||||
"mfg": "",
|
"mfg": "",
|
||||||
"mpn": ""
|
"mpn": "",
|
||||||
|
"sch_revision": ""
|
||||||
},
|
},
|
||||||
"layer_pairs": [],
|
"layer_pairs": [],
|
||||||
"layer_presets": [],
|
"layer_presets": [],
|
||||||
"viewports": []
|
"viewports": []
|
||||||
},
|
},
|
||||||
"boards": [],
|
"boards": [],
|
||||||
|
"component_class_settings": {
|
||||||
|
"assignments": [],
|
||||||
|
"meta": {
|
||||||
|
"version": 0
|
||||||
|
},
|
||||||
|
"sheet_component_classes": {
|
||||||
|
"enabled": false
|
||||||
|
}
|
||||||
|
},
|
||||||
"cvpcb": {
|
"cvpcb": {
|
||||||
"equivalence_files": []
|
"equivalence_files": []
|
||||||
},
|
},
|
||||||
@@ -210,11 +447,14 @@
|
|||||||
"duplicate_sheet_names": "error",
|
"duplicate_sheet_names": "error",
|
||||||
"endpoint_off_grid": "warning",
|
"endpoint_off_grid": "warning",
|
||||||
"extra_units": "error",
|
"extra_units": "error",
|
||||||
|
"field_name_whitespace": "warning",
|
||||||
"footprint_filter": "ignore",
|
"footprint_filter": "ignore",
|
||||||
"footprint_link_issues": "warning",
|
"footprint_link_issues": "warning",
|
||||||
"four_way_junction": "ignore",
|
"four_way_junction": "ignore",
|
||||||
"global_label_dangling": "warning",
|
"global_label_dangling": "warning",
|
||||||
|
"ground_pin_not_ground": "warning",
|
||||||
"hier_label_mismatch": "error",
|
"hier_label_mismatch": "error",
|
||||||
|
"isolated_pin_label": "warning",
|
||||||
"label_dangling": "error",
|
"label_dangling": "error",
|
||||||
"label_multiple_wires": "warning",
|
"label_multiple_wires": "warning",
|
||||||
"lib_symbol_issues": "warning",
|
"lib_symbol_issues": "warning",
|
||||||
@@ -237,6 +477,7 @@
|
|||||||
"similar_power": "warning",
|
"similar_power": "warning",
|
||||||
"simulation_model_issue": "ignore",
|
"simulation_model_issue": "ignore",
|
||||||
"single_global_label": "ignore",
|
"single_global_label": "ignore",
|
||||||
|
"stacked_pin_name": "warning",
|
||||||
"unannotated": "error",
|
"unannotated": "error",
|
||||||
"unconnected_wire_endpoint": "warning",
|
"unconnected_wire_endpoint": "warning",
|
||||||
"undefined_netclass": "error",
|
"undefined_netclass": "error",
|
||||||
@@ -269,13 +510,14 @@
|
|||||||
"priority": 2147483647,
|
"priority": 2147483647,
|
||||||
"schematic_color": "rgba(0, 0, 0, 0.000)",
|
"schematic_color": "rgba(0, 0, 0, 0.000)",
|
||||||
"track_width": 0.2,
|
"track_width": 0.2,
|
||||||
|
"tuning_profile": "",
|
||||||
"via_diameter": 0.6,
|
"via_diameter": 0.6,
|
||||||
"via_drill": 0.3,
|
"via_drill": 0.3,
|
||||||
"wire_width": 6
|
"wire_width": 6
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"meta": {
|
"meta": {
|
||||||
"version": 4
|
"version": 5
|
||||||
},
|
},
|
||||||
"net_colors": null,
|
"net_colors": null,
|
||||||
"netclass_assignments": null,
|
"netclass_assignments": null,
|
||||||
@@ -297,6 +539,10 @@
|
|||||||
},
|
},
|
||||||
"schematic": {
|
"schematic": {
|
||||||
"annotate_start_num": 0,
|
"annotate_start_num": 0,
|
||||||
|
"annotation": {
|
||||||
|
"method": 0,
|
||||||
|
"sort_order": 0
|
||||||
|
},
|
||||||
"bom_export_filename": "${PROJECTNAME}.csv",
|
"bom_export_filename": "${PROJECTNAME}.csv",
|
||||||
"bom_fmt_presets": [],
|
"bom_fmt_presets": [],
|
||||||
"bom_fmt_settings": {
|
"bom_fmt_settings": {
|
||||||
@@ -359,15 +605,298 @@
|
|||||||
"label": "Datasheet",
|
"label": "Datasheet",
|
||||||
"name": "Datasheet",
|
"name": "Datasheet",
|
||||||
"show": true
|
"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": "",
|
"filter_string": "",
|
||||||
"group_symbols": true,
|
"group_symbols": true,
|
||||||
"include_excluded_from_bom": true,
|
"include_excluded_from_bom": true,
|
||||||
"name": "Default Editing",
|
"name": "",
|
||||||
"sort_asc": true,
|
"sort_asc": true,
|
||||||
"sort_field": "Reference"
|
"sort_field": "Reference"
|
||||||
},
|
},
|
||||||
|
"bus_aliases": {},
|
||||||
"connection_grid_size": 50.0,
|
"connection_grid_size": 50.0,
|
||||||
"drawing": {
|
"drawing": {
|
||||||
"dashed_lines_dash_length_ratio": 12.0,
|
"dashed_lines_dash_length_ratio": 12.0,
|
||||||
@@ -375,6 +904,7 @@
|
|||||||
"default_line_thickness": 6.0,
|
"default_line_thickness": 6.0,
|
||||||
"default_text_size": 50.0,
|
"default_text_size": 50.0,
|
||||||
"field_names": [],
|
"field_names": [],
|
||||||
|
"hop_over_size_choice": 0,
|
||||||
"intersheets_ref_own_page": false,
|
"intersheets_ref_own_page": false,
|
||||||
"intersheets_ref_prefix": "",
|
"intersheets_ref_prefix": "",
|
||||||
"intersheets_ref_short": false,
|
"intersheets_ref_short": false,
|
||||||
@@ -398,6 +928,7 @@
|
|||||||
"net_format_name": "",
|
"net_format_name": "",
|
||||||
"page_layout_descr_file": "",
|
"page_layout_descr_file": "",
|
||||||
"plot_directory": "",
|
"plot_directory": "",
|
||||||
|
"reuse_designators": true,
|
||||||
"space_save_all_events": true,
|
"space_save_all_events": true,
|
||||||
"spice_current_sheet_as_root": false,
|
"spice_current_sheet_as_root": false,
|
||||||
"spice_external_command": "spice \"%I\"",
|
"spice_external_command": "spice \"%I\"",
|
||||||
@@ -406,13 +937,28 @@
|
|||||||
"spice_save_all_dissipations": false,
|
"spice_save_all_dissipations": false,
|
||||||
"spice_save_all_voltages": false,
|
"spice_save_all_voltages": false,
|
||||||
"subpart_first_id": 65,
|
"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": [
|
"sheets": [
|
||||||
[
|
[
|
||||||
"7972d0e7-2611-420d-b298-ef8307db6186",
|
"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
1
Hardware/open-bms/bms/fabrication-toolkit-options.json
Normal file
1
Hardware/open-bms/bms/fabrication-toolkit-options.json
Normal 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}
|
||||||
@@ -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)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
4
Hardware/open-bms/bms/fp-lib-table
Normal file
4
Hardware/open-bms/bms/fp-lib-table
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
(fp_lib_table
|
||||||
|
(version 7)
|
||||||
|
(lib (name "amass") (type "KiCad") (uri "${KIPRJMOD}/footprints/amass") (options "") (descr ""))
|
||||||
|
)
|
||||||
@@ -23,6 +23,8 @@ target = "riscv32imac-unknown-none-elf"
|
|||||||
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"
|
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 }
|
CARGO_WORKSPACE_DIR = { value = "", relative = true }
|
||||||
ESP_LOG = "info"
|
ESP_LOG = "info"
|
||||||
|
PATH = { value = "../../../bin:/usr/bin:/usr/local/bin", force = true, relative = true }
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
[unstable]
|
[unstable]
|
||||||
|
|||||||
@@ -109,12 +109,13 @@ pca9535 = { version = "2.0.0" }
|
|||||||
ina219 = { version = "0.2.0" }
|
ina219 = { version = "0.2.0" }
|
||||||
|
|
||||||
# Storage and filesystem
|
# Storage and filesystem
|
||||||
embedded-savegame = { version = "0.3.0" }
|
littlefs2 = { version = "0.6.1", features = ["c-stubs", "alloc"] }
|
||||||
|
littlefs2-core = "0.1.2"
|
||||||
|
|
||||||
# Serialization / codecs
|
# Serialization / codecs
|
||||||
serde = { version = "1.0.228", features = ["derive", "alloc"], default-features = false }
|
serde = { version = "1.0.228", features = ["derive", "alloc"], default-features = false }
|
||||||
serde_json = { version = "1.0.145", default-features = false, features = ["alloc"] }
|
serde_json = { version = "1.0.145", default-features = false, features = ["alloc"] }
|
||||||
bincode = { version = "2.0.1", default-features = false, features = ["derive", "alloc"] }
|
bincode = { version = "2.0.1", default-features = false, features = ["derive"] }
|
||||||
|
|
||||||
# Time and time zones
|
# Time and time zones
|
||||||
chrono = { version = "0.4.42", default-features = false, features = ["iana-time-zone", "alloc", "serde"] }
|
chrono = { version = "0.4.42", default-features = false, features = ["iana-time-zone", "alloc", "serde"] }
|
||||||
@@ -135,7 +136,6 @@ measurements = "0.11.1"
|
|||||||
|
|
||||||
# Project-specific
|
# Project-specific
|
||||||
mcutie = { version = "0.3.0", default-features = false, features = ["log", "homeassistant"] }
|
mcutie = { version = "0.3.0", default-features = false, features = ["log", "homeassistant"] }
|
||||||
no-panic = "0.1.36"
|
|
||||||
|
|
||||||
[patch.crates-io]
|
[patch.crates-io]
|
||||||
mcutie = { git = 'https://github.com/empirephoenix/mcutie.git' }
|
mcutie = { git = 'https://github.com/empirephoenix/mcutie.git' }
|
||||||
|
|||||||
@@ -1,2 +0,0 @@
|
|||||||
# This file is used for clippy configuration.
|
|
||||||
# It shouldn't contain the deny attributes, which belong to the crate root.
|
|
||||||
@@ -1,17 +1,17 @@
|
|||||||
use crate::hal::PLANT_COUNT;
|
use crate::hal::PLANT_COUNT;
|
||||||
use crate::plant_state::PlantWateringMode;
|
use crate::plant_state::PlantWateringMode;
|
||||||
use alloc::string::{String, ToString};
|
use alloc::string::String;
|
||||||
use bincode::{Decode, Encode};
|
use core::str::FromStr;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Encode, Decode)]
|
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub struct NetworkConfig {
|
pub struct NetworkConfig {
|
||||||
pub ap_ssid: String,
|
pub ap_ssid: heapless::String<32>,
|
||||||
pub ssid: Option<String>,
|
pub ssid: Option<heapless::String<32>>,
|
||||||
pub password: Option<String>,
|
pub password: Option<heapless::String<64>>,
|
||||||
pub mqtt_url: Option<String>,
|
pub mqtt_url: Option<String>,
|
||||||
pub base_topic: Option<String>,
|
pub base_topic: Option<heapless::String<64>>,
|
||||||
pub mqtt_user: Option<String>,
|
pub mqtt_user: Option<String>,
|
||||||
pub mqtt_password: Option<String>,
|
pub mqtt_password: Option<String>,
|
||||||
pub max_wait: u32,
|
pub max_wait: u32,
|
||||||
@@ -19,7 +19,7 @@ pub struct NetworkConfig {
|
|||||||
impl Default for NetworkConfig {
|
impl Default for NetworkConfig {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
ap_ssid: "PlantCtrl Init".to_string(),
|
ap_ssid: heapless::String::from_str("PlantCtrl Init").unwrap(),
|
||||||
ssid: None,
|
ssid: None,
|
||||||
password: None,
|
password: None,
|
||||||
mqtt_url: None,
|
mqtt_url: None,
|
||||||
@@ -31,7 +31,7 @@ impl Default for NetworkConfig {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Encode, Decode)]
|
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub struct NightLampConfig {
|
pub struct NightLampConfig {
|
||||||
pub enabled: bool,
|
pub enabled: bool,
|
||||||
@@ -54,7 +54,7 @@ impl Default for NightLampConfig {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Encode, Decode)]
|
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub struct TankConfig {
|
pub struct TankConfig {
|
||||||
pub tank_sensor_enabled: bool,
|
pub tank_sensor_enabled: bool,
|
||||||
@@ -79,26 +79,26 @@ impl Default for TankConfig {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Default, Encode, Decode)]
|
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Default)]
|
||||||
pub enum BatteryBoardVersion {
|
pub enum BatteryBoardVersion {
|
||||||
#[default]
|
#[default]
|
||||||
Disabled,
|
Disabled,
|
||||||
WchI2cSlave,
|
WchI2cSlave,
|
||||||
}
|
}
|
||||||
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Default, Encode, Decode)]
|
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Default)]
|
||||||
pub enum BoardVersion {
|
pub enum BoardVersion {
|
||||||
Initial,
|
Initial,
|
||||||
#[default]
|
#[default]
|
||||||
V4,
|
V4,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Default, Encode, Decode)]
|
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Default)]
|
||||||
pub struct BoardHardware {
|
pub struct BoardHardware {
|
||||||
pub board: BoardVersion,
|
pub board: BoardVersion,
|
||||||
pub battery: BatteryBoardVersion,
|
pub battery: BatteryBoardVersion,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Default, Encode, Decode)]
|
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Default)]
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub struct PlantControllerConfig {
|
pub struct PlantControllerConfig {
|
||||||
pub hardware: BoardHardware,
|
pub hardware: BoardHardware,
|
||||||
@@ -109,7 +109,7 @@ pub struct PlantControllerConfig {
|
|||||||
pub timezone: Option<String>,
|
pub timezone: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Encode, Decode)]
|
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub struct PlantConfig {
|
pub struct PlantConfig {
|
||||||
pub mode: PlantWateringMode,
|
pub mode: PlantWateringMode,
|
||||||
|
|||||||
@@ -1,7 +1,5 @@
|
|||||||
use alloc::format;
|
use alloc::format;
|
||||||
use alloc::string::{String, ToString};
|
use alloc::string::{String, ToString};
|
||||||
use chrono::format::ParseErrorKind;
|
|
||||||
use chrono_tz::ParseError;
|
|
||||||
use core::convert::Infallible;
|
use core::convert::Infallible;
|
||||||
use core::fmt;
|
use core::fmt;
|
||||||
use core::fmt::Debug;
|
use core::fmt::Debug;
|
||||||
@@ -16,18 +14,28 @@ use esp_hal::twai::EspTwaiError;
|
|||||||
use esp_radio::wifi::WifiError;
|
use esp_radio::wifi::WifiError;
|
||||||
use ina219::errors::{BusVoltageReadError, ShuntVoltageReadError};
|
use ina219::errors::{BusVoltageReadError, ShuntVoltageReadError};
|
||||||
use lib_bms_protocol::BmsProtocolError;
|
use lib_bms_protocol::BmsProtocolError;
|
||||||
|
use littlefs2_core::PathError;
|
||||||
use onewire::Error;
|
use onewire::Error;
|
||||||
use pca9535::ExpanderError;
|
use pca9535::ExpanderError;
|
||||||
|
|
||||||
//All error superconstruct
|
//All error superconstruct
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum FatError {
|
pub enum FatError {
|
||||||
|
BMSError {
|
||||||
|
error: String,
|
||||||
|
},
|
||||||
OneWireError {
|
OneWireError {
|
||||||
error: Error<Infallible>,
|
error: Error<Infallible>,
|
||||||
},
|
},
|
||||||
String {
|
String {
|
||||||
error: String,
|
error: String,
|
||||||
},
|
},
|
||||||
|
LittleFSError {
|
||||||
|
error: littlefs2_core::Error,
|
||||||
|
},
|
||||||
|
PathError {
|
||||||
|
error: PathError,
|
||||||
|
},
|
||||||
TryLockError {
|
TryLockError {
|
||||||
error: TryLockError,
|
error: TryLockError,
|
||||||
},
|
},
|
||||||
@@ -78,6 +86,8 @@ impl fmt::Display for FatError {
|
|||||||
}
|
}
|
||||||
FatError::OneWireError { error } => write!(f, "OneWireError {error:?}"),
|
FatError::OneWireError { error } => write!(f, "OneWireError {error:?}"),
|
||||||
FatError::String { error } => write!(f, "{error}"),
|
FatError::String { error } => write!(f, "{error}"),
|
||||||
|
FatError::LittleFSError { error } => write!(f, "LittleFSError {error:?}"),
|
||||||
|
FatError::PathError { error } => write!(f, "PathError {error:?}"),
|
||||||
FatError::TryLockError { error } => write!(f, "TryLockError {error:?}"),
|
FatError::TryLockError { error } => write!(f, "TryLockError {error:?}"),
|
||||||
FatError::WifiError { error } => write!(f, "WifiError {error:?}"),
|
FatError::WifiError { error } => write!(f, "WifiError {error:?}"),
|
||||||
FatError::SerdeError { error } => write!(f, "SerdeError {error:?}"),
|
FatError::SerdeError { error } => write!(f, "SerdeError {error:?}"),
|
||||||
@@ -96,6 +106,7 @@ impl fmt::Display for FatError {
|
|||||||
write!(f, "CanBusError {error:?}")
|
write!(f, "CanBusError {error:?}")
|
||||||
}
|
}
|
||||||
FatError::SNTPError { error } => write!(f, "SNTPError {error:?}"),
|
FatError::SNTPError { error } => write!(f, "SNTPError {error:?}"),
|
||||||
|
FatError::BMSError { error } => write!(f, "BMSError, {error}"),
|
||||||
FatError::OTAError => {
|
FatError::OTAError => {
|
||||||
write!(f, "OTA missing partition")
|
write!(f, "OTA missing partition")
|
||||||
}
|
}
|
||||||
@@ -138,28 +149,23 @@ impl<T> ContextExt<T> for Option<T> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T, E> ContextExt<T> for Result<T, E>
|
|
||||||
where
|
|
||||||
E: fmt::Debug,
|
|
||||||
{
|
|
||||||
fn context<C>(self, context: C) -> Result<T, FatError>
|
|
||||||
where
|
|
||||||
C: AsRef<str>,
|
|
||||||
{
|
|
||||||
match self {
|
|
||||||
Ok(value) => Ok(value),
|
|
||||||
Err(err) => Err(FatError::String {
|
|
||||||
error: format!("{}: {:?}", context.as_ref(), err),
|
|
||||||
}),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<Error<Infallible>> for FatError {
|
impl From<Error<Infallible>> for FatError {
|
||||||
fn from(error: Error<Infallible>) -> Self {
|
fn from(error: Error<Infallible>) -> Self {
|
||||||
FatError::OneWireError { error }
|
FatError::OneWireError { error }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
impl From<littlefs2_core::Error> for FatError {
|
||||||
|
fn from(value: littlefs2_core::Error) -> Self {
|
||||||
|
FatError::LittleFSError { error: value }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<PathError> for FatError {
|
||||||
|
fn from(value: PathError) -> Self {
|
||||||
|
FatError::PathError { error: value }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl From<TryLockError> for FatError {
|
impl From<TryLockError> for FatError {
|
||||||
fn from(value: TryLockError) -> Self {
|
fn from(value: TryLockError) -> Self {
|
||||||
FatError::TryLockError { error: value }
|
FatError::TryLockError { error: value }
|
||||||
@@ -277,7 +283,7 @@ impl<E: fmt::Debug> From<ShuntVoltageReadError<I2cDeviceError<E>>> for FatError
|
|||||||
|
|
||||||
impl From<Infallible> for FatError {
|
impl From<Infallible> for FatError {
|
||||||
fn from(value: Infallible) -> Self {
|
fn from(value: Infallible) -> Self {
|
||||||
match value {}
|
panic!("Infallible error: {:?}", value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -330,27 +336,3 @@ impl From<BmsProtocolError> for FatError {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<ParseError> for FatError {
|
|
||||||
fn from(value: ParseError) -> Self {
|
|
||||||
FatError::String {
|
|
||||||
error: format!("Parsing error: {value:?}"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<ParseErrorKind> for FatError {
|
|
||||||
fn from(value: ParseErrorKind) -> Self {
|
|
||||||
FatError::String {
|
|
||||||
error: format!("Parsing error: {value:?}"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<chrono::format::ParseError> for FatError {
|
|
||||||
fn from(value: chrono::format::ParseError) -> Self {
|
|
||||||
FatError::String {
|
|
||||||
error: format!("Parsing error: {value:?}"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,16 +1,18 @@
|
|||||||
use crate::bail;
|
use crate::bail;
|
||||||
use crate::config::{NetworkConfig, PlantControllerConfig};
|
use crate::config::{NetworkConfig, PlantControllerConfig};
|
||||||
use crate::hal::savegame_manager::SavegameManager;
|
|
||||||
use crate::hal::{PLANT_COUNT, TIME_ACCESS};
|
use crate::hal::{PLANT_COUNT, TIME_ACCESS};
|
||||||
use crate::log::{LogMessage, LOG_ACCESS};
|
use crate::log::{LogMessage, LOG_ACCESS};
|
||||||
use chrono::{DateTime, Utc};
|
use chrono::{DateTime, Utc};
|
||||||
|
use serde::Serialize;
|
||||||
|
|
||||||
use crate::fat_error::{ContextExt, FatError, FatResult};
|
use crate::fat_error::{ContextExt, FatError, FatResult};
|
||||||
|
use crate::hal::little_fs2storage_adapter::LittleFs2Filesystem;
|
||||||
use crate::hal::shared_flash::MutexFlashStorage;
|
use crate::hal::shared_flash::MutexFlashStorage;
|
||||||
use alloc::string::ToString;
|
use alloc::string::ToString;
|
||||||
use alloc::sync::Arc;
|
use alloc::sync::Arc;
|
||||||
use alloc::{format, string::String, vec, vec::Vec};
|
use alloc::{format, string::String, vec, vec::Vec};
|
||||||
use core::net::{IpAddr, Ipv4Addr, SocketAddr};
|
use core::net::{IpAddr, Ipv4Addr, SocketAddr};
|
||||||
|
use core::str::FromStr;
|
||||||
use core::sync::atomic::Ordering;
|
use core::sync::atomic::Ordering;
|
||||||
use embassy_executor::Spawner;
|
use embassy_executor::Spawner;
|
||||||
use embassy_net::udp::UdpSocket;
|
use embassy_net::udp::UdpSocket;
|
||||||
@@ -37,6 +39,8 @@ use esp_radio::wifi::{
|
|||||||
AccessPointConfig, AccessPointInfo, AuthMethod, ClientConfig, ModeConfig, ScanConfig,
|
AccessPointConfig, AccessPointInfo, AuthMethod, ClientConfig, ModeConfig, ScanConfig,
|
||||||
ScanTypeConfig, WifiController, WifiDevice, WifiStaState,
|
ScanTypeConfig, WifiController, WifiDevice, WifiStaState,
|
||||||
};
|
};
|
||||||
|
use littlefs2::fs::Filesystem;
|
||||||
|
use littlefs2_core::{FileType, PathBuf, SeekFrom};
|
||||||
use log::{error, info, warn};
|
use log::{error, info, warn};
|
||||||
use mcutie::{
|
use mcutie::{
|
||||||
Error, McutieBuilder, McutieReceiver, McutieTask, MqttMessage, PublishDisplay, Publishable,
|
Error, McutieBuilder, McutieReceiver, McutieTask, MqttMessage, PublishDisplay, Publishable,
|
||||||
@@ -56,6 +60,7 @@ static mut LOW_VOLTAGE_DETECTED: i8 = 0;
|
|||||||
#[esp_hal::ram(unstable(rtc_fast), unstable(persistent))]
|
#[esp_hal::ram(unstable(rtc_fast), unstable(persistent))]
|
||||||
static mut RESTART_TO_CONF: i8 = 0;
|
static mut RESTART_TO_CONF: i8 = 0;
|
||||||
|
|
||||||
|
const CONFIG_FILE: &str = "config.json";
|
||||||
const NTP_SERVER: &str = "pool.ntp.org";
|
const NTP_SERVER: &str = "pool.ntp.org";
|
||||||
|
|
||||||
static MQTT_CONNECTED_EVENT_RECEIVED: AtomicBool = AtomicBool::new(false);
|
static MQTT_CONNECTED_EVENT_RECEIVED: AtomicBool = AtomicBool::new(false);
|
||||||
@@ -63,6 +68,19 @@ static MQTT_ROUND_TRIP_RECEIVED: AtomicBool = AtomicBool::new(false);
|
|||||||
pub static MQTT_STAY_ALIVE: AtomicBool = AtomicBool::new(false);
|
pub static MQTT_STAY_ALIVE: AtomicBool = AtomicBool::new(false);
|
||||||
static MQTT_BASE_TOPIC: OnceLock<String> = OnceLock::new();
|
static MQTT_BASE_TOPIC: OnceLock<String> = OnceLock::new();
|
||||||
|
|
||||||
|
#[derive(Serialize, Debug)]
|
||||||
|
pub struct FileInfo {
|
||||||
|
filename: String,
|
||||||
|
size: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Debug)]
|
||||||
|
pub struct FileList {
|
||||||
|
total: usize,
|
||||||
|
used: usize,
|
||||||
|
files: Vec<FileInfo>,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Copy, Clone, Default)]
|
#[derive(Copy, Clone, Default)]
|
||||||
struct Timestamp {
|
struct Timestamp {
|
||||||
stamp: DateTime<Utc>,
|
stamp: DateTime<Utc>,
|
||||||
@@ -99,7 +117,7 @@ impl NtpTimestampGenerator for Timestamp {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub struct Esp<'a> {
|
pub struct Esp<'a> {
|
||||||
pub savegame: SavegameManager,
|
pub fs: Arc<Mutex<CriticalSectionRawMutex, Filesystem<'static, LittleFs2Filesystem>>>,
|
||||||
pub rng: Rng,
|
pub rng: Rng,
|
||||||
//first starter (ap or sta will take these)
|
//first starter (ap or sta will take these)
|
||||||
pub interface_sta: Option<WifiDevice<'static>>,
|
pub interface_sta: Option<WifiDevice<'static>>,
|
||||||
@@ -167,6 +185,69 @@ impl Esp<'_> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
pub(crate) async fn delete_file(&self, filename: String) -> FatResult<()> {
|
||||||
|
let file = PathBuf::try_from(filename.as_str())?;
|
||||||
|
let access = self.fs.lock().await;
|
||||||
|
access.remove(&file)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
pub(crate) async fn write_file(
|
||||||
|
&mut self,
|
||||||
|
filename: String,
|
||||||
|
offset: u32,
|
||||||
|
buf: &[u8],
|
||||||
|
) -> Result<(), FatError> {
|
||||||
|
let file = PathBuf::try_from(filename.as_str())?;
|
||||||
|
let access = self.fs.lock().await;
|
||||||
|
access.open_file_with_options_and_then(
|
||||||
|
|options| options.read(true).write(true).create(true),
|
||||||
|
&file,
|
||||||
|
|file| {
|
||||||
|
file.seek(SeekFrom::Start(offset))?;
|
||||||
|
file.write(buf)?;
|
||||||
|
Ok(())
|
||||||
|
},
|
||||||
|
)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn get_size(&mut self, filename: String) -> FatResult<usize> {
|
||||||
|
let file = PathBuf::try_from(filename.as_str())?;
|
||||||
|
let access = self.fs.lock().await;
|
||||||
|
let data = access.metadata(&file)?;
|
||||||
|
Ok(data.len())
|
||||||
|
}
|
||||||
|
pub(crate) async fn get_file(
|
||||||
|
&mut self,
|
||||||
|
filename: String,
|
||||||
|
chunk: u32,
|
||||||
|
) -> FatResult<([u8; 512], usize)> {
|
||||||
|
use littlefs2::io::Error as lfs2Error;
|
||||||
|
|
||||||
|
let file = PathBuf::try_from(filename.as_str())?;
|
||||||
|
let access = self.fs.lock().await;
|
||||||
|
let mut buf = [0_u8; 512];
|
||||||
|
let mut read = 0;
|
||||||
|
let offset = chunk * buf.len() as u32;
|
||||||
|
access.open_file_with_options_and_then(
|
||||||
|
|options| options.read(true),
|
||||||
|
&file,
|
||||||
|
|file| {
|
||||||
|
let length = file.len()? as u32;
|
||||||
|
if length == 0 {
|
||||||
|
Err(lfs2Error::IO)
|
||||||
|
} else if length > offset {
|
||||||
|
file.seek(SeekFrom::Start(offset))?;
|
||||||
|
read = file.read(&mut buf)?;
|
||||||
|
Ok(())
|
||||||
|
} else {
|
||||||
|
//exactly at end, do nothing
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)?;
|
||||||
|
Ok((buf, read))
|
||||||
|
}
|
||||||
|
|
||||||
pub(crate) async fn write_ota(&mut self, offset: u32, buf: &[u8]) -> Result<(), FatError> {
|
pub(crate) async fn write_ota(&mut self, offset: u32, buf: &[u8]) -> Result<(), FatError> {
|
||||||
let _ = check_erase(self.ota_target, offset, offset + 4096);
|
let _ = check_erase(self.ota_target, offset, offset + 4096);
|
||||||
@@ -233,19 +314,17 @@ impl Esp<'_> {
|
|||||||
&mut tx_meta,
|
&mut tx_meta,
|
||||||
&mut tx_buffer,
|
&mut tx_buffer,
|
||||||
);
|
);
|
||||||
socket.bind(123).context("Could not bind UDP socket")?;
|
socket.bind(123).unwrap();
|
||||||
|
|
||||||
let context = NtpContext::new(Timestamp::default());
|
let context = NtpContext::new(Timestamp::default());
|
||||||
|
|
||||||
let ntp_addrs = stack
|
let ntp_addrs = stack
|
||||||
.dns_query(NTP_SERVER, DnsQueryType::A)
|
.dns_query(NTP_SERVER, DnsQueryType::A)
|
||||||
.await
|
.await;
|
||||||
.context("Failed to resolve DNS")?;
|
if ntp_addrs.is_err() {
|
||||||
|
bail!("Failed to resolve DNS");
|
||||||
if ntp_addrs.is_empty() {
|
|
||||||
bail!("No IP addresses found for NTP server");
|
|
||||||
}
|
}
|
||||||
let ntp = ntp_addrs[0];
|
let ntp = ntp_addrs.unwrap()[0];
|
||||||
info!("NTP server: {ntp:?}");
|
info!("NTP server: {ntp:?}");
|
||||||
|
|
||||||
let mut counter = 0;
|
let mut counter = 0;
|
||||||
@@ -337,11 +416,9 @@ impl Esp<'_> {
|
|||||||
Err(_) => "PlantCtrl Emergency Mode".to_string(),
|
Err(_) => "PlantCtrl Emergency Mode".to_string(),
|
||||||
};
|
};
|
||||||
|
|
||||||
let device = self
|
let device = self.interface_ap.take().unwrap();
|
||||||
.interface_ap
|
let gw_ip_addr_str = "192.168.71.1";
|
||||||
.take()
|
let gw_ip_addr = Ipv4Addr::from_str(gw_ip_addr_str).expect("failed to parse gateway ip");
|
||||||
.context("AP interface already taken")?;
|
|
||||||
let gw_ip_addr = Ipv4Addr::new(192, 168, 71, 1);
|
|
||||||
|
|
||||||
let config = embassy_net::Config::ipv4_static(StaticConfigV4 {
|
let config = embassy_net::Config::ipv4_static(StaticConfigV4 {
|
||||||
address: Ipv4Cidr::new(gw_ip_addr, 24),
|
address: Ipv4Cidr::new(gw_ip_addr, 24),
|
||||||
@@ -370,7 +447,7 @@ impl Esp<'_> {
|
|||||||
println!("start net task");
|
println!("start net task");
|
||||||
spawner.spawn(net_task(runner)).ok();
|
spawner.spawn(net_task(runner)).ok();
|
||||||
println!("run dhcp");
|
println!("run dhcp");
|
||||||
spawner.spawn(run_dhcp(*stack, gw_ip_addr)).ok();
|
spawner.spawn(run_dhcp(*stack, gw_ip_addr_str)).ok();
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
if stack.is_link_up() {
|
if stack.is_link_up() {
|
||||||
@@ -381,7 +458,7 @@ impl Esp<'_> {
|
|||||||
while !stack.is_config_up() {
|
while !stack.is_config_up() {
|
||||||
Timer::after(Duration::from_millis(100)).await
|
Timer::after(Duration::from_millis(100)).await
|
||||||
}
|
}
|
||||||
println!("Connect to the AP `${ssid}` and point your browser to http://{gw_ip_addr}/");
|
println!("Connect to the AP `${ssid}` and point your browser to http://{gw_ip_addr_str}/");
|
||||||
stack
|
stack
|
||||||
.config_v4()
|
.config_v4()
|
||||||
.inspect(|c| println!("ipv4 config: {c:?}"));
|
.inspect(|c| println!("ipv4 config: {c:?}"));
|
||||||
@@ -395,17 +472,18 @@ impl Esp<'_> {
|
|||||||
spawner: Spawner,
|
spawner: Spawner,
|
||||||
) -> FatResult<Stack<'static>> {
|
) -> FatResult<Stack<'static>> {
|
||||||
esp_radio::wifi_set_log_verbose();
|
esp_radio::wifi_set_log_verbose();
|
||||||
let ssid = match &network_config.ssid {
|
let ssid = network_config.ssid.clone();
|
||||||
|
match &ssid {
|
||||||
Some(ssid) => {
|
Some(ssid) => {
|
||||||
if ssid.is_empty() {
|
if ssid.is_empty() {
|
||||||
bail!("Wifi ssid was empty")
|
bail!("Wifi ssid was empty")
|
||||||
}
|
}
|
||||||
ssid.to_string()
|
|
||||||
}
|
}
|
||||||
None => {
|
None => {
|
||||||
bail!("Wifi ssid was empty")
|
bail!("Wifi ssid was empty")
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
let ssid = ssid.unwrap().to_string();
|
||||||
info!("attempting to connect wifi {ssid}");
|
info!("attempting to connect wifi {ssid}");
|
||||||
let password = match network_config.password {
|
let password = match network_config.password {
|
||||||
Some(ref password) => password.to_string(),
|
Some(ref password) => password.to_string(),
|
||||||
@@ -413,10 +491,7 @@ impl Esp<'_> {
|
|||||||
};
|
};
|
||||||
let max_wait = network_config.max_wait;
|
let max_wait = network_config.max_wait;
|
||||||
|
|
||||||
let device = self
|
let device = self.interface_sta.take().unwrap();
|
||||||
.interface_sta
|
|
||||||
.take()
|
|
||||||
.context("STA interface already taken")?;
|
|
||||||
let config = embassy_net::Config::dhcpv4(DhcpConfig::default());
|
let config = embassy_net::Config::dhcpv4(DhcpConfig::default());
|
||||||
|
|
||||||
let seed = (self.rng.random() as u64) << 32 | self.rng.random() as u64;
|
let seed = (self.rng.random() as u64) << 32 | self.rng.random() as u64;
|
||||||
@@ -521,9 +596,9 @@ impl Esp<'_> {
|
|||||||
if let Ok(cur) = self.ota.current_ota_state() {
|
if let Ok(cur) = self.ota.current_ota_state() {
|
||||||
if cur == OtaImageState::PendingVerify {
|
if cur == OtaImageState::PendingVerify {
|
||||||
info!("Marking OTA image as valid");
|
info!("Marking OTA image as valid");
|
||||||
if let Err(err) = self.ota.set_current_ota_state(Valid) {
|
self.ota
|
||||||
error!("Could not set image to valid: {:?}", err);
|
.set_current_ota_state(Valid)
|
||||||
}
|
.expect("Could not set image to valid");
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
info!("No OTA image to mark as valid");
|
info!("No OTA image to mark as valid");
|
||||||
@@ -540,46 +615,48 @@ impl Esp<'_> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Load the most recently saved config from flash.
|
|
||||||
pub(crate) async fn load_config(&mut self) -> FatResult<PlantControllerConfig> {
|
pub(crate) async fn load_config(&mut self) -> FatResult<PlantControllerConfig> {
|
||||||
match self.savegame.load_latest()? {
|
let cfg = PathBuf::try_from(CONFIG_FILE)?;
|
||||||
None => bail!("No config stored"),
|
let config_exist = self.fs.lock().await.exists(&cfg);
|
||||||
Some(data) => {
|
if !config_exist {
|
||||||
let config: PlantControllerConfig = serde_json::from_slice(&data)?;
|
bail!("No config file stored")
|
||||||
Ok(config)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
let data = self.fs.lock().await.read::<4096>(&cfg)?;
|
||||||
|
let config: PlantControllerConfig = serde_json::from_slice(&data)?;
|
||||||
|
Ok(config)
|
||||||
}
|
}
|
||||||
|
pub(crate) async fn save_config(&mut self, config: Vec<u8>) -> FatResult<()> {
|
||||||
/// Load a config from a specific save slot.
|
let filesystem = self.fs.lock().await;
|
||||||
pub(crate) async fn load_config_slot(
|
let cfg = PathBuf::try_from(CONFIG_FILE)?;
|
||||||
&mut self,
|
filesystem.write(&cfg, &config)?;
|
||||||
idx: usize,
|
|
||||||
) -> FatResult<String> {
|
|
||||||
match self.savegame.load_slot(idx)? {
|
|
||||||
None => bail!("Slot {idx} is empty or invalid"),
|
|
||||||
Some(data) => {
|
|
||||||
Ok(String::from_utf8_lossy(&*data).to_string())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Persist a JSON config blob to the next wear-leveling slot.
|
|
||||||
pub(crate) async fn save_config(&mut self, mut config: Vec<u8>) -> FatResult<()> {
|
|
||||||
self.savegame.save(config.as_mut_slice())?;
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
pub(crate) async fn list_files(&self) -> FatResult<FileList> {
|
||||||
|
let path = PathBuf::new();
|
||||||
|
|
||||||
/// Delete a specific save slot by erasing it on flash.
|
let fs = self.fs.lock().await;
|
||||||
pub(crate) async fn delete_save_slot(&mut self, idx: usize) -> FatResult<()> {
|
let free_size = fs.available_space()?;
|
||||||
self.savegame.delete_slot(idx)
|
let total_size = fs.total_space();
|
||||||
}
|
|
||||||
|
|
||||||
/// Return metadata about all valid save slots.
|
let mut result = FileList {
|
||||||
pub(crate) async fn list_saves(
|
total: total_size,
|
||||||
&mut self,
|
used: total_size - free_size,
|
||||||
) -> FatResult<alloc::vec::Vec<crate::hal::savegame_manager::SaveInfo>> {
|
files: Vec::new(),
|
||||||
self.savegame.list_saves()
|
};
|
||||||
|
|
||||||
|
fs.read_dir_and_then(&path, |dir| {
|
||||||
|
for entry in dir {
|
||||||
|
let e = entry?;
|
||||||
|
if e.file_type() == FileType::File {
|
||||||
|
result.files.push(FileInfo {
|
||||||
|
filename: e.path().to_string(),
|
||||||
|
size: e.metadata().len(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
})?;
|
||||||
|
Ok(result)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) async fn init_rtc_deepsleep_memory(
|
pub(crate) async fn init_rtc_deepsleep_memory(
|
||||||
@@ -672,11 +749,11 @@ impl Esp<'_> {
|
|||||||
|
|
||||||
let mut builder: McutieBuilder<'_, String, PublishDisplay<String, &str>, 0> =
|
let mut builder: McutieBuilder<'_, String, PublishDisplay<String, &str>, 0> =
|
||||||
McutieBuilder::new(stack, "plant ctrl", mqtt_url);
|
McutieBuilder::new(stack, "plant ctrl", mqtt_url);
|
||||||
if let (Some(mqtt_user), Some(mqtt_password)) = (
|
if network_config.mqtt_user.is_some() && network_config.mqtt_password.is_some() {
|
||||||
network_config.mqtt_user.as_ref(),
|
builder = builder.with_authentication(
|
||||||
network_config.mqtt_password.as_ref(),
|
network_config.mqtt_user.as_ref().unwrap().as_str(),
|
||||||
) {
|
network_config.mqtt_password.as_ref().unwrap().as_str(),
|
||||||
builder = builder.with_authentication(mqtt_user, mqtt_password);
|
);
|
||||||
info!("With authentification");
|
info!("With authentification");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -728,10 +805,11 @@ impl Esp<'_> {
|
|||||||
Timer::after(Duration::from_millis(100)).await;
|
Timer::after(Duration::from_millis(100)).await;
|
||||||
}
|
}
|
||||||
|
|
||||||
let _ = Topic::General(round_trip_topic.clone())
|
Topic::General(round_trip_topic.clone())
|
||||||
.with_display("online_text")
|
.with_display("online_text")
|
||||||
.publish()
|
.publish()
|
||||||
.await;
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
let timeout = {
|
let timeout = {
|
||||||
let guard = TIME_ACCESS.get().await.lock().await;
|
let guard = TIME_ACCESS.get().await.lock().await;
|
||||||
@@ -883,8 +961,8 @@ async fn net_task(mut runner: Runner<'static, WifiDevice<'static>>) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[embassy_executor::task]
|
#[embassy_executor::task]
|
||||||
async fn run_dhcp(stack: Stack<'static>, ip: Ipv4Addr) {
|
async fn run_dhcp(stack: Stack<'static>, gw_ip_addr: &'static str) {
|
||||||
use core::net::SocketAddrV4;
|
use core::net::{Ipv4Addr, SocketAddrV4};
|
||||||
|
|
||||||
use edge_dhcp::{
|
use edge_dhcp::{
|
||||||
io::{self, DEFAULT_SERVER_PORT},
|
io::{self, DEFAULT_SERVER_PORT},
|
||||||
@@ -893,25 +971,21 @@ async fn run_dhcp(stack: Stack<'static>, ip: Ipv4Addr) {
|
|||||||
use edge_nal::UdpBind;
|
use edge_nal::UdpBind;
|
||||||
use edge_nal_embassy::{Udp, UdpBuffers};
|
use edge_nal_embassy::{Udp, UdpBuffers};
|
||||||
|
|
||||||
|
let ip = Ipv4Addr::from_str(gw_ip_addr).expect("dhcp task failed to parse gw ip");
|
||||||
|
|
||||||
let mut buf = [0u8; 1500];
|
let mut buf = [0u8; 1500];
|
||||||
|
|
||||||
let mut gw_buf = [Ipv4Addr::UNSPECIFIED];
|
let mut gw_buf = [Ipv4Addr::UNSPECIFIED];
|
||||||
|
|
||||||
let buffers = UdpBuffers::<3, 1024, 1024, 10>::new();
|
let buffers = UdpBuffers::<3, 1024, 1024, 10>::new();
|
||||||
let unbound_socket = Udp::new(stack, &buffers);
|
let unbound_socket = Udp::new(stack, &buffers);
|
||||||
let mut bound_socket = match unbound_socket
|
let mut bound_socket = unbound_socket
|
||||||
.bind(SocketAddr::V4(SocketAddrV4::new(
|
.bind(SocketAddr::V4(SocketAddrV4::new(
|
||||||
Ipv4Addr::UNSPECIFIED,
|
Ipv4Addr::UNSPECIFIED,
|
||||||
DEFAULT_SERVER_PORT,
|
DEFAULT_SERVER_PORT,
|
||||||
)))
|
)))
|
||||||
.await
|
.await
|
||||||
{
|
.unwrap();
|
||||||
Ok(s) => s,
|
|
||||||
Err(e) => {
|
|
||||||
error!("dhcp task failed to bind socket: {:?}", e);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
_ = io::server::run(
|
_ = io::server::run(
|
||||||
|
|||||||
88
Software/MainBoard/rust/src/hal/little_fs2storage_adapter.rs
Normal file
88
Software/MainBoard/rust/src/hal/little_fs2storage_adapter.rs
Normal file
@@ -0,0 +1,88 @@
|
|||||||
|
use crate::hal::shared_flash::MutexFlashStorage;
|
||||||
|
use embedded_storage::nor_flash::{check_erase, NorFlash, ReadNorFlash};
|
||||||
|
use esp_bootloader_esp_idf::partitions::FlashRegion;
|
||||||
|
use littlefs2::consts::U4096 as lfsCache;
|
||||||
|
use littlefs2::consts::U512 as lfsLookahead;
|
||||||
|
use littlefs2::driver::Storage as lfs2Storage;
|
||||||
|
use littlefs2::io::Error as lfs2Error;
|
||||||
|
use littlefs2::io::Result as lfs2Result;
|
||||||
|
use log::error;
|
||||||
|
|
||||||
|
pub struct LittleFs2Filesystem {
|
||||||
|
pub(crate) storage: &'static mut FlashRegion<'static, MutexFlashStorage>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl lfs2Storage for LittleFs2Filesystem {
|
||||||
|
const READ_SIZE: usize = 4096;
|
||||||
|
const WRITE_SIZE: usize = 4096;
|
||||||
|
const BLOCK_SIZE: usize = 4096; //usually optimal for flash access
|
||||||
|
const BLOCK_COUNT: usize = 8 * 1000 * 1000 / 4096; //8Mb in 4k blocks + a little space for stupid calculation errors
|
||||||
|
const BLOCK_CYCLES: isize = 100;
|
||||||
|
type CACHE_SIZE = lfsCache;
|
||||||
|
type LOOKAHEAD_SIZE = lfsLookahead;
|
||||||
|
|
||||||
|
fn read(&mut self, off: usize, buf: &mut [u8]) -> lfs2Result<usize> {
|
||||||
|
let read_size: usize = Self::READ_SIZE;
|
||||||
|
if off % read_size != 0 {
|
||||||
|
error!("Littlefs2Filesystem read error: offset not aligned to read size offset: {off} read_size: {read_size}");
|
||||||
|
return Err(lfs2Error::IO);
|
||||||
|
}
|
||||||
|
if buf.len() % read_size != 0 {
|
||||||
|
error!("Littlefs2Filesystem read error: length not aligned to read size length: {} read_size: {}", buf.len(), read_size);
|
||||||
|
return Err(lfs2Error::IO);
|
||||||
|
}
|
||||||
|
match self.storage.read(off as u32, buf) {
|
||||||
|
Ok(..) => Ok(buf.len()),
|
||||||
|
Err(err) => {
|
||||||
|
error!("Littlefs2Filesystem read error: {err:?}");
|
||||||
|
Err(lfs2Error::IO)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write(&mut self, off: usize, data: &[u8]) -> lfs2Result<usize> {
|
||||||
|
let write_size: usize = Self::WRITE_SIZE;
|
||||||
|
if off % write_size != 0 {
|
||||||
|
error!("Littlefs2Filesystem write error: offset not aligned to write size offset: {off} write_size: {write_size}");
|
||||||
|
return Err(lfs2Error::IO);
|
||||||
|
}
|
||||||
|
if data.len() % write_size != 0 {
|
||||||
|
error!("Littlefs2Filesystem write error: length not aligned to write size length: {} write_size: {}", data.len(), write_size);
|
||||||
|
return Err(lfs2Error::IO);
|
||||||
|
}
|
||||||
|
match self.storage.write(off as u32, data) {
|
||||||
|
Ok(..) => Ok(data.len()),
|
||||||
|
Err(err) => {
|
||||||
|
error!("Littlefs2Filesystem write error: {err:?}");
|
||||||
|
Err(lfs2Error::IO)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn erase(&mut self, off: usize, len: usize) -> lfs2Result<usize> {
|
||||||
|
let block_size: usize = Self::BLOCK_SIZE;
|
||||||
|
if off % block_size != 0 {
|
||||||
|
error!("Littlefs2Filesystem erase error: offset not aligned to block size offset: {off} block_size: {block_size}");
|
||||||
|
return Err(lfs2Error::IO);
|
||||||
|
}
|
||||||
|
if len % block_size != 0 {
|
||||||
|
error!("Littlefs2Filesystem erase error: length not aligned to block size length: {len} block_size: {block_size}");
|
||||||
|
return Err(lfs2Error::IO);
|
||||||
|
}
|
||||||
|
|
||||||
|
match check_erase(self.storage, off as u32, (off + len) as u32) {
|
||||||
|
Ok(_) => {}
|
||||||
|
Err(err) => {
|
||||||
|
error!("Littlefs2Filesystem check erase error: {err:?}");
|
||||||
|
return Err(lfs2Error::IO);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
match self.storage.erase(off as u32, (off + len) as u32) {
|
||||||
|
Ok(..) => Ok(len),
|
||||||
|
Err(err) => {
|
||||||
|
error!("Littlefs2Filesystem erase error: {err:?}");
|
||||||
|
Err(lfs2Error::IO)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,14 +3,14 @@ use lib_bms_protocol::BmsReadable;
|
|||||||
pub(crate) mod battery;
|
pub(crate) mod battery;
|
||||||
// mod can_api; // replaced by external canapi crate
|
// mod can_api; // replaced by external canapi crate
|
||||||
pub mod esp;
|
pub mod esp;
|
||||||
pub(crate) mod savegame_manager;
|
mod little_fs2storage_adapter;
|
||||||
pub(crate) mod rtc;
|
pub(crate) mod rtc;
|
||||||
mod shared_flash;
|
mod shared_flash;
|
||||||
mod v4_hal;
|
mod v4_hal;
|
||||||
mod water;
|
mod water;
|
||||||
|
|
||||||
use crate::alloc::string::ToString;
|
use crate::alloc::string::ToString;
|
||||||
use crate::hal::rtc::{BackupHeader, DS3231Module, RTCModuleInteraction};
|
use crate::hal::rtc::{DS3231Module, RTCModuleInteraction};
|
||||||
use esp_hal::peripherals::Peripherals;
|
use esp_hal::peripherals::Peripherals;
|
||||||
use esp_hal::peripherals::ADC1;
|
use esp_hal::peripherals::ADC1;
|
||||||
use esp_hal::peripherals::GPIO0;
|
use esp_hal::peripherals::GPIO0;
|
||||||
@@ -75,7 +75,7 @@ use measurements::{Current, Voltage};
|
|||||||
|
|
||||||
use crate::fat_error::{ContextExt, FatError, FatResult};
|
use crate::fat_error::{ContextExt, FatError, FatResult};
|
||||||
use crate::hal::battery::WCHI2CSlave;
|
use crate::hal::battery::WCHI2CSlave;
|
||||||
use crate::hal::savegame_manager::SavegameManager;
|
use crate::hal::little_fs2storage_adapter::LittleFs2Filesystem;
|
||||||
use crate::hal::water::TankSensor;
|
use crate::hal::water::TankSensor;
|
||||||
use crate::log::LOG_ACCESS;
|
use crate::log::LOG_ACCESS;
|
||||||
use embassy_sync::mutex::Mutex;
|
use embassy_sync::mutex::Mutex;
|
||||||
@@ -99,7 +99,9 @@ use esp_hal::uart::Uart;
|
|||||||
use esp_hal::Blocking;
|
use esp_hal::Blocking;
|
||||||
use esp_radio::{init, Controller};
|
use esp_radio::{init, Controller};
|
||||||
use esp_storage::FlashStorage;
|
use esp_storage::FlashStorage;
|
||||||
use log::{info, warn};
|
use littlefs2::fs::{Allocation, Filesystem as lfs2Filesystem};
|
||||||
|
use littlefs2::object_safe::DynStorage;
|
||||||
|
use log::{error, info, warn};
|
||||||
use portable_atomic::AtomicBool;
|
use portable_atomic::AtomicBool;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use shared_flash::MutexFlashStorage;
|
use shared_flash::MutexFlashStorage;
|
||||||
@@ -162,10 +164,6 @@ pub trait BoardInteraction<'a> {
|
|||||||
async fn get_mptt_current(&mut self) -> FatResult<Current>;
|
async fn get_mptt_current(&mut self) -> FatResult<Current>;
|
||||||
async fn can_power(&mut self, state: bool) -> FatResult<()>;
|
async fn can_power(&mut self, state: bool) -> FatResult<()>;
|
||||||
|
|
||||||
async fn backup_config(&mut self, config: &PlantControllerConfig) -> FatResult<()>;
|
|
||||||
async fn read_backup(&mut self) -> FatResult<PlantControllerConfig>;
|
|
||||||
async fn backup_info(&mut self) -> FatResult<BackupHeader>;
|
|
||||||
|
|
||||||
// Return JSON string with autodetected sensors per plant. Default: not supported.
|
// Return JSON string with autodetected sensors per plant. Default: not supported.
|
||||||
async fn detect_sensors(&mut self, _request: Detection) -> FatResult<Detection> {
|
async fn detect_sensors(&mut self, _request: Detection) -> FatResult<Detection> {
|
||||||
bail!("Autodetection is only available on v4 HAL with CAN bus");
|
bail!("Autodetection is only available on v4 HAL with CAN bus");
|
||||||
@@ -268,17 +266,12 @@ impl PlantHal {
|
|||||||
let rng = Rng::new();
|
let rng = Rng::new();
|
||||||
let esp_wifi_ctrl = &*mk_static!(
|
let esp_wifi_ctrl = &*mk_static!(
|
||||||
Controller<'static>,
|
Controller<'static>,
|
||||||
init().map_err(|e| FatError::String {
|
init().expect("Could not init wifi controller")
|
||||||
error: format!("Could not init wifi controller: {:?}", e)
|
|
||||||
})?
|
|
||||||
);
|
);
|
||||||
|
|
||||||
let (controller, interfaces) =
|
let (controller, interfaces) =
|
||||||
esp_radio::wifi::new(esp_wifi_ctrl, peripherals.WIFI, Default::default()).map_err(
|
esp_radio::wifi::new(esp_wifi_ctrl, peripherals.WIFI, Default::default())
|
||||||
|e| FatError::String {
|
.expect("Could not init wifi");
|
||||||
error: format!("Could not init wifi: {:?}", e),
|
|
||||||
},
|
|
||||||
)?;
|
|
||||||
|
|
||||||
let pcnt_module = Pcnt::new(peripherals.PCNT);
|
let pcnt_module = Pcnt::new(peripherals.PCNT);
|
||||||
|
|
||||||
@@ -332,7 +325,7 @@ impl PlantHal {
|
|||||||
pt.find_partition(esp_bootloader_esp_idf::partitions::PartitionType::Data(
|
pt.find_partition(esp_bootloader_esp_idf::partitions::PartitionType::Data(
|
||||||
DataPartitionSubType::Ota,
|
DataPartitionSubType::Ota,
|
||||||
))?
|
))?
|
||||||
.context("No OTA data partition found")?
|
.expect("No OTA data partition found")
|
||||||
);
|
);
|
||||||
|
|
||||||
let ota_data = mk_static!(
|
let ota_data = mk_static!(
|
||||||
@@ -377,16 +370,32 @@ impl PlantHal {
|
|||||||
.find_partition(esp_bootloader_esp_idf::partitions::PartitionType::Data(
|
.find_partition(esp_bootloader_esp_idf::partitions::PartitionType::Data(
|
||||||
DataPartitionSubType::LittleFs,
|
DataPartitionSubType::LittleFs,
|
||||||
))?
|
))?
|
||||||
.context("Storage data partition not found")?;
|
.expect("Data partition with littlefs not found");
|
||||||
let data_partition = mk_static!(PartitionEntry, data_partition);
|
let data_partition = mk_static!(PartitionEntry, data_partition);
|
||||||
|
|
||||||
let data = mk_static!(
|
let data = mk_static!(
|
||||||
FlashRegion<'static, MutexFlashStorage>,
|
FlashRegion<MutexFlashStorage>,
|
||||||
data_partition.as_embedded_storage(flash_storage_3)
|
data_partition.as_embedded_storage(flash_storage_3)
|
||||||
);
|
);
|
||||||
|
let lfs2filesystem = mk_static!(LittleFs2Filesystem, LittleFs2Filesystem { storage: data });
|
||||||
|
let alloc = mk_static!(Allocation<LittleFs2Filesystem>, lfs2Filesystem::allocate());
|
||||||
|
if lfs2filesystem.is_mountable() {
|
||||||
|
info!("Littlefs2 filesystem is mountable");
|
||||||
|
} else {
|
||||||
|
match lfs2filesystem.format() {
|
||||||
|
Ok(..) => {
|
||||||
|
info!("Littlefs2 filesystem is formatted");
|
||||||
|
}
|
||||||
|
Err(err) => {
|
||||||
|
error!("Littlefs2 filesystem could not be formatted: {err:?}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let savegame = SavegameManager::new(data);
|
#[allow(clippy::arc_with_non_send_sync)]
|
||||||
info!("Savegame storage initialized ({} slots × {} KB)", savegame_manager::SAVEGAME_SLOT_COUNT, savegame_manager::SAVEGAME_SLOT_SIZE / 1024);
|
let fs = Arc::new(Mutex::new(
|
||||||
|
lfs2Filesystem::mount(alloc, lfs2filesystem).expect("Could not mount lfs2 filesystem"),
|
||||||
|
));
|
||||||
|
|
||||||
let uart0 =
|
let uart0 =
|
||||||
Uart::new(peripherals.UART0, UartConfig::default()).map_err(|_| FatError::String {
|
Uart::new(peripherals.UART0, UartConfig::default()).map_err(|_| FatError::String {
|
||||||
@@ -396,7 +405,7 @@ impl PlantHal {
|
|||||||
let ap = interfaces.ap;
|
let ap = interfaces.ap;
|
||||||
let sta = interfaces.sta;
|
let sta = interfaces.sta;
|
||||||
let mut esp = Esp {
|
let mut esp = Esp {
|
||||||
savegame,
|
fs,
|
||||||
rng,
|
rng,
|
||||||
controller: Arc::new(Mutex::new(controller)),
|
controller: Arc::new(Mutex::new(controller)),
|
||||||
interface_sta: Some(sta),
|
interface_sta: Some(sta),
|
||||||
@@ -479,9 +488,7 @@ impl PlantHal {
|
|||||||
RefCell<I2c<Blocking>>,
|
RefCell<I2c<Blocking>>,
|
||||||
> = CriticalSectionMutex::new(RefCell::new(i2c));
|
> = CriticalSectionMutex::new(RefCell::new(i2c));
|
||||||
|
|
||||||
I2C_DRIVER.init(i2c_bus).map_err(|_| FatError::String {
|
I2C_DRIVER.init(i2c_bus).expect("Could not init i2c driver");
|
||||||
error: "Could not init i2c driver".to_string(),
|
|
||||||
})?;
|
|
||||||
|
|
||||||
let i2c_bus = I2C_DRIVER.get().await;
|
let i2c_bus = I2C_DRIVER.get().await;
|
||||||
let rtc_device = I2cDevice::new(i2c_bus);
|
let rtc_device = I2cDevice::new(i2c_bus);
|
||||||
@@ -643,7 +650,7 @@ pub fn next_partition(current: AppPartitionSubType) -> FatResult<AppPartitionSub
|
|||||||
|
|
||||||
pub async fn esp_time() -> DateTime<Utc> {
|
pub async fn esp_time() -> DateTime<Utc> {
|
||||||
let guard = TIME_ACCESS.get().await.lock().await;
|
let guard = TIME_ACCESS.get().await.lock().await;
|
||||||
DateTime::from_timestamp_micros(guard.current_time_us() as i64).unwrap_or(DateTime::UNIX_EPOCH)
|
DateTime::from_timestamp_micros(guard.current_time_us() as i64).unwrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn esp_set_time(time: DateTime<FixedOffset>) -> FatResult<()> {
|
pub async fn esp_set_time(time: DateTime<FixedOffset>) -> FatResult<()> {
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
use crate::hal::Box;
|
|
||||||
use crate::fat_error::FatResult;
|
use crate::fat_error::FatResult;
|
||||||
|
use crate::hal::Box;
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use bincode::{Decode, Encode};
|
use bincode::config::Configuration;
|
||||||
|
use bincode::{config, Decode, Encode};
|
||||||
use chrono::{DateTime, Utc};
|
use chrono::{DateTime, Utc};
|
||||||
use ds323x::ic::DS3231;
|
use ds323x::ic::DS3231;
|
||||||
use ds323x::interface::I2cInterface;
|
use ds323x::interface::I2cInterface;
|
||||||
@@ -18,21 +19,24 @@ use esp_hal::Blocking;
|
|||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
pub const X25: crc::Crc<u16> = crc::Crc::<u16>::new(&crc::CRC_16_IBM_SDLC);
|
pub const X25: crc::Crc<u16> = crc::Crc::<u16>::new(&crc::CRC_16_IBM_SDLC);
|
||||||
pub const EEPROM_PAGE: usize = 32;
|
const CONFIG: Configuration = config::standard();
|
||||||
//
|
//
|
||||||
#[async_trait(?Send)]
|
#[async_trait(?Send)]
|
||||||
pub trait RTCModuleInteraction {
|
pub trait RTCModuleInteraction {
|
||||||
|
async fn get_backup_info(&mut self) -> FatResult<BackupHeader>;
|
||||||
|
async fn get_backup_config(&mut self, chunk: usize) -> FatResult<([u8; 32], usize, u16)>;
|
||||||
|
async fn backup_config(&mut self, offset: usize, bytes: &[u8]) -> FatResult<()>;
|
||||||
|
async fn backup_config_finalize(&mut self, crc: u16, length: usize) -> FatResult<()>;
|
||||||
async fn get_rtc_time(&mut self) -> FatResult<DateTime<Utc>>;
|
async fn get_rtc_time(&mut self) -> FatResult<DateTime<Utc>>;
|
||||||
async fn set_rtc_time(&mut self, time: &DateTime<Utc>) -> FatResult<()>;
|
async fn set_rtc_time(&mut self, time: &DateTime<Utc>) -> FatResult<()>;
|
||||||
|
|
||||||
fn write(&mut self, offset: u32, data: &[u8]) -> FatResult<()>;
|
|
||||||
fn read(&mut self, offset:u32, data: &mut [u8]) -> FatResult<()>;
|
|
||||||
}
|
}
|
||||||
|
//
|
||||||
|
const BACKUP_HEADER_MAX_SIZE: usize = 64;
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, PartialEq, Debug, Default, Encode, Decode)]
|
#[derive(Serialize, Deserialize, PartialEq, Debug, Default, Encode, Decode)]
|
||||||
pub struct BackupHeader {
|
pub struct BackupHeader {
|
||||||
pub timestamp: i64,
|
pub timestamp: i64,
|
||||||
pub(crate) crc16: u16,
|
crc16: u16,
|
||||||
pub size: u16,
|
pub size: u16,
|
||||||
}
|
}
|
||||||
//
|
//
|
||||||
@@ -42,7 +46,7 @@ pub struct DS3231Module {
|
|||||||
DS3231,
|
DS3231,
|
||||||
>,
|
>,
|
||||||
|
|
||||||
pub storage: eeprom24x::Storage<
|
pub(crate) storage: eeprom24x::Storage<
|
||||||
I2cDevice<'static, CriticalSectionRawMutex, I2c<'static, Blocking>>,
|
I2cDevice<'static, CriticalSectionRawMutex, I2c<'static, Blocking>>,
|
||||||
B32,
|
B32,
|
||||||
TwoBytes,
|
TwoBytes,
|
||||||
@@ -53,6 +57,67 @@ pub struct DS3231Module {
|
|||||||
|
|
||||||
#[async_trait(?Send)]
|
#[async_trait(?Send)]
|
||||||
impl RTCModuleInteraction for DS3231Module {
|
impl RTCModuleInteraction for DS3231Module {
|
||||||
|
async fn get_backup_info(&mut self) -> FatResult<BackupHeader> {
|
||||||
|
let mut header_page_buffer = [0_u8; BACKUP_HEADER_MAX_SIZE];
|
||||||
|
|
||||||
|
self.storage.read(0, &mut header_page_buffer)?;
|
||||||
|
|
||||||
|
let (header, len): (BackupHeader, usize) =
|
||||||
|
bincode::decode_from_slice(&header_page_buffer[..], CONFIG)?;
|
||||||
|
|
||||||
|
log::info!("Raw header is {header_page_buffer:?} with size {len}");
|
||||||
|
Ok(header)
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn get_backup_config(&mut self, chunk: usize) -> FatResult<([u8; 32], usize, u16)> {
|
||||||
|
let mut header_page_buffer = [0_u8; BACKUP_HEADER_MAX_SIZE];
|
||||||
|
|
||||||
|
self.storage.read(0, &mut header_page_buffer)?;
|
||||||
|
let (header, _header_size): (BackupHeader, usize) =
|
||||||
|
bincode::decode_from_slice(&header_page_buffer[..], CONFIG)?;
|
||||||
|
|
||||||
|
let mut buf = [0_u8; 32];
|
||||||
|
let offset = chunk * buf.len() + BACKUP_HEADER_MAX_SIZE;
|
||||||
|
|
||||||
|
let end: usize = header.size as usize + BACKUP_HEADER_MAX_SIZE;
|
||||||
|
let current_end = offset + buf.len();
|
||||||
|
let chunk_size = if current_end > end {
|
||||||
|
end - offset
|
||||||
|
} else {
|
||||||
|
buf.len()
|
||||||
|
};
|
||||||
|
if chunk_size == 0 {
|
||||||
|
Ok((buf, 0, header.crc16))
|
||||||
|
} else {
|
||||||
|
self.storage.read(offset as u32, &mut buf)?;
|
||||||
|
//&buf[..chunk_size];
|
||||||
|
Ok((buf, chunk_size, header.crc16))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
async fn backup_config(&mut self, offset: usize, bytes: &[u8]) -> FatResult<()> {
|
||||||
|
//skip header and write after
|
||||||
|
self.storage
|
||||||
|
.write((BACKUP_HEADER_MAX_SIZE + offset) as u32, bytes)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn backup_config_finalize(&mut self, crc: u16, length: usize) -> FatResult<()> {
|
||||||
|
let mut header_page_buffer = [0_u8; BACKUP_HEADER_MAX_SIZE];
|
||||||
|
|
||||||
|
let time = self.get_rtc_time().await?.timestamp_millis();
|
||||||
|
let header = BackupHeader {
|
||||||
|
crc16: crc,
|
||||||
|
timestamp: time,
|
||||||
|
size: length as u16,
|
||||||
|
};
|
||||||
|
let config = config::standard();
|
||||||
|
let encoded = bincode::encode_into_slice(&header, &mut header_page_buffer, config)?;
|
||||||
|
log::info!("Raw header is {header_page_buffer:?} with size {encoded}");
|
||||||
|
self.storage.write(0, &header_page_buffer)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
async fn get_rtc_time(&mut self) -> FatResult<DateTime<Utc>> {
|
async fn get_rtc_time(&mut self) -> FatResult<DateTime<Utc>> {
|
||||||
Ok(self.rtc.datetime()?.and_utc())
|
Ok(self.rtc.datetime()?.and_utc())
|
||||||
}
|
}
|
||||||
@@ -61,14 +126,4 @@ impl RTCModuleInteraction for DS3231Module {
|
|||||||
let naive_time = time.naive_utc();
|
let naive_time = time.naive_utc();
|
||||||
Ok(self.rtc.set_datetime(&naive_time)?)
|
Ok(self.rtc.set_datetime(&naive_time)?)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn write(&mut self, offset: u32, data: &[u8]) -> FatResult<()> {
|
|
||||||
self.storage.write(offset, data)?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn read(&mut self, offset:u32, data: &mut [u8]) -> FatResult<()> {
|
|
||||||
self.storage.read(offset, data)?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,150 +0,0 @@
|
|||||||
use alloc::vec::Vec;
|
|
||||||
use embedded_savegame::storage::{Flash, Storage};
|
|
||||||
use embedded_storage::nor_flash::{NorFlash, ReadNorFlash};
|
|
||||||
use esp_bootloader_esp_idf::partitions::{FlashRegion, Error as PartitionError};
|
|
||||||
use serde::Serialize;
|
|
||||||
|
|
||||||
use crate::fat_error::{FatError, FatResult};
|
|
||||||
use crate::hal::shared_flash::MutexFlashStorage;
|
|
||||||
|
|
||||||
/// Size of each save slot in bytes (16 KB).
|
|
||||||
pub const SAVEGAME_SLOT_SIZE: usize = 16384;
|
|
||||||
|
|
||||||
/// Number of slots in the 8 MB storage partition.
|
|
||||||
pub const SAVEGAME_SLOT_COUNT: usize = 8 * 1024 * 1024 / SAVEGAME_SLOT_SIZE; // 512
|
|
||||||
|
|
||||||
/// Metadata about a single existing save slot, returned by [`SavegameManager::list_saves`].
|
|
||||||
#[derive(Serialize, Debug, Clone)]
|
|
||||||
pub struct SaveInfo {
|
|
||||||
pub idx: usize,
|
|
||||||
pub len: u32,
|
|
||||||
}
|
|
||||||
|
|
||||||
// ── Flash adapter ──────────────────────────────────────────────────────────────
|
|
||||||
|
|
||||||
/// Newtype wrapper around a [`PartitionError`] so we can implement the
|
|
||||||
/// [`core::fmt::Debug`] bound required by [`embedded_savegame::storage::Flash`].
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct SavegameFlashError(#[allow(dead_code)] PartitionError);
|
|
||||||
|
|
||||||
/// Adapts a `&mut FlashRegion<'static, MutexFlashStorage>` to the
|
|
||||||
/// [`embedded_savegame::storage::Flash`] trait.
|
|
||||||
///
|
|
||||||
/// `erase(addr)` erases exactly one slot (`SAVEGAME_SLOT_SIZE` bytes) starting
|
|
||||||
/// at `addr`, which is what embedded-savegame expects for NOR flash.
|
|
||||||
pub struct SavegameFlashAdapter<'a> {
|
|
||||||
region: &'a mut FlashRegion<'static, MutexFlashStorage>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Flash for SavegameFlashAdapter<'_> {
|
|
||||||
type Error = SavegameFlashError;
|
|
||||||
|
|
||||||
fn read(&mut self, addr: u32, buf: &mut [u8]) -> Result<(), Self::Error> {
|
|
||||||
ReadNorFlash::read(self.region, addr, buf).map_err(SavegameFlashError)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn write(&mut self, addr: u32, data: &mut [u8]) -> Result<(), Self::Error> {
|
|
||||||
NorFlash::write(self.region, addr, data).map_err(SavegameFlashError)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Erase one full slot at `addr`.
|
|
||||||
/// embedded-savegame calls this before writing to a slot, so we erase
|
|
||||||
/// the entire `SAVEGAME_SLOT_SIZE` bytes so subsequent writes land on
|
|
||||||
/// pre-erased (0xFF) pages.
|
|
||||||
fn erase(&mut self, addr: u32) -> Result<(), Self::Error> {
|
|
||||||
let end = addr + SAVEGAME_SLOT_SIZE as u32;
|
|
||||||
NorFlash::erase(self.region, addr, end).map_err(SavegameFlashError)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<SavegameFlashError> for FatError {
|
|
||||||
fn from(e: SavegameFlashError) -> Self {
|
|
||||||
FatError::String {
|
|
||||||
error: alloc::format!("Savegame flash error: {:?}", e),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ── SavegameManager ────────────────────────────────────────────────────────────
|
|
||||||
|
|
||||||
/// High-level save-game manager that stores JSON config blobs on the storage
|
|
||||||
/// partition using [`embedded_savegame`] for wear leveling and power-fail safety.
|
|
||||||
pub struct SavegameManager {
|
|
||||||
region: &'static mut FlashRegion<'static, MutexFlashStorage>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl SavegameManager {
|
|
||||||
pub fn new(region: &'static mut FlashRegion<'static, MutexFlashStorage>) -> Self {
|
|
||||||
Self { region }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Build a short-lived [`Storage`] that borrows our flash region.
|
|
||||||
fn storage(
|
|
||||||
&mut self,
|
|
||||||
) -> Storage<SavegameFlashAdapter<'_>, SAVEGAME_SLOT_SIZE, SAVEGAME_SLOT_COUNT> {
|
|
||||||
Storage::new(SavegameFlashAdapter {
|
|
||||||
region: &mut *self.region,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Persist `data` (JSON bytes) to the next available slot.
|
|
||||||
///
|
|
||||||
/// `scan()` advances the internal wear-leveling pointer to the latest valid
|
|
||||||
/// slot before `append()` writes to the next free one.
|
|
||||||
pub fn save(&mut self, data: &mut [u8]) -> FatResult<()> {
|
|
||||||
let mut st = self.storage();
|
|
||||||
st.scan()?;
|
|
||||||
st.append(data)?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Load the most recently saved data. Returns `None` if no valid save exists.
|
|
||||||
pub fn load_latest(&mut self) -> FatResult<Option<Vec<u8>>> {
|
|
||||||
let mut st = self.storage();
|
|
||||||
let slot = st.scan()?;
|
|
||||||
match slot {
|
|
||||||
None => Ok(None),
|
|
||||||
Some(slot) => {
|
|
||||||
let mut buf = alloc::vec![0u8; SAVEGAME_SLOT_SIZE];
|
|
||||||
match st.read(slot.idx, &mut buf)? {
|
|
||||||
None => Ok(None),
|
|
||||||
Some(data) => Ok(Some(data.to_vec())),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Load a specific save by slot index. Returns `None` if the slot is
|
|
||||||
/// empty or contains an invalid checksum.
|
|
||||||
pub fn load_slot(&mut self, idx: usize) -> FatResult<Option<Vec<u8>>> {
|
|
||||||
let mut st = self.storage();
|
|
||||||
let mut buf = alloc::vec![0u8; SAVEGAME_SLOT_SIZE];
|
|
||||||
match st.read(idx, &mut buf)? {
|
|
||||||
None => Ok(None),
|
|
||||||
Some(data) => Ok(Some(data.to_vec())),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Erase a specific slot by index, effectively deleting it.
|
|
||||||
pub fn delete_slot(&mut self, idx: usize) -> FatResult<()> {
|
|
||||||
let mut st = self.storage();
|
|
||||||
st.erase(idx).map_err(Into::into)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Iterate all slots and return metadata for every slot that contains a
|
|
||||||
/// valid save, using the Storage read API to avoid assuming internal slot structure.
|
|
||||||
pub fn list_saves(&mut self) -> FatResult<Vec<SaveInfo>> {
|
|
||||||
let mut saves = Vec::new();
|
|
||||||
let mut st = self.storage();
|
|
||||||
let mut buf = alloc::vec![0u8; SAVEGAME_SLOT_SIZE];
|
|
||||||
for idx in 0..SAVEGAME_SLOT_COUNT {
|
|
||||||
if let Some(data) = st.read(idx, &mut buf)? {
|
|
||||||
saves.push(SaveInfo {
|
|
||||||
idx,
|
|
||||||
len: data.len() as u32,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(saves)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -3,7 +3,7 @@ use crate::config::PlantControllerConfig;
|
|||||||
use crate::fat_error::{ContextExt, FatError, FatResult};
|
use crate::fat_error::{ContextExt, FatError, FatResult};
|
||||||
use crate::hal::battery::BatteryInteraction;
|
use crate::hal::battery::BatteryInteraction;
|
||||||
use crate::hal::esp::{hold_disable, hold_enable, Esp};
|
use crate::hal::esp::{hold_disable, hold_enable, Esp};
|
||||||
use crate::hal::rtc::{BackupHeader, RTCModuleInteraction, EEPROM_PAGE, X25};
|
use crate::hal::rtc::RTCModuleInteraction;
|
||||||
use crate::hal::water::TankSensor;
|
use crate::hal::water::TankSensor;
|
||||||
use crate::hal::{
|
use crate::hal::{
|
||||||
BoardInteraction, Detection, FreePeripherals, Moistures, Sensor, I2C_DRIVER, PLANT_COUNT,
|
BoardInteraction, Detection, FreePeripherals, Moistures, Sensor, I2C_DRIVER, PLANT_COUNT,
|
||||||
@@ -13,10 +13,8 @@ use crate::log::{LogMessage, LOG_ACCESS};
|
|||||||
use alloc::boxed::Box;
|
use alloc::boxed::Box;
|
||||||
use alloc::string::ToString;
|
use alloc::string::ToString;
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use bincode::config;
|
|
||||||
use canapi::id::{classify, plant_id, MessageKind, IDENTIFY_CMD_OFFSET};
|
use canapi::id::{classify, plant_id, MessageKind, IDENTIFY_CMD_OFFSET};
|
||||||
use canapi::SensorSlot;
|
use canapi::SensorSlot;
|
||||||
use core::cmp::min;
|
|
||||||
use embassy_embedded_hal::shared_bus::blocking::i2c::I2cDevice;
|
use embassy_embedded_hal::shared_bus::blocking::i2c::I2cDevice;
|
||||||
use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex;
|
use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex;
|
||||||
use embassy_time::{Duration, Timer, WithTimeout};
|
use embassy_time::{Duration, Timer, WithTimeout};
|
||||||
@@ -32,12 +30,8 @@ use ina219::SyncIna219;
|
|||||||
use log::{error, info, warn};
|
use log::{error, info, warn};
|
||||||
use measurements::Resistance;
|
use measurements::Resistance;
|
||||||
use measurements::{Current, Voltage};
|
use measurements::{Current, Voltage};
|
||||||
// use no_panic::no_panic;
|
|
||||||
use pca9535::{GPIOBank, Pca9535Immediate, StandardExpanderInterface};
|
use pca9535::{GPIOBank, Pca9535Immediate, StandardExpanderInterface};
|
||||||
|
|
||||||
pub const BACKUP_HEADER_MAX_SIZE: usize = 64;
|
|
||||||
const CONFIG: config::Configuration = config::standard();
|
|
||||||
|
|
||||||
const MPPT_CURRENT_SHUNT_OHMS: f64 = 0.05_f64;
|
const MPPT_CURRENT_SHUNT_OHMS: f64 = 0.05_f64;
|
||||||
const TWAI_BAUDRATE: twai::BaudRate = twai::BaudRate::Custom(twai::TimingConfig {
|
const TWAI_BAUDRATE: twai::BaudRate = twai::BaudRate::Custom(twai::TimingConfig {
|
||||||
baud_rate_prescaler: 200, // 40MHz / 200 * 2 = 100 on C6, 100 * 20 = 2000 divisor, 40MHz / 2000 = 20kHz
|
baud_rate_prescaler: 200, // 40MHz / 200 * 2 = 100 on C6, 100 * 20 = 2000 divisor, 40MHz / 2000 = 20kHz
|
||||||
@@ -340,12 +334,19 @@ impl<'a> BoardInteraction<'a> for V4<'a> {
|
|||||||
bail!("pump current sensor not available");
|
bail!("pump current sensor not available");
|
||||||
}
|
}
|
||||||
Some(pump_ina) => {
|
Some(pump_ina) => {
|
||||||
let raw = pump_ina.shunt_voltage()?;
|
let v = pump_ina
|
||||||
let shunt_voltage =
|
.shunt_voltage()
|
||||||
Voltage::from_microvolts(raw.shunt_voltage_uv().abs() as f64);
|
.map_err(|e| FatError::String {
|
||||||
let shut_value = Resistance::from_ohms(0.05_f64);
|
error: alloc::format!("{e:?}"),
|
||||||
let current = shunt_voltage.as_volts() / shut_value.as_ohms();
|
})
|
||||||
Ok(Current::from_amperes(current))
|
.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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -363,7 +364,7 @@ impl<'a> BoardInteraction<'a> for V4<'a> {
|
|||||||
async fn measure_moisture_hz(&mut self) -> FatResult<Moistures> {
|
async fn measure_moisture_hz(&mut self) -> FatResult<Moistures> {
|
||||||
self.can_power.set_high();
|
self.can_power.set_high();
|
||||||
Timer::after_millis(500).await;
|
Timer::after_millis(500).await;
|
||||||
let config = self.twai_config.take().context("twai config not set")?;
|
let config = self.twai_config.take().expect("twai config not set");
|
||||||
let mut twai = config.into_async().start();
|
let mut twai = config.into_async().start();
|
||||||
|
|
||||||
if twai.is_bus_off() {
|
if twai.is_bus_off() {
|
||||||
@@ -393,7 +394,7 @@ impl<'a> BoardInteraction<'a> for V4<'a> {
|
|||||||
async fn detect_sensors(&mut self, request: Detection) -> FatResult<Detection> {
|
async fn detect_sensors(&mut self, request: Detection) -> FatResult<Detection> {
|
||||||
self.can_power.set_high();
|
self.can_power.set_high();
|
||||||
Timer::after_millis(500).await;
|
Timer::after_millis(500).await;
|
||||||
let config = self.twai_config.take().context("twai config not set")?;
|
let config = self.twai_config.take().expect("twai config not set");
|
||||||
let mut twai = config.into_async().start();
|
let mut twai = config.into_async().start();
|
||||||
|
|
||||||
if twai.is_bus_off() {
|
if twai.is_bus_off() {
|
||||||
@@ -537,74 +538,6 @@ impl<'a> BoardInteraction<'a> for V4<'a> {
|
|||||||
}
|
}
|
||||||
Ok(())
|
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 = bincode::encode_into_slice(controller_config, &mut buffer, CONFIG)?;
|
|
||||||
info!("Writing backup config of size {}", length);
|
|
||||||
let mut checksum = X25.digest();
|
|
||||||
checksum.update(&buffer[..length]);
|
|
||||||
let mut header_page_buffer = [0_u8; BACKUP_HEADER_MAX_SIZE];
|
|
||||||
|
|
||||||
let time = self.rtc_module.get_rtc_time().await?.timestamp_millis();
|
|
||||||
let header = BackupHeader {
|
|
||||||
crc16: checksum.finalize(),
|
|
||||||
timestamp: time,
|
|
||||||
size: length as u16,
|
|
||||||
};
|
|
||||||
info!("Header is {:?}", header);
|
|
||||||
bincode::encode_into_slice(&header, &mut header_page_buffer, CONFIG)?;
|
|
||||||
info!("Header is serialized");
|
|
||||||
self.get_rtc_module().write(0, &header_page_buffer)?;
|
|
||||||
info!("Header written");
|
|
||||||
let mut to_write = length;
|
|
||||||
let mut chunk: usize = 0;
|
|
||||||
|
|
||||||
while to_write > 0 {
|
|
||||||
self.progress(chunk as u32).await;
|
|
||||||
let start = chunk * EEPROM_PAGE;
|
|
||||||
let end = start + min(EEPROM_PAGE, to_write);
|
|
||||||
let part = &buffer[start..end];
|
|
||||||
info!(
|
|
||||||
"Writing chunk {} of size {} to offset {}",
|
|
||||||
chunk,
|
|
||||||
part.len(),
|
|
||||||
start
|
|
||||||
);
|
|
||||||
to_write -= part.len();
|
|
||||||
chunk += 1;
|
|
||||||
self.get_rtc_module()
|
|
||||||
.write((BACKUP_HEADER_MAX_SIZE + chunk * EEPROM_PAGE) as u32, part)?;
|
|
||||||
}
|
|
||||||
info!("Backup complete");
|
|
||||||
self.clear_progress().await;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn read_backup(&mut self) -> FatResult<PlantControllerConfig> {
|
|
||||||
let info = self.backup_info().await?;
|
|
||||||
let mut store = alloc::vec![0_u8; info.size as usize];
|
|
||||||
self.rtc_module
|
|
||||||
.read(BACKUP_HEADER_MAX_SIZE as u32, store.as_mut_slice())?;
|
|
||||||
let mut checksum = X25.digest();
|
|
||||||
checksum.update(&store[..]);
|
|
||||||
let crc = checksum.finalize();
|
|
||||||
if crc != info.crc16 {
|
|
||||||
bail!("CRC mismatch in backup data")
|
|
||||||
}
|
|
||||||
let (decoded, _) = bincode::decode_from_slice(&store[..], CONFIG)?;
|
|
||||||
Ok(decoded)
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn backup_info(&mut self) -> FatResult<BackupHeader> {
|
|
||||||
let mut header_page_buffer = [0_u8; BACKUP_HEADER_MAX_SIZE];
|
|
||||||
self.get_rtc_module().read(0, &mut header_page_buffer)?;
|
|
||||||
info!("Read header page");
|
|
||||||
let info: Result<(BackupHeader, usize), bincode::error::DecodeError> =
|
|
||||||
bincode::decode_from_slice(&header_page_buffer[..], CONFIG);
|
|
||||||
info!("decoding header: {:?}", info);
|
|
||||||
let (header, _) = info.context("Could not read backup header")?;
|
|
||||||
Ok(header)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn wait_for_can_measurements(
|
async fn wait_for_can_measurements(
|
||||||
|
|||||||
@@ -33,8 +33,7 @@ impl<'a> TankSensor<'a> {
|
|||||||
one_wire_pin.apply_output_config(&OutputConfig::default().with_pull(Pull::None));
|
one_wire_pin.apply_output_config(&OutputConfig::default().with_pull(Pull::None));
|
||||||
|
|
||||||
let mut adc1_config = AdcConfig::new();
|
let mut adc1_config = AdcConfig::new();
|
||||||
let tank_pin =
|
let tank_pin = adc1_config.enable_pin_with_cal::<_, AdcCalLine<_>>(gpio5, Attenuation::_11dB);
|
||||||
adc1_config.enable_pin_with_cal::<_, AdcCalLine<_>>(gpio5, Attenuation::_11dB);
|
|
||||||
let tank_channel = Adc::new(adc1, adc1_config);
|
let tank_channel = Adc::new(adc1, adc1_config);
|
||||||
|
|
||||||
let one_wire_bus = OneWire::new(one_wire_pin, false);
|
let one_wire_bus = OneWire::new(one_wire_pin, false);
|
||||||
@@ -142,17 +141,12 @@ impl<'a> TankSensor<'a> {
|
|||||||
let value = self.tank_channel.read_oneshot(&mut self.tank_pin);
|
let value = self.tank_channel.read_oneshot(&mut self.tank_pin);
|
||||||
//force yield
|
//force yield
|
||||||
Timer::after_millis(10).await;
|
Timer::after_millis(10).await;
|
||||||
match value {
|
*sample = value.unwrap();
|
||||||
Ok(v) => *sample = v,
|
|
||||||
Err(e) => {
|
|
||||||
bail!("ADC Hardware error: {:?}", e);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
self.tank_power.set_low();
|
self.tank_power.set_low();
|
||||||
|
|
||||||
store.sort();
|
store.sort();
|
||||||
let median_mv = store[TANK_MULTI_SAMPLE / 2] as f32;
|
let median_mv = store[TANK_MULTI_SAMPLE / 2] as f32;
|
||||||
Ok(median_mv / 1000.0)
|
Ok(median_mv/1000.0)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -97,7 +97,7 @@ pub async fn log(
|
|||||||
impl LogArray {
|
impl LogArray {
|
||||||
pub fn get(&mut self) -> Vec<LogEntry> {
|
pub fn get(&mut self) -> Vec<LogEntry> {
|
||||||
let head: RangedU8<0, MAX_LOG_ARRAY_INDEX> =
|
let head: RangedU8<0, MAX_LOG_ARRAY_INDEX> =
|
||||||
RangedU8::new(self.head).unwrap_or(RangedU8::new_saturating(0));
|
RangedU8::new(self.head).unwrap_or(RangedU8::new(0).unwrap());
|
||||||
|
|
||||||
let mut rv: Vec<LogEntry> = Vec::new();
|
let mut rv: Vec<LogEntry> = Vec::new();
|
||||||
let mut index = head.wrapping_sub(1);
|
let mut index = head.wrapping_sub(1);
|
||||||
@@ -120,7 +120,7 @@ impl LogArray {
|
|||||||
txt_long: &str,
|
txt_long: &str,
|
||||||
) {
|
) {
|
||||||
let mut head: RangedU8<0, MAX_LOG_ARRAY_INDEX> =
|
let mut head: RangedU8<0, MAX_LOG_ARRAY_INDEX> =
|
||||||
RangedU8::new(self.head).unwrap_or(RangedU8::new_saturating(0));
|
RangedU8::new(self.head).unwrap_or(RangedU8::new(0).unwrap());
|
||||||
|
|
||||||
let mut txt_short_stack: heapless::String<TXT_SHORT_LENGTH> = heapless::String::new();
|
let mut txt_short_stack: heapless::String<TXT_SHORT_LENGTH> = heapless::String::new();
|
||||||
let mut txt_long_stack: heapless::String<TXT_LONG_LENGTH> = heapless::String::new();
|
let mut txt_long_stack: heapless::String<TXT_LONG_LENGTH> = heapless::String::new();
|
||||||
@@ -281,8 +281,6 @@ pub enum LogMessage {
|
|||||||
PumpMissingSensorCurrent,
|
PumpMissingSensorCurrent,
|
||||||
#[strum(serialize = "MPPT Current sensor could not be reached")]
|
#[strum(serialize = "MPPT Current sensor could not be reached")]
|
||||||
MPPTError,
|
MPPTError,
|
||||||
#[strum(serialize = "Parsing error reading message")]
|
|
||||||
UnknownMessage,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize)]
|
#[derive(Serialize)]
|
||||||
@@ -303,7 +301,7 @@ impl From<&LogMessage> for MessageTranslation {
|
|||||||
impl LogMessage {
|
impl LogMessage {
|
||||||
pub fn log_localisation_config() -> Vec<MessageTranslation> {
|
pub fn log_localisation_config() -> Vec<MessageTranslation> {
|
||||||
Vec::from_iter((0..LogMessage::len()).map(|i| {
|
Vec::from_iter((0..LogMessage::len()).map(|i| {
|
||||||
let msg_type = LogMessage::from_ordinal(i).unwrap_or(LogMessage::UnknownMessage);
|
let msg_type = LogMessage::from_ordinal(i).unwrap();
|
||||||
(&msg_type).into()
|
(&msg_type).into()
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,7 +8,6 @@
|
|||||||
reason = "mem::forget is generally not safe to do with esp_hal types, especially those \
|
reason = "mem::forget is generally not safe to do with esp_hal types, especially those \
|
||||||
holding buffers for the duration of a data transfer."
|
holding buffers for the duration of a data transfer."
|
||||||
)]
|
)]
|
||||||
#![deny(clippy::unwrap_used, clippy::expect_used, clippy::panic)]
|
|
||||||
|
|
||||||
//TODO insert version here and read it in other parts, also read this for the ota webview
|
//TODO insert version here and read it in other parts, also read this for the ota webview
|
||||||
esp_bootloader_esp_idf::esp_app_desc!();
|
esp_bootloader_esp_idf::esp_app_desc!();
|
||||||
@@ -40,7 +39,7 @@ use embassy_net::Stack;
|
|||||||
use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex;
|
use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex;
|
||||||
use embassy_sync::mutex::{Mutex, MutexGuard};
|
use embassy_sync::mutex::{Mutex, MutexGuard};
|
||||||
use embassy_sync::once_lock::OnceLock;
|
use embassy_sync::once_lock::OnceLock;
|
||||||
use embassy_time::{Duration, Instant, Timer};
|
use embassy_time::{Duration, Instant, Timer, WithTimeout};
|
||||||
use esp_hal::rom::ets_delay_us;
|
use esp_hal::rom::ets_delay_us;
|
||||||
use esp_hal::system::software_reset;
|
use esp_hal::system::software_reset;
|
||||||
use esp_println::{logger, println};
|
use esp_println::{logger, println};
|
||||||
@@ -248,7 +247,7 @@ async fn safe_main(spawner: Spawner) -> FatResult<()> {
|
|||||||
let reboot_now = Arc::new(AtomicBool::new(false));
|
let reboot_now = Arc::new(AtomicBool::new(false));
|
||||||
println!("starting webserver");
|
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()).await;
|
wait_infinity(board, WaitType::MissingConfig, reboot_now.clone()).await;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -298,9 +297,7 @@ async fn safe_main(spawner: Spawner) -> FatResult<()> {
|
|||||||
|
|
||||||
if let NetworkMode::Wifi { ref ip_address, .. } = network_mode {
|
if let NetworkMode::Wifi { ref ip_address, .. } = network_mode {
|
||||||
publish_firmware_info(&mut board, version, ip_address, &timezone_time.to_rfc3339()).await;
|
publish_firmware_info(&mut board, version, ip_address, &timezone_time.to_rfc3339()).await;
|
||||||
publish_battery_state(&mut board).await.unwrap_or_else(|e| {
|
publish_battery_state(&mut board).await;
|
||||||
error!("Error publishing battery state {e}");
|
|
||||||
});
|
|
||||||
let _ = publish_mppt_state(&mut board).await;
|
let _ = publish_mppt_state(&mut board).await;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -329,12 +326,7 @@ async fn safe_main(spawner: Spawner) -> FatResult<()> {
|
|||||||
info!("executing config mode override");
|
info!("executing config mode override");
|
||||||
//config upload will trigger reboot!
|
//config upload will trigger reboot!
|
||||||
let reboot_now = Arc::new(AtomicBool::new(false));
|
let reboot_now = Arc::new(AtomicBool::new(false));
|
||||||
let stack_val = stack.take();
|
spawner.spawn(http_server(reboot_now.clone(), stack.take().unwrap()))?;
|
||||||
if let Some(s) = stack_val {
|
|
||||||
spawner.spawn(http_server(reboot_now.clone(), s))?;
|
|
||||||
} else {
|
|
||||||
bail!("Network stack missing, hard abort")
|
|
||||||
}
|
|
||||||
wait_infinity(board, WaitType::ConfigButton, reboot_now.clone()).await;
|
wait_infinity(board, WaitType::ConfigButton, reboot_now.clone()).await;
|
||||||
} else {
|
} else {
|
||||||
LOG_ACCESS
|
LOG_ACCESS
|
||||||
@@ -414,11 +406,7 @@ async fn safe_main(spawner: Spawner) -> FatResult<()> {
|
|||||||
}
|
}
|
||||||
info!("Water temp is {}", water_temp.as_ref().unwrap_or(&0.));
|
info!("Water temp is {}", water_temp.as_ref().unwrap_or(&0.));
|
||||||
|
|
||||||
publish_tank_state(&mut board, &tank_state, water_temp)
|
publish_tank_state(&mut board, &tank_state, water_temp).await;
|
||||||
.await
|
|
||||||
.unwrap_or_else(|e| {
|
|
||||||
error!("Error publishing tank state {e}");
|
|
||||||
});
|
|
||||||
|
|
||||||
let moisture = board.board_hal.measure_moisture_hz().await?;
|
let moisture = board.board_hal.measure_moisture_hz().await?;
|
||||||
|
|
||||||
@@ -433,11 +421,7 @@ async fn safe_main(spawner: Spawner) -> FatResult<()> {
|
|||||||
PlantState::read_hardware_state(moisture, 7, &mut board).await,
|
PlantState::read_hardware_state(moisture, 7, &mut board).await,
|
||||||
];
|
];
|
||||||
|
|
||||||
publish_plant_states(&mut board, &timezone_time.clone(), &plantstate)
|
publish_plant_states(&mut board, &timezone_time.clone(), &plantstate).await;
|
||||||
.await
|
|
||||||
.unwrap_or_else(|e| {
|
|
||||||
error!("Error publishing plant states {e}");
|
|
||||||
});
|
|
||||||
|
|
||||||
let pump_required = plantstate
|
let pump_required = plantstate
|
||||||
.iter()
|
.iter()
|
||||||
@@ -641,18 +625,14 @@ async fn safe_main(spawner: Spawner) -> FatResult<()> {
|
|||||||
|
|
||||||
if stay_alive {
|
if stay_alive {
|
||||||
let reboot_now = Arc::new(AtomicBool::new(false));
|
let reboot_now = Arc::new(AtomicBool::new(false));
|
||||||
if let Some(s) = stack.take() {
|
spawner.spawn(http_server(reboot_now.clone(), stack.take().unwrap()))?;
|
||||||
spawner.spawn(http_server(reboot_now.clone(), s))?;
|
wait_infinity(board, WaitType::MqttConfig, reboot_now.clone()).await;
|
||||||
wait_infinity(board, WaitType::MqttConfig, reboot_now.clone()).await;
|
|
||||||
} else {
|
|
||||||
bail!("Network Stack missing, hard abort");
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
//TODO wait for all mqtt publishes?
|
//TODO wait for all mqtt publishes?
|
||||||
Timer::after_millis(5000).await;
|
Timer::after_millis(5000).await;
|
||||||
|
|
||||||
board.board_hal.get_esp().set_restart_to_conf(false);
|
board.board_hal.get_esp().set_restart_to_conf(false);
|
||||||
let _ = board
|
board
|
||||||
.board_hal
|
.board_hal
|
||||||
.deep_sleep(1000 * 1000 * 60 * deep_sleep_duration_minutes as u64)
|
.deep_sleep(1000 * 1000 * 60 * deep_sleep_duration_minutes as u64)
|
||||||
.await;
|
.await;
|
||||||
@@ -821,29 +801,30 @@ async fn publish_tank_state(
|
|||||||
board: &mut MutexGuard<'_, CriticalSectionRawMutex, HAL<'static>>,
|
board: &mut MutexGuard<'_, CriticalSectionRawMutex, HAL<'static>>,
|
||||||
tank_state: &TankState,
|
tank_state: &TankState,
|
||||||
water_temp: FatResult<f32>,
|
water_temp: FatResult<f32>,
|
||||||
) -> FatResult<()> {
|
) {
|
||||||
let state = serde_json::to_string(
|
let state = serde_json::to_string(
|
||||||
&tank_state.as_mqtt_info(&board.board_hal.get_config().tank, &water_temp),
|
&tank_state.as_mqtt_info(&board.board_hal.get_config().tank, &water_temp),
|
||||||
)?;
|
)
|
||||||
|
.unwrap();
|
||||||
board
|
board
|
||||||
.board_hal
|
.board_hal
|
||||||
.get_esp()
|
.get_esp()
|
||||||
.mqtt_publish("/water", &state)
|
.mqtt_publish("/water", &state)
|
||||||
.await;
|
.await;
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn publish_plant_states(
|
async fn publish_plant_states(
|
||||||
board: &mut MutexGuard<'_, CriticalSectionRawMutex, HAL<'static>>,
|
board: &mut MutexGuard<'_, CriticalSectionRawMutex, HAL<'static>>,
|
||||||
timezone_time: &DateTime<Tz>,
|
timezone_time: &DateTime<Tz>,
|
||||||
plantstate: &[PlantState; 8],
|
plantstate: &[PlantState; 8],
|
||||||
) -> FatResult<()> {
|
) {
|
||||||
for (plant_id, (plant_state, plant_conf)) in plantstate
|
for (plant_id, (plant_state, plant_conf)) in plantstate
|
||||||
.iter()
|
.iter()
|
||||||
.zip(&board.board_hal.get_config().plants.clone())
|
.zip(&board.board_hal.get_config().plants.clone())
|
||||||
.enumerate()
|
.enumerate()
|
||||||
{
|
{
|
||||||
let state = serde_json::to_string(&plant_state.to_mqtt_info(plant_conf, timezone_time))?;
|
let state =
|
||||||
|
serde_json::to_string(&plant_state.to_mqtt_info(plant_conf, timezone_time)).unwrap();
|
||||||
let plant_topic = format!("/plant{}", plant_id + 1);
|
let plant_topic = format!("/plant{}", plant_id + 1);
|
||||||
let _ = board
|
let _ = board
|
||||||
.board_hal
|
.board_hal
|
||||||
@@ -851,7 +832,6 @@ async fn publish_plant_states(
|
|||||||
.mqtt_publish(&plant_topic, &state)
|
.mqtt_publish(&plant_topic, &state)
|
||||||
.await;
|
.await;
|
||||||
}
|
}
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn publish_firmware_info(
|
async fn publish_firmware_info(
|
||||||
@@ -1003,11 +983,11 @@ async fn publish_mppt_state(
|
|||||||
|
|
||||||
async fn publish_battery_state(
|
async fn publish_battery_state(
|
||||||
board: &mut MutexGuard<'_, CriticalSectionRawMutex, HAL<'static>>,
|
board: &mut MutexGuard<'_, CriticalSectionRawMutex, HAL<'static>>,
|
||||||
) -> FatResult<()> {
|
) -> () {
|
||||||
let state = board.board_hal.get_battery_monitor().get_state().await;
|
let state = board.board_hal.get_battery_monitor().get_state().await;
|
||||||
let value = match state {
|
let value = match state {
|
||||||
Ok(state) => {
|
Ok(state) => {
|
||||||
let json = serde_json::to_string(&state)?.to_owned();
|
let json = serde_json::to_string(&state).unwrap().to_owned();
|
||||||
json.to_owned()
|
json.to_owned()
|
||||||
}
|
}
|
||||||
Err(_) => "error".to_owned(),
|
Err(_) => "error".to_owned(),
|
||||||
@@ -1019,7 +999,6 @@ async fn publish_battery_state(
|
|||||||
.mqtt_publish("/battery", &value)
|
.mqtt_publish("/battery", &value)
|
||||||
.await;
|
.await;
|
||||||
}
|
}
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn wait_infinity(
|
async fn wait_infinity(
|
||||||
@@ -1066,7 +1045,8 @@ async fn wait_infinity(
|
|||||||
exit_hold_blink = !exit_hold_blink;
|
exit_hold_blink = !exit_hold_blink;
|
||||||
|
|
||||||
let progress = core::cmp::min(elapsed, exit_hold_duration);
|
let progress = core::cmp::min(elapsed, exit_hold_duration);
|
||||||
let lit = ((progress.as_millis() * 8) / exit_hold_duration.as_millis())
|
let lit = ((progress.as_millis() as u64 * 8)
|
||||||
|
/ exit_hold_duration.as_millis() as u64)
|
||||||
.saturating_add(1)
|
.saturating_add(1)
|
||||||
.min(8) as usize;
|
.min(8) as usize;
|
||||||
|
|
||||||
@@ -1216,8 +1196,6 @@ async fn handle_serial_config(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
use embassy_time::WithTimeout;
|
|
||||||
#[allow(clippy::panic, clippy::unwrap_used, clippy::expect_used)]
|
|
||||||
#[esp_rtos::main]
|
#[esp_rtos::main]
|
||||||
async fn main(spawner: Spawner) -> ! {
|
async fn main(spawner: Spawner) -> ! {
|
||||||
// intialize embassy
|
// intialize embassy
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
use bincode::{Decode, Encode};
|
|
||||||
use crate::hal::Moistures;
|
use crate::hal::Moistures;
|
||||||
use crate::{config::PlantConfig, hal::HAL, in_time_range};
|
use crate::{config::PlantConfig, hal::HAL, in_time_range};
|
||||||
use chrono::{DateTime, TimeDelta, Utc};
|
use chrono::{DateTime, TimeDelta, Utc};
|
||||||
@@ -71,7 +70,7 @@ impl PumpState {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Clone, Copy, Debug, PartialEq, Encode, Decode)]
|
#[derive(Serialize, Deserialize, Clone, Copy, Debug, PartialEq)]
|
||||||
pub enum PlantWateringMode {
|
pub enum PlantWateringMode {
|
||||||
Off,
|
Off,
|
||||||
TargetMoisture,
|
TargetMoisture,
|
||||||
|
|||||||
@@ -158,11 +158,12 @@ pub async fn determine_tank_state(
|
|||||||
board: &mut MutexGuard<'static, CriticalSectionRawMutex, HAL<'static>>,
|
board: &mut MutexGuard<'static, CriticalSectionRawMutex, HAL<'static>>,
|
||||||
) -> TankState {
|
) -> TankState {
|
||||||
if board.board_hal.get_config().tank.tank_sensor_enabled {
|
if board.board_hal.get_config().tank.tank_sensor_enabled {
|
||||||
match board.board_hal.get_tank_sensor() {
|
match board
|
||||||
Ok(sensor) => match sensor.tank_sensor_voltage().await {
|
.board_hal
|
||||||
Ok(raw_sensor_value_mv) => TankState::Present(raw_sensor_value_mv),
|
.get_tank_sensor()
|
||||||
Err(err) => TankState::Error(TankError::BoardError(err.to_string())),
|
.map(|f| f.tank_sensor_voltage())
|
||||||
},
|
{
|
||||||
|
Ok(raw_sensor_value_mv) => TankState::Present(raw_sensor_value_mv.await.unwrap()),
|
||||||
Err(err) => TankState::Error(TankError::BoardError(err.to_string())),
|
Err(err) => TankState::Error(TankError::BoardError(err.to_string())),
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
use crate::fat_error::{FatError, FatResult};
|
use crate::fat_error::{FatError, FatResult};
|
||||||
use crate::webserver::read_up_to_bytes_from_request;
|
use crate::hal::rtc::X25;
|
||||||
use crate::BOARD_ACCESS;
|
use crate::BOARD_ACCESS;
|
||||||
use alloc::borrow::ToOwned;
|
use alloc::borrow::ToOwned;
|
||||||
|
use alloc::format;
|
||||||
use alloc::string::{String, ToString};
|
use alloc::string::{String, ToString};
|
||||||
use chrono::DateTime;
|
use chrono::DateTime;
|
||||||
use edge_http::io::server::Connection;
|
use edge_http::io::server::Connection;
|
||||||
@@ -20,9 +21,48 @@ pub(crate) async fn get_backup_config<T, const N: usize>(
|
|||||||
where
|
where
|
||||||
T: Read + Write,
|
T: Read + Write,
|
||||||
{
|
{
|
||||||
let mut board = BOARD_ACCESS.get().await.lock().await;
|
// First pass: verify checksum without sending data
|
||||||
let backup = board.board_hal.read_backup().await?;
|
let mut checksum = X25.digest();
|
||||||
|
let mut chunk = 0_usize;
|
||||||
|
loop {
|
||||||
|
let mut board = BOARD_ACCESS.get().await.lock().await;
|
||||||
|
board.board_hal.progress(chunk as u32).await;
|
||||||
|
let (buf, len, expected_crc) = board
|
||||||
|
.board_hal
|
||||||
|
.get_rtc_module()
|
||||||
|
.get_backup_config(chunk)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
// Update checksum with the actual data bytes of this chunk
|
||||||
|
checksum.update(&buf[..len]);
|
||||||
|
|
||||||
|
let is_last = len == 0 || len < buf.len();
|
||||||
|
if is_last {
|
||||||
|
let actual_crc = checksum.finalize();
|
||||||
|
if actual_crc != expected_crc {
|
||||||
|
BOARD_ACCESS
|
||||||
|
.get()
|
||||||
|
.await
|
||||||
|
.lock()
|
||||||
|
.await
|
||||||
|
.board_hal
|
||||||
|
.clear_progress()
|
||||||
|
.await;
|
||||||
|
conn.initiate_response(
|
||||||
|
409,
|
||||||
|
Some(
|
||||||
|
format!("Checksum mismatch expected {expected_crc} got {actual_crc}")
|
||||||
|
.as_str(),
|
||||||
|
),
|
||||||
|
&[],
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
return Ok(Some(409));
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
chunk += 1;
|
||||||
|
}
|
||||||
// Second pass: stream data
|
// Second pass: stream data
|
||||||
conn.initiate_response(
|
conn.initiate_response(
|
||||||
200,
|
200,
|
||||||
@@ -35,8 +75,35 @@ where
|
|||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
conn.write_all(serde_json::to_string(&backup)?.as_bytes())
|
let mut chunk = 0_usize;
|
||||||
.await?;
|
loop {
|
||||||
|
let mut board = BOARD_ACCESS.get().await.lock().await;
|
||||||
|
board.board_hal.progress(chunk as u32).await;
|
||||||
|
let (buf, len, _expected_crc) = board
|
||||||
|
.board_hal
|
||||||
|
.get_rtc_module()
|
||||||
|
.get_backup_config(chunk)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
if len == 0 {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
conn.write_all(&buf[..len]).await?;
|
||||||
|
|
||||||
|
if len < buf.len() {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
chunk += 1;
|
||||||
|
}
|
||||||
|
BOARD_ACCESS
|
||||||
|
.get()
|
||||||
|
.await
|
||||||
|
.lock()
|
||||||
|
.await
|
||||||
|
.board_hal
|
||||||
|
.clear_progress()
|
||||||
|
.await;
|
||||||
Ok(Some(200))
|
Ok(Some(200))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -46,12 +113,49 @@ pub(crate) async fn backup_config<T, const N: usize>(
|
|||||||
where
|
where
|
||||||
T: Read + Write,
|
T: Read + Write,
|
||||||
{
|
{
|
||||||
let input = read_up_to_bytes_from_request(conn, Some(4096)).await?;
|
let mut offset = 0_usize;
|
||||||
info!("Read input with length {}", input.len());
|
let mut buf = [0_u8; 32];
|
||||||
|
|
||||||
|
let mut checksum = X25.digest();
|
||||||
|
|
||||||
|
let mut counter = 0;
|
||||||
|
loop {
|
||||||
|
let to_write = conn.read(&mut buf).await?;
|
||||||
|
if to_write == 0 {
|
||||||
|
info!("backup finished");
|
||||||
|
break;
|
||||||
|
} else {
|
||||||
|
let mut board = BOARD_ACCESS.get().await.lock().await;
|
||||||
|
board.board_hal.progress(counter).await;
|
||||||
|
|
||||||
|
counter += 1;
|
||||||
|
board
|
||||||
|
.board_hal
|
||||||
|
.get_rtc_module()
|
||||||
|
.backup_config(offset, &buf[0..to_write])
|
||||||
|
.await?;
|
||||||
|
checksum.update(&buf[0..to_write]);
|
||||||
|
}
|
||||||
|
offset += to_write;
|
||||||
|
}
|
||||||
|
|
||||||
let mut board = BOARD_ACCESS.get().await.lock().await;
|
let mut board = BOARD_ACCESS.get().await.lock().await;
|
||||||
let config_to_backup = serde_json::from_slice(&input)?;
|
board
|
||||||
info!("Parsed send config to object");
|
.board_hal
|
||||||
board.board_hal.backup_config(&config_to_backup).await?;
|
.get_rtc_module()
|
||||||
|
.backup_config_finalize(checksum.finalize(), offset)
|
||||||
|
.await?;
|
||||||
|
board.board_hal.clear_progress().await;
|
||||||
|
conn.initiate_response(
|
||||||
|
200,
|
||||||
|
Some("OK"),
|
||||||
|
&[
|
||||||
|
("Access-Control-Allow-Origin", "*"),
|
||||||
|
("Access-Control-Allow-Headers", "*"),
|
||||||
|
("Access-Control-Allow-Methods", "*"),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
Ok(Some("saved".to_owned()))
|
Ok(Some("saved".to_owned()))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -62,12 +166,10 @@ where
|
|||||||
T: Read + Write,
|
T: Read + Write,
|
||||||
{
|
{
|
||||||
let mut board = BOARD_ACCESS.get().await.lock().await;
|
let mut board = BOARD_ACCESS.get().await.lock().await;
|
||||||
let info = board.board_hal.backup_info().await;
|
let header = board.board_hal.get_rtc_module().get_backup_info().await;
|
||||||
|
let json = match header {
|
||||||
let json = match info {
|
|
||||||
Ok(h) => {
|
Ok(h) => {
|
||||||
info!("Got backup info: {:?}", h);
|
let timestamp = DateTime::from_timestamp_millis(h.timestamp).unwrap();
|
||||||
let timestamp = DateTime::from_timestamp_millis(h.timestamp).unwrap_or_default();
|
|
||||||
let wbh = WebBackupHeader {
|
let wbh = WebBackupHeader {
|
||||||
timestamp: timestamp.to_rfc3339(),
|
timestamp: timestamp.to_rfc3339(),
|
||||||
size: h.size,
|
size: h.size,
|
||||||
@@ -75,7 +177,6 @@ where
|
|||||||
serde_json::to_string(&wbh)?
|
serde_json::to_string(&wbh)?
|
||||||
}
|
}
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
info!("Error getting backup info: {:?}", err);
|
|
||||||
let wbh = WebBackupHeader {
|
let wbh = WebBackupHeader {
|
||||||
timestamp: err.to_string(),
|
timestamp: err.to_string(),
|
||||||
size: 0,
|
size: 0,
|
||||||
|
|||||||
160
Software/MainBoard/rust/src/webserver/file_manager.rs
Normal file
160
Software/MainBoard/rust/src/webserver/file_manager.rs
Normal file
@@ -0,0 +1,160 @@
|
|||||||
|
use crate::fat_error::{FatError, FatResult};
|
||||||
|
use crate::webserver::read_up_to_bytes_from_request;
|
||||||
|
use crate::BOARD_ACCESS;
|
||||||
|
use alloc::borrow::ToOwned;
|
||||||
|
use alloc::format;
|
||||||
|
use alloc::string::String;
|
||||||
|
use edge_http::io::server::Connection;
|
||||||
|
use edge_http::Method;
|
||||||
|
use edge_nal::io::{Read, Write};
|
||||||
|
use log::info;
|
||||||
|
|
||||||
|
pub(crate) async fn list_files<T, const N: usize>(
|
||||||
|
_request: &mut Connection<'_, T, N>,
|
||||||
|
) -> FatResult<Option<String>> {
|
||||||
|
let mut board = BOARD_ACCESS.get().await.lock().await;
|
||||||
|
let result = board.board_hal.get_esp().list_files().await?;
|
||||||
|
let file_list_json = serde_json::to_string(&result)?;
|
||||||
|
Ok(Some(file_list_json))
|
||||||
|
}
|
||||||
|
pub(crate) async fn file_operations<T, const N: usize>(
|
||||||
|
conn: &mut Connection<'_, T, { N }>,
|
||||||
|
method: Method,
|
||||||
|
path: &&str,
|
||||||
|
prefix: &&str,
|
||||||
|
) -> Result<Option<u32>, FatError>
|
||||||
|
where
|
||||||
|
T: Read + Write,
|
||||||
|
{
|
||||||
|
let filename = &path[prefix.len()..];
|
||||||
|
info!("file request for {filename} with method {method}");
|
||||||
|
Ok(match method {
|
||||||
|
Method::Delete => {
|
||||||
|
let mut board = BOARD_ACCESS.get().await.lock().await;
|
||||||
|
board
|
||||||
|
.board_hal
|
||||||
|
.get_esp()
|
||||||
|
.delete_file(filename.to_owned())
|
||||||
|
.await?;
|
||||||
|
conn.initiate_response(
|
||||||
|
200,
|
||||||
|
Some("OK"),
|
||||||
|
&[
|
||||||
|
("Access-Control-Allow-Origin", "*"),
|
||||||
|
("Access-Control-Allow-Headers", "*"),
|
||||||
|
("Access-Control-Allow-Methods", "*"),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
Some(200)
|
||||||
|
}
|
||||||
|
Method::Get => {
|
||||||
|
let disposition = format!("attachment; filename=\"{filename}\"");
|
||||||
|
let size = {
|
||||||
|
let mut board = BOARD_ACCESS.get().await.lock().await;
|
||||||
|
board
|
||||||
|
.board_hal
|
||||||
|
.get_esp()
|
||||||
|
.get_size(filename.to_owned())
|
||||||
|
.await?
|
||||||
|
};
|
||||||
|
|
||||||
|
conn.initiate_response(
|
||||||
|
200,
|
||||||
|
Some("OK"),
|
||||||
|
&[
|
||||||
|
("Content-Type", "application/octet-stream"),
|
||||||
|
("Content-Disposition", disposition.as_str()),
|
||||||
|
("Content-Length", &format!("{size}")),
|
||||||
|
("Access-Control-Allow-Origin", "*"),
|
||||||
|
("Access-Control-Allow-Headers", "*"),
|
||||||
|
("Access-Control-Allow-Methods", "*"),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
let mut chunk = 0;
|
||||||
|
loop {
|
||||||
|
let mut board = BOARD_ACCESS.get().await.lock().await;
|
||||||
|
board.board_hal.progress(chunk).await;
|
||||||
|
let read_chunk = board
|
||||||
|
.board_hal
|
||||||
|
.get_esp()
|
||||||
|
.get_file(filename.to_owned(), chunk)
|
||||||
|
.await?;
|
||||||
|
let length = read_chunk.1;
|
||||||
|
if length == 0 {
|
||||||
|
info!("file request for {filename} finished");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
let data = &read_chunk.0[0..length];
|
||||||
|
conn.write_all(data).await?;
|
||||||
|
if length < read_chunk.0.len() {
|
||||||
|
info!("file request for {filename} finished");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
chunk += 1;
|
||||||
|
}
|
||||||
|
BOARD_ACCESS
|
||||||
|
.get()
|
||||||
|
.await
|
||||||
|
.lock()
|
||||||
|
.await
|
||||||
|
.board_hal
|
||||||
|
.clear_progress()
|
||||||
|
.await;
|
||||||
|
Some(200)
|
||||||
|
}
|
||||||
|
Method::Post => {
|
||||||
|
{
|
||||||
|
let mut board = BOARD_ACCESS.get().await.lock().await;
|
||||||
|
//ensure the file is deleted first; otherwise we would need to truncate the file which will not work with streaming
|
||||||
|
let _ = board
|
||||||
|
.board_hal
|
||||||
|
.get_esp()
|
||||||
|
.delete_file(filename.to_owned())
|
||||||
|
.await;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut offset = 0_usize;
|
||||||
|
let mut chunk = 0;
|
||||||
|
loop {
|
||||||
|
let buf = read_up_to_bytes_from_request(conn, Some(4096)).await?;
|
||||||
|
if buf.is_empty() {
|
||||||
|
info!("file request for {filename} finished");
|
||||||
|
break;
|
||||||
|
} else {
|
||||||
|
let mut board = BOARD_ACCESS.get().await.lock().await;
|
||||||
|
board.board_hal.progress(chunk as u32).await;
|
||||||
|
board
|
||||||
|
.board_hal
|
||||||
|
.get_esp()
|
||||||
|
.write_file(filename.to_owned(), offset as u32, &buf)
|
||||||
|
.await?;
|
||||||
|
}
|
||||||
|
offset += buf.len();
|
||||||
|
chunk += 1;
|
||||||
|
}
|
||||||
|
BOARD_ACCESS
|
||||||
|
.get()
|
||||||
|
.await
|
||||||
|
.lock()
|
||||||
|
.await
|
||||||
|
.board_hal
|
||||||
|
.clear_progress()
|
||||||
|
.await;
|
||||||
|
conn.initiate_response(
|
||||||
|
200,
|
||||||
|
Some("OK"),
|
||||||
|
&[
|
||||||
|
("Access-Control-Allow-Origin", "*"),
|
||||||
|
("Access-Control-Allow-Headers", "*"),
|
||||||
|
("Access-Control-Allow-Methods", "*"),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
Some(200)
|
||||||
|
}
|
||||||
|
_ => None,
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -114,41 +114,14 @@ pub(crate) async fn get_version_web<T, const N: usize>(
|
|||||||
Ok(Some(serde_json::to_string(&get_version(&mut board).await)?))
|
Ok(Some(serde_json::to_string(&get_version(&mut board).await)?))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return the current in-memory config, or — when `saveidx` is `Some(idx)` —
|
|
||||||
/// the JSON stored in save slot `idx`.
|
|
||||||
pub(crate) async fn get_config<T, const N: usize>(
|
pub(crate) async fn get_config<T, const N: usize>(
|
||||||
_request: &mut Connection<'_, T, N>,
|
_request: &mut Connection<'_, T, N>,
|
||||||
saveidx: Option<usize>,
|
|
||||||
) -> FatResult<Option<String>> {
|
) -> FatResult<Option<String>> {
|
||||||
let mut board = BOARD_ACCESS.get().await.lock().await;
|
let mut board = BOARD_ACCESS.get().await.lock().await;
|
||||||
let json = match saveidx {
|
let json = serde_json::to_string(&board.board_hal.get_config())?;
|
||||||
None => serde_json::to_string(board.board_hal.get_config())?,
|
|
||||||
Some(idx) => {
|
|
||||||
board.board_hal.get_esp().load_config_slot(idx).await?
|
|
||||||
}
|
|
||||||
};
|
|
||||||
Ok(Some(json))
|
Ok(Some(json))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return a JSON array describing every valid save slot on flash.
|
|
||||||
pub(crate) async fn list_saves<T, const N: usize>(
|
|
||||||
_request: &mut Connection<'_, T, N>,
|
|
||||||
) -> FatResult<Option<String>> {
|
|
||||||
let mut board = BOARD_ACCESS.get().await.lock().await;
|
|
||||||
let saves = board.board_hal.get_esp().list_saves().await?;
|
|
||||||
Ok(Some(serde_json::to_string(&saves)?))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Erase (delete) a single save slot by index.
|
|
||||||
pub(crate) async fn delete_save<T, const N: usize>(
|
|
||||||
_request: &mut Connection<'_, T, N>,
|
|
||||||
idx: usize,
|
|
||||||
) -> FatResult<Option<String>> {
|
|
||||||
let mut board = BOARD_ACCESS.get().await.lock().await;
|
|
||||||
board.board_hal.get_esp().delete_save_slot(idx).await?;
|
|
||||||
Ok(None)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) async fn get_battery_state<T, const N: usize>(
|
pub(crate) async fn get_battery_state<T, const N: usize>(
|
||||||
_request: &mut Connection<'_, T, N>,
|
_request: &mut Connection<'_, T, N>,
|
||||||
) -> FatResult<Option<String>> {
|
) -> FatResult<Option<String>> {
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
//offer ota and config mode
|
//offer ota and config mode
|
||||||
|
|
||||||
mod backup_manager;
|
mod backup_manager;
|
||||||
|
mod file_manager;
|
||||||
mod get_json;
|
mod get_json;
|
||||||
mod get_log;
|
mod get_log;
|
||||||
mod get_static;
|
mod get_static;
|
||||||
@@ -9,9 +10,10 @@ mod post_json;
|
|||||||
|
|
||||||
use crate::fat_error::{FatError, FatResult};
|
use crate::fat_error::{FatError, FatResult};
|
||||||
use crate::webserver::backup_manager::{backup_config, backup_info, get_backup_config};
|
use crate::webserver::backup_manager::{backup_config, backup_info, get_backup_config};
|
||||||
|
use crate::webserver::file_manager::{file_operations, list_files};
|
||||||
use crate::webserver::get_json::{
|
use crate::webserver::get_json::{
|
||||||
get_battery_state, get_config, get_live_moisture, get_log_localization_config, get_solar_state,
|
get_battery_state, get_config, get_live_moisture, get_log_localization_config, get_solar_state,
|
||||||
delete_save, get_time, get_timezones, get_version_web, list_saves, tank_info,
|
get_time, get_timezones, get_version_web, tank_info,
|
||||||
};
|
};
|
||||||
use crate::webserver::get_log::get_log;
|
use crate::webserver::get_log::get_log;
|
||||||
use crate::webserver::get_static::{serve_bundle, serve_favicon, serve_index};
|
use crate::webserver::get_static::{serve_bundle, serve_favicon, serve_index};
|
||||||
@@ -58,7 +60,10 @@ impl Handler for HTTPRequestRouter {
|
|||||||
let method = headers.method;
|
let method = headers.method;
|
||||||
let path = headers.path;
|
let path = headers.path;
|
||||||
|
|
||||||
let status = if path == "/ota" {
|
let prefix = "/file?filename=";
|
||||||
|
let status = if path.starts_with(prefix) {
|
||||||
|
file_operations(conn, method, &path, &prefix).await?
|
||||||
|
} else if path == "/ota" {
|
||||||
ota_operations(conn, method).await.map_err(|e| {
|
ota_operations(conn, method).await.map_err(|e| {
|
||||||
error!("Error handling ota: {e}");
|
error!("Error handling ota: {e}");
|
||||||
e
|
e
|
||||||
@@ -77,20 +82,13 @@ impl Handler for HTTPRequestRouter {
|
|||||||
"/time" => Some(get_time(conn).await),
|
"/time" => Some(get_time(conn).await),
|
||||||
"/battery" => Some(get_battery_state(conn).await),
|
"/battery" => Some(get_battery_state(conn).await),
|
||||||
"/solar" => Some(get_solar_state(conn).await),
|
"/solar" => Some(get_solar_state(conn).await),
|
||||||
|
"/get_config" => Some(get_config(conn).await),
|
||||||
|
"/files" => Some(list_files(conn).await),
|
||||||
"/log_localization" => Some(get_log_localization_config(conn).await),
|
"/log_localization" => Some(get_log_localization_config(conn).await),
|
||||||
"/tank" => Some(tank_info(conn).await),
|
"/tank" => Some(tank_info(conn).await),
|
||||||
"/backup_info" => Some(backup_info(conn).await),
|
"/backup_info" => Some(backup_info(conn).await),
|
||||||
"/timezones" => Some(get_timezones().await),
|
"/timezones" => Some(get_timezones().await),
|
||||||
"/moisture" => Some(get_live_moisture(conn).await),
|
"/moisture" => Some(get_live_moisture(conn).await),
|
||||||
"/list_saves" => Some(list_saves(conn).await),
|
|
||||||
// /get_config accepts an optional ?saveidx=N query parameter
|
|
||||||
p if p == "/get_config" || p.starts_with("/get_config?") => {
|
|
||||||
let saveidx: Option<usize> = p
|
|
||||||
.find("saveidx=")
|
|
||||||
.and_then(|pos| p[pos + 8..].split('&').next())
|
|
||||||
.and_then(|s| s.parse().ok());
|
|
||||||
Some(get_config(conn, saveidx).await)
|
|
||||||
}
|
|
||||||
_ => None,
|
_ => None,
|
||||||
};
|
};
|
||||||
match json {
|
match json {
|
||||||
@@ -129,26 +127,7 @@ impl Handler for HTTPRequestRouter {
|
|||||||
Some(json) => Some(handle_json(conn, json).await?),
|
Some(json) => Some(handle_json(conn, json).await?),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Method::Delete => {
|
Method::Options | Method::Delete | Method::Head | Method::Put => None,
|
||||||
// DELETE /delete_save?idx=N
|
|
||||||
let json = if path == "/delete_save" || path.starts_with("/delete_save?") {
|
|
||||||
let idx: Option<usize> = path
|
|
||||||
.find("idx=")
|
|
||||||
.and_then(|pos| path[pos + 4..].split('&').next())
|
|
||||||
.and_then(|s| s.parse().ok());
|
|
||||||
match idx {
|
|
||||||
Some(idx) => Some(delete_save(conn, idx).await),
|
|
||||||
None => Some(Err(FatError::String { error: "missing idx parameter".into() })),
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
};
|
|
||||||
match json {
|
|
||||||
None => None,
|
|
||||||
Some(json) => Some(handle_json(conn, json).await?),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Method::Options | Method::Head | Method::Put => None,
|
|
||||||
_ => None,
|
_ => None,
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -202,7 +181,6 @@ where
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[embassy_executor::task]
|
#[embassy_executor::task]
|
||||||
#[allow(clippy::panic, clippy::unwrap_used, clippy::expect_used)]
|
|
||||||
pub async fn http_server(reboot_now: Arc<AtomicBool>, stack: Stack<'static>) {
|
pub async fn http_server(reboot_now: Arc<AtomicBool>, stack: Stack<'static>) {
|
||||||
let buffer: TcpBuffers<2, 1024, 1024> = TcpBuffers::new();
|
let buffer: TcpBuffers<2, 1024, 1024> = TcpBuffers::new();
|
||||||
let tcp = Tcp::new(stack, &buffer);
|
let tcp = Tcp::new(stack, &buffer);
|
||||||
|
|||||||
@@ -108,7 +108,7 @@ where
|
|||||||
{
|
{
|
||||||
let actual_data = read_up_to_bytes_from_request(request, None).await?;
|
let actual_data = read_up_to_bytes_from_request(request, None).await?;
|
||||||
let time: SetTime = serde_json::from_slice(&actual_data)?;
|
let time: SetTime = serde_json::from_slice(&actual_data)?;
|
||||||
let parsed = DateTime::parse_from_rfc3339(time.time)?;
|
let parsed = DateTime::parse_from_rfc3339(time.time).unwrap();
|
||||||
esp_set_time(parsed).await?;
|
esp_set_time(parsed).await?;
|
||||||
Ok(None)
|
Ok(None)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -34,11 +34,6 @@ export interface NetworkConfig {
|
|||||||
max_wait: number
|
max_wait: number
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface SaveInfo {
|
|
||||||
idx: number,
|
|
||||||
len: number,
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface FileList {
|
export interface FileList {
|
||||||
total: number,
|
total: number,
|
||||||
used: number,
|
used: number,
|
||||||
|
|||||||
@@ -29,7 +29,41 @@
|
|||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<div class="subtitle">Save Slots:</div>
|
<div class="subtitle">Files:</div>
|
||||||
|
<div class="flexcontainer">
|
||||||
|
<div class="filekey">Total Size</div>
|
||||||
|
<div id="filetotalsize" class="filevalue"></div>
|
||||||
|
</div>
|
||||||
|
<div class="flexcontainer">
|
||||||
|
<div class="filekey">Used Size</div>
|
||||||
|
<div id="fileusedsize" class="filevalue"></div>
|
||||||
|
</div>
|
||||||
|
<div class="flexcontainer">
|
||||||
|
<div class="filekey">Free Size</div>
|
||||||
|
<div id="filefreesize" class="filevalue"></div>
|
||||||
|
</div>
|
||||||
|
<br>
|
||||||
|
|
||||||
|
<div class="flexcontainer" style="border-left-style: double; border-right-style: double; border-top-style: double;">
|
||||||
|
<div class="subtitle" >Upload:</div>
|
||||||
|
</div>
|
||||||
|
<div class="flexcontainer" style="border-left-style: double; border-right-style: double;">
|
||||||
|
<div class="flexcontainer">
|
||||||
|
<div class="filekey">
|
||||||
|
File:
|
||||||
|
</div>
|
||||||
|
<input id="fileuploadfile" class="filevalue" type="file">
|
||||||
|
</div>
|
||||||
|
<div class="flexcontainer">
|
||||||
|
<div class="filekey">
|
||||||
|
Name:
|
||||||
|
</div>
|
||||||
|
<input id="fileuploadname" class="filevalue" type="text">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="flexcontainer" style="border-left-style: double; border-right-style: double; border-bottom-style: double;">
|
||||||
|
<button id="fileuploadbtn" class="subtitle">Upload</button>
|
||||||
|
</div>
|
||||||
<br>
|
<br>
|
||||||
<div class="flexcontainer" style="border-left-style: double; border-right-style: double; border-top-style: double;">
|
<div class="flexcontainer" style="border-left-style: double; border-right-style: double; border-top-style: double;">
|
||||||
<div class="subtitle">List:</div>
|
<div class="subtitle">List:</div>
|
||||||
|
|||||||
@@ -1,49 +1,96 @@
|
|||||||
import {Controller} from "./main";
|
import {Controller} from "./main";
|
||||||
import {SaveInfo} from "./api";
|
import {FileInfo, FileList} from "./api";
|
||||||
|
const regex = /[^a-zA-Z0-9_.]/g;
|
||||||
|
|
||||||
|
function sanitize(str:string){
|
||||||
|
return str.replaceAll(regex, '_')
|
||||||
|
}
|
||||||
|
|
||||||
export class FileView {
|
export class FileView {
|
||||||
readonly fileListView: HTMLElement;
|
readonly fileListView: HTMLElement;
|
||||||
readonly controller: Controller;
|
readonly controller: Controller;
|
||||||
|
readonly filefreesize: HTMLElement;
|
||||||
|
readonly filetotalsize: HTMLElement;
|
||||||
|
readonly fileusedsize: HTMLElement;
|
||||||
|
|
||||||
constructor(controller: Controller) {
|
constructor(controller: Controller) {
|
||||||
(document.getElementById("fileview") as HTMLElement).innerHTML = require('./fileview.html') as string;
|
(document.getElementById("fileview") as HTMLElement).innerHTML = require('./fileview.html') as string;
|
||||||
this.fileListView = document.getElementById("fileList") as HTMLElement;
|
this.fileListView = document.getElementById("fileList") as HTMLElement
|
||||||
|
this.filefreesize = document.getElementById("filefreesize") as HTMLElement
|
||||||
|
this.filetotalsize = document.getElementById("filetotalsize") as HTMLElement
|
||||||
|
this.fileusedsize = document.getElementById("fileusedsize") as HTMLElement
|
||||||
|
|
||||||
|
let fileuploadfile = document.getElementById("fileuploadfile") as HTMLInputElement
|
||||||
|
let fileuploadname = document.getElementById("fileuploadname") as HTMLInputElement
|
||||||
|
let fileuploadbtn = document.getElementById("fileuploadbtn") as HTMLInputElement
|
||||||
|
fileuploadfile.onchange = () => {
|
||||||
|
const selectedFile = fileuploadfile.files?.[0];
|
||||||
|
if (selectedFile == null) {
|
||||||
|
//TODO error dialog here
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
fileuploadname.value = sanitize(selectedFile.name)
|
||||||
|
};
|
||||||
|
|
||||||
|
fileuploadname.onchange = () => {
|
||||||
|
let input = fileuploadname.value
|
||||||
|
let clean = sanitize(fileuploadname.value)
|
||||||
|
if (input != clean){
|
||||||
|
fileuploadname.value = clean
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fileuploadbtn.onclick = () => {
|
||||||
|
const selectedFile = fileuploadfile.files?.[0];
|
||||||
|
if (selectedFile == null) {
|
||||||
|
//TODO error dialog here
|
||||||
|
return
|
||||||
|
}
|
||||||
|
controller.uploadFile(selectedFile, selectedFile.name)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
this.controller = controller;
|
this.controller = controller;
|
||||||
}
|
}
|
||||||
|
|
||||||
setSaveList(saves: SaveInfo[], public_url: string) {
|
setFileList(fileList: FileList, public_url: string) {
|
||||||
// Sort newest first (highest index = most recently written slot)
|
this.filetotalsize.innerText = Math.floor(fileList.total / 1024) + "kB"
|
||||||
const sorted = saves.slice().sort((a, b) => b.idx - a.idx);
|
this.fileusedsize.innerText = Math.ceil(fileList.used / 1024) + "kB"
|
||||||
|
this.filefreesize.innerText = Math.ceil((fileList.total - fileList.used) / 1024) + "kB"
|
||||||
|
|
||||||
this.fileListView.textContent = "";
|
//fast clear
|
||||||
for (let i = 0; i < sorted.length; i++) {
|
this.fileListView.textContent = ""
|
||||||
new SaveEntry(this.controller, i, sorted[i], this.fileListView, public_url);
|
for (let i = 0; i < fileList.files.length; i++) {
|
||||||
|
let file = fileList.files[i]
|
||||||
|
new FileEntry(this.controller, i, file, this.fileListView, public_url);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class SaveEntry {
|
class FileEntry {
|
||||||
view: HTMLElement;
|
view: HTMLElement;
|
||||||
constructor(controller: Controller, fileid: number, saveinfo: SaveInfo, parent: HTMLElement, public_url: string) {
|
constructor(controller: Controller, fileid: number, fileinfo: FileInfo, parent: HTMLElement, public_url: string) {
|
||||||
this.view = document.createElement("div") as HTMLElement;
|
this.view = document.createElement("div") as HTMLElement
|
||||||
parent.appendChild(this.view);
|
parent.appendChild(this.view)
|
||||||
this.view.classList.add("fileentryouter");
|
this.view.classList.add("fileentryouter")
|
||||||
|
|
||||||
const template = require('./fileviewentry.html') as string;
|
const template = require('./fileviewentry.html') as string;
|
||||||
this.view.innerHTML = template.replaceAll("${fileid}", String(fileid));
|
this.view.innerHTML = template.replaceAll("${fileid}", String(fileid))
|
||||||
|
|
||||||
let name = document.getElementById("file_" + fileid + "_name") as HTMLElement;
|
let name = document.getElementById("file_" + fileid + "_name") as HTMLElement;
|
||||||
let size = document.getElementById("file_" + fileid + "_size") as HTMLElement;
|
let size = document.getElementById("file_" + fileid + "_size") as HTMLElement;
|
||||||
let deleteBtn = document.getElementById("file_" + fileid + "_delete") as HTMLButtonElement;
|
let deleteBtn = document.getElementById("file_" + fileid + "_delete") as HTMLButtonElement;
|
||||||
deleteBtn.onclick = () => {
|
deleteBtn.onclick = () => {
|
||||||
controller.deleteSlot(saveinfo.idx);
|
controller.deleteFile(fileinfo.filename);
|
||||||
};
|
}
|
||||||
|
|
||||||
let downloadBtn = document.getElementById("file_" + fileid + "_download") as HTMLAnchorElement;
|
let downloadBtn = document.getElementById("file_" + fileid + "_download") as HTMLAnchorElement;
|
||||||
downloadBtn.href = public_url + "/get_config?saveidx=" + saveinfo.idx;
|
downloadBtn.href = public_url + "/file?filename=" + fileinfo.filename
|
||||||
downloadBtn.download = "config_slot_" + saveinfo.idx + ".json";
|
downloadBtn.download = fileinfo.filename
|
||||||
|
|
||||||
name.innerText = "Slot " + saveinfo.idx;
|
name.innerText = fileinfo.filename;
|
||||||
size.innerText = saveinfo.len + " bytes";
|
size.innerText = fileinfo.size.toString()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,12 +1,11 @@
|
|||||||
<div class="flexcontainer">
|
<div class="flexcontainer">
|
||||||
<div id="file_${fileid}_name" class="filetitle">Slot</div>
|
<div id="file_${fileid}_name" class="filetitle">Name</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="flexcontainer">
|
<div class="flexcontainer">
|
||||||
<div class="filekey">Size</div>
|
<div class="filekey">Size</div>
|
||||||
<div id="file_${fileid}_size" class="filevalue"></div>
|
<div id = "file_${fileid}_size" class="filevalue"></div>
|
||||||
<a id="file_${fileid}_download" class="filevalue" target="_blank">Download</a>
|
<a id = "file_${fileid}_download" class="filevalue" target="_blank">Download</a>
|
||||||
<button id="file_${fileid}_delete" class="filevalue">Delete</button>
|
<button id = "file_${fileid}_delete" class="filevalue">Delete</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ import {
|
|||||||
SetTime, SSIDList, TankInfo,
|
SetTime, SSIDList, TankInfo,
|
||||||
TestPump,
|
TestPump,
|
||||||
VersionInfo,
|
VersionInfo,
|
||||||
SaveInfo, SolarState, PumpTestResult, Detection, CanPower
|
FileList, SolarState, PumpTestResult, Detection, CanPower
|
||||||
} from "./api";
|
} from "./api";
|
||||||
import {SolarView} from "./solarview";
|
import {SolarView} from "./solarview";
|
||||||
import {toast} from "./toast";
|
import {toast} from "./toast";
|
||||||
@@ -93,36 +93,65 @@ export class Controller {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async updateSaveList(): Promise<void> {
|
async updateFileList(): Promise<void> {
|
||||||
try {
|
try {
|
||||||
const response = await fetch(PUBLIC_URL + "/list_saves");
|
const response = await fetch(PUBLIC_URL + "/files");
|
||||||
const json = await response.json();
|
const json = await response.json();
|
||||||
const saves = json as SaveInfo[];
|
const filelist = json as FileList;
|
||||||
controller.fileview.setSaveList(saves, PUBLIC_URL);
|
controller.fileview.setFileList(filelist, PUBLIC_URL);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log(error);
|
console.log(error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
deleteSlot(idx: number) {
|
uploadFile(file: File, name: string) {
|
||||||
controller.progressview.addIndeterminate("slot_delete", "Deleting slot " + idx);
|
let current = 0;
|
||||||
|
let max = 100;
|
||||||
|
controller.progressview.addProgress("file_upload", (current / max) * 100, "Uploading File " + name + "(" + current + "/" + max + ")")
|
||||||
const ajax = new XMLHttpRequest();
|
const ajax = new XMLHttpRequest();
|
||||||
ajax.open("DELETE", PUBLIC_URL + "/delete_save?idx=" + idx);
|
ajax.upload.addEventListener("progress", event => {
|
||||||
ajax.send();
|
current = event.loaded / 1000;
|
||||||
ajax.addEventListener("error", () => {
|
max = event.total / 1000;
|
||||||
controller.progressview.removeProgress("slot_delete");
|
controller.progressview.addProgress("file_upload", (current / max) * 100, "Uploading File " + name + "(" + current + "/" + max + ")")
|
||||||
alert("Error deleting slot");
|
|
||||||
controller.updateSaveList();
|
|
||||||
}, false);
|
|
||||||
ajax.addEventListener("abort", () => {
|
|
||||||
controller.progressview.removeProgress("slot_delete");
|
|
||||||
alert("Aborted deleting slot");
|
|
||||||
controller.updateSaveList();
|
|
||||||
}, false);
|
}, false);
|
||||||
ajax.addEventListener("load", () => {
|
ajax.addEventListener("load", () => {
|
||||||
controller.progressview.removeProgress("slot_delete");
|
controller.progressview.removeProgress("file_upload")
|
||||||
controller.updateSaveList();
|
controller.updateFileList()
|
||||||
}, false);
|
}, false);
|
||||||
|
ajax.addEventListener("error", () => {
|
||||||
|
alert("Error upload")
|
||||||
|
controller.progressview.removeProgress("file_upload")
|
||||||
|
controller.updateFileList()
|
||||||
|
}, false);
|
||||||
|
ajax.addEventListener("abort", () => {
|
||||||
|
alert("abort upload")
|
||||||
|
controller.progressview.removeProgress("file_upload")
|
||||||
|
controller.updateFileList()
|
||||||
|
}, false);
|
||||||
|
ajax.open("POST", PUBLIC_URL + "/file?filename=" + name);
|
||||||
|
ajax.send(file);
|
||||||
|
}
|
||||||
|
|
||||||
|
deleteFile(name: string) {
|
||||||
|
controller.progressview.addIndeterminate("file_delete", "Deleting " + name);
|
||||||
|
const ajax = new XMLHttpRequest();
|
||||||
|
ajax.open("DELETE", PUBLIC_URL + "/file?filename=" + name);
|
||||||
|
ajax.send();
|
||||||
|
ajax.addEventListener("error", () => {
|
||||||
|
controller.progressview.removeProgress("file_delete")
|
||||||
|
alert("Error delete")
|
||||||
|
controller.updateFileList()
|
||||||
|
}, false);
|
||||||
|
ajax.addEventListener("abort", () => {
|
||||||
|
controller.progressview.removeProgress("file_delete")
|
||||||
|
alert("Error upload")
|
||||||
|
controller.updateFileList()
|
||||||
|
}, false);
|
||||||
|
ajax.addEventListener("load", () => {
|
||||||
|
controller.progressview.removeProgress("file_delete")
|
||||||
|
controller.updateFileList()
|
||||||
|
}, false);
|
||||||
|
controller.updateFileList()
|
||||||
}
|
}
|
||||||
|
|
||||||
async updateRTCData(): Promise<void> {
|
async updateRTCData(): Promise<void> {
|
||||||
@@ -639,7 +668,7 @@ const tasks = [
|
|||||||
{task: controller.updateSolarData, displayString: "Updating Solar Data"},
|
{task: controller.updateSolarData, displayString: "Updating Solar Data"},
|
||||||
{task: controller.downloadConfig, displayString: "Downloading Configuration"},
|
{task: controller.downloadConfig, displayString: "Downloading Configuration"},
|
||||||
{task: controller.version, displayString: "Fetching Version Information"},
|
{task: controller.version, displayString: "Fetching Version Information"},
|
||||||
{task: controller.updateSaveList, displayString: "Updating Save Slots"},
|
{task: controller.updateFileList, displayString: "Updating File List"},
|
||||||
{task: controller.getBackupInfo, displayString: "Fetching Backup Information"},
|
{task: controller.getBackupInfo, displayString: "Fetching Backup Information"},
|
||||||
{task: controller.loadLogLocaleConfig, displayString: "Loading Log Localization Config"},
|
{task: controller.loadLogLocaleConfig, displayString: "Loading Log Localization Config"},
|
||||||
{task: controller.loadTankInfo, displayString: "Loading Tank Information"},
|
{task: controller.loadTankInfo, displayString: "Loading Tank Information"},
|
||||||
|
|||||||
Reference in New Issue
Block a user