99
1010from .power_supply import PowerError , PowerSupply
1111
12+
1213class 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