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