Compare commits
10 Commits
36daf8d6ee
...
2aaa64374b
| Author | SHA1 | Date | |
|---|---|---|---|
| 2aaa64374b | |||
| 75c544c461 | |||
| a0ece1696f | |||
| ecb06e1a56 | |||
| 3425cd2339 | |||
| ec1761f73e | |||
| f2ff1914b1 | |||
| 3bf7ebc997 | |||
| bac9300b7e | |||
| d01bda9dd5 |
@@ -1,5 +1,5 @@
|
|||||||
[target.xtensa-esp32s3-none-elf]
|
[target.xtensa-esp32s3-none-elf]
|
||||||
runner = "espflash flash --monitor --chip esp32s3 --partition-table ./partitions.csv"
|
runner = "espflash flash --monitor --partition-table ./partitions.csv"
|
||||||
|
|
||||||
[env]
|
[env]
|
||||||
ESP_LOG="info"
|
ESP_LOG="info"
|
||||||
@@ -11,7 +11,7 @@ ESP_RTOS_CONFIG_TICK_RATE_HZ="50"
|
|||||||
[build]
|
[build]
|
||||||
rustflags = [
|
rustflags = [
|
||||||
"-C", "link-arg=-nostartfiles",
|
"-C", "link-arg=-nostartfiles",
|
||||||
"-C", "force-frame-pointers"
|
"-Z", "stack-protector=all",
|
||||||
]
|
]
|
||||||
|
|
||||||
target = "xtensa-esp32s3-none-elf"
|
target = "xtensa-esp32s3-none-elf"
|
||||||
|
|||||||
17
Cargo.toml
17
Cargo.toml
@@ -15,9 +15,10 @@ real-output = []
|
|||||||
dual-core = []
|
dual-core = []
|
||||||
simulation = ["dep:rmp"]
|
simulation = ["dep:rmp"]
|
||||||
radio = [
|
radio = [
|
||||||
"dep:bleps",
|
|
||||||
"dep:esp-radio",
|
"dep:esp-radio",
|
||||||
"dep:reqwless"
|
"dep:reqwless",
|
||||||
|
"dep:trouble-host",
|
||||||
|
"esp-rtos/esp-radio"
|
||||||
]
|
]
|
||||||
motion = ["mpu", "gps"]
|
motion = ["mpu", "gps"]
|
||||||
max-usb-power = []
|
max-usb-power = []
|
||||||
@@ -53,7 +54,6 @@ embassy-executor = { version = "0.9.0", features = [
|
|||||||
] }
|
] }
|
||||||
embassy-time = { version = "0.5.0", features = ["log"] }
|
embassy-time = { version = "0.5.0", features = ["log"] }
|
||||||
esp-rtos = { version = "0.2.0", features = [
|
esp-rtos = { version = "0.2.0", features = [
|
||||||
"esp-radio",
|
|
||||||
"embassy",
|
"embassy",
|
||||||
"esp-alloc",
|
"esp-alloc",
|
||||||
"esp32s3",
|
"esp32s3",
|
||||||
@@ -69,11 +69,11 @@ embassy-embedded-hal = "0.5.0"
|
|||||||
embedded-hal-async = "1.0.0"
|
embedded-hal-async = "1.0.0"
|
||||||
nalgebra = { version = "0.33.2", default-features = false, features = ["alloc", "libm"] }
|
nalgebra = { version = "0.33.2", default-features = false, features = ["alloc", "libm"] }
|
||||||
xtensa-lx-rt = { version = "*", features = ["float-save-restore"] }
|
xtensa-lx-rt = { version = "*", features = ["float-save-restore"] }
|
||||||
futures = { version = "0.3.31", default-features = false, features = ["async-await"] }
|
|
||||||
micromath = "2.1.0"
|
micromath = "2.1.0"
|
||||||
enumset = "1.1.10"
|
enumset = "1.1.10"
|
||||||
enum-map = "2.7.3"
|
enum-map = "2.7.3"
|
||||||
portable-atomic = { version = "1.11", features = ["critical-section"] }
|
portable-atomic = { version = "1.11", features = ["critical-section"] }
|
||||||
|
embassy-futures = { version = "0.1.2", features = ["log"] }
|
||||||
|
|
||||||
# Telemetry outputs
|
# Telemetry outputs
|
||||||
esp-radio = { version = "*", optional = true, features = [
|
esp-radio = { version = "*", optional = true, features = [
|
||||||
@@ -85,9 +85,9 @@ esp-radio = { version = "*", optional = true, features = [
|
|||||||
"coex",
|
"coex",
|
||||||
"unstable"
|
"unstable"
|
||||||
] }
|
] }
|
||||||
bleps = { git = "https://github.com/bjoernQ/bleps", optional = true, package = "bleps", rev = "a5148d8ae679e021b78f53fd33afb8bb35d0b62e", features = [ "macros", "async"] }
|
embassy-net = { version = "0.7.1", features = ["alloc", "dns", "medium-ethernet", "proto-ipv4", "tcp", "udp", "dhcpv4"] }
|
||||||
embedded-graphics = { version = "0.8.1", features = ["nalgebra_support"] }
|
reqwless = { version = "0.13.0", optional = true, features = ["log", "alloc"] }
|
||||||
ssd1306 = { version = "0.10.0", features = ["async"], optional = true }
|
trouble-host = { version = "0.5.1", optional = true, features = ["log"] }
|
||||||
|
|
||||||
# Sensors
|
# Sensors
|
||||||
nmea = { version = "0.7.0", optional = true, default-features = false, features = [
|
nmea = { version = "0.7.0", optional = true, default-features = false, features = [
|
||||||
@@ -127,6 +127,9 @@ lto = 'fat'
|
|||||||
opt-level = 's'
|
opt-level = 's'
|
||||||
overflow-checks = false
|
overflow-checks = false
|
||||||
|
|
||||||
|
[profile.dev.package.esp-radio]
|
||||||
|
opt-level = 3
|
||||||
|
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
image = "0.25"
|
image = "0.25"
|
||||||
rmp = { path = "../msgpack-rust/rmp/" }
|
rmp = { path = "../msgpack-rust/rmp/" }
|
||||||
|
|||||||
6
build.rs
6
build.rs
@@ -198,7 +198,7 @@ fn write_sim_data() {
|
|||||||
|
|
||||||
generate_sim_data::<AnnotationReading>(&[&annotation_input], &annotation_output, 0);
|
generate_sim_data::<AnnotationReading>(&[&annotation_input], &annotation_output, 0);
|
||||||
generate_sim_data::<GPSReading>(&[&gps_input], &gps_output, 0);
|
generate_sim_data::<GPSReading>(&[&gps_input], &gps_output, 0);
|
||||||
generate_sim_data::<IMUReading>(&[&accel_input, &gyro_input], &motion_output, 2);
|
generate_sim_data::<IMUReading>(&[&accel_input, &gyro_input], &motion_output, 3);
|
||||||
|
|
||||||
let mut unified_fd = File::create(unified_output.clone()).unwrap();
|
let mut unified_fd = File::create(unified_output.clone()).unwrap();
|
||||||
|
|
||||||
@@ -237,7 +237,7 @@ fn write_sim_data() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if unified_fd.metadata().unwrap().len() as usize >= data_size {
|
if unified_fd.metadata().unwrap().len() as usize >= data_size {
|
||||||
// FIXME: Need to implement data resampling
|
// FIXME: Need to implement automatic data resampling
|
||||||
panic!("Simulation data is too big! Cannot fit {:#x} bytes into a partition with a size of {data_size:#x} bytes.", unified_fd.metadata().unwrap().len());
|
panic!("Simulation data is too big! Cannot fit {:#x} bytes into a partition with a size of {data_size:#x} bytes.", unified_fd.metadata().unwrap().len());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -252,6 +252,8 @@ fn parse_partition_number(n: &str) -> Option<usize> {
|
|||||||
Some(usize::from_str_radix(hex_offset, 16).unwrap())
|
Some(usize::from_str_radix(hex_offset, 16).unwrap())
|
||||||
} else if let Some(mb_offset) = n.strip_suffix("M") {
|
} else if let Some(mb_offset) = n.strip_suffix("M") {
|
||||||
Some(mb_offset.parse::<usize>().unwrap() * 1024 * 1024)
|
Some(mb_offset.parse::<usize>().unwrap() * 1024 * 1024)
|
||||||
|
} else if let Some(kb_offset) = n.strip_suffix("K") {
|
||||||
|
Some(kb_offset.parse::<usize>().unwrap() * 1024)
|
||||||
} else {
|
} else {
|
||||||
Some(n.parse().unwrap())
|
Some(n.parse().unwrap())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,4 +4,4 @@
|
|||||||
nvs, data, nvs, , 0x6000,
|
nvs, data, nvs, , 0x6000,
|
||||||
phy_init, data, phy, , 0x1000,
|
phy_init, data, phy, , 0x1000,
|
||||||
factory, app, factory, , 2M,
|
factory, app, factory, , 2M,
|
||||||
sim, data, undefined,, 6M,
|
sim, data, undefined,, 5000K,
|
||||||
|
|||||||
|
@@ -7,13 +7,13 @@
|
|||||||
)]
|
)]
|
||||||
|
|
||||||
|
|
||||||
use core::ptr::addr_of_mut;
|
use core::{num::{self, Wrapping}, ptr::addr_of_mut};
|
||||||
|
|
||||||
use alloc::sync::Arc;
|
use alloc::{string::String, sync::Arc};
|
||||||
use embassy_executor::Spawner;
|
use embassy_executor::Spawner;
|
||||||
use embassy_time::{Instant, Timer};
|
use embassy_time::{Instant, Timer};
|
||||||
|
|
||||||
use esp_hal::{gpio::{Output, OutputConfig, Pin}, time::Rate};
|
use esp_hal::{gpio::{Output, OutputConfig, Pin}, time::Rate, xtensa_lx::debug_break};
|
||||||
use esp_hal::{
|
use esp_hal::{
|
||||||
clock::CpuClock, system::{AppCoreGuard, CpuControl, Stack}, timer::{systimer::SystemTimer, timg::{TimerGroup, Wdt}}
|
clock::CpuClock, system::{AppCoreGuard, CpuControl, Stack}, timer::{systimer::SystemTimer, timg::{TimerGroup, Wdt}}
|
||||||
};
|
};
|
||||||
@@ -22,7 +22,7 @@ use embassy_sync::{
|
|||||||
pubsub::PubSubChannel,
|
pubsub::PubSubChannel,
|
||||||
blocking_mutex::raw::NoopRawMutex
|
blocking_mutex::raw::NoopRawMutex
|
||||||
};
|
};
|
||||||
|
use static_cell::ConstStaticCell;
|
||||||
use log::*;
|
use log::*;
|
||||||
use renderbug_embassy::{events::Prediction, graphics::display::DisplayControls, logging::RenderbugLogger, tasks::{oled::{OledUI, OledUiSurfacePool, oled_ui}, safetyui::{SafetyUi, safety_ui_main}, ui::UiSurfacePool}};
|
use renderbug_embassy::{events::Prediction, graphics::display::DisplayControls, logging::RenderbugLogger, tasks::{oled::{OledUI, OledUiSurfacePool, oled_ui}, safetyui::{SafetyUi, safety_ui_main}, ui::UiSurfacePool}};
|
||||||
use renderbug_embassy::events::Measurement;
|
use renderbug_embassy::events::Measurement;
|
||||||
@@ -45,9 +45,6 @@ extern crate alloc;
|
|||||||
// 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!();
|
||||||
|
|
||||||
static STATIC_HI_EXEC: StaticCell<InterruptExecutor<2>> = StaticCell::new();
|
|
||||||
static CORE2_EXEC: StaticCell<Executor> = StaticCell::new();
|
|
||||||
static mut CORE2_STACK: Stack<16384> = Stack::new();
|
|
||||||
#[cfg(feature="radio")]
|
#[cfg(feature="radio")]
|
||||||
static WIFI_INIT: StaticCell<esp_radio::Controller<'static>> = StaticCell::new();
|
static WIFI_INIT: StaticCell<esp_radio::Controller<'static>> = StaticCell::new();
|
||||||
|
|
||||||
@@ -83,14 +80,14 @@ async fn main(spawner: Spawner) {
|
|||||||
let timer0 = TimerGroup::new(peripherals.TIMG0);
|
let timer0 = TimerGroup::new(peripherals.TIMG0);
|
||||||
let mut wdt = timer0.wdt;
|
let mut wdt = timer0.wdt;
|
||||||
wdt.set_timeout(esp_hal::timer::timg::MwdtStage::Stage0, esp_hal::time::Duration::from_secs(5));
|
wdt.set_timeout(esp_hal::timer::timg::MwdtStage::Stage0, esp_hal::time::Duration::from_secs(5));
|
||||||
//wdt.enable();
|
wdt.enable();
|
||||||
|
|
||||||
let swi = SoftwareInterruptControl::new(peripherals.SW_INTERRUPT);
|
|
||||||
//let hi_exec = STATIC_HI_EXEC.init(InterruptExecutor::new(swi.software_interrupt2));
|
|
||||||
//let hi_spawn = hi_exec.start(esp_hal::interrupt::Priority::max());
|
|
||||||
|
|
||||||
|
// Spawn the rendering task as soon as possible so it can start pushing pixels
|
||||||
spawner.must_spawn(renderbug_embassy::tasks::render::render(peripherals.RMT, peripherals.GPIO5.degrade(), surfaces, safety_surfaces, display_controls, wdt));
|
spawner.must_spawn(renderbug_embassy::tasks::render::render(peripherals.RMT, peripherals.GPIO5.degrade(), surfaces, safety_surfaces, display_controls, wdt));
|
||||||
|
|
||||||
|
// Wait one scheduler tick for the rendering task to get initialized
|
||||||
|
Timer::after_ticks(1).await;
|
||||||
|
|
||||||
#[cfg(feature="motion")]
|
#[cfg(feature="motion")]
|
||||||
{
|
{
|
||||||
use embassy_embedded_hal::shared_bus::asynch::i2c::I2cDevice;
|
use embassy_embedded_hal::shared_bus::asynch::i2c::I2cDevice;
|
||||||
@@ -131,7 +128,7 @@ async fn main(spawner: Spawner) {
|
|||||||
let mut storage = SharedFlash::new(FlashStorage::new());
|
let mut storage = SharedFlash::new(FlashStorage::new());
|
||||||
let mut buf = [8; 1024];
|
let mut buf = [8; 1024];
|
||||||
let partitions = esp_bootloader_esp_idf::partitions::read_partition_table(&mut storage, &mut buf).unwrap();
|
let partitions = esp_bootloader_esp_idf::partitions::read_partition_table(&mut storage, &mut buf).unwrap();
|
||||||
for sim_data in SimDataTable::open(storage, partitions).expect("Could not find partition for sim data!") {
|
for sim_data in SimDataTable::open(storage, partitions).expect("Could not find sim data!") {
|
||||||
let srcid = sim_data.srcid();
|
let srcid = sim_data.srcid();
|
||||||
info!("Found simulation data for {srcid:?}");
|
info!("Found simulation data for {srcid:?}");
|
||||||
if spawner.spawn(renderbug_embassy::tasks::simulation::simulation_task(sim_data, motion_bus.dyn_sender())).is_err() {
|
if spawner.spawn(renderbug_embassy::tasks::simulation::simulation_task(sim_data, motion_bus.dyn_sender())).is_err() {
|
||||||
@@ -141,15 +138,23 @@ async fn main(spawner: Spawner) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature="radio")]
|
#[cfg(feature="radio")]
|
||||||
let wifi_init = {
|
let (wifi, network_device, ble) = {
|
||||||
info!("Configuring wifi");
|
info!("Configuring wifi");
|
||||||
WIFI_INIT.init_with(|| {esp_radio::init().expect("Failed to initialize radio controller")})
|
esp_radio::wifi_set_log_verbose();
|
||||||
|
let wifi_init = WIFI_INIT.init_with(|| {esp_radio::init().expect("Failed to initialize radio controller")});
|
||||||
|
|
||||||
|
let ble = esp_radio::ble::controller::BleConnector::new(wifi_init, peripherals.BT, esp_radio::ble::Config::default()).unwrap();
|
||||||
|
|
||||||
|
let (wifi, interfaces) = esp_radio::wifi::new(wifi_init, peripherals.WIFI, esp_radio::wifi::Config::default())
|
||||||
|
.expect("Failed to initialize WIFI!");
|
||||||
|
|
||||||
|
(wifi, interfaces.sta, ble)
|
||||||
};
|
};
|
||||||
|
|
||||||
info!("Starting core 2");
|
|
||||||
|
|
||||||
let core2_main = |spawner: Spawner| {
|
let core2_main = |spawner: Spawner| {
|
||||||
static PREDICTIONS: StaticCell<PubSubChannel<NoopRawMutex, Prediction, 15, 5, 1>> = StaticCell::new();
|
info!("Starting application tasks");
|
||||||
|
|
||||||
|
static PREDICTIONS: StaticCell<PubSubChannel<NoopRawMutex, Prediction, 15, 6, 1>> = StaticCell::new();
|
||||||
let predictions = PREDICTIONS.init(PubSubChannel::new());
|
let predictions = PREDICTIONS.init(PubSubChannel::new());
|
||||||
|
|
||||||
#[cfg(not(feature="demo"))]
|
#[cfg(not(feature="demo"))]
|
||||||
@@ -173,8 +178,26 @@ async fn main(spawner: Spawner) {
|
|||||||
|
|
||||||
#[cfg(feature="radio")]
|
#[cfg(feature="radio")]
|
||||||
{
|
{
|
||||||
info!("Launching networking stack");
|
use embassy_net::StackResources;
|
||||||
spawner.must_spawn(renderbug_embassy::tasks::wifi::wireless_task(predictions.dyn_subscriber().unwrap(), wifi_init, peripherals.WIFI));
|
use esp_hal::rng::Rng;
|
||||||
|
use static_cell::ConstStaticCell;
|
||||||
|
|
||||||
|
info!("Setting up networking stack");
|
||||||
|
static RESOURCES: ConstStaticCell<StackResources<5>> = ConstStaticCell::new(StackResources::new());
|
||||||
|
let config = embassy_net::Config::dhcpv4(Default::default());
|
||||||
|
let seed = Rng::new().random() as i32;
|
||||||
|
let (stack, runner) = embassy_net::new(network_device, config, RESOURCES.take(), seed as u64);
|
||||||
|
info!("Launching network services");
|
||||||
|
//spawner.must_spawn(renderbug_embassy::tasks::wifi::net_task(runner));
|
||||||
|
|
||||||
|
info!("Starting connectivity task");
|
||||||
|
//spawner.must_spawn(renderbug_embassy::tasks::wifi::wifi_connect_task(wifi, stack, motion_bus.dyn_sender()));
|
||||||
|
|
||||||
|
info!("Launching HTTP telemetry");
|
||||||
|
//spawner.must_spawn(renderbug_embassy::tasks::wifi::http_telemetry_task(predictions.dyn_subscriber().unwrap(), stack));
|
||||||
|
|
||||||
|
info!("Starting BLE services");
|
||||||
|
spawner.must_spawn(renderbug_embassy::tasks::ble::ble_task(ble, predictions.dyn_subscriber().unwrap(), spawner));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature="dual-core")]
|
#[cfg(feature="dual-core")]
|
||||||
@@ -194,13 +217,21 @@ async fn main(spawner: Spawner) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
#[cfg(feature="dual-core")]
|
#[cfg(feature="dual-core")]
|
||||||
|
{
|
||||||
|
static mut CORE2_STACK: Stack<16384> = Stack::new();
|
||||||
|
let swi = SoftwareInterruptControl::new(peripherals.SW_INTERRUPT);
|
||||||
esp_rtos::start_second_core(peripherals.CPU_CTRL, swi.software_interrupt0, swi.software_interrupt1, unsafe { &mut *addr_of_mut!(CORE2_STACK) }, || {
|
esp_rtos::start_second_core(peripherals.CPU_CTRL, swi.software_interrupt0, swi.software_interrupt1, unsafe { &mut *addr_of_mut!(CORE2_STACK) }, || {
|
||||||
|
info!("Second CPU core started");
|
||||||
|
static CORE2_EXEC: StaticCell<Executor> = StaticCell::new();
|
||||||
let exec = CORE2_EXEC.init_with(|| { Executor::new() });
|
let exec = CORE2_EXEC.init_with(|| { Executor::new() });
|
||||||
exec.run(core2_main);
|
exec.run(core2_main);
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(not(feature="dual-core"))]
|
#[cfg(not(feature="dual-core"))]
|
||||||
core2_main(spawner);
|
core2_main(spawner);
|
||||||
|
|
||||||
|
info!("Ready to rock and roll");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[embassy_executor::task]
|
#[embassy_executor::task]
|
||||||
@@ -216,7 +247,11 @@ async fn wdt_task(mut wdt: Wdt<esp_hal::peripherals::TIMG1<'static>>) {
|
|||||||
|
|
||||||
#[embassy_executor::task]
|
#[embassy_executor::task]
|
||||||
async fn print_telemetry(mut events: DynSubscriber<'static, Prediction>) {
|
async fn print_telemetry(mut events: DynSubscriber<'static, Prediction>) {
|
||||||
|
info!("telemetry ready");
|
||||||
|
let mut num_events = Wrapping(0usize);
|
||||||
loop {
|
loop {
|
||||||
info!("predict={:?}", events.next_message_pure().await);
|
let next = events.next_message_pure().await;
|
||||||
|
trace!("idx={} predict={next:?}", num_events.0);
|
||||||
|
num_events += 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
use embassy_sync::{channel::DynamicSender, pubsub::DynPublisher};
|
use embassy_sync::pubsub::DynPublisher;
|
||||||
use embassy_time::{Duration, Instant};
|
use embassy_time::{Duration, Instant};
|
||||||
use nalgebra::{Rotation3, Vector2, Vector3, ComplexField, RealField};
|
use nalgebra::{Rotation3, Vector2, Vector3, ComplexField, RealField};
|
||||||
use log::*;
|
use log::*;
|
||||||
|
|||||||
@@ -74,10 +74,14 @@ pub enum SensorSource {
|
|||||||
IMU,
|
IMU,
|
||||||
GPS,
|
GPS,
|
||||||
|
|
||||||
|
// Connectivity related
|
||||||
|
Wifi,
|
||||||
|
|
||||||
// Fusion outputs
|
// Fusion outputs
|
||||||
GravityReference,
|
GravityReference,
|
||||||
ForwardsReference,
|
ForwardsReference,
|
||||||
Location,
|
Location,
|
||||||
|
Cloud,
|
||||||
|
|
||||||
// Simulated sensors
|
// Simulated sensors
|
||||||
Demo,
|
Demo,
|
||||||
|
|||||||
@@ -115,6 +115,10 @@ const SENSOR_IMAGES: &[SensorImage] = &[
|
|||||||
source: SensorSource::Location,
|
source: SensorSource::Location,
|
||||||
on: &images::LOCATION_ON, off: &images::LOCATION_OFF,
|
on: &images::LOCATION_ON, off: &images::LOCATION_OFF,
|
||||||
},
|
},
|
||||||
|
SensorImage {
|
||||||
|
source: SensorSource::Wifi,
|
||||||
|
on: &images::ONLINE_CONNECTING, off: &images::OFFLINE
|
||||||
|
},
|
||||||
#[cfg(feature="demo")]
|
#[cfg(feature="demo")]
|
||||||
SensorImage {
|
SensorImage {
|
||||||
source: SensorSource::Demo,
|
source: SensorSource::Demo,
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ impl SsdPixel {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const DITHER_MAP: [u32;15] = [
|
const DITHER_MAP: [u16;15] = [
|
||||||
0b1000_0000_0000_0000,
|
0b1000_0000_0000_0000,
|
||||||
0b1000_0000_0010_0000,
|
0b1000_0000_0010_0000,
|
||||||
0b1010_0000_0010_0000,
|
0b1010_0000_0010_0000,
|
||||||
@@ -62,9 +62,9 @@ impl AdditivePixelSink<BinaryColor> for SsdPixel {
|
|||||||
0 => (),
|
0 => (),
|
||||||
255 => self.set_pixel(pixel),
|
255 => self.set_pixel(pixel),
|
||||||
_ => {
|
_ => {
|
||||||
let dither_value = DITHER_MAP[opacity as usize / 16];
|
let dither_value = DITHER_MAP[opacity as usize / 17];
|
||||||
let dither_x = self.coords.x % 4;
|
let dither_x = self.coords.x % 4;
|
||||||
let dither_y = self.coords.x % 4;
|
let dither_y = self.coords.y % 4;
|
||||||
if dither_value.shr(dither_x).shr(dither_y * 4).bitand(0x01) == 1 {
|
if dither_value.shr(dither_x).shr(dither_y * 4).bitand(0x01) == 1 {
|
||||||
self.set_pixel(pixel);
|
self.set_pixel(pixel);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,6 +9,9 @@ pub mod idle;
|
|||||||
pub mod logging;
|
pub mod logging;
|
||||||
pub mod graphics;
|
pub mod graphics;
|
||||||
|
|
||||||
|
#[cfg(feature="simulation")]
|
||||||
|
pub mod storage;
|
||||||
|
|
||||||
#[cfg(feature="simulation")]
|
#[cfg(feature="simulation")]
|
||||||
pub mod simdata;
|
pub mod simdata;
|
||||||
|
|
||||||
|
|||||||
@@ -97,7 +97,7 @@ impl RmpData for StreamHeader {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn write_rmp<Writer: RmpWrite>(&self, writer: &mut Writer) -> Result<(), ValueWriteError<Writer::Error>> {
|
fn write_rmp<Writer: RmpWrite>(&self, _writer: &mut Writer) -> Result<(), ValueWriteError<Writer::Error>> {
|
||||||
todo!()
|
todo!()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
122
src/storage.rs
Normal file
122
src/storage.rs
Normal file
@@ -0,0 +1,122 @@
|
|||||||
|
use core::{cell::RefCell, fmt::Formatter};
|
||||||
|
|
||||||
|
use alloc::rc::Rc;
|
||||||
|
use embedded_storage::{ReadStorage, Storage};
|
||||||
|
use log::*;
|
||||||
|
use rmp::decode::{RmpRead, RmpReadErr};
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct SharedFlash<S> {
|
||||||
|
storage: Rc<RefCell<S>>
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<S> Clone for SharedFlash<S> {
|
||||||
|
fn clone(&self) -> Self {
|
||||||
|
Self {
|
||||||
|
storage: Rc::clone(&self.storage)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<S> SharedFlash<S> {
|
||||||
|
pub fn new(storage: S) -> Self {
|
||||||
|
Self {
|
||||||
|
storage: Rc::new(RefCell::new(storage))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<S: Storage> Storage for SharedFlash<S> {
|
||||||
|
fn write(&mut self, offset: u32, bytes: &[u8]) -> Result<(), Self::Error> {
|
||||||
|
self.storage.borrow_mut().write(offset, bytes)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<S: ReadStorage> ReadStorage for SharedFlash<S> {
|
||||||
|
type Error = S::Error;
|
||||||
|
|
||||||
|
fn read(&mut self, offset: u32, bytes: &mut [u8]) -> Result<(), Self::Error> {
|
||||||
|
self.storage.borrow_mut().read(offset, bytes)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn capacity(&self) -> usize {
|
||||||
|
self.storage.borrow().capacity()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum RangeReadError<E> {
|
||||||
|
OutOfData,
|
||||||
|
Storage(E)
|
||||||
|
}
|
||||||
|
impl<E: core::fmt::Debug + 'static> RmpReadErr for RangeReadError<E> {}
|
||||||
|
|
||||||
|
impl<E> core::fmt::Display for RangeReadError<E> {
|
||||||
|
fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
|
||||||
|
f.write_str("RmpErr")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct RangeReader<S> {
|
||||||
|
storage: S,
|
||||||
|
start: usize,
|
||||||
|
end: usize,
|
||||||
|
offset: usize
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<S: ReadStorage> RangeReader<S> {
|
||||||
|
pub const fn new(storage: S, start: usize, end: usize) -> Self {
|
||||||
|
assert!(start <= end);
|
||||||
|
// TODO: Should add bounds checking since we will know the size of the chunk already
|
||||||
|
Self {
|
||||||
|
storage,
|
||||||
|
start,
|
||||||
|
end,
|
||||||
|
offset: 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn seek(&mut self, offset: usize) -> Result<(), RangeReadError<S::Error>> {
|
||||||
|
self.offset += offset;
|
||||||
|
if self.offset > self.end {
|
||||||
|
Err(RangeReadError::OutOfData)
|
||||||
|
} else {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn subset(&self, size: usize) -> Result<Self, RangeReadError<S::Error>> where S: Clone + core::fmt::Debug {
|
||||||
|
trace!("subset {:#02x}:{:#02x} -> {:#02x}:{:#02x}", self.start, self.end, self.start + self.offset, self.start + self.offset + size);
|
||||||
|
if self.start + self.offset + size > self.end {
|
||||||
|
Err(RangeReadError::OutOfData)
|
||||||
|
} else {
|
||||||
|
Ok(Self {
|
||||||
|
storage: self.storage.clone(),
|
||||||
|
start: self.offset + self.start,
|
||||||
|
end: self.start + self.offset + size,
|
||||||
|
offset: 0
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<S: ReadStorage> RmpRead for RangeReader<S> where S::Error: core::fmt::Debug + 'static {
|
||||||
|
type Error = RangeReadError<S::Error>;
|
||||||
|
|
||||||
|
fn read_exact_buf(&mut self, buf: &mut [u8]) -> Result<(), RangeReadError<S::Error>> {
|
||||||
|
let pos = self.start + self.offset;
|
||||||
|
if pos > self.end {
|
||||||
|
Err(RangeReadError::OutOfData)
|
||||||
|
} else {
|
||||||
|
assert!(pos + buf.len() <= self.end);
|
||||||
|
match self.storage.read(pos as u32, buf) {
|
||||||
|
Ok(_) => {
|
||||||
|
self.offset += buf.len();
|
||||||
|
Ok(())
|
||||||
|
},
|
||||||
|
Err(err) => Err(RangeReadError::Storage(err))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
224
src/tasks/ble.rs
Normal file
224
src/tasks/ble.rs
Normal file
@@ -0,0 +1,224 @@
|
|||||||
|
|
||||||
|
use core::ptr::slice_from_raw_parts;
|
||||||
|
|
||||||
|
use embassy_executor::Spawner;
|
||||||
|
use embassy_futures::select::Either;
|
||||||
|
use embassy_sync::{blocking_mutex::raw::NoopRawMutex, pubsub::{DynSubscriber, PubSubChannel, Publisher}};
|
||||||
|
use esp_radio::ble::controller::BleConnector;
|
||||||
|
use static_cell::{ConstStaticCell, StaticCell};
|
||||||
|
use trouble_host::{attribute, prelude::*, types::gatt_traits::FromGattError};
|
||||||
|
use log::*;
|
||||||
|
|
||||||
|
use crate::{backoff::Backoff, events::Prediction};
|
||||||
|
|
||||||
|
#[gatt_server]
|
||||||
|
struct SerialServer {
|
||||||
|
serial_service: SerialService,
|
||||||
|
location_service: LocationService,
|
||||||
|
renderbug_service: RenderbugService
|
||||||
|
}
|
||||||
|
|
||||||
|
#[gatt_service(uuid="00000000-f43a-48c4-8411-efe6a38a5d75")]
|
||||||
|
struct RenderbugService {
|
||||||
|
// 32 bit float, 0 exponent, m/s velocity unit
|
||||||
|
#[descriptor(uuid=descriptors::CHARACTERISTIC_PRESENTATION_FORMAT, read, value = [0x14, 0x0, 0x27, 0x12, 0x01, 0x00, 0x00])]
|
||||||
|
#[descriptor(uuid = descriptors::MEASUREMENT_DESCRIPTION, read, value = "Speed (m/s)")]
|
||||||
|
#[characteristic(uuid="00000001-f43a-48c4-8411-efe6a38a5d75", notify)]
|
||||||
|
speed: f32,
|
||||||
|
|
||||||
|
// 32 bit float, 0 exponent, unitless
|
||||||
|
#[descriptor(uuid=descriptors::CHARACTERISTIC_PRESENTATION_FORMAT, read, value = [0x14, 0x0, 0x27, 0x00, 0x01, 0x00, 0x00])]
|
||||||
|
#[descriptor(uuid = descriptors::MEASUREMENT_DESCRIPTION, read, value = "Latitude")]
|
||||||
|
#[characteristic(uuid="00000002-f43a-48c4-8411-efe6a38a5d75", notify)]
|
||||||
|
latitude: f32,
|
||||||
|
|
||||||
|
// 32 bit float, 0 exponent, unitless
|
||||||
|
#[descriptor(uuid=descriptors::CHARACTERISTIC_PRESENTATION_FORMAT, read, value = [0x14, 0x0, 0x27, 0x00, 0x01, 0x00, 0x00])]
|
||||||
|
#[descriptor(uuid = descriptors::MEASUREMENT_DESCRIPTION, read, value = "Longitude")]
|
||||||
|
#[characteristic(uuid="00000003-f43a-48c4-8411-efe6a38a5d75", notify)]
|
||||||
|
longitude: f32,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[gatt_service(uuid=service::LOCATION_AND_NAVIGATION)]
|
||||||
|
struct LocationService {
|
||||||
|
#[characteristic(uuid=characteristic::LOCATION_AND_SPEED, notify)]
|
||||||
|
location_data: LocationData,
|
||||||
|
#[characteristic(uuid=characteristic::LN_FEATURE, read, value=0b0000101)] // Location and speed fields
|
||||||
|
features: u8,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Default, Debug)]
|
||||||
|
struct LocationData {
|
||||||
|
coordinates: [i32; 2],
|
||||||
|
velocity: u16
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FixedGattValue for LocationData {
|
||||||
|
fn as_gatt(&self) -> &[u8] {
|
||||||
|
let databuf = [0;Self::SIZE];
|
||||||
|
unsafe { core::slice::from_raw_parts(self as *const Self as *const u8, Self::SIZE) }
|
||||||
|
}
|
||||||
|
|
||||||
|
const SIZE: usize = core::mem::size_of::<Self>();
|
||||||
|
|
||||||
|
fn from_gatt(data: &[u8]) -> Result<Self, FromGattError> {
|
||||||
|
if data.len() != Self::SIZE {
|
||||||
|
Err(FromGattError::InvalidLength)
|
||||||
|
} else {
|
||||||
|
// SAFETY
|
||||||
|
// - Pointer is considered "valid" as per the rules outlined for validity in std::ptr v1.82.0
|
||||||
|
// - Pointer was generated from a slice of bytes matching the size of the type implementing Primitive, and all types implementing Primitive are valid for all possible configurations of bits
|
||||||
|
// - Primitive trait is constrained to require Copy
|
||||||
|
unsafe { Ok((data.as_ptr() as *const Self).read_unaligned()) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[gatt_service(uuid="6E400001-B5A3-F393-E0A9-E50E24DCCA9E")]
|
||||||
|
struct SerialService {
|
||||||
|
// The outside world writes here
|
||||||
|
#[descriptor(uuid = descriptors::MEASUREMENT_DESCRIPTION, read, value = "RX")]
|
||||||
|
#[characteristic(uuid = "6E400002-B5A3-F393-E0A9-E50E24DCCA9E", write)]
|
||||||
|
rx: heapless::String<128>,
|
||||||
|
|
||||||
|
// The outside world reads from here
|
||||||
|
#[descriptor(uuid = descriptors::MEASUREMENT_DESCRIPTION, read, value = "TX")]
|
||||||
|
#[characteristic(uuid = "6E400003-B5A3-F393-E0A9-E50E24DCCA9E", notify)]
|
||||||
|
tx: heapless::String<128>
|
||||||
|
}
|
||||||
|
|
||||||
|
#[embassy_executor::task]
|
||||||
|
async fn client_prediction_task(mut src: DynSubscriber<'static, Prediction>, sink: Publisher<'static, NoopRawMutex, Prediction, 5, 1, 1>) {
|
||||||
|
debug!("Started BLE client prediction stream");
|
||||||
|
loop {
|
||||||
|
sink.publish(src.next_message_pure().await).await;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static STATIC_RESOURCES: StaticCell<HostResources<DefaultPacketPool, 1, 1>> = StaticCell::new();
|
||||||
|
static STATIC_STACK: StaticCell<Stack<'static, ExternalController<BleConnector<'static>, 1>, DefaultPacketPool>> = StaticCell::new();
|
||||||
|
static STATIC_SERVER: StaticCell<SerialServer> = StaticCell::new();
|
||||||
|
static STATIC_CLIENT_PREDICTIONS: StaticCell<PubSubChannel<NoopRawMutex, Prediction, 5, 1, 1>> = StaticCell::new();
|
||||||
|
|
||||||
|
#[embassy_executor::task]
|
||||||
|
pub async fn ble_task(ble: BleConnector<'static>, predictions: DynSubscriber<'static, Prediction>, spawner: Spawner) {
|
||||||
|
info!("Starting BLE stack");
|
||||||
|
let server = STATIC_SERVER.init(SerialServer::new_with_config(GapConfig::Peripheral(PeripheralConfig { name: "Renderbug", appearance: &appearance::light_source::LED_ARRAY })).unwrap());
|
||||||
|
let control: ExternalController<esp_radio::ble::controller::BleConnector<'_>, 1> = ExternalController::new(ble);
|
||||||
|
let stack = STATIC_STACK.init(trouble_host::new(control, STATIC_RESOURCES.init(HostResources::new())));
|
||||||
|
let Host { mut peripheral, mut runner, .. } = stack.build();
|
||||||
|
|
||||||
|
let client_predictions = STATIC_CLIENT_PREDICTIONS.init(PubSubChannel::new());
|
||||||
|
spawner.must_spawn(client_prediction_task(predictions, client_predictions.publisher().unwrap()));
|
||||||
|
|
||||||
|
// The host task must be started and ticking beore we can start advertising, so we use a join() here instead of a separate task
|
||||||
|
let _ = embassy_futures::join::join(
|
||||||
|
runner.run(),
|
||||||
|
async {
|
||||||
|
loop {
|
||||||
|
let advertiser = Backoff::from_secs(5).forever().attempt(async || {
|
||||||
|
info!("Starting BLE advertising");
|
||||||
|
let mut adv_data = [0; 64];
|
||||||
|
let len = AdStructure::encode_slice(
|
||||||
|
&[
|
||||||
|
AdStructure::CompleteLocalName("Renderbug".as_bytes()),
|
||||||
|
AdStructure::Flags(LE_GENERAL_DISCOVERABLE | BR_EDR_NOT_SUPPORTED),
|
||||||
|
AdStructure::ServiceUuids128(&[
|
||||||
|
[ // Serial service
|
||||||
|
0x6E, 0x40, 0x00, 0x01,
|
||||||
|
0xB5, 0xA3,
|
||||||
|
0xF3, 0x93,
|
||||||
|
0xE0, 0xA9,
|
||||||
|
0xE5, 0x0E, 0x24, 0xDC, 0xCA, 0x9E
|
||||||
|
//0x9E, 0xCA, 0xDC, 0x24, 0x0E, 0xE5, 0xA9, 0xE0, 0x93, 0xF3, 0xA3, 0xB5,
|
||||||
|
//0x01, 0x00, 0x40, 0x6E,
|
||||||
|
],
|
||||||
|
[ // Renderbug mesh service
|
||||||
|
0x00, 0x00, 0x00, 0x00,
|
||||||
|
0xf4, 0x3a,
|
||||||
|
0x48, 0xc4,
|
||||||
|
0x84, 0x11,
|
||||||
|
0xef, 0xe6, 0xa3, 0x8a, 0x5d, 0x75
|
||||||
|
]
|
||||||
|
]),
|
||||||
|
AdStructure::ServiceUuids16(&[
|
||||||
|
// Location and navigation
|
||||||
|
[0x18, 0x19]
|
||||||
|
])
|
||||||
|
],
|
||||||
|
&mut adv_data[..],
|
||||||
|
).unwrap();
|
||||||
|
peripheral.advertise(
|
||||||
|
&Default::default(),
|
||||||
|
Advertisement::ConnectableScannableUndirected { adv_data: &adv_data[..len], scan_data: &[] }
|
||||||
|
).await
|
||||||
|
}).await.unwrap();
|
||||||
|
info!("Waiting for connection");
|
||||||
|
match advertiser.accept().await.and_then(|raw| { raw.with_attribute_server(server) }) {
|
||||||
|
Ok(conn) => {
|
||||||
|
if spawner.spawn(ble_connection(conn, server, client_predictions.dyn_subscriber().unwrap())).is_err() {
|
||||||
|
error!("Unable to spawn task for handling BLE connection! Please increase the embassy task pool size!");
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Err(e) => {
|
||||||
|
error!("Failed to accept new connection: {e:?}");
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
).await;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[embassy_executor::task]
|
||||||
|
async fn ble_connection(conn: GattConnection<'static, 'static, DefaultPacketPool>, server: &'static SerialServer<'static>, mut predictions: DynSubscriber<'static, Prediction>) {
|
||||||
|
info!("Started handling new connection with {:?}", conn.raw().peer_identity());
|
||||||
|
let mut location_data = LocationData::default();
|
||||||
|
loop {
|
||||||
|
let next = embassy_futures::select::select(conn.next(), predictions.next_message_pure()).await;
|
||||||
|
match next {
|
||||||
|
Either::First(gatt_event) => {
|
||||||
|
match gatt_event {
|
||||||
|
GattConnectionEvent::Disconnected { reason } => {
|
||||||
|
warn!("BLE Disconnected: {reason:?}");
|
||||||
|
return;
|
||||||
|
},
|
||||||
|
GattConnectionEvent::Gatt { event } => {
|
||||||
|
if let GattEvent::Write(event) = event {
|
||||||
|
if event.handle() == server.serial_service.rx.handle {
|
||||||
|
let buf = event.value(&server.serial_service.rx).unwrap();
|
||||||
|
info!("Received serial data {buf:?}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
_ => trace!("GATT event")
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Either::Second(prediction) => {
|
||||||
|
let evt_str = heapless::format!("{prediction:?}\n").unwrap();
|
||||||
|
server.serial_service.tx.notify(&conn, &evt_str).await.unwrap();
|
||||||
|
let update_location = match prediction {
|
||||||
|
Prediction::Velocity(v) => {
|
||||||
|
location_data.velocity = (v * 100.0) as u16;
|
||||||
|
server.renderbug_service.speed.notify(&conn, &v).await.unwrap();
|
||||||
|
true
|
||||||
|
},
|
||||||
|
Prediction::Location(coords) => {
|
||||||
|
location_data.coordinates[0] = (coords.x * 1e7) as i32;
|
||||||
|
location_data.coordinates[1] = (coords.y * 1e7) as i32;
|
||||||
|
server.renderbug_service.latitude.notify(&conn, &(coords.x as f32)).await.unwrap();
|
||||||
|
server.renderbug_service.longitude.notify(&conn, &(coords.y as f32)).await.unwrap();
|
||||||
|
true
|
||||||
|
},
|
||||||
|
_ => false
|
||||||
|
};
|
||||||
|
|
||||||
|
if update_location {
|
||||||
|
server.location_service.location_data.notify(&conn, &location_data).await.unwrap();
|
||||||
|
// TODO: write out NMEA sentences
|
||||||
|
//let mut nmea_str = heapless::String::from_utf8("foo");
|
||||||
|
//server.nmea_service.tx.notify(&conn, "foo").await.unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,6 +4,8 @@ pub mod mpu;
|
|||||||
pub mod gps;
|
pub mod gps;
|
||||||
#[cfg(feature="radio")]
|
#[cfg(feature="radio")]
|
||||||
pub mod wifi;
|
pub mod wifi;
|
||||||
|
#[cfg(feature="radio")]
|
||||||
|
pub mod ble;
|
||||||
#[cfg(feature="simulation")]
|
#[cfg(feature="simulation")]
|
||||||
pub mod simulation;
|
pub mod simulation;
|
||||||
#[cfg(feature="demo")]
|
#[cfg(feature="demo")]
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ use crate::{backoff::Backoff, graphics::ssd1306::SsdOutput, tasks::oled::{Locked
|
|||||||
|
|
||||||
|
|
||||||
#[embassy_executor::task]
|
#[embassy_executor::task]
|
||||||
pub async fn oled_render(mut output: SsdOutput, surfaces: OledUiSurfacePool, uniforms: LockedUniforms) {
|
pub async fn oled_render(mut output: SsdOutput, mut surfaces: OledUiSurfacePool, uniforms: LockedUniforms) {
|
||||||
warn!("Starting OLED rendering task");
|
warn!("Starting OLED rendering task");
|
||||||
Backoff::from_secs(1).forever().attempt::<_, (), DisplayError>(async || {
|
Backoff::from_secs(1).forever().attempt::<_, (), DisplayError>(async || {
|
||||||
const FPS: u64 = 30;
|
const FPS: u64 = 30;
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ use crate::graphics::display::NUM_PIXELS;
|
|||||||
use crate::{graphics::display::{BikeOutput, DisplayControls, Uniforms}, tasks::ui::UiSurfacePool};
|
use crate::{graphics::display::{BikeOutput, DisplayControls, Uniforms}, tasks::ui::UiSurfacePool};
|
||||||
|
|
||||||
#[embassy_executor::task]
|
#[embassy_executor::task]
|
||||||
pub async fn render(rmt: esp_hal::peripherals::RMT<'static>, gpio: AnyPin<'static>, surfaces: UiSurfacePool, safety_surfaces: UiSurfacePool, mut controls: DisplayControls, mut wdt: Wdt<esp_hal::peripherals::TIMG0<'static>>) {
|
pub async fn render(rmt: esp_hal::peripherals::RMT<'static>, gpio: AnyPin<'static>, mut surfaces: UiSurfacePool, mut safety_surfaces: UiSurfacePool, mut controls: DisplayControls, mut wdt: Wdt<esp_hal::peripherals::TIMG0<'static>>) {
|
||||||
let frequency: Rate = Rate::from_mhz(80);
|
let frequency: Rate = Rate::from_mhz(80);
|
||||||
let rmt = Rmt::new(rmt, frequency)
|
let rmt = Rmt::new(rmt, frequency)
|
||||||
.expect("Failed to initialize RMT").into_async();
|
.expect("Failed to initialize RMT").into_async();
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ use figments::prelude::*;
|
|||||||
use figments_render::output::Brightness;
|
use figments_render::output::Brightness;
|
||||||
use rgb::Rgba;
|
use rgb::Rgba;
|
||||||
use core::fmt::Debug;
|
use core::fmt::Debug;
|
||||||
use futures::join;
|
|
||||||
use log::*;
|
use log::*;
|
||||||
|
|
||||||
use crate::{animation::{AnimDisplay, AnimatedSurface, Animation}, events::{Personality, Prediction}, graphics::{display::{DisplayControls, SegmentSpace, Uniforms}, shaders::*}, tasks::ui::UiSurfacePool};
|
use crate::{animation::{AnimDisplay, AnimatedSurface, Animation}, events::{Personality, Prediction}, graphics::{display::{DisplayControls, SegmentSpace, Uniforms}, shaders::*}, tasks::ui::UiSurfacePool};
|
||||||
@@ -84,10 +83,10 @@ impl<S: Debug + Surface<Uniforms = Uniforms, CoordinateSpace = SegmentSpace, Pix
|
|||||||
self.headlight.set_visible(true);
|
self.headlight.set_visible(true);
|
||||||
self.brakelight.set_opacity(0);
|
self.brakelight.set_opacity(0);
|
||||||
self.brakelight.set_visible(true);
|
self.brakelight.set_visible(true);
|
||||||
join!(
|
embassy_futures::join::join(
|
||||||
fade_in.apply(&mut self.headlight),
|
fade_in.apply(&mut self.headlight),
|
||||||
fade_in.apply(&mut self.brakelight)
|
fade_in.apply(&mut self.brakelight)
|
||||||
);
|
).await;
|
||||||
|
|
||||||
info!("Fade out overlay");
|
info!("Fade out overlay");
|
||||||
TURN_OFF.apply(&mut self.overlay).await;
|
TURN_OFF.apply(&mut self.overlay).await;
|
||||||
@@ -100,17 +99,17 @@ impl<S: Debug + Surface<Uniforms = Uniforms, CoordinateSpace = SegmentSpace, Pix
|
|||||||
Personality::Active => {
|
Personality::Active => {
|
||||||
// FIXME: These should be a Off/Low/High enum, so the stopping brake looks different from the dayrunning brake.
|
// FIXME: These should be a Off/Low/High enum, so the stopping brake looks different from the dayrunning brake.
|
||||||
warn!("Active personality: Turning on safety lights");
|
warn!("Active personality: Turning on safety lights");
|
||||||
join!(
|
embassy_futures::join::join(
|
||||||
TURN_ON.apply(&mut self.brakelight),
|
TURN_ON.apply(&mut self.brakelight),
|
||||||
TURN_ON.apply(&mut self.headlight)
|
TURN_ON.apply(&mut self.headlight)
|
||||||
);
|
).await;
|
||||||
},
|
},
|
||||||
Personality::Parked => {
|
Personality::Parked => {
|
||||||
warn!("Idle personality: Turning off safety lights");
|
warn!("Idle personality: Turning off safety lights");
|
||||||
join!(
|
embassy_futures::join::join(
|
||||||
TURN_OFF.apply(&mut self.brakelight),
|
TURN_OFF.apply(&mut self.brakelight),
|
||||||
TURN_OFF.apply(&mut self.headlight)
|
TURN_OFF.apply(&mut self.headlight)
|
||||||
);
|
).await;
|
||||||
},
|
},
|
||||||
Personality::Sleeping => {
|
Personality::Sleeping => {
|
||||||
warn!("Sleeping personality: Safety UI is going to sleep");
|
warn!("Sleeping personality: Safety UI is going to sleep");
|
||||||
|
|||||||
@@ -1,56 +1,13 @@
|
|||||||
use core::cell::RefCell;
|
|
||||||
use core::fmt::Formatter;
|
|
||||||
|
|
||||||
use alloc::rc::Rc;
|
|
||||||
use embassy_sync::channel::DynamicSender;
|
use embassy_sync::channel::DynamicSender;
|
||||||
use embassy_time::{Duration, Timer};
|
use embassy_time::{Duration, Timer};
|
||||||
use embedded_storage::{ReadStorage, Storage};
|
use embedded_storage::ReadStorage;
|
||||||
use esp_bootloader_esp_idf::partitions::PartitionTable;
|
use esp_bootloader_esp_idf::partitions::PartitionTable;
|
||||||
use esp_storage::FlashStorage;
|
use esp_storage::FlashStorage;
|
||||||
use nalgebra::{Vector2, Vector3};
|
use nalgebra::{Vector2, Vector3};
|
||||||
use log::*;
|
use log::*;
|
||||||
use rmp::decode::{RmpRead, RmpReadErr, ValueReadError};
|
use rmp::decode::ValueReadError;
|
||||||
|
|
||||||
use crate::{Breaker, events::{Measurement, SensorSource, SensorState}, simdata::{AnnotationReading, EventRecord, EventStreamHeader, GPSReading, IMUReading, RmpData, SimDataError, StreamEvent, StreamHeader, StreamIndex, StreamType}};
|
use crate::{Breaker, events::{Measurement, SensorSource, SensorState}, simdata::{AnnotationReading, EventRecord, EventStreamHeader, GPSReading, IMUReading, RmpData, SimDataError, StreamEvent, StreamHeader, StreamIndex, StreamType}, storage::{RangeReadError, RangeReader, SharedFlash}};
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct SharedFlash<S> {
|
|
||||||
storage: Rc<RefCell<S>>
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<S> Clone for SharedFlash<S> {
|
|
||||||
fn clone(&self) -> Self {
|
|
||||||
Self {
|
|
||||||
storage: Rc::clone(&self.storage)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<S> SharedFlash<S> {
|
|
||||||
pub fn new(storage: S) -> Self {
|
|
||||||
Self {
|
|
||||||
storage: Rc::new(RefCell::new(storage))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<S: Storage> Storage for SharedFlash<S> {
|
|
||||||
fn write(&mut self, offset: u32, bytes: &[u8]) -> Result<(), Self::Error> {
|
|
||||||
self.storage.borrow_mut().write(offset, bytes)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<S: ReadStorage> ReadStorage for SharedFlash<S> {
|
|
||||||
type Error = S::Error;
|
|
||||||
|
|
||||||
fn read(&mut self, offset: u32, bytes: &mut [u8]) -> Result<(), Self::Error> {
|
|
||||||
self.storage.borrow_mut().read(offset, bytes)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn capacity(&self) -> usize {
|
|
||||||
self.storage.borrow().capacity()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct SimDataTable<S> {
|
pub struct SimDataTable<S> {
|
||||||
reader: RangeReader<S>,
|
reader: RangeReader<S>,
|
||||||
@@ -129,70 +86,6 @@ impl<S: ReadStorage + Clone + core::fmt::Debug> Iterator for SimDataTable<S> whe
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct RangeReader<S> {
|
|
||||||
storage: S,
|
|
||||||
start: usize,
|
|
||||||
end: usize,
|
|
||||||
offset: usize
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<S: ReadStorage> RangeReader<S> {
|
|
||||||
pub const fn new(storage: S, start: usize, end: usize) -> Self {
|
|
||||||
assert!(start <= end);
|
|
||||||
// TODO: Should add bounds checking since we will know the size of the chunk already
|
|
||||||
Self {
|
|
||||||
storage,
|
|
||||||
start,
|
|
||||||
end,
|
|
||||||
offset: 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn seek(&mut self, offset: usize) -> Result<(), RangeReadError<S::Error>> {
|
|
||||||
self.offset += offset;
|
|
||||||
if self.offset > self.end {
|
|
||||||
Err(RangeReadError::OutOfData)
|
|
||||||
} else {
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn subset(&self, size: usize) -> Result<Self, SimDataError<S::Error>> where S: Clone + core::fmt::Debug {
|
|
||||||
trace!("subset {:#02x}:{:#02x} -> {:#02x}:{:#02x}", self.start, self.end, self.start + self.offset, self.start + self.offset + size);
|
|
||||||
if self.start + self.offset + size > self.end {
|
|
||||||
Err(SimDataError::EndOfStream)
|
|
||||||
} else {
|
|
||||||
Ok(Self {
|
|
||||||
storage: self.storage.clone(),
|
|
||||||
start: self.offset + self.start,
|
|
||||||
end: self.start + self.offset + size,
|
|
||||||
offset: 0
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<S: ReadStorage> RmpRead for RangeReader<S> where S::Error: core::fmt::Debug + 'static {
|
|
||||||
type Error = RangeReadError<S::Error>;
|
|
||||||
|
|
||||||
fn read_exact_buf(&mut self, buf: &mut [u8]) -> Result<(), Self::Error> {
|
|
||||||
let pos = self.start + self.offset;
|
|
||||||
if pos > self.end {
|
|
||||||
Err(RangeReadError::OutOfData)
|
|
||||||
} else {
|
|
||||||
assert!(pos + buf.len() <= self.end);
|
|
||||||
match self.storage.read(pos as u32, buf) {
|
|
||||||
Ok(_) => {
|
|
||||||
self.offset += buf.len();
|
|
||||||
Ok(())
|
|
||||||
},
|
|
||||||
Err(err) => Err(RangeReadError::Storage(err))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct SimDataReader<S> {
|
pub struct SimDataReader<S> {
|
||||||
reader: RangeReader<S>,
|
reader: RangeReader<S>,
|
||||||
srcid: SensorSource,
|
srcid: SensorSource,
|
||||||
@@ -265,19 +158,6 @@ impl<S: ReadStorage> SimDataReader<S> where S::Error: core::fmt::Debug + 'static
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub enum RangeReadError<E> {
|
|
||||||
OutOfData,
|
|
||||||
Storage(E)
|
|
||||||
}
|
|
||||||
impl<E: core::fmt::Debug + 'static> RmpReadErr for RangeReadError<E> {}
|
|
||||||
|
|
||||||
impl<E> core::fmt::Display for RangeReadError<E> {
|
|
||||||
fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
|
|
||||||
f.write_str("RmpErr")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[embassy_executor::task(pool_size = 3)]
|
#[embassy_executor::task(pool_size = 3)]
|
||||||
pub async fn simulation_task(mut reader: SimDataReader<SharedFlash<FlashStorage>>, events: DynamicSender<'static, Measurement>) {
|
pub async fn simulation_task(mut reader: SimDataReader<SharedFlash<FlashStorage>>, events: DynamicSender<'static, Measurement>) {
|
||||||
warn!("Starting simulation for {:?}", reader.srcid());
|
warn!("Starting simulation for {:?}", reader.srcid());
|
||||||
|
|||||||
@@ -99,12 +99,12 @@ impl<S: Debug + Surface<Uniforms = Uniforms, CoordinateSpace = SegmentSpace, Pix
|
|||||||
let panels = Animation::default().duration(Duration::from_millis(300)).to(128);
|
let panels = Animation::default().duration(Duration::from_millis(300)).to(128);
|
||||||
let bg = Animation::default().duration(Duration::from_millis(300)).to(32);
|
let bg = Animation::default().duration(Duration::from_millis(300)).to(32);
|
||||||
let motion = Animation::default().duration(Duration::from_secs(1)).to(0);
|
let motion = Animation::default().duration(Duration::from_secs(1)).to(0);
|
||||||
join!(
|
embassy_futures::join::join4(
|
||||||
tail.apply(&mut self.tail),
|
tail.apply(&mut self.tail),
|
||||||
panels.apply(&mut self.panels),
|
panels.apply(&mut self.panels),
|
||||||
bg.apply(&mut self.background),
|
bg.apply(&mut self.background),
|
||||||
motion.apply(&mut self.motion)
|
motion.apply(&mut self.motion)
|
||||||
);
|
).await;
|
||||||
self.background.set_shader(Background::default());
|
self.background.set_shader(Background::default());
|
||||||
},
|
},
|
||||||
Scene::Idle => {
|
Scene::Idle => {
|
||||||
@@ -114,12 +114,12 @@ impl<S: Debug + Surface<Uniforms = Uniforms, CoordinateSpace = SegmentSpace, Pix
|
|||||||
let bg_fade = Animation::default().duration(Duration::from_millis(300)).to(128);
|
let bg_fade = Animation::default().duration(Duration::from_millis(300)).to(128);
|
||||||
|
|
||||||
// FIXME: The scenes shouldn't be touching the brake/headlights at all here. In fact, they should be dealt with in a whole separate task from the main UI, maybe running on the motion prediction executor
|
// FIXME: The scenes shouldn't be touching the brake/headlights at all here. In fact, they should be dealt with in a whole separate task from the main UI, maybe running on the motion prediction executor
|
||||||
join!(
|
embassy_futures::join::join4(
|
||||||
fg_fade.apply(&mut self.tail),
|
fg_fade.apply(&mut self.tail),
|
||||||
fg_fade.apply(&mut self.panels),
|
fg_fade.apply(&mut self.panels),
|
||||||
bg_fade.apply(&mut self.background),
|
bg_fade.apply(&mut self.background),
|
||||||
fg_fade.apply(&mut self.motion)
|
fg_fade.apply(&mut self.motion)
|
||||||
);
|
).await;
|
||||||
},
|
},
|
||||||
Scene::Accelerating => {
|
Scene::Accelerating => {
|
||||||
self.motion.set_shader(Movement::default());
|
self.motion.set_shader(Movement::default());
|
||||||
|
|||||||
@@ -1,71 +1,83 @@
|
|||||||
use alloc::string::ToString;
|
use alloc::string::ToString;
|
||||||
use embassy_executor::Spawner;
|
use embassy_sync::channel::DynamicSender;
|
||||||
use embassy_sync::pubsub::DynSubscriber;
|
use embassy_sync::pubsub::DynSubscriber;
|
||||||
use esp_radio::Controller;
|
use embassy_time::{Duration, Instant, WithTimeout};
|
||||||
use esp_radio::wifi::{ClientConfig, WifiDevice};
|
use esp_hal::rng::Rng;
|
||||||
|
use esp_radio::wifi::{ClientConfig, ScanConfig, WifiController, WifiDevice, WifiEvent};
|
||||||
use log::*;
|
use log::*;
|
||||||
use alloc::format;
|
use alloc::format;
|
||||||
|
|
||||||
use embassy_net::dns::DnsSocket;
|
use embassy_net::dns::DnsSocket;
|
||||||
use embassy_net::tcp::client::{TcpClient, TcpClientState};
|
use embassy_net::tcp::client::{TcpClient, TcpClientState};
|
||||||
use embassy_net::{Config, StackResources};
|
use embassy_net::Stack;
|
||||||
use nalgebra::Vector2;
|
use nalgebra::Vector2;
|
||||||
use reqwless::client::{HttpClient, TlsConfig};
|
use reqwless::client::{HttpClient, TlsConfig};
|
||||||
use static_cell::StaticCell;
|
|
||||||
|
|
||||||
|
use crate::ego::engine::gps_to_local_meters_haversine;
|
||||||
|
use crate::events::{Measurement, SensorSource, SensorState};
|
||||||
use crate::{backoff::Backoff, events::{Prediction}};
|
use crate::{backoff::Backoff, events::{Prediction}};
|
||||||
|
|
||||||
#[embassy_executor::task]
|
#[embassy_executor::task]
|
||||||
async fn net_task(mut runner: embassy_net::Runner<'static, WifiDevice<'static>>) {
|
pub async fn net_task(mut runner: embassy_net::Runner<'static, WifiDevice<'static>>) {
|
||||||
info!("Network stack is running");
|
info!("Starting network stack");
|
||||||
runner.run().await
|
runner.run().await
|
||||||
}
|
}
|
||||||
|
|
||||||
static RESOURCES: StaticCell<StackResources<5>> = StaticCell::new();
|
|
||||||
|
|
||||||
// TODO: Wifi task needs to know when there is data to upload, so it only connects when needed.
|
|
||||||
#[embassy_executor::task]
|
#[embassy_executor::task]
|
||||||
pub async fn wireless_task(mut predictions: DynSubscriber<'static, Prediction>, wifi_init: &'static mut Controller<'static>, wifi_device: esp_hal::peripherals::WIFI<'static>) {
|
pub async fn wifi_connect_task(mut wifi: WifiController<'static>, stack: Stack<'static>, motion: DynamicSender<'static, Measurement>) {
|
||||||
let (mut wifi, interfaces) = esp_radio::wifi::new(wifi_init, wifi_device, esp_radio::wifi::Config::default())
|
|
||||||
.expect("Failed to initialize WIFI!");
|
|
||||||
wifi.set_config(&esp_radio::wifi::ModeConfig::Client(
|
wifi.set_config(&esp_radio::wifi::ModeConfig::Client(
|
||||||
ClientConfig::default()
|
ClientConfig::default()
|
||||||
.with_ssid("The Frequencey".to_string())
|
.with_ssid("The Frequency".to_string())
|
||||||
.with_auth_method(esp_radio::wifi::AuthMethod::Wpa2Personal)
|
.with_auth_method(esp_radio::wifi::AuthMethod::Wpa2Personal)
|
||||||
.with_password("thepasswordkenneth".to_string())
|
.with_password("thepasswordkenneth".to_string())
|
||||||
)).unwrap();
|
)).unwrap();
|
||||||
wifi.set_mode(esp_radio::wifi::WifiMode::Sta).unwrap();
|
wifi.set_mode(esp_radio::wifi::WifiMode::Sta).unwrap();
|
||||||
wifi.set_power_saving(esp_radio::wifi::PowerSaveMode::Maximum).unwrap();
|
wifi.set_power_saving(esp_radio::wifi::PowerSaveMode::Maximum).unwrap();
|
||||||
wifi.start_async().await.unwrap();
|
|
||||||
|
|
||||||
let device = interfaces.sta;
|
|
||||||
// TODO: Somehow grab a real random seed from main()
|
|
||||||
let seed = 0;
|
|
||||||
|
|
||||||
let config = Config::dhcpv4(Default::default());
|
|
||||||
let (stack, runner) = embassy_net::new(device, config, RESOURCES.init_with(|| { StackResources::new() }), seed as u64);
|
|
||||||
info!("Launching network task");
|
|
||||||
unsafe { Spawner::for_current_executor().await }.must_spawn(net_task(runner));
|
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
Backoff::from_secs(3).forever().attempt(async || {
|
Backoff::from_secs(3).forever().attempt(async || {
|
||||||
info!("Connecting to wifi...");
|
info!("Connecting to wifi...");
|
||||||
|
wifi.start_async().await.unwrap();
|
||||||
|
motion.send(Measurement::SensorHardwareStatus(SensorSource::Wifi, crate::events::SensorState::AcquiringFix)).await;
|
||||||
|
let networks = wifi.scan_with_config_async(ScanConfig::default().with_show_hidden(true)).await.unwrap();
|
||||||
|
for network in networks {
|
||||||
|
info!("wifi: {} @ {}db", network.ssid, network.signal_strength);
|
||||||
|
}
|
||||||
match wifi.connect_async().await {
|
match wifi.connect_async().await {
|
||||||
Ok(_) => Ok(()),
|
Ok(_) => Ok(()),
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
error!("Wifi error: {e:?}");
|
error!("Unable to connect to wifi: {e:?}");
|
||||||
|
wifi.stop_async().await.unwrap();
|
||||||
Err(())
|
Err(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}).await.unwrap();
|
}).await.unwrap();
|
||||||
|
|
||||||
info!("Waiting for DHCP");
|
info!("Waiting for DHCP...");
|
||||||
stack.wait_config_up().await;
|
motion.send(Measurement::SensorHardwareStatus(SensorSource::Wifi, SensorState::Degraded)).await;
|
||||||
|
if stack.wait_config_up().with_timeout(Duration::from_secs(5)).await.is_ok() {
|
||||||
info!("Online!");
|
info!("Online!");
|
||||||
|
motion.send(Measurement::SensorHardwareStatus(SensorSource::Wifi, SensorState::Online)).await;
|
||||||
let ip_cfg = stack.config_v4().unwrap();
|
let ip_cfg = stack.config_v4().unwrap();
|
||||||
info!("ip={ip_cfg:?}");
|
info!("ip={ip_cfg:?}");
|
||||||
|
wifi.wait_for_event(WifiEvent::ApStaDisconnected).await;
|
||||||
|
info!("Wifi disconnected!");
|
||||||
|
} else {
|
||||||
|
warn!("DHCP timed out after 5 seconds. Disconnecting wifi");
|
||||||
|
wifi.disconnect_async().await.unwrap();
|
||||||
|
}
|
||||||
|
warn!("Stopping wifi device");
|
||||||
|
wifi.stop_async().await.unwrap();
|
||||||
|
motion.send(Measurement::SensorHardwareStatus(SensorSource::Wifi, SensorState::Offline)).await;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Wifi task needs to know when there is data to upload, so it only connects when needed.
|
||||||
|
#[embassy_executor::task]
|
||||||
|
pub async fn http_telemetry_task(mut predictions: DynSubscriber<'static, Prediction>, stack: Stack<'static>) {
|
||||||
|
// TODO: should wait for wifi disconnect event somehow and use that to restart sending the wifi around
|
||||||
|
|
||||||
|
let seed = Rng::new().random() as i32;
|
||||||
let mut rx_buf = [0; 4096];
|
let mut rx_buf = [0; 4096];
|
||||||
let mut tx_buf = [0; 4096];
|
let mut tx_buf = [0; 4096];
|
||||||
let dns = DnsSocket::new(stack);
|
let dns = DnsSocket::new(stack);
|
||||||
@@ -80,21 +92,33 @@ pub async fn wireless_task(mut predictions: DynSubscriber<'static, Prediction>,
|
|||||||
|
|
||||||
let mut client = HttpClient::new_with_tls(&tcp, &dns, tls);
|
let mut client = HttpClient::new_with_tls(&tcp, &dns, tls);
|
||||||
|
|
||||||
|
// TODO: Only should upload a data point after some distance has occurred
|
||||||
|
let mut last_location = Vector2::default();
|
||||||
|
let mut last_push = Instant::from_ticks(0);
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
if let Prediction::Location(coords) = predictions.next_message_pure().await {
|
if let Prediction::Location(coords) = predictions.next_message_pure().await {
|
||||||
if let Err(e) = push_location(&mut client, coords).await {
|
|
||||||
error!("HTTP error in publishing location: {e:?}");
|
if stack.is_config_up() {
|
||||||
break
|
// Only push to HTTP if we have an ip config etc
|
||||||
|
if last_push.elapsed().as_secs() >= 5 || gps_to_local_meters_haversine(&last_location, &coords).norm() >= 10.0 {
|
||||||
|
last_location = coords;
|
||||||
|
last_push = Instant::now();
|
||||||
|
if let Err(e) = Backoff::from_secs(3).attempt(async || {
|
||||||
|
push_location(&mut client, coords, Instant::now().as_millis()).await
|
||||||
|
}).await {
|
||||||
|
warn!("Could not submit location! {e:?}");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn push_location(client: &mut HttpClient<'_, TcpClient<'_, 1, 4096, 4096>, DnsSocket<'_>>, location: Vector2<f64>) -> Result<(), reqwless::Error> {
|
async fn push_location(client: &mut HttpClient<'_, TcpClient<'_, 1, 4096, 4096>, DnsSocket<'_>>, location: Vector2<f64>, timestamp: u64) -> Result<(), reqwless::Error> {
|
||||||
let mut buffer = [0u8; 4096];
|
let mut buffer = [0u8; 4096];
|
||||||
let base = "https://nextcloud.malloc.hackerbots.net/nextcloud/index.php/apps/phonetrack/logGet/a062000067304e9dee6590f1b8f9e0db/renderbug";
|
let base = "https://nextcloud.malloc.hackerbots.net/nextcloud/index.php/apps/phonetrack/logGet/a062000067304e9dee6590f1b8f9e0db/renderbug";
|
||||||
let url = format!("{base}?lat={}&lon={}", location.y, location.x);
|
let url = format!("{base}?lon={}&lat={}×tamp={}", location.y, location.x, timestamp);
|
||||||
info!("Pushing to {url}");
|
info!("Pushing to {url}");
|
||||||
let mut http_req = client
|
let mut http_req = client
|
||||||
.request(
|
.request(
|
||||||
@@ -104,10 +128,9 @@ async fn push_location(client: &mut HttpClient<'_, TcpClient<'_, 1, 4096, 4096>,
|
|||||||
.await?;
|
.await?;
|
||||||
let response = http_req.send(&mut buffer).await?;
|
let response = http_req.send(&mut buffer).await?;
|
||||||
|
|
||||||
info!("Got response");
|
|
||||||
let res = response.body().read_to_end().await?;
|
let res = response.body().read_to_end().await?;
|
||||||
|
|
||||||
let content = core::str::from_utf8(res).unwrap();
|
let content = core::str::from_utf8(res).unwrap();
|
||||||
info!("{content}");
|
debug!("HTTP response: {content}");
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user