Compare commits
13 commits
feat/glosa
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
666499c240 | ||
| 2db0dc0116 | |||
| ac6a3e398e | |||
|
|
71161d1d99 | ||
|
|
2c50668e7e | ||
|
|
4d7bef58a1 | ||
|
|
ba71e718df | ||
| 6604670490 | |||
|
|
7bbb47a844 | ||
| 2379a7eefc | |||
|
|
1d4e82335d | ||
|
|
7668c9bb7b | ||
|
|
baaee5f6ff |
31 changed files with 28802 additions and 373 deletions
4
.gitignore
vendored
4
.gitignore
vendored
|
|
@ -24,3 +24,7 @@ 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
|
||||||
|
|
|
||||||
59
Cargo.lock
generated
59
Cargo.lock
generated
|
|
@ -73,6 +73,12 @@ version = "1.0.102"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c"
|
checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "arbitrary-int"
|
||||||
|
version = "2.1.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "993a810118f8f37e9c4411c86f1c4c940a09a7ab34b7bf2d88d06f50c553fab7"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "arrayvec"
|
name = "arrayvec"
|
||||||
version = "0.7.6"
|
version = "0.7.6"
|
||||||
|
|
@ -186,9 +192,9 @@ checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "c-its-parser"
|
name = "c-its-parser"
|
||||||
version = "2.3.0"
|
version = "2.4.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f9bd739e1f76c017fc9fc6c21b9ff2aee4536bef357075d454e387e94a4d6287"
|
checksum = "7e94d9990b20498f621f164f23ae198c4365115eb4191ae106352ec74d4a4110"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"chrono",
|
"chrono",
|
||||||
"etherparse",
|
"etherparse",
|
||||||
|
|
@ -1165,6 +1171,7 @@ dependencies = [
|
||||||
"esp-println",
|
"esp-println",
|
||||||
"esp-radio",
|
"esp-radio",
|
||||||
"esp-rtos",
|
"esp-rtos",
|
||||||
|
"esp32-cits-core",
|
||||||
"geo-types",
|
"geo-types",
|
||||||
"log",
|
"log",
|
||||||
"mipidsi",
|
"mipidsi",
|
||||||
|
|
@ -1172,6 +1179,19 @@ 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]]
|
||||||
|
|
@ -1239,9 +1259,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "etherparse"
|
name = "etherparse"
|
||||||
version = "0.20.1"
|
version = "0.21.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "3ac016aaf11dfe643edcd088a166234bfcb72e7f06691abfcf21e9af524037f4"
|
checksum = "17304d06addb3283cdc4bd528e42dd95e73c8ee2d6492ffce415e93660885449"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"arrayvec",
|
"arrayvec",
|
||||||
]
|
]
|
||||||
|
|
@ -1371,13 +1391,11 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "geonetworking"
|
name = "geonetworking"
|
||||||
version = "0.3.0"
|
version = "0.4.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "6f6d36111f5057d3be7eb4e312a07edac4737fc791e4b0a73d812c6f710161e7"
|
checksum = "6da6fde770112587d5f5f7af439546e35bd35aa77a9e3e47e9968056783fe50d"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitvec",
|
"arbitrary-int",
|
||||||
"bitvec-nom2",
|
|
||||||
"bytes",
|
|
||||||
"nom",
|
"nom",
|
||||||
"num",
|
"num",
|
||||||
"num-traits",
|
"num-traits",
|
||||||
|
|
@ -2486,6 +2504,29 @@ 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"
|
||||||
|
|
|
||||||
29
Cargo.toml
29
Cargo.toml
|
|
@ -12,10 +12,10 @@ path = "./src/main.rs"
|
||||||
[[bin]]
|
[[bin]]
|
||||||
name = "test"
|
name = "test"
|
||||||
path = "./src/test.rs"
|
path = "./src/test.rs"
|
||||||
required-features = ["std"]
|
required-features = ["std", "cam"]
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = ["esp", "spat", "gnss", "screen"]
|
default = ["esp", "spat", "gnss_ubx", "screen"]
|
||||||
|
|
||||||
# build for ESP (needed to build test for `host-tuple`)
|
# build for ESP (needed to build test for `host-tuple`)
|
||||||
esp = [
|
esp = [
|
||||||
|
|
@ -30,13 +30,19 @@ 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"]
|
std = ["c-its-parser/std", "esp32-cits-core/std"]
|
||||||
|
|
||||||
|
# enable u-blox GNSS module
|
||||||
|
gnss_ubx = ["_gnss", "_uart", "dep:ublox"]
|
||||||
# enable L67K GNSS module
|
# enable L67K GNSS module
|
||||||
gnss = ["dep:embassy_gps"]
|
gnss_l67k = ["_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
|
||||||
|
cam_tx = ["c-its-parser/cam"]
|
||||||
# echo DENM events on serial and BLE
|
# echo DENM events on serial and BLE
|
||||||
denm = ["c-its-parser/denm"]
|
denm = ["c-its-parser/denm"]
|
||||||
# evaluate SPAT information
|
# evaluate SPAT information
|
||||||
|
|
@ -45,7 +51,13 @@ spat = ["c-its-parser/spatem", "c-its-parser/mapem"]
|
||||||
spat_debug = []
|
spat_debug = []
|
||||||
# enable ST7789 based display
|
# enable ST7789 based display
|
||||||
screen = ["dep:mipidsi", "dep:embedded-graphics", "dep:embedded-hal-bus", "dep:profont"]
|
screen = ["dep:mipidsi", "dep:embedded-graphics", "dep:embedded-hal-bus", "dep:profont"]
|
||||||
|
# enable I/O via UART
|
||||||
|
uart = ["_uart", "_gnss", "dep:prost"]
|
||||||
|
|
||||||
|
# internal feature, that any UART I/O is enabled
|
||||||
|
_uart = []
|
||||||
|
# internal feature, that any GNSS source is enabled
|
||||||
|
_gnss = []
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
esp-hal = { version = "~1.1.0", optional = true, features = ["esp32c5", "log-04", "unstable"] }
|
esp-hal = { version = "~1.1.0", optional = true, features = ["esp32c5", "log-04", "unstable"] }
|
||||||
|
|
@ -72,7 +84,7 @@ esp-backtrace = { version = "0.19.0", optional = true, features = [
|
||||||
"panic-handler",
|
"panic-handler",
|
||||||
"println",
|
"println",
|
||||||
] }
|
] }
|
||||||
esp-println = { version = "0.17.0", optional = true, features = ["esp32c5", "log-04"] }
|
esp-println = { version = "0.17.0", optional = true, default-features = false, features = ["colors", "critical-section", "jtag-serial", "esp32c5", "log-04"] }
|
||||||
esp-radio = { version = "0.18.0", optional = true, features = [
|
esp-radio = { version = "0.18.0", optional = true, features = [
|
||||||
"esp-alloc",
|
"esp-alloc",
|
||||||
"esp32c5",
|
"esp32c5",
|
||||||
|
|
@ -83,9 +95,11 @@ 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 }
|
||||||
c-its-parser = { version = "2.3.0", default-features = false, features = [
|
# needs newer GN with removed bitvec dependency
|
||||||
|
c-its-parser = { version = "2.4.0", default-features = false, features = [
|
||||||
"time",
|
"time",
|
||||||
"geo",
|
"geo",
|
||||||
"libm",
|
"libm",
|
||||||
|
|
@ -97,7 +111,8 @@ geo-types = { version = "0.7.19", default-features = false }
|
||||||
mipidsi = { version = "0.10.0", optional = true }
|
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", 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"
|
||||||
|
|
|
||||||
43
Readme.md
43
Readme.md
|
|
@ -5,15 +5,16 @@ 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)
|
||||||
- Seeed XIAO L67K (connected on GPIO12 (RX), GPIO11 (TX), GPIO1 (wakeup) and GPIO25 (reset))
|
- U-Blox M10Q GNSS (connected on GPIO12 (RX), GPIO11 (TX), and GPIO25 (reset))
|
||||||
only available with `gnss` feature (which is included in `spat` feature)
|
or Seeed XIAO L67K (connected on GPIO12 (RX), GPIO11 (TX), GPIO1 (wakeup) and GPIO25 (reset))
|
||||||
|
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 L67K GNSS module and in main application logic/ state.
|
Enable u-blox or 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.
|
||||||
|
|
@ -28,6 +29,32 @@ Shall "publish" the most relevant information of a DENMs via BLE
|
||||||
### CAM
|
### CAM
|
||||||
Just prints a line for each received CAM.
|
Just prints a line for each received CAM.
|
||||||
|
|
||||||
|
### CAM-TX
|
||||||
|
Generates and transmits a CAM when a GNSS update was received.
|
||||||
|
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
|
||||||
|
Enable UART I/O protocol according to [docs/uart-protocol.md](./docs/uart-protocol.md).
|
||||||
|
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
|
|
@ -52,13 +79,21 @@ rustup component add --toolchain nightly rustfmt
|
||||||
|
|
||||||
### Build and Flash
|
### Build and Flash
|
||||||
In default configuration only signal phases (SPAT) data is displayed for the upcoming intersection.
|
In default configuration only signal phases (SPAT) data is displayed for the upcoming intersection.
|
||||||
Additional features are available though feature flags:
|
Additional features are available through feature flags:
|
||||||
|
|
||||||
- `esp`: Run on ESP (required as feature flag to enable unit tests on host)
|
- `esp`: Run on ESP (required as feature flag to enable unit tests on host)
|
||||||
- `std`: Run on system with `std` library
|
- `std`: Run on system with `std` library
|
||||||
- `spat`: Enable signal phase (and intersection map) data
|
- `spat`: Enable signal phase (and intersection map) data
|
||||||
|
- `spat_debug`: Enable additional debug information for the SPAT/ GLOSA use case
|
||||||
- `denm`: Enable DENMs
|
- `denm`: Enable DENMs
|
||||||
- `cam`: Enable CAMs
|
- `cam`: Enable CAMs
|
||||||
|
- `cam_tx`: Enable CAM generation and transmission (needs time and position source)
|
||||||
|
|
||||||
|
Officially supported feature combinations are:
|
||||||
|
- `esp,spat,gnss_ubx,screen` (default features, "standalone")
|
||||||
|
- `esp,spat,gnss_ubx,screen,cam_tx` ("standalone with CAM")
|
||||||
|
- `esp,spat,gnss_ubx,screen,cam_tx,cam,denm`
|
||||||
|
- `esp,uart,cam,denm` ("smartphone companion")
|
||||||
|
|
||||||
Connect your ESP32-C5 and run:
|
Connect your ESP32-C5 and run:
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -35,18 +35,17 @@ enum GNAreaShape {
|
||||||
ETSI_AREASHAPE_CIRCLE = 0;
|
ETSI_AREASHAPE_CIRCLE = 0;
|
||||||
ETSI_AREASHAPE_RECTANGLE = 1;
|
ETSI_AREASHAPE_RECTANGLE = 1;
|
||||||
ETSI_AREASHAPE_ELLIPSIS = 2;
|
ETSI_AREASHAPE_ELLIPSIS = 2;
|
||||||
ETSI_AREASHAPE_UNKNOWN = 15;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// GeoNetworking Destination Area/ Source Position
|
// GeoNetworking Destination Area/ Source Position
|
||||||
message ITSPosition {
|
message ITSPosition {
|
||||||
optional int32 latitude = 1; // Latitude in 1/10th of micro degrees
|
required int32 latitude = 1; // Latitude in 1/10th of micro degrees
|
||||||
optional int32 longitude = 2; // Longitude in 1/10th of micro degrees
|
required int32 longitude = 2; // Longitude in 1/10th of micro degrees
|
||||||
}
|
}
|
||||||
|
|
||||||
// GeoNetworking Destination Area
|
// GeoNetworking Destination Area
|
||||||
message GNArea {
|
message GNArea {
|
||||||
optional GNAreaShape area = 1 [default = ETSI_AREASHAPE_UNKNOWN];
|
optional GNAreaShape area = 1;
|
||||||
optional ITSPosition position = 2;
|
optional ITSPosition position = 2;
|
||||||
optional int32 distA = 3; // Distance A of the geometric shape in meters
|
optional int32 distA = 3; // Distance A of the geometric shape in meters
|
||||||
optional int32 distB = 4; // Distance B of the geometric shape in meters, mandatory for rectangle and ellipsis
|
optional int32 distB = 4; // Distance B of the geometric shape in meters, mandatory for rectangle and ellipsis
|
||||||
|
|
@ -55,8 +54,6 @@ message GNArea {
|
||||||
|
|
||||||
// GeoNetworking Transport
|
// GeoNetworking Transport
|
||||||
enum GNTransport {
|
enum GNTransport {
|
||||||
GN_TRANSPORT_DEFAULT = 0;
|
|
||||||
GN_TRANSPORT_GUC = 2; // GeoUnicast
|
|
||||||
GN_TRANSPORT_GAC = 3; // Geographically-Scoped Anycast
|
GN_TRANSPORT_GAC = 3; // Geographically-Scoped Anycast
|
||||||
GN_TRANSPORT_GBC = 4; // Geographically-Scoped broadcast
|
GN_TRANSPORT_GBC = 4; // Geographically-Scoped broadcast
|
||||||
GN_TRANSPORT_TSB = 5; // Topologically-scoped broadcast
|
GN_TRANSPORT_TSB = 5; // Topologically-scoped broadcast
|
||||||
|
|
@ -70,6 +67,8 @@ message RawMsgTx {
|
||||||
required GNTransport gnTransport = 3;
|
required GNTransport gnTransport = 3;
|
||||||
required GNArea gnArea = 4;
|
required GNArea gnArea = 4;
|
||||||
required uint32 hopLimit = 5;
|
required uint32 hopLimit = 5;
|
||||||
|
|
||||||
|
optional uint32 stationType = 6; // for source position vector
|
||||||
}
|
}
|
||||||
|
|
||||||
// ------------------------------
|
// ------------------------------
|
||||||
|
|
@ -77,7 +76,8 @@ message RawMsgTx {
|
||||||
// ------------------------------
|
// ------------------------------
|
||||||
|
|
||||||
message PositionState {
|
message PositionState {
|
||||||
required Position position = 1;
|
required sfixed64 timestamp_ms = 1; // current UNIX time in milliseconds
|
||||||
|
required Position position = 2;
|
||||||
optional uint32 heading = 3; // heading in 1/10 deg
|
optional uint32 heading = 3; // heading in 1/10 deg
|
||||||
optional uint32 speed = 4; // speed in 1/100 m/s
|
optional uint32 speed = 4; // speed in 1/100 m/s
|
||||||
}
|
}
|
||||||
|
|
|
||||||
70
feat-permut.sh
Executable file
70
feat-permut.sh
Executable file
|
|
@ -0,0 +1,70 @@
|
||||||
|
#!/bin/bash
|
||||||
|
## Build/ Test/ Clippy all supported feature permutations
|
||||||
|
set -eu
|
||||||
|
|
||||||
|
mode=${1:-help}
|
||||||
|
wasm_target="--chrome --headless"
|
||||||
|
extended=${2:-false}
|
||||||
|
|
||||||
|
|
||||||
|
features_base=(
|
||||||
|
uart,cam,denm
|
||||||
|
spat,gnss_ubx,screen
|
||||||
|
spat,gnss_ubx,screen,cam_tx
|
||||||
|
)
|
||||||
|
features_more=(
|
||||||
|
spat,gnss_ubx,screen,cam,denm
|
||||||
|
spat,gnss_ubx,screen,cam_tx,cam,denm
|
||||||
|
)
|
||||||
|
features_test=(
|
||||||
|
spat,cam,denm,uart
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
runBuild () {
|
||||||
|
for feat in "${features[@]}"; do
|
||||||
|
echo "cargo build for $feat ..."
|
||||||
|
cargo build --release --no-default-features -F esp,$feat
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
runTest () {
|
||||||
|
for feat in "${features_test[@]}"; do
|
||||||
|
echo "cargo test for $feat ..."
|
||||||
|
cargo test --target host-tuple --bin test --no-default-features -F std,$feat
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
runClippy () {
|
||||||
|
for feat in "${features[@]}"; do
|
||||||
|
echo "clippy for $feat ..."
|
||||||
|
cargo clippy --no-default-features -F esp,$feat -- -Wclippy::pedantic
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
if [ $extended == "--more" ]; then
|
||||||
|
echo "running with extended feature list"
|
||||||
|
features=( "${features_base[@]}" "${features_more[@]}" )
|
||||||
|
else
|
||||||
|
echo "running standard feature list"
|
||||||
|
features=( "${features_base[@]}" )
|
||||||
|
fi
|
||||||
|
|
||||||
|
case $mode in
|
||||||
|
build)
|
||||||
|
runBuild
|
||||||
|
;;
|
||||||
|
clippy)
|
||||||
|
runClippy
|
||||||
|
;;
|
||||||
|
test)
|
||||||
|
runTest
|
||||||
|
;;
|
||||||
|
all)
|
||||||
|
runBuild
|
||||||
|
runClippy
|
||||||
|
runTest
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo "Usage: $0 [build | clippy | test | all] [--more]"
|
||||||
|
esac
|
||||||
BIN
hardware/c_its-companion-v0.1-back.jpg
Normal file
BIN
hardware/c_its-companion-v0.1-back.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 145 KiB |
BIN
hardware/c_its-companion-v0.1-front.jpg
Normal file
BIN
hardware/c_its-companion-v0.1-front.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 142 KiB |
8034
hardware/c_its-companion/c_its-companion.kicad_pcb
Normal file
8034
hardware/c_its-companion/c_its-companion.kicad_pcb
Normal file
File diff suppressed because it is too large
Load diff
138
hardware/c_its-companion/c_its-companion.kicad_prl
Normal file
138
hardware/c_its-companion/c_its-companion.kicad_prl
Normal file
|
|
@ -0,0 +1,138 @@
|
||||||
|
{
|
||||||
|
"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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
744
hardware/c_its-companion/c_its-companion.kicad_pro
Normal file
744
hardware/c_its-companion/c_its-companion.kicad_pro
Normal file
|
|
@ -0,0 +1,744 @@
|
||||||
|
{
|
||||||
|
"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": []
|
||||||
|
}
|
||||||
|
}
|
||||||
4985
hardware/c_its-companion/c_its-companion.kicad_sch
Normal file
4985
hardware/c_its-companion/c_its-companion.kicad_sch
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -0,0 +1 @@
|
||||||
|
{"ARCHIVE_NAME": "", "EXTRA_LAYERS": "", "ALL_ACTIVE_LAYERS": false, "EXTEND_EDGE_CUT": false, "ALTERNATIVE_EDGE_CUT": false, "AUTO TRANSLATE": true, "AUTO FILL": true, "EXCLUDE DNP": false, "OPEN BROWSER": true, "BACKUP_OPT": true}
|
||||||
4
hardware/c_its-companion/fp-lib-table
Normal file
4
hardware/c_its-companion/fp-lib-table
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
(fp_lib_table
|
||||||
|
(version 7)
|
||||||
|
(lib (name "library") (type "KiCad") (uri "${KIPRJMOD}/library") (options "") (descr ""))
|
||||||
|
)
|
||||||
11661
hardware/c_its-companion/library/Seeed_Studio_XIAO_Series.kicad_sym
Normal file
11661
hardware/c_its-companion/library/Seeed_Studio_XIAO_Series.kicad_sym
Normal file
File diff suppressed because it is too large
Load diff
542
hardware/c_its-companion/library/XIAO-ESP32-C5-DIP.kicad_mod
Normal file
542
hardware/c_its-companion/library/XIAO-ESP32-C5-DIP.kicad_mod
Normal file
|
|
@ -0,0 +1,542 @@
|
||||||
|
(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)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
558
hardware/c_its-companion/library/XIAO-ESP32-C5-SMD-DIP.kicad_mod
Normal file
558
hardware/c_its-companion/library/XIAO-ESP32-C5-SMD-DIP.kicad_mod
Normal file
|
|
@ -0,0 +1,558 @@
|
||||||
|
(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)
|
||||||
|
)
|
||||||
445
hardware/c_its-companion/library/XIAO-ESP32-C5-SMD.kicad_mod
Normal file
445
hardware/c_its-companion/library/XIAO-ESP32-C5-SMD.kicad_mod
Normal file
|
|
@ -0,0 +1,445 @@
|
||||||
|
(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)
|
||||||
|
)
|
||||||
BIN
hardware/c_its-companion/production/C-ITS_Companion_v0.1.zip
Normal file
BIN
hardware/c_its-companion/production/C-ITS_Companion_v0.1.zip
Normal file
Binary file not shown.
4
hardware/c_its-companion/sym-lib-table
Normal file
4
hardware/c_its-companion/sym-lib-table
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
(sym_lib_table
|
||||||
|
(version 7)
|
||||||
|
(lib (name "Seeed_Studio_XIAO_Series") (type "KiCad") (uri "${KIPRJMOD}/library/Seeed_Studio_XIAO_Series.kicad_sym") (options "") (descr ""))
|
||||||
|
)
|
||||||
6
run-smartphone.sh
Executable file
6
run-smartphone.sh
Executable file
|
|
@ -0,0 +1,6 @@
|
||||||
|
#!/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
|
||||||
6
run-standalone-cam.sh
Executable file
6
run-standalone-cam.sh
Executable file
|
|
@ -0,0 +1,6 @@
|
||||||
|
#!/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
|
||||||
6
run-standalone-no_cam.sh
Executable file
6
run-standalone-no_cam.sh
Executable file
|
|
@ -0,0 +1,6 @@
|
||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
## Builds common config for standalone device
|
||||||
|
## -> enable SPAT/ GLOSA with GNSS and a screen
|
||||||
|
|
||||||
|
cargo run --release
|
||||||
465
src/applogic/cam_tx.rs
Normal file
465
src/applogic/cam_tx.rs
Normal file
|
|
@ -0,0 +1,465 @@
|
||||||
|
//! C-ITS CAM Transmission
|
||||||
|
|
||||||
|
use alloc::vec::Vec;
|
||||||
|
|
||||||
|
use c_its_parser::gn as geonetworking;
|
||||||
|
use c_its_parser::standards::cam_1_4_1::cam_pdu_descriptions;
|
||||||
|
use c_its_parser::standards::cdd_1_3_1_1::its_container;
|
||||||
|
use c_its_parser::standards::extensions;
|
||||||
|
|
||||||
|
const PH_MAX_LENGHT_M: f32 = 500.;
|
||||||
|
const PH_MAX_ITEMS: usize = 23;
|
||||||
|
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)]
|
||||||
|
pub struct CamState {
|
||||||
|
station_id: u32,
|
||||||
|
station_type: its_container::StationType,
|
||||||
|
|
||||||
|
vehicle_length_dm: u16, // vehicle length is in 10cm steps
|
||||||
|
vehicle_width_dm: u8, // vehicle width is in 10cm steps
|
||||||
|
|
||||||
|
time: chrono::DateTime<chrono::Utc>,
|
||||||
|
pos_state: esp32_cits_core::PosVel,
|
||||||
|
|
||||||
|
/// database for path history, oldest item at the back of the vector
|
||||||
|
path_history: Vec<PathPoint>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||||
|
struct PathPoint {
|
||||||
|
pos: geo_types::Point,
|
||||||
|
time: chrono::DateTime<chrono::Utc>,
|
||||||
|
/// distance to previous point. Ignore on last point!
|
||||||
|
dist_m: f32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PathPoint {
|
||||||
|
fn new_from_pos(pos: &esp32_cits_core::PosVel, time: chrono::DateTime<chrono::Utc>) -> Self {
|
||||||
|
Self {
|
||||||
|
pos: pos.position,
|
||||||
|
time,
|
||||||
|
dist_m: 0.,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn new_from_pos_with_dist(
|
||||||
|
pos: &esp32_cits_core::PosVel,
|
||||||
|
time: chrono::DateTime<chrono::Utc>,
|
||||||
|
dist_m: f32,
|
||||||
|
) -> Self {
|
||||||
|
Self {
|
||||||
|
pos: pos.position,
|
||||||
|
time,
|
||||||
|
dist_m,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CamState {
|
||||||
|
pub fn new(
|
||||||
|
station_id: u32,
|
||||||
|
station_type: c_its_parser::standards::extensions::ItsStationType,
|
||||||
|
vehicle_length_dm: u16,
|
||||||
|
vehicle_width_dm: u8,
|
||||||
|
) -> Self {
|
||||||
|
Self {
|
||||||
|
station_id,
|
||||||
|
station_type: station_type.into(),
|
||||||
|
vehicle_length_dm,
|
||||||
|
vehicle_width_dm,
|
||||||
|
time: chrono::DateTime::<chrono::Utc>::default(),
|
||||||
|
pos_state: esp32_cits_core::PosVel::default(),
|
||||||
|
path_history: Vec::with_capacity(PH_MAX_ITEMS + 1),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn update(&mut self, time: chrono::DateTime<chrono::Utc>, pos: esp32_cits_core::PosVel) {
|
||||||
|
self.time = time;
|
||||||
|
self.pos_state = pos;
|
||||||
|
|
||||||
|
// Update path history
|
||||||
|
Self::update_ph(&mut self.path_history, time, &self.pos_state);
|
||||||
|
}
|
||||||
|
|
||||||
|
// this is a function, not a method for easier testing
|
||||||
|
fn update_ph(
|
||||||
|
path_history: &mut Vec<PathPoint>,
|
||||||
|
time: chrono::DateTime<chrono::Utc>,
|
||||||
|
pos: &esp32_cits_core::PosVel,
|
||||||
|
) {
|
||||||
|
// Update path history
|
||||||
|
if path_history.is_empty() {
|
||||||
|
// prime with current position if empty
|
||||||
|
path_history.push(PathPoint::new_from_pos(pos, time));
|
||||||
|
} else {
|
||||||
|
// unwrap is fine since we checked for non-empty before
|
||||||
|
let prev = path_history.first().unwrap();
|
||||||
|
|
||||||
|
// only add a point, if we moved more than PH_MIN_DIST_M
|
||||||
|
#[allow(clippy::cast_precision_loss, clippy::cast_possible_truncation)]
|
||||||
|
let dist = crate::geo_alg::haversine_dist(&prev.pos, &pos.position) as f32;
|
||||||
|
if dist > PH_MIN_DIST_M {
|
||||||
|
// new items shall be at the front of the vector
|
||||||
|
path_history.insert(0, PathPoint::new_from_pos_with_dist(pos, time, dist));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// check length (PH_MAX_LENGHT_M and PH_MAX_ITEMS points max)
|
||||||
|
if path_history.len() > PH_MAX_ITEMS {
|
||||||
|
let _ = path_history.pop();
|
||||||
|
}
|
||||||
|
|
||||||
|
let (_, cutoff_idx) =
|
||||||
|
path_history
|
||||||
|
.iter()
|
||||||
|
.enumerate()
|
||||||
|
.fold((0., None), |(len, cutoff_idx), (idx, item)| {
|
||||||
|
if cutoff_idx.is_none() {
|
||||||
|
let new_len = len + item.dist_m;
|
||||||
|
let new_cutoff_idx = if len > PH_MAX_LENGHT_M {
|
||||||
|
Some(idx)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
|
(new_len, new_cutoff_idx)
|
||||||
|
} else {
|
||||||
|
(len, cutoff_idx)
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if let Some(idx) = cutoff_idx {
|
||||||
|
path_history.drain(idx..);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn make_ph(&self) -> Vec<its_container::PathPoint> {
|
||||||
|
let mut ph = alloc::vec![];
|
||||||
|
|
||||||
|
self.path_history.iter().fold(
|
||||||
|
PathPoint::new_from_pos(&self.pos_state, self.time),
|
||||||
|
|prev_point, pt| {
|
||||||
|
// calculate delta position and append to `ph`
|
||||||
|
|
||||||
|
let delta_time = pt.time - prev_point.time;
|
||||||
|
let delta_lat = pt.pos.y() - prev_point.pos.y();
|
||||||
|
let delta_lon = pt.pos.x() - prev_point.pos.x();
|
||||||
|
|
||||||
|
// delta time is in 1/100 second / 10 ms steps
|
||||||
|
let path_delta_time = its_container::PathDeltaTime(
|
||||||
|
(delta_time.num_milliseconds().abs() / 10)
|
||||||
|
.min(65_535) // clamp to max value
|
||||||
|
.into(),
|
||||||
|
);
|
||||||
|
let delta_latitude = its_container::DeltaLatitude::from_deg(delta_lat);
|
||||||
|
let delta_longitude = its_container::DeltaLongitude::from_deg(delta_lon);
|
||||||
|
let delta_altitude = its_container::DeltaAltitude::unavailable();
|
||||||
|
|
||||||
|
let ppoint = its_container::PathPoint::new(
|
||||||
|
its_container::DeltaReferencePosition::new(
|
||||||
|
delta_latitude,
|
||||||
|
delta_longitude,
|
||||||
|
delta_altitude,
|
||||||
|
),
|
||||||
|
Some(path_delta_time),
|
||||||
|
);
|
||||||
|
|
||||||
|
ph.push(ppoint);
|
||||||
|
|
||||||
|
*pt
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
ph
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::too_many_lines)]
|
||||||
|
fn make_cam(&self) -> Result<cam_pdu_descriptions::CAM, alloc::string::String> {
|
||||||
|
let header = its_container::ItsPduHeader::new(
|
||||||
|
2,
|
||||||
|
extensions::ItsMessageId::Cam.as_u8(),
|
||||||
|
its_container::StationID(self.station_id),
|
||||||
|
);
|
||||||
|
|
||||||
|
let generation_delta_time = {
|
||||||
|
let its_timestamp = c_its_parser::time_utils::TimestampIts::from(self.time);
|
||||||
|
|
||||||
|
#[allow(clippy::cast_possible_truncation)]
|
||||||
|
let ts_mod = (its_timestamp.0 % 65_536) as u16;
|
||||||
|
|
||||||
|
cam_pdu_descriptions::GenerationDeltaTime(ts_mod)
|
||||||
|
};
|
||||||
|
|
||||||
|
let heading = match self.pos_state.heading_deg {
|
||||||
|
Some(value) => its_container::Heading {
|
||||||
|
heading_value: its_container::HeadingValue::from_deg(value)
|
||||||
|
.map_err(|err| alloc::format!("Failed to make HeadingValue: {err}"))?,
|
||||||
|
heading_confidence: its_container::HeadingConfidence(127), // unavailable for now
|
||||||
|
},
|
||||||
|
None => its_container::Heading {
|
||||||
|
heading_value: its_container::HeadingValue(0),
|
||||||
|
heading_confidence: its_container::HeadingConfidence(127), // unavailable for now
|
||||||
|
},
|
||||||
|
};
|
||||||
|
let speed = match self.pos_state.speed_mps {
|
||||||
|
Some(value) => its_container::Speed {
|
||||||
|
speed_value: its_container::SpeedValue::from_mps(value)
|
||||||
|
.map_err(|err| alloc::format!("Failed to make SpeedValue: {err}"))?,
|
||||||
|
speed_confidence: its_container::SpeedConfidence(127), // unavailable for now
|
||||||
|
},
|
||||||
|
None => its_container::Speed {
|
||||||
|
speed_value: its_container::SpeedValue(0),
|
||||||
|
speed_confidence: its_container::SpeedConfidence(127), // unavailable for now
|
||||||
|
},
|
||||||
|
};
|
||||||
|
let reference_position = its_container::ReferencePosition {
|
||||||
|
latitude: its_container::Latitude::from_deg(self.pos_state.position.y()),
|
||||||
|
longitude: its_container::Longitude::from_deg(self.pos_state.position.x()),
|
||||||
|
position_confidence_ellipse: its_container::PosConfidenceEllipse {
|
||||||
|
semi_major_confidence: its_container::SemiAxisLength(4095), // unavailable for now
|
||||||
|
semi_minor_confidence: its_container::SemiAxisLength(4095), // unavailable for now
|
||||||
|
semi_major_orientation: its_container::HeadingValue::unavailable(),
|
||||||
|
},
|
||||||
|
altitude: its_container::Altitude {
|
||||||
|
altitude_value: its_container::AltitudeValue(800_001), // unavailable for now
|
||||||
|
altitude_confidence: its_container::AltitudeConfidence::unavailable,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
let basic_container = cam_pdu_descriptions::BasicContainer::new(
|
||||||
|
self.station_type.clone(),
|
||||||
|
reference_position,
|
||||||
|
);
|
||||||
|
let high_frequency_container =
|
||||||
|
cam_pdu_descriptions::HighFrequencyContainer::basicVehicleContainerHighFrequency(
|
||||||
|
cam_pdu_descriptions::BasicVehicleContainerHighFrequency {
|
||||||
|
heading,
|
||||||
|
speed,
|
||||||
|
drive_direction: its_container::DriveDirection::unavailable,
|
||||||
|
vehicle_length: its_container::VehicleLength {
|
||||||
|
vehicle_length_value: its_container::VehicleLengthValue(
|
||||||
|
self.vehicle_length_dm,
|
||||||
|
),
|
||||||
|
vehicle_length_confidence_indication:
|
||||||
|
its_container::VehicleLengthConfidenceIndication::unavailable,
|
||||||
|
},
|
||||||
|
vehicle_width: its_container::VehicleWidth(self.vehicle_width_dm),
|
||||||
|
longitudinal_acceleration: its_container::LongitudinalAcceleration {
|
||||||
|
longitudinal_acceleration_value:
|
||||||
|
its_container::LongitudinalAccelerationValue::unavailable(),
|
||||||
|
longitudinal_acceleration_confidence: its_container::AccelerationConfidence(
|
||||||
|
102, // unavailable
|
||||||
|
),
|
||||||
|
},
|
||||||
|
curvature: its_container::Curvature {
|
||||||
|
curvature_value: its_container::CurvatureValue::unavailable(),
|
||||||
|
curvature_confidence: its_container::CurvatureConfidence::unavailable,
|
||||||
|
},
|
||||||
|
curvature_calculation_mode:
|
||||||
|
its_container::CurvatureCalculationMode::unavailable,
|
||||||
|
yaw_rate: its_container::YawRate {
|
||||||
|
yaw_rate_value: its_container::YawRateValue::unavailable(),
|
||||||
|
yaw_rate_confidence: its_container::YawRateConfidence::unavailable,
|
||||||
|
},
|
||||||
|
acceleration_control: None,
|
||||||
|
lane_position: None,
|
||||||
|
steering_wheel_angle: None,
|
||||||
|
lateral_acceleration: None,
|
||||||
|
vertical_acceleration: None,
|
||||||
|
performance_class: None,
|
||||||
|
cen_dsrc_tolling_zone: None,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
let low_frequency_container =
|
||||||
|
cam_pdu_descriptions::LowFrequencyContainer::basicVehicleContainerLowFrequency(
|
||||||
|
cam_pdu_descriptions::BasicVehicleContainerLowFrequency {
|
||||||
|
vehicle_role: its_container::VehicleRole::default,
|
||||||
|
exterior_lights: its_container::ExteriorLights::default(),
|
||||||
|
path_history: its_container::PathHistory(self.make_ph()),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
let cam_parameters = cam_pdu_descriptions::CamParameters::new(
|
||||||
|
basic_container,
|
||||||
|
high_frequency_container,
|
||||||
|
Some(low_frequency_container),
|
||||||
|
None,
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok(cam_pdu_descriptions::CAM::new(
|
||||||
|
header,
|
||||||
|
cam_pdu_descriptions::CoopAwareness::new(generation_delta_time, cam_parameters),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl esp32_cits_core::tx::GnItsPayload for CamState {
|
||||||
|
fn make_eh(
|
||||||
|
&self,
|
||||||
|
address: [u8; 6],
|
||||||
|
own_position: &esp32_cits_core::PosVel,
|
||||||
|
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,
|
||||||
|
> {
|
||||||
|
let station_type = geonetworking::en302636_4_1::StationType::try_from(self.station_type.0)
|
||||||
|
.unwrap_or_default();
|
||||||
|
|
||||||
|
// CAM always uses SHB, hop-limit 1
|
||||||
|
Ok((
|
||||||
|
esp32_cits_core::tx::gn::make_shb_eh(address, station_type, own_position, time)?,
|
||||||
|
None,
|
||||||
|
1,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn make_payload(
|
||||||
|
&self,
|
||||||
|
) -> Result<(Vec<u8>, c_its_parser::standards::extensions::ItsMessageId), alloc::string::String>
|
||||||
|
{
|
||||||
|
let msg = self.make_cam()?;
|
||||||
|
let uper = msg.encode_to_uper()?;
|
||||||
|
Ok((uper, c_its_parser::standards::extensions::ItsMessageId::Cam))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use alloc::vec;
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
use crate::init_test_env_logger;
|
||||||
|
|
||||||
|
fn posstate_from_pos(position: geo_types::Point) -> esp32_cits_core::PosVel {
|
||||||
|
esp32_cits_core::PosVel {
|
||||||
|
position,
|
||||||
|
heading_deg: None,
|
||||||
|
speed_mps: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn cam_path_history_update() {
|
||||||
|
init_test_env_logger();
|
||||||
|
|
||||||
|
let time = chrono::DateTime::from_timestamp(0, 0).unwrap();
|
||||||
|
let p1 = geo_types::Point::new(10., 53.5);
|
||||||
|
let p2 = geo_types::Point::new(10., 53.5 + 0.000045); // ~5m further (< PH_MIN_DIST_M)
|
||||||
|
let p3 = geo_types::Point::new(10., 53.5 + 0.00027); // ~30m further (> PH_MIN_DIST_M)
|
||||||
|
|
||||||
|
let ps1 = posstate_from_pos(p1);
|
||||||
|
let pp1 = PathPoint::new_from_pos(&ps1, time);
|
||||||
|
let ps2 = posstate_from_pos(p2);
|
||||||
|
let pp2 = PathPoint::new_from_pos(&ps2, time);
|
||||||
|
let ps3 = posstate_from_pos(p3);
|
||||||
|
let pp3 = PathPoint::new_from_pos_with_dist(&ps3, time, 30.02267);
|
||||||
|
|
||||||
|
let mut path_history: Vec<PathPoint> = vec![];
|
||||||
|
|
||||||
|
// add first point, expect one point in PH
|
||||||
|
CamState::update_ph(&mut path_history, time, &posstate_from_pos(p1));
|
||||||
|
assert_eq!(1, path_history.len());
|
||||||
|
assert_eq!(&pp1, path_history.first().unwrap());
|
||||||
|
|
||||||
|
// add insignificant point, expect still one point in PH
|
||||||
|
CamState::update_ph(&mut path_history, time, &posstate_from_pos(p2));
|
||||||
|
assert_eq!(1, path_history.len());
|
||||||
|
assert_eq!(&pp1, path_history.first().unwrap());
|
||||||
|
|
||||||
|
// add significantly moved point, expect [pp3, pp1] vector
|
||||||
|
CamState::update_ph(&mut path_history, time, &posstate_from_pos(p3));
|
||||||
|
assert_eq!(2, path_history.len());
|
||||||
|
assert_eq!(&pp3, path_history.first().unwrap());
|
||||||
|
assert_eq!(&pp1, path_history.last().unwrap());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn cam_path_history_max_len() {
|
||||||
|
init_test_env_logger();
|
||||||
|
|
||||||
|
let time = chrono::DateTime::from_timestamp(0, 0).unwrap();
|
||||||
|
let p1 = geo_types::Point::new(10., 53.5);
|
||||||
|
let p2 = geo_types::Point::new(10., 53.5 + 0.00023); // at ~25m from p1 (> PH_MIN_DIST_M)
|
||||||
|
let p3 = geo_types::Point::new(10., 53.5 + 0.00445); // at ~495m from p1 (slightly less than PH_MAX_LENGHT_M)
|
||||||
|
let p4 = geo_types::Point::new(10., 53.5 + 0.00477); // at ~530m from p1 (slightly more than PH_MAX_LENGHT_M)
|
||||||
|
|
||||||
|
let ps1 = posstate_from_pos(p1);
|
||||||
|
let pp1 = PathPoint::new_from_pos(&ps1, time);
|
||||||
|
let ps2 = posstate_from_pos(p2);
|
||||||
|
let pp2 = PathPoint::new_from_pos(&ps2, time);
|
||||||
|
let ps3 = posstate_from_pos(p3);
|
||||||
|
let pp3 = PathPoint::new_from_pos(&ps3, time);
|
||||||
|
let ps4 = posstate_from_pos(p4);
|
||||||
|
let pp4 = PathPoint::new_from_pos(&ps4, time);
|
||||||
|
|
||||||
|
let mut path_history: Vec<PathPoint> = vec![];
|
||||||
|
|
||||||
|
// prime with p3, p4 (should give a length of ~470m)
|
||||||
|
CamState::update_ph(&mut path_history, time, &posstate_from_pos(p4));
|
||||||
|
CamState::update_ph(&mut path_history, time, &posstate_from_pos(p3));
|
||||||
|
assert_eq!(2, path_history.len());
|
||||||
|
|
||||||
|
// Add p2, should drop p4 from vector because of > PH_MAX_LENGHT_M
|
||||||
|
CamState::update_ph(&mut path_history, time, &posstate_from_pos(p2));
|
||||||
|
assert_eq!(2, path_history.len());
|
||||||
|
assert_eq!(p2, path_history.get(0).unwrap().pos);
|
||||||
|
assert_eq!(469.24323, path_history.get(0).unwrap().dist_m);
|
||||||
|
assert_eq!(p3, path_history.get(1).unwrap().pos);
|
||||||
|
assert_eq!(35.582424, path_history.get(1).unwrap().dist_m);
|
||||||
|
|
||||||
|
// Add p1, should not drop anything from vector because still < PH_MAX_LENGHT_M
|
||||||
|
CamState::update_ph(&mut path_history, time, &posstate_from_pos(p1));
|
||||||
|
assert_eq!(3, path_history.len());
|
||||||
|
assert_eq!(p1, path_history.get(0).unwrap().pos);
|
||||||
|
assert_eq!(25.57487, path_history.get(0).unwrap().dist_m);
|
||||||
|
assert_eq!(p2, path_history.get(1).unwrap().pos);
|
||||||
|
assert_eq!(469.24323, path_history.get(1).unwrap().dist_m);
|
||||||
|
assert_eq!(p3, path_history.get(2).unwrap().pos);
|
||||||
|
assert_eq!(35.582424, path_history.get(2).unwrap().dist_m);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -6,7 +6,7 @@ use alloc::rc::Rc;
|
||||||
use alloc::vec::Vec;
|
use alloc::vec::Vec;
|
||||||
|
|
||||||
use log::info;
|
use log::info;
|
||||||
#[cfg(any(feature = "gnss", feature = "spat"))]
|
#[cfg(feature = "spat")]
|
||||||
use log::warn;
|
use log::warn;
|
||||||
#[cfg(feature = "spat")]
|
#[cfg(feature = "spat")]
|
||||||
use log::{debug, error};
|
use log::{debug, error};
|
||||||
|
|
@ -18,6 +18,8 @@ pub mod v2x;
|
||||||
|
|
||||||
#[cfg(feature = "cam")]
|
#[cfg(feature = "cam")]
|
||||||
pub mod cam;
|
pub mod cam;
|
||||||
|
#[cfg(any(feature = "cam_tx", test))]
|
||||||
|
pub mod cam_tx;
|
||||||
#[cfg(feature = "denm")]
|
#[cfg(feature = "denm")]
|
||||||
pub mod denm;
|
pub mod denm;
|
||||||
#[cfg(feature = "spat")]
|
#[cfg(feature = "spat")]
|
||||||
|
|
@ -47,7 +49,7 @@ pub struct State {
|
||||||
time: chrono::NaiveDateTime,
|
time: chrono::NaiveDateTime,
|
||||||
|
|
||||||
#[allow(unused)]
|
#[allow(unused)]
|
||||||
pos: PositionState,
|
pos: esp32_cits_core::PosVel,
|
||||||
|
|
||||||
#[cfg(feature = "spat")]
|
#[cfg(feature = "spat")]
|
||||||
last_prune: chrono::NaiveDateTime,
|
last_prune: chrono::NaiveDateTime,
|
||||||
|
|
@ -128,15 +130,7 @@ pub struct GnssFix {
|
||||||
pub speed_mps: Option<f32>,
|
pub speed_mps: Option<f32>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(unused)]
|
impl From<&GnssFix> for esp32_cits_core::PosVel {
|
||||||
#[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);
|
||||||
|
|
||||||
|
|
@ -147,7 +141,7 @@ impl From<&GnssFix> for PositionState {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl From<GnssFix> for PositionState {
|
impl From<GnssFix> for esp32_cits_core::PosVel {
|
||||||
fn from(value: GnssFix) -> Self {
|
fn from(value: GnssFix) -> Self {
|
||||||
(&value).into()
|
(&value).into()
|
||||||
}
|
}
|
||||||
|
|
@ -210,7 +204,7 @@ impl State {
|
||||||
Self {
|
Self {
|
||||||
initialized: false,
|
initialized: false,
|
||||||
time: chrono::NaiveDateTime::default(),
|
time: chrono::NaiveDateTime::default(),
|
||||||
pos: PositionState::default(),
|
pos: esp32_cits_core::PosVel::default(),
|
||||||
#[cfg(feature = "spat")]
|
#[cfg(feature = "spat")]
|
||||||
last_prune: chrono::NaiveDateTime::default(),
|
last_prune: chrono::NaiveDateTime::default(),
|
||||||
#[cfg(feature = "spat")]
|
#[cfg(feature = "spat")]
|
||||||
|
|
@ -637,6 +631,11 @@ impl State {
|
||||||
pub fn time(&self) -> chrono::prelude::NaiveDateTime {
|
pub fn time(&self) -> chrono::prelude::NaiveDateTime {
|
||||||
self.time
|
self.time
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(unused)]
|
||||||
|
pub fn pos(&self) -> &esp32_cits_core::PosVel {
|
||||||
|
&self.pos
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
|
@ -688,13 +687,13 @@ mod tests {
|
||||||
let own_pos_passed = geo_types::Point::new(9.977217, 53.5560602);
|
let own_pos_passed = geo_types::Point::new(9.977217, 53.5560602);
|
||||||
let own_heading = 90.;
|
let own_heading = 90.;
|
||||||
|
|
||||||
state.heading_deg = Some(own_heading);
|
state.pos.heading_deg = Some(own_heading);
|
||||||
|
|
||||||
state.position = own_pos_infront;
|
state.pos.position = own_pos_infront;
|
||||||
state.run_glosa();
|
state.run_glosa();
|
||||||
assert!(matches!(state.glosa_state, GlosaState::Idle));
|
assert!(matches!(state.glosa_state, GlosaState::Idle));
|
||||||
|
|
||||||
state.position = own_pos_inside;
|
state.pos.position = own_pos_inside;
|
||||||
state.run_glosa();
|
state.run_glosa();
|
||||||
assert!(matches!(state.glosa_state, GlosaState::Locked(_)));
|
assert!(matches!(state.glosa_state, GlosaState::Locked(_)));
|
||||||
if let GlosaState::Locked(info) = &state.glosa_state {
|
if let GlosaState::Locked(info) = &state.glosa_state {
|
||||||
|
|
@ -702,7 +701,7 @@ mod tests {
|
||||||
assert!(info.lane_id == 1 || info.lane_id == 2);
|
assert!(info.lane_id == 1 || info.lane_id == 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
state.position = own_pos_inside;
|
state.pos.position = own_pos_inside;
|
||||||
state.run_glosa();
|
state.run_glosa();
|
||||||
assert!(matches!(state.glosa_state, GlosaState::Locked(_)));
|
assert!(matches!(state.glosa_state, GlosaState::Locked(_)));
|
||||||
if let GlosaState::Locked(info) = &state.glosa_state {
|
if let GlosaState::Locked(info) = &state.glosa_state {
|
||||||
|
|
@ -710,7 +709,7 @@ mod tests {
|
||||||
assert!(info.lane_id == 1 || info.lane_id == 2);
|
assert!(info.lane_id == 1 || info.lane_id == 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
state.position = own_pos_passed;
|
state.pos.position = own_pos_passed;
|
||||||
state.run_glosa();
|
state.run_glosa();
|
||||||
assert!(matches!(state.glosa_state, GlosaState::Idle));
|
assert!(matches!(state.glosa_state, GlosaState::Idle));
|
||||||
}
|
}
|
||||||
|
|
|
||||||
466
src/io.rs
466
src/io.rs
|
|
@ -5,6 +5,12 @@ pub mod msg {
|
||||||
include!(concat!(env!("OUT_DIR"), "/c_its_io.rs"));
|
include!(concat!(env!("OUT_DIR"), "/c_its_io.rs"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
use alloc::string::ToString;
|
||||||
|
use alloc::vec::Vec;
|
||||||
|
|
||||||
|
use c_its_parser::gn as geonetworking;
|
||||||
|
use log::error;
|
||||||
|
|
||||||
impl From<msg::RawMsgRx> for msg::SerialOutputMsg {
|
impl From<msg::RawMsgRx> for msg::SerialOutputMsg {
|
||||||
fn from(value: msg::RawMsgRx) -> Self {
|
fn from(value: msg::RawMsgRx) -> Self {
|
||||||
use msg::serial_output_msg::Payload;
|
use msg::serial_output_msg::Payload;
|
||||||
|
|
@ -25,7 +31,145 @@ impl From<msg::DenmEvent> for msg::SerialOutputMsg {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<msg::PositionState> for crate::applogic::PositionState {
|
impl msg::RawMsgRx {
|
||||||
|
pub fn serialize_uart_proto(&self) -> Vec<u8> {
|
||||||
|
serialize_serial_msg(&Into::<msg::SerialOutputMsg>::into(self.clone()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl msg::DenmEvent {
|
||||||
|
#[allow(unused)]
|
||||||
|
pub fn serialize_uart_proto(&self) -> Vec<u8> {
|
||||||
|
serialize_serial_msg(&Into::<msg::SerialOutputMsg>::into(*self))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Default)]
|
||||||
|
pub struct Parser {
|
||||||
|
buffer: alloc::vec::Vec<u8>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Parser {
|
||||||
|
const SOF_LEN: u16 = 4;
|
||||||
|
const SOF1: u8 = 0x56;
|
||||||
|
const SOF2: u8 = 0x32;
|
||||||
|
const SOF3: u8 = 0x58;
|
||||||
|
const SOF4: u8 = 0x2B;
|
||||||
|
const EOF_LEN: u16 = 2;
|
||||||
|
#[allow(unused)]
|
||||||
|
const EOF1: u8 = 0x0d;
|
||||||
|
#[allow(unused)]
|
||||||
|
const EOF2: u8 = 0x0a;
|
||||||
|
const LENGTH_LEN: u16 = 2;
|
||||||
|
|
||||||
|
/// Parses incoming data and return list of messages
|
||||||
|
pub fn parse(&mut self, input: &[u8]) -> alloc::vec::Vec<msg::SerialInputMsg> {
|
||||||
|
use prost::Message as _;
|
||||||
|
|
||||||
|
// combine with existing data
|
||||||
|
self.buffer.append(&mut input.to_vec());
|
||||||
|
|
||||||
|
// find start of frame and parse
|
||||||
|
let mut output = alloc::vec![];
|
||||||
|
while let Some(pos) = Self::find_sof(&self.buffer) {
|
||||||
|
// drop everything before the SOF
|
||||||
|
self.buffer.drain(..pos);
|
||||||
|
|
||||||
|
// get length field and check buffer size
|
||||||
|
if let Some(len_bytes) = self
|
||||||
|
.buffer
|
||||||
|
.get(usize::from(Self::SOF_LEN)..usize::from(Self::SOF_LEN + Self::LENGTH_LEN))
|
||||||
|
{
|
||||||
|
// unwrap is fine since we queried 2 bytes before, so we can also convert to 2-byte slice
|
||||||
|
let msg_size = u16::from_be_bytes(len_bytes.try_into().unwrap());
|
||||||
|
|
||||||
|
let required_buf_size = Self::SOF_LEN + Self::LENGTH_LEN + msg_size + Self::EOF_LEN;
|
||||||
|
if let Some(payload) = self.buffer.get(
|
||||||
|
usize::from(Self::SOF_LEN + Self::LENGTH_LEN)
|
||||||
|
..usize::from(required_buf_size - 2),
|
||||||
|
) {
|
||||||
|
match msg::SerialInputMsg::decode(payload) {
|
||||||
|
Ok(data) => {
|
||||||
|
output.push(data);
|
||||||
|
}
|
||||||
|
Err(err) => error!("Failed to parse input: {err:?}"),
|
||||||
|
}
|
||||||
|
|
||||||
|
// consume buffer
|
||||||
|
self.buffer.drain(..usize::from(required_buf_size));
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
output
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the start index of the SOF sequence, if present
|
||||||
|
fn find_sof(input: &[u8]) -> Option<usize> {
|
||||||
|
// search for SOF
|
||||||
|
if let Some(sof1_pos) = input.iter().position(|i| *i == Self::SOF1) {
|
||||||
|
// drop everything before sof1_pos and check if remainder continues with SOF sequence
|
||||||
|
let (_, msg) = input.split_at(sof1_pos);
|
||||||
|
if msg.starts_with(&[Self::SOF1, Self::SOF2, Self::SOF3, Self::SOF4]) {
|
||||||
|
// found the SOF sequence
|
||||||
|
return Some(sof1_pos);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// if we reach this position, SOF sequence wasn't found
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn serialize_serial_msg<T>(msg: &T) -> Vec<u8>
|
||||||
|
where
|
||||||
|
T: prost::Message,
|
||||||
|
{
|
||||||
|
let mut msg_buf = msg.encode_to_vec();
|
||||||
|
|
||||||
|
let sof = [0x56u8, 0x32, 0x58, 0x2B];
|
||||||
|
let eof = [0x0du8, 0x0a];
|
||||||
|
#[allow(clippy::cast_possible_truncation)]
|
||||||
|
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);
|
||||||
|
|
||||||
|
out
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<msg::PositionState> for crate::applogic::GnssFix {
|
||||||
|
fn from(value: msg::PositionState) -> Self {
|
||||||
|
let time =
|
||||||
|
chrono::DateTime::from_timestamp_millis(value.timestamp_ms).map(|v| v.naive_utc());
|
||||||
|
|
||||||
|
let latitude_deg = value.position.latitude_deg.into();
|
||||||
|
let longitude_deg = value.position.longitude_deg.into();
|
||||||
|
let heading_deg = value.heading.map(|v| {
|
||||||
|
#[allow(clippy::cast_precision_loss)]
|
||||||
|
let val = (v as f32) / 10.;
|
||||||
|
val
|
||||||
|
});
|
||||||
|
let speed_mps = value.speed.map(|v| {
|
||||||
|
#[allow(clippy::cast_precision_loss)]
|
||||||
|
let val = (v as f32) / 100.;
|
||||||
|
val
|
||||||
|
});
|
||||||
|
|
||||||
|
Self {
|
||||||
|
time,
|
||||||
|
latitude_deg,
|
||||||
|
longitude_deg,
|
||||||
|
heading_deg,
|
||||||
|
speed_mps,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<msg::PositionState> for esp32_cits_core::PosVel {
|
||||||
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(),
|
||||||
|
|
@ -49,3 +193,323 @@ impl From<msg::PositionState> for crate::applogic::PositionState {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<msg::ItsPosition> for esp32_cits_core::PosVel {
|
||||||
|
fn from(value: msg::ItsPosition) -> Self {
|
||||||
|
let latitude_deg = f64::from(value.latitude) / 10_000_000.;
|
||||||
|
let longitude_deg = f64::from(value.longitude) / 10_000_000.;
|
||||||
|
let position = geo_types::Point::new(longitude_deg, latitude_deg);
|
||||||
|
|
||||||
|
Self {
|
||||||
|
position,
|
||||||
|
heading_deg: None,
|
||||||
|
speed_mps: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl esp32_cits_core::tx::GnItsPayload for msg::RawMsgTx {
|
||||||
|
fn make_eh(
|
||||||
|
&self,
|
||||||
|
address: [u8; 6],
|
||||||
|
own_position: &esp32_cits_core::PosVel,
|
||||||
|
time: chrono::DateTime<chrono::Utc>,
|
||||||
|
seq_no: &mut u16,
|
||||||
|
) -> Result<
|
||||||
|
(
|
||||||
|
c_its_parser::gn::en302636_4_1::ExtendedHeader,
|
||||||
|
Option<c_its_parser::gn::en302636_4_1::AreaType>,
|
||||||
|
u8,
|
||||||
|
),
|
||||||
|
alloc::string::String,
|
||||||
|
> {
|
||||||
|
let station_type = self
|
||||||
|
.station_type
|
||||||
|
.and_then(|v| {
|
||||||
|
#[allow(clippy::cast_possible_truncation)]
|
||||||
|
let raw_val = v as u8;
|
||||||
|
geonetworking::en302636_4_1::StationType::try_from(raw_val).ok()
|
||||||
|
})
|
||||||
|
.unwrap_or_default();
|
||||||
|
#[allow(clippy::cast_possible_truncation)]
|
||||||
|
let hop_limit = self.hop_limit as u8;
|
||||||
|
|
||||||
|
Ok(match self.gn_transport() {
|
||||||
|
msg::GnTransport::Gac => {
|
||||||
|
if self.gn_area.position.is_none() {
|
||||||
|
return Err("Failed to create GAC: gnArea.position missing".to_string());
|
||||||
|
}
|
||||||
|
|
||||||
|
*seq_no += 1;
|
||||||
|
|
||||||
|
let (atype, geoanycast) = Self::make_geobcast(
|
||||||
|
address,
|
||||||
|
station_type,
|
||||||
|
own_position,
|
||||||
|
time,
|
||||||
|
self.gn_area,
|
||||||
|
*seq_no,
|
||||||
|
)?;
|
||||||
|
let eh = geonetworking::en302636_4_1::ExtendedHeader::GAC(geoanycast);
|
||||||
|
(eh, Some(atype), hop_limit)
|
||||||
|
}
|
||||||
|
msg::GnTransport::Gbc => {
|
||||||
|
if self.gn_area.position.is_none() {
|
||||||
|
return Err("Failed to create GAC: gnArea.position missing".to_string());
|
||||||
|
}
|
||||||
|
|
||||||
|
*seq_no += 1;
|
||||||
|
|
||||||
|
let (atype, geoanycast) = Self::make_geobcast(
|
||||||
|
address,
|
||||||
|
station_type,
|
||||||
|
own_position,
|
||||||
|
time,
|
||||||
|
self.gn_area,
|
||||||
|
*seq_no,
|
||||||
|
)?;
|
||||||
|
let eh = geonetworking::en302636_4_1::ExtendedHeader::GBC(geoanycast);
|
||||||
|
(eh, Some(atype), hop_limit)
|
||||||
|
}
|
||||||
|
msg::GnTransport::Tsb => {
|
||||||
|
*seq_no += 1;
|
||||||
|
|
||||||
|
(
|
||||||
|
esp32_cits_core::tx::gn::make_tsb_eh(
|
||||||
|
address,
|
||||||
|
station_type,
|
||||||
|
own_position,
|
||||||
|
time,
|
||||||
|
*seq_no,
|
||||||
|
)?,
|
||||||
|
None,
|
||||||
|
hop_limit,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
msg::GnTransport::Shb => (
|
||||||
|
esp32_cits_core::tx::gn::make_shb_eh(address, station_type, own_position, time)?,
|
||||||
|
None,
|
||||||
|
1,
|
||||||
|
),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn make_payload(
|
||||||
|
&self,
|
||||||
|
) -> Result<(Vec<u8>, c_its_parser::standards::extensions::ItsMessageId), alloc::string::String>
|
||||||
|
{
|
||||||
|
#[allow(clippy::cast_possible_truncation)]
|
||||||
|
let message_id = (self.message_type as u8).try_into()?;
|
||||||
|
Ok((self.payload.clone(), message_id))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl msg::RawMsgTx {
|
||||||
|
fn make_geobcast(
|
||||||
|
address: [u8; 6],
|
||||||
|
station_type: geonetworking::en302636_4_1::StationType,
|
||||||
|
own_position: &esp32_cits_core::PosVel,
|
||||||
|
time: chrono::DateTime<chrono::Utc>,
|
||||||
|
gn_area: msg::GnArea,
|
||||||
|
sequence_number: u16,
|
||||||
|
) -> Result<
|
||||||
|
(
|
||||||
|
c_its_parser::gn::en302636_4_1::AreaType,
|
||||||
|
geonetworking::en302636_4_1::GeoBroadcast,
|
||||||
|
),
|
||||||
|
alloc::string::String,
|
||||||
|
> {
|
||||||
|
let source_position_vector =
|
||||||
|
esp32_cits_core::tx::gn::make_lpv(address, station_type, own_position, time)?;
|
||||||
|
|
||||||
|
let (atype, dist_a, dist_b, angle) = match gn_area.area() {
|
||||||
|
msg::GnAreaShape::EtsiAreashapeCircle => (
|
||||||
|
c_its_parser::gn::en302636_4_1::AreaType::Circular,
|
||||||
|
gn_area.dist_a,
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
),
|
||||||
|
msg::GnAreaShape::EtsiAreashapeRectangle => (
|
||||||
|
c_its_parser::gn::en302636_4_1::AreaType::Rectangular,
|
||||||
|
gn_area.dist_a,
|
||||||
|
gn_area.dist_b,
|
||||||
|
gn_area.angle,
|
||||||
|
),
|
||||||
|
msg::GnAreaShape::EtsiAreashapeEllipsis => (
|
||||||
|
c_its_parser::gn::en302636_4_1::AreaType::Ellipsoidal,
|
||||||
|
gn_area.dist_a,
|
||||||
|
gn_area.dist_b,
|
||||||
|
gn_area.angle,
|
||||||
|
),
|
||||||
|
};
|
||||||
|
|
||||||
|
#[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 distance_a = dist_a
|
||||||
|
.map(|v| {
|
||||||
|
#[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
|
||||||
|
let val = v as u16;
|
||||||
|
val
|
||||||
|
})
|
||||||
|
.unwrap_or_default();
|
||||||
|
let distance_b = dist_b
|
||||||
|
.map(|v| {
|
||||||
|
#[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
|
||||||
|
let val = v as u16;
|
||||||
|
val
|
||||||
|
})
|
||||||
|
.unwrap_or_default();
|
||||||
|
let angle = angle
|
||||||
|
.map(|v| {
|
||||||
|
#[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
|
||||||
|
let val = v as u16;
|
||||||
|
val
|
||||||
|
})
|
||||||
|
.unwrap_or_default();
|
||||||
|
|
||||||
|
let gac = geonetworking::en302636_4_1::GeoAnycast::try_from_values(
|
||||||
|
sequence_number,
|
||||||
|
source_position_vector,
|
||||||
|
latitude_deg,
|
||||||
|
longitude_deg,
|
||||||
|
distance_a,
|
||||||
|
distance_b,
|
||||||
|
angle,
|
||||||
|
)
|
||||||
|
.map_err(|err| alloc::format!("{err}"))?;
|
||||||
|
|
||||||
|
Ok((atype, gac))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
use crate::init_test_env_logger;
|
||||||
|
|
||||||
|
impl From<msg::RawMsgTx> for msg::SerialInputMsg {
|
||||||
|
fn from(value: msg::RawMsgTx) -> Self {
|
||||||
|
use msg::serial_input_msg::Payload;
|
||||||
|
|
||||||
|
Self {
|
||||||
|
payload: Some(Payload::TxMsg(value)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<msg::PositionState> for msg::SerialInputMsg {
|
||||||
|
fn from(value: msg::PositionState) -> Self {
|
||||||
|
use msg::serial_input_msg::Payload;
|
||||||
|
|
||||||
|
Self {
|
||||||
|
payload: Some(Payload::Position(value)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// manual example data:
|
||||||
|
// SerialInputMsg(RawMsgTx): 5632582b0040123e0a340202c173256817284029d5b3400f1feaf01ffffffc23b7743e00000fc0007e8138250737feebfff600000dffff7ffff1ce40400010011807220028020d0a
|
||||||
|
// SerialInputMsg(PositionState): 5632582b00170a1509a0bbe2a29f010000120a0d00002842150000b8410d0a
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn uart_parser() {
|
||||||
|
// generate some test UART input messages
|
||||||
|
let raw_tx = msg::RawMsgTx {
|
||||||
|
payload: crate::testdata::CAM_SMALL.to_vec(),
|
||||||
|
message_type: 1,
|
||||||
|
gn_transport: msg::GnTransport::Shb.into(),
|
||||||
|
gn_area: msg::GnArea {
|
||||||
|
area: None,
|
||||||
|
position: None,
|
||||||
|
dist_a: None,
|
||||||
|
dist_b: None,
|
||||||
|
angle: None,
|
||||||
|
},
|
||||||
|
hop_limit: 2,
|
||||||
|
station_type: None,
|
||||||
|
};
|
||||||
|
let raw_tx_buf = serialize_serial_msg::<msg::SerialInputMsg>(&raw_tx.clone().into());
|
||||||
|
|
||||||
|
let pos_state = msg::PositionState {
|
||||||
|
timestamp_ms: 1785144196000,
|
||||||
|
position: msg::Position {
|
||||||
|
latitude_deg: 42.,
|
||||||
|
longitude_deg: 23.,
|
||||||
|
},
|
||||||
|
heading: None,
|
||||||
|
speed: None,
|
||||||
|
};
|
||||||
|
let pos_state_buf = serialize_serial_msg::<msg::SerialInputMsg>(&pos_state.clone().into());
|
||||||
|
|
||||||
|
let mut parser = Parser::default();
|
||||||
|
|
||||||
|
// feed individual complete messages to parser
|
||||||
|
let output = parser.parse(&raw_tx_buf);
|
||||||
|
assert_eq!(1, output.len());
|
||||||
|
assert_eq!(
|
||||||
|
&msg::SerialInputMsg::from(raw_tx.clone()),
|
||||||
|
output.get(0).unwrap()
|
||||||
|
);
|
||||||
|
|
||||||
|
let output = parser.parse(&pos_state_buf);
|
||||||
|
assert_eq!(1, output.len());
|
||||||
|
assert_eq!(
|
||||||
|
&msg::SerialInputMsg::from(pos_state.clone()),
|
||||||
|
output.get(0).unwrap()
|
||||||
|
);
|
||||||
|
|
||||||
|
// feed multiple complete messages to parser
|
||||||
|
let mut combined_buf = pos_state_buf.clone();
|
||||||
|
combined_buf.extend_from_slice(&raw_tx_buf);
|
||||||
|
|
||||||
|
let output = parser.parse(&combined_buf);
|
||||||
|
assert_eq!(2, output.len());
|
||||||
|
assert_eq!(
|
||||||
|
&msg::SerialInputMsg::from(pos_state.clone()),
|
||||||
|
output.get(0).unwrap()
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
&msg::SerialInputMsg::from(raw_tx.clone()),
|
||||||
|
output.get(1).unwrap()
|
||||||
|
);
|
||||||
|
|
||||||
|
// feed partial messages to parser
|
||||||
|
let buf1 = &raw_tx_buf[0..3];
|
||||||
|
let buf2 = &raw_tx_buf[3..];
|
||||||
|
|
||||||
|
let output = parser.parse(buf1);
|
||||||
|
assert_eq!(0, output.len());
|
||||||
|
|
||||||
|
let output = parser.parse(buf2);
|
||||||
|
assert_eq!(1, output.len());
|
||||||
|
assert_eq!(
|
||||||
|
&msg::SerialInputMsg::from(raw_tx.clone()),
|
||||||
|
output.get(0).unwrap()
|
||||||
|
);
|
||||||
|
|
||||||
|
// feed messages with garbage in between
|
||||||
|
let mut buf1 = vec![0xabu8, 0xcd]; // prepend some garbage
|
||||||
|
buf1.extend_from_slice(&raw_tx_buf[0..3]);
|
||||||
|
let mut buf2 = (&raw_tx_buf[3..]).to_vec();
|
||||||
|
buf2.extend_from_slice(&[0xfeu8, 0x42]); // append some garbage
|
||||||
|
buf2.extend_from_slice(&pos_state_buf); // append second message
|
||||||
|
buf2.extend_from_slice(&[0x11u8, 0x22]); // append some more garbage
|
||||||
|
|
||||||
|
let output = parser.parse(&buf1);
|
||||||
|
assert_eq!(0, output.len());
|
||||||
|
|
||||||
|
let output = parser.parse(&buf2);
|
||||||
|
assert_eq!(2, output.len());
|
||||||
|
assert_eq!(
|
||||||
|
&msg::SerialInputMsg::from(raw_tx.clone()),
|
||||||
|
output.get(0).unwrap()
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
&msg::SerialInputMsg::from(pos_state.clone()),
|
||||||
|
output.get(1).unwrap()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
450
src/main.rs
450
src/main.rs
|
|
@ -7,6 +7,18 @@
|
||||||
)]
|
)]
|
||||||
// #![deny(clippy::large_stack_frames)]
|
// #![deny(clippy::large_stack_frames)]
|
||||||
|
|
||||||
|
// we can't use UART I/O and GNSS at the same time
|
||||||
|
#[cfg(all(feature = "uart", feature = "gnss_ubx", feature = "gnss_l67k"))]
|
||||||
|
compile_error!(
|
||||||
|
"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)
|
||||||
|
#[cfg(all(feature = "cam_tx", not(feature = "_gnss")))]
|
||||||
|
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;
|
||||||
use core::cell::RefCell;
|
use core::cell::RefCell;
|
||||||
|
|
@ -14,17 +26,19 @@ use core::cell::RefCell;
|
||||||
use core::cell::UnsafeCell;
|
use core::cell::UnsafeCell;
|
||||||
|
|
||||||
use c_its_parser::gn as geonetworking;
|
use c_its_parser::gn as geonetworking;
|
||||||
|
#[cfg(feature = "cam_tx")]
|
||||||
|
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")]
|
#[cfg(feature = "gnss_l67k")]
|
||||||
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 esp_radio::wifi;
|
use esp32_cits_core::radio;
|
||||||
use geonetworking::Decode as _;
|
use geonetworking::Decode as _;
|
||||||
use log::{error, info, warn};
|
use log::{error, info, warn};
|
||||||
|
|
||||||
|
|
@ -33,22 +47,40 @@ extern crate alloc;
|
||||||
mod applogic;
|
mod applogic;
|
||||||
mod cache;
|
mod cache;
|
||||||
mod geo_alg;
|
mod geo_alg;
|
||||||
|
#[cfg(feature = "uart")]
|
||||||
mod io;
|
mod io;
|
||||||
mod radio;
|
|
||||||
#[cfg(feature = "screen")]
|
#[cfg(feature = "screen")]
|
||||||
mod screen;
|
mod screen;
|
||||||
|
#[cfg(feature = "gnss_ubx")]
|
||||||
|
mod ubx;
|
||||||
|
|
||||||
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"))]
|
||||||
|
const GN_IS_MOBILE: bool = true;
|
||||||
|
#[cfg(feature = "cam_tx")]
|
||||||
|
const OWN_STATION_TYPE: ItsStationType = ItsStationType::Cyclist;
|
||||||
|
#[cfg(feature = "cam_tx")]
|
||||||
|
const OWN_VEHICLE_WIDTH: u8 = 5; // in 10cm steps!
|
||||||
|
#[cfg(feature = "cam_tx")]
|
||||||
|
const OWN_VEHICLE_LENGTH: u16 = 20; // in 10cm steps!
|
||||||
|
#[cfg(feature = "_uart")]
|
||||||
|
const SERIAL_FIFO_SIZE_THLD: u8 = 100; // hardware has max. 128 byte
|
||||||
|
|
||||||
// This creates a default app-descriptor required by the esp-idf bootloader.
|
// This creates a default app-descriptor required by the esp-idf bootloader.
|
||||||
// For more information see: <https://docs.espressif.com/projects/esp-idf/en/stable/esp32/api-reference/system/app_image_format.html#application-description>
|
// For more information see: <https://docs.espressif.com/projects/esp-idf/en/stable/esp32/api-reference/system/app_image_format.html#application-description>
|
||||||
esp_bootloader_esp_idf::esp_app_desc!();
|
esp_bootloader_esp_idf::esp_app_desc!();
|
||||||
|
|
||||||
#[cfg(feature = "gnss")]
|
#[cfg(feature = "_gnss")]
|
||||||
static GNSS_UPDATE: Mutex<RefCell<Option<applogic::GnssFix>>> = Mutex::new(RefCell::new(None));
|
static GNSS_UPDATE: Mutex<RefCell<Option<applogic::GnssFix>>> = Mutex::new(RefCell::new(None));
|
||||||
static WIFI_RX_QUEUE: Mutex<RefCell<Option<ArrayQueue<Vec<u8>>>>> = Mutex::new(RefCell::new(None));
|
static WIFI_RX_QUEUE: Mutex<RefCell<Option<ArrayQueue<Vec<u8>>>>> = Mutex::new(RefCell::new(None));
|
||||||
|
#[cfg(feature = "_uart")]
|
||||||
|
static UART_RX_BUF: Mutex<RefCell<Option<Vec<u8>>>> = Mutex::new(RefCell::new(None));
|
||||||
|
#[cfg(feature = "_uart")]
|
||||||
|
static SERIAL: Mutex<RefCell<Option<esp_hal::uart::Uart<'static, esp_hal::Blocking>>>> =
|
||||||
|
Mutex::new(RefCell::new(None));
|
||||||
|
|
||||||
#[allow(
|
#[allow(
|
||||||
clippy::large_stack_frames,
|
clippy::large_stack_frames,
|
||||||
|
|
@ -117,21 +149,38 @@ async fn main(spawner: Spawner) -> ! {
|
||||||
screen
|
screen
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Setup UART I/O channel
|
||||||
|
#[cfg(feature = "uart")]
|
||||||
|
setup_uart0(
|
||||||
|
115_200,
|
||||||
|
Some(b'\n'),
|
||||||
|
peripherals.GPIO12,
|
||||||
|
peripherals.GPIO11,
|
||||||
|
peripherals.UART0,
|
||||||
|
serial_handler,
|
||||||
|
)
|
||||||
|
.expect("Failed to initialize UART");
|
||||||
|
|
||||||
// Setup WiFi
|
// Setup WiFi
|
||||||
// sadly, it doesn't work any more when we move `esp_radio::wifi::new` to `setup_wifi_sniffer`
|
// sadly, it doesn't work any more when we move `esp_radio::wifi::new` to `setup_wifi_sniffer`
|
||||||
let (mut wifi_controller, mut interfaces) = esp_radio::wifi::new(
|
let (mut wifi_controller, interfaces) = esp_radio::wifi::new(
|
||||||
peripherals.WIFI,
|
peripherals.WIFI,
|
||||||
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)));
|
||||||
});
|
});
|
||||||
radio::setup_wifi_sniffer(WIFI_CHANNEL, &mut interfaces.sniffer, handle_frame)
|
let mut wlan_iface = interfaces.sniffer;
|
||||||
|
radio::setup_wifi_sniffer(WIFI_CHANNEL, &mut wlan_iface, handle_frame)
|
||||||
.expect("Fatal error initializing 802.11p sniffer");
|
.expect("Fatal error initializing 802.11p sniffer");
|
||||||
|
|
||||||
info!("WiFi promiscuous mode on channel {WIFI_CHANNEL} running");
|
info!("WiFi promiscuous mode on channel {WIFI_CHANNEL} running");
|
||||||
|
|
@ -139,14 +188,15 @@ 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: Seeed XIAO L67K:
|
// Setup GNSS:
|
||||||
|
// 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")]
|
#[cfg(feature = "gnss_l67k")]
|
||||||
spawner.spawn(
|
spawner.spawn(
|
||||||
gnss_task(
|
gnss_l67k_task(
|
||||||
peripherals.GPIO1,
|
peripherals.GPIO1,
|
||||||
peripherals.GPIO25,
|
peripherals.GPIO25,
|
||||||
peripherals.UART0,
|
peripherals.UART0,
|
||||||
|
|
@ -155,14 +205,167 @@ 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"))]
|
||||||
|
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 state = applogic::State::new();
|
let mut state = applogic::State::new();
|
||||||
|
#[cfg(feature = "cam_tx")]
|
||||||
|
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,
|
||||||
|
)
|
||||||
|
};
|
||||||
|
#[cfg(feature = "uart")]
|
||||||
|
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;
|
||||||
|
|
||||||
#[cfg(feature = "gnss")]
|
#[cfg(feature = "_uart")]
|
||||||
|
{
|
||||||
|
let mut new_data = None;
|
||||||
|
critical_section::with(|cs| {
|
||||||
|
let mut buf_rc = UART_RX_BUF.borrow(cs).borrow_mut();
|
||||||
|
// unwrap is fine b/c we stored something in it before
|
||||||
|
let buf = buf_rc.as_mut().unwrap();
|
||||||
|
|
||||||
|
// we move the buffer to new_data to do the processing outside of the critical section lock
|
||||||
|
if !buf.is_empty() {
|
||||||
|
new_data = Some(core::mem::take(buf));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if let Some(data) = new_data {
|
||||||
|
#[cfg(feature = "uart")]
|
||||||
|
{
|
||||||
|
for msg in uart_input.parse(&data) {
|
||||||
|
match msg.payload {
|
||||||
|
Some(io::msg::serial_input_msg::Payload::Position(fix)) => {
|
||||||
|
let pos_state = fix.into();
|
||||||
|
|
||||||
|
critical_section::with(|cs| {
|
||||||
|
let gnss_update_ref = GNSS_UPDATE.borrow(cs);
|
||||||
|
gnss_update_ref.replace(Some(pos_state));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
Some(io::msg::serial_input_msg::Payload::TxMsg(tx_request)) => {
|
||||||
|
use esp32_cits_core::tx::GnItsPayload as _;
|
||||||
|
|
||||||
|
info!(
|
||||||
|
"UART: Got new TX message: Type {} with {} bytes",
|
||||||
|
tx_request.message_type,
|
||||||
|
tx_request.payload.len()
|
||||||
|
);
|
||||||
|
let own_position = state.pos();
|
||||||
|
let time = state.time().and_utc();
|
||||||
|
|
||||||
|
match tx_request.encode_packet(
|
||||||
|
mac_bytes,
|
||||||
|
own_position,
|
||||||
|
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 GN message: {err}"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#[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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "_gnss")]
|
||||||
{
|
{
|
||||||
let mut new_position = false;
|
let mut new_position = false;
|
||||||
critical_section::with(|cs| {
|
critical_section::with(|cs| {
|
||||||
|
|
@ -171,6 +374,33 @@ async fn main(spawner: Spawner) -> ! {
|
||||||
if let Some(fix) = gnss_update_ref.replace(None) {
|
if let Some(fix) = gnss_update_ref.replace(None) {
|
||||||
state.update_with_gpsfix(&fix);
|
state.update_with_gpsfix(&fix);
|
||||||
new_position = true;
|
new_position = true;
|
||||||
|
|
||||||
|
#[cfg(feature = "cam_tx")]
|
||||||
|
{
|
||||||
|
use esp32_cits_core::tx::GnItsPayload as _;
|
||||||
|
let time = state.time().and_utc();
|
||||||
|
|
||||||
|
cam.update(time, fix.clone().into());
|
||||||
|
|
||||||
|
match cam.encode_packet(
|
||||||
|
mac_bytes,
|
||||||
|
&fix.into(),
|
||||||
|
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}"),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -236,12 +466,15 @@ async fn main(spawner: Spawner) -> ! {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "cam")]
|
#[cfg(any(feature = "cam", feature = "cam_tx"))]
|
||||||
ItsMessage::Cam {
|
ItsMessage::Cam {
|
||||||
geonetworking: _,
|
geonetworking: _,
|
||||||
transport: _,
|
transport: _,
|
||||||
etsi,
|
etsi,
|
||||||
} => applogic::cam::handle_cam(&etsi),
|
} => {
|
||||||
|
#[cfg(feature = "cam")]
|
||||||
|
applogic::cam::handle_cam(&etsi);
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(feature = "denm")]
|
#[cfg(feature = "denm")]
|
||||||
ItsMessage::DenmV2 {
|
ItsMessage::DenmV2 {
|
||||||
|
|
@ -277,7 +510,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: wifi::sniffer::PromiscuousPkt<'_>) {
|
fn handle_frame(frame: esp_radio::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")]
|
||||||
|
|
@ -298,27 +531,18 @@ fn handle_frame(frame: wifi::sniffer::PromiscuousPkt<'_>) {
|
||||||
.naive_utc()
|
.naive_utc()
|
||||||
};
|
};
|
||||||
|
|
||||||
// Ignore frames with errors and non-data frames
|
match esp32_cits_core::rx::is_broadcast_frame(&frame) {
|
||||||
if frame.rx_cntl.rx_state != 0 {
|
Err(err) => {
|
||||||
warn!("Received frame has RX error: {}", frame.rx_cntl.rx_state);
|
warn!("{err}");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if !radio::PromiscuousPktType::Data.matches(frame.frame_type) {
|
Ok(false) => {
|
||||||
return;
|
// drop frame
|
||||||
}
|
return;
|
||||||
|
}
|
||||||
// Ignore non-broadcast frames
|
Ok(true) => {
|
||||||
if frame.len < (4 + 6) {
|
// continue
|
||||||
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);
|
||||||
|
|
@ -386,6 +610,18 @@ fn handle_frame(frame: wifi::sniffer::PromiscuousPkt<'_>) {
|
||||||
error!("V2X RX queue is full");
|
error!("V2X RX queue is full");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Send data to UART
|
||||||
|
#[cfg(feature = "uart")]
|
||||||
|
{
|
||||||
|
let message = io::msg::RawMsgRx {
|
||||||
|
msg_type: message_id.as_u8().into(),
|
||||||
|
payload: frame.data.to_vec(),
|
||||||
|
}
|
||||||
|
.serialize_uart_proto();
|
||||||
|
|
||||||
|
send_uart0(&message);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -453,10 +689,10 @@ fn get_its_header<'p>(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "gnss")]
|
#[cfg(feature = "gnss_l67k")]
|
||||||
#[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_task(
|
async fn gnss_l67k_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>,
|
||||||
|
|
@ -506,6 +742,38 @@ async fn gnss_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
|
||||||
|
|
@ -513,3 +781,111 @@ where
|
||||||
{
|
{
|
||||||
error!("graphics failed: {error:?}");
|
error!("graphics failed: {error:?}");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "_uart")]
|
||||||
|
/// Configures UART0 with an interrupt handler and saves it to `SERIAL`
|
||||||
|
fn setup_uart0(
|
||||||
|
baud: u32,
|
||||||
|
cmd_char: Option<u8>,
|
||||||
|
gpio12: esp_hal::peripherals::GPIO12<'static>,
|
||||||
|
gpio11: esp_hal::peripherals::GPIO11<'static>,
|
||||||
|
uart0: esp_hal::peripherals::UART0<'static>,
|
||||||
|
handler: esp_hal::interrupt::InterruptHandler,
|
||||||
|
) -> Result<(), alloc::string::String> {
|
||||||
|
critical_section::with(|cs| {
|
||||||
|
UART_RX_BUF
|
||||||
|
.borrow(cs)
|
||||||
|
.replace(Some(alloc::vec::Vec::with_capacity(255)));
|
||||||
|
});
|
||||||
|
|
||||||
|
let mut uart = esp_hal::uart::Uart::new(
|
||||||
|
uart0,
|
||||||
|
esp_hal::uart::Config::default()
|
||||||
|
.with_baudrate(baud)
|
||||||
|
.with_rx(
|
||||||
|
esp_hal::uart::RxConfig::default()
|
||||||
|
.with_fifo_full_threshold(u16::from(SERIAL_FIFO_SIZE_THLD))
|
||||||
|
.with_timeout(50),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.map_err(|err| alloc::format!("UART0 config error: {err}"))?
|
||||||
|
.with_rx(gpio12)
|
||||||
|
.with_tx(gpio11);
|
||||||
|
|
||||||
|
if let Some(char) = cmd_char {
|
||||||
|
uart.set_at_cmd(
|
||||||
|
esp_hal::uart::AtCmdConfig::default()
|
||||||
|
.with_cmd_char(char)
|
||||||
|
.with_char_num(1)
|
||||||
|
.with_pre_idle_count(0) // command start can be right after other data
|
||||||
|
.with_post_idle_count(0) // we don't expect a gap between the command start and subsequent text
|
||||||
|
.with_gap_timeout(5), // needs to be >0 to work
|
||||||
|
);
|
||||||
|
}
|
||||||
|
uart.set_interrupt_handler(handler);
|
||||||
|
uart.listen(
|
||||||
|
esp_hal::uart::UartInterrupt::AtCmd
|
||||||
|
| esp_hal::uart::UartInterrupt::RxTimeout
|
||||||
|
| esp_hal::uart::UartInterrupt::RxFifoFull, // just to be save
|
||||||
|
);
|
||||||
|
|
||||||
|
critical_section::with(|cs| SERIAL.borrow_ref_mut(cs).replace(uart));
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "_uart")]
|
||||||
|
#[esp_hal::handler]
|
||||||
|
fn serial_handler() {
|
||||||
|
critical_section::with(|cs| {
|
||||||
|
let mut serial = SERIAL.borrow_ref_mut(cs);
|
||||||
|
let mut uart_buf = UART_RX_BUF.borrow_ref_mut(cs);
|
||||||
|
|
||||||
|
if let (Some(serial), Some(uart_buf)) = (serial.as_mut(), uart_buf.as_mut()) {
|
||||||
|
let mut buf = [0u8; SERIAL_FIFO_SIZE_THLD as usize];
|
||||||
|
|
||||||
|
match serial.read_buffered(&mut buf) {
|
||||||
|
Ok(0) => {}
|
||||||
|
Ok(len) => {
|
||||||
|
uart_buf.extend_from_slice(&buf[..len]);
|
||||||
|
}
|
||||||
|
Err(err) => error!("Failed to read from UART: {err}"),
|
||||||
|
}
|
||||||
|
|
||||||
|
serial.clear_interrupts(
|
||||||
|
esp_hal::uart::UartInterrupt::RxFifoFull
|
||||||
|
| esp_hal::uart::UartInterrupt::RxTimeout
|
||||||
|
| esp_hal::uart::UartInterrupt::AtCmd,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "_uart")]
|
||||||
|
fn send_uart0(data: &[u8]) {
|
||||||
|
critical_section::with(|cs| {
|
||||||
|
let mut serial = SERIAL.borrow_ref_mut(cs);
|
||||||
|
|
||||||
|
if let Some(serial) = serial.as_mut()
|
||||||
|
&& let Err(err) = serial.write(data)
|
||||||
|
{
|
||||||
|
error!("Failed to write to UART: {err:?}");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
#[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
288
src/radio.rs
|
|
@ -1,288 +0,0 @@
|
||||||
//! 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());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -6,7 +6,7 @@ extern crate alloc;
|
||||||
mod applogic;
|
mod applogic;
|
||||||
mod cache;
|
mod cache;
|
||||||
mod geo_alg;
|
mod geo_alg;
|
||||||
mod radio;
|
mod io;
|
||||||
mod testdata;
|
mod testdata;
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
|
|
|
||||||
|
|
@ -455,3 +455,11 @@ pub const CAM: &[u8] = &[
|
||||||
0x00, 0x06, 0xc6, 0x70, 0x00, 0xdf, 0x80, 0x8d, 0x5f, 0xde, 0x66, 0x27, 0x00, 0x07, 0x3c, 0x04,
|
0x00, 0x06, 0xc6, 0x70, 0x00, 0xdf, 0x80, 0x8d, 0x5f, 0xde, 0x66, 0x27, 0x00, 0x07, 0x3c, 0x04,
|
||||||
0x76, 0xfd, 0x67, 0x31, 0x9c, 0x00, 0x58, 0x60, 0x41, 0x37, 0xd3, 0x15, 0x89, 0xc0, 0x06, 0xdc,
|
0x76, 0xfd, 0x67, 0x31, 0x9c, 0x00, 0x58, 0x60, 0x41, 0x37, 0xd3, 0x15, 0x89, 0xc0, 0x06, 0xdc,
|
||||||
];
|
];
|
||||||
|
|
||||||
|
#[cfg(any(feature = "cam_tx", feature = "uart"))]
|
||||||
|
pub const CAM_SMALL: &[u8] = &[
|
||||||
|
0x2, 0x2, 0xc1, 0x73, 0x25, 0x68, 0x17, 0x28, 0x40, 0x29, 0xd5, 0xb3, 0x40, 0xf, 0x1f, 0xea,
|
||||||
|
0xf0, 0x1f, 0xff, 0xff, 0xfc, 0x23, 0xb7, 0x74, 0x3e, 0x0, 0x0, 0xf, 0xc0, 0x0, 0x7e, 0x81,
|
||||||
|
0x38, 0x25, 0x7, 0x37, 0xfe, 0xeb, 0xff, 0xf6, 0x0, 0x0, 0xd, 0xff, 0xff, 0x7f, 0xff, 0xf1,
|
||||||
|
0xce, 0x40, 0x40, 0x0,
|
||||||
|
];
|
||||||
|
|
|
||||||
106
src/ubx.rs
Normal file
106
src/ubx.rs
Normal file
|
|
@ -0,0 +1,106 @@
|
||||||
|
//! 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),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue