+890
-1
Diff
round #4
+21
.tangled/workflows/test.yml
+21
.tangled/workflows/test.yml
···
1
+
when:
2
+
- event: ["push", "pull_request"]
3
+
branch: main
4
+
5
+
engine: nixery
6
+
7
+
dependencies:
8
+
nixpkgs:
9
+
- clang
10
+
- cargo
11
+
- rustfmt
12
+
- protobuf
13
+
- cargo-nextest
14
+
15
+
steps:
16
+
- name: Format check
17
+
command: cargo fmt --all --check
18
+
- name: Tests
19
+
command: cargo nextest run --workspace --locked --no-fail-fast
20
+
- name: Doc Tests
21
+
command: cargo test --workspace --locked --doc --no-fail-fast
+255
Cargo.lock
+255
Cargo.lock
···
1
+
# This file is automatically @generated by Cargo.
2
+
# It is not intended for manual editing.
3
+
version = 4
4
+
5
+
[[package]]
6
+
name = "byteorder"
7
+
version = "1.5.0"
8
+
source = "registry+https://github.com/rust-lang/crates.io-index"
9
+
checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
10
+
11
+
[[package]]
12
+
name = "cfg-if"
13
+
version = "1.0.4"
14
+
source = "registry+https://github.com/rust-lang/crates.io-index"
15
+
checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
16
+
17
+
[[package]]
18
+
name = "critical-section"
19
+
version = "1.2.0"
20
+
source = "registry+https://github.com/rust-lang/crates.io-index"
21
+
checksum = "790eea4361631c5e7d22598ecd5723ff611904e3344ce8720784c93e3d83d40b"
22
+
23
+
[[package]]
24
+
name = "document-features"
25
+
version = "0.2.12"
26
+
source = "registry+https://github.com/rust-lang/crates.io-index"
27
+
checksum = "d4b8a88685455ed29a21542a33abd9cb6510b6b129abadabdcef0f4c55bc8f61"
28
+
dependencies = [
29
+
"litrs",
30
+
]
31
+
32
+
[[package]]
33
+
name = "embassy-executor-timer-queue"
34
+
version = "0.1.0"
35
+
source = "registry+https://github.com/rust-lang/crates.io-index"
36
+
checksum = "2fc328bf943af66b80b98755db9106bf7e7471b0cf47dc8559cd9a6be504cc9c"
37
+
38
+
[[package]]
39
+
name = "embassy-strike-driver"
40
+
version = "0.1.0"
41
+
dependencies = [
42
+
"critical-section",
43
+
"embassy-sync",
44
+
"embassy-time",
45
+
"pollster",
46
+
]
47
+
48
+
[[package]]
49
+
name = "embassy-sync"
50
+
version = "0.7.2"
51
+
source = "registry+https://github.com/rust-lang/crates.io-index"
52
+
checksum = "73974a3edbd0bd286759b3d483540f0ebef705919a5f56f4fc7709066f71689b"
53
+
dependencies = [
54
+
"cfg-if",
55
+
"critical-section",
56
+
"embedded-io-async",
57
+
"futures-core",
58
+
"futures-sink",
59
+
"heapless",
60
+
]
61
+
62
+
[[package]]
63
+
name = "embassy-time"
64
+
version = "0.5.0"
65
+
source = "registry+https://github.com/rust-lang/crates.io-index"
66
+
checksum = "f4fa65b9284d974dad7a23bb72835c4ec85c0b540d86af7fc4098c88cff51d65"
67
+
dependencies = [
68
+
"cfg-if",
69
+
"critical-section",
70
+
"document-features",
71
+
"embassy-time-driver",
72
+
"embassy-time-queue-utils",
73
+
"embedded-hal 0.2.7",
74
+
"embedded-hal 1.0.0",
75
+
"embedded-hal-async",
76
+
"futures-core",
77
+
]
78
+
79
+
[[package]]
80
+
name = "embassy-time-driver"
81
+
version = "0.2.1"
82
+
source = "registry+https://github.com/rust-lang/crates.io-index"
83
+
checksum = "a0a244c7dc22c8d0289379c8d8830cae06bb93d8f990194d0de5efb3b5ae7ba6"
84
+
dependencies = [
85
+
"document-features",
86
+
]
87
+
88
+
[[package]]
89
+
name = "embassy-time-queue-utils"
90
+
version = "0.3.0"
91
+
source = "registry+https://github.com/rust-lang/crates.io-index"
92
+
checksum = "80e2ee86063bd028a420a5fb5898c18c87a8898026da1d4c852af2c443d0a454"
93
+
dependencies = [
94
+
"embassy-executor-timer-queue",
95
+
"heapless",
96
+
]
97
+
98
+
[[package]]
99
+
name = "embedded-hal"
100
+
version = "0.2.7"
101
+
source = "registry+https://github.com/rust-lang/crates.io-index"
102
+
checksum = "35949884794ad573cf46071e41c9b60efb0cb311e3ca01f7af807af1debc66ff"
103
+
dependencies = [
104
+
"nb 0.1.3",
105
+
"void",
106
+
]
107
+
108
+
[[package]]
109
+
name = "embedded-hal"
110
+
version = "1.0.0"
111
+
source = "registry+https://github.com/rust-lang/crates.io-index"
112
+
checksum = "361a90feb7004eca4019fb28352a9465666b24f840f5c3cddf0ff13920590b89"
113
+
114
+
[[package]]
115
+
name = "embedded-hal-async"
116
+
version = "1.0.0"
117
+
source = "registry+https://github.com/rust-lang/crates.io-index"
118
+
checksum = "0c4c685bbef7fe13c3c6dd4da26841ed3980ef33e841cddfa15ce8a8fb3f1884"
119
+
dependencies = [
120
+
"embedded-hal 1.0.0",
121
+
]
122
+
123
+
[[package]]
124
+
name = "embedded-io"
125
+
version = "0.6.1"
126
+
source = "registry+https://github.com/rust-lang/crates.io-index"
127
+
checksum = "edd0f118536f44f5ccd48bcb8b111bdc3de888b58c74639dfb034a357d0f206d"
128
+
129
+
[[package]]
130
+
name = "embedded-io-async"
131
+
version = "0.6.1"
132
+
source = "registry+https://github.com/rust-lang/crates.io-index"
133
+
checksum = "3ff09972d4073aa8c299395be75161d582e7629cd663171d62af73c8d50dba3f"
134
+
dependencies = [
135
+
"embedded-io",
136
+
]
137
+
138
+
[[package]]
139
+
name = "futures-core"
140
+
version = "0.3.31"
141
+
source = "registry+https://github.com/rust-lang/crates.io-index"
142
+
checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e"
143
+
144
+
[[package]]
145
+
name = "futures-sink"
146
+
version = "0.3.31"
147
+
source = "registry+https://github.com/rust-lang/crates.io-index"
148
+
checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7"
149
+
150
+
[[package]]
151
+
name = "hash32"
152
+
version = "0.3.1"
153
+
source = "registry+https://github.com/rust-lang/crates.io-index"
154
+
checksum = "47d60b12902ba28e2730cd37e95b8c9223af2808df9e902d4df49588d1470606"
155
+
dependencies = [
156
+
"byteorder",
157
+
]
158
+
159
+
[[package]]
160
+
name = "heapless"
161
+
version = "0.8.0"
162
+
source = "registry+https://github.com/rust-lang/crates.io-index"
163
+
checksum = "0bfb9eb618601c89945a70e254898da93b13be0388091d42117462b265bb3fad"
164
+
dependencies = [
165
+
"hash32",
166
+
"stable_deref_trait",
167
+
]
168
+
169
+
[[package]]
170
+
name = "litrs"
171
+
version = "1.0.0"
172
+
source = "registry+https://github.com/rust-lang/crates.io-index"
173
+
checksum = "11d3d7f243d5c5a8b9bb5d6dd2b1602c0cb0b9db1621bafc7ed66e35ff9fe092"
174
+
175
+
[[package]]
176
+
name = "nb"
177
+
version = "0.1.3"
178
+
source = "registry+https://github.com/rust-lang/crates.io-index"
179
+
checksum = "801d31da0513b6ec5214e9bf433a77966320625a37860f910be265be6e18d06f"
180
+
dependencies = [
181
+
"nb 1.1.0",
182
+
]
183
+
184
+
[[package]]
185
+
name = "nb"
186
+
version = "1.1.0"
187
+
source = "registry+https://github.com/rust-lang/crates.io-index"
188
+
checksum = "8d5439c4ad607c3c23abf66de8c8bf57ba8adcd1f129e699851a6e43935d339d"
189
+
190
+
[[package]]
191
+
name = "pollster"
192
+
version = "0.4.0"
193
+
source = "registry+https://github.com/rust-lang/crates.io-index"
194
+
checksum = "2f3a9f18d041e6d0e102a0a46750538147e5e8992d3b4873aaafee2520b00ce3"
195
+
dependencies = [
196
+
"pollster-macro",
197
+
]
198
+
199
+
[[package]]
200
+
name = "pollster-macro"
201
+
version = "0.4.0"
202
+
source = "registry+https://github.com/rust-lang/crates.io-index"
203
+
checksum = "ac5da421106a50887c5b51d20806867db377fbb86bacf478ee0500a912e0c113"
204
+
dependencies = [
205
+
"proc-macro2",
206
+
"quote",
207
+
"syn",
208
+
]
209
+
210
+
[[package]]
211
+
name = "proc-macro2"
212
+
version = "1.0.106"
213
+
source = "registry+https://github.com/rust-lang/crates.io-index"
214
+
checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934"
215
+
dependencies = [
216
+
"unicode-ident",
217
+
]
218
+
219
+
[[package]]
220
+
name = "quote"
221
+
version = "1.0.44"
222
+
source = "registry+https://github.com/rust-lang/crates.io-index"
223
+
checksum = "21b2ebcf727b7760c461f091f9f0f539b77b8e87f2fd88131e7f1b433b3cece4"
224
+
dependencies = [
225
+
"proc-macro2",
226
+
]
227
+
228
+
[[package]]
229
+
name = "stable_deref_trait"
230
+
version = "1.2.1"
231
+
source = "registry+https://github.com/rust-lang/crates.io-index"
232
+
checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596"
233
+
234
+
[[package]]
235
+
name = "syn"
236
+
version = "2.0.114"
237
+
source = "registry+https://github.com/rust-lang/crates.io-index"
238
+
checksum = "d4d107df263a3013ef9b1879b0df87d706ff80f65a86ea879bd9c31f9b307c2a"
239
+
dependencies = [
240
+
"proc-macro2",
241
+
"quote",
242
+
"unicode-ident",
243
+
]
244
+
245
+
[[package]]
246
+
name = "unicode-ident"
247
+
version = "1.0.22"
248
+
source = "registry+https://github.com/rust-lang/crates.io-index"
249
+
checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5"
250
+
251
+
[[package]]
252
+
name = "void"
253
+
version = "1.0.2"
254
+
source = "registry+https://github.com/rust-lang/crates.io-index"
255
+
checksum = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d"
+16
Cargo.toml
+16
Cargo.toml
···
1
+
[workspace]
2
+
resolver = "3"
3
+
members = ["embassy-strike-driver"]
4
+
5
+
[workspace.package]
6
+
authors = ["Sachy.dev <sachymetsu@tutamail.com>"]
7
+
edition = "2024"
8
+
repository = "https://tangled.org/sachy.dev/strike-sensor"
9
+
version = "0.1.0"
10
+
license = "AGPL-3.0-only"
11
+
rust-version = "1.91.0"
12
+
13
+
[workspace.dependencies]
14
+
embassy-time = "0.5"
15
+
embassy-sync = "0.7"
16
+
defmt = "1"
+5
-1
README.md
+5
-1
README.md
···
4
4
5
5
The sensor designs can be found in the [kicad](./kicad) folder.
6
6
7
+
## Driver
8
+
9
+
Currently, the driver needs to be tested and coded according to how the new PCB/circuit behaves, so this will require a bit of work before considered "ready".
10
+
7
11
## Licenses
8
12
9
-
The sensor hardware designs are licensed under the [CERN Open Hardware Licence Version 2 - Strongly Reciprocal](./kicad/LICENSE-CERN-OHL-S) license.
13
+
The sensor hardware designs are licensed under the [CERN Open Hardware Licence Version 2 - Strongly Reciprocal](./kicad/LICENSE-CERN-OHL-S) license.
+17
embassy-strike-driver/Cargo.toml
+17
embassy-strike-driver/Cargo.toml
···
1
+
[package]
2
+
name = "embassy-strike-driver"
3
+
authors.workspace = true
4
+
edition.workspace = true
5
+
repository.workspace = true
6
+
version.workspace = true
7
+
license.workspace = true
8
+
rust-version.workspace = true
9
+
10
+
[dependencies]
11
+
embassy-time.workspace = true
12
+
embassy-sync.workspace = true
13
+
14
+
[dev-dependencies]
15
+
embassy-time = { workspace = true, features = ["mock-driver", "generic-queue-8"] }
16
+
critical-section = { version = "1.1", features = ["std"] }
17
+
pollster = { version = "0.4", features = ["macro"] }
+576
embassy-strike-driver/src/lib.rs
+576
embassy-strike-driver/src/lib.rs
···
1
+
//! A generalised Embassy driver for the Strike Sensor v1
2
+
//!
3
+
#![no_std]
4
+
5
+
use core::cell::Cell;
6
+
7
+
use embassy_sync::{
8
+
blocking_mutex::raw::NoopRawMutex,
9
+
zerocopy_channel::{Channel, Receiver, Sender},
10
+
};
11
+
use embassy_time::{Duration, Instant, Ticker, Timer};
12
+
13
+
pub const BLOCK_SIZE: usize = 512;
14
+
pub type ZeroCopyChannel<'device> = Channel<'device, NoopRawMutex, (i64, [u16; BLOCK_SIZE])>;
15
+
16
+
pub trait TimeSource {
17
+
fn timestamp(&self) -> i64;
18
+
fn get_source() -> Self;
19
+
}
20
+
21
+
pub trait AdcSource {
22
+
fn sample_average(&self, samples: &mut [u16]) -> impl Future<Output = u16>;
23
+
fn sample(&self, samples: &mut [u16]) -> impl Future<Output = ()>;
24
+
}
25
+
26
+
pub trait PwmSource {
27
+
fn set_duty(&mut self, duty: u16);
28
+
}
29
+
30
+
/// A Mutable Buffer trait
31
+
pub trait BufferMut<T> {
32
+
fn push(&mut self, value: T);
33
+
fn clear(&mut self);
34
+
fn as_slice(&self) -> &[T];
35
+
fn len(&self) -> usize;
36
+
fn is_empty(&self) -> bool;
37
+
}
38
+
39
+
#[derive(Debug)]
40
+
struct DetectorState {
41
+
max_duty: Cell<u8>,
42
+
duty: Cell<u8>,
43
+
avg: Cell<u16>,
44
+
strikes: Cell<u16>,
45
+
warn_level: Cell<u16>,
46
+
}
47
+
48
+
impl Default for DetectorState {
49
+
fn default() -> Self {
50
+
Self {
51
+
max_duty: Cell::new(0),
52
+
duty: Cell::new(0),
53
+
avg: Cell::new(0),
54
+
strikes: Cell::new(0),
55
+
warn_level: Cell::new(255),
56
+
}
57
+
}
58
+
}
59
+
60
+
#[derive(Debug)]
61
+
pub struct DetectorConfig {
62
+
blip_threshold: Cell<u16>,
63
+
blip_size: Cell<usize>,
64
+
}
65
+
66
+
impl Default for DetectorConfig {
67
+
fn default() -> Self {
68
+
Self {
69
+
blip_threshold: Cell::new(14),
70
+
blip_size: Cell::new(2),
71
+
}
72
+
}
73
+
}
74
+
75
+
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
76
+
pub enum DetectorUpdate<'a> {
77
+
Tick {
78
+
timestamp: i64,
79
+
level: u16,
80
+
},
81
+
Detection {
82
+
timestamp: i64,
83
+
samples: &'a [u16],
84
+
peaks: &'a [usize],
85
+
},
86
+
}
87
+
88
+
pub struct DetectorDriver<T: TimeSource, P: PwmSource, A: AdcSource> {
89
+
state: DetectorState,
90
+
config: DetectorConfig,
91
+
timer_source: T,
92
+
pwm: P,
93
+
adc: A,
94
+
}
95
+
96
+
impl<T, P, A> DetectorDriver<T, P, A>
97
+
where
98
+
T: TimeSource,
99
+
P: PwmSource,
100
+
A: AdcSource,
101
+
{
102
+
pub fn new(config: DetectorConfig, timer_source: T, pwm: P, adc: A) -> Self {
103
+
Self {
104
+
state: Default::default(),
105
+
config,
106
+
timer_source,
107
+
pwm,
108
+
adc,
109
+
}
110
+
}
111
+
112
+
pub fn reset_timer_source(&mut self) {
113
+
self.timer_source = T::get_source();
114
+
}
115
+
116
+
pub fn get_timestamp(&self) -> i64 {
117
+
self.timer_source.timestamp()
118
+
}
119
+
120
+
pub fn set_blip_threshold(&self, threshold: u16) {
121
+
self.config.blip_threshold.set(threshold);
122
+
}
123
+
124
+
pub fn set_blip_size(&self, size: usize) {
125
+
self.config.blip_size.set(size);
126
+
}
127
+
128
+
pub async fn tune(&mut self, samples: &mut [u16]) {
129
+
// info!("Tuning Detector for correct voltage settings");
130
+
let mut duty = 0;
131
+
self.pwm.set_duty(duty);
132
+
Timer::after_secs(2).await;
133
+
let mut act_value = self.adc.sample_average(samples).await;
134
+
135
+
// info!("initial ACT: {}", act_value);
136
+
137
+
while act_value < 1364 {
138
+
duty += 2;
139
+
if duty >= 256 {
140
+
duty = 0;
141
+
self.pwm.set_duty(duty);
142
+
Timer::after_secs(2).await;
143
+
act_value = self.adc.sample_average(samples).await;
144
+
// info!("Restarting tuning");
145
+
continue;
146
+
}
147
+
self.pwm.set_duty(duty);
148
+
Timer::after_millis(250).await;
149
+
act_value = self.adc.sample_average(samples).await;
150
+
// info!("ACT: {}, Duty: {}", act_value, duty);
151
+
}
152
+
self.state.max_duty.set(duty as u8);
153
+
duty = (duty / 3) * 2;
154
+
self.pwm.set_duty(duty);
155
+
self.state.duty.set(duty as u8);
156
+
// info!("Set detection duty to: {}", duty);
157
+
// Allow voltage level to stabilize after tuning
158
+
Timer::after_secs(2).await;
159
+
let avg = self.adc.sample_average(samples).await;
160
+
self.state.avg.set(avg);
161
+
}
162
+
163
+
/// Samples and returns an `i64` timestamp.
164
+
pub async fn sample_with_zerocopy<'a>(
165
+
&self,
166
+
dma: &mut Sender<'a, NoopRawMutex, (i64, [u16; BLOCK_SIZE])>,
167
+
) {
168
+
loop {
169
+
let (time, samples) = dma.send().await;
170
+
*time = self.timer_source.timestamp();
171
+
self.adc.sample(samples).await;
172
+
dma.send_done();
173
+
}
174
+
}
175
+
176
+
pub fn detect_from_sample<B, F>(
177
+
&self,
178
+
timestamp: i64,
179
+
samples: &[u16],
180
+
peaks: &mut B,
181
+
update: F,
182
+
) where
183
+
B: BufferMut<usize>,
184
+
F: Fn(DetectorUpdate<'_>),
185
+
{
186
+
peaks.clear();
187
+
188
+
let new_avg = analyse_buffer_by_stepped_windows(
189
+
self.config.blip_threshold.get(),
190
+
samples,
191
+
self.state.avg.get(),
192
+
peaks,
193
+
);
194
+
195
+
let blips = peaks.len();
196
+
197
+
if blips >= self.config.blip_size.get() {
198
+
self.state
199
+
.strikes
200
+
.update(|strike| strike.saturating_add(32));
201
+
202
+
update(DetectorUpdate::Detection {
203
+
timestamp,
204
+
samples,
205
+
peaks: peaks.as_slice(),
206
+
});
207
+
}
208
+
209
+
self.state.avg.set(new_avg);
210
+
}
211
+
212
+
pub async fn detect_with_zerocopy<'a, 'd, B, F>(
213
+
&self,
214
+
dma: &mut Receiver<'a, NoopRawMutex, (i64, [u16; BLOCK_SIZE])>,
215
+
peaks: &'d mut B,
216
+
update: F,
217
+
) where
218
+
B: BufferMut<usize>,
219
+
F: Fn(DetectorUpdate<'_>),
220
+
{
221
+
loop {
222
+
let (timestamp, samples) = dma.receive().await;
223
+
224
+
self.detect_from_sample(*timestamp, samples, peaks, &update);
225
+
226
+
dma.receive_done();
227
+
}
228
+
}
229
+
230
+
pub async fn tick<F>(&self, update: F)
231
+
where
232
+
F: Fn(DetectorUpdate),
233
+
{
234
+
let mut inactive: Option<Instant> = None;
235
+
let mut interval = Ticker::every(Duration::from_secs(1));
236
+
237
+
loop {
238
+
interval.next().await;
239
+
let strikes = self.state.strikes.get();
240
+
let mut warn_level = self.state.warn_level.get();
241
+
242
+
if strikes > 32 {
243
+
warn_level = warn_level.saturating_add(strikes);
244
+
}
245
+
246
+
let decay = warn_level >> 8;
247
+
248
+
self.state.strikes.set(0);
249
+
self.state.warn_level.set(warn_level - decay);
250
+
251
+
match inactive {
252
+
Some(_) if decay > 0 => {
253
+
inactive = None;
254
+
}
255
+
Some(val) if val.elapsed() >= Duration::from_secs(3600) => break,
256
+
None if decay == 0 => {
257
+
inactive = Some(Instant::now());
258
+
}
259
+
None => {
260
+
update(DetectorUpdate::Tick {
261
+
timestamp: self.timer_source.timestamp(),
262
+
level: warn_level - 255,
263
+
});
264
+
}
265
+
_ => continue,
266
+
}
267
+
}
268
+
}
269
+
}
270
+
271
+
fn analyse_buffer_by_stepped_windows<B: BufferMut<usize>>(
272
+
threshold: u16,
273
+
buf: &[u16],
274
+
average: u16,
275
+
peaks: &mut B,
276
+
) -> u16 {
277
+
const CHUNK_SIZE: usize = BLOCK_SIZE / 32;
278
+
const CHUNK_STEP: usize = CHUNK_SIZE / 2;
279
+
280
+
let mut total = 0u32;
281
+
let mut len = 0u32;
282
+
283
+
for (i, window) in buf.windows(CHUNK_SIZE).enumerate().step_by(CHUNK_STEP) {
284
+
let window_total = window.iter().copied().sum::<u16>();
285
+
let window_avg = window_total / CHUNK_SIZE as u16;
286
+
let diff = average.saturating_sub(window_avg);
287
+
288
+
if diff > threshold {
289
+
peaks.push(i);
290
+
} else {
291
+
total += window_total as u32;
292
+
len += CHUNK_SIZE as u32;
293
+
}
294
+
}
295
+
296
+
(total / len) as u16
297
+
}
298
+
299
+
#[cfg(test)]
300
+
mod tests {
301
+
use core::future::poll_fn;
302
+
use embassy_time::MockDriver;
303
+
304
+
extern crate alloc;
305
+
use super::*;
306
+
307
+
#[derive(Debug, Default)]
308
+
struct MockMachine {
309
+
pwm: Cell<u16>,
310
+
}
311
+
312
+
impl MockMachine {
313
+
fn adc_sample_avg(&self) -> u16 {
314
+
self.pwm.get().saturating_mul(14)
315
+
}
316
+
}
317
+
318
+
struct MockPwm<'a>(&'a MockMachine);
319
+
320
+
impl PwmSource for MockPwm<'_> {
321
+
fn set_duty(&mut self, duty: u16) {
322
+
self.0.pwm.set(duty);
323
+
}
324
+
}
325
+
326
+
struct MockAdc<'a>(&'a MockMachine);
327
+
328
+
impl AdcSource for MockAdc<'_> {
329
+
async fn sample(&self, samples: &mut [u16]) {
330
+
samples.fill(self.0.adc_sample_avg());
331
+
}
332
+
333
+
async fn sample_average(&self, _samples: &mut [u16]) -> u16 {
334
+
self.0.adc_sample_avg()
335
+
}
336
+
}
337
+
338
+
struct MockTimeSource;
339
+
340
+
impl TimeSource for MockTimeSource {
341
+
fn get_source() -> Self {
342
+
Self
343
+
}
344
+
fn timestamp(&self) -> i64 {
345
+
0
346
+
}
347
+
}
348
+
349
+
impl BufferMut<usize> for alloc::vec::Vec<usize> {
350
+
fn push(&mut self, value: usize) {
351
+
self.push(value);
352
+
}
353
+
354
+
fn clear(&mut self) {
355
+
self.clear();
356
+
}
357
+
358
+
fn len(&self) -> usize {
359
+
self.len()
360
+
}
361
+
362
+
fn is_empty(&self) -> bool {
363
+
self.is_empty()
364
+
}
365
+
366
+
fn as_slice(&self) -> &[usize] {
367
+
self
368
+
}
369
+
}
370
+
371
+
async fn tune_detector_manually<'a>(
372
+
detector: &mut DetectorDriver<MockTimeSource, MockPwm<'a>, MockAdc<'a>>,
373
+
buf: &mut [u16],
374
+
driver: &MockDriver,
375
+
) {
376
+
let mut tuning = core::pin::pin!(detector.tune(buf));
377
+
378
+
poll_fn(|cx| match tuning.as_mut().poll(cx) {
379
+
core::task::Poll::Ready(_) => core::task::Poll::Ready(()),
380
+
core::task::Poll::Pending => {
381
+
cx.waker().wake_by_ref();
382
+
driver.advance(Duration::from_secs(2));
383
+
384
+
core::task::Poll::Pending
385
+
}
386
+
})
387
+
.await;
388
+
}
389
+
390
+
fn generate_signal(samples: &mut [u16]) {
391
+
// Example voltage drop signal
392
+
samples[5] -= 60;
393
+
samples[6] -= 55;
394
+
samples[7] -= 50;
395
+
samples[8] -= 45;
396
+
samples[9] -= 40;
397
+
samples[10] -= 35;
398
+
samples[11] -= 30;
399
+
samples[12] -= 28;
400
+
samples[13] -= 26;
401
+
samples[14] -= 24;
402
+
samples[15] -= 22;
403
+
samples[16] -= 20;
404
+
samples[17] -= 18;
405
+
samples[18] -= 16;
406
+
samples[19] -= 14;
407
+
samples[20] -= 12;
408
+
samples[21] -= 11;
409
+
samples[22] -= 10;
410
+
samples[23] -= 9;
411
+
samples[24] -= 8;
412
+
samples[25] -= 7;
413
+
samples[26] -= 6;
414
+
samples[27] -= 5;
415
+
samples[28] -= 4;
416
+
samples[29] -= 3;
417
+
samples[30] -= 2;
418
+
samples[31] -= 1;
419
+
}
420
+
421
+
#[pollster::test]
422
+
async fn tuning_cycle_completes() {
423
+
let driver = embassy_time::MockDriver::get();
424
+
driver.reset();
425
+
let device = MockMachine::default();
426
+
let pwm = MockPwm(&device);
427
+
let adc = MockAdc(&device);
428
+
429
+
let mut detector = DetectorDriver::new(Default::default(), MockTimeSource, pwm, adc);
430
+
let mut buf = [0; 16];
431
+
432
+
tune_detector_manually(&mut detector, &mut buf, driver).await;
433
+
434
+
assert_eq!(detector.state.max_duty.get(), 98);
435
+
assert_eq!(detector.state.duty.get(), 64);
436
+
assert_eq!(detector.adc.sample_average(&mut buf).await, 896);
437
+
}
438
+
439
+
#[pollster::test]
440
+
async fn tick_updates_only_on_raised_levels() {
441
+
let driver = embassy_time::MockDriver::get();
442
+
driver.reset();
443
+
let device = MockMachine::default();
444
+
let pwm = MockPwm(&device);
445
+
let adc = MockAdc(&device);
446
+
447
+
let detector = DetectorDriver::new(Default::default(), MockTimeSource, pwm, adc);
448
+
449
+
detector.state.max_duty.set(98);
450
+
detector.state.duty.set(64);
451
+
detector.state.avg.set(896);
452
+
453
+
{
454
+
let mut tick = core::pin::pin!(detector.tick(|a| {
455
+
assert_eq!(
456
+
a,
457
+
DetectorUpdate::Tick {
458
+
timestamp: 0,
459
+
level: 300
460
+
}
461
+
);
462
+
}));
463
+
464
+
poll_fn(|cx| match tick.as_mut().poll(cx) {
465
+
core::task::Poll::Ready(_) => core::task::Poll::Ready(()),
466
+
core::task::Poll::Pending => {
467
+
if detector.state.warn_level.get() > 255 {
468
+
return core::task::Poll::Ready(());
469
+
}
470
+
detector.state.strikes.set(300);
471
+
driver.advance(Duration::from_secs(1));
472
+
cx.waker().wake_by_ref();
473
+
core::task::Poll::Pending
474
+
}
475
+
})
476
+
.await;
477
+
}
478
+
479
+
assert_eq!(detector.state.warn_level.get(), 553);
480
+
}
481
+
482
+
#[pollster::test]
483
+
async fn detection_updates_only_on_large_enough_signals() {
484
+
let driver = embassy_time::MockDriver::get();
485
+
driver.reset();
486
+
let device = MockMachine::default();
487
+
let pwm = MockPwm(&device);
488
+
let adc = MockAdc(&device);
489
+
490
+
let detector = DetectorDriver::new(Default::default(), MockTimeSource, pwm, adc);
491
+
detector.pwm.0.pwm.set(64);
492
+
detector.state.max_duty.set(98);
493
+
detector.state.duty.set(64);
494
+
detector.state.avg.set(896);
495
+
496
+
let mut samples = alloc::vec![0; BLOCK_SIZE];
497
+
498
+
detector.adc.sample(&mut samples).await;
499
+
500
+
samples[5] -= 3;
501
+
samples[6] -= 1;
502
+
503
+
let mut peaks = alloc::vec::Vec::with_capacity(512);
504
+
505
+
let update = |_update: DetectorUpdate<'_>| {
506
+
panic!("This update function shouldn't be called");
507
+
};
508
+
509
+
detector.detect_from_sample(0, &samples, &mut peaks, update);
510
+
511
+
assert_eq!(peaks.len(), 0);
512
+
513
+
generate_signal(&mut samples);
514
+
515
+
let expected_peaks = alloc::vec![0, 8];
516
+
let called = Cell::new(false);
517
+
518
+
let update = |update: DetectorUpdate<'_>| {
519
+
called.set(true);
520
+
assert_eq!(
521
+
update,
522
+
DetectorUpdate::Detection {
523
+
timestamp: 0,
524
+
samples: &samples,
525
+
peaks: expected_peaks.as_slice()
526
+
}
527
+
)
528
+
};
529
+
530
+
detector.detect_from_sample(0, &samples, &mut peaks, update);
531
+
532
+
assert_eq!(peaks.len(), 2);
533
+
assert!(called.get());
534
+
}
535
+
536
+
#[pollster::test]
537
+
async fn detection_sensitivity_can_be_configured() {
538
+
let driver = embassy_time::MockDriver::get();
539
+
driver.reset();
540
+
let device = MockMachine::default();
541
+
let pwm = MockPwm(&device);
542
+
let adc = MockAdc(&device);
543
+
544
+
let detector = DetectorDriver::new(Default::default(), MockTimeSource, pwm, adc);
545
+
detector.pwm.0.pwm.set(64);
546
+
detector.state.max_duty.set(98);
547
+
detector.state.duty.set(64);
548
+
detector.state.avg.set(896);
549
+
550
+
let mut samples = alloc::vec![0; BLOCK_SIZE];
551
+
let mut peaks: alloc::vec::Vec<usize> = alloc::vec::Vec::with_capacity(BLOCK_SIZE);
552
+
553
+
// Require bigger size blips.
554
+
detector.config.blip_size.set(3);
555
+
556
+
detector.adc.sample(&mut samples).await;
557
+
generate_signal(&mut samples);
558
+
559
+
detector.detect_from_sample(0, &samples, &mut peaks, |_update| {
560
+
panic!("Update shouldn't be called");
561
+
});
562
+
563
+
assert_eq!(peaks.len(), 2);
564
+
565
+
// Require bigger threshold for blip detection
566
+
detector.config.blip_size.set(2);
567
+
detector.config.blip_threshold.set(24);
568
+
569
+
detector.detect_from_sample(0, &samples, &mut peaks, |_update| {
570
+
panic!("Update shouldn't be called");
571
+
});
572
+
573
+
// Update didn't call because detected blip wasn't big enough
574
+
assert_eq!(peaks.len(), 1);
575
+
}
576
+
}
History
10 rounds
0 comments
1 commit
expand
collapse
feat: Embassy driver
1/1 success
expand
collapse
expand 0 comments
pull request successfully merged
1 commit
expand
collapse
feat: Embassy driver
1/1 success
expand
collapse
expand 0 comments
1 commit
expand
collapse
feat: Embassy driver
1/1 success
expand
collapse
expand 0 comments
1 commit
expand
collapse
feat: Embassy driver
1/1 failed
expand
collapse
expand 0 comments
1 commit
expand
collapse
feat: Embassy driver
1/1 success
expand
collapse
expand 0 comments
1 commit
expand
collapse
feat: Embassy driver
1/1 success
expand
collapse
expand 0 comments
1 commit
expand
collapse
feat: Embassy driver