Various notes on the source code to make things go.

1) Install httpd
2) Install php

wave_analysis.php

This piece of code loads the mains.wav data points in to an array. It attempts to find a positive going zero crossing point. It then throws the data into an image and adds some text with the peak-to-peak voltage and the RMS voltage (the algorithm simply rectifies (abs) and takes an average - equivalent to squaring (rectification) and then taking the square root and averaging).

It can be run directly from the web browser in the usual way or, as is done here, run via the crontab every minute.

There is also an output of just the peak-to-peak and RMS voltages with a timestamp in JSON. The objective is to pipe this information into ElasticSearch via FileBeat and Logstash. Kibana is used for the analytics over the indices once the data is captured. It can then be overlaid and combined with the JSON output of the Fronius inverter providing comprehensive analytics.

<?php
  /* Phill Bertolus 2018 - completely free to use */
  header("Content-type: image/png");

  $filename   = "/dev/shm/mains.wav";
  $myfile     = fopen($filename, "r") or die("Unable to open file!");
  $binarydata = fread($myfile, filesize($filename));
  $array      = unpack("s4000", $binarydata);
  fclose($myfile);

  $samples       = 2000;
  $width         = 1024;
  $height        = 768;
  $axis_inset    = 50;
  $voltage_scale = 427;

  $x_pixels   = $width  - 2 * $axis_inset;
  $y_pixels   = $height - 2 * $axis_inset;
  $im         = imagecreate($width, $height) or die("Cannot Initialize new GD image stream");
  $background_color = imagecolorallocate($im, 255, 255, 255);
  $black      = imagecolorallocate($im, 0, 0, 0);
  imageline($im, $axis_inset, $height - $axis_inset, $width - $axis_inset, $height - $axis_inset, $black);
  imageline($im, $axis_inset, $height - $axis_inset, $axis_inset, $axis_inset, $black);
  $last_x = $axis_inset;
  $last_y = $height - $axis_inset;

  // Find positive going zero crossing for lock
  $sync_lock    = -1;
  $sync_voltage = 0;
  $last_voltage = 260;
  for ($i = 1; $i < $samples; $i++) {
    $voltage = 260 * ($array[$i] / 32768);
    if (($voltage > -5) AND ($voltage < 5)) {
      $direction = $voltage - $last_voltage;
      if (($direction > 0) AND ($voltage > 0)) {
        $sync_lock = $i;
        break 1;
      }
    }
    $last_voltage = $voltage;
  }

  $voltage_min   = PHP_INT_MAX;
  $voltage_max   = PHP_INT_MIN;
  $voltage_total = 0;
  for ($i = 1 ; $i < $samples; $i++) {
    $voltage_normalized = $array[$i + $sync_lock] / 32768;
    $voltage_total     += abs($voltage_normalized);
    $voltage_min        = min($voltage_min, $voltage_normalized * $voltage_scale);
    $voltage_max        = max($voltage_max, $voltage_normalized * $voltage_scale);
    $x_point = $axis_inset + $i / $samples * $x_pixels;
    $y_point = $height - ($axis_inset + $voltage_normalized * $y_pixels + $y_pixels / 2);
    imageline($im, $last_x, $last_y, $x_point, $y_point, $black);
    $last_x  = $x_point;
    $last_y  = $y_point;
  }
  $voltage_rms = 2 * ($voltage_total / $samples) * $voltage_scale;
  $voltage_ptp = $voltage_max - $voltage_min;
  $Title       = "Voltage p-p: $voltage_ptp, Voltage(RMS): $voltage_rms";
  imagestring ($im , 5, 200, 10, $Title, $black);

  $objDateTime = new DateTime('NOW');
  $json_out    = '{'."\n".'"ptp" : '.$voltage_ptp.','."\n".'"rms" : '.$voltage_rms.','."\n".'"Timestamp" : "'.$objDateTime->format(DateTime::W3C).'"'."\n".'}'."\n";
  $filename    = "/dev/shm/mains.json";
  $myfile      = fopen($filename, "w") or die("Unable to open file!");
  fwrite($myfile , "$json_out");
  fclose($myfile);

  imagepng($im);
  imagedestroy($im);
?>

 

PHP source code for Version 2

<?php
  /* Phill Bertolus 2019 - completely free to use */
  /* Free as in free free */
  header("Content-type: image/png");

  $filename     = "/dev/shm/mains.wav";
  $myfile       = fopen($filename, "r") or die("Unable to open file!");
  $binarydata   = fread($myfile, filesize($filename));
  $arrayData    = unpack("s8000", $binarydata);
  fclose($myfile);

  const MINUTES_IN_DAY = 60*24;

  $powerhistory = "/dev/shm/power-history.json";
  $myfile       = @fopen($powerhistory, "r");
  if ($myfile === FALSE ) {
    $powerData  = array_fill(0, MINUTES_IN_DAY, 0);
  } else {
    $binarydata = fread($myfile, filesize($filename));
    $powerData  = json_decode($binarydata, TRUE);
    fclose($myfile);
  }
  $current_minute = date('G') % 24 *60 + date('i');
  $current_day    = date('z') + 1;

  $powerdaily   = "/dev/shm/power-daily.json";
  $myfile       = @fopen($powerdaily, "r");
  if ($myfile === FALSE ) {
    $powerDaily = array_fill(0, 365, 0);
  } else {
    $binarydata = fread($myfile, filesize($filename));
    $powerDaily = json_decode($binarydata, TRUE);
    fclose($myfile);
  }

  const SAMPLES      = 2000;
  const SAMPLE_RATE  = 32000;
  const MAINS_FREQ   = 50;
  const WIDTH        = 1024;
  const HEIGHT       = 768;
  const VOLT_SCALE   = 426.4;
  const AMPS_SCALE   = 29.8;
  const SHIFT_RIGHT  = 65536;
  const PHASE_OFFSET = 60;

  $axis_inset        = 50;
  $x_pixels          = WIDTH  - 2 * $axis_inset;
  $y_pixels          = HEIGHT - 2 * $axis_inset;

  function x_to_screen_x($x) {
    global $axis_inset;

    return $x + $axis_inset;
  }

  function y_to_screen_y($y) {
    global $axis_inset;

    return HEIGHT - $axis_inset - $y;
  }

  function scale_to_x_axis($x, $data_width){
    global $axis_inset;

    return round($x / $data_width * (WIDTH - 2*$axis_inset));
  }

  function scale_to_y_axis($y, $data_height){
    global $axis_inset;

    return round($y / $data_height * (HEIGHT - 2*$axis_inset));
  }

  function current_minute_to_screen_x() {
    global $current_minute;

    return x_to_screen_x(scale_to_x_axis($current_minute, 1440));
  }

  $arrayVoltage     = array();
  $arrayCurrent     = array();
  for ($i = 1; $i < SAMPLES * 2; $i++) {
    $arrayVoltage[$i] = $arrayData[$i * 2]     / SHIFT_RIGHT;
    $arrayCurrent[$i] = $arrayData[$i * 2 - 1] / SHIFT_RIGHT;
  }

  $im         = imagecreate(WIDTH, HEIGHT) or die("Cannot Initialize new GD image stream");
  $background_color = imagecolorallocate($im, 255, 255, 255);
  $black      = imagecolorallocate($im,   0,   0,   0);
  $red        = imagecolorallocate($im, 255,   0,   0);
  $green      = imagecolorallocate($im,   0, 195,   0);
  $blue       = imagecolorallocate($im,   0,   0, 255);
  imageline($im, $axis_inset, HEIGHT - $axis_inset - $y_pixels/2, WIDTH - $axis_inset, HEIGHT - $axis_inset - $y_pixels/2, $black);
  imageline($im, $axis_inset-1, HEIGHT - $axis_inset, $axis_inset-1, $axis_inset, $black);
  $last_x = $axis_inset;
  //$last_y = HEIGHT - $axis_inset;
  $last_y     = HEIGHT / 2;

  // Find positive going zero crossing for lock
  $sync_lock    = -1;
  $sync_voltage = 0;
  $last_voltage = 260;
  for ($i = 1; $i < SAMPLES; $i++) {
    $voltage = 260 * ($arrayVoltage[$i]);
    if (($voltage > -5) AND ($voltage < 5)) {
      $direction = $voltage - $last_voltage;
      if (($direction > 0) AND ($voltage > 0)) {
        $sync_lock = $i;
        break 1;
      }
    }
    $last_voltage = $voltage;
  }

  // Frequency measurement variables
  // measure over 3 cycles.
  $sample_period = SAMPLE_RATE / MAINS_FREQ;
  $cycle_end     = 3 * $sample_period;
  // Find positive going zero crossing for lock
  $zero_xing     = -1;
  $sync_voltage  = 0;
  $last_voltage  = 260;
  for ($i = $cycle_end  + $sync_lock - 100; $i < SAMPLES + $sync_lock; $i++) {
    $voltage = 260 * $arrayVoltage[$i];
    if (($voltage > -5) AND ($voltage < 5)) {
      $direction = $voltage - $last_voltage;
      if (($direction > 0) AND ($voltage > 0)) {
        $zero_xing = $i;
        break 1;
      }
    }
    $last_voltage = $voltage;
  }
  $freq = 3 * SAMPLE_RATE / ($zero_xing - $sync_lock);
  $vertical_line = $zero_xing - $sync_lock;
  $pixel_pos_x   = $vertical_line / SAMPLES * $x_pixels;
  imageline($im, $axis_inset + $pixel_pos_x, HEIGHT - $axis_inset, $axis_inset + $pixel_pos_x, $axis_inset, $green);
  imageline($im, $axis_inset + $pixel_pos_x / 3, HEIGHT - $axis_inset, $axis_inset + $pixel_pos_x / 3, $axis_inset, $green);
  imageline($im, $axis_inset + $pixel_pos_x / 3 * 2, HEIGHT - $axis_inset, $axis_inset + $pixel_pos_x / 3 * 2, $axis_inset, $green);

  $voltage_min   = PHP_INT_MAX;
  $voltage_max   = PHP_INT_MIN;
  $current_min   = PHP_INT_MAX;
  $current_max   = PHP_INT_MIN;
  $voltage_total = 0;
  $current_total = 0;
  $last_vy       = HEIGHT - ($axis_inset + $y_pixels / 2);
  $last_iy       = $last_vy;
  for ($i = 0 ; $i < SAMPLES; $i++) {
#   $voltage_normalized = $arrayData[$i + $sync_lock];
    $voltage_normalized = $arrayVoltage[$i + $sync_lock];
    $current_normalized = $arrayCurrent[$i + $sync_lock + PHASE_OFFSET ];
    $voltage_total     += abs($voltage_normalized);
    $current_total     += abs($current_normalized);
    $voltage_min        = min($voltage_min, $voltage_normalized * VOLT_SCALE);
    $voltage_max        = max($voltage_max, $voltage_normalized * VOLT_SCALE);
    $current_min        = min($current_min, $current_normalized * AMPS_SCALE);
    $current_max        = max($current_max, $current_normalized * AMPS_SCALE);
//  $x_point            = $axis_inset + $i / SAMPLES * $x_pixels;
    $x_point            = x_to_screen_x(scale_to_x_axis($i, SAMPLES));
    $vy_point           = HEIGHT - ($axis_inset + $voltage_normalized * $y_pixels + $y_pixels / 2);
    $iy_point           = HEIGHT - ($axis_inset + $current_normalized * $y_pixels + $y_pixels / 2);
    imageline($im, $last_x, $last_vy, $x_point, $vy_point, $black);
    imageline($im, $last_x, $last_iy, $x_point, $iy_point, $blue);
    $last_x             = $x_point;
    $last_vy            = $vy_point;
    $last_iy            = $iy_point;
  }
  $voltage_rms   = 2 * ($voltage_total / SAMPLES) * VOLT_SCALE;
  $voltage_ptp   = $voltage_max - $voltage_min;
  $current_rms   = 2 * ($current_total / SAMPLES) * AMPS_SCALE;
  $current_ptp   = $current_max - $current_min;
  $objDateTime   = new DateTime('NOW');
  $current_watts = $voltage_rms*$current_rms;
  $powerData[$current_minute] = $current_watts;
  $Title         = "Voltage p-p: ".sprintf("%6.02f",$voltage_ptp)."V, Voltage RMS: ".sprintf("%6.02f",$voltage_rms)."V,";
  imagestring ($im , 5,  50, 10, $Title, $black);
  $Title         = "Frequency: ".number_format($freq, 2)."Hz";
  imagestring ($im , 5, 450, 10, $Title, $green);
  $Title         = "Current p-p: ".sprintf("%6.02f",$current_ptp)."A, Current RMS: ".sprintf("%6.02f",$current_rms)."A,";
  imagestring ($im , 5,  50, 25, $Title, $blue);
  $Title         = "Power:    ".sprintf("%6.02f",$current_watts)."W";
  imagestring ($im , 5, 450, 25, $Title, $red);
  $Title         = "Timestamp: ".$objDateTime->format(DateTime::W3C);
  imagestring ($im , 5,  50, 735, $Title, $black);
  imagepng($im, "/dev/shm/mains.png");
  imagedestroy($im);

  $json_out      = '{'."\n".'"ptp" : '.number_format($voltage_ptp, 2).','."\n".'"rms" : '.number_format($voltage_rms, 2).','."\n".'"freq" : '.number_format($freq, 2).','."\n".'"Timestamp" : "'.$objDateTime->format(DateTime::W3C).'"'."\n".'}'."\n";
  $filename      = "/dev/shm/mains.json";
  $myfile        = fopen($filename, "w") or die("Unable to open file!");
  fwrite($myfile , "$json_out");
  fclose($myfile);

  $json_out      = json_encode($powerData);
  $myfile        = fopen($powerhistory, "w") or die("Unable to open file!");
  fwrite($myfile , "$json_out");
  fclose($myfile);

  //
  // Power graph
  //
  $im            = imagecreatetruecolor(WIDTH, HEIGHT) or die("Cannot Initialize new GD image stream");
  $background_color =
                   imagecolorallocate($im, 255, 255, 255);
  $black         = imagecolorallocate($im,   0,   0,   0);
  $red           = imagecolorallocate($im, 255,   0,   0);
  $green         = imagecolorallocate($im,   0, 195,   0);
  $blue          = imagecolorallocate($im,  33,  89, 135);
  $lightblue     = imagecolorallocate($im, 142, 195, 248);
  $grey          = imagecolorallocate($im, 175, 175, 175);
  $faint_grey    = imagecolorallocate($im, 225, 225, 225);

  // Fill old data area - from 'now' until midnight
  imagefilledrectangle($im, 0, 0, WIDTH, HEIGHT, $background_color);
  $x_current_minute = scale_to_x_axis($current_minute, 1440) - $x_pixels;
  for ($i = 0; $i < $x_pixels; $i++){
    $gradient = 225 + (30) / $x_pixels * (($i - $x_current_minute) % $x_pixels);
    $gradientColor = imagecolorallocate($im, $gradient, $gradient, $gradient);
    imageline($im, x_to_screen_x($i), y_to_screen_y(0), x_to_screen_x($i), y_to_screen_y($y_pixels), $gradientColor);
  }
  // x-axis
  imageline($im, x_to_screen_x(0), y_to_screen_y(-1), x_to_screen_x($x_pixels), y_to_screen_y(-1), $grey);
  // y-axis
  imageline($im, x_to_screen_x(-1), y_to_screen_y(0), x_to_screen_x(-1), y_to_screen_y($y_pixels), $grey);
  // current time tick & text
  imageline($im, current_minute_to_screen_x(), y_to_screen_y(0), current_minute_to_screen_x(), y_to_screen_y(-20), $red);
  $time          = str_pad(intval($current_minute / 60),2,'0',STR_PAD_LEFT).':'.str_pad(intval($current_minute % 60), 2, '0', STR_PAD_LEFT);
  imagestring ($im , 3, current_minute_to_screen_x() - 17, y_to_screen_y(-20), $time, $grey);
  //
  // Ticks on x-axis
  //
  for ($i = 0 ; $i < 24; $i++) {
    $x_point = x_to_screen_x(scale_to_x_axis($i, 24));
    imageline($im, $x_point, y_to_screen_y(-1), $x_point, y_to_screen_y(-5), $grey);
    if ($i % 2 == 1) {
      imagestring($im , 3, $x_point - 17, y_to_screen_y(-8), str_pad($i.':00', 5, '0', STR_PAD_LEFT), $grey);
    }
  }
  //
  // Ticks on y-axis
  //
  for ($i = 0 ; $i < 3501; $i+=500) {
    $x_point = x_to_screen_x(-1);
    $y_point = y_to_screen_y(scale_to_y_axis($i, 3500));
    imageline($im, $x_point, $y_point, $x_point - 5, $y_point, $grey);
    imagestring($im , 3, $x_point - 41, $y_point - 6, str_pad($i, 5, ' ', STR_PAD_LEFT), $grey);
  }
  //
  // Power data
  //
  $totalEnergy = 0;
  for ($i = 0 ; $i < 1440; $i++) {
    $vx_point = x_to_screen_x(scale_to_x_axis($i, 1440));
    $vy_point = y_to_screen_y(scale_to_y_axis($powerData[$i], 3500));
//  echo "xp=$xp, yp=$yp, vx_point=$vx_point, vy_point=$vy_point, totalEnergy=$totalEnergy\n";
    if ($i > $current_minute)
      $color = $lightblue;
    else
      $color = $blue;
    imageline($im, $vx_point, y_to_screen_y(0), $vx_point, $vy_point, $color);
    $totalEnergy += $powerData[$i];
  }
  $totalEnergy /= 60000;
  $Title        = "Energy consumption kWh (last 24 hours):";
  imagestring ($im , 5,  50, 10, $Title, $black);
  $Title        = sprintf("%5.02f",$totalEnergy)."kWh";
  imagestring ($im , 5, 410, 10, $Title, $red);
  imagepng    ($im, "/dev/shm/power-history.png");
  imagedestroy($im);

  $powerDaily[$current_day] = $totalEnergy;
  $json_out      = json_encode($powerDaily);
  $myfile        = fopen($powerdaily, "w") or die("Unable to open file!");
  fwrite($myfile , "$json_out");
  fclose($myfile);


?>