0
0
Fork 0

Compare commits

..

1 commit

Author SHA1 Message Date
Jannik Beyerstedt
202d874dee TMP: Add message generator 2026-07-27 13:45:12 +02:00
32 changed files with 730 additions and 27541 deletions

4
.gitignore vendored
View file

@ -24,7 +24,3 @@ target/
# Ignore .DS_Store file in mac # Ignore .DS_Store file in mac
**/.DS_Store **/.DS_Store
# Ignore KiCad backups and lock files
hardware/c_its-companion/c_its-companion-backups/
*.kicad_pro.lck

41
Cargo.lock generated
View file

@ -192,9 +192,9 @@ checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33"
[[package]] [[package]]
name = "c-its-parser" name = "c-its-parser"
version = "2.4.1" version = "2.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7e94d9990b20498f621f164f23ae198c4365115eb4191ae106352ec74d4a4110" checksum = "cd4db66f495280ec88eef8712dd9ab7d1aacc590f62485e3e03a89b3ce4f8b14"
dependencies = [ dependencies = [
"chrono", "chrono",
"etherparse", "etherparse",
@ -1171,7 +1171,6 @@ dependencies = [
"esp-println", "esp-println",
"esp-radio", "esp-radio",
"esp-rtos", "esp-rtos",
"esp32-cits-core",
"geo-types", "geo-types",
"log", "log",
"mipidsi", "mipidsi",
@ -1179,19 +1178,6 @@ dependencies = [
"profont", "profont",
"prost", "prost",
"prost-build", "prost-build",
"ublox",
]
[[package]]
name = "esp32-cits-core"
version = "0.1.0"
source = "git+https://git.hamburg.ccc.de/jtbx/esp32-cits-core?branch=main#a6d0ea6ca2da130fde2b84060a34ef45e772a459"
dependencies = [
"c-its-parser",
"chrono",
"esp-hal",
"esp-radio",
"geo-types",
] ]
[[package]] [[package]]
@ -2504,29 +2490,6 @@ version = "1.20.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "40ce102ab67701b8526c123c1bab5cbe42d7040ccfd0f64af1a385808d2f43de" checksum = "40ce102ab67701b8526c123c1bab5cbe42d7040ccfd0f64af1a385808d2f43de"
[[package]]
name = "ublox"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5bcc96bd937066775fb87034e1dbf6d7165a8975c74ddec1105e31b4a050538b"
dependencies = [
"bitflags",
"chrono",
"num-traits",
"ublox_derive",
]
[[package]]
name = "ublox_derive"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f5eda7d1c1151a40ae85c7c065a19959909573535034e18e1015b738993cd2c0"
dependencies = [
"proc-macro2",
"quote",
"syn 1.0.109",
]
[[package]] [[package]]
name = "ufmt-write" name = "ufmt-write"
version = "0.1.0" version = "0.1.0"

View file

@ -14,8 +14,14 @@ name = "test"
path = "./src/test.rs" path = "./src/test.rs"
required-features = ["std", "cam"] required-features = ["std", "cam"]
[[bin]]
## run with cargo run --target host-tuple --bin test-proto --no-default-features -F std,uart
name = "test-proto"
path = "./src/test-proto.rs"
required-features = ["std", "uart", "cam"]
[features] [features]
default = ["esp", "spat", "gnss_ubx", "screen"] default = ["esp", "spat", "gnss", "screen"]
# build for ESP (needed to build test for `host-tuple`) # build for ESP (needed to build test for `host-tuple`)
esp = [ esp = [
@ -30,15 +36,11 @@ esp = [
"dep:esp-backtrace", "dep:esp-backtrace",
"dep:esp-println", "dep:esp-println",
"dep:esp-radio", "dep:esp-radio",
"esp32-cits-core/esp32c5",
"esp32-cits-core/log-04",
] ]
std = ["c-its-parser/std", "esp32-cits-core/std"] std = ["c-its-parser/std"]
# enable u-blox GNSS module
gnss_ubx = ["_gnss", "_uart", "dep:ublox"]
# enable L67K GNSS module # enable L67K GNSS module
gnss_l67k = ["_gnss", "dep:embassy_gps"] gnss = ["_gnss", "dep:embassy_gps"]
# echo CAM data on serial # echo CAM data on serial
cam = ["c-its-parser/cam"] cam = ["c-its-parser/cam"]
# send CAM autonomously # send CAM autonomously
@ -95,7 +97,6 @@ esp-radio = { version = "0.18.0", optional = true, features = [
] } ] }
embassy_gps = { version = "0.1.0", optional = true, default-features = false, features = ["esp", "log-04"], git = "https://github.com/jbeyerstedt/embassy_gps.git", branch = "feat/rework-gpsfix" } embassy_gps = { version = "0.1.0", optional = true, default-features = false, features = ["esp", "log-04"], git = "https://github.com/jbeyerstedt/embassy_gps.git", branch = "feat/rework-gpsfix" }
esp32-cits-core = { version = "0.1.0", default-features = false, git = "https://git.hamburg.ccc.de/jtbx/esp32-cits-core", branch = "main" }
embedded-hal-bus = { version = "0.3.0", optional = true } embedded-hal-bus = { version = "0.3.0", optional = true }
embedded-graphics = { version = "0.8.2", optional = true } embedded-graphics = { version = "0.8.2", optional = true }
# needs newer GN with removed bitvec dependency # needs newer GN with removed bitvec dependency
@ -112,7 +113,6 @@ mipidsi = { version = "0.10.0", optional = true }
num-traits = { version = "0.2.19", default-features = false, features = ["libm"] } num-traits = { version = "0.2.19", default-features = false, features = ["libm"] }
profont = { version = "0.7.0", optional = true } profont = { version = "0.7.0", optional = true }
prost = { version = "0.14", optional = true, default-features = false, features = ["derive"] } prost = { version = "0.14", optional = true, default-features = false, features = ["derive"] }
ublox = { version = "0.10", optional = true, default-features = false, features = ["alloc", "ubx_proto27"] }
[dev-dependencies] [dev-dependencies]
assert_float_eq = "1.2.0" assert_float_eq = "1.2.0"

View file

@ -5,16 +5,15 @@ Displays different C-ITS receive-only use cases for cheap.
## Hardware Requirements ## Hardware Requirements
The code is currently tailored to: The code is currently tailored to:
- Seeed XIAO ESP32-C5 (8MB PSRAM) - Seeed XIAO ESP32-C5 (8MB PSRAM)
- U-Blox M10Q GNSS (connected on GPIO12 (RX), GPIO11 (TX), and GPIO25 (reset)) - Seeed XIAO L67K (connected on GPIO12 (RX), GPIO11 (TX), GPIO1 (wakeup) and GPIO25 (reset))
or Seeed XIAO L67K (connected on GPIO12 (RX), GPIO11 (TX), GPIO1 (wakeup) and GPIO25 (reset)) only available with `gnss` feature (which is included in `spat` feature)
only available with `gnss_ubx` or `gnss_l67k` feature (which is included in `spat` feature)
- Some ST7789 172*320px screen (connected on GPIO8 (SCL/ SCK), GPIO10 (SDA/ MOSI), GPIO7 (reset), GPIO23 (DC), GPIO24 (CS), TODO GPIO0 (BL)) - Some ST7789 172*320px screen (connected on GPIO8 (SCL/ SCK), GPIO10 (SDA/ MOSI), GPIO7 (reset), GPIO23 (DC), GPIO24 (CS), TODO GPIO0 (BL))
only available with `screen` feature (which is included in `spat` feature) only available with `screen` feature (which is included in `spat` feature)
## Features ## Features
### GNSS ### GNSS
Enable u-blox or L67K GNSS module and in main application logic/ state. Enable L67K GNSS module and in main application logic/ state.
### SPAT ### SPAT
Takes the current position and heading from the GNSS receiver and displays the signal phases of the upcoming intersection. Takes the current position and heading from the GNSS receiver and displays the signal phases of the upcoming intersection.
@ -33,25 +32,6 @@ Just prints a line for each received CAM.
Generates and transmits a CAM when a GNSS update was received. Generates and transmits a CAM when a GNSS update was received.
Depends on the `gnss` feature. Depends on the `gnss` feature.
Note: In germany, using the 5875 to 5915 MHz frequency band is only permitted for C-ITS use cases which implement transmit power reduction measures with at least 30 dB reduction range (or equivalent measures) and have a maximum EIRP of 33 dBm.
This is based on EU regulation 2014/53/EU, so it applies to other EU countries in similar ways.
The relevant harmonized standard is ETSI EN 302 571 which mandates (excerpt of most relevant points):
- a maximum power spectral density of 23 dBm/Mhz EIRP
- transmit power control (TCP) in a range from 3 dBm up to the maximum output power of the equipment
* used for CEN DSRC and HDR DSRC interference mitigation by implementing measures from ETSI TS 102 792
+ Mode A or B: Output power less than 10 dBm EIRP and less than -65 or -45 dBm/MHz EIRP in CEN DSRC band -> **needs to be measured**
+ Mode C: Maximum transmission time of less than 1 ms and at least a few 100 ms between transmission (realistically) -> **can't be ensured with big messages**
+ Mode D: "any" realistically possible transmission time, but at least 500 ms between transmissions for ~5 ITS stations/ 1000ms for 10 ITS stations -> **safe to assume when we only send CAMs at 1 Hz**
+ But modes C and D also need to:
- use protected zone information from CAM messages
- use a protected zone database, e.g. from www.asecap-pzdb.com
* used for dynamic congestion control (DCC)
+ mandatory mechanism described in ETSI EN 302 571, chapter 4.2.10
This is **not** implemented in this application!
**So legally, you're not allowed to send with this.**
### UART ### UART
Enable UART I/O protocol according to [docs/uart-protocol.md](./docs/uart-protocol.md). Enable UART I/O protocol according to [docs/uart-protocol.md](./docs/uart-protocol.md).
@ -90,10 +70,10 @@ Additional features are available through feature flags:
- `cam_tx`: Enable CAM generation and transmission (needs time and position source) - `cam_tx`: Enable CAM generation and transmission (needs time and position source)
Officially supported feature combinations are: Officially supported feature combinations are:
- `esp,spat,gnss_ubx,screen` (default features, "standalone") - `esp,spat,gnss,screen` (default features)
- `esp,spat,gnss_ubx,screen,cam_tx` ("standalone with CAM") - `esp,spat,gnss,screen,cam_tx`
- `esp,spat,gnss_ubx,screen,cam_tx,cam,denm` - `esp,spat,gnss,screen,cam_tx,cam,denm`
- `esp,uart,cam,denm` ("smartphone companion") - `esp,uart,cam,denm`
Connect your ESP32-C5 and run: Connect your ESP32-C5 and run:

View file

@ -9,12 +9,12 @@ extended=${2:-false}
features_base=( features_base=(
uart,cam,denm uart,cam,denm
spat,gnss_ubx,screen spat,gnss,screen
spat,gnss_ubx,screen,cam_tx spat,gnss,screen,cam_tx
) )
features_more=( features_more=(
spat,gnss_ubx,screen,cam,denm spat,gnss,screen,cam,denm
spat,gnss_ubx,screen,cam_tx,cam,denm spat,gnss,screen,cam_tx,cam,denm
) )
features_test=( features_test=(
spat,cam,denm,uart spat,cam,denm,uart

Binary file not shown.

Before

Width:  |  Height:  |  Size: 145 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 142 KiB

File diff suppressed because it is too large Load diff

View file

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

View file

@ -1,744 +0,0 @@
{
"board": {
"3dviewports": [
{
"name": "ISO-back",
"ww": 1.0,
"wx": 23.768348693847656,
"wy": 1.82621169090271,
"wz": -13.010103225708008,
"xw": 0.0,
"xx": -0.6424655318260193,
"xy": 0.5901128053665161,
"xz": -0.4888812303543091,
"yw": 0.0,
"yx": 0.7663145065307617,
"yy": 0.49455612897872925,
"yz": -0.41009292006492615,
"zw": 0.0,
"zx": -0.00022190232994034886,
"zy": -0.638107419013977,
"zz": -0.7699472904205322
},
{
"name": "ISO-front",
"ww": 1.0,
"wx": 4.608643054962158,
"wy": 18.730253219604492,
"wz": -27.170215606689453,
"xw": 0.0,
"xx": 0.6427879333496094,
"xy": -0.5868241190910339,
"xz": 0.49240362644195557,
"yw": 0.0,
"yx": 0.7660443186759949,
"yy": 0.4924042820930481,
"yz": -0.4131760001182556,
"zw": 0.0,
"zx": -2.1759660739917308e-08,
"zy": 0.6427874565124512,
"zz": 0.7660446763038635
}
],
"design_settings": {
"defaults": {
"apply_defaults_to_fp_barcodes": false,
"apply_defaults_to_fp_dimensions": false,
"apply_defaults_to_fp_fields": false,
"apply_defaults_to_fp_shapes": false,
"apply_defaults_to_fp_text": false,
"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": 1.27
},
"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": {
"border_display_style": 2,
"border_hatch_pitch": 0.5,
"corner_radius": 0.0,
"corner_smoothing": 0,
"fill_mode": 0,
"hatch_gap": 1.5,
"hatch_orientation": 0.0,
"hatch_smoothing_level": 0,
"hatch_smoothing_value": 0.1,
"hatch_thickness": 1.0,
"min_clearance": 0.5,
"min_island_area": 10.0,
"min_thickness": 0.25,
"pad_connection": 1,
"remove_islands": 0,
"thermal_relief_gap": 0.5,
"thermal_relief_spoke_width": 0.5
}
},
"diff_pair_dimensions": [
{
"gap": 0.0,
"via_gap": 0.0,
"width": 0.0
}
],
"drc_exclusions": [],
"meta": {
"version": 2
},
"rule_severities": {
"annular_width": "error",
"clearance": "error",
"connection_width": "warning",
"copper_edge_clearance": "error",
"copper_sliver": "warning",
"courtyards_overlap": "error",
"creepage": "error",
"diff_pair_gap_out_of_range": "error",
"diff_pair_uncoupled_length_too_long": "error",
"drill_out_of_range": "error",
"duplicate_footprints": "warning",
"extra_footprint": "warning",
"footprint": "error",
"footprint_filters_mismatch": "ignore",
"footprint_symbol_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": "warning",
"silk_over_copper": "warning",
"silk_overlap": "warning",
"skew_out_of_range": "error",
"solder_mask_bridge": "error",
"starved_thermal": "error",
"text_height": "warning",
"text_on_edge_cuts": "error",
"text_thickness": "warning",
"through_hole_pad_without_hole": "error",
"too_many_vias": "error",
"track_angle": "error",
"track_dangling": "warning",
"track_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
],
"tuning_pattern_settings": {
"diff_pair_defaults": {
"corner_radius_percentage": 80,
"corner_style": 1,
"max_amplitude": 1.0,
"min_amplitude": 0.2,
"single_sided": false,
"spacing": 1.0
},
"diff_pair_skew_defaults": {
"corner_radius_percentage": 80,
"corner_style": 1,
"max_amplitude": 1.0,
"min_amplitude": 0.2,
"single_sided": false,
"spacing": 0.6
},
"single_track_defaults": {
"corner_radius_percentage": 80,
"corner_style": 1,
"max_amplitude": 1.0,
"min_amplitude": 0.2,
"single_sided": false,
"spacing": 0.6
}
},
"via_dimensions": [
{
"diameter": 0.0,
"drill": 0.0
}
],
"zones_allow_external_fillets": false
},
"ipc2581": {
"bom_rev": "",
"dist": "",
"distpn": "",
"internal_id": "",
"mfg": "",
"mpn": "",
"sch_revision": "v0.1"
},
"layer_pairs": [],
"layer_presets": [],
"viewports": []
},
"boards": [],
"component_class_settings": {
"assignments": [],
"meta": {
"version": 0
},
"sheet_component_classes": {
"enabled": false
}
},
"cvpcb": {
"equivalence_files": []
},
"erc": {
"erc_exclusions": [],
"meta": {
"version": 0
},
"pin_map": [
[
0,
0,
0,
0,
0,
0,
1,
0,
0,
0,
0,
2
],
[
0,
2,
0,
1,
0,
0,
1,
0,
2,
2,
2,
2
],
[
0,
0,
0,
0,
0,
0,
1,
0,
1,
0,
1,
2
],
[
0,
1,
0,
0,
0,
0,
1,
1,
2,
1,
1,
2
],
[
0,
0,
0,
0,
0,
0,
1,
0,
0,
0,
0,
2
],
[
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
2
],
[
1,
1,
1,
1,
1,
0,
1,
1,
1,
1,
1,
2
],
[
0,
0,
0,
1,
0,
0,
1,
0,
0,
0,
0,
2
],
[
0,
2,
1,
2,
0,
0,
1,
0,
2,
2,
2,
2
],
[
0,
2,
0,
1,
0,
0,
1,
0,
2,
0,
0,
2
],
[
0,
2,
1,
1,
0,
0,
1,
0,
2,
0,
0,
2
],
[
2,
2,
2,
2,
2,
2,
2,
2,
2,
2,
2,
2
]
],
"rule_severities": {
"bus_definition_conflict": "error",
"bus_entry_needed": "error",
"bus_to_bus_conflict": "error",
"bus_to_net_conflict": "error",
"different_unit_footprint": "error",
"different_unit_net": "error",
"duplicate_reference": "error",
"duplicate_sheet_names": "error",
"endpoint_off_grid": "warning",
"extra_units": "error",
"field_name_whitespace": "warning",
"footprint_filter": "ignore",
"footprint_link_issues": "warning",
"four_way_junction": "ignore",
"ground_pin_not_ground": "warning",
"hier_label_mismatch": "error",
"isolated_pin_label": "warning",
"label_dangling": "error",
"label_multiple_wires": "warning",
"lib_symbol_issues": "warning",
"lib_symbol_mismatch": "warning",
"missing_bidi_pin": "warning",
"missing_input_pin": "warning",
"missing_power_pin": "error",
"missing_unit": "warning",
"multiple_net_names": "warning",
"net_not_bus_member": "warning",
"no_connect_connected": "warning",
"no_connect_dangling": "warning",
"pin_not_connected": "error",
"pin_not_driven": "error",
"pin_to_pin": "warning",
"power_pin_not_driven": "error",
"same_local_global_label": "warning",
"similar_label_and_power": "warning",
"similar_labels": "warning",
"similar_power": "warning",
"simulation_model_issue": "ignore",
"single_global_label": "ignore",
"stacked_pin_name": "warning",
"unannotated": "error",
"unconnected_wire_endpoint": "warning",
"undefined_netclass": "error",
"unit_value_mismatch": "error",
"unresolved_variable": "error",
"wire_dangling": "error"
}
},
"libraries": {
"pinned_footprint_libs": [],
"pinned_symbol_libs": []
},
"meta": {
"filename": "c_its-companion.kicad_pro",
"version": 3
},
"net_settings": {
"classes": [
{
"bus_width": 12,
"clearance": 0.2,
"diff_pair_gap": 0.25,
"diff_pair_via_gap": 0.25,
"diff_pair_width": 0.2,
"line_style": 0,
"microvia_diameter": 0.3,
"microvia_drill": 0.1,
"name": "Default",
"pcb_color": "rgba(0, 0, 0, 0.000)",
"priority": 2147483647,
"schematic_color": "rgba(0, 0, 0, 0.000)",
"track_width": 0.2,
"tuning_profile": "",
"via_diameter": 0.6,
"via_drill": 0.3,
"wire_width": 6
},
{
"name": "Power",
"pcb_color": "rgba(0, 0, 0, 0.000)",
"priority": 0,
"schematic_color": "rgba(0, 0, 0, 0.000)",
"track_width": 0.5,
"tuning_profile": ""
}
],
"meta": {
"version": 5
},
"net_colors": null,
"netclass_assignments": null,
"netclass_patterns": [
{
"netclass": "Power",
"pattern": "GND"
},
{
"netclass": "Power",
"pattern": "+3V3"
},
{
"netclass": "Power",
"pattern": "/BAT*"
},
{
"netclass": "Power",
"pattern": "/V_*"
}
]
},
"pcbnew": {
"last_paths": {
"idf": "",
"netlist": "",
"plot": "",
"specctra_dsn": "",
"vrml": ""
},
"page_layout_descr_file": ""
},
"schematic": {
"annotate_start_num": 0,
"annotation": {
"method": 0,
"sort_order": 0
},
"bom_export_filename": "${PROJECTNAME}.csv",
"bom_fmt_presets": [],
"bom_fmt_settings": {
"field_delimiter": ",",
"keep_line_breaks": false,
"keep_tabs": false,
"name": "CSV",
"ref_delimiter": ",",
"ref_range_delimiter": "",
"string_delimiter": "\""
},
"bom_presets": [],
"bom_settings": {
"exclude_dnp": false,
"fields_ordered": [
{
"group_by": false,
"label": "Reference",
"name": "Reference",
"show": true
},
{
"group_by": false,
"label": "Qty",
"name": "${QUANTITY}",
"show": true
},
{
"group_by": true,
"label": "Value",
"name": "Value",
"show": true
},
{
"group_by": true,
"label": "DNP",
"name": "${DNP}",
"show": true
},
{
"group_by": true,
"label": "Exclude from BOM",
"name": "${EXCLUDE_FROM_BOM}",
"show": true
},
{
"group_by": true,
"label": "Exclude from Board",
"name": "${EXCLUDE_FROM_BOARD}",
"show": true
},
{
"group_by": true,
"label": "Footprint",
"name": "Footprint",
"show": true
},
{
"group_by": false,
"label": "Datasheet",
"name": "Datasheet",
"show": true
}
],
"filter_string": "",
"group_symbols": true,
"include_excluded_from_bom": true,
"name": "Default Editing",
"sort_asc": true,
"sort_field": "Reference"
},
"bus_aliases": {},
"connection_grid_size": 50.0,
"drawing": {
"dashed_lines_dash_length_ratio": 12.0,
"dashed_lines_gap_length_ratio": 3.0,
"default_line_thickness": 6.0,
"default_text_size": 50.0,
"field_names": [],
"hop_over_size_choice": 0,
"intersheets_ref_own_page": false,
"intersheets_ref_prefix": "",
"intersheets_ref_short": false,
"intersheets_ref_show": false,
"intersheets_ref_suffix": "",
"junction_size_choice": 3,
"label_size_ratio": 0.375,
"operating_point_overlay_i_precision": 3,
"operating_point_overlay_i_range": "~A",
"operating_point_overlay_v_precision": 3,
"operating_point_overlay_v_range": "~V",
"overbar_offset_ratio": 1.23,
"pin_symbol_size": 25.0,
"text_offset_ratio": 0.15
},
"legacy_lib_dir": "",
"legacy_lib_list": [],
"meta": {
"version": 1
},
"page_layout_descr_file": "",
"plot_directory": "",
"reuse_designators": true,
"subpart_first_id": 65,
"subpart_id_separator": 0,
"top_level_sheets": [
{
"filename": "c_its-companion.kicad_sch",
"name": "Root",
"uuid": "9ad468b0-d7e4-4d4a-b652-c4a81a952e30"
}
],
"used_designators": "C1,#PWR1-10,J1-2,SW1,TP1,U1-2,#FLG1-3",
"variants": []
},
"sheets": [
[
"9ad468b0-d7e4-4d4a-b652-c4a81a952e30",
"Root"
]
],
"text_variables": {},
"tuning_profiles": {
"meta": {
"version": 0
},
"tuning_profiles_impedance_geometric": []
}
}

File diff suppressed because it is too large Load diff

View file

@ -1 +0,0 @@
{"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, "BACKUP_OPT": true}

View file

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

View file

@ -1,542 +0,0 @@
(footprint "XIAO-ESP32-C5-DIP"
(version 20241229)
(generator "pcbnew")
(generator_version "9.0")
(layer "F.Cu")
(property "Reference" "REF**"
(at -0.275 -10.275 0)
(unlocked yes)
(layer "F.SilkS")
(uuid "7bab359d-5e5b-4dff-9db4-e7c8562ce635")
(effects
(font
(size 0.635 0.635)
(thickness 0.1016)
)
)
)
(property "Value" "XIAO-ESP32-C5-DIP"
(at 0 0 0)
(unlocked yes)
(layer "F.Fab")
(uuid "79cabf23-4ebc-4d37-9172-bb2cf4ea975f")
(effects
(font
(size 0.635 0.635)
(thickness 0.1016)
)
)
)
(property "Datasheet" ""
(at 0 0 0)
(unlocked yes)
(layer "F.Fab")
(hide yes)
(uuid "663dc686-104e-47e1-9669-e1228477fa6f")
(effects
(font
(size 1.27 1.27)
(thickness 0.15)
)
)
)
(property "Description" ""
(at 0 0 0)
(unlocked yes)
(layer "F.Fab")
(hide yes)
(uuid "a25b2a4a-94ce-41bd-8341-b98204aa9142")
(effects
(font
(size 1.27 1.27)
(thickness 0.15)
)
)
)
(attr smd)
(fp_line
(start -10.414 -6.985)
(end -10.414 6.985)
(stroke
(width 0.127)
(type solid)
)
(layer "F.SilkS")
(uuid "e213e2f5-659f-4cdb-b390-b284746621aa")
)
(fp_line
(start -8.509 -8.89)
(end 8.636 -8.89)
(stroke
(width 0.127)
(type solid)
)
(layer "F.SilkS")
(uuid "f58afa0e-63f9-4096-9d17-7cf8e5de7759")
)
(fp_line
(start -8.509 8.89)
(end 8.636 8.89)
(stroke
(width 0.127)
(type solid)
)
(layer "F.SilkS")
(uuid "9c6409b2-5f1b-4299-85bf-d07c96f13a2a")
)
(fp_line
(start 10.541 -4.495)
(end 11.551272 -4.491272)
(stroke
(width 0.127)
(type solid)
)
(layer "F.SilkS")
(uuid "4c45fdff-98df-401f-bf6d-768af24b67a9")
)
(fp_line
(start 10.541 6.985)
(end 10.541 -6.985)
(stroke
(width 0.1)
(type solid)
)
(layer "F.SilkS")
(uuid "eb7ba2c6-3729-4940-9c6b-4ac369cf3075")
)
(fp_line
(start 10.541 6.985)
(end 10.541 -6.985)
(stroke
(width 0.127)
(type solid)
)
(layer "F.SilkS")
(uuid "6f680559-bbef-46de-9ef5-efda7c2411e9")
)
(fp_line
(start 11.551 4.504)
(end 10.541 4.504)
(stroke
(width 0.127)
(type solid)
)
(layer "F.SilkS")
(uuid "eddbec16-11c2-42ad-90d6-191cfc991c10")
)
(fp_line
(start 12.051 -3.991272)
(end 12.051 4.004)
(stroke
(width 0.127)
(type solid)
)
(layer "F.SilkS")
(uuid "e973f8d1-81df-437d-bffe-4f734e1ac18e")
)
(fp_arc
(start -10.414 -6.985)
(mid -9.856038 -8.332038)
(end -8.509 -8.89)
(stroke
(width 0.127)
(type solid)
)
(layer "F.SilkS")
(uuid "88e5d6ea-6b70-4830-9817-980fd2b32f79")
)
(fp_arc
(start -8.509 8.89)
(mid -9.856038 8.332038)
(end -10.414 6.985)
(stroke
(width 0.127)
(type solid)
)
(layer "F.SilkS")
(uuid "f108f05c-d5f6-4de1-bc6f-4c31ef53e967")
)
(fp_arc
(start 8.636 -8.89)
(mid 9.983038 -8.332038)
(end 10.541 -6.985)
(stroke
(width 0.127)
(type solid)
)
(layer "F.SilkS")
(uuid "c6870bc4-897a-485a-b951-3feda1bdd32e")
)
(fp_arc
(start 10.541 6.985)
(mid 9.983038 8.332038)
(end 8.636 8.89)
(stroke
(width 0.127)
(type solid)
)
(layer "F.SilkS")
(uuid "e1d8f660-1495-43db-8a6b-7fdc7dc42e35")
)
(fp_arc
(start 11.551272 -4.491272)
(mid 11.904644 -4.344724)
(end 12.051 -3.991272)
(stroke
(width 0.127)
(type default)
)
(layer "F.SilkS")
(uuid "a9b2ba2a-ad25-4191-9070-ebc0333a9101")
)
(fp_arc
(start 12.051 4.004)
(mid 11.904553 4.357553)
(end 11.551 4.504)
(stroke
(width 0.127)
(type default)
)
(layer "F.SilkS")
(uuid "45e349ac-e285-4bed-a05b-ae5a80336af0")
)
(fp_circle
(center 9.37 -7.857)
(end 9.624 -7.857)
(stroke
(width 0.5)
(type solid)
)
(fill yes)
(layer "F.SilkS")
(uuid "6839324b-c75a-49ba-8c0c-16e2191166eb")
)
(fp_circle
(center 10.25 -9)
(end 10.504 -9)
(stroke
(width 0.5)
(type solid)
)
(fill yes)
(layer "F.SilkS")
(uuid "6e5f4857-2ad9-4493-8234-6091a6821232")
)
(fp_rect
(start -10.425 -8.9)
(end 10.55 8.9)
(stroke
(width 0.05)
(type default)
)
(fill no)
(layer "F.CrtYd")
(uuid "b53697bc-d105-40e2-af66-708b16a7f26c")
)
(fp_rect
(start -10.425 -8.9)
(end 10.55 8.9)
(stroke
(width 0.1)
(type default)
)
(fill no)
(layer "F.Fab")
(uuid "a45a327e-f036-4877-8d89-39f4f4b0c715")
)
(fp_circle
(center 9.376 -7.854)
(end 9.63 -7.854)
(stroke
(width 0.5)
(type solid)
)
(fill yes)
(layer "F.Fab")
(uuid "12a1812e-f866-498e-9d6e-50e427087f34")
)
(pad "1" smd roundrect
(at 7.62 -8.455 90)
(size 2.432 1.524)
(layers "F.Cu" "F.Mask")
(roundrect_rratio 0.1)
(thermal_bridge_angle 45)
(uuid "82bbfb55-8baa-4218-ac89-ab07d82ef7f8")
)
(pad "1" thru_hole circle
(at 7.62 -7.62 90)
(size 1.524 1.524)
(drill 0.889)
(layers "*.Cu" "*.Mask")
(remove_unused_layers no)
(uuid "f2fffd4f-460c-4b4d-b954-811e935ed1d6")
)
(pad "2" smd roundrect
(at 5.08 -8.455 90)
(size 2.432 1.524)
(layers "F.Cu" "F.Mask")
(roundrect_rratio 0.1)
(thermal_bridge_angle 45)
(uuid "18ef9914-2d15-494c-9020-2334abcc1460")
)
(pad "2" thru_hole circle
(at 5.08 -7.62 90)
(size 1.524 1.524)
(drill 0.889)
(layers "*.Cu" "*.Mask")
(remove_unused_layers no)
(uuid "4732cc3f-e641-4870-a9b3-ae13729c7bba")
)
(pad "3" smd roundrect
(at 2.54 -8.455 90)
(size 2.432 1.524)
(layers "F.Cu" "F.Mask")
(roundrect_rratio 0.1)
(thermal_bridge_angle 45)
(uuid "15338a55-8514-4218-a2c1-2cae003104c3")
)
(pad "3" thru_hole circle
(at 2.54 -7.62 90)
(size 1.524 1.524)
(drill 0.889)
(layers "*.Cu" "*.Mask")
(remove_unused_layers no)
(uuid "0cef5e55-d8ff-4394-9cc1-cc318608b0d9")
)
(pad "4" smd roundrect
(at 0 -8.455 90)
(size 2.432 1.524)
(layers "F.Cu" "F.Mask")
(roundrect_rratio 0.1)
(thermal_bridge_angle 45)
(uuid "191a259d-5773-4a80-91de-baa69611fab8")
)
(pad "4" thru_hole circle
(at 0 -7.62 90)
(size 1.524 1.524)
(drill 0.889)
(layers "*.Cu" "*.Mask")
(remove_unused_layers no)
(uuid "200b8da4-9395-48a5-b7ac-92a6a147c215")
)
(pad "5" smd roundrect
(at -2.54 -8.455 90)
(size 2.432 1.524)
(layers "F.Cu" "F.Mask")
(roundrect_rratio 0.1)
(thermal_bridge_angle 45)
(uuid "52012467-1c46-4a8e-aa82-6f6ce69bd2ca")
)
(pad "5" thru_hole circle
(at -2.54 -7.62 90)
(size 1.524 1.524)
(drill 0.889)
(layers "*.Cu" "*.Mask")
(remove_unused_layers no)
(uuid "2b50ccce-3634-47d1-bf7d-a3db4f3f9a9f")
)
(pad "6" smd roundrect
(at -5.08 -8.455 90)
(size 2.432 1.524)
(layers "F.Cu" "F.Mask")
(roundrect_rratio 0.1)
(thermal_bridge_angle 45)
(uuid "04aeaf95-142a-40f6-97a3-04453fc50824")
)
(pad "6" thru_hole circle
(at -5.08 -7.62 90)
(size 1.524 1.524)
(drill 0.889)
(layers "*.Cu" "*.Mask")
(remove_unused_layers no)
(uuid "7636de9d-d8e5-49c9-9fff-0cd76515690d")
)
(pad "7" smd roundrect
(at -7.62 -8.455 90)
(size 2.432 1.524)
(layers "F.Cu" "F.Mask")
(roundrect_rratio 0.1)
(thermal_bridge_angle 45)
(uuid "e997fd0b-233a-4656-afad-cb2f8b9d87c9")
)
(pad "7" thru_hole circle
(at -7.62 -7.62 90)
(size 1.524 1.524)
(drill 0.889)
(layers "*.Cu" "*.Mask")
(remove_unused_layers no)
(uuid "9d2c0d0c-05dd-477e-8384-b14eec83fc2e")
)
(pad "8" thru_hole circle
(at -7.62 7.62 270)
(size 1.524 1.524)
(drill 0.889)
(layers "*.Cu" "*.Mask")
(remove_unused_layers no)
(uuid "1e50c66f-49b8-4b03-9889-8cc6ca45ad8e")
)
(pad "8" smd roundrect
(at -7.62 8.455 270)
(size 2.432 1.524)
(layers "F.Cu" "F.Mask")
(roundrect_rratio 0.1)
(thermal_bridge_angle 45)
(uuid "e7b72803-b5f0-433c-8313-83247355fb43")
)
(pad "9" thru_hole circle
(at -5.08 7.62 270)
(size 1.524 1.524)
(drill 0.889)
(layers "*.Cu" "*.Mask")
(remove_unused_layers no)
(uuid "808bf9dc-1c26-4a77-93c9-e4c6d5eccbb2")
)
(pad "9" smd roundrect
(at -5.08 8.455 270)
(size 2.432 1.524)
(layers "F.Cu" "F.Mask")
(roundrect_rratio 0.1)
(thermal_bridge_angle 45)
(uuid "532de3b6-bd30-47c5-b70e-0bca6abbd997")
)
(pad "10" thru_hole circle
(at -2.54 7.62 270)
(size 1.524 1.524)
(drill 0.889)
(layers "*.Cu" "*.Mask")
(remove_unused_layers no)
(uuid "a792a2ac-537b-4136-8f92-b81f7ce8f753")
)
(pad "10" smd roundrect
(at -2.54 8.455 270)
(size 2.432 1.524)
(layers "F.Cu" "F.Mask")
(roundrect_rratio 0.1)
(thermal_bridge_angle 45)
(uuid "6e3f5c75-6143-468e-bb07-d89020c03f59")
)
(pad "11" thru_hole circle
(at 0 7.62 270)
(size 1.524 1.524)
(drill 0.889)
(layers "*.Cu" "*.Mask")
(remove_unused_layers no)
(uuid "fb78046a-9b91-4893-b010-827957cc07be")
)
(pad "11" smd roundrect
(at 0 8.455 270)
(size 2.432 1.524)
(layers "F.Cu" "F.Mask")
(roundrect_rratio 0.1)
(thermal_bridge_angle 45)
(uuid "5249f425-bece-432d-8076-28e1b8b386e3")
)
(pad "12" thru_hole circle
(at 2.54 7.62 270)
(size 1.524 1.524)
(drill 0.889)
(layers "*.Cu" "*.Mask")
(remove_unused_layers no)
(uuid "0b8ccc97-5100-4dcf-8500-e99789ee0b4c")
)
(pad "12" smd roundrect
(at 2.54 8.455 270)
(size 2.432 1.524)
(layers "F.Cu" "F.Mask")
(roundrect_rratio 0.1)
(thermal_bridge_angle 45)
(uuid "82f1bbd1-708b-4d2d-9113-7ba3c2313670")
)
(pad "13" thru_hole circle
(at 5.08 7.62 270)
(size 1.524 1.524)
(drill 0.889)
(layers "*.Cu" "*.Mask")
(remove_unused_layers no)
(uuid "03882061-01af-484b-b620-9e92a3daaf68")
)
(pad "13" smd roundrect
(at 5.08 8.455 270)
(size 2.432 1.524)
(layers "F.Cu" "F.Mask")
(roundrect_rratio 0.1)
(thermal_bridge_angle 45)
(uuid "954f51d1-3d75-42fb-a02a-7389b30718d9")
)
(pad "14" thru_hole circle
(at 7.62 7.62 270)
(size 1.524 1.524)
(drill 0.889)
(layers "*.Cu" "*.Mask")
(remove_unused_layers no)
(uuid "c6d1af47-da13-4409-9ab1-55d358cc57eb")
)
(pad "14" smd roundrect
(at 7.62 8.455 270)
(size 2.432 1.524)
(layers "F.Cu" "F.Mask")
(roundrect_rratio 0.1)
(thermal_bridge_angle 45)
(uuid "6b725fd4-d6f0-46bf-b3e0-7ec07cc05622")
)
(embedded_fonts no)
(model "${AMZPATH}/3dmodels/AMz3D.3dshapes/XIAO-nRF52840 v15.step"
(hide yes)
(offset
(xyz -1.778 -6.096 0.381)
)
(scale
(xyz 1 1 1)
)
(rotate
(xyz -90 0 0)
)
)
(model "${AMZPATH}/3dmodels/AMz3D.3dshapes/Seeeduino XIAO SAMD21 v4.step"
(offset
(xyz 0 0 0)
)
(scale
(xyz 1 1 1)
)
(rotate
(xyz 0 0 0)
)
)
(model "${AMZPATH}/3dmodels/AMz3D.3dshapes/Seeed Studio XIAO RP2040 v26.step"
(hide yes)
(offset
(xyz 0 0 0)
)
(scale
(xyz 1 1 1)
)
(rotate
(xyz 0 0 90)
)
)
(model "${AMZPATH}/3dmodels/AMz3D.3dshapes/Seeed Studio XIAO-ESP32-C3.step"
(hide yes)
(offset
(xyz -1.7018 -6.096 0)
)
(scale
(xyz 1 1 1)
)
(rotate
(xyz -90 0 0)
)
)
(model "${AMZPATH}/3dmodels/AMz3D.3dshapes/XIAO-ESP32S3 v2.step"
(hide yes)
(offset
(xyz -1.778 -6.1722 0)
)
(scale
(xyz 1 1 1)
)
(rotate
(xyz -90 0 0)
)
)
)

View file

@ -1,558 +0,0 @@
(footprint "XIAO-ESP32-C5-SMD-DIP"
(version 20260206)
(generator "pcbnew")
(generator_version "10.0")
(layer "F.Cu")
(property "Reference" "REF**"
(at 7.312 -21.336 0)
(layer "F.SilkS")
(uuid "95a6896c-2eab-4f28-adc5-7fd64550efd1")
(effects
(font
(size 0.78232 0.78232)
(thickness 0.10668)
)
(justify left bottom)
)
)
(property "Value" "Val**"
(at 8.89 -10.795 0)
(layer "F.Fab")
(uuid "ead571aa-8f25-4413-9dd8-3ecea3d8b1b5")
(effects
(font
(size 0.536448 0.536448)
(thickness 0.073152)
)
)
)
(property "Datasheet" ""
(at 0 0 0)
(unlocked yes)
(layer "F.Fab")
(hide yes)
(uuid "e0e614f2-f99b-42a7-87d5-6d40f74c047a")
(effects
(font
(size 1.27 1.27)
(thickness 0.15)
)
)
)
(property "Description" ""
(at 0 0 0)
(unlocked yes)
(layer "F.Fab")
(hide yes)
(uuid "074d8848-2ba7-4146-8c49-a0cf65e2aa82")
(effects
(font
(size 1.27 1.27)
(thickness 0.15)
)
)
)
(duplicate_pad_numbers_are_jumpers no)
(fp_line
(start 0 -1.987)
(end 0 -19.132)
(stroke
(width 0.1)
(type solid)
)
(layer "F.SilkS")
(uuid "e72c3c07-5bc8-4f5f-97cd-36412308e250")
)
(fp_line
(start 1.905 -0.082)
(end 15.875 -0.082)
(stroke
(width 0.1)
(type solid)
)
(layer "F.SilkS")
(uuid "e3e0ab25-f18a-4068-92e8-92a17b4caf05")
)
(fp_line
(start 4.595 -21.037)
(end 4.598728 -22.047272)
(stroke
(width 0.127)
(type solid)
)
(layer "F.SilkS")
(uuid "4838598f-afa6-445e-b237-7544ae7e9968")
)
(fp_line
(start 5.098728 -22.547)
(end 13.094 -22.547)
(stroke
(width 0.127)
(type solid)
)
(layer "F.SilkS")
(uuid "101b5417-f570-447c-b9f6-42639e5eff3c")
)
(fp_line
(start 13.594 -22.047)
(end 13.594 -21.037)
(stroke
(width 0.127)
(type solid)
)
(layer "F.SilkS")
(uuid "37ee6532-c85a-4899-82db-921e02cce4af")
)
(fp_line
(start 15.875 -21.037)
(end 1.905 -21.037)
(stroke
(width 0.1)
(type solid)
)
(layer "F.SilkS")
(uuid "f94b2798-d11f-47d7-8f2c-02478417b6f1")
)
(fp_line
(start 17.78 -1.987)
(end 17.78 -19.132)
(stroke
(width 0.1)
(type solid)
)
(layer "F.SilkS")
(uuid "8c3282cc-86c2-4cf3-a698-856916df5f40")
)
(fp_arc
(start 0 -19.136)
(mid 0.557962 -20.483038)
(end 1.905 -21.041)
(stroke
(width 0.1)
(type solid)
)
(layer "F.SilkS")
(uuid "2a1b788b-cdcf-4a49-acac-d187cf344ffc")
)
(fp_arc
(start 1.905 -0.082)
(mid 0.557962 -0.639962)
(end 0 -1.987)
(stroke
(width 0.1)
(type solid)
)
(layer "F.SilkS")
(uuid "677c2a8e-0212-4651-99b4-af42d29d0bd1")
)
(fp_arc
(start 4.598728 -22.047272)
(mid 4.745276 -22.400644)
(end 5.098728 -22.547)
(stroke
(width 0.127)
(type default)
)
(layer "F.SilkS")
(uuid "b38fc005-51b4-4609-b7e3-78320e82da0d")
)
(fp_arc
(start 13.094 -22.547)
(mid 13.447553 -22.400553)
(end 13.594 -22.047)
(stroke
(width 0.127)
(type default)
)
(layer "F.SilkS")
(uuid "46a43512-01fe-440e-b1e8-31a50057cbf8")
)
(fp_arc
(start 15.875 -21.037)
(mid 17.222038 -20.479038)
(end 17.78 -19.132)
(stroke
(width 0.1)
(type solid)
)
(layer "F.SilkS")
(uuid "f77dffb2-d331-43db-9018-069e9fa47c57")
)
(fp_arc
(start 17.78 -1.987)
(mid 17.222038 -0.639962)
(end 15.875 -0.082)
(stroke
(width 0.1)
(type solid)
)
(layer "F.SilkS")
(uuid "535b0a70-da0d-4557-a5dd-d25a51ee5468")
)
(fp_circle
(center -0.21 -20.621)
(end -0.21 -20.875)
(stroke
(width 0.5)
(type solid)
)
(fill yes)
(layer "F.SilkS")
(uuid "c4f8c64e-4c5d-41c1-8b14-aca8fcf77968")
)
(fp_circle
(center 1.064 -19.904)
(end 1.064 -20.158)
(stroke
(width 0.5)
(type solid)
)
(fill yes)
(layer "F.SilkS")
(uuid "163ea809-1cd6-43c9-968e-14c093715603")
)
(fp_rect
(start 17.8 0)
(end 0 -21)
(stroke
(width 0.05)
(type default)
)
(fill no)
(layer "F.CrtYd")
(uuid "477d102d-f2d0-4f67-ad93-9f60da51bd76")
)
(fp_rect
(start 0 -21)
(end 17.8 0)
(stroke
(width 0.1)
(type default)
)
(fill no)
(layer "F.Fab")
(uuid "e7107e2f-485b-49f6-9f5d-2537ab0ef458")
)
(fp_circle
(center 1.084 -19.9)
(end 1.084 -20.154)
(stroke
(width 0.5)
(type solid)
)
(fill yes)
(layer "F.Fab")
(uuid "089122fe-193e-4cef-a9b8-54d37cb7fa99")
)
(pad "1" smd roundrect
(at 0.835 -18.12)
(size 2.75 2)
(layers "F.Cu" "F.Mask" "F.Paste")
(roundrect_rratio 0.25)
(solder_mask_margin 0.0508)
(thermal_bridge_angle 45)
(uuid "351f5f05-8bc3-4b53-9daf-3207bd71876b")
)
(pad "1" thru_hole circle
(at 1.26 -18.14 180)
(size 1.524 1.524)
(drill 0.889)
(layers "*.Cu" "*.Mask")
(remove_unused_layers no)
(uuid "6a16c3b2-8d4e-4efe-a84e-dbc418c7fe79")
)
(pad "2" smd roundrect
(at 0.835 -15.58)
(size 2.75 2)
(layers "F.Cu" "F.Mask" "F.Paste")
(roundrect_rratio 0.25)
(solder_mask_margin 0.0508)
(thermal_bridge_angle 45)
(uuid "cfdee4aa-d600-484f-9a5c-2062a00bde1d")
)
(pad "2" thru_hole circle
(at 1.26 -15.6 180)
(size 1.524 1.524)
(drill 0.889)
(layers "*.Cu" "*.Mask")
(remove_unused_layers no)
(uuid "69193d02-4e31-4519-a90d-949c3f48e92c")
)
(pad "3" smd roundrect
(at 0.835 -13.04)
(size 2.75 2)
(layers "F.Cu" "F.Mask" "F.Paste")
(roundrect_rratio 0.25)
(solder_mask_margin 0.0508)
(thermal_bridge_angle 45)
(uuid "52a69e1c-edab-4079-a491-6bcb12907f61")
)
(pad "3" thru_hole circle
(at 1.26 -13.06 180)
(size 1.524 1.524)
(drill 0.889)
(layers "*.Cu" "*.Mask")
(remove_unused_layers no)
(uuid "a3aed707-7e8b-4b1b-ba96-06cfb9154fcb")
)
(pad "4" smd roundrect
(at 0.835 -10.5)
(size 2.75 2)
(layers "F.Cu" "F.Mask" "F.Paste")
(roundrect_rratio 0.25)
(solder_mask_margin 0.0508)
(thermal_bridge_angle 45)
(uuid "1e864c46-432c-460c-af69-13a1c9d0fd2f")
)
(pad "4" thru_hole circle
(at 1.26 -10.52 180)
(size 1.524 1.524)
(drill 0.889)
(layers "*.Cu" "*.Mask")
(remove_unused_layers no)
(uuid "eee65312-1942-42a1-a13a-2a28a114ec95")
)
(pad "5" smd roundrect
(at 0.835 -7.96)
(size 2.75 2)
(layers "F.Cu" "F.Mask" "F.Paste")
(roundrect_rratio 0.25)
(solder_mask_margin 0.0508)
(thermal_bridge_angle 45)
(uuid "da854e17-c3b3-4a83-9d47-a379e4d7efa5")
)
(pad "5" thru_hole circle
(at 1.26 -7.98 180)
(size 1.524 1.524)
(drill 0.889)
(layers "*.Cu" "*.Mask")
(remove_unused_layers no)
(uuid "febdd29f-f361-4b8e-b2b5-7502d3e5fdea")
)
(pad "6" smd roundrect
(at 0.835 -5.42)
(size 2.75 2)
(layers "F.Cu" "F.Mask" "F.Paste")
(roundrect_rratio 0.25)
(solder_mask_margin 0.0508)
(thermal_bridge_angle 45)
(uuid "3fde7f90-983d-44c1-939e-47d36175373f")
)
(pad "6" thru_hole circle
(at 1.26 -5.44 180)
(size 1.524 1.524)
(drill 0.889)
(layers "*.Cu" "*.Mask")
(remove_unused_layers no)
(uuid "323ef0a5-3407-4ac3-b76f-8ac19e044d1e")
)
(pad "7" smd roundrect
(at 0.835 -2.88)
(size 2.75 2)
(layers "F.Cu" "F.Mask" "F.Paste")
(roundrect_rratio 0.25)
(solder_mask_margin 0.0508)
(thermal_bridge_angle 45)
(uuid "ebfd6bef-ea17-4635-877b-58b57c784dec")
)
(pad "7" thru_hole circle
(at 1.26 -2.9 180)
(size 1.524 1.524)
(drill 0.889)
(layers "*.Cu" "*.Mask")
(remove_unused_layers no)
(uuid "2b179783-65b0-4d7c-850b-74c9fa3a7a64")
)
(pad "8" thru_hole circle
(at 16.5 -2.9)
(size 1.524 1.524)
(drill 0.889)
(layers "*.Cu" "*.Mask")
(remove_unused_layers no)
(uuid "5cbc8aa6-fdfd-49e6-b018-da1d4ff342aa")
)
(pad "8" smd roundrect
(at 17 -2.88)
(size 2.75 2)
(layers "F.Cu" "F.Mask" "F.Paste")
(roundrect_rratio 0.25)
(solder_mask_margin 0.0508)
(thermal_bridge_angle 45)
(uuid "19eb14d0-5713-47b7-86f2-4bccede8ba54")
)
(pad "9" thru_hole circle
(at 16.5 -5.44)
(size 1.524 1.524)
(drill 0.889)
(layers "*.Cu" "*.Mask")
(remove_unused_layers no)
(uuid "e7f197b5-1fc8-47f6-9f49-60ffd6fa2335")
)
(pad "9" smd roundrect
(at 17 -5.42)
(size 2.75 2)
(layers "F.Cu" "F.Mask" "F.Paste")
(roundrect_rratio 0.25)
(solder_mask_margin 0.0508)
(thermal_bridge_angle 45)
(uuid "4c3b83bc-a000-4a70-887c-6b775bc40ee1")
)
(pad "10" thru_hole circle
(at 16.5 -7.98)
(size 1.524 1.524)
(drill 0.889)
(layers "*.Cu" "*.Mask")
(remove_unused_layers no)
(uuid "4df7fd17-71d5-4f8f-a3f2-68c6537a767f")
)
(pad "10" smd roundrect
(at 17 -7.96)
(size 2.75 2)
(layers "F.Cu" "F.Mask" "F.Paste")
(roundrect_rratio 0.25)
(solder_mask_margin 0.0508)
(thermal_bridge_angle 45)
(uuid "223d5c33-d2a3-4398-8d3f-544bd6345a86")
)
(pad "11" thru_hole circle
(at 16.5 -10.52)
(size 1.524 1.524)
(drill 0.889)
(layers "*.Cu" "*.Mask")
(remove_unused_layers no)
(uuid "dad67976-a604-485a-bb37-2d67ea771b2c")
)
(pad "11" smd roundrect
(at 17 -10.5)
(size 2.75 2)
(layers "F.Cu" "F.Mask" "F.Paste")
(roundrect_rratio 0.25)
(solder_mask_margin 0.0508)
(thermal_bridge_angle 45)
(uuid "1f04f1d2-ce82-4256-b122-36f63f189efe")
)
(pad "12" thru_hole circle
(at 16.5 -13.06)
(size 1.524 1.524)
(drill 0.889)
(layers "*.Cu" "*.Mask")
(remove_unused_layers no)
(uuid "c55d522b-9f8b-4fa4-afaa-880f3b1c9c5f")
)
(pad "12" smd roundrect
(at 17 -13.04)
(size 2.75 2)
(layers "F.Cu" "F.Mask" "F.Paste")
(roundrect_rratio 0.25)
(solder_mask_margin 0.0508)
(thermal_bridge_angle 45)
(uuid "31818d88-bbc0-4c22-b4ed-d6884ef71ebb")
)
(pad "13" thru_hole circle
(at 16.5 -15.6)
(size 1.524 1.524)
(drill 0.889)
(layers "*.Cu" "*.Mask")
(remove_unused_layers no)
(uuid "e46165bc-b289-44b0-b7f6-6759bba3d4b8")
)
(pad "13" smd roundrect
(at 17 -15.58)
(size 2.75 2)
(layers "F.Cu" "F.Mask" "F.Paste")
(roundrect_rratio 0.25)
(solder_mask_margin 0.0508)
(thermal_bridge_angle 45)
(uuid "3fbf02f8-81da-4fba-bc95-9e47b9abaa05")
)
(pad "14" thru_hole circle
(at 16.5 -18.14)
(size 1.524 1.524)
(drill 0.889)
(layers "*.Cu" "*.Mask")
(remove_unused_layers no)
(uuid "71ca3093-fd4e-46c3-9980-6d986d711719")
)
(pad "14" smd roundrect
(at 17 -18.12)
(size 2.75 2)
(layers "F.Cu" "F.Mask" "F.Paste")
(roundrect_rratio 0.25)
(solder_mask_margin 0.0508)
(thermal_bridge_angle 45)
(uuid "ce7a6ff3-5c3a-4077-bf86-beedc24debb3")
)
(pad "15" smd circle
(at 7.6166 -19.1415)
(size 1.7 1.7)
(layers "F.Cu" "F.Mask" "F.Paste")
(uuid "7273149b-9aea-4a23-9b4e-db1e0d6c76fc")
)
(pad "16" smd circle
(at 10.1566 -19.1415)
(size 1.7 1.7)
(layers "F.Cu" "F.Mask" "F.Paste")
(uuid "0aef11c3-14ad-4cb5-9b05-95a892988ce8")
)
(pad "17" smd circle
(at 7.6166 -16.6015)
(size 1.7 1.7)
(layers "F.Cu" "F.Mask" "F.Paste")
(uuid "158a5cbb-af86-4fa6-8d58-6831485085c5")
)
(pad "18" smd circle
(at 10.1566 -16.6015)
(size 1.7 1.7)
(layers "F.Cu" "F.Mask" "F.Paste")
(uuid "3b7bcd8a-c97c-46f6-9faa-4e525d03d6e8")
)
(pad "19" smd circle
(at 7.6166 -14.0615)
(size 1.7 1.7)
(layers "F.Cu" "F.Mask" "F.Paste")
(uuid "ad4ba963-211d-4878-a4cb-255a615c14de")
)
(pad "20" smd circle
(at 10.1566 -14.0615)
(size 1.7 1.7)
(layers "F.Cu" "F.Mask" "F.Paste")
(uuid "8c7e088d-0846-4d7d-9143-5ce3fdb0c4fd")
)
(pad "21" smd circle
(at 7.6166 -11.5215)
(size 1.7 1.7)
(layers "F.Cu" "F.Mask" "F.Paste")
(uuid "c6bbe198-c8c7-4b83-a36e-6a6fdeb3b94f")
)
(pad "22" smd circle
(at 10.064 -11.5783)
(size 1.7 1.7)
(layers "F.Cu" "F.Mask" "F.Paste")
(uuid "4f1e574b-3b71-4a23-b765-d779726fd3c6")
)
(pad "23" smd roundrect
(at 7.3086 -4.9683 270)
(size 2.5 1.1)
(layers "F.Cu" "F.Mask" "F.Paste")
(roundrect_rratio 0.25)
(thermal_bridge_angle 45)
(uuid "08d345bf-1b32-499f-94bb-44332eb5115d")
)
(pad "24" smd roundrect
(at 9.8486 -4.9683 270)
(size 2.5 1.1)
(layers "F.Cu" "F.Mask" "F.Paste")
(roundrect_rratio 0.25)
(thermal_bridge_angle 45)
(uuid "8b3ca1bb-eee0-4c3d-8f3b-eefea4a68bbb")
)
(pad "25" smd roundrect
(at 9.949 -8.412 270)
(size 3.1 1.9)
(layers "F.Cu" "F.Mask" "F.Paste")
(roundrect_rratio 0.25)
(thermal_bridge_angle 45)
(uuid "45570478-e499-45ab-bb97-539cabc4e177")
)
(embedded_fonts no)
)

View file

@ -1,445 +0,0 @@
(footprint "XIAO-ESP32-C5-SMD"
(version 20241229)
(generator "pcbnew")
(generator_version "9.0")
(layer "F.Cu")
(property "Reference" "REF**"
(at 7.312 -21.336 0)
(layer "F.SilkS")
(uuid "a9f5f5dd-597c-45dd-b31d-a35c3963eed2")
(effects
(font
(size 0.78232 0.78232)
(thickness 0.10668)
)
(justify left bottom)
)
)
(property "Value" "Val**"
(at 8.89 -10.795 0)
(layer "F.Fab")
(uuid "2d66aca7-e0b3-44df-838e-be285890792c")
(effects
(font
(size 0.536448 0.536448)
(thickness 0.073152)
)
)
)
(property "Datasheet" ""
(at 0 0 0)
(unlocked yes)
(layer "F.Fab")
(hide yes)
(uuid "5173b428-b87b-497c-9fd1-0d444c2ced85")
(effects
(font
(size 1.27 1.27)
(thickness 0.15)
)
)
)
(property "Description" ""
(at 0 0 0)
(unlocked yes)
(layer "F.Fab")
(hide yes)
(uuid "ea7181c4-fdca-44f1-ad15-d0627f50a68e")
(effects
(font
(size 1.27 1.27)
(thickness 0.15)
)
)
)
(fp_line
(start 0 -1.987)
(end 0 -19.132)
(stroke
(width 0.1)
(type solid)
)
(layer "F.SilkS")
(uuid "df0486b4-d04f-4201-a301-beede6e15862")
)
(fp_line
(start 1.905 -0.082)
(end 15.875 -0.082)
(stroke
(width 0.1)
(type solid)
)
(layer "F.SilkS")
(uuid "f54abd8d-62ed-4b05-8e37-f29acdadc0f8")
)
(fp_line
(start 4.595 -21.037)
(end 4.598728 -22.047272)
(stroke
(width 0.127)
(type solid)
)
(layer "F.SilkS")
(uuid "fead6daa-b9f4-4bd1-a131-b5d2cd3184de")
)
(fp_line
(start 5.098728 -22.547)
(end 13.094 -22.547)
(stroke
(width 0.127)
(type solid)
)
(layer "F.SilkS")
(uuid "b90ae92d-7c1a-4689-bf64-c3528a3ea3ee")
)
(fp_line
(start 13.594 -22.047)
(end 13.594 -21.037)
(stroke
(width 0.127)
(type solid)
)
(layer "F.SilkS")
(uuid "b540fbcc-8e06-44d7-95f6-8b142e4c90fb")
)
(fp_line
(start 15.875 -21.037)
(end 1.905 -21.037)
(stroke
(width 0.1)
(type solid)
)
(layer "F.SilkS")
(uuid "2b80542c-5088-4f89-ae32-3400cf894ca1")
)
(fp_line
(start 17.78 -1.987)
(end 17.78 -19.132)
(stroke
(width 0.1)
(type solid)
)
(layer "F.SilkS")
(uuid "4cd8add0-8a8c-468d-ba6f-b86787c288af")
)
(fp_arc
(start 0 -19.136)
(mid 0.557962 -20.483038)
(end 1.905 -21.041)
(stroke
(width 0.1)
(type solid)
)
(layer "F.SilkS")
(uuid "4d9bf7a5-4ab4-48bb-b8ac-0a5ce4523642")
)
(fp_arc
(start 1.905 -0.082)
(mid 0.557962 -0.639962)
(end 0 -1.987)
(stroke
(width 0.1)
(type solid)
)
(layer "F.SilkS")
(uuid "d09661c8-1ad3-40e9-8323-9dabc8f3f98b")
)
(fp_arc
(start 4.598728 -22.047272)
(mid 4.745276 -22.400644)
(end 5.098728 -22.547)
(stroke
(width 0.127)
(type default)
)
(layer "F.SilkS")
(uuid "7a58f637-14d7-4804-b127-f1d029b5d1e6")
)
(fp_arc
(start 13.094 -22.547)
(mid 13.447553 -22.400553)
(end 13.594 -22.047)
(stroke
(width 0.127)
(type default)
)
(layer "F.SilkS")
(uuid "d0bc8663-987f-4e2e-b464-096a547b822f")
)
(fp_arc
(start 15.875 -21.037)
(mid 17.222038 -20.479038)
(end 17.78 -19.132)
(stroke
(width 0.1)
(type solid)
)
(layer "F.SilkS")
(uuid "77004ea5-53a4-40ab-b35c-7bdd22107e34")
)
(fp_arc
(start 17.78 -1.987)
(mid 17.222038 -0.639962)
(end 15.875 -0.082)
(stroke
(width 0.1)
(type solid)
)
(layer "F.SilkS")
(uuid "94189dc1-14b5-4f17-b9d3-0163ed99e583")
)
(fp_circle
(center -0.21 -20.621)
(end -0.21 -20.875)
(stroke
(width 0.5)
(type solid)
)
(fill yes)
(layer "F.SilkS")
(uuid "dc606cc5-10bc-4635-81d1-fa27b1e6b4e2")
)
(fp_circle
(center 1.064 -19.904)
(end 1.064 -20.158)
(stroke
(width 0.5)
(type solid)
)
(fill yes)
(layer "F.SilkS")
(uuid "2b5e2829-6d01-441f-9983-fc4a273ff483")
)
(fp_rect
(start 17.8 0)
(end 0 -21)
(stroke
(width 0.05)
(type default)
)
(fill no)
(layer "F.CrtYd")
(uuid "e428eb56-00a2-4249-bb91-d2e5823b2899")
)
(fp_rect
(start 0 -21)
(end 17.8 0)
(stroke
(width 0.1)
(type default)
)
(fill no)
(layer "F.Fab")
(uuid "2133d927-559d-4615-ab92-732772c02d94")
)
(fp_circle
(center 1.084 -19.9)
(end 1.084 -20.154)
(stroke
(width 0.5)
(type solid)
)
(fill yes)
(layer "F.Fab")
(uuid "50e04a65-8ff2-4d1f-be98-2db48101e90a")
)
(pad "1" smd roundrect
(at 0.835 -18.12)
(size 2.75 2)
(layers "F.Cu" "F.Mask" "F.Paste")
(roundrect_rratio 0.25)
(solder_mask_margin 0.0508)
(thermal_bridge_angle 45)
(uuid "c68f6b12-dcf5-46b1-b159-2eef9023b0ac")
)
(pad "2" smd roundrect
(at 0.835 -15.58)
(size 2.75 2)
(layers "F.Cu" "F.Mask" "F.Paste")
(roundrect_rratio 0.25)
(solder_mask_margin 0.0508)
(thermal_bridge_angle 45)
(uuid "525b09fc-9099-4274-98c3-45f84d0591ab")
)
(pad "3" smd roundrect
(at 0.835 -13.04)
(size 2.75 2)
(layers "F.Cu" "F.Mask" "F.Paste")
(roundrect_rratio 0.25)
(solder_mask_margin 0.0508)
(thermal_bridge_angle 45)
(uuid "99cc76a7-0a78-4868-871b-c214ecac5525")
)
(pad "4" smd roundrect
(at 0.835 -10.5)
(size 2.75 2)
(layers "F.Cu" "F.Mask" "F.Paste")
(roundrect_rratio 0.25)
(solder_mask_margin 0.0508)
(thermal_bridge_angle 45)
(uuid "8038b67b-b3bf-4a3e-b790-40857963c957")
)
(pad "5" smd roundrect
(at 0.835 -7.96)
(size 2.75 2)
(layers "F.Cu" "F.Mask" "F.Paste")
(roundrect_rratio 0.25)
(solder_mask_margin 0.0508)
(thermal_bridge_angle 45)
(uuid "84711cb2-d222-464e-bcd5-f76af75cc612")
)
(pad "6" smd roundrect
(at 0.835 -5.42)
(size 2.75 2)
(layers "F.Cu" "F.Mask" "F.Paste")
(roundrect_rratio 0.25)
(solder_mask_margin 0.0508)
(thermal_bridge_angle 45)
(uuid "aabcfc3b-fa72-4e48-95d1-d53474158cc0")
)
(pad "7" smd roundrect
(at 0.835 -2.88)
(size 2.75 2)
(layers "F.Cu" "F.Mask" "F.Paste")
(roundrect_rratio 0.25)
(solder_mask_margin 0.0508)
(thermal_bridge_angle 45)
(uuid "3e086061-1f3f-4b1e-9354-920126d40249")
)
(pad "8" smd roundrect
(at 17 -2.88)
(size 2.75 2)
(layers "F.Cu" "F.Mask" "F.Paste")
(roundrect_rratio 0.25)
(solder_mask_margin 0.0508)
(thermal_bridge_angle 45)
(uuid "974a7e29-b5d3-475c-b481-2bc14f5b3495")
)
(pad "9" smd roundrect
(at 17 -5.42)
(size 2.75 2)
(layers "F.Cu" "F.Mask" "F.Paste")
(roundrect_rratio 0.25)
(solder_mask_margin 0.0508)
(thermal_bridge_angle 45)
(uuid "a553cc0b-22bc-49aa-9fb7-680c812e8abd")
)
(pad "10" smd roundrect
(at 17 -7.96)
(size 2.75 2)
(layers "F.Cu" "F.Mask" "F.Paste")
(roundrect_rratio 0.25)
(solder_mask_margin 0.0508)
(thermal_bridge_angle 45)
(uuid "b650936d-c436-4d93-abb7-7957add5cfb1")
)
(pad "11" smd roundrect
(at 17 -10.5)
(size 2.75 2)
(layers "F.Cu" "F.Mask" "F.Paste")
(roundrect_rratio 0.25)
(solder_mask_margin 0.0508)
(thermal_bridge_angle 45)
(uuid "177728f1-7b02-41a6-9de4-bdaee546f6d9")
)
(pad "12" smd roundrect
(at 17 -13.04)
(size 2.75 2)
(layers "F.Cu" "F.Mask" "F.Paste")
(roundrect_rratio 0.25)
(solder_mask_margin 0.0508)
(thermal_bridge_angle 45)
(uuid "eb6539ef-4d84-4ae5-bc4e-237dae21b9ef")
)
(pad "13" smd roundrect
(at 17 -15.58)
(size 2.75 2)
(layers "F.Cu" "F.Mask" "F.Paste")
(roundrect_rratio 0.25)
(solder_mask_margin 0.0508)
(thermal_bridge_angle 45)
(uuid "c8744fbe-93c9-4c54-84bc-435357de679f")
)
(pad "14" smd roundrect
(at 17 -18.12)
(size 2.75 2)
(layers "F.Cu" "F.Mask" "F.Paste")
(roundrect_rratio 0.25)
(solder_mask_margin 0.0508)
(thermal_bridge_angle 45)
(uuid "fd579d63-bbef-4609-9252-aad3f944a314")
)
(pad "15" smd circle
(at 7.6166 -19.1415)
(size 1.7 1.7)
(layers "F.Cu" "F.Mask" "F.Paste")
(uuid "ad890579-1e55-4b71-9d59-080a59598896")
)
(pad "16" smd circle
(at 10.1566 -19.1415)
(size 1.7 1.7)
(layers "F.Cu" "F.Mask" "F.Paste")
(uuid "25d6fdae-01ac-4ca4-8702-55badc8bcbc8")
)
(pad "17" smd circle
(at 7.6166 -16.6015)
(size 1.7 1.7)
(layers "F.Cu" "F.Mask" "F.Paste")
(uuid "e73ae295-736f-4acf-8ee1-0c120ebcebea")
)
(pad "18" smd circle
(at 10.1566 -16.6015)
(size 1.7 1.7)
(layers "F.Cu" "F.Mask" "F.Paste")
(uuid "7adfba87-f861-42a5-bb73-c3f9fac53180")
)
(pad "19" smd circle
(at 7.6166 -14.0615)
(size 1.7 1.7)
(layers "F.Cu" "F.Mask" "F.Paste")
(uuid "619dc2a5-519d-48ac-9c78-8c438df2a14d")
)
(pad "20" smd circle
(at 10.1566 -14.0615)
(size 1.7 1.7)
(layers "F.Cu" "F.Mask" "F.Paste")
(uuid "aa8b72d0-17bf-47d4-94b1-df9199638a29")
)
(pad "21" smd circle
(at 7.6166 -11.5215)
(size 1.7 1.7)
(layers "F.Cu" "F.Mask" "F.Paste")
(uuid "8539b5cc-db2c-4fa4-9ead-b842a1885293")
)
(pad "22" smd circle
(at 10.064 -11.5783)
(size 1.7 1.7)
(layers "F.Cu" "F.Mask" "F.Paste")
(uuid "313edcfe-ad25-4ea7-b786-d8290dfb479a")
)
(pad "23" smd roundrect
(at 7.3086 -4.9683 270)
(size 2.5 1.1)
(layers "F.Cu" "F.Mask" "F.Paste")
(roundrect_rratio 0.25)
(thermal_bridge_angle 45)
(uuid "144d3c3d-6207-433c-9e61-4f75722af865")
)
(pad "24" smd roundrect
(at 9.8486 -4.9683 270)
(size 2.5 1.1)
(layers "F.Cu" "F.Mask" "F.Paste")
(roundrect_rratio 0.25)
(thermal_bridge_angle 45)
(uuid "3780a4ef-348c-4241-b04b-2cb4f7127fa2")
)
(pad "25" smd roundrect
(at 9.949 -8.412 270)
(size 3.1 1.9)
(layers "F.Cu" "F.Mask" "F.Paste")
(roundrect_rratio 0.25)
(thermal_bridge_angle 45)
(uuid "00be3957-5a21-4898-8292-59f5f447623f")
)
(embedded_fonts no)
)

View file

@ -1,4 +0,0 @@
(sym_lib_table
(version 7)
(lib (name "Seeed_Studio_XIAO_Series") (type "KiCad") (uri "${KIPRJMOD}/library/Seeed_Studio_XIAO_Series.kicad_sym") (options "") (descr ""))
)

View file

@ -1,6 +0,0 @@
#!/bin/sh
## Builds common config for smartphone connected device
## -> only receive CAM and DENMs via UART (TODO: and BLE)
cargo run --release --no-default-features -F esp,cam,denm,uart

View file

@ -1,6 +0,0 @@
#!/bin/sh
## Builds common config for standalone device with automatic CAM transmission
## -> enable SPAT/ GLOSA with GNSS and a screen + CAM transmission
cargo run --release -F cam_tx

View file

@ -1,6 +0,0 @@
#!/bin/sh
## Builds common config for standalone device
## -> enable SPAT/ GLOSA with GNSS and a screen
cargo run --release

View file

@ -11,45 +11,6 @@ const PH_MAX_LENGHT_M: f32 = 500.;
const PH_MAX_ITEMS: usize = 23; const PH_MAX_ITEMS: usize = 23;
const PH_MIN_DIST_M: f32 = 20.; const PH_MIN_DIST_M: f32 = 20.;
/// Stand-alone CAM generator
///
/// Generates CAM messages from time and [`PosVel`] updates
///
/// # Send a message
/// ```
/// use esp32_cits_core::tx::GnItsPayload as _;
///
/// let (mut seq_no, mac_bytes) = {
/// // unwrap is fine since [`esp_hal::efuse::MacAddress`] is just an `[u8; 6]`
/// let mac_bytes = mac.as_bytes().try_into().unwrap();
///
/// (0, mac_bytes)
/// };
///
/// let mut cam = {
/// let station_id = make_station_id(mac);
/// info!("V2X Station ID: {station_id}");
///
/// applogic::cam_tx::CamState::new(
/// station_id,
/// OWN_STATION_TYPE,
/// OWN_VEHICLE_LENGTH,
/// OWN_VEHICLE_WIDTH,
/// )
/// };
///
/// cam.update(time, fix.clone().into());
/// match cam.encode_packet(mac_bytes, pos_vel, time, GN_IS_MOBILE, &mut seq_no) {
/// Ok(data) => {
/// if let Err(err) =
/// radio::send_80211_bcast_frame(&mut wlan_iface, &mac_bytes.clone(), &data)
/// {
/// warn!("Failed to send 802.11 frame: {err}");
/// }
/// }
/// Err(err) => warn!("Failed to create CAM: {err}"),
/// }
/// ```
#[derive(Debug)] #[derive(Debug)]
pub struct CamState { pub struct CamState {
station_id: u32, station_id: u32,
@ -59,7 +20,7 @@ pub struct CamState {
vehicle_width_dm: u8, // vehicle width is in 10cm steps vehicle_width_dm: u8, // vehicle width is in 10cm steps
time: chrono::DateTime<chrono::Utc>, time: chrono::DateTime<chrono::Utc>,
pos_state: esp32_cits_core::PosVel, pos_state: super::PositionState,
/// database for path history, oldest item at the back of the vector /// database for path history, oldest item at the back of the vector
path_history: Vec<PathPoint>, path_history: Vec<PathPoint>,
@ -74,7 +35,7 @@ struct PathPoint {
} }
impl PathPoint { impl PathPoint {
fn new_from_pos(pos: &esp32_cits_core::PosVel, time: chrono::DateTime<chrono::Utc>) -> Self { fn new_from_pos(pos: &super::PositionState, time: chrono::DateTime<chrono::Utc>) -> Self {
Self { Self {
pos: pos.position, pos: pos.position,
time, time,
@ -83,7 +44,7 @@ impl PathPoint {
} }
fn new_from_pos_with_dist( fn new_from_pos_with_dist(
pos: &esp32_cits_core::PosVel, pos: &super::PositionState,
time: chrono::DateTime<chrono::Utc>, time: chrono::DateTime<chrono::Utc>,
dist_m: f32, dist_m: f32,
) -> Self { ) -> Self {
@ -108,12 +69,12 @@ impl CamState {
vehicle_length_dm, vehicle_length_dm,
vehicle_width_dm, vehicle_width_dm,
time: chrono::DateTime::<chrono::Utc>::default(), time: chrono::DateTime::<chrono::Utc>::default(),
pos_state: esp32_cits_core::PosVel::default(), pos_state: super::PositionState::default(),
path_history: Vec::with_capacity(PH_MAX_ITEMS + 1), path_history: Vec::with_capacity(PH_MAX_ITEMS + 1),
} }
} }
pub fn update(&mut self, time: chrono::DateTime<chrono::Utc>, pos: esp32_cits_core::PosVel) { pub fn update(&mut self, time: chrono::DateTime<chrono::Utc>, pos: super::PositionState) {
self.time = time; self.time = time;
self.pos_state = pos; self.pos_state = pos;
@ -125,7 +86,7 @@ impl CamState {
fn update_ph( fn update_ph(
path_history: &mut Vec<PathPoint>, path_history: &mut Vec<PathPoint>,
time: chrono::DateTime<chrono::Utc>, time: chrono::DateTime<chrono::Utc>,
pos: &esp32_cits_core::PosVel, pos: &super::PositionState,
) { ) {
// Update path history // Update path history
if path_history.is_empty() { if path_history.is_empty() {
@ -332,11 +293,11 @@ impl CamState {
} }
} }
impl esp32_cits_core::tx::GnItsPayload for CamState { impl crate::v2x_tx::GnItsPayload for CamState {
fn make_eh( fn make_eh(
&self, &self,
address: [u8; 6], address: [u8; 6],
own_position: &esp32_cits_core::PosVel, own_position: &crate::applogic::PositionState,
time: chrono::DateTime<chrono::Utc>, time: chrono::DateTime<chrono::Utc>,
_seq_no: &mut u16, _seq_no: &mut u16,
) -> Result< ) -> Result<
@ -352,7 +313,7 @@ impl esp32_cits_core::tx::GnItsPayload for CamState {
// CAM always uses SHB, hop-limit 1 // CAM always uses SHB, hop-limit 1
Ok(( Ok((
esp32_cits_core::tx::gn::make_shb_eh(address, station_type, own_position, time)?, crate::v2x_tx::gn::make_shb_eh(address, station_type, own_position, time),
None, None,
1, 1,
)) ))
@ -375,8 +336,8 @@ mod tests {
use super::*; use super::*;
use crate::init_test_env_logger; use crate::init_test_env_logger;
fn posstate_from_pos(position: geo_types::Point) -> esp32_cits_core::PosVel { fn posstate_from_pos(position: geo_types::Point) -> crate::applogic::PositionState {
esp32_cits_core::PosVel { crate::applogic::PositionState {
position, position,
heading_deg: None, heading_deg: None,
speed_mps: None, speed_mps: None,

View file

@ -49,7 +49,7 @@ pub struct State {
time: chrono::NaiveDateTime, time: chrono::NaiveDateTime,
#[allow(unused)] #[allow(unused)]
pos: esp32_cits_core::PosVel, pos: PositionState,
#[cfg(feature = "spat")] #[cfg(feature = "spat")]
last_prune: chrono::NaiveDateTime, last_prune: chrono::NaiveDateTime,
@ -130,7 +130,15 @@ pub struct GnssFix {
pub speed_mps: Option<f32>, pub speed_mps: Option<f32>,
} }
impl From<&GnssFix> for esp32_cits_core::PosVel { #[allow(unused)]
#[derive(Debug, Default, Clone, PartialEq)]
pub struct PositionState {
pub position: geo_types::Point,
pub heading_deg: Option<f32>,
pub speed_mps: Option<f32>,
}
impl From<&GnssFix> for PositionState {
fn from(value: &GnssFix) -> Self { fn from(value: &GnssFix) -> Self {
let position = geo_types::Point::new(value.longitude_deg, value.latitude_deg); let position = geo_types::Point::new(value.longitude_deg, value.latitude_deg);
@ -141,7 +149,7 @@ impl From<&GnssFix> for esp32_cits_core::PosVel {
} }
} }
} }
impl From<GnssFix> for esp32_cits_core::PosVel { impl From<GnssFix> for PositionState {
fn from(value: GnssFix) -> Self { fn from(value: GnssFix) -> Self {
(&value).into() (&value).into()
} }
@ -204,7 +212,7 @@ impl State {
Self { Self {
initialized: false, initialized: false,
time: chrono::NaiveDateTime::default(), time: chrono::NaiveDateTime::default(),
pos: esp32_cits_core::PosVel::default(), pos: PositionState::default(),
#[cfg(feature = "spat")] #[cfg(feature = "spat")]
last_prune: chrono::NaiveDateTime::default(), last_prune: chrono::NaiveDateTime::default(),
#[cfg(feature = "spat")] #[cfg(feature = "spat")]
@ -633,7 +641,7 @@ impl State {
} }
#[allow(unused)] #[allow(unused)]
pub fn pos(&self) -> &esp32_cits_core::PosVel { pub fn pos(&self) -> &PositionState {
&self.pos &self.pos
} }
} }

View file

@ -169,7 +169,7 @@ impl From<msg::PositionState> for crate::applogic::GnssFix {
} }
} }
impl From<msg::PositionState> for esp32_cits_core::PosVel { impl From<msg::PositionState> for crate::applogic::PositionState {
fn from(value: msg::PositionState) -> Self { fn from(value: msg::PositionState) -> Self {
let position = geo_types::Point::new( let position = geo_types::Point::new(
value.position.longitude_deg.into(), value.position.longitude_deg.into(),
@ -194,7 +194,7 @@ impl From<msg::PositionState> for esp32_cits_core::PosVel {
} }
} }
impl From<msg::ItsPosition> for esp32_cits_core::PosVel { impl From<msg::ItsPosition> for crate::applogic::PositionState {
fn from(value: msg::ItsPosition) -> Self { fn from(value: msg::ItsPosition) -> Self {
let latitude_deg = f64::from(value.latitude) / 10_000_000.; let latitude_deg = f64::from(value.latitude) / 10_000_000.;
let longitude_deg = f64::from(value.longitude) / 10_000_000.; let longitude_deg = f64::from(value.longitude) / 10_000_000.;
@ -208,11 +208,11 @@ impl From<msg::ItsPosition> for esp32_cits_core::PosVel {
} }
} }
impl esp32_cits_core::tx::GnItsPayload for msg::RawMsgTx { impl crate::v2x_tx::GnItsPayload for msg::RawMsgTx {
fn make_eh( fn make_eh(
&self, &self,
address: [u8; 6], address: [u8; 6],
own_position: &esp32_cits_core::PosVel, own_position: &crate::applogic::PositionState,
time: chrono::DateTime<chrono::Utc>, time: chrono::DateTime<chrono::Utc>,
seq_no: &mut u16, seq_no: &mut u16,
) -> Result< ) -> Result<
@ -275,19 +275,19 @@ impl esp32_cits_core::tx::GnItsPayload for msg::RawMsgTx {
*seq_no += 1; *seq_no += 1;
( (
esp32_cits_core::tx::gn::make_tsb_eh( crate::v2x_tx::gn::make_tsb_eh(
address, address,
station_type, station_type,
own_position, own_position,
time, time,
*seq_no, *seq_no,
)?, ),
None, None,
hop_limit, hop_limit,
) )
} }
msg::GnTransport::Shb => ( msg::GnTransport::Shb => (
esp32_cits_core::tx::gn::make_shb_eh(address, station_type, own_position, time)?, crate::v2x_tx::gn::make_shb_eh(address, station_type, own_position, time),
None, None,
1, 1,
), ),
@ -308,7 +308,7 @@ impl msg::RawMsgTx {
fn make_geobcast( fn make_geobcast(
address: [u8; 6], address: [u8; 6],
station_type: geonetworking::en302636_4_1::StationType, station_type: geonetworking::en302636_4_1::StationType,
own_position: &esp32_cits_core::PosVel, own_position: &crate::applogic::PositionState,
time: chrono::DateTime<chrono::Utc>, time: chrono::DateTime<chrono::Utc>,
gn_area: msg::GnArea, gn_area: msg::GnArea,
sequence_number: u16, sequence_number: u16,
@ -320,7 +320,7 @@ impl msg::RawMsgTx {
alloc::string::String, alloc::string::String,
> { > {
let source_position_vector = let source_position_vector =
esp32_cits_core::tx::gn::make_lpv(address, station_type, own_position, time)?; crate::v2x_tx::gn::make_lpv(address, station_type, own_position, time);
let (atype, dist_a, dist_b, angle) = match gn_area.area() { let (atype, dist_a, dist_b, angle) = match gn_area.area() {
msg::GnAreaShape::EtsiAreashapeCircle => ( msg::GnAreaShape::EtsiAreashapeCircle => (

View file

@ -8,16 +8,11 @@
// #![deny(clippy::large_stack_frames)] // #![deny(clippy::large_stack_frames)]
// we can't use UART I/O and GNSS at the same time // we can't use UART I/O and GNSS at the same time
#[cfg(all(feature = "uart", feature = "gnss_ubx", feature = "gnss_l67k"))] #[cfg(all(feature = "uart", feature = "gnss"))]
compile_error!( compile_error!("feature \"uart\" and feature \"gnss\" cannot be enabled at the same time");
"feature \"uart\" and features \"gnss_ubx\" or \"gnss_l67k\" cannot be enabled at the same time"
);
// autonomous CAM generation need a time and position source (aka _gnss) // autonomous CAM generation need a time and position source (aka _gnss)
#[cfg(all(feature = "cam_tx", not(feature = "_gnss")))] #[cfg(all(feature = "cam_tx", not(feature = "_gnss")))]
compile_error!("feature \"cam_tx\" needs a time and position source (either GNSS or UART)"); compile_error!("feature \"cam_tx\" needs a time and position source (either GNSS or UART)");
// we can't have both GNSS drivers enabled at the same time
#[cfg(all(feature = "gnss_ubx", feature = "gnss_l67k"))]
compile_error!("feature \"gnss_ubx\" and feature \"gnss_l67k\" cannot be enabled at the same time");
use alloc::string::ToString as _; use alloc::string::ToString as _;
use alloc::vec::Vec; use alloc::vec::Vec;
@ -31,14 +26,14 @@ use c_its_parser::standards::extensions::ItsStationType;
use critical_section::Mutex; use critical_section::Mutex;
use crossbeam_queue::ArrayQueue; use crossbeam_queue::ArrayQueue;
use embassy_executor::Spawner; use embassy_executor::Spawner;
#[cfg(feature = "gnss_l67k")] #[cfg(feature = "gnss")]
use embassy_gps::gps::l76k; use embassy_gps::gps::l76k;
use esp_backtrace as _; use esp_backtrace as _;
use esp_hal::clock::CpuClock; use esp_hal::clock::CpuClock;
use esp_hal::timer::timg::TimerGroup; use esp_hal::timer::timg::TimerGroup;
#[cfg(all(target_arch = "riscv32", feature = "spat"))] #[cfg(all(target_arch = "riscv32", feature = "spat"))]
use esp_println::println; use esp_println::println;
use esp32_cits_core::radio; use esp_radio::wifi;
use geonetworking::Decode as _; use geonetworking::Decode as _;
use log::{error, info, warn}; use log::{error, info, warn};
@ -49,13 +44,13 @@ mod cache;
mod geo_alg; mod geo_alg;
#[cfg(feature = "uart")] #[cfg(feature = "uart")]
mod io; mod io;
mod radio;
#[cfg(feature = "screen")] #[cfg(feature = "screen")]
mod screen; mod screen;
#[cfg(feature = "gnss_ubx")] #[cfg(any(feature = "cam_tx", feature = "uart"))]
mod ubx; mod v2x_tx;
const WIFI_CHANNEL: radio::Channel = 180; const WIFI_CHANNEL: radio::Channel = 180;
const WIFI_TXPOWER: i8 = 10; // dBm, 2..20
#[cfg(feature = "spat")] #[cfg(feature = "spat")]
const SPAT_RATE_LIMIT: u8 = 5; // keep every n-th message const SPAT_RATE_LIMIT: u8 = 5; // keep every n-th message
#[cfg(any(feature = "cam_tx", feature = "uart"))] #[cfg(any(feature = "cam_tx", feature = "uart"))]
@ -168,13 +163,9 @@ async fn main(spawner: Spawner) -> ! {
esp_radio::wifi::ControllerConfig::default(), esp_radio::wifi::ControllerConfig::default(),
) )
.expect("Failed to initialize Wi-Fi controller"); .expect("Failed to initialize Wi-Fi controller");
if let Err(err) = wifi_controller.set_band_mode(esp_radio::wifi::BandMode::_5G) { if let Err(err) = wifi_controller.set_band_mode(esp_radio::wifi::BandMode::_5G) {
error!("Failed to set WiFi to 5GHz only: {err}"); error!("Failed to set WiFi to 5GHz only: {err}");
} }
if let Err(err) = wifi_controller.set_max_tx_power(WIFI_TXPOWER * 4) {
error!("Failed to set WiFi TX power: {err}");
}
critical_section::with(|cs| { critical_section::with(|cs| {
WIFI_RX_QUEUE.borrow(cs).replace(Some(ArrayQueue::new(2))); WIFI_RX_QUEUE.borrow(cs).replace(Some(ArrayQueue::new(2)));
@ -188,15 +179,14 @@ async fn main(spawner: Spawner) -> ! {
let mac = esp_hal::efuse::base_mac_address(); let mac = esp_hal::efuse::base_mac_address();
info!("WiFi MAC: {mac}"); info!("WiFi MAC: {mac}");
// Setup GNSS: // Setup GNSS: Seeed XIAO L67K:
// Seeed XIAO L67K:
// RX: D7/ GPIO12 // RX: D7/ GPIO12
// TX: D6/ GPIO11 // TX: D6/ GPIO11
// WAKEUP: D0/ GPIO1 -> HIGH for active, LOW for Sleep // WAKEUP: D0/ GPIO1 -> HIGH for active, LOW for Sleep
// RESET: D2/ GPIO25 -> HIGH for normal, LOW for Reset // RESET: D2/ GPIO25 -> HIGH for normal, LOW for Reset
#[cfg(feature = "gnss_l67k")] #[cfg(feature = "gnss")]
spawner.spawn( spawner.spawn(
gnss_l67k_task( gnss_task(
peripherals.GPIO1, peripherals.GPIO1,
peripherals.GPIO25, peripherals.GPIO25,
peripherals.UART0, peripherals.UART0,
@ -205,29 +195,7 @@ async fn main(spawner: Spawner) -> ! {
) )
.expect("Failed to spawn GNSS task"), .expect("Failed to spawn GNSS task"),
); );
#[cfg(feature = "gnss")]
// U-Blox module:
// RX: D7/ GPIO12
// TX: D6/ GPIO11
// RESET: D2/ GPIO25 -> HIGH for normal, LOW for Reset
#[cfg(feature = "gnss_ubx")]
{
setup_uart0(
9600,
Some(b'\n'),
peripherals.GPIO12,
peripherals.GPIO11,
peripherals.UART0,
serial_handler,
)
.expect("Failed to initialize UART for GNSS");
// do some settings (UART receive and parsing runs in main loop)
spawner
.spawn(gnss_ubx_config(peripherals.GPIO25).expect("Failed to spawn GNSS config task"));
}
#[cfg(feature = "_gnss")]
info!("GNSS task started, waiting for GNSS fix..."); info!("GNSS task started, waiting for GNSS fix...");
#[cfg(any(feature = "cam_tx", feature = "uart"))] #[cfg(any(feature = "cam_tx", feature = "uart"))]
@ -241,7 +209,7 @@ async fn main(spawner: Spawner) -> ! {
let mut state = applogic::State::new(); let mut state = applogic::State::new();
#[cfg(feature = "cam_tx")] #[cfg(feature = "cam_tx")]
let mut cam = { let mut cam = {
let station_id = make_station_id(mac); let station_id = v2x_tx::make_station_id(mac);
info!("V2X Station ID: {station_id}"); info!("V2X Station ID: {station_id}");
applogic::cam_tx::CamState::new( applogic::cam_tx::CamState::new(
@ -253,8 +221,6 @@ async fn main(spawner: Spawner) -> ! {
}; };
#[cfg(feature = "uart")] #[cfg(feature = "uart")]
let mut uart_input = io::Parser::default(); let mut uart_input = io::Parser::default();
#[cfg(feature = "gnss_ubx")]
let mut ubx_parser = ublox::Parser::default();
loop { loop {
embassy_time::Timer::after(embassy_time::Duration::from_millis(1)).await; embassy_time::Timer::after(embassy_time::Duration::from_millis(1)).await;
@ -286,7 +252,7 @@ async fn main(spawner: Spawner) -> ! {
}); });
} }
Some(io::msg::serial_input_msg::Payload::TxMsg(tx_request)) => { Some(io::msg::serial_input_msg::Payload::TxMsg(tx_request)) => {
use esp32_cits_core::tx::GnItsPayload as _; use v2x_tx::GnItsPayload as _;
info!( info!(
"UART: Got new TX message: Type {} with {} bytes", "UART: Got new TX message: Type {} with {} bytes",
@ -319,49 +285,6 @@ async fn main(spawner: Spawner) -> ! {
} }
} }
} }
#[cfg(feature = "gnss_ubx")]
{
let mut it = ubx_parser.consume_ubx(&data);
loop {
match it.next() {
Some(Ok(ublox::UbxPacket::Proto27(packet_ref))) => match &packet_ref {
ublox::proto27::PacketRef::MonVer(packet) => {
println!("{}", ubx::pretty_print_mon_ver(packet));
}
ublox::proto27::PacketRef::MonComms(packet) => {
println!("{}", ubx::pretty_print_mon_comms(packet));
}
ublox::proto27::PacketRef::MonRf(packet) => {
println!("{}", ubx::pretty_print_mon_rf(packet));
}
ublox::proto27::PacketRef::NavSat(packet) => {
println!("{}", ubx::pretty_print_nav_sat(packet));
}
ublox::proto27::PacketRef::NavPvt(pvt) => {
match applogic::GnssFix::try_from(pvt) {
Ok(pos_state) => {
critical_section::with(|cs| {
let gnss_update_ref = GNSS_UPDATE.borrow(cs);
gnss_update_ref.replace(Some(pos_state));
});
}
Err(err) => warn!("{err}"),
}
}
_ => {
println!("UBX: {packet_ref:?}");
}
},
Some(Err(_)) => {
// Received a malformed packet
}
None => {
// The internal buffer is now empty
break;
}
}
}
}
} }
} }
@ -377,7 +300,7 @@ async fn main(spawner: Spawner) -> ! {
#[cfg(feature = "cam_tx")] #[cfg(feature = "cam_tx")]
{ {
use esp32_cits_core::tx::GnItsPayload as _; use v2x_tx::GnItsPayload;
let time = state.time().and_utc(); let time = state.time().and_utc();
cam.update(time, fix.clone().into()); cam.update(time, fix.clone().into());
@ -510,7 +433,7 @@ async fn main(spawner: Spawner) -> ! {
reason = "adhering to callback interface" reason = "adhering to callback interface"
)] )]
#[allow(clippy::too_many_lines)] #[allow(clippy::too_many_lines)]
fn handle_frame(frame: esp_radio::wifi::sniffer::PromiscuousPkt<'_>) { fn handle_frame(frame: wifi::sniffer::PromiscuousPkt<'_>) {
#[cfg(feature = "spat")] #[cfg(feature = "spat")]
const PRUNE_INTERVAL: chrono::Duration = chrono::Duration::seconds(60); const PRUNE_INTERVAL: chrono::Duration = chrono::Duration::seconds(60);
#[cfg(feature = "spat")] #[cfg(feature = "spat")]
@ -531,18 +454,27 @@ fn handle_frame(frame: esp_radio::wifi::sniffer::PromiscuousPkt<'_>) {
.naive_utc() .naive_utc()
}; };
match esp32_cits_core::rx::is_broadcast_frame(&frame) { // Ignore frames with errors and non-data frames
Err(err) => { if frame.rx_cntl.rx_state != 0 {
warn!("{err}"); warn!("Received frame has RX error: {}", frame.rx_cntl.rx_state);
return; return;
} }
Ok(false) => { if !radio::PromiscuousPktType::Data.matches(frame.frame_type) {
// drop frame
return; return;
} }
Ok(true) => {
// continue // Ignore non-broadcast frames
if frame.len < (4 + 6) {
warn!(
"Received frame too small to be a valid broadcast frame: {} bytes",
frame.len
);
return;
} }
let dest_mac = &frame.data[4..10];
if dest_mac != [0xff; 6] {
warn!("Received frame is not broadcast frame: Dest MAC {dest_mac:02x?}");
return;
} }
// info!("Received frame with {} bytes", frame.len); // info!("Received frame with {} bytes", frame.len);
@ -689,10 +621,10 @@ fn get_its_header<'p>(
} }
} }
#[cfg(feature = "gnss_l67k")] #[cfg(feature = "gnss")]
#[allow(clippy::large_stack_frames, reason = "GNSS driver needs some space")] #[allow(clippy::large_stack_frames, reason = "GNSS driver needs some space")]
#[embassy_executor::task] #[embassy_executor::task]
async fn gnss_l67k_task( async fn gnss_task(
gps_wakeup: esp_hal::peripherals::GPIO1<'static>, gps_wakeup: esp_hal::peripherals::GPIO1<'static>,
gps_reset: esp_hal::peripherals::GPIO25<'static>, gps_reset: esp_hal::peripherals::GPIO25<'static>,
uart: esp_hal::peripherals::UART0<'static>, uart: esp_hal::peripherals::UART0<'static>,
@ -742,38 +674,6 @@ async fn gnss_l67k_task(
} }
} }
#[cfg(feature = "gnss_ubx")]
#[embassy_executor::task]
async fn gnss_ubx_config(reset_pin: esp_hal::peripherals::GPIO25<'static>) {
// reset the module
let mut gnss_reset = esp_hal::gpio::Output::new(
reset_pin,
esp_hal::gpio::Level::Low,
esp_hal::gpio::OutputConfig::default(),
);
embassy_time::Timer::after(embassy_time::Duration::from_millis(2)).await;
gnss_reset.set_high();
embassy_time::Timer::after(embassy_time::Duration::from_secs(1)).await;
info!("UBX: Enabling ubx protocol with NAV-PVT and NAV-SAT");
let buffer = ublox::packets::cfg_val::CfgValSetBuilder {
version: 1,
layers: ublox::packets::cfg_val::CfgLayerSet::RAM,
reserved1: 0,
cfg_data: &[
ublox::cfg_val::CfgVal::Uart1OutProtNmea(false),
ublox::cfg_val::CfgVal::Uart1OutProtUbx(true),
// CFG-MSGOUT-UBX_NAV_PVT_UART1
ublox::cfg_val::CfgVal::MsgOutUbxNavPvtUart1(1),
// CFG-MSGOUT-UBX_NAV_SAT_UART1
// ublox::cfg_val::CfgVal::MsgOutUbxNavSatUart1(10),
],
}
.into_packet_vec();
send_uart0(&buffer);
}
#[cfg(feature = "screen")] #[cfg(feature = "screen")]
fn log_screen_error<E>(error: &E) fn log_screen_error<E>(error: &E)
where where
@ -873,19 +773,3 @@ fn send_uart0(data: &[u8]) {
} }
}); });
} }
#[cfg(all(feature = "esp", feature = "cam_tx"))]
/// Creates a station ID from the WLAN MAC address
///
/// Will use `0xC173` as static prefix and uses last 16 bit from MAC address for the remainder.
///
/// # Panics
/// Won't panic in practive b/c of pre-conditions
#[must_use]
pub fn make_station_id(mac: esp_hal::efuse::MacAddress) -> u32 {
// unwrap is fine here since we can be sure that the MacAddress is 6 byte
// and a slice of 2 bytes can be converted to [u8; 2]
let mac_last_16bit = u16::from_be_bytes(mac.as_bytes().get(4..6).unwrap().try_into().unwrap());
0xC173_0000 | u32::from(mac_last_16bit)
}

288
src/radio.rs Normal file
View file

@ -0,0 +1,288 @@
//! IEEE 802.11p C-ITS Capture with ESP32-C5
#![allow(unused)]
// -----------------------------
// missing interface definitions
// -----------------------------
pub type Channel = core::ffi::c_int;
unsafe extern "C" {
/// Changes the 802.11 PHY channel
///
/// C function signature: `void phy_change_channel(int,int,int,int)`
///
/// Options:
/// - `primary`: Channel frequency in MHz
/// - `ignored1`: Ignored (set to 1)
/// - `ignored2`: Ignored (set to 0)
/// - `ht_mode`: Probably HT mode
unsafe fn phy_change_channel(
primary: Channel,
ignored1: core::ffi::c_int,
ignored2: core::ffi::c_int,
ht_mode: core::ffi::c_int,
) -> core::ffi::c_int;
/// Enables 802.11p mode on the ESP32-C5
///
/// C function signature: `void phy_11p_set(int,int)`
///
/// Options:
/// - `enable`: Boolean option to enable 802.11p mode
/// - `half_rate`: 5 MHz mode
unsafe fn phy_11p_set(enable: u8, half_rate: u8);
}
/// Enum values for `wifi_promiscuous_pkt_type_t` used in `esp_radio::wifi::sniffer::PromiscuousPkt::frame_type`
///
/// Original value is actually `core::ffi::c_uint`, but that can't be used in `#[repr()]` attributes,
/// so we're using `u8` as the smallest suitable type here that can be casted to a bigger type.
#[repr(u8)]
#[derive(Debug, Clone, Copy)]
#[allow(dead_code)]
pub(crate) enum PromiscuousPktType {
/// Management frame, indicates 'buf' argument is `wifi_promiscuous_pkt_t`
Mgmt = 0,
/// Control frame, indicates 'buf' argument is `wifi_promiscuous_pkt_t`
Ctrl = 1,
/// Data frame, indicates 'buf' argument is `wifi_promiscuous_pkt_t`
Data = 2,
/// Other type, such as MIMO etc. 'buf' argument is `wifi_promiscuous_pkt_t` but the payload is zero length.
Misc = 3,
}
impl PromiscuousPktType {
#[allow(dead_code)]
pub fn as_repr(self) -> u8 {
self as u8
}
/// Converts to underlying type of `wifi_promiscuous_pkt_type_t` (used by `esp_radio::wifi::sniffer::PromiscuousPkt::frame_type`)
pub fn as_frame_type(self) -> core::ffi::c_uint {
core::ffi::c_uint::from(self.as_repr())
}
/// Determines if we are the same frame type as `esp_radio::wifi::sniffer::PromiscuousPkt::frame_type`
pub fn matches(self, frame_type: core::ffi::c_uint) -> bool {
self.as_frame_type() == frame_type
}
}
#[cfg(feature = "esp")]
/// Configure WiFi Sniffer to a certain 802.11p channel
pub fn setup_wifi_sniffer(
channel: Channel,
sniffer: &mut esp_radio::wifi::sniffer::Sniffer,
callback: fn(esp_radio::wifi::sniffer::PromiscuousPkt<'_>),
) -> Result<(), alloc::string::String> {
sniffer
.set_promiscuous_mode(true)
.map_err(|err| alloc::format!("Failed to enable promiscuous mode: {err:?}"))?;
sniffer.set_receive_cb(callback);
// convert channel number to frequency in MHz
let primary = 5000 + (channel * 5);
unsafe {
phy_11p_set(1, 0);
match phy_change_channel(primary, 1, 0, 0) {
0 => Ok(()),
ret => Err(alloc::format!("Failed to change channel: {ret}")),
}
}
}
#[cfg(feature = "esp")]
/// Sends 802.11p broadcast frame
///
/// Expects `data` to contain GN header (BTP header and ITS payload) to be a valid C-ITS/ ITS-G5 packet/ frame.
///
/// # Errors
/// Fails when MAC is not the right size or returns frame send error
pub fn send_80211_bcast_frame(
sniffer: &mut esp_radio::wifi::sniffer::Sniffer,
mac_addr: &[u8],
data: &[u8],
) -> Result<(), alloc::string::String> {
const HEADER_SIZE: usize = 24 + 8;
let mac = mac_addr
.try_into()
.map_err(|err| alloc::format!("Unexpected MAC address size: {err}"))?;
// assemble frame
let mut frame_buf = alloc::vec::Vec::with_capacity(HEADER_SIZE + data.len());
frame_buf.extend_from_slice(&DataFrameHeader::new_80211p(mac).to_binary());
frame_buf.extend_from_slice(&LlcHeader::new_80211p().to_binary());
frame_buf.extend_from_slice(data);
// send
sniffer
.send_raw_frame(true, &frame_buf, true)
.map_err(|err| alloc::format!("Failed to send 802.11p frame: {err:?}"))
}
/// IEEE 802.11p frame header
#[derive(Debug, Default)]
struct DataFrameHeader {
/// byte 0: subtype in upper nibble, type in lower nibble
/// "normal" data frame: 0x08, QoS data frame: 0x88
pub type_and_subtype: u8,
/// byte 1: frame contol field, 0x00 for 802.11p
pub fcf_flags: u8,
/// byte 2..3: duration in micros, usually 0x0000 for 802.11p (toDS = 0, fromDS = 0)
pub duration: u16,
/// byte 4..9: receiver/ destination for 802.11p
pub address_1: [u8; 6],
/// byte 10..15: transmitter/ source for 802.11p
pub address_2: [u8; 6],
/// byte 16..21: BSSID for 802.11p
pub address_3: [u8; 6],
/// byte 22..24: upper 12 bits: seq. no, lower 4 bits: fragment no
pub sequence_control: u16,
// Note: address 4 is not used in 802.11p
// pub qos: Option<[u8; 2]>, // QoS frames are supported by radio library
// Note: 802.11p does not use HT mode(s)
}
impl DataFrameHeader {
const BCAST_MAC: [u8; 6] = [0xFF; 6];
/// Create an 802.11p (non-QoS) data frame header with a source MAC address
pub(crate) fn new_80211p(source_mac: &[u8; 6]) -> Self {
Self {
type_and_subtype: 0x08,
address_1: Self::BCAST_MAC,
address_2: *source_mac,
address_3: Self::BCAST_MAC,
..Default::default()
}
}
pub(crate) fn to_binary(&self) -> [u8; 24] {
let mut buf = [0u8; 24];
buf[0] = self.type_and_subtype;
buf[1] = self.fcf_flags;
Self::write_u16(&mut buf, 2, self.duration);
Self::write_mac_addr(&mut buf, 4, &self.address_1);
Self::write_mac_addr(&mut buf, 10, &self.address_2);
Self::write_mac_addr(&mut buf, 16, &self.address_3);
Self::write_u16(&mut buf, 22, self.sequence_control);
buf
}
fn write_u16(buf: &mut [u8; 24], offset: usize, data: u16) {
for (idx, byte) in data.to_be_bytes().iter().enumerate() {
buf[idx + offset] = *byte;
}
}
fn write_mac_addr(buf: &mut [u8; 24], offset: usize, data: &[u8; 6]) {
for (idx, byte) in data.iter().enumerate() {
buf[idx + offset] = *byte;
}
}
}
/// IEEE 802.11p LLC header
#[derive(Debug, Default)]
struct LlcHeader {
/// byte 0: SNAP (0xaa) in 802.11p
pub dsap: u8, // SNAP in
/// byte 1: SNAP (0xaa) in 802.11p
pub lsap: u8, // SNAP in
/// byte 2: SNAP (0xaa) in 802.11p
pub control_1: u8, // 0x03
// Note: optional second control byte not used in 802.11p
// SNAP fields
/// set to 0 since ethertype is used for protocol ID in 802.11p
pub oui: [u8; 3],
/// ethertype GeoNetworking (0x8947) in 802.11p
pub protocol_id: u16,
}
impl LlcHeader {
pub(crate) fn new_80211p() -> Self {
Self {
dsap: 0xaa,
lsap: 0xaa,
control_1: 0x03,
oui: [0; 3],
protocol_id: 0x8947,
}
}
pub(crate) fn to_binary(&self) -> [u8; 8] {
let mut buf: [u8; 8] = [
self.dsap,
self.lsap,
self.control_1,
0x00,
0x00,
0x00,
0x00,
0x00,
];
buf[3] = self.oui[0];
buf[4] = self.oui[1];
buf[5] = self.oui[2];
buf[6] = self.protocol_id.to_be_bytes()[0];
buf[7] = self.protocol_id.to_be_bytes()[1];
buf
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn data_frame_test() {
// build a typical IEEE 802.11p frame
const OWN_MAC: [u8; 6] = [0x12, 0x34, 0x56, 0x78, 0xab, 0xcd];
const REF_HEADER: [u8; 24] = [
0x08, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x12, 0x34, 0x56, 0x78,
0xab, 0xcd, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00,
];
let frame = DataFrameHeader::new_80211p(&OWN_MAC);
assert_eq!(0x08, frame.type_and_subtype); // data frame, subtype 0
assert_eq!(0x0000, frame.fcf_flags);
assert_eq!(0x0000, frame.duration);
assert_eq!([0xFF; 6], frame.address_1);
assert_eq!(OWN_MAC, frame.address_2);
assert_eq!([0xFF; 6], frame.address_3);
assert_eq!(0x0000, frame.sequence_control);
assert_eq!(REF_HEADER, frame.to_binary());
}
#[test]
fn llc_test() {
// build a typical IEEE 802.11p LLC header
const REF_HEADER: [u8; 8] = [0xaa, 0xaa, 0x03, 0x00, 0x00, 0x00, 0x89, 0x47];
let llc = LlcHeader::new_80211p();
assert_eq!(0xaa, llc.dsap);
assert_eq!(0xaa, llc.lsap);
assert_eq!(0x03, llc.control_1);
assert_eq!([0x00; 3], llc.oui);
assert_eq!(0x8947, llc.protocol_id);
assert_eq!(REF_HEADER, llc.to_binary());
}
}

89
src/test-proto.rs Normal file
View file

@ -0,0 +1,89 @@
#![cfg(not(target_arch = "riscv32"))]
#![allow(unused)]
extern crate alloc;
mod applogic;
mod io;
mod v2x_tx;
fn main() {
use prost::Message as _;
// env_logger::init();
// generate some test UART messages as hex strings
let raw_its = input_from_raw(io::msg::RawMsgTx {
payload: [0xaa, 0xbb, 0xcc, 0xdd].to_vec(),
message_type: 1,
gn_transport: io::msg::GnTransport::Shb.into(),
gn_area: io::msg::GnArea {
area: None,
position: None,
dist_a: None,
dist_b: None,
angle: None,
},
hop_limit: 2,
station_type: None,
});
println!("{}", serialize_serial_msg_hex(&raw_its));
let pos = input_from_position(io::msg::PositionState {
timestamp_ms: 1785144196000,
position: io::msg::Position {
latitude_deg: 42.,
longitude_deg: 23.,
},
heading: None,
speed: None,
});
println!("{}", serialize_serial_msg_hex(&pos));
}
fn input_from_raw(value: io::msg::RawMsgTx) -> io::msg::SerialInputMsg {
use io::msg::serial_input_msg::Payload;
io::msg::SerialInputMsg {
payload: Some(Payload::TxMsg(value)),
}
}
fn input_from_position(value: io::msg::PositionState) -> io::msg::SerialInputMsg {
use io::msg::serial_input_msg::Payload;
io::msg::SerialInputMsg {
payload: Some(Payload::Position(value)),
}
}
pub(crate) fn serialize_serial_msg_hex(msg: &io::msg::SerialInputMsg) -> alloc::string::String {
use prost::Message as _;
let mut msg_buf = msg.encode_to_vec();
let sof = [0x56u8, 0x32, 0x58, 0x2B];
let eof = [0x0du8, 0x0a];
let len: [u8; 2] = (msg_buf.len() as u16).to_be_bytes();
let mut out = sof.to_vec();
out.extend_from_slice(&len);
out.append(&mut msg_buf);
out.extend_from_slice(&eof);
// convert to hex string
let mut hex_str = alloc::string::String::default();
for c in out {
hex_str.push_str(format!("{c:02x}").as_str());
}
hex_str
}
// /// Initializes the env-logger to be used inside tests
// fn init_test_env_logger() {
// let _ = env_logger::builder()
// .format_timestamp(None)
// .filter_level(log::LevelFilter::Debug)
// .is_test(true)
// .try_init();
// }

View file

@ -7,7 +7,9 @@ mod applogic;
mod cache; mod cache;
mod geo_alg; mod geo_alg;
mod io; mod io;
mod radio;
mod testdata; mod testdata;
mod v2x_tx;
fn main() { fn main() {
// env_logger::init(); // env_logger::init();

View file

@ -1,106 +0,0 @@
//! u-blox helpers
#![allow(unused)]
use alloc::vec::Vec;
pub fn pretty_print_mon_ver(packet: &ublox::mon_ver::MonVerRef) -> alloc::string::String {
alloc::format!(
"UBX-MON-VER: SW version: {} HW version: {}; Extensions: {:?}",
packet.software_version(),
packet.hardware_version(),
packet.extension().collect::<Vec<&str>>()
)
}
pub fn pretty_print_mon_comms(packet: &ublox::mon_comms::MonCommsRef) -> alloc::string::String {
alloc::format!(
"UBX-MON-COMMS: {} ports ({:?}), {} tx err, info {:?}",
packet.n_ports(),
packet.prot_ids(),
packet.tx_errors(),
packet
.ports()
.map(|v| alloc::format!("{v:?}"))
.collect::<Vec<_>>()
)
}
pub fn pretty_print_mon_rf(packet: &ublox::mon_rf::MonRfRef) -> alloc::string::String {
alloc::format!(
"UBX-MON-RF: {:?}",
packet
.blocks()
.map(|v| alloc::format!("{v:?}"))
.collect::<Vec<_>>()
)
}
pub fn pretty_print_nav_sat(packet: &ublox::nav_sat::NavSatRef) -> alloc::string::String {
// sort by quality flags
let mut sat_infos = packet.svs().collect::<Vec<_>>();
sat_infos.sort_unstable_by_key(|v| v.flags_raw() & 0x7);
sat_infos.reverse();
let sat_info_strings = sat_infos
.iter()
.map(|v| {
alloc::format!(
"- {}/{:3}: E {:3.0}, A {:3.0}, CNO {:2.1} dBHz, {:?}, {:?}",
v.gnss_id(),
v.sv_id(),
v.elev(),
v.azim(),
v.cno(),
v.flags().quality_ind(),
v.flags().health(),
)
})
.collect::<Vec<_>>();
alloc::format!(
"UBX-NAV-SAT: num {:?}\n{}",
packet.num_svs(),
sat_info_strings.join("\n")
)
}
impl<'a> TryFrom<&ublox::nav_pvt::proto27::NavPvtRef<'a>> for crate::applogic::GnssFix {
type Error = alloc::string::String;
fn try_from(value: &ublox::nav_pvt::proto27::NavPvtRef<'a>) -> Result<Self, Self::Error> {
let has_time = value.fix_type() == ublox::GnssFixType::Fix3D
|| value.fix_type() == ublox::GnssFixType::GPSPlusDeadReckoning
|| value.fix_type() == ublox::GnssFixType::TimeOnlyFix;
let has_posvel = value.fix_type() == ublox::GnssFixType::Fix3D
|| value.fix_type() == ublox::GnssFixType::GPSPlusDeadReckoning;
if !has_posvel || !has_time {
return Err(alloc::format!(
"NAV-PVT without valid fix: {:?}",
value.fix_type()
));
}
let pos: ublox::PositionLLA = value.into();
let vel: ublox::Velocity = value.into();
let time: chrono::DateTime<chrono::Utc> = value
.try_into()
.map_err(|err| alloc::format!("Failed to convert time: {err}"))?;
let latitude_deg = pos.lat;
let longitude_deg = pos.lon;
#[allow(clippy::cast_precision_loss, clippy::cast_possible_truncation)]
let heading_deg = vel.heading as f32;
#[allow(clippy::cast_precision_loss, clippy::cast_possible_truncation)]
let speed_mps = vel.speed as f32;
Ok(Self {
time: Some(time.naive_utc()),
latitude_deg,
longitude_deg,
heading_deg: Some(heading_deg),
speed_mps: Some(speed_mps),
})
}
}

94
src/v2x_tx/gn.rs Normal file
View file

@ -0,0 +1,94 @@
//! Geonetworking utilities
use c_its_parser::gn as geonetworking;
#[allow(unused)]
pub fn make_shb_eh(
address: [u8; 6],
station_type: geonetworking::en302636_4_1::StationType,
own_position: &crate::applogic::PositionState,
time: chrono::DateTime<chrono::Utc>,
) -> geonetworking::en302636_4_1::ExtendedHeader {
let source_position_vector = make_lpv(address, station_type, own_position, time);
geonetworking::en302636_4_1::ExtendedHeader::SHB(
geonetworking::en302636_4_1::SingleHopBroadcast {
source_position_vector,
media_dependent_data: [0; 4],
},
)
}
#[allow(unused)]
pub fn make_tsb_eh(
address: [u8; 6],
station_type: geonetworking::en302636_4_1::StationType,
own_position: &crate::applogic::PositionState,
time: chrono::DateTime<chrono::Utc>,
sequence_number: u16,
) -> geonetworking::en302636_4_1::ExtendedHeader {
let source_position_vector = make_lpv(address, station_type, own_position, time);
geonetworking::en302636_4_1::ExtendedHeader::TSB(
geonetworking::en302636_4_1::TopologicallyScopedBroadcast::new(
sequence_number,
source_position_vector,
),
)
}
#[allow(unused)]
pub fn make_gbc_eh(
address: [u8; 6],
station_type: geonetworking::en302636_4_1::StationType,
own_position: &crate::applogic::PositionState,
time: chrono::DateTime<chrono::Utc>,
target_pos: geo_types::Point,
radius_m: u16,
sequence_number: u16,
) -> geonetworking::en302636_4_1::ExtendedHeader {
let source_position_vector = make_lpv(address, station_type, own_position, time);
#[allow(clippy::cast_possible_truncation)]
let latitude_deg = target_pos.x() as f32;
#[allow(clippy::cast_possible_truncation)]
let longitude_deg = target_pos.y() as f32;
geonetworking::en302636_4_1::ExtendedHeader::GBC(
geonetworking::en302636_4_1::GeoBroadcast::try_from_values(
sequence_number,
source_position_vector,
latitude_deg,
longitude_deg,
radius_m,
0,
0,
)
.expect("Failed to create circular GBC"),
)
}
pub fn make_lpv(
address: [u8; 6],
station_type: geonetworking::en302636_4_1::StationType,
own_position: &crate::applogic::PositionState,
time: chrono::DateTime<chrono::Utc>,
) -> geonetworking::en302636_4_1::LongPositionVector {
let timestamp_its = c_its_parser::time_utils::TimestampIts::from(time);
#[allow(clippy::cast_possible_truncation)]
let latitude_deg = own_position.position.y() as f32;
#[allow(clippy::cast_possible_truncation)]
let longitude_deg = own_position.position.x() as f32;
let speed_mps = own_position.speed_mps.unwrap_or_default();
let heading_deg = own_position.heading_deg.unwrap_or_default();
geonetworking::en302636_4_1::LongPositionVector::try_from_values(
geonetworking::en302636_4_1::Address::new(false, station_type, address),
timestamp_its.0,
latitude_deg,
longitude_deg,
true,
speed_mps,
heading_deg,
)
.expect("Failed to create LongPositionVector")
}

164
src/v2x_tx/mod.rs Normal file
View file

@ -0,0 +1,164 @@
//! V2X Transmission
use alloc::vec::Vec;
use c_its_parser::gn::{self as geonetworking, Encode};
pub mod gn;
#[cfg(all(feature = "esp", feature = "cam_tx"))]
/// Creates a station ID from the WLAN MAC address
///
/// Will use `0xC173` as static prefix and uses last 16 bit from MAC address for the remainder.
pub fn make_station_id(mac: esp_hal::efuse::MacAddress) -> u32 {
// unwrap is fine here since we can be sure that the MacAddress is 6 byte
// and a slice of 2 bytes can be converted to [u8; 2]
let mac_last_16bit = u16::from_be_bytes(mac.as_bytes().get(4..6).unwrap().try_into().unwrap());
0xC173_0000 | u32::from(mac_last_16bit)
}
pub trait GnItsPayload {
// common GN parameters
const LIFETIME_MILLIS: u32 = 60_000; // itsGnDefaultPacketLifetime is 60 seconds according to ETSI EN 302 636-4-1 V1.4.1
// ---------------------------------------------
// methods to build a GN packet with ITS payload
// ---------------------------------------------
/// Builds the [`geonetworking::en302636_4_1::ExtendedHeader`] and additional information for the ITS message
///
/// Output:
/// - Extended header
/// - Area type (when using GAC or GBC)
/// - hop limit
///
/// Note: Make sure to increase the sequence number on multi-hop packets.
fn make_eh(
&self,
address: [u8; 6],
own_position: &crate::applogic::PositionState,
time: chrono::DateTime<chrono::Utc>,
seq_no: &mut u16,
) -> Result<
(
geonetworking::en302636_4_1::ExtendedHeader,
Option<geonetworking::en302636_4_1::AreaType>,
u8,
),
alloc::string::String,
>;
/// Creates the ITS payload (without BTP header)
fn make_payload(
&self,
) -> Result<(Vec<u8>, c_its_parser::standards::extensions::ItsMessageId), alloc::string::String>;
// ---------------------------------------------
// common methods (with implementation)
// ---------------------------------------------
/// Encodes the GN packet
fn encode_packet(
&self,
address: [u8; 6],
own_position: &crate::applogic::PositionState,
time: chrono::DateTime<chrono::Utc>,
is_mobile: bool,
seq_no: &mut u16,
) -> Result<Vec<u8>, alloc::string::String> {
// Build payload (including BTP header)
let (mut its_payload, msg_type) = self.make_payload()?;
let mut payload = make_btp_b(msg_type).encode()?;
payload.append(&mut its_payload);
#[allow(clippy::cast_possible_truncation)]
let payload_length = (payload.len()) as u16;
// get extended header (depending on message type and sometimes content as well)
let (eh, area_type, maximum_hop_limit) =
self.make_eh(address, own_position, time, seq_no)?;
// Build GN headers
let traffic_class = geonetworking::en302636_4_1::TrafficClass::try_new(
false,
false,
make_traffic_class(msg_type),
)
.map_err(|err| alloc::format!("Failed to create TrafficClass: {err:?}"))?;
let common = geonetworking::en302636_4_1::CommonHeader::from_values(
geonetworking::en302636_4_1::NextAfterCommon::BTPB,
geonetworking::en302636_4_1::HeaderType::new_from(&eh, area_type)?,
traffic_class,
is_mobile,
payload_length,
maximum_hop_limit,
);
let remaining_hop_limit = maximum_hop_limit;
let basic = geonetworking::en302636_4_1::BasicHeader::try_new(
1,
geonetworking::en302636_4_1::NextAfterBasic::CommonHeader,
geonetworking::en302636_4_1::Lifetime::from_milliseconds(Self::LIFETIME_MILLIS),
remaining_hop_limit,
)
.map_err(|err| alloc::format!("Failed to create BasicHeader: {err}"))?;
// Assemble and encode packet
let packet = geonetworking::Packet::Unsecured {
basic,
common,
extended: Some(eh),
payload: &payload,
};
packet
.encode_to_vec()
.map_err(|err| alloc::format!("Failed to encode GN packet: {err:?}"))
}
}
/// Creates a BTP-B header based on the ITS message ID (message type)
///
/// Port numbers and destination port info according to ETSI TS 103 248
fn make_btp_b(
msg_type: c_its_parser::standards::extensions::ItsMessageId,
) -> c_its_parser::transport::TransportHeader {
let (destination_port, destination_port_info) = match msg_type {
c_its_parser::standards::extensions::ItsMessageId::Denm => (2002, 0),
c_its_parser::standards::extensions::ItsMessageId::Cam => (2001, 0),
c_its_parser::standards::extensions::ItsMessageId::Mapem => (2003, 0),
c_its_parser::standards::extensions::ItsMessageId::Spatem => (2004, 0),
c_its_parser::standards::extensions::ItsMessageId::Saem => (2005, 0),
c_its_parser::standards::extensions::ItsMessageId::Ivim => (2006, 0),
c_its_parser::standards::extensions::ItsMessageId::Srem => (2007, 0),
c_its_parser::standards::extensions::ItsMessageId::Ssem => (2008, 0),
c_its_parser::standards::extensions::ItsMessageId::Cpm => (2009, 0),
c_its_parser::standards::extensions::ItsMessageId::Poi => (2010, 0),
c_its_parser::standards::extensions::ItsMessageId::EvRsr => (2012, 0),
c_its_parser::standards::extensions::ItsMessageId::Rtcmem => (2013, 0),
_ => (0, 0), // no information found for the subsequent types
};
c_its_parser::transport::TransportHeader::BtpB(c_its_parser::transport::BasicTransportBHeader {
destination_port,
destination_port_info,
})
}
/// Creates a traffic class ID based on the ITS message ID (message type)
fn make_traffic_class(msg_type: c_its_parser::standards::extensions::ItsMessageId) -> u8 {
#[allow(clippy::match_same_arms)]
match msg_type {
c_its_parser::standards::extensions::ItsMessageId::Denm => 1,
c_its_parser::standards::extensions::ItsMessageId::Cam => 2,
c_its_parser::standards::extensions::ItsMessageId::Mapem => 3, // from C-Roads RS_RSP_063(1)
c_its_parser::standards::extensions::ItsMessageId::Spatem => 3, // from C-Roads RS_RSP_063(1)
c_its_parser::standards::extensions::ItsMessageId::Ivim => 3, // from C-Roads RS_RSP_063(1)
c_its_parser::standards::extensions::ItsMessageId::Srem => 2, // from C-Roads RS_RSP_063(1)
c_its_parser::standards::extensions::ItsMessageId::Ssem => 2, // from C-Roads RS_RSP_063(1)
c_its_parser::standards::extensions::ItsMessageId::Rtcmem => 3, // CSP_MaxPrio=252 -> 3
_ => 3, // fall-back to lowest priority
}
}