Files
inverter/inverter.pl

1594 lines
55 KiB
Perl
Executable File

#!/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 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_pvoutput_php=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( "/opt/inverter/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 = "";
my $suffix = shift;
#
# 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} . (defined $suffix ? ("_" . $suffix) : "") . ".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:" .
"$HoH{ETODAY}{VALUE}";
print "Ran: $rrdexe update $rrdfile $rrdLine\n";
system( "$rrdexe update $rrdfile $rrdLine" );
$rrdfile = getRrdFileName("today");
if (-e $rrdfile) {
$rrdLine = "$unixTimeStamp:" .
"$HoH{PAC}{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}" );
print " ran: " . $config->scripts_pvoutput_php . " " . ($HoH{ETODAY}{VALUE} * 1000) . " $HoH{PAC}{VALUE} $HoH{VAC}{VALUE} $HASH{SERIAL}\n";
system ($config->scripts_pvoutput_php . " " . ($HoH{ETODAY}{VALUE} * 1000) . " $HoH{PAC}{VALUE} $HoH{VAC}{VALUE} $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;
}
}
#######################################################################