Read SPL value

Read AR-844 USB data

Ok I've understood more or less the USB data communication from the AR844 to the USB.

I had to decide what to do to rad the data out of my SPL meter. There are many ways to do it, kernel driver, libusb userland code, perl ... python ...

The current solution I have is a little quick&dirty but ... it works :-)

I wrote a simple perl script "get_spl.pl" that read the measure from the instrument, print out on console the result and eventually does add the measure to a .csv (comma separated value) file.

Perl was simple to use, and also my life was easy simply by using perl+libusb modules.

Since I know the VENDOR_ID ad PRODUCT_ID the first part of the script does the USB communication setup, open the interface and print the endpoint.

The script has a "for" loop that actually only runs once, the reason is that I plan to have an input parameter to let the script to capture multiple values in a sort of "batch capture". But for now I do only read one value each time the script get called.

Here is the full code:

#!/usr/bin/perl -w

#
#=BEGIN BRAINWORKS GPL
#
# This file is part of the BrainWorks RPi Environmental Monitor.
#
# Copyright(c) 2013 Gianluca Filippini
# http://www.brainworks.it
# info@brainworks.it
#
#    This program is free software: you can redistribute it and/or modify
#    it under the terms of the GNU General Public License as published by
#    the Free Software Foundation, either version 3 of the License, or
#    (at your option) any later version.
#
#    This program is distributed in the hope that it will be useful,
#    but WITHOUT ANY WARRANTY; without even the implied warranty of
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#    GNU General Public License for more details.
#
#    You should have received a copy of the GNU General Public License
#    along with this program.  If not, see .
#
#=END BRAINWORKS GPL
#


use Date::Format;
use Time::HiRes qw(usleep);
use strict;
use Device::USB;

# output csv file
my $data_path=$ARGV[0];
my $data_file=$ARGV[1];

# Brain Actuated Technologies
my $VENDOR = 0x1234; 

# AR844 Digital Sound Level Meter
my $PRODUCT = 0x5678; 

my $tmp=0;

my $usb = Device::USB->new();
my $dev = $usb->find_device( $VENDOR, $PRODUCT );

printf "Device: %04X:%04X\n", $dev->idVendor(), $dev->idProduct();

$tmp = $dev->open();
print "open: ".$tmp."\n";

$tmp = $dev->detach_kernel_driver_np(0);
print "detach: ".$tmp."\n";

my $cfg = $dev->config()->[0];
my $inter = $cfg->interfaces()->[0]->[0];

print "Interface:", $inter->bNumEndpoints(), 
  " name: ", $dev->get_string_simple($inter->iInterface()),
  ": endpoint count: ", $inter->bNumEndpoints(), "\n";
print "\n";


my $buf;
my @bytes;
my $SoundLeveldB;
my $MeasureSpeed;
my $MeasureCurveType;
my $MeasureRange;
my $MeasureDate;
my $MeasureTime;


for (my $count = 1; $count >= 1; $count--) {
  #
  # send the READ command
  $buf = "\x{b3}\x{00}\x{00}\x{00}\x{00}\x{00}\x{00}\x{00}"; 
  $tmp = $dev->interrupt_write( 0x02, $buf, 1000 );

  #
  # read the SPL measure
  $tmp = $dev->interrupt_read( 0x81, $buf = "", 8, 1000 );
  @bytes = unpack 'C*', $buf;

  #
  # some simple conversion and human friendly logging
  my $datetime = time;
  my $MeasureDate = time2str("%Y-%m-%d", $datetime);
  print $MeasureDate;
  my $MeasureTime = time2str("%H:%M:%S", $datetime);
  print $MeasureTime;

  $SoundLeveldB = (256*$bytes[0] + $bytes[1])/10;
  printf " %2.1f ",$SoundLeveldB;

  $MeasureSpeed = $bytes[2] >> 6;
  if ($MeasureSpeed eq 1) {
    print "FAST ";
  } else {
    print "SLOW ";
  }

  $MeasureCurveType = ($bytes[2] >> 4) & 0x01;
  if ($MeasureCurveType eq 0) {
    print "A ";
  } else {
    print "C ";
  }

  $MeasureRange = $bytes[2] & 0x07;
  print $MeasureRange;
  print "\n";

  #
  #output to CSV file
  if ( (defined $data_path) && (defined $data_file) ) {
    my $filename=$data_path."/".$data_file;

    open DATAFILE, ">>", $filename;

    print DATAFILE $MeasureDate.",";
    print DATAFILE $MeasureTime.",";
    print DATAFILE $SoundLeveldB.",";
    print DATAFILE $MeasureSpeed.",";
    print DATAFILE $MeasureCurveType.",";
    print DATAFILE $MeasureRange.",";
    print DATAFILE time2str("%H,", $datetime);
    print DATAFILE time2str("%M,", $datetime);
    print DATAFILE time2str("%S\n", $datetime);

    close (DATAFILE);
  }
  
  #
  # sleep 1sec
  usleep(1000000);
}

#
#print "reset!\n";
$dev->reset();

print "\n";	
print "\n";    

exit(1);

The inner part of the for loop is where the USB data get exchanged. I'm writing a "0xb3" (plus zeros) command and then read back the value from the AR844.
After that I do expand the single bytes and convert them to the final measure. While I do print dome "human friendly" console output I also append the value to the .csv file.
Note that $data_path/$data_file are taken from the console line (ARGV).

[2013-03-10] The only problem I have with this script is that I still cannot do multiple reads in the for loop. Somehow after reading the value back from the AR844 (interrupt_read) the spl meter is stuck until I do the reset.
This is not a big problem for me because I plan to read every 60sec (1 minute) but I really would like to fix this. I will probably move to python and libsub ... it seems a little bit more reliable.

Background Sampling loop

Finally here is the last note on "cron".
I use cron to launch another "wrapper" script that does read from the AR844 unit every minute or so and monitor a settings file content to decide if to "pause" or "continue" or "quit". Using a file is simple enough for my purposes and will allow to control the data sampling for the background process.

The sampling background script is launched in my "bootstrap.sh" trivial script:

cd /home/pi/noisemeter/
echo run > ./settings/rpi_em_ctrl.txt
./rpi_em_sampling_loop.pl /home/pi/noisemeter/data/&

As you see I simply echo the word "run" into the setting fle "rpi_em_ctrl.txt" (i.e. RaspberryPi Environmental Monitor Control) and launch the sampling loop in background. The "rpi_em_sampling_loop.pl" is the wrapper to the "get_spl.pl" script. Note that the argument to for the sampling loop script is actualy the folder where I save all the .csv data files.

#!/usr/bin/perl -w

#
#=BEGIN BRAINWORKS GPL
#
# This file is part of the BrainWorks RPi Environmental Monitor.
#
# Copyright(c) 2013 Gianluca Filippini
# http://www.brainworks.it
# info@brainworks.it
#
#    This program is free software: you can redistribute it and/or modify
#    it under the terms of the GNU General Public License as published by
#    the Free Software Foundation, either version 3 of the License, or
#    (at your option) any later version.
#
#    This program is distributed in the hope that it will be useful,
#    but WITHOUT ANY WARRANTY; without even the implied warranty of
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#    GNU General Public License for more details.
#
#    You should have received a copy of the GNU General Public License
#    along with this program.  If not, see .
#
#=END BRAINWORKS GPL
#


use Date::Format;
use Time::HiRes qw(usleep);
use strict;

#
# Locals
#
my $rpi_em_ctrl="quit";
my $sampling_period_usec=50000000; #almost 1min
my $data_folder=$ARGV[0];
my $verbose=$ARGV[1];
my $app_path="/home/pi/noisemeter/";
my $tmp = 0;
my $buf = "";

$app_path =~ s/\A\s+//s;
$app_path =~ s/\s+\Z//s; 


if (!(defined $verbose)) {
  $verbose=0;
}

if ( ($#ARGV != 0) && ($#ARGV != 1) ) {
  print "usage: rpi_em_sampling_loop ./path_data_folder verbosity[1-3] \n";
  exit;
}

#
# Infinite loop based on the "rpi_em_ctrl.txt" file
#
open RPI_EM_CTRL, "<", "./settings/rpi_em_ctrl.txt" or die $!;
chomp($rpi_em_ctrl = );
close (RPI_EM_CTRL);

if ($verbose>0) {
  print "RPi Environmental Monitor: SAMPLING LOOP STARTED\n";
}

while ( $rpi_em_ctrl ne "quit" ) {

  if ( $rpi_em_ctrl eq "run" ) {
    my $time_now = time;

    #
    # MAIN DATA SAMPLING LOOP
    #   
    if ($verbose>0) {
      print time2str("%Y-%m-%d %H:%M:%S %Z\n", $time_now);   
    }

    #
    # get SPL level
    $buf = "daily_spl_".time2str("%Y-%m-%d", time).".csv";
    $tmp = "$app_path/get_spl.pl $data_folder $buf > /dev/null";
    $tmp = `$tmp`;

    #
    # get internal pressure/temperature level
    $buf = `sudo $app_path/get_internal_temperature_pressure.py`;
    chomp($buf);
    my @values = split(/ /,$buf);
    my $int_pressure=(int($values[0]*100))/100;
    my $int_temperature=(int($values[1]*100))/100;

    #
    # get external temperature
    my $ext_temperature = `$app_path/get_external_temperature.py`;

    #
    # save barometric data
    $buf = "$data_folder/daily_barometric_".time2str("%Y-%m-%d", time).".csv";
    open RPI_EM_TP, ">>", $buf or die $!;
    $buf = time2str("%Y-%m-%d,%H:%M:%S",time);
    print RPI_EM_TP $buf.",".$time_now.",".$int_pressure.",".$int_temperature.",".$ext_temperature;
    close (RPI_EM_TP)
      
  }

  # sleep
  usleep($sampling_period_usec);

  # check the ctrl file
  open RPI_EM_CTRL, "<", "./settings/rpi_em_ctrl.txt" or die $!;
  chomp($rpi_em_ctrl = );
  close (RPI_EM_CTRL);
} 

if ($verbose>0) {
  print "RPi Environmental Monitor: SAMPLING LOOP STOPPED\n";
}

Important: to avoid running libusb as root make sure you have the correct rules in your "/etc/udev/rules.d".
It is useful to read the libusb FAQ on this link: "libusb-without-root-permissions"
I do have a file "40-noisemeter.rules" with the following settings:

ATTRS{idVendor}=="1234", ATTRS{idProduct}=="5678", SUBSYSTEMS=="usb", ACTION=="add", MODE="0777", GROUP="plugdev"

Notes: The "rpi_em_sampling.pl" does a loop ad sample few different values: the SPL, the internal temperature, the external temperature and also the barometric pressure.

I will add the notes on capturing temperature and pressure on a future post.