A RPi Pico powered Lightning Detector
at main 6.7 kB view raw
1mod analysis; 2mod data; 3 4use core::cell::Cell; 5 6use alloc::vec::Vec; 7use sachy_fmt::{info, unwrap}; 8 9use embassy_futures::select::select3; 10use embassy_rp::peripherals::DMA_CH1; 11use embassy_sync::{ 12 blocking_mutex::raw::NoopRawMutex, 13 zerocopy_channel::{Channel as ZChannel, Receiver, Sender}, 14}; 15use embassy_time::{Duration, Instant, Ticker, Timer}; 16 17use crate::{ 18 adc::AdcDriver, 19 constants::{BLIP_SIZE, BLOCK_SIZE}, 20 pwm::PwmDriver, 21 rtc::GlobalRtc, 22 updates::UpdateConnection, 23 utils::{static_alloc, try_buffer, try_static_timestamped_block_vecs}, 24}; 25 26#[embassy_executor::task] 27pub async fn detector_task( 28 adc: AdcDriver<'static, NoopRawMutex, DMA_CH1>, 29 pwm: PwmDriver<'static>, 30 rtc: GlobalRtc<'static>, 31) { 32 info!("Waiting for RTC to start running"); 33 while !rtc.is_ready().await { 34 Timer::after_secs(2).await; 35 } 36 37 info!("Allocating detector resources"); 38 39 let blocks = unwrap!( 40 try_static_timestamped_block_vecs(4, BLOCK_SIZE), 41 "Couldn't allocate block buffers" 42 ); 43 44 let buf_channel: &mut ZChannel<NoopRawMutex, (i64, Vec<u16>)> = 45 static_alloc(ZChannel::new(blocks)); 46 47 let (mut sender, mut receiver) = buf_channel.split(); 48 49 let mut detector = Detector::new(adc, pwm); 50 let mut samples = unwrap!(try_buffer(64), "Failed to allocate sample buffer"); 51 let mut peaks = Vec::new(); 52 53 unwrap!( 54 peaks.try_reserve_exact(BLOCK_SIZE), 55 "Failed to allocate peaks buffer" 56 ); 57 58 loop { 59 let average = detector.tune(samples.as_mut_slice()).await; 60 61 select3( 62 detector.sample(&mut sender, rtc), 63 detector.analyse(&mut receiver, average, &mut peaks), 64 detector.tick(rtc), 65 ) 66 .await; 67 } 68} 69 70struct Detector<'device> { 71 adc: AdcDriver<'device, NoopRawMutex, DMA_CH1>, 72 pwm: PwmDriver<'device>, 73 state: DetectorState, 74} 75 76#[derive(Debug)] 77struct DetectorState { 78 strikes: Cell<u16>, 79 warn_level: Cell<u16>, 80} 81 82impl Default for DetectorState { 83 fn default() -> Self { 84 Self { 85 strikes: Cell::new(0), 86 warn_level: Cell::new(255), 87 } 88 } 89} 90 91impl<'device> Detector<'device> { 92 fn new(adc: AdcDriver<'device, NoopRawMutex, DMA_CH1>, mut pwm: PwmDriver<'device>) -> Self { 93 pwm.enable(); 94 95 Self { 96 adc, 97 pwm, 98 state: DetectorState::default(), 99 } 100 } 101} 102 103impl Detector<'_> { 104 async fn tune(&mut self, samples: &mut [u16]) -> u16 { 105 info!("Tuning Detector for correct voltage settings"); 106 let mut duty = 0; 107 self.pwm.set_duty(duty); 108 Timer::after_secs(2).await; 109 let mut act_value = self.adc.sample_average(samples).await; 110 111 info!("initial ACT: {}", act_value); 112 113 while act_value < 1364 { 114 duty += 2; 115 if duty >= 256 { 116 duty = 0; 117 self.pwm.set_duty(duty); 118 Timer::after_secs(2).await; 119 act_value = self.adc.sample_average(samples).await; 120 info!("Restarting tuning"); 121 continue; 122 } 123 self.pwm.set_duty(duty); 124 Timer::after_millis(250).await; 125 act_value = self.adc.sample_average(samples).await; 126 info!("ACT: {}, Duty: {}", act_value, duty); 127 } 128 129 duty = (duty / 3) * 2; 130 self.pwm.set_duty(duty); 131 info!("Set detection duty to: {}", duty); 132 // Allow voltage level to stabilize after tuning 133 Timer::after_secs(2).await; 134 info!("Ready for strike detection"); 135 136 self.adc.sample_average(samples).await 137 } 138 139 async fn sample( 140 &self, 141 data: &mut Sender<'static, NoopRawMutex, (i64, Vec<u16>)>, 142 rtc: GlobalRtc<'static>, 143 ) { 144 let track_time = rtc.track_time().await; 145 146 loop { 147 let (timestamp, buf) = data.send().await; 148 *timestamp = track_time.timestamp(); 149 self.adc.sample(buf).await; 150 data.send_done(); 151 } 152 } 153 154 async fn analyse( 155 &self, 156 data: &mut Receiver<'static, NoopRawMutex, (i64, Vec<u16>)>, 157 mut average: u16, 158 peaks: &mut Vec<u16>, 159 ) { 160 loop { 161 peaks.clear(); 162 163 let (timestamp, buf) = data.receive().await; 164 165 let new_avg = 166 analysis::analyse_buffer_by_stepped_windows(buf.as_slice(), average, peaks); 167 168 let blips = peaks.len(); 169 170 if blips >= BLIP_SIZE { 171 self.state 172 .strikes 173 .update(|strike| strike.saturating_add(32)); 174 175 if let Some(net_data) = UpdateConnection::can_update() { 176 data::transmit_strike( 177 *timestamp, 178 buf.as_slice(), 179 peaks.as_slice(), 180 average, 181 &net_data, 182 ); 183 } 184 185 info!( 186 "Strikes detected at {}s! avg {}, signal strength: {}, blip {}, level {}", 187 Instant::now().as_secs(), 188 average, 189 blips, 190 peaks.iter().max(), 191 self.state.warn_level.get() 192 ); 193 } 194 195 data.receive_done(); 196 197 average = new_avg; 198 } 199 } 200 201 async fn tick(&self, rtc: GlobalRtc<'static>) { 202 let mut inactive: Option<Instant> = None; 203 let mut interval = Ticker::every(Duration::from_secs(1)); 204 let track_time = rtc.track_time().await; 205 206 loop { 207 interval.next().await; 208 let strikes = self.state.strikes.get(); 209 let mut warn_level = self.state.warn_level.get(); 210 211 if strikes > 32 { 212 warn_level = warn_level.saturating_add(strikes); 213 } 214 215 let decay = warn_level >> 8; 216 217 self.state.strikes.set(0); 218 self.state.warn_level.set(warn_level - decay); 219 220 match inactive { 221 Some(_) if decay > 0 => { 222 inactive = None; 223 } 224 Some(val) if val.elapsed() >= Duration::from_secs(3600) => break, 225 None if decay == 0 => { 226 inactive = Some(Instant::now()); 227 } 228 None => { 229 if let Some(net_data) = UpdateConnection::can_update() { 230 let now = track_time.timestamp(); 231 data::transmit_level_update(now, decay, &net_data); 232 } 233 } 234 _ => continue, 235 } 236 } 237 } 238}