HiveBrain v1.2.0
Get Started
← Back to all entries
patternpythonMinor

Read decibel level from a USB meter, display it as a live visualization, and send it via FTP

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
meterftpreadleveldecibeldisplayusbliveviavisualization

Problem

This question was posed to me by someone in my university's athletics department who had a USB sound level meter and a simple Python script which printed the current sound level (he was provided with that script when he bought the meter):


Can you write a Python script to read the decibel level of the arena during basketball games? We're interested in adding that data as a 'live stat' in our official app.

This script represents my attempt at meeting his requirements. I'm self-taught in Python, and most of what I do in the language uses the NLTK and BeautifulSoup, so building a GUI and handling data from a USB device are outside my 'comfort zone'. So far it does what we need it to do, but I'm sure it can be improved.

Usage

For FTP uploading, the script assumes a ftpconfig.py file structured like this:

#!/usr/bin/env python
# -*- coding: utf-8 -*-

FTP_HOST = 'ftp.example.com'
FTP_USERNAME = 'username'
FTP_PASSWORD = 'password'
FTP_DIR = 'subdirectory'


To send output via FTP, call the script with python decibelviz.py --ftp. Calling the script without that --ftp flag won't try to send anything via FTP.

Note also that the DecibelVisualizer.live_dbs() method, which gets the reading from the USB meter, will return random values in the same interval if the meter's not connected. This makes for a useful demo mode.

The visualization itself looks like this:

Output

The script saves two sorts of JSON output files.

The file kudbs.json is the file that's sent via FTP every fifteen seconds. It's saved with no indentation (it's assumed that this will be for machine reading, not human reading) and is overwritten on successful upload.

kudbs.json

[[1449674472,52.0],[1449674473,67.0],[1449674473,31.0],[1449674474,91.0],[1449674475,87.0],[1449674475,129.0],[1449674476,108.0],[1449674477,35.0]]


The file totalresults_XX.json is saved when the Stop button is pressed, and contains all data gathered since the Start button was pressed. This file is in

Solution

I can answer question 1, and I have some general feedback besides that. First, your question.

Your DecibelVisualizer's init is very long. I think at the very least you should split some of them up into separate functions to make it easier to see what's happening at each point. Your configure_grid certainly seems like a good candidate.

I'd also question whether or not you actually need all of those optional parameters. For settings that are almost never supplied, perhaps they could be set up separate to __init__ with a different function? Normally I would advise that it's helpful to let the user provide them on __init__ but there are really a lot of parameters there and it harms readability.

In particular, I don't think self.colors should be defined in the __init__ since it's a constant. You should instead have it at class level.

class DecibelVisualizer(object):

    self.COLORS = {
        13: '#E50000',
        12: '#E14400',
        11: '#DD8700',
        10: '#D9C701',
        9:  '#A7D501',
        8:  '#65D201',
        7:  '#25CE02',
        6:  '#02CA1C',
        5:  '#02C658',
        4:  '#03C391',
        3:  '#03B6BF',
        2:  '#037ABB',
        1:  '#0341B7',
        0:  '#040AB4',
    }


When defined here it can either be accessed with DecibelVisualizer.COLORS or still with self.COLORS. Each instance of the class can refer back to it. And since it's always the same there's no need to make a new one with each visualiser.

You don't need pass in your except block here, you may have misunderstood its use. It's merely a keyword that does nothing, used as a placeholder where Python expects a block of code but you don't need to write any. For example, here's what would happen if you tried to remove both print and pass:

try:
    from ftpconfig import FTP_HOST, FTP_USERNAME, FTP_PASSWORD, FTP_DIR
except ImportError:
    # Nothing here


This will raise a syntax error, because there must be indented code after except ImportError. However you can just put pass in there to tell Python to do nothing while still satisfying the syntax requirement. But if you have actual code in there, like your print, then there's no longer a need for pass.

In your fibonacci_number function you call range, but since you're using Python 2.7 you should use xrange, which doesn't create a full list in one go and so is more efficient.

It's good that you've documented well, your classes and functions are quite clear. Avoid over commenting though. People will know what __init__ does especially when your initialisations are simple so you could leave out notes like Initialize the ReadoutHeading object..

It's not Pythonic to use if boolean == False, instead use the boolean value directly. Either with if boolean or if not boolean. Also it's not necessary to set a boolean for your while loop since you're just going to break out of it anyway. You can just do while True:

if not overwrite:
        idx = 1
        while True:
            str_idx = str(idx).rjust(2, '0')
            fname = '{}_{}'.format(filename, str_idx)
            if os.path.isfile(fname + '.json'):
                idx += 1
            else:
                filename = fname
                break


Likewise in the ternary, use if not:

indent = 3 if not overwrite else None

Code Snippets

class DecibelVisualizer(object):

    self.COLORS = {
        13: '#E50000',
        12: '#E14400',
        11: '#DD8700',
        10: '#D9C701',
        9:  '#A7D501',
        8:  '#65D201',
        7:  '#25CE02',
        6:  '#02CA1C',
        5:  '#02C658',
        4:  '#03C391',
        3:  '#03B6BF',
        2:  '#037ABB',
        1:  '#0341B7',
        0:  '#040AB4',
    }
try:
    from ftpconfig import FTP_HOST, FTP_USERNAME, FTP_PASSWORD, FTP_DIR
except ImportError:
    # Nothing here
if not overwrite:
        idx = 1
        while True:
            str_idx = str(idx).rjust(2, '0')
            fname = '{}_{}'.format(filename, str_idx)
            if os.path.isfile(fname + '.json'):
                idx += 1
            else:
                filename = fname
                break
indent = 3 if not overwrite else None

Context

StackExchange Code Review Q#113389, answer score: 3

Revisions (0)

No revisions yet.