1594 lines
55 KiB
Perl
Executable File
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;
|
|
}
|
|
|
|
}
|
|
|
|
#######################################################################
|