1#!/usr/bin/env python3 2# 3# Copyright 2019 - The Android Open Source Project 4# 5# Licensed under the Apache License, Version 2.0 (the "License"); 6# you may not use this file except in compliance with the License. 7# You may obtain a copy of the License at 8# 9# http://www.apache.org/licenses/LICENSE-2.0 10# 11# Unless required by applicable law or agreed to in writing, software 12# distributed under the License is distributed on an "AS IS" BASIS, 13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14# See the License for the specific language governing permissions and 15# limitations under the License. 16 17import numpy as np 18 19from acts.controllers.monsoon_lib.sampling.engine.assembly_line import BufferList 20from acts.controllers.monsoon_lib.sampling.engine.transformer import ParallelTransformer 21from acts.controllers.monsoon_lib.sampling.engine.transformer import SequentialTransformer 22 23 24class Tee(SequentialTransformer): 25 """Outputs main_current values to the specified file. 26 27 Attributes: 28 _filename: the name of the file to open. 29 _fd: the filestream written to. 30 """ 31 32 def __init__(self, filename, measure_after_seconds=0): 33 """Creates an OutputStream. 34 35 Args: 36 filename: the path to the file to write the collected data to. 37 measure_after_seconds: the number of seconds to skip before 38 logging data as part of the measurement. 39 """ 40 super().__init__() 41 self._filename = filename 42 self._fd = None 43 self.measure_after_seconds = measure_after_seconds 44 # The time of the first sample gathered. 45 self._start_time = None 46 47 def on_begin(self): 48 self._fd = open(self._filename, 'w+') 49 50 def on_end(self): 51 self._fd.close() 52 53 def _transform_buffer(self, buffer): 54 """Writes the reading values to a file. 55 56 Args: 57 buffer: A list of HvpmReadings. 58 """ 59 for sample in buffer: 60 if self._start_time is None: 61 self._start_time = sample.sample_time 62 if (sample.sample_time - self._start_time < 63 self.measure_after_seconds): 64 continue 65 self._fd.write('%0.9f %.12f\n' % 66 (sample.sample_time, sample.main_current)) 67 self._fd.flush() 68 return BufferList([buffer]) 69 70 71class PerfgateTee(SequentialTransformer): 72 """Outputs records of nanoseconds,current,voltage to the specified file. 73 74 Similar to Tee, but this version includes voltage, which may help with 75 accuracy in the power calculations. 76 77 This output type can be enabled by passing this transformer to the 78 transformers kwarg in Monsoon.measure_power(): 79 80 # Uses the default Tee 81 > monsoon.measure_power(..., output_path=filename]) 82 83 # Uses PerfgateTee 84 > monsoon.measure_power(..., transformers=[PerfgateTee(filename)]) 85 86 Attributes: 87 _filename: the name of the file to open. 88 _fd: the filestream written to. 89 """ 90 91 def __init__(self, filename, measure_after_seconds=0): 92 """Creates an OutputStream. 93 94 Args: 95 filename: the path to the file to write the collected data to. 96 measure_after_seconds: the number of seconds to skip before logging 97 data as part of the measurement. 98 """ 99 super().__init__() 100 self._filename = filename 101 self._fd = None 102 self.measure_after_seconds = measure_after_seconds 103 # The time of the first sample gathered. 104 self._start_time = None 105 106 def on_begin(self): 107 self._fd = open(self._filename, 'w+') 108 109 def on_end(self): 110 self._fd.close() 111 112 def _transform_buffer(self, buffer): 113 """Writes the reading values to a file. 114 115 Args: 116 buffer: A list of HvpmReadings. 117 """ 118 for sample in buffer: 119 if self._start_time is None: 120 self._start_time = sample.sample_time 121 if (sample.sample_time - self._start_time < 122 self.measure_after_seconds): 123 continue 124 self._fd.write( 125 '%i,%.6f,%.6f\n' % 126 (sample.sample_time * 1e9, sample.main_current, 127 sample.main_voltage)) 128 self._fd.flush() 129 return BufferList([buffer]) 130 131 132class SampleAggregator(ParallelTransformer): 133 """Aggregates the main current value and the number of samples gathered.""" 134 135 def __init__(self, start_after_seconds=0): 136 """Creates a new SampleAggregator. 137 138 Args: 139 start_after_seconds: The number of seconds to wait before gathering 140 data. Useful for allowing the device to settle after USB 141 disconnect. 142 """ 143 super().__init__() 144 self._num_samples = 0 145 self._sum_currents = 0 146 self.start_after_seconds = start_after_seconds 147 # The time of the first sample gathered. 148 self._start_time = None 149 150 def _transform_buffer(self, buffer): 151 """Aggregates the sample data. 152 153 Args: 154 buffer: A buffer of H/LvpmReadings. 155 """ 156 for sample in buffer: 157 if self._start_time is None: 158 self._start_time = sample.sample_time 159 if sample.sample_time - self._start_time < self.start_after_seconds: 160 continue 161 self._num_samples += 1 162 self._sum_currents += sample.main_current 163 return buffer 164 165 @property 166 def num_samples(self): 167 """The number of samples read from the device.""" 168 return self._num_samples 169 170 @property 171 def sum_currents(self): 172 """The total sum of current values gathered so far.""" 173 return self._sum_currents 174 175 176class DownSampler(SequentialTransformer): 177 """Takes in sample outputs and returns a downsampled version of that data. 178 179 Note for speed, the downsampling must occur at a perfect integer divisor of 180 the Monsoon's sample rate (5000 hz). 181 """ 182 _MONSOON_SAMPLE_RATE = 5000 183 184 def __init__(self, downsample_factor): 185 """Creates a DownSampler Transformer. 186 187 Args: 188 downsample_factor: The number of samples averaged together for a 189 single output sample. 190 """ 191 super().__init__() 192 193 self._mean_width = int(downsample_factor) 194 self._leftovers = [] 195 196 def _transform_buffer(self, buffer): 197 """Returns the buffer downsampled by an integer factor. 198 199 The algorithm splits data points into three categories: 200 201 tail: The remaining samples where not enough were collected to 202 reach the integer factor for downsampling. The tail is stored 203 in self._leftovers between _transform_buffer calls. 204 tailless_buffer: The samples excluding the tail that can be 205 downsampled directly. 206 207 Below is a diagram explaining the buffer math: 208 209 input: input buffer n input buffer n + 1 210 ╔══════════════════════════╗ ╔══════════════════════════╗ 211 ... ║ ╔╗╔╗╔╗╔╗╔╗╔╗╔╗╔╗╔╗╔╗╔╗╔╗ ║ ║ ╔╗╔╗╔╗╔╗╔╗╔╗╔╗╔╗╔╗╔╗╔╗╔╗ ║ ... 212 ║ ╚╝╚╝╚╝╚╝╚╝╚╝╚╝╚╝╚╝╚╝╚╝╚╝ ║ ║ ╚╝╚╝╚╝╚╝╚╝╚╝╚╝╚╝╚╝╚╝╚╝╚╝ ║ 213 ╚══════════════════════════╝ ╚══════════════════════════╝ 214 ▼ ▼ 215 alg: ╔═════════════════════╦════╗ ╔═════════════════════╦════╗ 216 ║ ╔╗╔╗╔╗╔╗╔╗╔╗╔╗╔╗╔╗╔╗║╔╗╔╗║ ║ ╔╗╔╗╔╗╔╗╔╗╔╗╔╗╔╗╔╗╔╗║╔╗╔╗║ 217 ║ ╚╝╚╝╚╝╚╝╚╝╚╝╚╝╚╝╚╝╚╝║╚╝╚╝║ ║ ╚╝╚╝╚╝╚╝╚╝╚╝╚╝╚╝╚╝╚╝║╚╝╚╝║ 218 ... ║ tailless_buffer ║tail║ ║ tailless_buffer ║tail║ ... 219 ╚═════════════════════╩════╝ ╚═════════════════════╩════╝ 220 ──┬───┘ └─┬─┘ ... └─┬─┘ └────┬─────┘ └─┬─┘ ... └─┬─┘ └──┬─── 221 ╔╗ ╔╗ ╔╗ ╔╗ ╔╗ ╔╗ ╔╗ ╔╗ ╔╗ ╔╗ ╔╗ 222 ╚╝ ╚╝ ╚╝ ╚╝ ╚╝ ╚╝ ╚╝ ╚╝ ╚╝ ╚╝ ╚╝ 223 └─────────┬────────┘ └──────────┬─────────┘ 224 ▼ ▼ 225 output: ╔════════════════╗ ╔════════════════╗ 226 ║ ╔╗ ╔╗ ╔╗ ╔╗ ╔╗ ║ ║ ╔╗ ╔╗ ╔╗ ╔╗ ╔╗ ║ 227 ║ ╚╝ ╚╝ ╚╝ ╚╝ ╚╝ ║ ║ ╚╝ ╚╝ ╚╝ ╚╝ ╚╝ ║ 228 ╚════════════════╝ ╚════════════════╝ 229 output buffer n output buffer n + 1 230 """ 231 tail_length = int( 232 (len(buffer) + len(self._leftovers)) % self._mean_width) 233 234 tailless_buffer = np.array(buffer[:len(buffer) - tail_length]) 235 236 sample_count = len(tailless_buffer) + len(self._leftovers) 237 238 downsampled_values = np.mean( 239 np.resize( 240 np.append(self._leftovers, tailless_buffer), 241 (sample_count // self._mean_width, self._mean_width)), 242 axis=1) 243 244 self._leftovers = buffer[len(buffer) - tail_length:] 245 246 return downsampled_values 247