-
Notifications
You must be signed in to change notification settings - Fork 6
/
main.rs
227 lines (189 loc) · 5.66 KB
/
main.rs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
#![deny(warnings)]
extern crate libc;
extern crate time;
extern crate errno;
use std::env;
use time::precise_time_ns;
use ffi::{gpio_init, read};
mod sched;
mod ffi;
// Time out if anything takes more than this long.
const TIMEOUT_NS: u64 = 1_000_000_000;
// Bus is idle if low for this long.
const IDLE_NS: u64 = 10_000_000;
// Number of bits per transmission.
const BITS: usize = 17;
// Bits per second.
const BITRATE: u64 = 1000;
// Length of recording for each transmission.
// Double the ideal transmission time to account for clock inaccuracy.
const RECORD_NS: u64 = 2 * (1_000_000_000 / BITRATE) * (BITS as u64);
// Bail out if we record more than this many edges (noise?)
const MAX_EDGES: usize = 3 * BITS;
// Signature for a good sample.
const SIGNATURE: u16 = 0xB400;
// Mask for signature.
const SIGNATURE_MASK: u16 = 0xFC00;
// Value of first resistor: BAT to input
const R1: f64 = 477000.0;
// Value of second resistor: input to GND
const R2: f64 = 118400.0;
// Input voltage of the microcontroller. This is the top of the ADC range.
const VCC: f64 = 3.30;
// Max ADC reading.
const MAX_ADC: f64 = 1023.0;
// Number of samples to average.
const SAMPLES: usize = 10;
// Error out if we take more than this many bad samples.
const MAX_BAD_SAMPLES: usize = 20;
#[derive(Debug)]
enum Error {
TimeoutIdle,
TimeoutWait,
TooManyEdges,
TooFewEdges,
PrematureEnd,
BadSignature,
}
// Take a single voltage reading from the sensor.
fn read_voltage() -> Result<f64, Error> {
// We don't really need to allocate a new Vec for each reading.
// But it's out of the realtime path and the time spent in this
// function is dominated by waiting for data, anyway.
let mut edges = Vec::with_capacity(MAX_EDGES);
edges.push(0);
//
// REALTIME SECTION BEGINS HERE
//
// Keep this code tight because we're trying to capture
// precise timings.
{
let _realtime = sched::Realtime::enter();
// Wait for idle bus.
let t0 = precise_time_ns();
let mut tlast = t0;
loop {
let t = precise_time_ns();
if (t - t0) >= TIMEOUT_NS {
return Err(Error::TimeoutIdle);
}
if read() {
tlast = t;
} else if (t - tlast) >= IDLE_NS {
break;
}
}
// Wait for bus to go high: start of transmission.
let mut t0 = precise_time_ns();
loop {
let t = precise_time_ns();
if (t - t0) >= TIMEOUT_NS {
return Err(Error::TimeoutWait);
}
if read() {
t0 = t;
break;
}
}
// Record for the designated time period.
let mut state = true;
loop {
let t = precise_time_ns();
if (t - t0) >= RECORD_NS {
break;
}
if read() != state {
edges.push(t - t0);
if edges.len() > MAX_EDGES {
return Err(Error::TooManyEdges);
}
state = !state;
}
}
}
//
// REALTIME SECTION ENDS HERE
//
if edges.len() < BITS {
return Err(Error::TooFewEdges);
}
// Clock recovery. Some intervals between edges will be 2x the others;
// average the short intervals.
let deltas: Vec<_> = edges.windows(2).map(|x| x[1] - x[0]).collect();
let threshold = (deltas.iter().max().unwrap() + deltas.iter().min().unwrap()) / 2;
let (sum_low, num_low) = deltas.iter()
.filter(|&&x| x < threshold)
.fold((0, 0), |(sum, num), x| (sum + x, num + 1));
let clock = sum_low / num_low;
// Decoded value.
let mut val = 0u16;
// Sample in the middle of the first half of each bit,
// skipping the first (always 1).
// i.e. 2*clock + clock/2, 4*clock + clock/2, ...
let mut bit = false;
let mut t = 5*clock/2;
let mut it = edges.iter().peekable();
for _ in 0..BITS-1 {
loop {
match it.peek() {
None => return Err(Error::PrematureEnd),
Some(&&p) if p > t => break,
_ => {
bit = !bit;
it.next().unwrap(); // must succeed, .peek() was Some
}
}
}
val >>= 1;
if bit {
val |= 0x8000;
}
t += 2*clock;
}
if (val & SIGNATURE_MASK) != SIGNATURE {
return Err(Error::BadSignature);
}
val &= !SIGNATURE_MASK;
Ok(((val as f64) / MAX_ADC) * ((R1 + R2) / R2) * VCC)
}
const MUNIN_CONFIG: &'static str
= include_str!("munin.cfg");
fn main() {
let mut args = env::args();
if let None = args.next() {
panic!("no argv[0]???");
}
match args.next() {
Some(ref s) if s == "config" => {
print!("{}", MUNIN_CONFIG);
return;
}
Some(_) => panic!("unrecognized command line option"),
None => (),
}
unsafe {
gpio_init();
}
let mut sample_sum = 0.0;
let mut good_samples = 0;
let mut bad_samples = 0;
loop {
match read_voltage() {
Ok(v) => {
sample_sum += v;
good_samples += 1;
if good_samples >= SAMPLES {
break;
}
}
Err(e) => {
bad_samples += 1;
if bad_samples > MAX_BAD_SAMPLES {
panic!("Too many bad samples! Last error: {:?}", e);
}
}
}
}
let avg_voltage = sample_sum / (good_samples as f64);
println!("battery_voltage.value {:6.3}", avg_voltage);
}