commit 9c9fce8383603db811109f8ca7df9a397d1356e0 Author: Rik Veenboer Date: Tue Nov 27 09:26:25 2012 +0100 start.sh on boot, run inverter.php in infinite loop diff --git a/etc/init.d/inverter b/etc/init.d/inverter new file mode 100644 index 0000000..d85007c --- /dev/null +++ b/etc/init.d/inverter @@ -0,0 +1,20 @@ +#!/bin/sh +### BEGIN INIT INFO +# Provides: inverter +# Required-Start: $network +# Required-Stop: $network +# Default-Start: 2 3 5 +# Description: +### END INIT INFO + +case "$1" in +'start') + /opt/inverter/start.sh + ;; +'stop') + ;; +*) + echo "Usage: $0 { start | stop }" + ;; +esac +exit 0 diff --git a/opt/inverter/config.ini b/opt/inverter/config.ini new file mode 100644 index 0000000..b3df0b3 --- /dev/null +++ b/opt/inverter/config.ini @@ -0,0 +1,362 @@ +#----------------------------------------- +# CONFIGURATION FILE FOR INVERTER MONITOR +#----------------------------------------- + +# Should work with PHOENIXTEC manufactured inverters: CMS / Sun Ezy / Orion / Eaton et al. +# +# Should at least work with: +# * CMS2000 (CMS 2000) +# * CMS10000 (CMS 10000) - requires more testing though +# * SE2800 (SunEzy 2800) +# * SE4600 (SunEzy 600E) +# * ETN2000 (Eaton 2000) +# +# NOTE: http://pvoutput.org capable of accepting 60 data updates per hour, +# but will only keep 1 every 5-10mins depending on your setting. + +#------- +# flags +#------- + +[flags] +debug = 0 # 0 = NO, 1 = YES +use_pvoutput = 1 # 0 = NO, 1 = YES to export data to http://pvoutput.org +use_rrdtool = 1 # 0 = NO, 1 = YES to export data to rrdtool for graphing + +#------------------- +# number of seconds +#------------------- + +[secs] +datapoll_freq = 5 +pvoutput_freq = 300 # every 5-10mins per your setting in http://pvoutput.org +timeout = 2 +reinit = 10 # -1 = infinite num of times (ie dont die) + +#------------------ +# file path to use +#------------------ + +[paths] +windows = "C:/solar" # windows +other = "/opt/inverter/data" # unix/linux + +#------------------------- +# script and binary files +#------------------------- + +[scripts] +pvoutput = "perl pvoutput.pl" # to export data to http://pvoutput.org +create_rrd = "perl create_rrd.pl" # to export data to rrdtool for graphing +rrdtool_exe_win = "rrdtool" # windows +rrdtool_exe_oth = "/usr/bin/rrdtool" # unix/linux + +#---------------------- +# serial port settings +#---------------------- + +[serial] +baud = 9600 +port_win = "COM5" # windows, COM port +#port_oth = "/dev/ttyS0" # unix/linux, serial port +port_oth = "/dev/ttyUSB0" # unix/linux, USB port +#port_oth = "/dev/rfcomm0" # unix/linux, bluetooth port +parity = "none" +databits = 8 +stopbits = 1 +handshake = "none" +datatype = 'raw' + +#------------------------------------------------ +# hex start indeces and lengths for certain data +#------------------------------------------------ + +[hex] +data_to_follow_index = 8 +capacity_index = 20 +capacity_length = 12 +firmware_index = 32 +firmware_length = 14 +model_index = 46 +model_length = 28 +manuf_index = 74 +manuf_length = 32 +serial_index = 106 +serial_length = 20 +other_index = 138 +other_length = 8 +confserial_index = 18 + +#----------------------------------------------- +# hex packet codes - SEND (request to inverter) +#----------------------------------------------- + +[sendhex] +initialise = "aaaa010000000004000159" +serial = "aaaa010000000000000155" +conf_serial1 = "aaaa0100000000010b" +conf_serial2 = "01" +version = "aaaa01000001010300015a" +paramfmt = "aaaa010000010101000158" +param = "aaaa01000001010400015b" +datafmt = "aaaa010000010100000157" +data = "aaaa010000010102000159" + +#------------------------------------------------ +# hex packet codes - RECV (response to inverter) +#------------------------------------------------ + +[recvhex] +serial = "aaaa0000010000800a" +conf_serial = "aaaa000101000081" +version = "aaaa000101000183" +paramfmt = "aaaa000101000181" +param = "aaaa000101000184" +datafmt = "aaaa000101000180" +data = "aaaa000101000182" + +#--------------------- +# inverter parameters +#--------------------- + +[param_vpvstart] +hexcode = "40" +multiply = 0.1 +measure = "V" +index = -1 +descr = "PV Start-up voltage" + +[param_tstart] +hexcode = "41" +multiply = 1 +measure = "Sec" +index = -1 +descr = "Time to connect grid" + +[param_vacmin] +hexcode = "44" +multiply = 0.1 +measure = "V" +index = -1 +descr = "Minimum operational grid voltage" + +[param_vacmax] +hexcode = "45" +multiply = 0.1 +measure = "V" +index = -1 +descr = "Maximum operational grid voltage" + +[param_facmin] +hexcode = "46" +multiply = 0.01 +measure = "Hz" +index = -1 +descr = "Minimum operational frequency" + +[param_facmax] +hexcode = "47" +multiply = 0.01 +measure = "Hz" +index = -1 +descr = "Maximum operational frequency" + +[param_zacmax] +hexcode = "48" +multiply = 1 +measure = "mOhm" +index = -1 +descr = "Maximum operational grid impendance" + +[param_dzac] +hexcode = "49" +multiply = 1 +measure = "mOhm" +index = -1 +descr = "Allowable Delta Zac of operation" + +#--------------- +# inverter data +#--------------- + +[data_temp] +hexcode = "00" +multiply = 0.1 +measure = "deg C" +index = -1 +descr = "Internal Temperature" + +[data_vpv1] +hexcode = "01" +multiply = 0.1 +measure = "V" +index = -1 +descr = "Panel 1 Voltage" + +[data_vpv2] +hexcode = "02" +multiply = 0.1 +measure = "V" +index = -1 +descr = "Panel 2 Voltage" + +[data_vpv3] +hexcode = "03" +multiply = 0.1 +measure = "V" +index = -1 +descr = "Panel 3 Voltage" + +[data_ipv1] +hexcode = "04" +multiply = 0.1 +measure = "A" +index = -1 +descr = "Panel 1 DC Current" + +[data_ipv2] +hexcode = "05" +multiply = 0.1 +measure = "A" +index = -1 +descr = "Panel 2 DC Current" + +[data_ipv3] +hexcode = "06" +multiply = 0.1 +measure = "A" +index = -1 +descr = "Panel 3 DC Current" + +[data_etoday] +hexcode = "0d" +multiply = 0.01 +measure = "kWh" +index = -1 +descr = "Accumulated Energy Today" + +[data_vpv] +hexcode = "40" +multiply = 0.1 +measure = "V" +index = -1 +descr = "Panel Voltage" + +[data_iac] +hexcode = "41" +multiply = 0.1 +measure = "A" +index = -1 +descr = "Grid Current" + +[data_vac] +hexcode = "42" +multiply = 0.1 +measure = "V" +index = -1 +descr = "Grid Voltage" + +[data_fac] +hexcode = "43" +multiply = 0.01 +measure = "Hz" +index = -1 +descr = "Grid Frequency" + +[data_pac] +hexcode = "44" # "0b" for 3phase +multiply = 1 +measure = "W" +index = -1 +descr = "Output Power" + +[data_zac] +hexcode = "45" +multiply = 1 +measure = "mOhm" +index = -1 +descr = "Grid Impedance" + +[data_etotalh] +hexcode = "47" # "07" for 3phase +multiply = 256 +measure = "kWh" +index = -1 +descr = "Accumulated Energy (high bit)" + +[data_etotall] +hexcode = "48" # "08" for 3phase +multiply = 0.1 +measure = "kWh" +index = -1 +descr = "Accumulated Energy (low bit)" + +[data_htotalh] +hexcode = "49" # "09" for 3phase +multiply = 256 +measure = "hrs" +index = -1 +descr = "Working Hours (high bit)" + +[data_htotall] +hexcode = "4a" # "0a" for 3phase +multiply = 1 +measure = "hrs" +index = -1 +descr = "Working Hours (low bit)" + +[data_mode] +hexcode = "4c" # "0c" for 3phase +multiply = 1 +measure = " " +index = -1 +descr = "Operating Mode" + +[data_errgv] +hexcode = "78" +multiply = 1 +measure = " " +index = -1 +descr = "Error message: GV fault value" + +[data_errgf] +hexcode = "79" +multiply = 1 +measure = " " +index = -1 +descr = "Error message: GF fault value" + +[data_errgz] +hexcode = "7a" +multiply = 1 +measure = " " +index = -1 +descr = "Error message: GZ fault value" + +[data_errtemp] +hexcode = "7b" +multiply = 1 +measure = " " +index = -1 +descr = "Error message: Tmp fault value" + +[data_errpv1] +hexcode = "7c" +multiply = 1 +measure = " " +index = -1 +descr = "Error message: PV1 fault value" + +[data_errgfc1] +hexcode = "7d" +multiply = 1 +measure = " " +index = -1 +descr = "Error message: GFC1 fault value" + +[data_errmode] +hexcode = "7e" +multiply = 1 +measure = " " +index = -1 +descr = "Error mode" diff --git a/opt/inverter/create_rrd.pl b/opt/inverter/create_rrd.pl new file mode 100644 index 0000000..5bb8129 --- /dev/null +++ b/opt/inverter/create_rrd.pl @@ -0,0 +1,54 @@ +#!/usr/bin/perl -w +# +# AS AT 27Mar2011 +# +# This creates the rrd file using rrdtool (Round Robin Database Tool) +# Download & install rrdtool for your platform (unix/linux/windows) +# - http://oss.oetiker.ch/rrdtool/download.en.html +# - http://www.mywebhostingblog.net/window-hosting/install-rrdtool-on-windows-server/] +# +# CREATED BY: slampt with help from JinbaIttai +# +# + editions by shell_l_d: +# + converted to perl script +# +# Usage examples: +# perl create_rrd.pl "c:/solar/inverter.rrd" "c:/rrdtool/rrdtool" +# perl create_rrd.pl "/tmp/inverter.rrd" "/usr/bin/rrdtool" +# +# Arguments: +# $ARGV[0] = path & name for the new rrd file +# $ARGV[1] = path to rrdtool +# +####################################################################### + +my $rrdfile = $ARGV[0]; +my $rrdexe = $ARGV[1]; + +# +# Info: http://oss.oetiker.ch/rrdtool/doc/rrdcreate.en.html +# --step = base interval in secs with which data will be fed into the RRD. +# --start = time in secs since EPOCH when first value should be added to the RRD +# DS = Data Source +# DS:ds-name:GAUGE|COUNTER|DERIVE|ABSOLUTE:heartbeat:min:max +# RRA = Round Robin Archive +# RRA:AVERAGE|MIN|MAX|LAST:xff:steps:rows +# +my $rrdCreateLine = "--step 60 " . +# "--start 1300774440 " . + "DS:TEMP:GAUGE:120:U:U " . + "DS:VPV:GAUGE:120:U:U " . + "DS:IAC:GAUGE:120:U:U " . + "DS:VAC:GAUGE:120:U:U " . + "DS:FAC:GAUGE:120:U:U " . + "DS:PAC:GAUGE:120:0:U " . + "DS:ETOTAL:GAUGE:120:0:U " . + "DS:HTOTAL:GAUGE:120:0:U " . + "DS:MODE:GAUGE:120:0:U " . + "DS:ETODAY:GAUGE:120:0:U " . + "RRA:AVERAGE:0.5:1:576 " . # 1*60secs= 1min , 576* 1min = 9.6hrs = 0.4days + "RRA:AVERAGE:0.5:6:672 " . # 6*60secs= 6mins, 672* 6mins = 67.2hrs = 2.8days + "RRA:AVERAGE:0.5:24:732 " . # 24*60secs= 24mins, 732* 24mins = 292.8hrs = 12.2days + "RRA:AVERAGE:0.5:144:1460"; #144*60secs=144mins, 1460*144mins = 3504.0hrs = 146.0days + +system( "$rrdexe create $rrdfile $rrdCreateLine" ); \ No newline at end of file diff --git a/opt/inverter/inverter.php b/opt/inverter/inverter.php new file mode 100644 index 0000000..53ff8f3 --- /dev/null +++ b/opt/inverter/inverter.php @@ -0,0 +1,44 @@ +#!/usr/bin/php + /dev/null'; + +printf("Be awake between %s and %s\n", $sWake, $sSleep); +$fWake = getHour($sWake); +$fSleep = getHour($sSleep); +chdir('/opt/inverter/'); + +while (true) { + // Check for current need to be awake + $fNow = getHour(); + if (!($bAwake = $fNow >= $fWake)) { + printf("[%s] Too early to wake!\n", date('r')); + } else if ($bSleep = $fNow >= $fSleep) { + printf("[%s] Time to sleep!\n", date('r')); + } + + if ($bAwake && !$bSleep) { + // Need to be awake now + printf("[%s] Running task\n", date('r')); + system($sTask); + printf("[%s] Task ended\n", date('r')); + } else { + // Don't need to be awake now + if (!$bAwake) { + // Sleep untill wake time + $iTime = strtotime($sWake); + } else { + // Sleep untill next day wake time + $iTime = strtotime(sprintf('%s + 1 day', $sWake)); + } + printf("[%s] Sleeping untill: %s\n", date('r'), date('r', $iTime)); + time_sleep_until($iTime); + } +} +echo "\n"; + +function getHour($sTime = null) { + $iTime = $sTime === null ? time() : strtotime($sTime); + return date('H', $iTime) + date('i', $iTime) / 60; +} diff --git a/opt/inverter/inverter.pl b/opt/inverter/inverter.pl new file mode 100644 index 0000000..7da323f --- /dev/null +++ b/opt/inverter/inverter.pl @@ -0,0 +1,1582 @@ +#!/usr/bin/perl -w +# +# AS AT 04May2011 +# +# inverter.pl - polls data from the RS232 port on certain inverters and outputs data to a csv file +# & optionally to http://pvoutput.org, depending on the configuration file settings (config.ini). +# +# Usage examples: +# perl inverter.pl +# perl inverter.pl "COM1" +# perl inverter.pl "/dev/ttyS0" +# +# Arguments: +# $ARGV[0] = OPTIONAL port name (eg: "COM1") if have >1 inverter. See: config.ini for defaults. +# +# Output Filenames +# * inverter_[serial#]_YYYYMMDD.csv +# * inverter_err_[serial#]_YYYYMM.csv +# * inverter_[serial#].rrd +# +####################################################################### +# +# (c) 2010-2011 jinba @ jinba-ittai.net +# Licensed under the GNU GPL version 2 +# +# + editions by shell_l_d: +# + edited to work with ActivePerl (Windows) too +# + added version/firmware checking & combined CMS2k & CMS10k (parseData) versions +# + added data format checking (per findings from JinbaIttai & Ingmar) +# + added %HoH, %HASH +# + added writeToPort() & added warnings to readbuf() +# + edited calc of sleep so dont have to keep DATAPOLL_FREQ_SECS set to 60. +# + edited pvoutput code so dont have to keep DATAPOLL_FREQ_SECS set to 60. +# + added DESCR to $HoH hash of hashes & used in parseDataFmt() +# + added check if ( $seconds > 0 ) before 'sleep $seconds' +# + added DESCR to parseData() & replaced die with warning in writeToFile() +# + added %HoHparams, closeSerialPort(), parseParamFmt(), parseParam() +# + renamed %HoH items: ETOTAL, HTOTAL, UNK1 through UNK9 +# + edited etotal & htotal calcs in WriteToFile() +# + renamed DIVIDEBY to MULTIPLY & edited their values & replaced / with * in parseParam() & parseData() +# + added rrdtool code +# + edited REINIT_DEFAULT & writeReadBuffer() +# + added warning to closeSerialPort() & edited REINIT_DEFAULT line in writeReadBuffer() +# + added getDate_YYYYMM(), getErrFileName(). +# + edited parseData(). +# + implemented AppConfig & a configuration file (config.ini). +# +# + editions by mmcdon23: +# + added getDateTime_HHMM() & getDate_YYYYMMDD() +# + added date & time strings to writeToFile() +# + added "_YYYYMMDD.csv" to logfile name (suffix) +# + edited parseData() by using 0 for ETODAY in morning if hasn't reset yet +# + moved sleep from parseData() to main +# + moved code from initialise, readbuf & main to writeReadBuffer() +# & altered to continue reading until read pattern matched. +# + added pvoutput code to main & only send to PVoutput every 5 or 10th min per PVOUTPUT_FREQ constant. +# + added calc of sleep seconds to stop the script creeping away from the 00 minute mark. +# + edited added if statement to parseParamFmt() & parseDataFmt() & around the calls to them +# +# + editions by nigol2: +# + added optional port argument in case have more than 1 inverter +# + added serial# to logfile name in case have more than 1 inverter +# +# + editions by slampt: +# + uncommented & edited rrdtool (graphing) code in writeToFile() with help from JinbaIttai +# +####################################################################### +# +# Required to be installed: +# * perl +# * perl AppConfig module +# * perl Win32::SerialPort module (Windows) +# * perl Device::SerialPort module (Unix/Linux) +# +####################################################################### + +#use strict; +use warnings; +use AppConfig; # used to read from a config file + +$| = 1; # don't let Perl buffer I/O + +####################################################################### +# +# Define constants & variables (most from config file) +# + +# create a new AppConfig object & auto-define all variables +my $config = AppConfig->new(); + +# define new variables +$config->define( "flags_debug!" ); +$config->define( "flags_use_pvoutput!" ); +$config->define( "flags_use_rrdtool!" ); +$config->define( "secs_datapoll_freq=s" ); +$config->define( "secs_pvoutput_freq=s" ); +$config->define( "secs_timeout=s" ); +$config->define( "secs_reinit=s" ); +$config->define( "paths_windows=s" ); +$config->define( "paths_other=s" ); +$config->define( "scripts_pvoutput=s" ); +$config->define( "scripts_create_rrd=s" ); +$config->define( "scripts_rrdtool_exe_win=s" ); +$config->define( "scripts_rrdtool_exe_oth=s" ); +$config->define( "serial_baud=s" ); +$config->define( "serial_port_win=s" ); +$config->define( "serial_port_oth=s" ); +$config->define( "serial_parity=s" ); +$config->define( "serial_databits=s" ); +$config->define( "serial_stopbits=s" ); +$config->define( "serial_handshake=s" ); +$config->define( "serial_datatype=s" ); +$config->define( "hex_data_to_follow_index=s" ); +$config->define( "hex_capacity_index=s" ); +$config->define( "hex_capacity_length=s" ); +$config->define( "hex_firmware_index=s" ); +$config->define( "hex_firmware_length=s" ); +$config->define( "hex_model_index=s" ); +$config->define( "hex_model_length=s" ); +$config->define( "hex_manuf_index=s" ); +$config->define( "hex_manuf_length=s" ); +$config->define( "hex_serial_index=s" ); +$config->define( "hex_serial_length=s" ); +$config->define( "hex_other_index=s" ); +$config->define( "hex_other_length=s" ); +$config->define( "hex_confserial_index=s" ); +$config->define( "sendhex_initialise=s" ); +$config->define( "sendhex_serial=s" ); +$config->define( "sendhex_conf_serial1=s" ); +$config->define( "sendhex_conf_serial2=s" ); +$config->define( "sendhex_version=s" ); +$config->define( "sendhex_paramfmt=s" ); +$config->define( "sendhex_param=s" ); +$config->define( "sendhex_datafmt=s" ); +$config->define( "sendhex_data=s" ); +$config->define( "recvhex_serial=s" ); +$config->define( "recvhex_conf_serial=s" ); +$config->define( "recvhex_version=s" ); +$config->define( "recvhex_paramfmt=s" ); +$config->define( "recvhex_param=s" ); +$config->define( "recvhex_datafmt=s" ); +$config->define( "recvhex_data=s" ); +$config->define( "param_vpvstart_hexcode=s" ); +$config->define( "param_vpvstart_multiply=s" ); +$config->define( "param_vpvstart_measure=s" ); +$config->define( "param_vpvstart_index=s" ); +$config->define( "param_vpvstart_descr=s" ); +$config->define( "param_tstart_hexcode=s" ); +$config->define( "param_tstart_multiply=s" ); +$config->define( "param_tstart_measure=s" ); +$config->define( "param_tstart_index=s" ); +$config->define( "param_tstart_descr=s" ); +$config->define( "param_vacmin_hexcode=s" ); +$config->define( "param_vacmin_multiply=s" ); +$config->define( "param_vacmin_measure=s" ); +$config->define( "param_vacmin_index=s" ); +$config->define( "param_vacmin_descr=s" ); +$config->define( "param_vacmax_hexcode=s" ); +$config->define( "param_vacmax_multiply=s" ); +$config->define( "param_vacmax_measure=s" ); +$config->define( "param_vacmax_index=s" ); +$config->define( "param_vacmax_descr=s" ); +$config->define( "param_facmin_hexcode=s" ); +$config->define( "param_facmin_multiply=s" ); +$config->define( "param_facmin_measure=s" ); +$config->define( "param_facmin_index=s" ); +$config->define( "param_facmin_descr=s" ); +$config->define( "param_facmax_hexcode=s" ); +$config->define( "param_facmax_multiply=s" ); +$config->define( "param_facmax_measure=s" ); +$config->define( "param_facmax_index=s" ); +$config->define( "param_facmax_descr=s" ); +$config->define( "param_zacmax_hexcode=s" ); +$config->define( "param_zacmax_multiply=s" ); +$config->define( "param_zacmax_measure=s" ); +$config->define( "param_zacmax_index=s" ); +$config->define( "param_zacmax_descr=s" ); +$config->define( "param_dzac_hexcode=s" ); +$config->define( "param_dzac_multiply=s" ); +$config->define( "param_dzac_measure=s" ); +$config->define( "param_dzac_index=s" ); +$config->define( "param_dzac_descr=s" ); +$config->define( "data_temp_hexcode=s" ); +$config->define( "data_temp_multiply=s" ); +$config->define( "data_temp_measure=s" ); +$config->define( "data_temp_index=s" ); +$config->define( "data_temp_descr=s" ); +$config->define( "data_vpv1_hexcode=s" ); +$config->define( "data_vpv1_multiply=s" ); +$config->define( "data_vpv1_measure=s" ); +$config->define( "data_vpv1_index=s" ); +$config->define( "data_vpv1_descr=s" ); +$config->define( "data_vpv2_hexcode=s" ); +$config->define( "data_vpv2_multiply=s" ); +$config->define( "data_vpv2_measure=s" ); +$config->define( "data_vpv2_index=s" ); +$config->define( "data_vpv2_descr=s" ); +$config->define( "data_vpv3_hexcode=s" ); +$config->define( "data_vpv3_multiply=s" ); +$config->define( "data_vpv3_measure=s" ); +$config->define( "data_vpv3_index=s" ); +$config->define( "data_vpv3_descr=s" ); +$config->define( "data_ipv1_hexcode=s" ); +$config->define( "data_ipv1_multiply=s" ); +$config->define( "data_ipv1_measure=s" ); +$config->define( "data_ipv1_index=s" ); +$config->define( "data_ipv1_descr=s" ); +$config->define( "data_ipv2_hexcode=s" ); +$config->define( "data_ipv2_multiply=s" ); +$config->define( "data_ipv2_measure=s" ); +$config->define( "data_ipv2_index=s" ); +$config->define( "data_ipv2_descr=s" ); +$config->define( "data_ipv3_hexcode=s" ); +$config->define( "data_ipv3_multiply=s" ); +$config->define( "data_ipv3_measure=s" ); +$config->define( "data_ipv3_index=s" ); +$config->define( "data_ipv3_descr=s" ); +$config->define( "data_etoday_hexcode=s" ); +$config->define( "data_etoday_multiply=s" ); +$config->define( "data_etoday_measure=s" ); +$config->define( "data_etoday_index=s" ); +$config->define( "data_etoday_descr=s" ); +$config->define( "data_vpv_hexcode=s" ); +$config->define( "data_vpv_multiply=s" ); +$config->define( "data_vpv_measure=s" ); +$config->define( "data_vpv_index=s" ); +$config->define( "data_vpv_descr=s" ); +$config->define( "data_ipv3_hexcode=s" ); +$config->define( "data_ipv3_multiply=s" ); +$config->define( "data_ipv3_measure=s" ); +$config->define( "data_ipv3_index=s" ); +$config->define( "data_ipv3_descr=s" ); +$config->define( "data_iac_hexcode=s" ); +$config->define( "data_iac_multiply=s" ); +$config->define( "data_iac_measure=s" ); +$config->define( "data_iac_index=s" ); +$config->define( "data_iac_descr=s" ); +$config->define( "data_vac_hexcode=s" ); +$config->define( "data_vac_multiply=s" ); +$config->define( "data_vac_measure=s" ); +$config->define( "data_vac_index=s" ); +$config->define( "data_vac_descr=s" ); +$config->define( "data_fac_hexcode=s" ); +$config->define( "data_fac_multiply=s" ); +$config->define( "data_fac_measure=s" ); +$config->define( "data_fac_index=s" ); +$config->define( "data_fac_descr=s" ); +$config->define( "data_pac_hexcode=s" ); +$config->define( "data_pac_multiply=s" ); +$config->define( "data_pac_measure=s" ); +$config->define( "data_pac_index=s" ); +$config->define( "data_pac_descr=s" ); +$config->define( "data_zac_hexcode=s" ); +$config->define( "data_zac_multiply=s" ); +$config->define( "data_zac_measure=s" ); +$config->define( "data_zac_index=s" ); +$config->define( "data_zac_descr=s" ); +$config->define( "data_etotalh_hexcode=s" ); +$config->define( "data_etotalh_multiply=s" ); +$config->define( "data_etotalh_measure=s" ); +$config->define( "data_etotalh_index=s" ); +$config->define( "data_etotalh_descr=s" ); +$config->define( "data_etotall_hexcode=s" ); +$config->define( "data_etotall_multiply=s" ); +$config->define( "data_etotall_measure=s" ); +$config->define( "data_etotall_index=s" ); +$config->define( "data_etotall_descr=s" ); +$config->define( "data_htotalh_hexcode=s" ); +$config->define( "data_htotalh_multiply=s" ); +$config->define( "data_htotalh_measure=s" ); +$config->define( "data_htotalh_index=s" ); +$config->define( "data_htotalh_descr=s" ); +$config->define( "data_htotall_hexcode=s" ); +$config->define( "data_htotall_multiply=s" ); +$config->define( "data_htotall_measure=s" ); +$config->define( "data_htotall_index=s" ); +$config->define( "data_htotall_descr=s" ); +$config->define( "data_mode_hexcode=s" ); +$config->define( "data_mode_multiply=s" ); +$config->define( "data_mode_measure=s" ); +$config->define( "data_mode_index=s" ); +$config->define( "data_mode_descr=s" ); +$config->define( "data_errgv_hexcode=s" ); +$config->define( "data_errgv_multiply=s" ); +$config->define( "data_errgv_measure=s" ); +$config->define( "data_errgv_index=s" ); +$config->define( "data_errgv_descr=s" ); +$config->define( "data_errgf_hexcode=s" ); +$config->define( "data_errgf_multiply=s" ); +$config->define( "data_errgf_measure=s" ); +$config->define( "data_errgf_index=s" ); +$config->define( "data_errgf_descr=s" ); +$config->define( "data_errgz_hexcode=s" ); +$config->define( "data_errgz_multiply=s" ); +$config->define( "data_errgz_measure=s" ); +$config->define( "data_errgz_index=s" ); +$config->define( "data_errgz_descr=s" ); +$config->define( "data_errtemp_hexcode=s" ); +$config->define( "data_errtemp_multiply=s" ); +$config->define( "data_errtemp_measure=s" ); +$config->define( "data_errtemp_index=s" ); +$config->define( "data_errtemp_descr=s" ); +$config->define( "data_errpv1_hexcode=s" ); +$config->define( "data_errpv1_multiply=s" ); +$config->define( "data_errpv1_measure=s" ); +$config->define( "data_errpv1_index=s" ); +$config->define( "data_errpv1_descr=s" ); +$config->define( "data_errgfc1_hexcode=s" ); +$config->define( "data_errgfc1_multiply=s" ); +$config->define( "data_errgfc1_measure=s" ); +$config->define( "data_errgfc1_index=s" ); +$config->define( "data_errgfc1_descr=s" ); +$config->define( "data_errmode_hexcode=s" ); +$config->define( "data_errmode_multiply=s" ); +$config->define( "data_errmode_measure=s" ); +$config->define( "data_errmode_index=s" ); +$config->define( "data_errmode_descr=s" ); + +# fill variables by reading configuration file +$config->file( "config.ini" ) || die "FAILED to open and/or read config file: config.ini\n"; + +if ($config->flags_debug) { + print "debug=" . $config->flags_debug ; + print ", use_pvoutput=" . $config->flags_use_pvoutput ; + print ", use_rrdtool=" . $config->flags_use_rrdtool . "\n" ; +} + +# +# inverter parameter format codes (hash of hashes) +# +%HoHparams = ( + 'VPV-START' => { + HEXCODE => $config->param_vpvstart_hexcode, + MULTIPLY => $config->param_vpvstart_multiply, + MEAS => $config->param_vpvstart_measure, + INDEX => $config->param_vpvstart_index, + VALUE => 0, + DESCR => $config->param_vpvstart_descr, + }, + 'T-START' => { + HEXCODE => $config->param_tstart_hexcode, + MULTIPLY => $config->param_tstart_multiply, + MEAS => $config->param_tstart_measure, + INDEX => $config->param_tstart_index, + VALUE => 0, + DESCR => $config->param_tstart_descr, + }, + 'VAC-MIN' => { + HEXCODE => $config->param_vacmin_hexcode, + MULTIPLY => $config->param_vacmin_multiply, + MEAS => $config->param_vacmin_measure, + INDEX => $config->param_vacmin_index, + VALUE => 0, + DESCR => $config->param_vacmin_descr, + }, + 'VAC-MAX' => { + HEXCODE => $config->param_vacmax_hexcode, + MULTIPLY => $config->param_vacmax_multiply, + MEAS => $config->param_vacmax_measure, + INDEX => $config->param_vacmax_index, + VALUE => 0, + DESCR => $config->param_vacmax_descr, + }, + 'FAC-MIN' => { + HEXCODE => $config->param_facmin_hexcode, + MULTIPLY => $config->param_facmin_multiply, + MEAS => $config->param_facmin_measure, + INDEX => $config->param_facmin_index, + VALUE => 0, + DESCR => $config->param_facmin_descr, + }, + 'FAC-MAX' => { + HEXCODE => $config->param_facmax_hexcode, + MULTIPLY => $config->param_facmax_multiply, + MEAS => $config->param_facmax_measure, + INDEX => $config->param_facmax_index, + VALUE => 0, + DESCR => $config->param_facmax_descr, + }, + 'ZAC-MAX' => { + HEXCODE => $config->param_zacmax_hexcode, + MULTIPLY => $config->param_zacmax_multiply, + MEAS => $config->param_zacmax_measure, + INDEX => $config->param_zacmax_index, + VALUE => 0, + DESCR => $config->param_zacmax_descr, + }, + 'DZAC' => { + HEXCODE => $config->param_dzac_hexcode, + MULTIPLY => $config->param_dzac_multiply, + MEAS => $config->param_dzac_measure, + INDEX => $config->param_dzac_index, + VALUE => 0, + DESCR => $config->param_dzac_descr, + }, +); + +# +# inverter data format codes (hash of hashes) +# +%HoH = ( + TEMP => { + HEXCODE => $config->data_temp_hexcode, + MULTIPLY => $config->data_temp_multiply, + MEAS => $config->data_temp_measure, + INDEX => $config->data_temp_index, + VALUE => 0, + DESCR => $config->data_temp_descr, + }, + VPV1 => { + HEXCODE => $config->data_vpv1_hexcode, + MULTIPLY => $config->data_vpv1_multiply, + MEAS => $config->data_vpv1_measure, + INDEX => $config->data_vpv1_index, + VALUE => 0, + DESCR => $config->data_vpv1_descr, + }, + VPV2 => { + HEXCODE => $config->data_vpv2_hexcode, + MULTIPLY => $config->data_vpv2_multiply, + MEAS => $config->data_vpv2_measure, + INDEX => $config->data_vpv2_index, + VALUE => 0, + DESCR => $config->data_vpv2_descr, + }, + VPV3 => { + HEXCODE => $config->data_vpv3_hexcode, + MULTIPLY => $config->data_vpv3_multiply, + MEAS => $config->data_vpv3_measure, + INDEX => $config->data_vpv3_index, + VALUE => 0, + DESCR => $config->data_vpv3_descr, + }, + IPV1 => { + HEXCODE => $config->data_ipv1_hexcode, + MULTIPLY => $config->data_ipv1_multiply, + MEAS => $config->data_ipv1_measure, + INDEX => $config->data_ipv1_index, + VALUE => 0, + DESCR => $config->data_ipv1_descr, + }, + IPV2 => { + HEXCODE => $config->data_ipv2_hexcode, + MULTIPLY => $config->data_ipv2_multiply, + MEAS => $config->data_ipv2_measure, + INDEX => $config->data_ipv2_index, + VALUE => 0, + DESCR => $config->data_ipv2_descr, + }, + IPV3 => { + HEXCODE => $config->data_ipv3_hexcode, + MULTIPLY => $config->data_ipv3_multiply, + MEAS => $config->data_ipv3_measure, + INDEX => $config->data_ipv3_index, + VALUE => 0, + DESCR => $config->data_ipv3_descr, + }, + ETODAY => { + HEXCODE => $config->data_etoday_hexcode, + MULTIPLY => $config->data_etoday_multiply, + MEAS => $config->data_etoday_measure, + INDEX => $config->data_etoday_index, + VALUE => 0, + DESCR => $config->data_etoday_descr, + }, + VPV => { + HEXCODE => $config->data_vpv_hexcode, + MULTIPLY => $config->data_vpv_multiply, + MEAS => $config->data_vpv_measure, + INDEX => $config->data_vpv_index, + VALUE => 0, + DESCR => $config->data_vpv_descr, + }, + IAC => { + HEXCODE => $config->data_iac_hexcode, + MULTIPLY => $config->data_iac_multiply, + MEAS => $config->data_iac_measure, + INDEX => $config->data_iac_index, + VALUE => 0, + DESCR => $config->data_iac_descr, + }, + VAC => { + HEXCODE => $config->data_vac_hexcode, + MULTIPLY => $config->data_vac_multiply, + MEAS => $config->data_vac_measure, + INDEX => $config->data_vac_index, + VALUE => 0, + DESCR => $config->data_vac_descr, + }, + FAC => { + HEXCODE => $config->data_fac_hexcode, + MULTIPLY => $config->data_fac_multiply, + MEAS => $config->data_fac_measure, + INDEX => $config->data_fac_index, + VALUE => 0, + DESCR => $config->data_fac_descr, + }, + PAC => { + HEXCODE => $config->data_pac_hexcode, + MULTIPLY => $config->data_pac_multiply, + MEAS => $config->data_pac_measure, + INDEX => $config->data_pac_index, + VALUE => 0, + DESCR => $config->data_pac_descr, + }, + ZAC => { + HEXCODE => $config->data_zac_hexcode, + MULTIPLY => $config->data_zac_multiply, + MEAS => $config->data_zac_measure, + INDEX => $config->data_zac_index, + VALUE => 0, + DESCR => $config->data_zac_descr, + }, + ETOTALH => { + HEXCODE => $config->data_etotalh_hexcode, + MULTIPLY => $config->data_etotalh_multiply, + MEAS => $config->data_etotalh_measure, + INDEX => $config->data_etotalh_index, + VALUE => 0, + DESCR => $config->data_etotalh_descr, + }, + ETOTALL => { + HEXCODE => $config->data_etotall_hexcode, + MULTIPLY => $config->data_etotall_multiply, + MEAS => $config->data_etotall_measure, + INDEX => $config->data_etotall_index, + VALUE => 0, + DESCR => $config->data_etotall_descr, + }, + HTOTALH => { + HEXCODE => $config->data_htotalh_hexcode, + MULTIPLY => $config->data_htotalh_multiply, + MEAS => $config->data_htotalh_measure, + INDEX => $config->data_htotalh_index, + VALUE => $config->data_htotalh_index, + DESCR => $config->data_htotalh_descr, + }, + HTOTALL => { + HEXCODE => $config->data_htotall_hexcode, + MULTIPLY => $config->data_htotall_multiply, + MEAS => $config->data_htotall_measure, + INDEX => $config->data_htotall_index, + VALUE => 0, + DESCR => $config->data_htotall_descr, + }, + MODE => { + HEXCODE => $config->data_mode_hexcode, + MULTIPLY => $config->data_mode_multiply, + MEAS => $config->data_mode_measure, + INDEX => $config->data_mode_index, + VALUE => 0, + DESCR => $config->data_mode_descr, + }, + ERR_GV => { + HEXCODE => $config->data_errgv_hexcode, + MULTIPLY => $config->data_errgv_multiply, + MEAS => $config->data_errgv_measure, + INDEX => $config->data_errgv_index, + VALUE => 0, + DESCR => $config->data_errgv_descr, + }, + ERR_GF => { + HEXCODE => $config->data_errgf_hexcode, + MULTIPLY => $config->data_errgf_multiply, + MEAS => $config->data_errgf_measure, + INDEX => $config->data_errgf_index, + VALUE => 0, + DESCR => $config->data_errgf_descr, + }, + ERR_GZ => { + HEXCODE => $config->data_errgz_hexcode, + MULTIPLY => $config->data_errgz_multiply, + MEAS => $config->data_errgz_measure, + INDEX => $config->data_errgz_index, + VALUE => 0, + DESCR => $config->data_errgz_descr, + }, + ERR_TEMP => { + HEXCODE => $config->data_errtemp_hexcode, + MULTIPLY => $config->data_errtemp_multiply, + MEAS => $config->data_errtemp_measure, + INDEX => $config->data_errtemp_index, + VALUE => 0, + DESCR => $config->data_errtemp_descr, + }, + ERR_PV1 => { + HEXCODE => $config->data_errpv1_hexcode, + MULTIPLY => $config->data_errpv1_multiply, + MEAS => $config->data_errpv1_measure, + INDEX => $config->data_errpv1_index, + VALUE => 0, + DESCR => $config->data_errpv1_descr, + }, + ERR_GFC1 => { + HEXCODE => $config->data_errgfc1_hexcode, + MULTIPLY => $config->data_errgfc1_multiply, + MEAS => $config->data_errgfc1_measure, + INDEX => $config->data_errgfc1_index, + VALUE => 0, + DESCR => $config->data_errgfc1_descr, + }, + ERR_MODE => { + HEXCODE => $config->data_errmode_hexcode, + MULTIPLY => $config->data_errmode_multiply, + MEAS => $config->data_errmode_measure, + INDEX => $config->data_errmode_index, + VALUE => 0, + DESCR => $config->data_errmode_descr, + }, + UNK10 => { + HEXCODE => "7f", + MULTIPLY => 1, + MEAS => "", + INDEX => -1, + VALUE => 0, + DESCR => "Unknown", + }, + # ---------------- UNKNOWN ---------------------- + UNK11 => { + HEXCODE => "zz", # unknown + MULTIPLY => 0.1, + MEAS => "", + INDEX => -1, + VALUE => 0, + DESCR => "Unknown", + }, + UNK12 => { + HEXCODE => "zz", # unknown + MULTIPLY => 0.1, + MEAS => "", + INDEX => -1, + VALUE => 0, + DESCR => "Unknown", + }, + UNK13 => { + HEXCODE => "zz", # unknown + MULTIPLY => 0.1, + MEAS => "", + INDEX => -1, + VALUE => 0, + DESCR => "Unknown", + }, + UNK14 => { + HEXCODE => "zz", # unknown + MULTIPLY => 0.1, + MEAS => "", + INDEX => -1, + VALUE => 0, + DESCR => "Unknown", + }, + IDC1 => { + HEXCODE => "zz", # unknown + MULTIPLY => 0.1, + MEAS => "A", + INDEX => -1, + VALUE => 0, + DESCR => "", + }, + IDC2 => { + HEXCODE => "zz", # unknown + MULTIPLY => 0.1, + MEAS => "A", + INDEX => -1, + VALUE => 0, + DESCR => "", + }, + IDC3 => { + HEXCODE => "zz", # unknown + MULTIPLY => 0.1, + MEAS => "A", + INDEX => -1, + VALUE => 0, + DESCR => "", + }, + IAC1 => { + HEXCODE => "zz", # unknown + MULTIPLY => 0.1, + MEAS => "A", + INDEX => -1, + VALUE => 0, + DESCR => "", + }, + VAC1 => { + HEXCODE => "zz", # unknown + MULTIPLY => 0.1, + MEAS => "V", + INDEX => -1, + VALUE => 0, + DESCR => "", + }, + FAC1 => { + HEXCODE => "zz", # unknown + MULTIPLY => 0.01, + MEAS => "Hz", + INDEX => -1, + VALUE => 0, + DESCR => "", + }, + PDC1 => { + HEXCODE => "zz", # unknown + MULTIPLY => 1, + MEAS => "W", + INDEX => -1, + VALUE => 0, + DESCR => "", + }, + IAC2 => { + HEXCODE => "zz", # unknown + MULTIPLY => 0.1, + MEAS => "A", + INDEX => -1, + VALUE => 0, + DESCR => "", + }, + VAC2 => { + HEXCODE => "zz", # unknown + MULTIPLY => 0.1, + MEAS => "V", + INDEX => -1, + VALUE => 0, + DESCR => "", + }, + FAC2 => { + HEXCODE => "zz", # unknown + MULTIPLY => 0.01, + MEAS => "Hz", + INDEX => -1, + VALUE => 0, + DESCR => "", + }, + PDC2 => { + HEXCODE => "zz", # unknown + MULTIPLY => 1, + MEAS => "W", + INDEX => -1, + VALUE => 0, + DESCR => "", + }, + IAC3 => { + HEXCODE => "zz", # unknown + MULTIPLY => 0.1, + MEAS => "A", + INDEX => -1, + VALUE => 0, + DESCR => "", + }, + VAC3 => { + HEXCODE => "zz", # unknown + MULTIPLY => 0.1, + MEAS => "V", + INDEX => -1, + VALUE => 0, + DESCR => "", + }, + FAC3 => { + HEXCODE => "zz", # unknown + MULTIPLY => 0.01, + MEAS => "Hz", + INDEX => -1, + VALUE => 0, + DESCR => "", + }, + PDC3 => { + HEXCODE => "zz", # unknown + MULTIPLY => 1, + MEAS => "W", + INDEX => -1, + VALUE => 0, + DESCR => "", + }, +# ---------------- UNKNOWN ---------------------- +# PVP1 "W", "PV 1 voltage" +# PVP2 "W", "PV 2 voltage" +# PVP3 "W", "PV 3 voltage" +# IAC1 "A", "PV 1 Grid current" +# IAC2 "A", "PV 2 Grid current" +# IAC3 "A", "PV 3 Grid current" +# VAC1 "V", "PV 1 Grid voltage" +# VAC2 "V", "PV 2 Grid voltage" +# VAC3 "V", "PV 3 Grid voltage" +# TEMP1 "deg C", "External temperature sensor 1" +# TEMP2 "deg C", "External temperature sensor 2" +# RAD1 "W/m2", "Irradiance sensor 1" +# RAD2 "W/m2", "Irradiance sensor 2" +); + + +# +# inverter version information (hash) +# +%HASH = ( + CAPACITY => "", + FIRMWARE => "", + MODEL => "", + MANUF => "", + SERIAL => "", + OTHER => "", +); + +####################################################################### + + + +####################################################################### +# +# Open serial/usb/bluetooth port depending on Operating System +# +sub initialiseSerialPort() { + + print "Initialise Serial Port... "; + if ($^O eq 'MSWin32') { # Win32 (ActivePerl) + eval "use Win32::SerialPort"; + $port = $ARGV[0] || $config->serial_port_win; + + # Open the serial port + $serial = Win32::SerialPort->new ($port, 0, '') || die "Can\'t open $port: $!"; + } + else { # Unix/Linux/other + eval "use Device::SerialPort"; + $port = $ARGV[0] || $config->serial_port_oth; + + # Open the serial port + $serial = Device::SerialPort->new ($port, 0, '') || die "Can\'t open $port: $!"; + } + print "port = $port\n"; + + # + # Open the serial port + # + $serial->error_msg(1); # use built-in hardware error messages like "Framing Error" + $serial->user_msg(1); # use built-in function messages like "Waiting for ..." + #$serial->baudrate($config->serial_baud) || die 'fail setting baudrate, try -b option'; + $serial->{'_L_BAUD'}{$config->serial_baud} = $config->serial_baud; + $serial->parity($config->serial_parity) || die 'fail setting parity'; + $serial->databits($config->serial_databits) || die 'fail setting databits'; + $serial->stopbits($config->serial_stopbits) || die 'fail setting stopbits'; + $serial->handshake($config->serial_handshake) || die 'fail setting handshake'; + $serial->datatype($config->serial_datatype) || die 'fail setting datatype'; + $serial->write_settings || die 'could not write settings'; + $serial->read_char_time(0); # don't wait for each character + $serial->read_const_time(1000); # 1 second per unfulfilled "read" call +} + +####################################################################### +# +# Close serial/usb/bluetooth port +# +sub closeSerialPort() { + $serial->close || warn "*** WARNING Close port failed, connection may have died.\n"; + undef $serial; +} + +####################################################################### +# +# Trim function to remove whitespace from start and end of a string +# +sub trim($) { + my $string = shift; + $string =~ s/^\s+//; + $string =~ s/\s+$//; + return $string; +} + +####################################################################### +# +# Turn raw data from the inverter into a hex string +# +sub convRawToHex() { + my $pstring = shift; + my $hstring = unpack ("H*",$pstring); + return $hstring; +} + +####################################################################### +# +# Turn hex string into raw data for transmission to the inverter +# +sub convHexToRaw() { + my $hstring = shift; + my $pstring = pack (qq{H*},qq{$hstring}); + return $pstring; +} + +####################################################################### +# +# Return Date & Time in format: "DD/MM/YYYY HH:MM:SS" +# +sub getDateTime { + local($time_since_epoch) = @_; + local($sec,$min,$hour,$dayOfMth,$monthOffset,$yearOffset,$dayOfWk,$dayOfYr,$isDST) = localtime($time_since_epoch); + local($year) = 1900 + $yearOffset; + local($month) = 1 + $monthOffset; + return sprintf("%.2d/%.2d/%d %.2d:%.2d:%.2d", $dayOfMth, $month, $year, $hour, $min, $sec); +} + +####################################################################### +# +# Return Date in format: "YYYYMMDD" +# +sub getDate_YYYYMMDD { + local($time_since_epoch) = @_; + local($sec,$min,$hour,$dayOfMth,$monthOffset,$yearOffset,$dayOfWk,$dayOfYr,$isDST) = localtime($time_since_epoch); + local($year) = 1900 + $yearOffset; + local($month) = 1 + $monthOffset; + return sprintf("%d%.2d%.2d", $year, $month, $dayOfMth); +} + +####################################################################### +# +# Return Date in format: "YYYYMM" +# +sub getDate_YYYYMM { + local($time_since_epoch) = @_; + local($sec,$min,$hour,$dayOfMth,$monthOffset,$yearOffset,$dayOfWk,$dayOfYr,$isDST) = localtime($time_since_epoch); + local($year) = 1900 + $yearOffset; + local($month) = 1 + $monthOffset; + return sprintf("%d%.2d", $year, $month); +} + +####################################################################### +# +# Return Time in format: "HH:MM" +# +sub getTime_HHMM { + local($time_since_epoch) = @_; + local($sec,$min,$hour,$dayOfMth,$monthOffset,$yearOffset,$dayOfWk,$dayOfYr,$isDST) = localtime($time_since_epoch); + return sprintf("%.2d:%.2d", $hour, $min); +} + +####################################################################### +# +# Return LogFile Name: [path]/inverter_[serial#]_[yyyymmdd].csv +# +sub getLogFileName { + my $logfile = ""; + + # + # set path + # + if ($^O eq 'MSWin32') { # Win32 (ActivePerl) + $logfile = $config->paths_windows; + } + else { # Unix/Linux/other + $logfile = $config->paths_other; + } + + # + # append filename + # + $logfile .= "/inverter_" . $HASH{SERIAL} . "_" . getDate_YYYYMMDD(time) . ".csv"; + return $logfile; +} + +####################################################################### +# +# Return ErrFile Name: [path]/inverter_err_[serial#]_[yyyymm].log +# +sub getErrFileName { + my $errfile = ""; + + # + # set path + # + if ($^O eq 'MSWin32') { # Win32 (ActivePerl) + $errfile = $config->paths_windows; + } + else { # Unix/Linux/other + $errfile = $config->paths_other; + } + + # + # append filename + # + $errfile .= "/inverter_err_" . $HASH{SERIAL} . "_" . getDate_YYYYMM(time) . ".log"; + return $errfile; +} + +####################################################################### +# +# Return RRD File Name: [path]/inverter_[serial#].rrd +# +sub getRrdFileName { + my $rrdfile = ""; + + # + # set path + # + if ($^O eq 'MSWin32') { # Win32 (ActivePerl) + $rrdfile = $config->paths_windows; + } + else { # Unix/Linux/other + $rrdfile = $config->paths_other; + } + + # + # append filename + # + $rrdfile .= "/inverter_" . $HASH{SERIAL} . ".rrd"; + return $rrdfile; +} + +####################################################################### +# +# Write to the port (the inverter is on) & warn if it fails +# +sub writeToPort() { + my $writeStr = shift; + my $countOut = $serial->write($writeStr); + warn "*** write failed ( $countOut ) ***\n" unless ($countOut); + warn "*** write incomplete ( $countOut ) ***\n" if ( $countOut != length($writeStr) ); + return $countOut; +} + +####################################################################### +# +# Write to serial port then read result from buffer +# until expected read response received or timeout exceeded +# +sub writeReadBuffer() { + my $writeString = shift; + my $readPattern = shift; + my $chars=0; + my $buffer=""; + my $buffer2="x"; + my $timeout = $config->secs_timeout; + my $reinit = 0; + + if ($config->flags_debug) { + print "writeReadBuffer: writeString=$writeString\n"; + print "writeReadBuffer: readPattern=$readPattern\n"; + } + + # + # Write to (Serial) Port + # + &writeToPort(&convHexToRaw($writeString)); + + # sleep for dodgy cables (eg beginner soldering or usb converters) + # sleep 2; + + # + # Read response from buffer until either expected response received or timeout reached + # + while ( $timeout > 0 ) { + + my ($countIn,$stringIn) = $serial->read(255); # will read _up to_ 255 chars + if ($countIn > 0) { + + if ($config->flags_debug) { + print "writeReadBuffer: saw..." . &convRawToHex($stringIn) . "\n"; + } + + $chars += $countIn; + $buffer .= $stringIn; + $hexBuffer = &convRawToHex($buffer); + + # + # Check to see if expected read response is in the $buffer, say "last" if we find it + # + if ( $hexBuffer =~ /$readPattern/ ) { + ($buffer2) = ( $hexBuffer =~ /($readPattern.*$)/ ); + if ($config->flags_debug) { + print "writeReadBuffer: found=$buffer2\n"; + } + last; + } + + } + else { + $timeout--; + } + + # + # check if timeout was reached + # + if ($timeout==0) { + print "Re-Init...\n"; + &closeSerialPort; + &initialiseSerialPort; + + $timeout = $config->secs_timeout; + $reinit++; + + print "Waited " . $config->secs_timeout . " seconds and never saw $readPattern\n"; + #die "Waited " . $config->secs_timeout . " seconds and never saw $readPattern\n"; + } + + # + # check if reinitialise port timeout was reached, if so die + # + if ($config->secs_reinit >= 0 && $reinit > $config->secs_reinit) { + &closeSerialPort; + die "REINIT MAX exceeded, aborted.\n"; + } + + } # end of while loop + + print "Recv <- $buffer2 \n"; + return $buffer2; +} + +####################################################################### +# +# Prepare the REQUEST_CONF_SERIAL packet +# grab serial# from input data, use it to create the response packet, incl checksum in format: +# RESPONSE_CONF_SERIAL_1 + $hexSerial + RESPONSE_CONF_SERIAL_2 + $hexReqChkSum +# +sub calcReqConfSerial() { + my $hexStr = shift; + my $hexSerial = substr($hexStr, $config->hex_confserial_index, $config->hex_serial_length); + my $hexReqRegex = $config->sendhex_conf_serial1 . $hexSerial . $config->sendhex_conf_serial2; + + # + # calculate hex checksum for the request + # + my $rawReq = &convHexToRaw( $hexReqRegex ); + my $rawReqChkSum = unpack ( "%C*", $rawReq ); + my $hexReqChkSum = sprintf ( "%04x ", $rawReqChkSum ); + + # + # join it all together to create the request + # + my $reqConfSerial = $hexReqRegex . $hexReqChkSum; + return($reqConfSerial); +} + +####################################################################### +# +# Initialise Inverter - handshake is done here +# +sub initialiseInverter() { + # + # step 1: Start initial handshake with inverter (reset network) + # + my $rawRequest = &convHexToRaw($config->sendhex_initialise); + print "Send -> req init inverter: " . $config->sendhex_initialise . "\n"; + &writeToPort($rawRequest); + + # + # step 2: request the serial number (query network) + # + print "Send -> req serial: " . $config->sendhex_serial . "\n"; + my $hexResponse = &writeReadBuffer($config->sendhex_serial,$config->recvhex_serial); + + # + # step 3: confirm the serial number + # + my $confSerialRequest = &calcReqConfSerial($hexResponse); + print "Send -> confirm serial: $confSerialRequest \n"; + my $hexResponse2 = &writeReadBuffer($confSerialRequest,$config->recvhex_conf_serial); +} + +####################################################################### +# +# Parse Version/Firmware Data - store in %HASH +# +sub parseVersData() { + print "* Version info:\n"; + my $hexData = shift; + my $asciiVers = ( pack ("H*", $hexData) ); + my $hexLength = length($hexData); + print "asciiVers=$asciiVers\n"; + + # + # convert portions of hex to ascii + # + if ( $config->hex_capacity_length > 0 && $config->hex_capacity_index + $config->hex_capacity_length < $hexLength ) { + $HASH{CAPACITY} = &trim( pack ("H*", substr($hexData, $config->hex_capacity_index, $config->hex_capacity_length)) ); + } + if ( $config->hex_firmware_length > 0 && $config->hex_firmware_index + $config->hex_firmware_length < $hexLength ) { + $HASH{FIRMWARE} = &trim( pack ("H*", substr($hexData, $config->hex_firmware_index, $config->hex_firmware_length)) ); + } + if ( $config->hex_model_length > 0 && $config->hex_model_index + $config->hex_model_length < $hexLength ) { + $HASH{MODEL} = &trim( pack ("H*", substr($hexData, $config->hex_model_index, $config->hex_model_length)) ); + } + if ( $config->hex_manuf_length > 0 && $config->hex_manuf_index + $config->hex_manuf_length < $hexLength ) { + $HASH{MANUF} = &trim( pack ("H*", substr($hexData, $config->hex_manuf_index, $config->hex_manuf_length)) ); + } + if ( $config->hex_serial_length > 0 && $config->hex_serial_index + $config->hex_serial_length < $hexLength ) { + $HASH{SERIAL} = &trim( pack ("H*", substr($hexData, $config->hex_serial_index, $config->hex_serial_length)) ); + } + if ( $config->hex_other_length > 0 && $config->hex_other_index + $config->hex_other_length < $hexLength ) { + $HASH{OTHER} = &trim( pack ("H*", substr($hexData, $config->hex_other_index, $config->hex_other_length)) ); + } + + # + # display version information (in sorted order) + # + for $key ( sort( keys ( %HASH ) ) ) { + printf "%-8s : %s\n", $key, $HASH{$key}; + } +} + +####################################################################### +# +# Parse Parameter Format +# based on $HoHparams{HEXCODE} & store index/position in $HoHparams{INDEX} +# +sub parseParamFmt() { + print "* Parameter Format:\n"; + my $hexData = shift; + if ($hexData eq "") { + print "n/a\n"; + return; + } + + # split hex string into an array of 2char hex strings + @d = ( $hexData =~ m/..?/g ); + + my $dataOffset = $config->hex_data_to_follow_index + 1; + my $dataToFollow = hex($d[$config->hex_data_to_follow_index]); + print "dataToFollow = hex($d[$config->hex_data_to_follow_index]) = $dataToFollow\n"; + + my $i = 0; + for $x ($dataOffset .. $dataOffset + $dataToFollow - 1) { + printf "%2s = %2s", $x, $d[$x] ; + for $key ( keys ( %HoHparams ) ) { + if ( $HoHparams{$key}{HEXCODE} eq $d[$x]) { + $HoHparams{$key}{INDEX} = $i; + printf " = %-10s = %2s = %s", $key, $HoHparams{$key}{INDEX}, $HoHparams{$key}{DESCR} ; + } + } + print "\n"; + $i++; + } +} + +####################################################################### +# +# Parse Parameters +# based on $HoHparams{INDEX} & store value in $HoHparams{VALUE} +# +sub parseParam() { + print "* Parameters:\n"; + my $hexData = shift; + my $dataToFollow = hex( substr( $hexData, $config->hex_data_to_follow_index*2, 2 ) ); + my $startIndex = ( $config->hex_data_to_follow_index + 1 )*2; + my $numOfChars = $dataToFollow * 2; + my $data = substr( $hexData, $startIndex, $numOfChars ); + + # split hex string into an array of 4char hex strings + @d = ( $data =~ m/..?.?.?/g ); + + # display data values - sort %HoH by INDEX + for $key ( sort {$HoHparams{$a}{INDEX} <=> $HoHparams{$b}{INDEX} } keys ( %HoHparams ) ) { + if ( $HoHparams{$key}{INDEX} ne "-1" ) { + $HoHparams{$key}{VALUE} = hex( $d[$HoHparams{$key}{INDEX}] ) * $HoHparams{$key}{MULTIPLY}; + printf "%-10s: %8s %-5s = %s \n", $key, $HoHparams{$key}{VALUE}, $HoHparams{$key}{MEAS}, $HoHparams{$key}{DESCR} ; + } + } +} + +####################################################################### +# +# Set Data Format Manually - if not sure of REQUEST_DATAFMT response break-up yet +# eg: CMS 10000 +# +sub setDataFmt2() { + print "* Data Format2:\n"; + $HoH{TEMP}{INDEX} = 0; + $HoH{VPV}{INDEX} = 1; + $HoH{VPV2}{INDEX} = 2; + $HoH{VPV3}{INDEX} = 3; + $HoH{IDC1}{INDEX} = 4; + $HoH{IDC2}{INDEX} = 5; + $HoH{IDC3}{INDEX} = 6; + $HoH{ETOTALH}{INDEX} = 7; + $HoH{ETOTALL}{INDEX} = 8; + $HoH{HTOTALH}{INDEX} = 9; + $HoH{HTOTALL}{INDEX} = 10; + $HoH{PAC}{INDEX} = 11; + $HoH{MODE}{INDEX} = 12; + $HoH{ETODAY}{INDEX} = 13; + $HoH{ERR_GV}{INDEX} = 14; + $HoH{ERR_GF}{INDEX} = 15; + $HoH{ERR_GZ}{INDEX} = 16; + $HoH{ERR_TEMP}{INDEX} = 17; + $HoH{ERR_PV1}{INDEX} = 18; + $HoH{ERR_GFC1}{INDEX} = 19; + $HoH{ERR_MODE}{INDEX} = 20; + $HoH{IAC1}{INDEX} = 21; + $HoH{VAC1}{INDEX} = 22; + $HoH{FAC1}{INDEX} = 23; + $HoH{PDC1}{INDEX} = 24; + $HoH{UNK10}{INDEX} = 25; + $HoH{UNK11}{INDEX} = 26; + $HoH{UNK12}{INDEX} = 27; + $HoH{UNK13}{INDEX} = 28; + $HoH{IAC2}{INDEX} = 29; + $HoH{VAC2}{INDEX} = 30; + $HoH{FAC2}{INDEX} = 31; + $HoH{PDC2}{INDEX} = 32; + $HoH{UNK14}{INDEX} = 33; + + # + # display data format indexes - sort %HoH by INDEX + # + for $key ( sort {$HoH{$a}{INDEX} <=> $HoH{$b}{INDEX} } keys ( %HoH ) ) { + if ( $HoH{$key}{INDEX} ne "-1" ) { + printf "%-8s = %2s = %s\n", $key, $HoH{$key}{INDEX}, $HoH{$key}{DESCR} ; + } + } +} + +####################################################################### +# +# Parse Data Format +# based on $HoH{HEXCODE} & store index/position in $HoH{INDEX} +# +sub parseDataFmt() { + print "* Data Format:\n"; + my $hexData = shift; + if ($hexData eq "") { + print "n/a\n"; + return; + } + + # split hex string into an array of 2char hex strings + @d = ( $hexData =~ m/..?/g ); + + my $dataOffset = $config->hex_data_to_follow_index + 1; + my $dataToFollow = hex($d[$config->hex_data_to_follow_index]); + print "dataToFollow = hex($d[$config->hex_data_to_follow_index]) = $dataToFollow\n"; + + if ($HASH{MODEL} eq "CMS 10000") { + &setDataFmt2; # temp: until get hold of CMS 10000 protocol & figure out hexcodes & keys + return; + } + + my $i = 0; + for $x ($dataOffset .. $dataOffset + $dataToFollow - 1) { + printf "%2s = %2s", $x, $d[$x] ; + for $key ( keys ( %HoH ) ) { + if ( $HoH{$key}{HEXCODE} eq $d[$x]) { + $HoH{$key}{INDEX} = $i; + printf " = %-8s = %2s = %s", $key, $HoH{$key}{INDEX}, $HoH{$key}{DESCR} ; + } + } + print "\n"; + $i++; + } +} + +####################################################################### +# +# Parse Data +# based on $HoH{INDEX} & store value in $HoH{VALUE} +# +sub parseData() { + print "* Data:\n"; + my $hexData = shift; + my $dataToFollow = hex( substr( $hexData, $config->hex_data_to_follow_index*2, 2 ) ); + my $startIndex = ( $config->hex_data_to_follow_index + 1 )*2; + my $numOfChars = $dataToFollow * 2; + my $data = substr( $hexData, $startIndex, $numOfChars ); + + # split hex string into an array of 4char hex strings + @d = ( $data =~ m/..?.?.?/g ); + + # display data values - sort %HoH by INDEX + for $key ( sort {$HoH{$a}{INDEX} <=> $HoH{$b}{INDEX} } keys ( %HoH ) ) { + if ( $HoH{$key}{INDEX} ne "-1" ) { + $HoH{$key}{VALUE} = hex( $d[$HoH{$key}{INDEX}] ) * $HoH{$key}{MULTIPLY}; + printf "%-8s: %8s %-5s = %s \n", $key, $HoH{$key}{VALUE}, $HoH{$key}{MEAS}, $HoH{$key}{DESCR} ; + } + } + + # + # Sometimes CMS2000 inverter keeps yesterdays ETODAY for first 30mins or more each morning + # hence some logic to use ZERO if hour < 9am and ETODAY > 1.0 kWh + # + if ($HASH{MODEL} eq "CMS 2000") { + my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime(time); + if ($hour < 9 && $HoH{ETODAY}{VALUE} > 1.0) { + + # + # open errfile in 'append' mode + # + my $errfile = getErrFileName(); + if ( open(ERRFILE, ">>$errfile") ) { + my $dateTimeStr = getDateTime(time); + my $errLine = "$dateTimeStr - ETODAY=$HoH{ETODAY}{VALUE}, will use 0 instead, as not reset since yesterday."; + print "ERROR logged to: $errfile\n"; + print ERRFILE "$errLine\n"; + close (ERRFILE); + } + $HoH{ETODAY}{VALUE} = 0; + } + } +} + +####################################################################### +# +# Write certain inverter data to a csv file +# +sub writeToFile() { + my $logfile = getLogFileName(); + my $rrdfile = getRrdFileName(); + + # + # open logfile in 'append' mode + # + if ( open(LOGFILE, ">>$logfile") ) { + print "Logging to: $logfile\n"; + + # + # add file header to logfile (if file exists & is empty) + # + if ( -z $logfile ) { +# print LOGFILE "DATE,TIMESTAMP,TEMP,VPV,IAC,VAC,FAC,PAC,ETOTAL,HTOTAL,MODE,ETODAY\n"; + print LOGFILE "DATE,TIMESTAMP,TEMP,VPV,IAC,VAC,FAC,PAC,ETOTAL,HTOTAL,MODE,ETODAY" . + ",ETOTALH,HTOTALH,ERR_GV,ERR_GF,ERR_GZ,ERR_TEMP,ERR_PV1,ERR_GFC1,ERR_MODE,UNK10\n"; + } + + my $etotal = ($HoH{ETOTALL}{VALUE} + $HoH{ETOTALH}{VALUE}); + my $htotal = ($HoH{HTOTALL}{VALUE} + $HoH{HTOTALH}{VALUE}); + if ($config->flags_debug) { + print "etotal=$etotal htotal=$htotal\n"; + } + + # + # write data to logfile & close it + # + my $unixTimeStamp = time; # secs since epoch + my $dateTimeStr = getDateTime($unixTimeStamp); + my $csvLine = "$dateTimeStr," . + "$unixTimeStamp," . + "$HoH{TEMP}{VALUE}," . + "$HoH{VPV}{VALUE}," . + "$HoH{IAC}{VALUE}," . + "$HoH{VAC}{VALUE}," . + "$HoH{FAC}{VALUE}," . + "$HoH{PAC}{VALUE}," . + "$etotal," . + "$htotal," . + "$HoH{MODE}{VALUE}," . + "$HoH{ETODAY}{VALUE}," . + "$HoH{ETOTALH}{VALUE}," . + "$HoH{HTOTALH}{VALUE}," . + "$HoH{ERR_GV}{VALUE}," . + "$HoH{ERR_GF}{VALUE}," . + "$HoH{ERR_GZ}{VALUE}," . + "$HoH{ERR_TEMP}{VALUE}," . + "$HoH{ERR_PV1}{VALUE}," . + "$HoH{ERR_GFC1}{VALUE}," . + "$HoH{ERR_MODE}{VALUE}," . + "$HoH{UNK10}{VALUE}"; + print LOGFILE "$csvLine\n"; + close (LOGFILE); + + # + # write data to rrdtool for graphing + # + if ($config->flags_use_rrdtool) { + + my $rrdexe = $config->scripts_rrdtool_exe_oth; # Unix/Linux/other + if ($^O eq 'MSWin32') { # Win32 (ActivePerl) + $rrdexe = $config->scripts_rrdtool_exe_win; + } + + # + # create rrd file - if it doesn't exist + # + if ( ! -e $rrdfile ) { + print "Ran: " . $config->scripts_create_rrd . " \"$rrdfile\" \"$rrdexe\"\n"; + system ($config->scripts_create_rrd . " \"$rrdfile\" \"$rrdexe\"" ); + } + + # + # update rrd file + # + my $rrdLine = "$unixTimeStamp:" . + "$HoH{TEMP}{VALUE}:" . + "$HoH{VPV}{VALUE}:" . + "$HoH{IAC}{VALUE}:" . + "$HoH{VAC}{VALUE}:" . + "$HoH{FAC}{VALUE}:" . + "$HoH{PAC}{VALUE}:" . + "$etotal:" . + "$htotal:" . + "$HoH{MODE}{VALUE}:" . + "$HoH{ETODAY}{VALUE}"; + print "Ran: $rrdexe update $rrdfile $rrdLine\n"; + system( "$rrdexe update $rrdfile $rrdLine" ); + } + + } + else { + warn "*** WARNING Could not open logfile: $logfile\n"; + } +} + +####################################################################### +# +# MAIN +# +print "Starting up at " . getDateTime(time) . " running on $^O ...\n"; +my $lastPollTime = 0; +my $nextPollTime = 0; +my $lastPvoutputTime = 0; +my $nextPvoutputTime = 0; + +# +# Initialise Serial Port & Inverter +# +&initialiseSerialPort; +&initialiseInverter; + +# +# Request Inverter Version Information +# +print "Send -> req version: " . $config->sendhex_version . "\n"; +$hexResponse = &writeReadBuffer($config->sendhex_version,$config->recvhex_version); +&parseVersData($hexResponse); + +# +# Request Inverter Parameter Format Information +# +if ( $config->sendhex_paramfmt ne " " ) { + print "Send -> req param format: " . $config->sendhex_paramfmt . "\n"; + $hexResponse = &writeReadBuffer($config->sendhex_paramfmt,$config->recvhex_paramfmt); + &parseParamFmt($hexResponse); +} + +# +# Request Inverter Parameter Information +# +print "Send -> req params: " . $config->sendhex_param . "\n"; +$hexResponse = &writeReadBuffer($config->sendhex_param,$config->recvhex_param); +&parseParam($hexResponse); + +# +# Request Inverter Data Format Information +# +if ( $config->sendhex_datafmt ne " " ) { + print "Send -> req data format: " . $config->sendhex_datafmt . "\n"; + $hexResponse = &writeReadBuffer($config->sendhex_datafmt,$config->recvhex_datafmt); + &parseDataFmt($hexResponse); +} + +# +# The main loop starts here +# +while (1) { + + # + # Request Inverter Data (regular data poll) + # + print "Send -> req data as at " . getDateTime(time) . " : " . $config->sendhex_data . "\n"; + $lastPollTime = time; + $nextPollTime = $lastPollTime + $config->secs_datapoll_freq; + $hexResponse = &writeReadBuffer($config->sendhex_data,$config->recvhex_data); + &parseData($hexResponse); + &writeToFile(); + + # + # Export data to http://pvoutput.org + # + if ($config->flags_use_pvoutput) { + $nextPvoutputTime = $lastPvoutputTime + $config->secs_pvoutput_freq; + if ( $lastPvoutputTime == 0 || $nextPvoutputTime <= time ) { + my $date = getDate_YYYYMMDD(time); + my $time = getTime_HHMM(time); + print "PVOUTPUT as at " . getDateTime(time) . " ...\n"; + print " ran: " . $config->scripts_pvoutput . " " . ($HoH{ETODAY}{VALUE} * 1000) . "$HoH{PAC}{VALUE} $HoH{VAC}{VALUE} $date $time $HASH{SERIAL}\n"; + system ($config->scripts_pvoutput . " " . ($HoH{ETODAY}{VALUE} * 1000) . " $HoH{PAC}{VALUE} $HoH{VAC}{VALUE} $date $time $HASH{SERIAL}" ); + $lastPvoutputTime = time; + } + } + + # + # Sleep until next time data needs to be polled (per DATAPOLL_FREQ_SECS constant) + # + $seconds = $nextPollTime - time; + if ( $seconds > 0 ) { + print "Sleeping: $seconds secs as at " . getDateTime(time) . " ...\n"; + sleep $seconds; + } + +} + +####################################################################### \ No newline at end of file diff --git a/opt/inverter/pvoutput.pl b/opt/inverter/pvoutput.pl new file mode 100644 index 0000000..586e321 --- /dev/null +++ b/opt/inverter/pvoutput.pl @@ -0,0 +1,121 @@ +#!/usr/bin/perl -w +# +# AS AT 04May2011 +# +# Submit solar production data to pvoutput.org per http://pvoutput.org/help.html#api +# +# Setup your pvoutput settings per http://pvoutput.org/help.html#api +# Set all values in @PVOUTPUT in this script to match those in your pvoutput settings +# - API_KEY & SYSTEM_ID to your settings from pvoutput.org +# - SERIAL_NUM to your inverter's serial number +# - add another record (in curly braces) if you have more than 1 inverter +# +# V1: Initial release +# +# Copyright Eric Sandeen 2010 +# released under GNU GPL v3 or later +# +# + editions by mmcdon23: +# + removed enphase envoy lines +# + added 4 arguments +# +# + editions by shell_l_d: +# + added serial_num argument so works for multiple inverters +# + replaced variables with @PVOUTPUT array of hashes +# + removed die if $current_watts = 0 +# +# Usage examples: +# perl pvoutput.pl 5500 1813 20110307 12:15 1234567890 +# +# Arguments: +# $ARGV[0] = (ETODAY) watt hrs exported so far today +# $ARGV[1] = (PAC) current watts +# $ARGV[2] = (VAC) current voltage +# $ARGV[3] = date (YYYYMMDD) +# $ARGV[4] = time (HH:MM) +# $ARGV[5] = inverter serial number - in case of multiple inverters +# +####################################################################### + +use HTTP::Request::Common qw(POST GET); +use LWP::UserAgent; # Web User Agent +use strict; + +my $daily_watthrs = $ARGV[0]; +my $current_watts = $ARGV[1]; +my $current_volts = $ARGV[2]; +my $log_date = $ARGV[3]; +my $log_time = $ARGV[4]; +my $serial_num = $ARGV[5]; + +use constant { + DEBUG_SCRIPT => 0, # 0 = NO, 1 = YES + LIVE_DATA_URL => "http://pvoutput.org/service/r1/addstatus.jsp", +}; + +# +# Array of Hashes of pvoutput information for each inverter - add more as required (in curly braces) +# +my @PVOUTPUT = ( + { + SERIAL_NUM => "1204DQ0116", + API_KEY => "16e7a916d69656e354d00461a4da1d2e40cfa4f1", + SYSTEM_ID => "12419", + }, +); + + +####################################################################### + + +# +# Display arguments if $debug turned on +# +if ( DEBUG_SCRIPT ) { + print "Serial: $serial_num as at: $log_date $log_time\n"; + print "Now: $current_watts W\n"; + print "Today: $current_volts Wh\n"; + print "Today: $daily_watthrs Wh\n"; +} + +# +# Prepare the web request +# +my $ua = LWP::UserAgent->new; + +# +# Loop through the PVOUTPUT Array of Hashes to find the matching inverter serial number +# +my $i; +for $i ( 0 .. $#PVOUTPUT ) { + if ( $PVOUTPUT[$i]{SERIAL_NUM} eq $serial_num ) { + if ( DEBUG_SCRIPT ) { + print $PVOUTPUT[$i]{SERIAL_NUM} . " serial match found at index $i\n"; + } + $ua->default_header( + "X-Pvoutput-Apikey" => $PVOUTPUT[$i]{API_KEY}, + "X-Pvoutput-SystemId" => $PVOUTPUT[$i]{SYSTEM_ID}, + "Content-Type" => "application/x-www-form-urlencoded" + ); + } +} + +# +# Prepare request string +# +print "Sending to PVOUTPUT [ d => $log_date, t => $log_time, v1 => $daily_watthrs, v2 => $current_watts, v6 => $current_volts ]\n"; +my $request = POST LIVE_DATA_URL, [ d => $log_date, t => $log_time, v1 => $daily_watthrs, v2 => $current_watts, v6 => $current_volts ]; + +# +# Send request to pvoutput to add/update live output status +# +my $res = $ua->request($request); + +# +# Display any errors +# +if (! $res->is_success) { + die "Couldn't submit data to pvoutput.org:" . $res->status_line . "\n"; +} + +exit; \ No newline at end of file diff --git a/opt/inverter/start.sh b/opt/inverter/start.sh new file mode 100644 index 0000000..e15143c --- /dev/null +++ b/opt/inverter/start.sh @@ -0,0 +1,2 @@ +#!/bin/bash +/opt/inverter/inverter.php >> /opt/inverter/log 2>&1 &