Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 7698a00652 |
17 changed files with 127 additions and 185 deletions
|
|
@ -98,8 +98,7 @@ Additional features are available through feature flags:
|
|||
|
||||
Officially supported feature combinations are:
|
||||
- `esp,spat,gnss_ubx,screen` (default features, "standalone")
|
||||
- `esp,spat,gnss_ubx,screen,ble` ("standalone with BLE")
|
||||
- `esp,spat,gnss_ubx,screen,cam_tx,ble` ("standalone with CAM")
|
||||
- `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 with UART")
|
||||
- `esp,ble,cam,denm,cam_tx` ("smartphone companion with BLE")
|
||||
|
|
@ -133,6 +132,7 @@ Additional hardware:
|
|||
The housing is a FreeCAD model at [hardware/housing/c_its-companion-housing.FCStd](./hardware/housing/c_its-companion-housing.FCStd), but the same folder also contains finished 3mf models that can be loaded into a 3D printer slicer.
|
||||
Exports are available for:
|
||||
|
||||
- 2mm: no battery (just display standoffs)
|
||||
- 7mm: EREMIT 654060 (6.5x40x60 mm) LiPo battery
|
||||
- 9mm: Some 9x40x60 mm LiPo battery, e.g. LP803860 (2000 mAh)
|
||||
|
||||
|
|
|
|||
22
build.rs
22
build.rs
|
|
@ -9,7 +9,6 @@ fn main() -> std::io::Result<()> {
|
|||
println!("cargo:rustc-link-arg=-Tlinkall.x");
|
||||
}
|
||||
|
||||
// compile protobuf definition to rust code
|
||||
let descriptor_path =
|
||||
std::path::PathBuf::from(env::var("OUT_DIR").unwrap()).join("proto_descriptor.bin");
|
||||
|
||||
|
|
@ -18,27 +17,6 @@ fn main() -> std::io::Result<()> {
|
|||
.compile_well_known_types()
|
||||
.compile_protos(&["docs/c_its-messages.proto"], &["docs/"])?;
|
||||
|
||||
// get git revision
|
||||
println!("cargo:rustc-rerun-if-changed=.git/HEAD");
|
||||
|
||||
// length:
|
||||
// - tag name: 6-8 chars (v0.1.0 to v1.23.42)
|
||||
// - offset+hash: 0-12 chars (up to 99 commits from tag + 8 char hash)
|
||||
// - SUM: 6-20
|
||||
let git_output = std::process::Command::new("git")
|
||||
.args(["describe", "--tags", "--match", "v*", "--always"])
|
||||
.output()
|
||||
.unwrap();
|
||||
assert!(
|
||||
git_output.stderr.is_empty(),
|
||||
"{}",
|
||||
String::from_utf8(git_output.stderr).unwrap()
|
||||
);
|
||||
println!(
|
||||
"cargo:rustc-env=GIT_REVISION={}",
|
||||
String::from_utf8(git_output.stdout).unwrap()
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -7,16 +7,15 @@ extended=${2:-false}
|
|||
|
||||
|
||||
features_base=(
|
||||
spat,gnss_ubx,screen # "standalone" simple
|
||||
spat,gnss_ubx,screen,ble # "standalone" with BLE
|
||||
spat,gnss_ubx,screen,ble,cam_tx # "standalone" with CAM
|
||||
spat,gnss_ubx,screen,cam_tx,cam,denm
|
||||
uart,cam,denm # "smartphone companion with UART"
|
||||
ble,cam,denm,cam_tx # "smartphone companion with BLE"
|
||||
uart,cam,denm
|
||||
ble,cam,denm,cam_tx
|
||||
uart,ble,cam,denm
|
||||
spat,gnss_ubx,screen
|
||||
spat,gnss_ubx,screen,cam_tx
|
||||
)
|
||||
features_more=(
|
||||
uart,ble,cam,denm
|
||||
spat,gnss_ubx,screen,cam_tx
|
||||
spat,gnss_ubx,screen,ble
|
||||
spat,gnss_ubx,screen,ble,cam_tx
|
||||
spat,gnss_ubx,screen,cam,denm
|
||||
spat,gnss_ubx,screen,cam_tx,cam,denm
|
||||
gnss_ubx,screen,cam_tx,cam,denm
|
||||
|
|
|
|||
BIN
hardware/housing/c_its-companion-housing-bottom-2mm.3mf
Normal file
BIN
hardware/housing/c_its-companion-housing-bottom-2mm.3mf
Normal file
Binary file not shown.
BIN
hardware/housing/c_its-companion-housing-bottom-7mm.3mf
Normal file
BIN
hardware/housing/c_its-companion-housing-bottom-7mm.3mf
Normal file
Binary file not shown.
BIN
hardware/housing/c_its-companion-housing-bottom-9mm.3mf
Normal file
BIN
hardware/housing/c_its-companion-housing-bottom-9mm.3mf
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
hardware/housing/c_its-companion-housing-lid.3mf
Normal file
BIN
hardware/housing/c_its-companion-housing-lid.3mf
Normal file
Binary file not shown.
Binary file not shown.
|
|
@ -1,6 +0,0 @@
|
|||
#!/bin/sh
|
||||
|
||||
## Builds common config for standalone device
|
||||
## -> enable SPAT/ GLOSA with GNSS and a screen + BLE
|
||||
|
||||
cargo run --release -F ble
|
||||
|
|
@ -3,4 +3,4 @@
|
|||
## 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,ble
|
||||
cargo run --release -F cam_tx
|
||||
|
|
|
|||
99
src/ble.rs
99
src/ble.rs
|
|
@ -39,73 +39,9 @@ pub enum BleSendable {
|
|||
SpatemRaw(io::msg::RawMsgRx),
|
||||
}
|
||||
|
||||
pub const DIS_SERIAL_MAX_LEN: usize = 17;
|
||||
pub const DIS_FW_REVISION_MAX_LEN: usize = 20; // git-describe
|
||||
pub const DIS_SW_REVISION_MAX_LEN: usize = 66; // max length of get_feature_list, joined with comma
|
||||
|
||||
pub struct DeviceInfo {
|
||||
/// WiFi MAC
|
||||
pub serial_number: [u8; DIS_SERIAL_MAX_LEN],
|
||||
/// git tag/ revision
|
||||
pub fw_revision: [u8; DIS_FW_REVISION_MAX_LEN],
|
||||
/// used for feature flags
|
||||
pub sw_revision: [u8; DIS_SW_REVISION_MAX_LEN],
|
||||
}
|
||||
|
||||
impl DeviceInfo {
|
||||
pub fn new(serial_number: &str, fw_revision: &str, sw_revision: &str) -> Self {
|
||||
Self {
|
||||
serial_number: string_as_padded_buf(serial_number, DIS_SERIAL_MAX_LEN)
|
||||
.try_into()
|
||||
.unwrap(),
|
||||
fw_revision: string_as_padded_buf(fw_revision, DIS_FW_REVISION_MAX_LEN)
|
||||
.try_into()
|
||||
.unwrap(),
|
||||
sw_revision: string_as_padded_buf(sw_revision, DIS_SW_REVISION_MAX_LEN)
|
||||
.try_into()
|
||||
.unwrap(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn string_as_padded_buf(value: &str, length: usize) -> alloc::vec::Vec<u8> {
|
||||
let buf = value.as_bytes();
|
||||
let buf_len = buf.len();
|
||||
|
||||
// cut buffer at maximum length or pad with whitespace
|
||||
if buf_len > length {
|
||||
buf[..length].to_vec()
|
||||
} else {
|
||||
let padding_len = length - buf_len;
|
||||
let padding = alloc::vec![b' '; padding_len];
|
||||
|
||||
[buf, &padding].concat()
|
||||
}
|
||||
}
|
||||
|
||||
#[gatt_server]
|
||||
struct Server {
|
||||
c_its_events: CITSEvents,
|
||||
device_info_service: DeviceInfoService,
|
||||
}
|
||||
|
||||
#[gatt_service(uuid = service::DEVICE_INFORMATION)]
|
||||
struct DeviceInfoService {
|
||||
/// Manufacturer name
|
||||
#[characteristic(uuid = characteristic::MANUFACTURER_NAME_STRING, read, value = "jtbx")]
|
||||
mfg_name: &'static str,
|
||||
/// Model number
|
||||
#[characteristic(uuid = characteristic::MODEL_NUMBER_STRING, read, value = "ESP32-C5 C-ITS Companion")]
|
||||
model_number: &'static str,
|
||||
// Serial Number String
|
||||
#[characteristic(uuid = characteristic::SERIAL_NUMBER_STRING, read, value = [0u8; DIS_SERIAL_MAX_LEN])]
|
||||
serial_number: [u8; DIS_SERIAL_MAX_LEN],
|
||||
// Firmware Revision String
|
||||
#[characteristic(uuid = characteristic::FIRMWARE_REVISION_STRING, read, value = [0u8; DIS_FW_REVISION_MAX_LEN])]
|
||||
fw_revision: [u8; DIS_FW_REVISION_MAX_LEN],
|
||||
// Software Revision String
|
||||
#[characteristic(uuid = characteristic::SOFTWARE_REVISION_STRING, read, value = [0u8; DIS_SW_REVISION_MAX_LEN])]
|
||||
sw_revision: [u8; DIS_SW_REVISION_MAX_LEN],
|
||||
}
|
||||
|
||||
#[gatt_service(uuid = "c0b70000-d4f4-4000-ada8-f99a02ee315c")]
|
||||
|
|
@ -174,8 +110,6 @@ pub async fn run(
|
|||
CONTROLLER_SLOTS,
|
||||
>,
|
||||
name: alloc::string::String,
|
||||
short_name: alloc::string::String,
|
||||
device_info: DeviceInfo,
|
||||
) {
|
||||
let mut resources: HostResources<DefaultPacketPool, CONNECTIONS_MAX, L2CAP_CHANNELS_MAX> =
|
||||
HostResources::new();
|
||||
|
|
@ -186,7 +120,6 @@ pub async fn run(
|
|||
..
|
||||
} = stack.build();
|
||||
|
||||
info!("BLE name: {name} (short: {short_name})");
|
||||
info!(
|
||||
"Starting advertising and GATT service for {}",
|
||||
crate::util::get_rx_feature_list().join(",")
|
||||
|
|
@ -197,24 +130,9 @@ pub async fn run(
|
|||
}))
|
||||
.unwrap();
|
||||
|
||||
// set "dynamic" device info data
|
||||
let _ = server
|
||||
.device_info_service
|
||||
.serial_number
|
||||
.set(&server, &device_info.serial_number);
|
||||
let _ = server
|
||||
.device_info_service
|
||||
.fw_revision
|
||||
.set(&server, &device_info.fw_revision);
|
||||
let _ = server
|
||||
.device_info_service
|
||||
.sw_revision
|
||||
.set(&server, &device_info.sw_revision);
|
||||
|
||||
// advertise and run update logic
|
||||
let _ = embassy_futures::join::join(ble_task(runner), async {
|
||||
loop {
|
||||
match advertise(&mut peripheral, &server, &short_name).await {
|
||||
match advertise(&mut peripheral, &server).await {
|
||||
Ok(conn) => {
|
||||
info!("BLE: New client connected");
|
||||
|
||||
|
|
@ -258,19 +176,16 @@ pub fn update(payload: BleSendable) {
|
|||
async fn advertise<'values, 'server, C: Controller>(
|
||||
peripheral: &mut Peripheral<'values, C, DefaultPacketPool>,
|
||||
server: &'server Server<'values>,
|
||||
// name in advertisement, max. 8 characters
|
||||
short_name: &'values str,
|
||||
) -> Result<GattConnection<'values, 'server, DefaultPacketPool>, BleHostError<C::Error>> {
|
||||
let mut advertiser_data = [0; 31]; // 37 bytes payload - 6 byte address
|
||||
let mut advertiser_data = [0; 35];
|
||||
let service_uuid = Uuid::from(0xc0b7_0000_d4f4_4000_ada8_f99a_02ee_315c_u128)
|
||||
.as_raw()
|
||||
.try_into()
|
||||
.unwrap();
|
||||
let len = AdStructure::encode_slice(
|
||||
&[
|
||||
AdStructure::Flags(LE_GENERAL_DISCOVERABLE | BR_EDR_NOT_SUPPORTED), // 2+ 1 byte
|
||||
AdStructure::ServiceUuids128(&[service_uuid]), // 2+16 byte = 21
|
||||
AdStructure::ShortenedLocalName(short_name.as_bytes()), // 2+ 8 byte = 31
|
||||
AdStructure::Flags(LE_GENERAL_DISCOVERABLE | BR_EDR_NOT_SUPPORTED),
|
||||
AdStructure::ServiceUuids128(&[service_uuid]),
|
||||
],
|
||||
&mut advertiser_data[..],
|
||||
)?;
|
||||
|
|
@ -278,11 +193,7 @@ async fn advertise<'values, 'server, C: Controller>(
|
|||
let advertiser = peripheral
|
||||
.advertise(
|
||||
&trouble_host::advertise::AdvertisementParameters {
|
||||
primary_phy: PhyKind::Le2M,
|
||||
secondary_phy: PhyKind::Le2M,
|
||||
timeout: Some(embassy_time::Duration::from_secs(3)),
|
||||
interval_min: embassy_time::Duration::from_millis(150),
|
||||
interval_max: embassy_time::Duration::from_secs(1),
|
||||
timeout: Some(embassy_time::Duration::from_secs(2)),
|
||||
..Default::default()
|
||||
},
|
||||
Advertisement::ConnectableScannableUndirected {
|
||||
|
|
|
|||
126
src/main.rs
126
src/main.rs
|
|
@ -177,6 +177,116 @@ async fn main(spawner: Spawner) -> ! {
|
|||
screen
|
||||
};
|
||||
|
||||
{
|
||||
let _ = screen
|
||||
.update(screen::ScreenState::Initial)
|
||||
.inspect_err(log_screen_error);
|
||||
embassy_time::Timer::after(embassy_time::Duration::from_secs(2)).await;
|
||||
|
||||
#[cfg(feature = "spat")]
|
||||
{
|
||||
let _ = screen
|
||||
.update(screen::ScreenState::GlosaSearching(
|
||||
applogic::GlosaSearching {
|
||||
known_intersection_ids: alloc::vec![123],
|
||||
},
|
||||
))
|
||||
.inspect_err(log_screen_error);
|
||||
embassy_time::Timer::after(embassy_time::Duration::from_secs(2)).await;
|
||||
let _ = screen
|
||||
.update(screen::ScreenState::GlosaFound(applogic::GlosaFound {
|
||||
intersection_id: 1234,
|
||||
approach_id: Some(42),
|
||||
}))
|
||||
.inspect_err(log_screen_error);
|
||||
embassy_time::Timer::after(embassy_time::Duration::from_secs(2)).await;
|
||||
let _ = screen
|
||||
.update(screen::ScreenState::GlosaLocked(
|
||||
applogic::GlosaSignalInfo {
|
||||
intersection_id: 1234,
|
||||
signal_groups: alloc::vec![
|
||||
applogic::SignalGroupData {
|
||||
phase: applogic::SignalPhase::Red,
|
||||
maneuver: applogic::v2x::map::Maneuvers {
|
||||
left_allowed: true,
|
||||
staight_allowed: false,
|
||||
right_allowed: false
|
||||
},
|
||||
timing: Some(applogic::TimingData {
|
||||
min_end_sec: 23,
|
||||
likely_end_sec: None,
|
||||
max_end_sec: None
|
||||
}),
|
||||
usages: alloc::vec![applogic::v2x::map::UsageType::Vehicle]
|
||||
},
|
||||
applogic::SignalGroupData {
|
||||
phase: applogic::SignalPhase::Red,
|
||||
maneuver: applogic::v2x::map::Maneuvers {
|
||||
left_allowed: true,
|
||||
staight_allowed: false,
|
||||
right_allowed: false
|
||||
},
|
||||
timing: Some(applogic::TimingData {
|
||||
min_end_sec: 23,
|
||||
likely_end_sec: None,
|
||||
max_end_sec: None
|
||||
}),
|
||||
usages: alloc::vec![applogic::v2x::map::UsageType::Vehicle]
|
||||
}
|
||||
],
|
||||
},
|
||||
))
|
||||
.inspect_err(log_screen_error);
|
||||
embassy_time::Timer::after(embassy_time::Duration::from_secs(2)).await;
|
||||
|
||||
let _ = screen
|
||||
.update(screen::ScreenState::GlosaLocked(
|
||||
applogic::GlosaSignalInfo {
|
||||
intersection_id: 1234,
|
||||
signal_groups: alloc::vec![applogic::SignalGroupData {
|
||||
phase: applogic::SignalPhase::Red,
|
||||
maneuver: applogic::v2x::map::Maneuvers {
|
||||
left_allowed: true,
|
||||
staight_allowed: false,
|
||||
right_allowed: false
|
||||
},
|
||||
timing: Some(applogic::TimingData {
|
||||
min_end_sec: 23,
|
||||
likely_end_sec: Some(24),
|
||||
max_end_sec: Some(42)
|
||||
}),
|
||||
usages: alloc::vec![
|
||||
applogic::v2x::map::UsageType::Vehicle,
|
||||
applogic::v2x::map::UsageType::Bike,
|
||||
]
|
||||
}],
|
||||
},
|
||||
))
|
||||
.inspect_err(log_screen_error);
|
||||
embassy_time::Timer::after(embassy_time::Duration::from_secs(2)).await;
|
||||
}
|
||||
|
||||
#[cfg(feature = "denm")]
|
||||
{
|
||||
use c_its_parser::standards::cdd_2_2_1::etsi_its_cdd::EmergencyVehicleApproachingSubCauseCode;
|
||||
let _ = screen
|
||||
.update(screen::ScreenState::DenmWarning(
|
||||
// c_its_parser::standards::cdd_2_2_1::etsi_its_cdd::CauseCodeChoice::emergencyVehicleApproaching95(0.into()),
|
||||
c_its_parser::standards::cdd_2_2_1::etsi_its_cdd::etsi_its_cdd::CauseCodeChoice::emergencyVehicleApproaching95(EmergencyVehicleApproachingSubCauseCode(0)),
|
||||
))
|
||||
.inspect_err(log_screen_error);
|
||||
embassy_time::Timer::after(embassy_time::Duration::from_secs(2)).await;
|
||||
|
||||
// try to reset screen for 4 more seconds
|
||||
for _ in 0..4 {
|
||||
let _ = screen
|
||||
.update(screen::ScreenState::Initial)
|
||||
.inspect_err(log_screen_error);
|
||||
embassy_time::Timer::after(embassy_time::Duration::from_secs(1)).await;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Setup UART I/O channel
|
||||
#[cfg(feature = "uart")]
|
||||
setup_uart0(
|
||||
|
|
@ -222,24 +332,12 @@ async fn main(spawner: Spawner) -> ! {
|
|||
let controller = ble::make_controller(peripherals.BT);
|
||||
let serial = &mac.as_bytes()[3..6];
|
||||
let name = alloc::format!(
|
||||
"C-ITS ESP {:02x}:{:02x}:{:02x}",
|
||||
"ESP32 C-ITS {:02x}{:02x}{:02x}",
|
||||
serial[0],
|
||||
serial[1],
|
||||
serial[2]
|
||||
);
|
||||
let short_name = alloc::format!("V2X {:02x}{:02x}", serial[1], serial[2]); // max 8 chars!
|
||||
|
||||
let fw_revision = core::env!("GIT_REVISION");
|
||||
let sw_revision = util::get_feature_list().join(",");
|
||||
spawner.spawn(
|
||||
ble::run(
|
||||
controller,
|
||||
name,
|
||||
short_name,
|
||||
ble::DeviceInfo::new(&mac.to_string(), fw_revision, &sw_revision),
|
||||
)
|
||||
.expect("Failed to spawn BLE task"),
|
||||
);
|
||||
spawner.spawn(ble::run(controller, name).expect("Failed to spawn BLE task"));
|
||||
}
|
||||
|
||||
// Setup GNSS:
|
||||
|
|
|
|||
38
src/util.rs
38
src/util.rs
|
|
@ -31,41 +31,3 @@ pub(crate) fn get_tx_feature_list() -> Vec<&'static str> {
|
|||
|
||||
features
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
pub(crate) fn get_feature_list() -> Vec<&'static str> {
|
||||
let mut features = alloc::vec![];
|
||||
|
||||
if cfg!(feature = "gnss_ubx") {
|
||||
features.push("gnss_ubx");
|
||||
}
|
||||
if cfg!(feature = "gnss_l67k") {
|
||||
features.push("gnss_l67k");
|
||||
}
|
||||
if cfg!(feature = "cam_tx") {
|
||||
features.push("cam_tx");
|
||||
}
|
||||
if cfg!(feature = "cam") {
|
||||
features.push("cam");
|
||||
}
|
||||
if cfg!(feature = "denm") {
|
||||
features.push("denm");
|
||||
}
|
||||
if cfg!(feature = "spat") {
|
||||
features.push("spat");
|
||||
}
|
||||
if cfg!(feature = "spat_debug") {
|
||||
features.push("spat_debug");
|
||||
}
|
||||
if cfg!(feature = "screen") {
|
||||
features.push("screen");
|
||||
}
|
||||
if cfg!(feature = "uart") {
|
||||
features.push("uart");
|
||||
}
|
||||
if cfg!(feature = "ble") {
|
||||
features.push("ble");
|
||||
}
|
||||
|
||||
features
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue