#<?
/*********************************************************************
 *
 *  Wattmon
 *    
 *  File : cronmin.cgi
 * 
 *  Description: CRON script that runs once a minute
 *
 *********************************************************************
 * Company:         Cynergy Software
 *
 * Software License Agreement
 *
 * Copyright (c) 2013-2017 Cynergy Software.  All rights reserved.
 *
 * Cynergy licenses to you the right to use, modify, copy, and 
 * distribute: 
 * (i)  the Software when used on a Wattmon device
 * (ii) the Software on any platform or device for personal use
 *
 * For commercial use please contact us via http://www.wattmon.com
 *
 * THE SOFTWARE AND DOCUMENTATION ARE PROVIDED "AS IS" WITHOUT 
 * WARRANTY OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT 
 * LIMITATION, ANY WARRANTY OF MERCHANTABILITY, FITNESS FOR A 
 * PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT SHALL 
 * CYNERGY BE LIABLE FOR ANY INCIDENTAL, SPECIAL, INDIRECT OR 
 * CONSEQUENTIAL DAMAGES, LOST PROFITS OR LOST DATA, COST OF 
 * PROCUREMENT OF SUBSTITUTE GOODS, TECHNOLOGY OR SERVICES, ANY CLAIMS 
 * BY THIRD PARTIES (INCLUDING BUT NOT LIMITED TO ANY DEFENSE 
 * THEREOF), ANY CLAIMS FOR INDEMNITY OR CONTRIBUTION, OR OTHER 
 * SIMILAR COSTS, WHETHER ASSERTED ON THE BASIS OF CONTRACT, TORT 
 * (INCLUDING NEGLIGENCE), BREACH OF WARRANTY, OR OTHERWISE.
 *
 * Author               Date        Comment
 *~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 * Akash Heimlich           v1.0
 * 
 * Change Log
 **********************************************************************
 * @modified 20-04-17
 * modified ini_get_array in top part to load only the data group
 * @modified 14-02-17
 * Does not save to disk if export_only is enabled
 * @modified 05-01-17
 * kwh values not properly counted when scripts run for more than 1 second
 * @modifed 14-10-16
 * added support for csv logging in multiple groups
 * @modifed 31-05-16
 * moved to float indexed arrays to save memory
 * @modifed 26-05-16
 * added divide by /cnt instead of 60 which was causing issues with many variables.
 * @modifed 10-03-16
 * Modified the /3600 to (60*$cnt) to account for losses in the counter
 * @modifed 05-03-16
 * Added instantaneous and average values
 * Added check to ensure ahcount.ini is written even if no data logging enabled
***********************************************************************/

// ensure it does not break on the older firmwares
if ($_GLOBALS["VERMINOR"]<1107) {
	include("/scripts/cronmin_legacy.cgi");
	die();
}

// do not time out
error_reporting(0);
$prefix='';
if ($_SERVER['HW_PLATFORM']==11) {
    $prefix='0:';
}
if ($_SERVER['HW_PLATFORM']>=20) {  // ULTRA
        $prefix=intval($_GLOBALS['log_drive']).':';
}
mkdir($prefix."/logs");

if ($_GLOBALS['loaded']) {
	$year=intval(strftime("%Y",time()));
	if ($year==1970) {
		log("Time is not set, attempting to sync with Wattmon time server.");
		exec("/scripts/time.cgi",2000);
	}
    $_GLOBALS['locked_min'] = 1;
    
    // get the number of seconds actually processed (cronsec runs) which can vary depending on load
    
    $cnt = $_GLOBALS["cnt"];
    if ($cnt == 0) 
        $cnt=1;
        
    // reset the seconds counter for the next minute    
    
    $_GLOBALS["cnt"] = 0;

    // call all pre-minute hooks in modules
    
	for ($ww=0;$ww<sizeof($_GLOBALS['hooks']['minpre']);$ww++) {
                include($_GLOBALS['hooks']['minpre'][$ww]);
    }
    
    // if a global voltage role is not define, use the default A1 input and preset calib values
    
    if (!$_GLOBALS['v_role']) {
        $val = adc_read(0);
        $v = floatval($_GLOBALS['v_adc_mul']) * (floatval($val) + floatval($_GLOBALS['v_adc_offset']));
    } else {    
        
        // use global v_role role id
    
        $v=mb_get_val_by_role($_GLOBALS['v_role']);
    }
    
    $total_am_in=0;
    $total_am_out=0;
    $needwrite=0;
    
     // iterate through all loaded groups, and update the Ah and Watt hour counters
     
    $numgrp=sizeof($_GLOBALS['grp']);
    
    for ($grp=0; $grp < $numgrp; $grp++) {

		// this is a dirty fix for arrays that seem to sometimes get corrupted
        
		$got_corrupted=0;
        for ($j=0;$j<16;$j++) {
            if (!is_float($_GLOBALS['grp'][$grp]['vals'][$j])) {
                $got_corrupted=1;
                
            }
        }
        if ($got_corrupted) {
            log('array group '.$grp.' seems corrupted, re-configuring it');
            $arr=indexed_array(4,16);
            for ($j=0;$j<16;$j++) {
                $arr[$j]=$_GLOBALS['grp'][$grp]['vals'][$j];
            }
            $_GLOBALS['grp'][$grp]['vals']=$arr;
        }

        // get total number of current (c) and watt (w) roles
        
        $ccnt=sizeof($_GLOBALS['grp'][$grp]['c']);
        $wcnt=sizeof($_GLOBALS['grp'][$grp]['w']);
        
        // if there are current roles to process
        
        if ($ccnt && is_array($_GLOBALS['grp'][$grp]['vals'])) {
            
            // group vals are indexed arrays for memory optimisation
            // refer to cronsec.cgi for a description of the index values
            
            $am_in=$_GLOBALS['grp'][$grp]['vals'][8];  // am_in
            $am_out=$_GLOBALS['grp'][$grp]['vals'][9];  // am_out
            
            // update the ampere minute counters
            
            // ah today out = sum of all amps / number of reads per minute
            $_GLOBALS['grp'][$grp]['vals'][3] += $am_out / (60*$cnt);   //ahd_out
            
            // ah today in = sum of all amps / number of reads per minute
            $_GLOBALS['grp'][$grp]['vals'][2] += $am_in / (60*$cnt);     //ahd_in
            
            // if voltage role for the group is define, use that or else use the global role
            
            if ($_GLOBALS['grp'][$grp]['v']) {
                $v1 = mb_get_val_by_role($_GLOBALS['grp'][$grp]['v']);    
            } else $v1=$v;
            
            // average watts for the last minute, for logging
            // if a watt counter is defined, simply let that do the logging and just log current and voltage - we won't aim
            // to duplicate that with current and voltage
            if (!$wcnt) {
                $wd_in = $_GLOBALS['grp'][$grp]['vals'][4];     // wd_in
                $wd_out = $_GLOBALS['grp'][$grp]['vals'][5];   // wd_out
                
                // update the wh per day
                // wh = sum of amps over time * voltage for the minute / number of reads per hour
                
                $_GLOBALS['grp'][$grp]['vals'][4] = $wd_in + ((floatval($am_in) * $v1 / (60*$cnt)));  //wd_in
                $_GLOBALS['grp'][$grp]['vals'][5] = $wd_out + ((floatval($am_out) * $v1 / (60*$cnt)));   // wd_out
                
                // update the lifetime kwh
                // convert wh to kwh and add to the global total
                
                $_GLOBALS['grp'][$grp]['vals'][6] = $_GLOBALS['grp'][$grp]['vals'][6] + ((floatval($am_in) * $v1 / (60*$cnt))) / 1000;  //kwh_in
                $_GLOBALS['grp'][$grp]['vals'][7] = $_GLOBALS['grp'][$grp]['vals'][7] + ((floatval($am_out) * $v1 / (60*$cnt))) / 1000;   //kwh_out

                // update the average watts per minute (in and out)
                
                $_GLOBALS['grp'][$grp]['vals'][14] = ((floatval($am_in) / $cnt * $v1));  // wm_in
                $_GLOBALS['grp'][$grp]['vals'][15] = ((floatval($am_out) / $cnt * $v1));    // wm_out
            }
            
            // update average amps per minute - divide by $cnt so the value will represent an average over the last minute.
            
            $_GLOBALS['grp'][$grp]['vals'][8] = $_GLOBALS['grp'][$grp]['vals'][8] / $cnt;   // am_in
            $_GLOBALS['grp'][$grp]['vals'][9] = $_GLOBALS['grp'][$grp]['vals'][9] / $cnt; // am_out
        } else {
            // if this has a watt counter associated
            
            if ($_GLOBALS['grp'][$grp]['wr']) {
                
                // get watts per minute
                
                $wm = floatval($_GLOBALS['grp'][$grp]['wm']) / (60*$cnt);
                
                // get watt hours per day
                
                $wd_in = $_GLOBALS['grp'][$grp]['vals'][4];     // wd_in
                $wd_out = $_GLOBALS['grp'][$grp]['vals'][5];   // wd_out
                
                // if watts are positive, assume discharge
                if ($wm > 0) 
                    $_GLOBALS['grp'][$grp]['vals'][5] = $wd_out+$wm;       //wd_out
                    
                // if watts are negative, assume charge
                if ($wm<0) 
                    $_GLOBALS['grp'][$grp]['vals'][4] = $wd_in-$wm;         // wd_in
            }
        }
        
        // if a watt role is defined
        if ($wcnt) {

            // average watts for the last minute, for logging
            
            $wm_in = $_GLOBALS['grp'][$grp]['vals'][14];     // wm_in
            $wm_out = $_GLOBALS['grp'][$grp]['vals'][15];   // wm_out
            
            // divide by cnt to get the average for the minute
            
            $_GLOBALS['grp'][$grp]['vals'][14] = ($wm_in/$cnt);  // wm_in
            $_GLOBALS['grp'][$grp]['vals'][15] = ($wm_out/$cnt);    // wm_out
            
            // if any watts went out, count it
            
            if ($_GLOBALS['grp'][$grp]['vals'][15] > 0)     // wm_out
                $_GLOBALS['grp'][$grp]['vals'][5] += ($_GLOBALS['grp'][$grp]['vals'][15] / 60);      // wd_out wm_out
            
            // if watts came in, count it
            
            if ($_GLOBALS['grp'][$grp]['vals'][14] > 0)      // wm_in
                $_GLOBALS['grp'][$grp]['vals'][4] += ($_GLOBALS['grp'][$grp]['vals'][14] / 60);        //wd_in wm_in

            // update the kwh registers
            
            $_GLOBALS['grp'][$grp]['vals'][6] = $_GLOBALS['grp'][$grp]['vals'][6] + (($_GLOBALS['grp'][$grp]['vals'][14] / 60)) / 1000;        // kwh_in, kwh_in, wm_in
            $_GLOBALS['grp'][$grp]['vals'][7] = $_GLOBALS['grp'][$grp]['vals'][7] + (($_GLOBALS['grp'][$grp]['vals'][15] / 60)) / 1000;     // kwh_out, kwh_out, wm_out
                
        }
        
        // if this group should be consolidated (affect battery SoC)
        
        if ($_GLOBALS['grp'][$grp]['type']==1) {
            
            // add amp minutes to total amp minutes counter
            // this is required as multiple groups may affect the battery SoC (i.e a Solar and Grid channel)
            
            $total_am_in += $am_in;
            $total_am_out += $am_out;
        }
    }      
    
    // energy balance for the last minute
    
    $bal = ($total_am_in / $cnt) - ($total_am_out / $cnt);
    
    // update the average_cnt variable which will be used to determine when to update the battery prediction
    
    $_GLOBALS['average_cnt'] = $_GLOBALS['average_cnt']+1; 
    $_GLOBALS['average_ah'] = $_GLOBALS['average_ah'] + $bal;    
    
    // if it's time to update battery prediction (once a minute)
    
    if ($_GLOBALS['average_cnt']>=60) {
        
        // divide the average ah by the number of runs
        
        $_GLOBALS['average_ah'] = $_GLOBALS['average_ah'] / $_GLOBALS['average_cnt'];
        $_GLOBALS['average_cnt']=1;    
    }
    
    // load battery settings
    
    $settings=ini_get_array("/config/battery.ini","battery");
    
    // get the average current in and out based on the number of points collected
    
    $total_am_in=$total_am_in / $cnt;
    $total_am_out=$total_am_out / $cnt;
    
    // adjust the input charge current if efficency factor is set
    $adj_current_in = $total_am_in * $settings['charge_efficiency'] / 100;
    
    $total_out=$total_am_out; 

    // if there was some discharge    
    
    if ($total_out > 0) {
        
        // use the peukert constant for lead-acid chemistry to calculate the actual discharge
        // this may vary based on the age of the battery - to disable this just set the peukert contact to 1
        // Ip = C * (C / (H * I))^(k-1)
        // where C is the capacity, H is the discharge time and I is the measured current.  k is the peukert const
        
        $peukert_const = $settings['peukert_const'];
    	$peuk_current = power($total_out, $peukert_const);
    	$peukert_const = $peukert_const - 1;
    	$pk2 = power($settings['battery_ah'] / $settings['c_rating'], $peukert_const);
    	
    	// update the apparent current based on load
    	
    	$adj_current_out = $peuk_current / $pk2;
    } else $adj_current_out=$total_out;
    
    // update the battery current Ah rating
    
   	$_GLOBALS['battery_cur_ah'] = $_GLOBALS['battery_cur_ah'] + ($adj_current_in - $adj_current_out) / 60;

    if ($_GLOBALS['battery_cur_ah']>0) {
        $_GLOBALS['battery_good_ah']=$_GLOBALS['battery_cur_ah'];
    } else if ($_GLOBALS['battery_good_ah']>5) {
        log("Restoring battery AH to last good AH as it seems to be corrupt.");
        $_GLOBALS['battery_cur_ah']=$_GLOBALS['battery_good_ah'];
    }
    // prevent impossible conditions
    
    if ($_GLOBALS['battery_cur_ah'] > $settings['battery_ah'])  
        $_GLOBALS['battery_cur_ah'] = floatval($settings['battery_ah']);
        
    if ($_GLOBALS['battery_cur_ah'] < 0)  {
        log("Negative battery ah: ".$_GLOBALS['battery_cur_ah']);
        $_GLOBALS['battery_cur_ah'] = 0;
        
    }
    
// ==== BATTERY FULL ALGORITHM
    
    // the following section takes care of resetting the battery to full in the condition that the amp counters
    // are out of sync.  This could happen for many reasons such as cloud days, or incorrect settings
    
    // if the battery full voltage has been reached
    
    if (floatval($v) >= floatval($settings['battery_full_voltage'])) {
        $is_full=0;
        
        // increment the full time
        
        $_GLOBALS['battery_full_time']=intval($_GLOBALS['battery_full_time'])+1;
        
        // if the battery is full for more than the set time in seconds
        
        if ($_GLOBALS['battery_full_time'] > $_GLOBALS['battery_met_time']) {
            
            // if we are using the return amps feature
            
            if ($_GLOBALS['battery_return_amps']) {
                
                // if the return amps are less than the set point, restore battery to full
                
                if ($_GLOBALS['battery_return_amps'] > $total_am_in)
                    $is_full=1;
                    
            } else $is_full=1;  // otherwise just set the battery to full
            
            if ($is_full) {
                
                // log the battery full condition
                
                if ($_GLOBALS['battery_cur_ah'] < $settings['battery_ah'])
                    log('Battery set to full, AH was:'+$_GLOBALS['battery_cur_ah']+'\r\n');
                
                // restore the AH counter to the actual battery ampere hours
                
                $_GLOBALS['battery_cur_ah'] = $settings['battery_ah'];
            }
        }
    } else {
        // restart the counter if voltage dips below full threshold at any time
        $_GLOBALS['battery_full_time']=0;
    }
    
    // update battery percentage
    
    $_GLOBALS['battery_percent']=$_GLOBALS['battery_cur_ah'] * 100 / $settings['battery_ah'];

// ==== BATTERY CHARGE CYCLE ALGORITHM
    
  // if the battery last ah value is not set, initialise it
  
    if (!$_GLOBALS['battery_last_ah']) {
        
        // set the value to the current ah
        
        $_GLOBALS['battery_last_ah'] = $_GLOBALS['battery_cur_ah'];
        
        // set the charge mdoe to discharge
        
        $_GLOBALS['charge_mode']=0;
        
        // if the average ah per minute is positive, set the mode to charge (1)
        
        if ($_GLOBALS['average_ah']>0)
            $_GLOBALS['charge_mode']=1; // charge
            
    } else {  // check if anything needs to be accounted for such as charge or dicharge
    
        // calculate the difference in ah between last_ah set point and figure out the percentage 
        // in relation to battery Ah
        
        $diff=$_GLOBALS['battery_cur_ah']-$_GLOBALS['battery_last_ah'];
        $perc = $diff * 100 / $settings['battery_ah'];
                    
        // if we are current discharging
        
        if ($_GLOBALS['charge_mode']==0) { // discharging
        
            // if there is a change of more than 1 percent of battery capacity (positively)
            if ($perc >= 1) {
                
                // set mode to charging
                
                $_GLOBALS['charge_mode']=1;
                
                // increment the number of charge cycles
                
                $_GLOBALS['charge_cycles']++;
                
                // calculate the depth of discharge to the nearest 5%
                
                $dod = intval( $_GLOBALS['battery_last_ah'] * 100 / $settings['battery_ah']/20)*5;
                
                // get the saved number of charges to the current DoD
                $soc_cnt=ini_get($prefix."/logs/soc.ini","soc","soc".$dod,0);
                
                // update the config file with the new counter values
                ini_set($prefix."/logs/soc.ini", "soc", "soc".$dod, $soc_cnt + 1);
                ini_set($prefix."/logs/soc.ini", "soc", "charge_cycles", $soc_cnt + 1);
                
                // reset the last_ah value so the process starts again
                $_GLOBALS['battery_last_ah']=$_GLOBALS['battery_cur_ah'];
            } 
                        
        } else {  
            // if we are in charge mode
            if ($_GLOBALS['charge_mode']==1) {  // charging
            
                // if there is a change of more than 1 percent (discharge)
                if ($perc < -1) {   // discharge
                    
                    // set mode to discharge
                    
                    $_GLOBALS['charge_mode']=0;
                    
                    // increment discharge cycle count
                    $_GLOBALS['discharge_cycles']++;
                    
                    // reset the last_ah value so the process starts again
                    $_GLOBALS['battery_last_ah']=$_GLOBALS['battery_cur_ah'];
                }
            }    
        }
    }
        
    $cur=$total_am_in;

// ==== CSV LOGGING ALGORITHM

    
    // create log folder for the year (does nothing if it exists)
    
    $logdir=$prefix."/logs/"+strftime("%Y",time());
    mkdir($logdir);     
    
    // create the month folder
    
    $logdir=$prefix."/logs/"+strftime("%Y",time())."/".strftime("%m",time());
    mkdir($logdir);     
    
    // get current minute and hour
    
    $m=strftime("%M",time());
    $h=strftime("%H",time());
    
    // get the number of log files and iterate through to see which are active
    
    $num_groups = ini_get("/config/datalog.ini","data","num_groups",0);
    $active_group=-1;
    
    for ($i=0; $i < $num_groups; $i++) {
        
        // get the log file status
        $s=ini_get("/config/datalog.ini","data",'group'+$i+'status',0);
        
        // if the data log is active
        if ($s==1) {
            
            // set the active_group variable to know the current active datalog group
            $active_group=$i;
            
            // calculate the active_group_index from the global active group list based on active group number
            for ($j=0; $j < sizeof($_GLOBALS['active_groups']); $j++) {
                if ($_GLOBALS['active_groups'][$j]['group_num']==$i) {
                    $active_group_index=$j;
                    break;
                }
            }
            
            // get the data log type
            $t=ini_get("/config/datalog.ini","data",'group'+$i+'type',0);
            
            // if the log is of type split (i.e. create day/month files)
            
            if ($t==0) {
                
                // set the day and month file names
                
                $logfile = $logdir."/".strftime("%Y%m%d",time())."_"+$i+".csv";
                $logfilem = $logdir."/".strftime("%Y%m",time())."_"+$i+".csv";
            } else {  // the log is a single consolidated log
                $logfile = $logdir."/log_"+$i+".csv";
            }
            if ($_GLOBALS['firmware_logging']==0) {    
                $logtext=time();
            } else
                $logtext='';
            $logfile2 = $prefix."/logs/log_"+$i+".ini";

            // get the number of data points in the log file
            
            $grpnvar=ini_get("/config/datalog.ini","data",'group'+$i+"numvar",0);
            
            // if the arrays in memory have not yet been create (on a reboot)
            
            if ($_GLOBALS['grp_memarr_created'.$i]==0) {
                
                $_GLOBALS['grp_memarr_created'.$i] = 1;
                
                // create an indexed array of floats with twice the number of data points defined
                if ($_GLOBALS['firmware_logging']==0) {    
                    $_GLOBALS['grp_memarr'.$i] = indexed_array(4,$grpnvar*2);
                    
                    // load the [data] section of the log file current values ini file
                    
                    $arr=ini_get_array($logfile2,'data');
                    
                    // update the saved values into memory
                    for ($j=0; $j < $grpnvar;$j++) {
                        $_GLOBALS['grp_memarr'.$i][$j*2]= $arr['p'.$j];
                        $_GLOBALS['grp_memarr'.$i][$j*2 + 1] = $arr['pd'.$j];
                    }
                }
                $_GLOBALS['grp_meminfo'.$i]= indexed_array(3,6);  // create an int array
                $_GLOBALS['grp_meminfo'.$i][0]=$arr['counter'];
                $_GLOBALS['grp_meminfo'.$i][1]=$arr['counter_d'];
                $_GLOBALS['grp_meminfo'.$i][2]=$arr['date_created'];
                $_GLOBALS['grp_meminfo'.$i][3]=$arr['date_latest'];
                $_GLOBALS['grp_meminfo'.$i][4]=$arr['num_entries'];
                $_GLOBALS['grp_meminfo'.$i][5]=$arr['num_days'];
                
                // free mem
                $arr=0;
            } else {
                if ($_GLOBALS['firmware_logging']==0) {    
    				$got_corrupted=0;
    				if (function_exists('gettype')) {
    				    $got_corrupted=gettype(&$_GLOBALS['grp_memarr'.$i])!='array_float';
    				} else {
                        for ($j=0;$j<$grpnvar*2;$j++) {
                            if (!is_float($_GLOBALS['grp_memarr'.$i][$j])) {
                                $got_corrupted=1;
                                
                            }
                        }
    				}
                    if ($got_corrupted) {
                        log('grp_memarr'.$i.' seems corrupted, re-configuring it');
                        $arr=indexed_array(4,$grpnvar*2);
                        
                        for ($j=0;$j<$grpnvar*2;$j++) {
                            $arr[$j]=$_GLOBALS['grp_memarr'.$i][$j];
                        }
                        $_GLOBALS['grp_memarr'.$i]=$arr;
                    }
                }
			}
            if ($_GLOBALS['firmware_logging']==0) {    
			if (is_array($_GLOBALS['minval'.$i])) {
			    if (function_exists('gettype')) {
				    $got_corrupted=gettype(&$_GLOBALS['minval'.$i])!='array_float';
				} else {
    				for ($j=0;$j<$grpnvar;$j++) {
                        if (!is_float($_GLOBALS['minval'.$i][$j])) {
                            $got_corrupted=1;
                            
                        }
                    }
				}
                if ($got_corrupted) {
                    log('minval'.$i.' seems corrupted, re-configuring it');
                    $arr=indexed_array(4,$grpnvar);
                    
                    for ($j=0;$j<$grpnvar;$j++) {
                        $arr[$j]=0;//$_GLOBALS['minval'.$i][$j];
                    }
                    $_GLOBALS['minval'.$i]=$arr;

					$c1=sizeof( $_GLOBALS['minrole'.$i]);
                          //  $_GLOBALS['debug']='size='.$c1;
                    for ($k=0;$k<$c1;$k++) {
                        $v=mb_get_val_by_role($_GLOBALS['minrole'.$i][$k]);
                        if ($_GLOBALS['minrole'.$i][$k]) {
                            $v=mb_get_val_by_role($_GLOBALS['minrole'.$i][$k]);
                        } else {
                            if ($_GLOBALS['minvt'.$i][$k]==1)  { // sysvar
                                $v=$_GLOBALS['grp_memarr'.$i][$k*2];
                            }
                        }
                        {
                            if ($_GLOBALS['minfn'.$i][$k]==0) {
                                        //if (!$_GLOBALS['minval'][$i]) {
                                $_GLOBALS['minval'.$i][$k]=$_GLOBALS['minval'.$i][$k]+$v;
                                    //} else 
                                        //$_GLOBALS['minval'][$i]=($_GLOBALS['minval'][$i]+$v)/2;
                            } else {
                                if ($_GLOBALS['minfn'.$i][$k]==2) { // MAX
                                    if ($_GLOBALS['minval'.$i][$k]<$v) $_GLOBALS['minval'.$i][$k]=$v;
                                } else { // MIN
                                    if ($_GLOBALS['minfn'.$i][$k]==1) { // MIN
                                        if ($_GLOBALS['minval'.$i][$k]>$v) $_GLOBALS['minval'.$i][$k]=$v;
                                    } else {    // just take the average value
                                        $_GLOBALS['minval'.$i][$k]=$v;
                                    }
                                }
                            }
                        } 
                    }
                }
				
			}
            }
            // iterate through all data points in the log file
	        if ($_GLOBALS['firmware_logging']==0) {           
            for ($j=0; $j < $grpnvar; $j++) {
                
                // build up the log CSV string
                $logtext=$logtext.",";
                
                // get scale factor
                $scale = floatval(ini_get("/config/datalog.ini", "data", 'group' + $i + 'var' + $j + 'varscale', 0.0));
                if ($scale==0) $scale=1;
                
                // get variable type, variable function and value
                
                $vt=ini_get("/config/datalog.ini","data",'group'+$i+'var'+$j+'vartype',0);
                $vfn=ini_get("/config/datalog.ini","data",'group'+$i+'var'+$j+'varfn',0);
                $vval=ini_get("/config/datalog.ini","data",'group'+$i+'var'+$j+'varval','');
                if ($vval=='') $vval=0;
                
                // if this is a system variable
                
                if ($vt==1) {  // system
                
                    // get the variable index (system variable number)
                    $varid=ini_get("/config/datalog.ini","data",'group'+$i+'var'+$j+'varval',0);
                    
                    // read the system variable for corresponding index
                    $varst=ini_get("/config/sysvars.ini","sysvars","sysvar".$varid."name","");
                    
                    // split it into parts
                    $var_arr=explode($varst,";");
                    $var=0.0;
                    
                    // if it is a global variable
                    if ($var_arr[0]=='V') {
                        $vval = $var_arr[1];
                        
                        // get the value from the global variable as a float
                        $var = floatval($_GLOBALS[$var_arr[1]]);
                        
                        // get precision
                        $dig = intval($var_arr[2]);
                        
                        // divide by scale factor
                        $var = $var / intval($var_arr[3]);
                    } else {  
                        
                        // if the sysvar comes from a group variable
                        
                        if ($var_arr[0]=='G') {
                            // for legacy sysvar settings and specific variable, see if the variable exists in the array
                            if (isset($_GLOBALS['grp'][intval($var_arr[1])][$var_arr[2]])) {
                                if (($vfn==0) && ($var_arr[2]==12) || ($var_arr[2]==13)) {// w_in  or w_out special case do we take the value or the rounded value
									$var=$_GLOBALS['grp'][intval($var_arr[1])]['vals'][$g+2];
                                } else {
                                    $var=$_GLOBALS['grp'][intval($var_arr[1])][$var_arr[2]];
                                }
                            } else {
                                // map the variable name to indices int he group (see cronsec.cgi for index definitions)
                                $g_arr=array("ah_in","ah_out", "ahd_in","ahd_out", "wd_in", "wd_out", "kwh_in",  "kwh_out",  "am_in",  "am_out", "a_in","a_out","w_in",  "w_out",  "wm_in",   "wm_out");
                                for ($g=0; $g < 16; $g++) {
                                    
                                    // if the sysvar name matches the index
                                    if ($var_arr[2]==$g_arr[$g]) {
                                        if (($vfn==0) && ($g==12) || ($g==13)) {// w_in  or w_out special case do we take the value or the rounded value
											$var=$_GLOBALS['grp'][intval($var_arr[1])]['vals'][$g+2];
										} else {
                                        // set the variable name and break
	                                        $var=$_GLOBALS['grp'][intval($var_arr[1])]['vals'][$g];
										}
                                        break;
                                    }
                               }
                            }
                            // update the variable name
                            $vval=$var_arr[2]."_grp".$var_arr[1];
                            
                            // precision
                            $dig=intval($var_arr[3]);
                            
                            // scale it
                            $var=$var/intval($var_arr[4]);
                        }
                    }
                    
                    // scale the variable by the data point scale factor
					$varcalc = $var * $scale;
					
					// if the value is rounded to a whole number, just add the int to save space
					
					if (floatval($varcalc)==intval($varcalc)) {
	                    $logtext=$logtext . intval($varcalc);                    
					} else  // add the value formatted to the specified precision
						$logtext=$logtext . number_format($varcalc,$dig);                    
						
                } else {
                    
                    // is the data point a device (role variable)
                    
                    if ($vt==2) {  // device
                    
                        // precision set to 3 points after the decimal
                        $dig=3;
                        
                        // if a function (min, max, inst, etc) has been specified, take actual value fromt the minval array for the data point
					
                        if ($_GLOBALS['minfn'.$i][$j]>0)  {
                            // this value will already be up-to-date as cronsec.cgi will be constantly updating it
							if (($_GLOBALS['minfn'.$i][$j]!=3))
                                $var=$_GLOBALS['minval'.$i][$j];
                            else $var=mb_get_val_by_role($_GLOBALS['minrole'.$i][$j]);

                        } else {
                            // take the average value for the minute (default)
                            $var = $_GLOBALS['minval'.$i][$j] / $cnt;
                        }
                            
                        $_GLOBALS['minval'.$i][$j]=0;
                        
                        // scale the value
                        $varcalc=$var * $scale;
                        
                        // check sanity if any min or max value is defined
                        if (($_GLOBALS['logmin'.$i][$j]!=0) || ($_GLOBALS['logmax'.$i][$j]!=0)) {
                            if ($varcalc < $_GLOBALS['logmin'.$i][$j]) 
                                $varcalc = 0;
                            if ($varcalc > $_GLOBALS['logmax'.$i][$j]) 
                                $varcalc = 0;
                        }
                        
                        // if the value is rounded to a whole number, just add the int to save space
                        
						if (floatval($varcalc)==intval($varcalc)) {
							$logtext=$logtext . intval($varcalc);
						} else 
                        	$logtext=$logtext . number_format(floatval($varcalc),3);
                        
                    }
                    
                    // if the variable is a global variable
                    
                    if ($vt==3) {  // global
                    
                        $var=floatval($_GLOBALS[$vval]);
                        
                        // scale it
                        
						$varcalc=$var*$scale;
						
						// if the value is rounded to a whole number, just add the int to save space
						
						if (floatval($varcalc)==intval($varcalc)) {
    	                    $logtext=$logtext. intval($varcalc);
						} else
							$logtext=$logtext . number_format(floatval($varcalc),3);
                    }                
                }
                
                $var=floatval($var)*$scale;
                
                // for the average function (0)
                
                if ($vfn==0) {
                    // update mem array
                    $_GLOBALS['grp_memarr'.$i][$j*2] = ($_GLOBALS['grp_memarr'.$i][$j*2] + floatval($var)) / 2;
                    $_GLOBALS['grp_memarr'.$i][$j*2 +1] = ($_GLOBALS['grp_memarr'.$i][$j*2 + 1] + floatval($var)) / 2;
                }
                
                // for the min function (1)
                
                if ($vfn==1) {
                    
                    // if the value is not set yet, just set to the current value
                    
                    if ($_GLOBALS['grp_memcounter'.$i]==0) {
                        $_GLOBALS['grp_memarr'.$i][$j*2] = number_format(floatval($var),3);
                        $_GLOBALS['grp_memarr'.$i][$j*2+1] = number_format(floatval($var),3);
                    } else {
                        
                        // if the value is less than the set point, update it
                        
                        if ($_GLOBALS['grp_memarr'.$i][$j*2] > floatval($var)) 
                            $_GLOBALS['grp_memarr'.$i][$j*2] = number_format(floatval($var),3);
                            
                        if ($_GLOBALS['grp_memarr'.$i][$j*2 + 1] > floatval($var)) 
                            $_GLOBALS['grp_memarr'.$i][$j*2 + 1] = number_format(floatval($var),3);
                    }
                }
                
                // max function (2)
                
                if ($vfn==2) {
                   
                        $_GLOBALS['grp_memarr'.$i][$j*2]=floatval($var);
                        $_GLOBALS['grp_memarr'.$i][$j*2+1]=floatval($var);
                }
                
            } 
            }
            // if date created is not set, update it to current date
            
            if ($_GLOBALS['grp_meminfo'.$i][2]==0) 
                $_GLOBALS['grp_meminfo'.$i][2] = time();
            
            // increment num entries
            
            $_GLOBALS['grp_meminfo'.$i][4] = $_GLOBALS['grp_meminfo'.$i][4]+1;
            
            // increment counter
            
            $_GLOBALS['grp_meminfo'.$i][0] = $_GLOBALS['grp_meminfo'.$i][0]+1;
            
            // increment day counter
            
            $_GLOBALS['grp_meminfo'.$i][1] = $_GLOBALS['grp_meminfo'.$i][1]+1;
            
            // if the latest group date does not match the current day, increment the number of days
            
            if (strftime("%d",$_GLOBALS['grp_meminfo'.$i][3]) != strftime("%d",time())) 
                $_GLOBALS['grp_meminfo'.$i][5]=$_GLOBALS['grp_meminfo'.$i][5]+1;
            
            // set latest update time
            
            $_GLOBALS['grp_meminfo'.$i][3]=time();

            $_GLOBALS['log'.$i.'curmin']=intval($m) % $_GLOBALS['log'.$i.'mins'];
            
			// if the minute interval is different, log on the minute in relation to the 0th minute (i.e. every 5 minutes on the hour)

            //if (($_GLOBALS['log'.$i.'mins']==1) || ($_GLOBALS['log'.$i.'curmin'] ==0)) {
			if (!$_GLOBALS['log'.$i.'mins'] || ($_GLOBALS['log'.$i.'mins']==1) || ($_GLOBALS['log'.$i.'curmin'] ==0)) {
			    // we can hook here in case there is a hook so that data can be modified if needed before writing
                $_GLOBALS['log_group']=$i;
                
                // this is filled automatically...
                if ($_GLOBALS['firmware_logging']==0) {   
                    $_GLOBALS['log'.$i.'_line']=$logtext;
                } else {
                    $time=microtime();
                    if (strlen($_GLOBALS['log'.$i.'_line'])==0) {
                        log("[notice] Waiting for firmware logger task to generate data");
                        while (strlen($_GLOBALS['log'.$i.'_line'])==0) {
                            if (microtime()-$time>10000) {
                                log("[error] Firmware logger task did not generate data in time.");
                                break;
                            }
                        }
                    }
                }
                
                $logtext='';
                for ($ww=0;$ww<sizeof($_GLOBALS['hooks']['minlog']);$ww++) {
                    include($_GLOBALS['hooks']['minlog'][$ww]);
                } 
                // add the value to the csv line to the buffer in memory
                if ($_GLOBALS['log'.$i.'_line']) {
                        $_GLOBALS['log'.$i.'buf'] = $_GLOBALS['log'.$i.'buf'] . $_GLOBALS['log'.$i.'_line']."\r\n";
                    if ($m==0) {
                        $_GLOBALS['log'.$i.'hbuf'] =$_GLOBALS['log'.$i.'_line']."\r\n";
                    }
                }
                $_GLOBALS['log'.$i.'_line']='';  // free the memory
                // increment number of lines in memory
                $_GLOBALS['log'.$i.'lines']++;
                //$_GLOBALS['log'.$i.'curmin']=0;
            }
            
            // get export settings
            
            $export_only=ini_get("/config/dataexport.ini","export","export_only",0);
            $export_portal=ini_get("/config/dataexport.ini","export","export_portal",0);
            $intervalportal=ini_get("/config/dataexport.ini","export","intervalportal",5);
            $headers=ini_get("/config/datalog.ini","data","headers",1);
            // force a write if we cross an hour boundary, and reset the interval
            if ( ($_GLOBALS['log'.$i.'interval']<=1) || $m==59) 
            {
                // if we want local storage of the CSV data (export only is false)
                if (!$export_only) {
                    
                    // append the memory buffer to the log file (either consolidated for by month/day)
                    
                    if ($_GLOBALS['log'.$i.'buf']) {
                        $f=fopen($logfile,"a");
                        if ($f) {
                            if (!ftell($f)) {
                                get_headers($f);
                            }
                            
                            fwrite($f,&$_GLOBALS['log'.$i.'buf']);
                            fclose($f);
                        }
                    }
                    // clear the memory buffer
                    
                    $_GLOBALS['log'.$i.'buf']='';
                }
                
                // update the log interval for the next write to disk from the config settings
                
                $_GLOBALS['log'.$i.'interval']=ini_get("/config/datalog.ini","data","group".$i."interval",15);
                
                // force a write
                $needwrite=1;
                   
            } else {
                
                // decrement the counter once a minute until we need to write to disk
                
                $_GLOBALS['log'.$i.'interval']--;
            }
            
            // if the current minute is 0 (once per hour on the hour)
            if ($m==0) {
                
                // reset the counter for the data point 
                $_GLOBALS['grp_meminfo'.$i][0]=0;  // counter
                
                $logtext=time();;
                
                // if we don't only export data we need to build up the hourly CSV log file
                
                if (!$export_only) {
                    
                    if (!$_GLOBALS['firmware_logging']) {               
                        // iterate through all data points
                        
                        for ($j=0; $j < $grpnvar; $j++) {
                            $logtext=$logtext.",";
                            $logtext=$logtext . number_format( $_GLOBALS['grp_memarr'.$i][$j*2], 2);
                             $_GLOBALS['grp_memarr'.$i][$j*2]=0;
                        }
                        $logtext=$logtext."\r\n";
                    } else {
                        $logtext=$_GLOBALS['log'.$i.'hbuf'];
                        $_GLOBALS['log'.$i.'hbuf']=0;
                        
                    }
                    // open the month log
                    $f=fopen($logfilem,"a");
                    
                    if ($f) {
                        fwrite($f,$logtext);
                        fclose($f);                    
                    }    
                }
                
                // once a day if local CSV logging is enabled
                
                if (!$export_only && ($h==0)) {
                    // reset day counter
                    $_GLOBALS['grp_meminfo'.$i][1]=0;  // counterd
                    
                    // update the yearly CSV log
                    if ($_GLOBALS['firmware_logging']==0) {
                        $logfilem = $prefix."/logs/"+strftime("%Y",time())."/".strftime("%Y",time())."_"+$i+".csv";
                        $logtext=time();
                        for ($j=0;$j<$grpnvar;$j++) {
                            $logtext=$logtext.",";
                            $logtext=$logtext.number_format($_GLOBALS['grp_memarr'.$i][$j*2+1],2);
                            $_GLOBALS['grp_memarr'.$i][$j*2+1]=0;
                        }
                        $logtext=$logtext."\r\n";
                        $f=fopen($logfilem,"a");
                        if ($f) {
                            fwrite($f,$logtext);
                            fclose($f);                    
                        }
                    }
                }
            }
            
            // if data needs to be written to disk, write the logfile current values
            
            if ($needwrite) {
                $arr=ini_get_array($logfile2);
                
                if (!$_GLOBALS['firmware_logging']) {
                    for ($j=0;$j<$grpnvar;$j++) {
                        $arr['data']['p'.$j]=$_GLOBALS['grp_memarr'.$i][$j*2];
                        $arr['data']['pd'.$j]=$_GLOBALS['grp_memarr'.$i][$j*2+1];
                    }
                }
                $arr['data']['counter']=$_GLOBALS['grp_meminfo'.$i][0];
                $arr['data']['counter_d']=$_GLOBALS['grp_meminfo'.$i][1];
                $arr['data']['date_created']=$_GLOBALS['grp_meminfo'.$i][2];
                $arr['data']['date_latest']=$_GLOBALS['grp_meminfo'.$i][3];
                $arr['data']['num_entries']=$_GLOBALS['grp_meminfo'.$i][4];
                $arr['data']['num_days']=$_GLOBALS['grp_meminfo'.$i][5];

                ini_put_array($logfile2,&$arr);
                $arr=0;
            }
        }
    }
    
    // if there are no active groups we still need to write the ahcount every 5 minutes
    if ($active_group==-1) {
      //  log("min=".(intval($m) % 5)." and want 4");
        if ((intval($m) % 5)==4) $needwrite=1;
    }
    
    // if we need to write to disk
    
    if ($needwrite) {
        
        // update the main ahcount.ini file which has most important battery variables
        // open this in append mode so that even if something goes wrong it will not truncate the file to 0
        $f=fopen($prefix."/logs/ahcount.ini","a");
        
        if ($f) {
        	fseek($f,0,0); 
            $st="[data]\r\n"+
                "battery_percent="+$_GLOBALS['battery_percent']+"\r\n"+
                "battery_cur_ah="+$_GLOBALS['battery_cur_ah']+"\r\n"+
                "charge_cycles="+$_GLOBALS['charge_cycles']+"\r\n"+
                "discharge_cycles="+$_GLOBALS['discharge_cycles']+"\r\n"+
                "charge_mode="+$_GLOBALS['charge_mode']+"\r\n";
                
            fwrite($f,$st);    
    
            // iterate through the groups
            for ($grp=0;$grp < $numgrp; $grp++) {    
                $str =  "grp".$grp."_ahdin=".$_GLOBALS['grp'][$grp]['vals'][2]."\r\n" +      // ahd_in
                        "grp".$grp."_ahdout=".$_GLOBALS['grp'][$grp]['vals'][3]."\r\n" +    // ahd_out
                        "grp".$grp."_wdin=".$_GLOBALS['grp'][$grp]['vals'][4]."\r\n" +        //wd_in
                        "grp".$grp."_wdout=".$_GLOBALS['grp'][$grp]['vals'][5]."\r\n"+           //wd_out
                        "grp".$grp."_kwhin=".$_GLOBALS['grp'][$grp]['vals'][6]."\r\n" +      // kwh_in
                        "grp".$grp."_kwhout=".$_GLOBALS['grp'][$grp]['vals'][7]."\r\n";     // kww_out
                        
                        
                        
                fwrite($f,$str);
                
                // clear the counter for the next minute
                
                $_GLOBALS['grp'][$grp]['vals'][8]=0;          // am_in    
                $_GLOBALS['grp'][$grp]['vals'][9]=0;         // am_out
                $_GLOBALS['grp'][$grp]['vals'][14]=0;      // am_in    
                $_GLOBALS['grp'][$grp]['vals'][15]=0;     // am_out
                
            }
            
            fclose($f);    
        } 
    } else { 
        
        // iterate through groups and clear the minute values for the next second
        
        for ($grp=0;$grp < $numgrp; $grp++) {    
                $_GLOBALS['grp'][$grp]['vals'][8]=0;      // am_in    
                $_GLOBALS['grp'][$grp]['vals'][9]=0;     // am_out
                $_GLOBALS['grp'][$grp]['vals'][14]=0;      // am_in    
                $_GLOBALS['grp'][$grp]['vals'][15]=0;     // am_out
                
        }        
    }
    
	// free up memory
 	$str=0;
    $settings=0;
    $logtext='';
    
    // check for custom device driver includes
    $num_dev=mb_num_devices(); 
    for ($di=0;$di<$num_dev;$di++) {
        $dev=mb_get_dev_by_index($di);
        if (file_exists("/dev/inc/dev".$dev['device_type_id'].".inc")) {
            include("/dev/inc/dev".$dev['device_type_id'].".inc");
            call_user_func("periodic_".$dev['device_type_id'], &$dev);
        }
    }
    
    // call any post-minute hooks defined in modules
    $_GLOBALS['locked_min'] = 0;

    // the following is a 'dirty' work-around.  If a package has an include file and a .cgi file with the same name,
    // use the exec() function to cfall the cgi instead.  This will allow it to run when sufficient memory is available
    // whereas it would possibly error out with the include() call due to insufficient memory.
    for ($ww=0;$ww<sizeof($_GLOBALS['hooks']['min']);$ww++) {
        //$s=substr($_GLOBALS['hooks']['min'][$ww],0,strlen($_GLOBALS['hooks']['min'][$ww])-3)."cgi";
//        if (file_exists($s)) {
  //          exec($s,500);
//        } else 
		include($_GLOBALS['hooks']['min'][$ww]);
    }
    // if a resync is needed, run it in the background
    if ($_GLOBALS['md5_sync']) {
        if ($_GLOBALS['need_md5_resync']) {
            
            if (file_exists("/scripts/md5_resync.cgi")) {
                log("[md5_sync]: Resyncing file system");
                exec("/scripts/md5_resync.cgi",1000);
            }
        }
        if ($_GLOBALS['need_md5_check']) {
            
            if (file_exists("/scripts/md5_check.cgi")) {
                log("[md5_sync]: Scheduling file system check");
                exec("/scripts/md5_check.cgi",1000);
            }
        }
    }
}

/* Watchdog timer in software  to reboot after 5 minutes */
	if (function_exists("clear_watchdog")) {
			clear_watchdog();
	}
	
	function get_headers($f) {
	    $ini = ini_get_array("/config/dataexport.ini","export");
	    $static_header = intval($ini['static_header']);
        if ($static_header && file_exists("/config/csv_headers.txt")) {
            $data='';
            $fh=fopen("/config/csv_headers.txt","r");
            while (!feof($fh)) {
                $data=fread($fh,512);
                fwrite($f,$data);
                print(".");
                $data='';
            }
            fwrite($f,"\r\n");
            fclose($fh);
            return;
            //return $data;
        }
	    //$ini = ini_get_array("/config/dataexport.ini","export");
        $num_groups = ini_get("/config/datalog.ini","data","num_groups",0);
        $data='"ts",';
        for ($i=0;$i<$num_groups;$i++) {
            $s=ini_get("/config/datalog.ini","data",'group'+$i+'status',0);
            
            if ($s==1) {
    
                $grpnvar=ini_get("/config/datalog.ini","data",'group'+$i+"numvar",0);
                for ($j=0;$j<$grpnvar;$j++) {
                    $vt=ini_get("/config/datalog.ini","data",'group'+$i+'var'+$j+'vartype',0);
                    $vfn=ini_get("/config/datalog.ini","data",'group'+$i+'var'+$j+'varfn',0);
                    $vval=ini_get("/config/datalog.ini","data",'group'+$i+'var'+$j+'varval',0);
                    if ($vt==1) {  // system
                        $varst=ini_get("/config/sysvars.ini","sysvars","sysvar".$vval."name","");
                        $varr=explode($varst,';');
                        if ($varr[0]=='V') {
                            $varst=$varr[1];
                        } else {
                            if ($varr[0]=='G') {
                                $varst=$varr[2].$varr[1];
                            }
                        }
                    } else {
                        if ($vt==2) {  // device
                            $varst=ini_get("/config/roles.ini","roles","role".$vval,"");
                        }
                        if ($vt==3) {  // global
                             $varst=ini_get("/config/datalog.ini","data",'group'+$i+'var'+$j+'varval','');
                        }                
                    }
                    $data.='"'.$varst.'"';
                    if ($j<($grpnvar-1)) $data.=",";
                    fwrite($f,$data);
                    $data='';
                }
    
            }
        }
        fwrite($f,"\r\n");
        return 1;//($data."\r\n");
    }
?>