Skip to content

Commit d0db5ca

Browse files
committed
Store much higher (time) res power readings any time we've just fetched
new readings. This allows for better plotting/analysis but still keeping runtime polling low.
1 parent 0bc608d commit d0db5ca

2 files changed

Lines changed: 49 additions & 35 deletions

File tree

.vscode/launch.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -204,7 +204,7 @@
204204
"request": "launch",
205205
"module": "meshtastic",
206206
"justMyCode": false,
207-
"args": ["--slog", "--power-ppk2-supply", "--power-stress", "--power-voltage", "3.3", "--seriallog", "--ble"]
207+
"args": ["--slog", "--power-ppk2-supply", "--power-stress", "--power-voltage", "3.3", "--ble"]
208208
},
209209
{
210210
"name": "meshtastic test",

meshtastic/powermon/ppk2.py

Lines changed: 48 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99

1010
from .power_supply import PowerError, PowerSupply
1111

12+
1213
class PPK2PowerSupply(PowerSupply):
1314
"""Interface for talking with the NRF PPK2 high-resolution micro-power supply.
1415
Power Profiler Kit II is what you should google to find it for purchase.
@@ -35,12 +36,18 @@ def __init__(self, portName: Optional[str] = None):
3536
self.current_min = 0
3637
self.current_sum = 0
3738
self.current_num_samples = 0
39+
self.current_average = 0
3840

3941
# for tracking avera data read length (to determine if we are sleeping efficiently in measurement_loop)
4042
self.total_data_len = 0
4143
self.num_data_reads = 0
4244
self.max_data_len = 0
4345

46+
# Normally we just sleep with a timeout on this condition (polling the power measurement data repeatedly)
47+
# but any time our measurements have been fully consumed (via reset_measurements) we notify() this condition
48+
# to trigger a new reading ASAP.
49+
self.want_measurement = threading.Condition()
50+
4451
self.r = r = ppk2_api.PPK2_API(
4552
portName
4653
) # serial port will be different for you
@@ -55,52 +62,57 @@ def __init__(self, portName: Optional[str] = None):
5562
def measurement_loop(self):
5663
"""Endless measurement loop will run in a thread."""
5764
while self.measuring:
58-
# always reads 4096 bytes, even if there is no new samples - or possibly the python single thread (because of global interpreter lock)
59-
# is always behind and thefore we are inherently dropping samples semi randomly!!!
60-
read_data = self.r.get_data()
61-
if read_data != b"":
62-
samples, _ = self.r.get_samples(read_data)
63-
64-
# update invariants
65-
if len(samples) > 0:
66-
if self.current_num_samples == 0:
67-
self.current_min = samples[
68-
0
69-
] # we need at least one sample to get an initial min
70-
self.current_max = max(self.current_max, max(samples))
71-
self.current_min = min(self.current_min, min(samples))
72-
self.current_sum += sum(samples)
73-
self.current_num_samples += len(samples)
74-
# logging.debug(f"PPK2 data_len={len(read_data)}, sample_len={len(samples)}")
75-
76-
self.num_data_reads += 1
77-
self.total_data_len += len(read_data)
78-
self.max_data_len = max(self.max_data_len, len(read_data))
79-
80-
time.sleep(0.01) # FIXME figure out correct sleep duration
65+
with self.want_measurement:
66+
self.want_measurement.wait(0.0001 if self.num_data_reads == 0 else 0.01)
67+
# normally we poll using this timeout, but sometimes
68+
# reset_measurement() will notify us to read immediately
69+
70+
# always reads 4096 bytes, even if there is no new samples - or possibly the python single thread (because of global interpreter lock)
71+
# is always behind and thefore we are inherently dropping samples semi randomly!!!
72+
read_data = self.r.get_data()
73+
if read_data != b"":
74+
samples, _ = self.r.get_samples(read_data)
75+
76+
# update invariants
77+
if len(samples) > 0:
78+
if (
79+
self.current_num_samples == 0
80+
): # First set of new reads, reset min/max
81+
self.current_max = 0
82+
self.current_min = samples[
83+
0
84+
] # we need at least one sample to get an initial min
85+
self.current_max = max(self.current_max, max(samples))
86+
self.current_min = min(self.current_min, min(samples))
87+
self.current_sum += sum(samples)
88+
self.current_num_samples += len(samples)
89+
# logging.debug(f"PPK2 data_len={len(read_data)}, sample_len={len(samples)}")
90+
91+
self.num_data_reads += 1
92+
self.total_data_len += len(read_data)
93+
self.max_data_len = max(self.max_data_len, len(read_data))
8194

8295
def get_min_current_mA(self):
83-
"""Returns max current in mA (since last call to this method)."""
96+
"""Return the min current in mA."""
8497
return self.current_min / 1000
8598

8699
def get_max_current_mA(self):
87-
"""Returns max current in mA (since last call to this method)."""
100+
"""Return the max current in mA."""
88101
return self.current_max / 1000
89102

90103
def get_average_current_mA(self):
91-
"""Returns average current in mA (since last call to this method)."""
92-
if self.current_num_samples == 0:
93-
return 0
94-
else:
95-
return (
96-
self.current_sum / self.current_num_samples / 1000
97-
) # measurements are in microamperes, divide by 1000
104+
"""Return the average current in mA."""
105+
if self.current_num_samples != 0:
106+
# If we have new samples, calculate a new average
107+
self.current_average = self.current_sum / self.current_num_samples
108+
109+
# Even if we don't have new samples, return the last calculated average
110+
# measurements are in microamperes, divide by 1000
111+
return self.current_average / 1000
98112

99113
def reset_measurements(self):
100114
"""Reset current measurements."""
101115
# Use the last reading as the new only reading (to ensure we always have a valid current reading)
102-
self.current_max = 0
103-
self.current_min = 0
104116
self.current_sum = 0
105117
self.current_num_samples = 0
106118

@@ -110,6 +122,8 @@ def reset_measurements(self):
110122
self.num_data_reads = 0
111123
self.total_data_len = 0
112124
self.max_data_len = 0
125+
with self.want_measurement:
126+
self.want_measurement.notify() # notify the measurement loop to read immediately
113127

114128
def close(self) -> None:
115129
"""Close the power meter."""

0 commit comments

Comments
 (0)