Gas station without pumps

2016 September 5

Improved spectral response for dehumidifier noise

Filed under: Data acquisition — gasstationwithoutpumps @ 14:29
Tags: , , , , , ,

In Dehumidifier, I provided spectral analysis of the sound from the 30-pint Whynter RPD-321EW Energy Star Portable Dehumidifier, using my BitScope USB oscilloscope to record the sound and averaging the absolute value of the FFT over 100 short traces. Today I decided to improve the FFT analysis by using the scipy.signal.welch() function to split a longer trace into windows, apply a Dolph-Chebyshev window-shaping function, and average the resulting power spectra. The main improvement here is the use of the window-shaping function.

I used PteroDAQ with a Teensy LC board to record at 15kHz, which was as fast as I could go with 4× averaging communicating with my old MacBook Pro laptop. Based on the Bitscope recordings, though, that should include all the interesting signal, which appears to be limited to about 5.4kHz. I could only record for about 330,000 samples to avoid overrunning the USB buffer and dropping samples, so I also tried 12kHz, which allowed me to run longer. The analysis looked much the same, with no apparent reduction in the noise floor, so I’ll only present the 15kHz results. (Note: when I get a new laptop this fall, I should be able to run at this sampling rate for much longer, which should allow smoothing out more of the noise.)

The dehumidifier looks very much like 1/f noise, with a big spike around 1050Hz. The spikes at 60Hz, 120Hz, 240Hz, and 360Hz may be electrical noise picked up by the sound amplifier, rather than acoustic noise.

The dehumidifier looks very much like 1/f noise, with a big spike around 1050Hz. The spikes at 60Hz, 120Hz, 240Hz, and 360Hz may be electrical noise picked up by the sound amplifier, rather than acoustic noise.

Using Welch’s method and a proper window function results in a much cleaner spectrum than before. I used a 216 FFT window for the analysis, since I had plenty of data, which gives a fairly fine resolution of about 0.23Hz. The 60Hz spike is appearing at 58.36Hz, which is a little troubling, as the PteroDAQ crystal and the power company both have better frequency control than a 2.7% error. The 120Hz, 240Hz, and 360Hz spikes are at most one bin off from where they should be, so I don’t understand the shift at 60Hz.

I checked my code by testing with a recording of a triangle wave, and found that Welch’s method with an 80dB Dolph-Chebyshev window did an excellent job of removing the spectral smear that I was getting by just averaging rectangular FFT windows (my first approach). The noise floor for Welch’s method on those measurements was pretty much flat across the entire spectrum (maybe rising at higher frequencies), so the 1/f plot here is not an artifact of the analysis, but a real measurement of the acoustic noise. Using 60dB window resulted in a higher noise floor, but 100dB and larger did not lower the noise floor, so I stuck with 80dB as a good parameter value. It might be useful to use other values if the FFT window width is changed or the noise in the data is different.

I’ve included the source code for the program, so that others can use it. It does require that SciPy be installed, in order to use the scipy.signal package.

#! /usr/bin/env python3

"""Sun Sep  4 21:40:28 PDT 2016 Kevin Karplus

Reads from stdin a white-space separated file 
whose first column is a timestamp (in seconds) and 
whose subsequent columns are floating-point data values.

May contain multiple traces, separated by blank lines.

Outputs a tab-separated file with two columns:
        the frequencies
        the Fourier transform

"""

from __future__ import print_function, division

import argparse
import sys
from math import sqrt
import numpy as np
try:
    from future_builtins import zip
except ImportError:     # either before Python2.6 or one of the Python 3.*
    try:
        from itertools import izip as zip
    except ImportError: # not an early Python, so must be Python 3.*
        pass

import scipy
import scipy.signal

class Trace:
    """One contiguous trace consisting of two 1D numpy arrays of the same length
        times:  timestamps in seconds
        values: arbitrary floats
      and sampling_freq: the sampling frequency in Hz
    """
    def __init__(self,times,values,sampling_freq=None):
        self.times=np.array(times)
        self.values=np.array(values)
        assert self.times.shape==self.values.shape
        if sampling_freq is None and len(times)>1:
            sampling_freq = (len(times)-1)/(times[-1]-times[0])
        self.sampling_freq=sampling_freq
    def __len__(self):
        return len(self.values)

def read_trace(file, column=2, sampling_freq=None, echo_comments_to=None):
    """Generator that yields traces from tab-separated rows of data.
    Each row contains a timestamp in the first column and any number of subsequent
    columns, one of which is selected to be the values for the trace.
    
    New traces are started whenever the timestamp in the first column decreases.
    
    If sampling_freq is None, the sampling_freq for the
    trace is deduced from the timestamps.
    
    Comments and blank lines are echoed to a file-like object,
    echo_comments_to, if it is not None.
    """
    times=[]    # time values (in seconds) from frst column
    values=[]   # values from the specified column
    
    for line in file:
        line=line.strip()
        if line.startswith('#') or len(line)==0:
            if echo_comments_to is not None:
                print(line, file=echo_comments_to)
            continue
        fields=line.split()
        if len(fields)>=column:
            time=float(fields[0])
            if times and time<times[-1]:
                yield Trace(times=times, values=values, sampling_freq=sampling_freq)
                times=[]
                values=[]
            times.append(time)
            values.append(float(fields[column-1]))
    if times:
        yield Trace(times=times, values=values, sampling_freq=sampling_freq)


def posint(opt):
    """returns int(opt) if that is positive,
            otherwise raises an argparse.ArgumentTypeError
    """
    try:
        x = int(opt)
    except:
        x=None
    if x is None or x<=0:
        raise argparse.ArgumentTypeError("{} is not a positive integer".format(opt))
    return x

def parse_options(argv):
    """Parse the options and return what argparse does:
        a structure whose fields are the possible options
    """
    parser = argparse.ArgumentParser( description= __doc__, formatter_class = argparse.ArgumentDefaultsHelpFormatter )
    parser.add_argument("--sampling_freq","-s", type=float,
            default=None,
            help="""Sampling frequency (in Hz). 
            If none specified, then estimate sampling frequency from the timestamps.
            """)
    parser.add_argument("--window", "-w", type=posint,
            default=(1<<16),
            help="""Max window size (in samples) for FFT.
            Longer traces will be split up into windows of this size
            """)
    parser.add_argument("--column" , "-c", type=posint, default=2,
           help="""What column should be Fourier transformed?""")
    parser.add_argument("--welch" , type=float, default=None,
           help="""Use Welch's method to produce averaged spectrum from windows. 
           Value is dB difference between main lobe and sidelobes for Dolph-Chebyshev window. 
           80 seems reasonable with 16-bit data (60 produces a higher noise floor, without noticeably narrowing the spikes).
           Note, sqrt is used to get amplitude spectrum, rather than power spectrum. """)
    options=parser.parse_args(argv[1:])
    return options


def main(argv):
    """parse the command line arguments and do FFTs
    """
    options = parse_options(argv)
    sampling_freq= options.sampling_freq

    # input file may contain separate time traces.
    # Each new trace stats when the time value is less than the previous time.
    
    for trace in read_trace(sys.stdin, column=options.column, 
                        sampling_freq=options.sampling_freq, 
                        echo_comments_to=sys.stdout):
        print("# using sampling frequency {:.6f} Hz for FFT of trace with {} samples".format(trace.sampling_freq,len(trace)))


        fft_len=64      # length of the fft to do
        while fft_len<len(trace) and fft_len<options.window:
            fft_len *= 2
        
        if options.welch is not None:
            welch_f,welch_spectrum = scipy.signal.welch(trace.values, trace.sampling_freq, nperseg=fft_len, scaling="spectrum", window=("chebwin",options.welch))
            print("")       # separate the traces
            for f,w in zip(welch_f,welch_spectrum):
                print("{0:.7f}\t{1:.6f}".format(f, sqrt(w)*(fft_len//2)))
        else:
            for start in range(0,len(trace),fft_len):
                window=trace.values[start:start+fft_len]

                # remove DC component, so that zero-padding makes sense
                # WARNING: This in-place change could introduce errors if
                # overlapping windows are allowed.
                dc = sum(window)/len(window)
                window -= dc

                fft = scipy.fftpack.fft(window, n=fft_len)
                fft[0] += dc

                print("")       # separate the traces
                print("# Window width {} = {:.3f}s".format(fft_len,fft_len/trace.sampling_freq))
                for i,f in enumerate(fft[0:fft_len//2]):
                    print("{0:.7f}\t{1:.6f}".format(i*trace.sampling_freq/fft_len, f))


if __name__ == "__main__" :
    sys.exit(main(sys.argv))

Dehumidifier

Filed under: Data acquisition — gasstationwithoutpumps @ 00:05
Tags: , , ,

In Average annual power use, I mentioned

I will be buying a dehumidifier for our house, to reduce the condensation on the walls. Current Energy Start rated dehumidifiers remove about 1.85 liters of water per kWh used, and I don’t think we need to remove 2775 liters of water a year (7.6 l/day) from our house, so the dehumidifier will add nothing to our electricity bill.  Based on reviews (in Consumer Reports and on Amazon), we’re looking at the 30-pint Whynter RPD-321EW Energy Star Portable Dehumidifier, as it has good performance in cool rooms (our house gets quite cool in winter, especially when we’re both at work) and is relatively easy to empty (we don’t have a convenient way to rig up a drain hose).

We bought the dehumidifier and it seems to work ok. The timer is a bit crude, as it does not have a time-of-day clock, but you can set up a delay of up to 24 hours for turning on and turning off.  This means that we’ll have to reset the timer daily. That’s not as much of an imposition as one might think, because we have to empty the water daily any way.  People running a dehumidifier with a drain hookup might find the need to reset the timer more of a nuisance.

Our biggest disappointment with the dehumidifier is that it is loud. They claim that it is only 53dB, but it seems louder than that to me—unfortunately, I don’t have a sound pressure meter to measure it with. The compressor is fairly quiet, but the fan is loud, which suggests a poorly designed air flow. We can run the dehumidifier in the living room, but not in the bedroom, because of the noise it makes.

I can’t measure the loudness of the dehumidifier in calibrated units, but I could record chunks of sound from it using an electret mic, an amplifier, and the BitScope USB oscilloscope. I then wrote a program using SciPy to take the FFT of each recorded trace, and averaged the absolute values of FFTs for a hundred or so traces:

The noise is "pink" noise, with more low-frequency components than high frequency ones. The 5kHz cutoff is not an artifact of my amplifier, which has fairly flat gain out to about 100kHz, nor of the microphone, which is fairly flat to at least 20 kHz.

The noise is “pink” noise, with more low-frequency components than high frequency ones. The 5kHz cutoff is not an artifact of my amplifier, which has fairly flat gain out to about 100kHz, nor of the microphone, which is fairly flat to at least 20 kHz.

We can use a lower sampling rate to zoom in on the lower frequency end of the spectrum:

The 60Hz peak is sharper here, and 120Hz is clearly missing, but the next several harmonics of 60Hz are present. I don’t have an explanation for the dip between 400Hz and 480Hz.

I don’t know whether the spike around 1067Hz (in both plots) is an artifact of my test setup or is present in the sound. I suspect it is present in the sound.

The dehumidifier takes 340 watts to run (about 40W for the fan and 300W for the compressor), and it extracts about 5 pints in 5 hours (somewhat slower than its claimed 30 pints/24 hours, but that is probably based on warmer, wetter air, which is easier to extract water from). We only run for 5 hours a night, because we don’t want to be woken up by the loud beeping that occurs when the 7-pint bucket fills up.  (There is no way mentioned in the manual to disable the alarm—my son and I might disassemble the dehumidifier to disconnect the irritating beep.)

We plan to run the dehumidifier only during off-peak hours (at night or weekend days), for about 45 hours a week, which will consume about 800kWh a year. At PG&E off-peak rates, that is around $120 a year, but we had $98 of unused Net-Electric Metering (NEM) credits, and our minimum bill is around $111, so the extra electricity use will make no change in our bill. We will probably want to pay for carbon offsets, though, since we are increasing our carbon footprint by about 0.23MT (so about $2 in carbon offsets).

2016 July 30

Average annual power use

Filed under: Uncategorized — gasstationwithoutpumps @ 00:47
Tags: , , , , ,

I just got my “True Up” bill from PG&E—it has been about a year since the solar panels were installed. During that time, the panels generated about 2.63MWh of electricity (7.2kWh/day): 77kWh more than we used during the year. PG&E reimbursed me $2.11 for the extra electricity, but wiped out the $106 of Net Energy Metering credits that we had accrued from generating electricity during peak time and using electricity during off-peak times.

Next year, we’ll be facing a minimum delivery charge of about $120 for the year. If we follow the same peak/off-peak usage, that means that we could use about another $226 worth of electricity without increasing our bill (other than losing the $2.11 credit). That would be about 1.5MWh off-peak, or only 660kWh peak consumption. What that translates to for us is that I will be buying a dehumidifier for our house, to reduce the condensation on the walls. Current Energy Start rated dehumidifiers remove about 1.85 liters of water per kWh used, and I don’t think we need to remove 2775 liters of water a year (7.6 l/day) from our house, so the dehumidfier will add nothing to our electricity bill.  Based on reviews (in Consumer Reports and on Amazon), we’re looking at the 30-pint Whynter RPD-321EW Energy Star Portable Dehumidifier, is it has good performance in cool rooms (our house gets quite cool in winter, especially when we’re both at work) and is relatively easy to empty (we don’t have a convenient way to rig up a drain hose).

We are fairly light users of electricity by US standards. We used about 2.63MWh a year, but the US average is 10.932 MWh/year, and the California average is 6.744MWh/year [https://www.eia.gov/tools/faqs/faq.cfm?id=97&t=3].  PG&E also reports what people in our area use: similar houses use 6.042MWh/year, and efficient similar houses use 3.262MWh/year [https://pge.opower.com/ei/app/myEnergyUse].

Part of the reason we use so little electricity is that we rely on natural gas for heating, hot water, cooking, and clothes drying, using about 433 therms a year.  Here we are not particularly efficient: PG&E reports that similar houses use 548 therms/year, but efficient similar houses use only 293 therms/year [https://pge.opower.com/ei/app/myEnergyUse]. Shorter showers and setting up a clothes line would probably reduce our usage, but heating is the biggest chunk, and our house is already as cool as we are willing to live in.  We’ve invested in insulation over the years, but there is only so much you can do with a poured-concrete house for sane amounts of money.

A therm is about 29.3001 kWh, so our natural gas use is about equivalent to 12.7MWh—much more energy usage than our electricity!

We’ve been planning to buy carbon offset credits for our energy usage this year (see previous estimates in Solar lies).  Nothing for electricity of course, since we had a slight surplus there.  According to PG&E, natural gas produces about 6.1 kg CO2 per therm (and their electricity generation is about 238 g/kWh, only slightly more than the 208g for the same amount of energy from natural gas) [http://www.pge.com/includes/docs/pdfs/about/environment/calculator/assumptions.pdf].

I calculate approximately the following CO2 production from our various uses this year:

My wife and I have considered taking another trip this year, to Boston, which would add another 4.9MT. Note that flying is by far the most energy intensive thing we do—reducing travel is probably the only way we could significantly reduce our carbon footprint.  As carbon offsets, we’re considering projects like https://www.cooleffect.org/content/project/efficient-cookstove-project/, which cost $6–$10 per MT.  Do any of my readers know of good carbon offsets that aren’t scams or just enabling polluters?

 

%d bloggers like this: