<?php

    ############################################################################
    #
    #   Meteotemplate
    #   http://www.meteotemplate.com
    #   Free website template for weather enthusiasts
    #   Author: Jachym
    #           Brno, Czech Republic
    #   First release: 2015
    #
    ############################################################################
    #           
    #   v2.0    2017-04-20  Now also handle data of update programs
    #   v2.1    2017-04-24  Handling of fallen rain between 23:55 and 00:00:00
    #   v2.2    2017-07-02  Added Extra sensors; fixed bug rain at begin of day
    #   v3.0    2017-08-11  Added multipliers
    #   v3.1    2017-12-20  Discard outdated parameters; added mysql error logging
    #   v3.2    2021-05-08  added option not reload ( from meteotemplateLive.txt ) sensor not received
    #   v3.2a   2021-05-08  added option $numSensors to define max extra sensor
    #   v3.2b   2021-07-12  added option $logGetPost to log api.php call ( cache/apiReqLog.txt )
    #   v3.3    2022-08-22  added logging of errors ( cache/apiErrorLog.txt )
    #   v3.4    2022-09-30  removed data from meteotemplateLive.txt from previous day for ET and L ( as already done for R )
    #   v3.5    2022-11-09  added require_once for config.php and functions.php, $base conditional initialization
    #   v3.6    2023-11-02  extended sensor definition support
    #   v3.6a   2023-11-06  added compatibility with non extended extra sensor ( standard meteotemplate )
    #
    ############################################################################

    if (!isset($base)) $base = "";

    // load main info
    require_once($base."config.php");

    // load dependencies
    require_once($base."scripts/functions.php");
        
    //ini_set('display_errors', 1);
    //ini_set('display_startup_errors', 1);
    //error_reporting(E_ALL);

    // set the exception handler
	set_exception_handler('exception_handler');

	if (!function_exists('count8'))
	    {
		function count8($a)
	       {
	       if ((PHP_VERSION_ID < 70300) || (is_countable($a)))
	           return count($a);
	   	   else
   	   	       return 0;
	   	   }
     	}

    $apiLog = array();
    $Log = array();
    $apiLog['info'][] = "starting script v3.5: ".date("Y-m-d H:i:s");
    $Log['info'][] = "starting script v3.5: ".date("Y-m-d H:i:s");
    $rawInput = array();
    $utc = time();
    $timeId = "Time";
    ini_set( 'serialize_precision', -1 );       // so json_encode does not 'change roundness' ( for PHP >= 7.1 )
	$reloadLive = 1;                           	// 1 = old behaviour  0 = does not reload sensor not received
	$numSensors = 8;
	$logGetPost = 0;							// 1 = append log Get/Post params to cache/apiReqLog.txt ( does not log your password ), file grows
    $append2ApiLog = 0;							// 1 = log appended to cache/apiLog.txt ( use for debug only, file grows )  0 = log written to cache/apiLog.txt ( old behaviour )
	$shiftPP = 0;								// 0 = no renaming  1= rename PP2 -> PP3, PP1 -> PP2, PP0 -> PP1 ( for 'standard' meteobridge settings )

	if ($logGetPost)
	    {
        // log post and get data
        if(isset($_POST))
            {
            foreach($_POST as $urlParameter=>$value)
                {
                if($urlParameter!="PASSWORD" && $urlParameter!="PASS")
                    {$Log['info'][] = "post ".$urlParameter.": ".$value;}
                }
            }
        if(isset($_GET))
            {
            foreach($_GET as $urlParameter=>$value)
                {
                if($urlParameter!="PASSWORD" && $urlParameter!="PASS")
                    {$Log['info'][] = "get ".$urlParameter.": ".$value;}
                }
            }
	    generateLog($base, $Log, "apiReqLog.txt", 1);
	    }
	

    if(isset($apiUpdate)){
        ############################################################################
        // api included by CUMULUS, WU, CUSTOM, NETATMO, WLIP
        ############################################################################
            
        $rawUpdate['SW'] = $apiUpdate;
        $apiLog['info'][] = "Handling data from ".$apiUpdate;
        // load data
        foreach ($rawUpdate as $key => $value) {
            $rawInput[$key] = $value;
            if($key == 'U'){
                $apiLog['info'][] = "update 'U' = ".$rawInput['U']." (".date("Y-m-d H:i:s",$rawInput['U']).")";
            }
            else{
                $apiLog['info'][] = "update '".$key."' = ".$rawInput[$key];
            }
        }
    }
    else{
        ############################################################################
        // api section called by http
        ############################################################################
        // check acces authorization
        if(isset($_GET['PASS'])){
            $password = $_GET['PASS'];
        }
        else if(isset($_GET['PASSWORD'])){
            $password = $_GET['PASSWORD'];
        }
        else{
            die("No password provided");
        }
        // check if password is correct
        if($password!=$updatePassword){
            if($password==$adminPassword){ // if admin password provided accept, but notify
                echo "Authorized via admin password";
            }
            else{
                die("Unauthorized");
            }
        }
        $apiLog['info'][] = "Authorized access.";
        $apiLog['info'][] = "Current date/time: ".date("Y-m-d H:i:s");
        $apiLog['info'][] = "api called by http";

		if ($shiftPP)
		   {
		   // rename PP2 -> PP3, PP1 -> PP2, PP0 -> PP1 ( for 'standard' meteobridge settings )
		   if (isset($_GET['PP2'])) {$_GET['PP3'] = $_GET['PP2']; unset($_GET['PP2']);}
		   if (isset($_GET['PP1'])) {$_GET['PP2'] = $_GET['PP1']; unset($_GET['PP1']);}
		   if (isset($_GET['PP0'])) {$_GET['PP1'] = $_GET['PP0']; unset($_GET['PP0']);}
		   }

        // read raw input
        if(isset($_GET)){
            foreach($_GET as $urlParameter=>$value){
                if($urlParameter!="PASSWORD" && $urlParameter!="PASS"){
                    $rawInput[$urlParameter] = $value;
                    $apiLog['info'][] = "update ".$urlParameter.": ".$value;
                }
            }
        }
        else{
            // no data in api
            $apiLog['error'][] = "api: no data";
	        generateLog($base, $apiLog, "apiLog.txt", $append2ApiLog);
            die("api: no data");
        }
        ############################################################################
        // end of api section
        ############################################################################
    }
    
    ############################################################################
    // common part for both:
    // apiUpdate section called by /update/update.php
    // api section called by http
    ############################################################################
    
    $apiLog['info'][] = "Begin of common part of api script";
    
    ############################################################################
    #                            HANDLING LIVE DATA   
    ############################################################################
    
    $apiLog['info'][] = "Start handling live data";
    
    // Preset time of live update
    foreach ($rawInput as $key => $value) {
        $rawInput[$key.$timeId] = $utc;
    }

    // multipliers
    if($multiplierT != 0 && isset($rawInput['T'])){
        $rawInput["T"] = $rawInput["T"] + $multiplierT;
    }
    if($multiplierP != 0 && isset($rawInput['P'])){
        $rawInput["P"] = $rawInput["P"] + $multiplierP;
    }
    if($multiplierW != 1 && isset($rawInput['W'])){
        $rawInput["W"] = $rawInput["W"] * $multiplierW;
    }
    if($multiplierR != 1 && isset($rawInput['R'])){
        $rawInput["R"] = $rawInput["R"] * $multiplierR;
    }
    if($multiplierW != 1 && isset($rawInput['G'])){
        $rawInput["G"] = $rawInput["G"] * $multiplierW;
    }
    if($multiplierR != 1 && isset($rawInput['RR'])){
        $rawInput["RR"] = $rawInput["RR"] * $multiplierR;
    }

    // Check if extra sensors should be loaded
    $apiLog['info'][] = "Checking if extra sensors should be logged";
    $extraSensors = array();
    if(file_exists($base."update/apiSettings.txt")){
        $extraAPIRaw = json_decode(file_get_contents($base."update/apiSettings.txt"),true);
        foreach($extraAPIRaw as $extraParam=>$extraValue){
            if($extraValue==1){
                $extraSensors[] = $extraParam;
            }
        }
    }
    else{
        $apiLog['info'][] = "Extra sensor settings file not found.";
    }

    $apiLog['info'][] = "Extra sensors data to save in db: " . implode(", ",$extraSensors);


    // read previous live data
    if(file_exists($base."meteotemplateLive.txt")){
        // get latest date from cache file
        $mtLive = file_get_contents($base."meteotemplateLive.txt");
        $liveInput = json_decode($mtLive,true);
        if (is_null($liveInput)) {$liveInput = array();}
        
        if(array_key_exists("R".$timeId,$liveInput)){ // check if rain is from yesterday, if so, delete it
            $dayRain = date("Ymd",$liveInput['R'.$timeId]);
            $dayNow = date("Ymd");
            if($dayRain!=$dayNow){
                unset($liveInput['R']);
                unset($liveInput['RTime']);
            }
        }
        
        if(array_key_exists("ET".$timeId,$liveInput)){ // check if evapotranspiration is from yesterday, if so, delete it
            $dayET = date("Ymd",$liveInput['ET'.$timeId]);
            $dayNow = date("Ymd");
            if($dayET!=$dayNow){
                unset($liveInput['ET']);
                unset($liveInput['ETTime']);
            }
        }
        
        if(array_key_exists("L".$timeId,$liveInput)){ // check if lightning is from yesterday, if so, delete it
            $dayLightning = date("Ymd",$liveInput['L'.$timeId]);
            $dayNow = date("Ymd");
            if($dayLightning!=$dayNow){
                unset($liveInput['L']);
                unset($liveInput['LTime']);
            }
        }
        
        foreach ($liveInput as $key => $value) {
            $pos = strpos($key,$timeId);
            if ($pos > 0) {
                // time parameter
                if($utc-$value > 600) {
                    // value more than 10 minutes ago; skip writing back
                    $apiLog['info'][] = "Variable too old, remove from meteotemplateLive.txt: ".$key.": ".date("Y-m-d H:i:s",$value);
                    $keyValue = substr($key,0,$pos);
                    // remove value and timestamp from array
                    unset($liveInput[$keyValue]);  // remove value
                    unset($liveInput[$key]);       // remove timestamp
                }  
            }
            else {
                // value parameter
                $liveInput[$key] = $value;
            }
        }
    }
    // convert UGP to P when needed
    if(isset($rawInput['UGP']) && !isset($rawInput['P'])){
        $apiLog['info'][] = "convert UGP: ".$rawInput['UGP'];
        // get station elevation from config
        // $stationElevation
        // $stationElevationUnits - m/ft 
        // define constants for SLP calculations

        // convert elevation to m if in ft
        if($stationElevationUnits=="ft"){
            $elevationM = $stationElevation * 0.3048;
        }
        else{
            $elevationM = $stationElevation;
        }
        $apiLog['info'][] = "elevationM: ".$elevationM;
        $apiLog['info'][] = "liveInput['T']: ".$liveInput['T'];
        if(isset($liveInput['T'])){ // temperature is available, use more accurate formula
            $temperatureK = $liveInput['T'] + 273.15; // temperature in K
            $constant['g'] = 9.80665; // Earth-surface gravitational constant [m/s2]
            $constant['M'] = 0.0289644; // molar mass of dry air [kg/mol]
            $constant['R'] = 8.31447; // universal gas constant
            $partA = ($constant['g'] * $constant['M'] * $elevationM) / ($constant['R'] * $temperatureK);
            $convertedP = $rawInput['UGP'] / exp(- $partA);
            $apiLog['info'][] = "more accurate convertedP: ".$convertedP;
        }
        else{ // temperature is N/A use less accurate formula
            $convertedP = $rawInput['UGP'] - ($elevationM/9.2);
            $apiLog['info'][] = "less accurate convertedP: ".$convertedP;
        }
        $rawInput['P'] = (string)(round($convertedP,3));
        $rawInput['P'.$timeId] = $utc;
        $apiLog['info'][] = "calculated P: ".$rawInput['P'];
        // multiplier for converted UGP to P
        if($multiplierP != 0 && isset($rawInput['P'])){
            $rawInput["P"] = $rawInput["P"] + $multiplierP;
        }
    }
    
	if ($reloadLive)
	    {
        // add saved live data fields for not updated fields
        foreach ($liveInput as $key => $value) 
            {
            if(!isset($rawInput[$key]))
                {
                // if live data key not present use saved live data
                $rawInput[$key] = $value;
                // time fields ($rawInput[$key.$timeId]) will be copied too
                $apiLog['info'][] = "add live data: ".$key.": ".$value;
                }
            }
    
        // calculate dewpoint
        $rawInput['D'] = (string)dewpoint($rawInput['T'], $rawInput['H']);
        $rawInput['D'.$timeId] = $utc;
        $apiLog['info'][] = "calculated D: ".$rawInput['D'];
    
        // calculate apparent temp
        $rawInput['A'] = (string)apparent($rawInput['T'], $rawInput['H'], $rawInput['W'] / 3.6);
        $rawInput['A'.$timeId] = $utc;
        $apiLog['info'][] = "calculated A: ".$rawInput['A'];
    
        // save raw input
        $rawOutput = json_encode($rawInput,JSON_NUMERIC_CHECK);
        $apiLog['info'][] = "Save meteotemplateLive.txt: ".$rawOutput;
        file_put_contents($base."meteotemplateLive.txt",$rawOutput);
	    }
	else
	    {
        // calculate dewpoint
        if (isset($rawInput['T']) && isset($rawInput['H']) )
            {
            $rawInput['D'] = (string)dewpoint($rawInput['T'], $rawInput['H']);
            $rawInput['D'.$timeId] = $utc;
            $apiLog['info'][] = "calculated D: ".$rawInput['D'];
            }
    
        // calculate apparent temp
        if (isset($rawInput['T']) && isset($rawInput['H']) && isset($rawInput['W']) )
            {
            $rawInput['A'] = (string)apparent($rawInput['T'], $rawInput['H'], $rawInput['W'] / 3.6);
            $rawInput['A'.$timeId] = $utc;
            $apiLog['info'][] = "calculated A: ".$rawInput['A'];
            }
	    $rawInputOld = $rawInput;
        // add saved live data fields for not updated fields
        foreach ($liveInput as $key => $value) 
            {
            if(!isset($rawInput[$key]))
                {
                // if live data key not present use saved live data
                $rawInput[$key] = $value;
                // time fields ($rawInput[$key.$timeId]) will be copied too
                $apiLog['info'][] = "add live data: ".$key.": ".$value;
                }
            }
    
        // save raw input
        $rawOutput = json_encode($rawInput,JSON_NUMERIC_CHECK);
        $apiLog['info'][] = "Save meteotemplateLive.txt: ".$rawOutput;
        file_put_contents($base."meteotemplateLive.txt",$rawOutput);
        $rawInput = $rawInputOld;
	    }

    // check we have write permissions for the cache folder and meteotemplateLive.txt
    if(!file_exists($base."meteotemplateLive.txt")){
        $apiLog['error'][] = "meteotemplateLive.txt was not created! Probably incorrect permissions for the template root folder.";
        echo "Live conditions file not created. Check permissions!";
    }

    ############################################################################
    #                               PARSING
    ############################################################################

    $apiLog['info'][] = "Start data parsing";
    $convertUgp = false;

    // save live data fields to raw data
    foreach ($rawInput as $key => $value) {
        $rawData[$key] = $value;
    }
    // ESSENTIAL
    // Date and time
    $apiLog['info'][] = "Parsing date; Server time: ".date("Y-m-d H:i:s");
    $valid['date'] = true;

   if(!isset($rawData['U'])){
       $valid['date'] = false;
       $apiLog['error'][] = "No value for date specified.";
   }
   else{
       // date must be passed as UNIX timestamp
       // checking: date must be numeric, must be after 1990 and must not be in the future
       $apiLog['info'][] = "Validating date: ".$rawData['U']." (".date("Y-m-d H:i:s",$rawData['U']).")";
       if(is_numeric($rawData['U'])){
           if($rawData['U']>631152000 && $rawData['U']<(time()+10*60)){ // 10 minutes tolerance
               $data['timestamp'] = ($rawData['U']); // also save the timestamp
               $data['date'] = date("Y-m-d H:i:s",$rawData['U']); // convert to MySQL accepted format
               $apiLog['info'][] = "Date is valid; difference with server time is ".($rawData['U'] - $utc)." s" ;
           }
           else{
               $valid['date'] = false;
               $apiLog['error'][] = "Date is invalid; difference with server time is ".($rawData['U'] - $utc)." s";
           }
       }
       else{
           $valid['date'] = false;
           $apiLog['error'][] = "Date is invalid (not numeric)";
       }
   }

    ############################################################################
    // Parameters
    ############################################################################


    ############################################################################
    // Outside Temperature
    $apiLog['info'][] = "Parsing temperature";
    $valid['T'] = true;
    if(!isset($rawData['T'])){
        $valid['T'] = false;
        $apiLog['error'][] = "No temperature data provided.";
    }
    else{
        $rawT = trim($rawData['T']);
        $apiLog['info'][] = "Temperature: ".$rawT." C";
        if($rawT===""){
            $valid['T'] = false;
            $apiLog['error'][] = "Temperature field blank.";
        }
        elseif(!is_numeric($rawT)){
            $valid['T'] = false;
            $apiLog['error'][] = "Temperature is not a number.";
        }
    }
    // apply conversion - API uses Celsius
    if($valid['T']){
        $apiLog['info'][] = "Database units: ".$dataTempUnits;
        if($dataTempUnits!="C"){
            $rawT = convertor($rawT, "C", $dataTempUnits);
            $apiLog['info'][] = "Temperature converted to: ".$rawT." ".$dataTempUnits;
        }
        // check limits
        $apiLog['info'][] = "Checking temperature is between limits specified in template Main settings.";
        $apiLog['info'][] = "Minimum temperature limit: ".$limitTempMin." ".$dataTempUnits;
        $apiLog['info'][] = "Maximum temperature limit: ".$limitTempMax." ".$dataTempUnits;
        if($rawT>=$limitTempMin && $rawT<=$limitTempMax){
            $apiLog['info'][] = "Temperature is OK and within the allowed limits";
            $data['T'] = $rawT;
        }
        else{
            $apiLog['error'][] = "Temperature is outside the allowed limits.";
            $valid['T'] = false;
        }
    }

    ############################################################################
    // Outside Max Temperature
    $apiLog['info'][] = "Parsing maximum temperature";
    $valid['Tmax'] = true;
    if(!isset($rawData['TMX'])){
        $valid['Tmax'] = false;
        $apiLog['error'][] = "No max temperature data provided.";
    }
    else{
        $rawTmax = trim($rawData['TMX']);
        $apiLog['info'][] = "Max Temperature: ".$rawTmax." C";
        if($rawTmax===""){
            $valid['Tmax'] = false;
            $apiLog['error'][] = "Max Temperature field blank.";
        }
        elseif(!is_numeric($rawTmax)){
            $valid['Tmax'] = false;
            $apiLog['error'][] = "Max Temperature is not a number.";
        }
    }
    // apply conversion - API uses Celsius
    if($valid['Tmax']){
        $apiLog['info'][] = "Database units: ".$dataTempUnits;
        if($dataTempUnits!="C"){
            $rawTmax = convertor($rawTmax, "C", $dataTempUnits);
            $apiLog['info'][] = "Temperature converted to: ".$rawTmax." ".$dataTempUnits;
        }
        // check limits
        $apiLog['info'][] = "Checking max temperature is between limits specified in template Main settings.";
        $apiLog['info'][] = "Minimum temperature limit: ".$limitTempMin." ".$dataTempUnits;
        $apiLog['info'][] = "Maximum temperature limit: ".$limitTempMax." ".$dataTempUnits;
        if($rawTmax>=$limitTempMin && $rawTmax<=$limitTempMax){
            $apiLog['info'][] = "Max Temperature is OK and within the allowed limits";
            $data['Tmax'] = $rawTmax;
        }
        else{
            $apiLog['error'][] = "Max Temperature is outside the allowed limits.";
            $valid['Tmax'] = false;
        }
    }

    ############################################################################
    // Outside Min Temperature
    $apiLog['info'][] = "Parsing minimum temperature";
    $valid['Tmin'] = true;
    if(!isset($rawData['TMN'])){
        $valid['Tmin'] = false;
        $apiLog['error'][] = "No min temperature data provided.";
    }
    else{
        $rawTmin = trim($rawData['TMN']);
        $apiLog['info'][] = "Min Temperature: ".$rawTmin." C";
        if($rawTmin===""){
            $valid['Tmin'] = false;
            $apiLog['error'][] = "Min Temperature field blank.";
        }
        elseif(!is_numeric($rawTmin)){
            $valid['Tmin'] = false;
            $apiLog['error'][] = "Min Temperature is not a number.";
        }
    }
    // apply conversion - API uses Celsius
    if($valid['Tmin']){
        $apiLog['info'][] = "Database units: ".$dataTempUnits;
        if($dataTempUnits!="C"){
            $rawTmin = convertor($rawTmin, "C", $dataTempUnits);
            $apiLog['info'][] = "Temperature converted to: ".$rawTmin." ".$dataTempUnits;
        }
        // check limits
        $apiLog['info'][] = "Checking min temperature is between limits specified in template Main settings.";
        $apiLog['info'][] = "Minimum temperature limit: ".$limitTempMin." ".$dataTempUnits;
        $apiLog['info'][] = "Maximum temperature limit: ".$limitTempMax." ".$dataTempUnits;
        if($rawTmin>=$limitTempMin && $rawTmin<=$limitTempMax){
            $apiLog['info'][] = "Min Temperature is OK and within the allowed limits";
            $data['Tmin'] = $rawTmin;
        }
        else{
            $apiLog['error'][] = "Min Temperature is outside the allowed limits.";
            $valid['Tmin'] = false;
        }
    }

    ############################################################################
    // Humidity
    $apiLog['info'][] = "Parsing humidity";
    $valid['H'] = true;
    if(!isset($rawData['H'])){
        $valid['H'] = false;
        $apiLog['error'][] = "No humidity data provided.";
    }
    else{
        $rawH = trim($rawData['H']);
        $apiLog['info'][] = "Humidity: ".$rawH." percent";
        if($rawH===""){
            $valid['H'] = false;
            $apiLog['error'][] = "Humidity field blank.";
        }
        elseif(!is_numeric($rawH)){
            $valid['H'] = false;
            $apiLog['error'][] = "Humidity is not a number.";
        }
    }

    if($valid['H']){
        // discard nonsense
        if($rawH<0 && $rawH>100){
            $valid['H'] = false;
            $apiLog['error'][] = "Humidity is outside sensible range (0-100%).";
        }
        else{
            // check limits from main settings
            $apiLog['info'][] = "Checking humidity is between limits specified in template Main settings.";
            $apiLog['info'][] = "Minimum humidity limit: ".$limitHumidityMin." %";
            $apiLog['info'][] = "Maximum humidity limit: ".$limitHumidityMax." %";
            if($rawH>=$limitHumidityMin && $rawH<=$limitHumidityMax){
                $apiLog['info'][] = "Humidity is OK and within the allowed limits";
                $data['H'] = $rawH;
            }
            else{
                $apiLog['error'][] = "Humidity is outside the allowed limits.";
                $valid['H'] = false;
            }
        }
    }

    ############################################################################
    // Wind Speed
    $apiLog['info'][] = "Parsing wind speed";
    $valid['W'] = true;
    if(!isset($rawData['W'])){
        $valid['W'] = false;
        $apiLog['error'][] = "No wind speed data provided.";
    }
    else{
        $rawW = trim($rawData['W']);
        $apiLog['info'][] = "Wind speed: ".$rawW." kmh";
        if($rawW===""){
            $valid['W'] = false;
            $apiLog['error'][] = "Wind speed field blank.";
        }
        elseif(!is_numeric($rawW)){
            $valid['W'] = false;
            $apiLog['info'][] = "Wind speed is not a number.";
        }
    }
    // apply conversion - API uses km/h
    if($valid['W']){
        $apiLog['info'][] = "Database wind speed units: ".$dataWindUnits;
        if($dataWindUnits!="kmh"){
            $rawW = convertor($rawW, "kmh", $dataWindUnits);
            $apiLog['info'][] = "Wind speed  converted to: ".$rawW." ".$dataWindUnits;
        }
        // discard nonsense
        if($rawW<0){
            $valid['W'] = false;
            $apiLog['error'][] = "Wind speed cannot be negative.";
        }
        else{
            // check limits
            $apiLog['info'][] = "Checking wind speed is between limits specified in template Main settings.";
            $apiLog['info'][] = "Minimum wind speed limit: ".$limitWindMin." ".$dataWindUnits;
            $apiLog['info'][] = "Maximum wind speed limit: ".$limitWindMax." ".$dataWindUnits;
            if($rawW>=$limitWindMin && $rawW<=$limitWindMax){
                $apiLog['info'][] = "Wind speed is OK and within the allowed limits";
                $data['W'] = $rawW;
            }
            else{
                $apiLog['error'][] = "Wind speed is outside the allowed limits.";
                $valid['W'] = false;
            }
        }
    }

    ############################################################################
    // Wind Gust
    $apiLog['info'][] = "Parsing wind gust";
    $valid['G'] = true;
    if(!isset($rawData['G'])){
        $valid['G'] = false;
        $apiLog['error'][] = "No wind gust data provided.";
    }
    else{
        $rawG = trim($rawData['G']);
        $apiLog['info'][] = "Wind gust: ".$rawG." kmh";
        if($rawG===""){
            $valid['G'] = false;
            $apiLog['error'][] = "Wind gust field blank.";
        }
        elseif(!is_numeric($rawG)){
            $valid['G'] = false;
            $apiLog['error'][] = "Wind gust is not a number.";
        }
    }
    // apply conversion - API uses km/h
    if($valid['G']){
        $apiLog['info'][] = "Database wind units: ".$dataWindUnits;
        if($dataWindUnits!="kmh"){
            $rawG = convertor($rawG, "kmh", $dataWindUnits);
            $apiLog['info'][] = "Wind gust  converted to: ".$rawG." ".$dataWindUnits;
        }
        // discard nonsense
        if($rawG<0){
            $valid['G'] = false;
            $apiLog['error'][] = "Wind gust cannot be negative.";
        }
        else{
            // check limits
            $apiLog['info'][] = "Checking wind gust is between limits specified in template Main settings.";
            $apiLog['info'][] = "Minimum wind gust limit: ".$limitWindMin." ".$dataWindUnits;
            $apiLog['info'][] = "Maximum wind gust limit: ".$limitWindMax." ".$dataWindUnits;
            if($rawG>=$limitWindMin && $rawG<=$limitWindMax){
                $apiLog['info'][] = "Wind gust is OK and within the allowed limits";
                $data['G'] = $rawG;
            }
            else{
                $apiLog['error'][] = "Wind gust is outside the allowed limits.";
                $valid['G'] = false;
            }
        }
    }

    ############################################################################
    // Wind direction (bearing)
    $apiLog['info'][] = "Parsing wind direction";
    $valid['B'] = true;
    if(!isset($rawData['B'])){
        $valid['B'] = false;
        $apiLog['error'][] = "No wind direction data provided.";
    }
    else{
        $rawB = trim($rawData['B']);
        $apiLog['info'][] = "Wind direction: ".$rawB." degrees";
        if($rawB===""){
            $valid['B'] = false;
            $apiLog['error'][] = "Wind direction field blank.";
        }
        elseif(!is_numeric($rawB)){
            $valid['B'] = false;
            $apiLog['error'][] = "Wind direction is not a number.";
        }
    }

    if($valid['B']){
        // discard nonsense
        if($rawB<0 && $rawB>360){
            $valid['B'] = false;
            $apiLog['error'][] = "Wind direction is outside sensible range (0-360 degrees).";
        }
        else{
            $apiLog['info'][] = "Wind direction is OK and within the allowed limits";
            $data['B'] = $rawB;
        }
    }

    ############################################################################
    // Precipitation
    $apiLog['info'][] = "Parsing daily cumulative precipitation";
    $valid['R'] = true;
    if(!isset($rawData['R'])){
        $valid['R'] = false;
        $apiLog['error'][] = "No precipitation data provided.";
    }
    else{
        $rawR = trim($rawData['R']);
        $apiLog['info'][] = "Precipitation: ".$rawR." mm";
        if($rawR===""){
            $valid['R'] = false;
            $apiLog['error'][] = "Precipitation field blank.";
        }
        elseif(!is_numeric($rawR)){
            $valid['R'] = false;
            $apiLog['error'][] = "Precipitation is not a number.";
        }
    }
    // apply conversion - API uses mm
    if($valid['R']){
        $apiLog['info'][] = "Database precipitation units: ".$dataRainUnits;
        if($dataRainUnits!="mm"){
            $rawR = convertor($rawR, "mm", $dataRainUnits);
            $apiLog['info'][] = "Precipitation  converted to: ".$rawR." ".$dataRainUnits;
        }
        // discard nonsense
        if($rawR<0){
            $valid['R'] = false;
            $apiLog['error'][] = "Precipitation cannot be negative.";
        }
        else{
            // check limits
            $apiLog['info'][] = "Checking precipitation is between limits specified in template Main settings.";
            $apiLog['info'][] = "Minimum precipitation limit: ".$limitRainMin." ".$dataRainUnits;
            $apiLog['info'][] = "Maximum precipitation limit: ".$limitRainMax." ".$dataRainUnits;
            if($rawR>=$limitRainMin && $rawR<=$limitRainMax){
                $apiLog['info'][] = "Precipitation is OK and within the allowed limits";
                $data['R'] = $rawR;
            }
            else{
                $apiLog['error'][] = "Precipitation is outside the allowed limits.";
                $valid['R'] = false;
            }
        }
    }

    ############################################################################
    // Rain rate
    $apiLog['info'][] = "Parsing rain rate";
    $valid['RR'] = true;
    if(!isset($rawData['RR'])){
        $valid['RR'] = false;
        $apiLog['error'][] = "No rain rate data provided.";
    }
    else{
        $rawRR = trim($rawData['RR']);
        $apiLog['info'][] = "Rain rate: ".$rawRR." mm/h";
        if($rawRR===""){
            $valid['RR'] = false;
            $apiLog['error'][] = "Rain rate field blank.";
        }
        elseif(!is_numeric($rawR)){
            $valid['RR'] = false;
            $apiLog['error'][] = "Rain rate is not a number.";
        }
    }
    // apply conversion - API uses mm/h
    if($valid['RR']){
        $apiLog['info'][] = "Database rain units: ".$dataRainUnits."/h";
        if($dataRainUnits!="mm"){
            $rawRR = convertor($rawRR, "mm", $dataRainUnits);
            $apiLog['info'][] = "Rain rate  converted to: ".$rawRR." ".$dataRainUnits."/h";
        }
        // discard nonsense
        if($rawRR<0){
            $valid['RR'] = false;
            $apiLog['error'][] = "Rain rate cannot be negative.";
        }
        else{
            // check limits
            $apiLog['info'][] = "Checking rain rate is between limits specified in template Main settings.";
            $apiLog['info'][] = "Minimum rain rate limit: ".$limitRainRateMin." ".$dataRainUnits."/h";
            $apiLog['info'][] = "Maximum rain rate limit: ".$limitRainRateMax." ".$dataRainUnits."/h";
            if($rawRR>=$limitRainRateMin && $rawRR<=$limitRainRateMax){
                $apiLog['info'][] = "Rain rate is OK and within the allowed limits";
                $data['RR'] = $rawRR;
            }
            else{
                $apiLog['error'][] = "Rain rate is outside the allowed limits.";
                $valid['RR'] = false;
            }
        }
    }

    ############################################################################
    // Solar radiation
    $valid['S'] = true;
    if($solarSensor){
        $apiLog['info'][] = "Parsing solar radiation";
        if(!isset($rawData['S'])){
            $valid['S'] = false;
            $apiLog['error'][] = "No solar radiation data provided.";
        }
        else{
            $rawS = trim($rawData['S']);
            $apiLog['info'][] = "Solar radiation: ".$rawS." w/m2";
            if($rawS===""){
                $valid['S'] = false;
                $apiLog['error'][] = "Solar radiation field blank.";
            }
            elseif(!is_numeric($rawS)){
                $valid['S'] = false;
                $apiLog['error'][] = "Solar radiation is not a number.";
            }
        }

        if($valid['S']){
            // discard nonsense
            if($rawS<0){
                $valid['S'] = false;
                $apiLog['error'][] = "Solar radiation cannot be negative.";
            }
            else{
                // check limits from main settings
                $apiLog['info'][] = "Checking solar radiation is between limits specified in template Main settings.";
                $apiLog['info'][] = "Minimum solar radiation limit: ".$limitSolarMin." W/m2";
                $apiLog['info'][] = "Maximum solar radiation limit: ".$limitSolarMax." W/m2";
                if($rawS>=$limitSolarMin && $rawS<=$limitSolarMax){
                    $apiLog['info'][] = "Solar radiation is OK and within the allowed limits";
                    $data['S'] = $rawS;
                }
                else{
                    $apiLog['error'][] = "Solar radiation is outside the allowed limits.";
                    $valid['S'] = false;
                }
            }
        }
    }
    else{
        $valid['S'] = false;
        $apiLog['info'][] = "Solar radiation sensor disabled in Main settings - skipping.";
    }

    ############################################################################
    // EXTRA SENSORS
    ############################################################################
    // Now parse extra sensors
    if ( file_exists($base."update/apiSettingsEx.php") )
        {require_once($base."update/apiSettingsEx.php");}
    else
        {
        // for compatibiliti with non extended extra sensor ( with standard meteotemplate )
        $extraSensorsEx = array();
        foreach($extraSensors as $extraSensor)
            {
            if ($extraSensor == 'TIN') $extraSensorsEx['TIN']=array('descr'=>'Indoor temperature','limits'=>array(1,50),'unit'=>'&deg;C','decimals'=>1);
            if ($extraSensor == 'HIN') $extraSensorsEx['HIN']=array('descr'=>'Indoor humidity','limits'=>array(0,100),'unit'=>'%','decimals'=>1);
            if ($extraSensor == 'UV')  $extraSensorsEx['UV']=array('descr'=>'UV','limits'=>array(0,20),'unit'=>'','decimals'=>1);
            if ($extraSensor == 'SN')  $extraSensorsEx['SN']=array('descr'=>'Snowfall','limits'=>array(0,5000),'unit'=>'mm','decimals'=>0);
            if ($extraSensor == 'SD')  $extraSensorsEx['SD']=array('descr'=>'Snow depth','limits'=>array(0,10000),'unit'=>'mm','decimals'=>0);
            if ($extraSensor == 'NL')  $extraSensorsEx['NL']=array('descr'=>'Noise level','limits'=>array(0,200),'unit'=>'dB','decimals'=>1);
            if ($extraSensor == 'L')   $extraSensorsEx['L']=array('descr'=>'Lightning','limits'=>array(0,10000),'unit'=>'','decimals'=>0);
            if ($extraSensor == 'LD')  $extraSensorsEx['LD']=array('descr'=>'Lightning distance','limits'=>array(0,100),'unit'=>'Km','decimals'=>0);
            if ($extraSensor == 'SS')  $extraSensorsEx['LT']=array('descr'=>'Lightning time','limits'=>array(0,10000000000),'unit'=>'','decimals'=>0);
            if ($extraSensor == 'SS')  $extraSensorsEx['SS']=array('descr'=>'Sunshine','limits'=>array(0,24),'unit'=>'h','decimals'=>1);
            if ($extraSensor == 'ET')  $extraSensorsEx['ET']=array('descr'=>'Evapotranspiration','limits'=>array(0,24),'unit'=>'mm','decimals'=>3);

            if (sensor_like($extraSensor,'T#')) $extraSensorsEx[$extraSensor]=array('descr'=>'Extra temperature sensor '.$extraSensor,'limits'=>array(-60,60),'unit'=>'&deg;C','decimals'=>1);
            if (sensor_like($extraSensor,'H#')) $extraSensorsEx[$extraSensor]=array('descr'=>'Extra humidity sensor '.$extraSensor,'limits'=>array(0,100),'unit'=>'%','decimals'=>1,'save'=>1);
            if (sensor_like($extraSensor,'TS#')) $extraSensorsEx[$extraSensor]=array('descr'=>'Soil temperature sensor '.$extraSensor,'limits'=>array(-60,80),'unit'=>'&deg;C','decimals'=>1);
            if (sensor_like($extraSensor,'SM#')) $extraSensorsEx[$extraSensor]=array('descr'=>'Soil moisture sensor '.$extraSensor,'limits'=>array(0,200),'unit'=>'','decimals'=>1,'save'=>1);
            if (sensor_like($extraSensor,'LT#')) $extraSensorsEx[$extraSensor]=array('descr'=>'Leaf temperature sensor '.$extraSensor,'limits'=>array(-60,80),'unit'=>'&deg;C','decimals'=>1);
            if (sensor_like($extraSensor,'LW#')) $extraSensorsEx[$extraSensor]=array('descr'=>'Leaf wettness sensor '.$extraSensor,'limits'=>array(0,15),'unit'=>'','decimals'=>1);
            if (sensor_like($extraSensor,'CO2_#')) $extraSensorsEx[$extraSensor]=array('descr'=>'CO2 sensor '.$extraSensor,'limits'=>array(300,600),'unit'=>'ppm','decimals'=>0);
            if (sensor_like($extraSensor,'CO_#')) $extraSensorsEx[$extraSensor]=array('descr'=>'CO sensor '.$extraSensor,'limits'=>array(0.1,50),'unit'=>'ppm','decimals'=>0);
            if (sensor_like($extraSensor,'NO2_#')) $extraSensorsEx[$extraSensor]=array('descr'=>'NO2 sensor '.$extraSensor,'limits'=>array(0,10),'unit'=>'ppm','decimals'=>0);
            if (sensor_like($extraSensor,'SO2_#')) $extraSensorsEx[$extraSensor]=array('descr'=>'SO2 sensor '.$extraSensor,'limits'=>array(0,1000),'unit'=>'ppb','decimals'=>0);
            if (sensor_like($extraSensor,'O3_#')) $extraSensorsEx[$extraSensor]=array('descr'=>'O3 sensor '.$extraSensor,'limits'=>array(0,100),'unit'=>'ppb','decimals'=>0);
            if (sensor_like($extraSensor,'PP#')) $extraSensorsEx[$extraSensor]=array('descr'=>'Particulate pollution '.$extraSensor,'limits'=>array(0,1000),'unit'=>'ug/m3','decimals'=>0);
            }
        }
    if ( (count8($extraSensors)>0) ){
        
        $extraQueryParams = array();
        $extraQueryValues = array();
         $apiLog['info'][] = "Now parsing extra sensors.";
         foreach($extraSensors as $extraSensor){
             if(isset($rawData[$extraSensor])){
                $thisSensorVal = $rawData[$extraSensor];
                $apiLog['info'][] = "Sensor ".$extraSensorsEx[$extraSensor]['descr']." raw value: " . $thisSensorVal . " " . $extraSensorsEx[$extraSensor]['unit'];
                $apiLog['info'][] = "Limits for this sensor: " . $extraSensorsEx[$extraSensor]['limits'][0] ." to " . $extraSensorsEx[$extraSensor]['limits'][1];
                if($thisSensorVal >= $extraSensorsEx[$extraSensor]['limits'][0] && $thisSensorVal <= $extraSensorsEx[$extraSensor]['limits'][1] && is_numeric($thisSensorVal)){
                    $apiLog['info'][] = "Sensor data within acceptable limits.";
                    $extraQueryParams[] = $extraSensor;
                    $extraQueryValues[] = number_format($thisSensorVal, $extraSensorsEx[$extraSensor]['decimals'], ".", "");
                }
                else{
                    $apiLog['info'][] = "Sensor data outside acceptable limits or value invalid.";
                }
             }
             else{
                 $apiLog['info'][] = "Sensor: ".$extraSensorsEx[$extraSensor]['descr']." not found in API file, skipping...";
             }
         }
    }
    else{
         $apiLog['info'][] = "No extra sensors set to log to extra alldata table.";
    }

    ############################################################################
    // Create/load cache file
    ############################################################################

    // first check how old the cache is, if it is older than 30 minutes delete it to prevent averaging current values with very old values when station offline
    if(file_exists($base."cache/apiCache.txt")){
        // get latest date from cache file
        $cacheRaw = file_get_contents($base."cache/apiCache.txt");
        $cache = json_decode($cacheRaw,true);
        $latestCacheDate = $cache['timestamp'][count8($cache['timestamp'])-1];
        if (time()-$latestCacheDate > 60 * 30) {
            unlink($base."cache/apiCache.txt");
            $apiLog['info'][] = "Cache file is over 30 minutes old, deleting it.";
        }
    }

    // cache file exists - i.e. it is valid, load it
    if(file_exists($base."cache/apiCache.txt")){
        $cacheRaw = file_get_contents($base."cache/apiCache.txt");
        $cache = json_decode($cacheRaw,true);
        $apiLog['info'][] = "Cached data loaded from cache/apiCache.txt.";
    }
    // cache file does not exist, create empty file
    else{
        $cache = array();
        $apiLog['info'][] = "No cache file found, create empty file.";
    }

    
    ############################################################################
    // Check for partial data
    ############################################################################
    
    // Check for partial data in cache when data is not present
    $validCachedT = false;
    if(!isset($rawData['T']) && $reloadLive){
        // use cached data instead when present
        if(count8($cache['T']) > 0) {
            $T = $cache['T'][count8($cache['T'])-1];
            $validCachedT = true;
            $apiLog['info'][] = "Use cached Temperature for calculations: ".$T;
        }
    }
    else {
        if($valid['T']) {
            $T = $data['T'];
        }
    }

    $validCachedH = false;
    if(!isset($rawData['H']) && $reloadLive){
        // use cached data instead when present
        if(count8($cache['H']) > 0) {
            $H = $cache['H'][count8($cache['H'])-1];
            $validCachedH = true;
            $apiLog['info'][] = "Use cached Humidity for calculations: ".$H;
        }
    }
    else {
        if($valid['H']) {
            $H = $data['H'];
        }
    }
        
    $validCachedW = false;
    if(!isset($rawData['W']) && $reloadLive){
        // use cached data instead when present
        if(count8($cache['W']) > 0) {
            $W = $cache['W'][count8($cache['W'])-1];
            $validCachedW = true;
            $apiLog['info'][] = "Use cached Wind for calculations: ".$W;
        }
    }
    else {
        if($valid['W']) {
            $W = $data['W'];
        }
    }
        
    ############################################################################
    //  Unadjusted gauge pressure
    // convert UGP to P when needed
    if(isset($rawData['UGP']) && !isset($rawData['P'])){
        $convertUgp = true;
        $apiLog['info'][] = "convert UGP: ".$rawData['UGP'];
        // get station elevation from config
        // $stationElevation
        // $stationElevationUnits - m/ft 
        // define constants for SLP calculations

        // convert elevation to m if in ft
        if($stationElevationUnits=="ft"){
            $elevationM = $stationElevation * 0.3048;
        }
        else{
            $elevationM = $stationElevation;
        }
        $apiLog['info'][] = "elevationM: ".$elevationM;
        if($valid['T'] || $validCachedT){ // temperature is available, use more accurate formula
            $apiLog['info'][] = "T: ".$T;
            $temperatureK = $T + 273.15; // temperature in K
            $constant['g'] = 9.80665; // Earth-surface gravitational constant [m/s2]
            $constant['M'] = 0.0289644; // molar mass of dry air [kg/mol]
            $constant['R'] = 8.31447; // universal gas constant
            $partA = ($constant['g'] * $constant['M'] * $elevationM) / ($constant['R'] * $temperatureK);
            $convertedP = $rawData['UGP'] / exp(- $partA);
            $apiLog['info'][] = "more accurate convertedP: ".$convertedP;
            $rawData['P'] = (string)(round($convertedP,3));
        }
        else{ // temperature is N/A skip calculation
            $apiLog['info'][] = "No valid temp, skip less accurate calculation ";
        }
    }

    ############################################################################
    // Barometric pressure
    $apiLog['info'][] = "Parsing pressure";
    $valid['P'] = true;
    if(!isset($rawData['P'])){
        $valid['P'] = false;
        $apiLog['error'][] = "No pressure data provided.";
    }
    else{
        $rawP = trim($rawData['P']);
        $apiLog['info'][] = "Pressure: ".$rawP." hpa";
        if($rawP===""){
            $valid['P'] = false;
            $apiLog['error'][] = "Pressure field blank.";
        }
        elseif(!is_numeric($rawP)){
            $valid['P'] = false;
            $apiLog['error'][] = "Pressure is not a number.";
        }
    }
    // apply conversion - API uses hectopascals
    if($valid['P']){
        $apiLog['info'][] = "Database pressure units: ".$dataPressUnits;
        if($dataPressUnits!="hpa"){
            $rawP = convertor($rawP, "hpa", $dataPressUnits);
            $apiLog['info'][] = "Pressure  converted to: ".$rawP." ".$dataPressUnits;
        }
        // check limits
        $apiLog['info'][] = "Checking pressure is between limits specified in template Main settings.";
        $apiLog['info'][] = "Minimum pressure limit: ".$limitPressureMin." ".$dataPressUnits;
        $apiLog['info'][] = "Maximum pressure limit: ".$limitPressureMax." ".$dataPressUnits;
        if($rawP>=$limitPressureMin && $rawP<=$limitPressureMax){
            $apiLog['info'][] = "Pressure is OK and within the allowed limits";
            $data['P'] = $rawP;
            $apiLog['info'][] = "Pressure is valid.";
        }
        else{
            $apiLog['error'][] = "Pressure is outside the allowed limits.";
            $valid['P'] = false;
        }
    }

    ############################################################################
    // Calculations
    ############################################################################

    ############################################################################
    // Dew point
    // essential: valid temperature and valid humidity
    $valid['D'] = true;
    if(($valid['T'] || $validCachedT) && ($valid['H'] || $validCachedH)){
        // convert temperature to Celsius if necessary
        if($dataTempUnits=="F"){
            $temperatureC = convertor($T,"F","C");
        }
        else{
            $temperatureC = $T;
        }
        $rawDCelsius = dewpoint($temperatureC, $H);
        $apiLog['info'][] = "rawDCelsius: ".$rawDCelsius." ".$temperatureC." ".$H;
        // convert back to Farenheit if necessary
        if($dataTempUnits=="F"){
            $rawD = convertor($rawDCelsius,"C","F");
        }
        else{
            $rawD = $rawDCelsius;
        }
        $apiLog['info'][] = "Calculated dew point: ".$rawD." ".$dataTempUnits;
        // just do some basic checking, but should be ok since T and H both valid
        if(is_numeric($rawD) && $rawD!=="" && !is_null($rawD) && $rawD>-100 && $rawD<200){
            $data['D'] = $rawD;
            $apiLog['info'][] = "Dew point ok.";
        }
        else{
            $apiLog['error'][] = "There is some problem with the calculated dew point value. Ignored.";
            $valid['D'] = false;
        }
    }
    else{
        $valid['D'] = false;
        $apiLog['info'][] = "Temperature, humidity or both are not valid and dew point calculation is therefore skipped.";
    }

    ############################################################################
    // Apparent Temperature
    // essential: valid temperature, valid humidity and valid wind speed
    $valid['A'] = true;

    if(($valid['T'] || $validCachedT) && ($valid['H'] || $validCachedH) && ($valid['W'] || $validCachedW)){
        // convert temperature to Celsius if necessary
        if($dataTempUnits=="F"){
            $temperatureC = convertor($T,"F","C");
        }
        else{
            $temperatureC = $T;
        }
        if($dataWindUnits!="ms"){
            $windMS = convertor($W,$dataWindUnits,"ms");
        }
        else{
            $windMS = $W;
        }
        $rawACelsius = apparent($temperatureC, $H, $windMS);
        // convert back to Farenheit if necessary
        if($dataTempUnits=="F")
        {
            $rawA = convertor($rawACelsius,"C","F");
        }
        else{
            $rawA = $rawACelsius;
		}
        $apiLog['info'][] = "Calculated apparent temperature: ".$rawA." ".$dataTempUnits;
        // just do some basic checking, but should be ok since T, H and W both valid
        if(is_numeric($rawA) && $rawA!=="" && !is_null($rawA) && $rawA>-100 && $rawA<200){
            $data['A'] = $rawA;
            $apiLog['info'][] = "Apparent temperature ok. ".$data['A'];
        }
        else{
            $apiLog['error'][] = "There is some problem with the calculated apparent temperature value. Ignored.";
            $valid['A'] = false;
        }
    }
    else{
        $valid['A'] = false;
        $apiLog['info'][] = "Temperature, humidity, wind speed or their combination are not valid and apparent temperature calculation is therefore skipped.";
    }


    ############################################################################
    // Check db update / update db
    ############################################################################

    $dbInterval = 300;
    
    // check if cache file contain a record
    if(isset($cache["timestamp"]) && (count8($cache["timestamp"]) > 0) ) {
        // timeForUpdate calculated from timestamp of first cached data
        $checkTimestamp = $cache["timestamp"][0];
        $apiLog['info'][] = "End time for database update based on timestamp of first cache data: ".date("Y-m-d H:i:s",$checkTimestamp);
    }
    else {
        // timeForUpdate calculated from timestamp of current data
        $checkTimestamp = $data['timestamp'];
        $apiLog['info'][] = "End time for database update based on timestamp of current data: ".date("Y-m-d H:i:s",$checkTimestamp);
    }
    if($checkTimestamp % $dbInterval == 0) {
        $timeForUpdate = $checkTimestamp;  // $checkTimestamp is equal to end time of current archive period   
    }
    else {
        $timeForUpdate = floor($checkTimestamp / $dbInterval) * $dbInterval + $dbInterval;  // end time of current archive period  
    }
    $apiLog['info'][] = "Rounded end time for database update: ".date("Y-m-d H:i:s",$timeForUpdate);
    
    $updateTime = false;
    if($data['timestamp'] >= $timeForUpdate){
        $updateTime = true;
    }
    if($data['timestamp'] == $timeForUpdate){
        // addDataToCache - current record belongs to the current archive set
        ############################################################################
        // Add current data set to cache
        ############################################################################

        // only do something if we have a valid date
        if($valid['date']){
            // add current data set to either empty cache file or add to existing cache
            $cache['timestamp'][] = $data['timestamp'];
            $cache['date'][] = $data['date'];
            if($valid['T']){
                $cache['T'][] = $data['T'];
            }
            if($valid['Tmax']){
                $cache['Tmax'][] = $data['Tmax'];
            }
            if($valid['Tmin']){
                $cache['Tmin'][] = $data['Tmin'];
            }
            if($valid['H']){
                $cache['H'][] = $data['H'];
            }
            if($valid['P']){
                $cache['P'][] = $data['P'];
            }
            if($valid['W']){
                $cache['W'][] = $data['W'];
            }
            if($valid['G']){
                $cache['G'][] = $data['G'];
            }
            if($valid['B']){
                $cache['B'][] = $data['B'];
            }
            if($valid['R']){
                // rain is cumulative, yet we cache for newDay checks
                $cache['R'][] = $data['R']; 
            }
            if($valid['RR']){
                $cache['RR'][] = $data['RR'];
            }
            if($valid['S']){
                $cache['S'][] = $data['S'];
            }
            if($valid['D']){
                $cache['D'][] = $data['D'];
            }
            if($valid['A']){
                $cache['A'][] = $data['A'];
            }

            // extra parameters
            if(count8($extraQueryParams)>0){
                for($j = 0; $j < count8($extraQueryParams); $j++){
                    $cache[$extraQueryParams[$j]][] = $extraQueryValues[$j];
                }
            }
        }
    }

    // if time for update, do the update
    if($updateTime){
        $apiLog['info'][] = "Time to update the database, preparing query.";
        // create the column name array for db and values array and insert date
        $db['parameters'][] = "DateTime";
        $firstCacheDate = $cache['timestamp'][0]; 
        $lastCacheDate = $cache['timestamp'][count8($cache['timestamp'])-1]; 
        $apiLog['info'][] = "Timestamp first cached record: ".date("Y-m-d H:i:s",$firstCacheDate);
        $apiLog['info'][] = "Timestamp last cached record: ".date("Y-m-d H:i:s",$lastCacheDate);
        $apiLog['info'][] = "Timestamp last received record: ".date("Y-m-d H:i:s",$data['timestamp']);
        $apiLog['info'][] = "Timestamp new database record: ".date("Y-m-d H:i:s",$timeForUpdate);

        // check if this update is at 00:00:00
        $dayUpdateMin1 = date("Ymd",$timeForUpdate-1);
        $dayUpdate = date("Ymd",$timeForUpdate);
        if($dayUpdateMin1!=$dayUpdate){
           // start of a new day at 00:00:00
           $newDay = true;
        }
        else {
           $newDay = false;
        }
        $db['fields'][] = "'".date("Y-m-d H:i:s",$timeForUpdate)."'";
        
        // average temperature
        if(isset($cache['T'])){
            $db['parameters'][] = "T";
            // take temperature average and convert to db units
            $rawValue = array_sum($cache['T'])/count8($cache['T']);
            $db['fields'][] = number_format($rawValue,1,".","");
        }

        // maximum temperature
        if(isset($cache['Tmax'])){
            $db['parameters'][] = "Tmax";
            // take maximum temperature and convert to db units
            $rawValue = max($cache['Tmax']);
            $db['fields'][] = number_format($rawValue,1,".","");
        }
        else{ // if Tmax is not available see if T is and if so, use that
            if(isset($cache['T'])){
                $db['parameters'][] = "Tmax";
                // take maximum and convert to db units
                $rawValue = max($cache['T']);
                $db['fields'][] = number_format($rawValue,1,".","");
            }
        }

        // minimum temperature
        if(isset($cache['Tmin'])){
            $db['parameters'][] = "Tmin";
            // take minimum temperature and convert to db units
            $rawValue = min($cache['Tmin']);
            $db['fields'][] = number_format($rawValue,1,".","");
        }
        else{ // if Tmin is not available see if T is and if so, use that
            if(isset($cache['T'])){
                $db['parameters'][] = "Tmin";
                // take minimum and convert to db units
                $rawValue = min($cache['T']);
                $db['fields'][] = number_format($rawValue,1,".","");
            }
        }

        // relative humidity
        if(isset($cache['H'])){
            $db['parameters'][] = "H";
            // take humidity average
            $rawValue = array_sum($cache['H'])/count8($cache['H']);
            $db['fields'][] = number_format($rawValue,1,".","");
        }

        // barometric pressure
        // determine necessary number of decimal places for pressure
        if($dataPressUnits=="inhg"){
            $decimalsP = 2;
        }
        else{
            $decimalsP = 1;
        }
        if(isset($cache['P'])){
            $db['parameters'][] = "P";
            // take pressure average
            $rawValue = array_sum($cache['P'])/count8($cache['P']);
            $db['fields'][] = number_format($rawValue,$decimalsP,".","");
        }

        // wind speed
        if(isset($cache['W'])){
            $db['parameters'][] = "W";
            // take wind average
            $rawValue = array_sum($cache['W'])/count8($cache['W']);
            $db['fields'][] = number_format($rawValue,1,".","");
        }

        // wind gust
        if(isset($cache['G'])){
            $db['parameters'][] = "G";
            // take wind gust maximum
            $rawValue = max($cache['G']);
            $db['fields'][] = number_format($rawValue,1,".","");
        }
        else{
           // no gust data cached, take max wind speed instead
           if(isset($cache['W'])){
               $db['parameters'][] = "G";
               // take wind total
               $rawValue = max($cache['W']);
               $db['fields'][] = number_format($rawValue,1,".","");
           }
       }

        // wind bearing
        if(isset($cache['B'])){
            $db['parameters'][] = "B";
            // take wind bearing average
            $rawValue = avgWindUpdate($cache['B']);
            $db['fields'][] = number_format($rawValue,0,".","");
        }

        // precipitation
        // determine necessary number of decimal places for precipitation
        if($dataRainUnits=="in"){
            $decimalsR = 2;
        }
        else{
            $decimalsR = 1;
        }
        if(isset($cache['R'])){
            $db['parameters'][] = "R";
            // take cumulative daily rain and round it to the desired number of decimals
            // use max value for check and update of last days registration
            $maxRain = number_format(max($cache['R']),$decimalsR,".","");
            // use last value for other periods
            $currentRain = number_format($cache['R'][count8($cache['R'])-1],$decimalsR,".","");

            // check if newDay
            if($newDay){
                $apiLog['info'][] = "First database record in new day; check rain values";
                $previousRain = "";
                // read previous rain from database
                $queryRain = "SELECT DateTime, R FROM alldata ORDER BY DateTime DESC LIMIT 1";
                $thisQuery = mysqli_query($con,$queryRain);
                if(!$thisQuery){
                    $apiLog['error'][] = "Meteotemplate MySQL Error: ".mysqli_error($con)." with query: ".$queryRain;
                }
                else{
                    while($row = mysqli_fetch_array($thisQuery)){
                        // the previous daily rain is already rounded to the desired number of decimals
                        $previousRain = $row['R'];
                        $apiLog['info'][] = "previous rain=".number_format($previousRain,$decimalsR,".","")." maxRain=".number_format($maxRain,$decimalsR,".","");
                        if($previousRain != "" && $maxRain > $previousRain){
                            // save rain during last (5-minute) period of the day
                            $lastRain = $maxRain - $previousRain;
                            $apiLog['info'][] = "Last rain of this day=".number_format($lastRain,$decimalsR,".","");
                            $previousDate = $row['DateTime'];
                            $newQuery = "UPDATE alldata SET R=".$maxRain." WHERE DateTime='".$previousDate."'";
                            $otherQuery = mysqli_query($con,$newQuery);
                            if(!$otherQuery){
                                $apiLog['error'][] = "Meteotemplate MySQL Error: ".mysqli_error($con)." with query: ".$newQuery;
                            }
                            else{
                                $apiLog['info'][] = "The database is updated with the following query: ".$newQuery;
                            }
                        }
                        // reset total rain at start of new day
                        $currentRain = 0.0;
                        $apiLog['info'][] = "Reset rain at start of new day=".number_format($currentRain,$decimalsR,".","");
                    }
                }
            }
            else{
                $apiLog['info'][] = "No new day, currentRain=".number_format($currentRain,$decimalsR,".","");
            }
            $db['fields'][] = number_format($currentRain,$decimalsR,".","");
        }

        // rain rate
        if(isset($cache['RR'])){
            $db['parameters'][] = "RR";
            // take rain rate average or maximum based on template settings
            if($apiRRCalculation == "max"){
                $rawValue = max($cache['RR']);
            }
            else{
                $rawValue = array_sum($cache['RR'])/count8($cache['RR']);
            }
            $db['fields'][] = number_format($rawValue,$decimalsR,".","");
        }

        // solar radiation
        if($solarSensor){
            if(isset($cache['S'])){
                $db['parameters'][] = "S";
                // take solar radiation average
                $rawValue = array_sum($cache['S'])/count8($cache['S']);
                $db['fields'][] = number_format($rawValue,1,".","");
            }
        }

        // dew point
        if(isset($cache['D'])){
            $db['parameters'][] = "D";
            // take dew point average
            $rawValue = array_sum($cache['D'])/count8($cache['D']);
            $db['fields'][] = number_format($rawValue,1,".","");
        }

        // apparent temperature
        if(isset($cache['A'])){
            $db['parameters'][] = "A";
            // take apparent temperature average
            $rawValue = array_sum($cache['A'])/count8($cache['A']);
            $db['fields'][] = number_format($rawValue,1,".","");
        }
        
        // calculations
        $finalExtraParams = array();
        $finalExtraValues = array();
        if(count8($extraSensors)>0){
            foreach($extraSensors as $extraSensor){
                if(isset($cache[$extraSensor])){
                    $finalExtraParams[] = $extraSensor;
                    if ( ($extraSensor=="L") || ($extraSensor=="ET") )
                        {
                        $thisExtraVal = max($cache[$extraSensor]);
						if($newDay) {$thisExtraVal = 0;}
						}
                    else
                        {$thisExtraVal = array_sum($cache[$extraSensor])/count8($cache[$extraSensor]);}
                    $finalExtraValues[] = $thisExtraVal;
                }
            }
        }

        // UPDATE!

        // Check database connection
        if (!$con) {
            $apiLog['error'][] = "Unable to connect to MySQL";
        }
        
        $query = "
            REPLACE INTO alldata
            (".implode(',',$db['parameters']).")
            values (".implode(',',$db['fields'])."
        )";   
    
        $thisQuery = mysqli_query($con,$query);
        if(!$thisQuery){
            $apiLog['error'][] = "Meteotemplate MySQL Error: ".mysqli_error($con)." with query: ".$query;
        }
        else{
            $apiLog['info'][] = "The database is updated with the following query: ".$query;
        }
        if(date("H")==0 && date("i") <= 10){
            $thisQuery = mysqli_query($con,"ALTER TABLE alldata ORDER BY DateTime");
            if(!$thisQuery){
                $apiLog['error'][] = "Meteotemplate MySQL Error: ".mysqli_error($con)." with query: ALTER TABLE alldata ORDER BY DateTime";
            }
        }

        // perform query to alldataExtra if some params are available
        if(count8($finalExtraParams)>0){
            $hm = date("H:i",$timeForUpdate);
            if ( (($hm == "00:00") || ($hm == "00:05")) && isset($finalExtraParams['L']) ) {$finalExtraParams['L'] = 0;}
            $finalExtraParams[] = "DateTime";
            $finalExtraValues[] = "'".date("Y-m-d H:i:s",$timeForUpdate)."'";
            $apiLog['info'][] = "Preparing extra sensor query...";
            $queryExtra = "
                REPLACE INTO alldataExtra
                (".implode(',',$finalExtraParams).")
                values (".implode(',',$finalExtraValues).")
            ";

            $thisQuery = mysqli_query($con,$queryExtra);
            if(!$thisQuery){
                $apiLog['error'][] = "Meteotemplate MySQL Error: ".mysqli_error($con)." with query: ".$queryExtra;
            }
            else{
                $apiLog['info'][] = "The extra database table is updated with the following query: ".$queryExtra;
            }
            // only do this at 0:00-0:10
            if(date("H")==0 && date("i") <= 10){
                $thisQuery = mysqli_query($con,"ALTER TABLE alldataExtra ORDER BY DateTime");
                if(!$thisQuery){
                    $apiLog['error'][] = "Meteotemplate MySQL Error: ".mysqli_error($con)." with query: ALTER TABLE alldataExtra ORDER BY DateTime";
                }
            }
        }

        // save latest results of cache and log
        file_put_contents($base."cache/latestApiCache.txt",json_encode($cache));

        if(!file_exists($base."cache/latestApiCache.txt")){
            $apiLog['error'][] = "cache/latestApiCache.txt was not created! Probably incorrect permissions on the cache folder.";
        }
        
        // delete cache
        unlink($base."cache/apiCache.txt");

        $apiLog['info'][] = "Cache file deleted.";
        
        if($data['timestamp'] > $timeForUpdate){
            // addDataToCache - current record belongs to the next archive set
            ############################################################################
            // Add current data set to cache
            ############################################################################
            $cache = array();
            // only do something if we have a valid date
            if($valid['date']){
                // add current data set to either empty cache file or add to existing cache
                $cache['timestamp'][] = $data['timestamp'];
                $cache['date'][] = $data['date'];
                if($valid['T']){
                    $cache['T'][] = $data['T'];
                }
                if($valid['Tmax']){
                    $cache['Tmax'][] = $data['Tmax'];
                }
                if($valid['Tmin']){
                    $cache['Tmin'][] = $data['Tmin'];
                }
                if($valid['H']){
                    $cache['H'][] = $data['H'];
                }
                if($valid['P']){
                    $cache['P'][] = $data['P'];
                }
                if($valid['W']){
                    $cache['W'][] = $data['W'];
                }
                if($valid['G']){
                    $cache['G'][] = $data['G'];
                }
                if($valid['B']){
                    $cache['B'][] = $data['B'];
                }
                if($valid['R']){
                    // rain is cumulative, yet we cache for newDay checks
                    $cache['R'][] = $data['R']; 
                }
                if($valid['RR']){
                    $cache['RR'][] = $data['RR'];
                }
                if($valid['S']){
                    $cache['S'][] = $data['S'];
                }
                if($valid['D']){
                    $cache['D'][] = $data['D'];
                }
                if($valid['A']){
                    $cache['A'][] = $data['A'];
                }

                // extra sensors
                if(count8($extraQueryParams)>0){
                    for($j = 0; $j < count8($extraQueryParams); $j++){
                        $cache[$extraQueryParams[$j]][] = $extraQueryValues[$j];
                    }
                }
            }
            $apiLog['info'][] = "Saving first new data to cache/apiCache.txt, timestamp=".date("Y-m-d H:i:s",$data['timestamp']);
            file_put_contents($base."cache/apiCache.txt",json_encode($cache));
        }
        // create API update log file
        $updateLog = "";
        foreach($apiLog['info'] as $info){
            $updateLog .= $info."\n\r";
        }
        $updateLog .= "\n\rERRORS:\n\r";
        if(isset($apiLog['error'])){
            foreach($apiLog['error'] as $error){
                $updateLog .= $error."\n\r";
            }
        }
        file_put_contents($base."cache/latestApiLog.txt",$updateLog);
    }
    // not yet time for update - just save updated cache file
    else{
        // addDataToCache - current record belongs to the next archive set
        ############################################################################
        // Add current data set to cache
        ############################################################################

        // only do something if we have a valid date
        if($valid['date']){
            // add current data set to either empty cache file or add to existing cache
            $cache['timestamp'][] = $data['timestamp'];
            $cache['date'][] = $data['date'];
            if($valid['T']){
                $cache['T'][] = $data['T'];
            }
            if($valid['Tmax']){
                $cache['Tmax'][] = $data['Tmax'];
            }
            if($valid['Tmin']){
                $cache['Tmin'][] = $data['Tmin'];
            }
            if($valid['H']){
                $cache['H'][] = $data['H'];
            }
            if($valid['P']){
                $cache['P'][] = $data['P'];
            }
            if($valid['W']){
                $cache['W'][] = $data['W'];
            }
            if($valid['G']){
                $cache['G'][] = $data['G'];
            }
            if($valid['B']){
                $cache['B'][] = $data['B'];
            }
            if($valid['R']){
                // rain is cumulative, yet we cache for newDay checks
                $cache['R'][] = $data['R']; 
            }
            if($valid['RR']){
                $cache['RR'][] = $data['RR'];
            }
            if($valid['S']){
                $cache['S'][] = $data['S'];
            }
            if($valid['D']){
                $cache['D'][] = $data['D'];
            }
            if($valid['A']){
                $cache['A'][] = $data['A'];
            }

            // extra sensors
            if(count8($extraQueryParams)>0){
                for($j = 0; $j < count8($extraQueryParams); $j++){
                    $cache[$extraQueryParams[$j]][] = $extraQueryValues[$j];
                }
            }
        }
        $apiLog['info'][] = "Not yet time to update the db, saving new data to cache/apiCache.txt";
        echo "writing file: $base"."cache/apiCache.txt";
        file_put_contents($base."cache/apiCache.txt",json_encode($cache));
    }

    // create API update log file
	generateLog($base, $apiLog, "apiLog.txt", $append2ApiLog);
    if($convertUgp)
        {file_put_contents($base."cache/apiLogConvertUgp.txt",$updateLog);}
    
    // return success status to caller
    echo "Success";
    http_response_code(200);
	if ($logGetPost)
	    {
        $updateLog = "exiting ".date("Y-m-d H:i:s")."\n";
        file_put_contents($base."cache/apiReqLog.txt",$updateLog,FILE_APPEND);
		}

    ############################################################################
    // Functions
    ############################################################################

    // Dew point calculation
    // accepts temperature [C] and humidity [%]
    // returns dew point [C]
    function dewpoint($dewT,$dewH){
        $calcD = round(((pow(($dewH/100), 0.125))*(112+0.9*$dewT)+(0.1*$dewT)-112),1);
        return $calcD;
    }

    // Apparent temperature calculation
    // accepts temperature [C], humidity [%], wind speed [m/s]
    // returns apparent temperature [C]
    function apparent($apparentT,$apparentH,$apparentW){
        $e = ($apparentH/100)*6.105*pow(2.71828, ((17.27*$apparentT)/(237.7+$apparentT)));
        $calcA = round(($apparentT + 0.33*$e-0.7*$apparentW-4),1);
        return $calcA;
    }

    // Average wind bearing
    // accepts wind directions as an array and data as number between 0 and 360
    function avgWindUpdate($directions) { // based on http://en.wikipedia.org/wiki/Yamartino_method
        $sinSum = 0;
        $cosSum = 0;
        foreach ($directions as $value) {
            $sinSum += sin(deg2rad($value));
            $cosSum += cos(deg2rad($value));
        }
        return intval(rad2deg(atan2($sinSum, $cosSum)) + 360) % 360;
    }

    function generateLog($base, $apiLog, $fileName, $flags=false)
        {
        //global $apiLog;
        //global $base;
        // create log and exit update script 
        $apiLog['info'][] = "Generating log file cache/".$fileName;
        $updateLog = "\nstarting log: ".date("Y-m-d H:i:s")."\n";
        $updateLog .= "INFOS:\n";
        foreach($apiLog['info'] as $info)
            {$updateLog .= $info."\n";}
        if(isset($apiLog['error']))
            {
            $updateLog .= "\nERRORS:\n\r";
            foreach($apiLog['error'] as $error)
                {$updateLog .= $error."\n";}
            }
        $updateLog .= "ending log: ".date("Y-m-d H:i:s")."\n";
        if ($flags == false)
            {file_put_contents($base."cache/".$fileName,$updateLog);}
        else
            {file_put_contents($base."cache/".$fileName,$updateLog,FILE_APPEND);}
        }

    function sensor_like($key,$keyex)
        {
        if (strlen($key) > 1)
            {
            if (is_numeric(substr($key, -2)))
                {$key=substr($key,0,strlen($key)-2).'#';}
            else if (is_numeric(substr($key, -1)))
                {$key=substr($key,0,strlen($key)-1).'#';}
            }
        return ($key == $keyex);
        }

function exception_handler(Throwable $exception)
    {
    global $base;
    // write tracelines into main template
    //$msg = "PHP Fatal error:  Uncaught exception '%s' with message '%s' in %s:%s\nStack trace:\n%s\n  thrown in %s on line %s";
    $msg = "PHP Fatal error:  Uncaught exception '%s' with message '%s' in %s:%s\n  thrown in %s on line %s";
    $msg = sprintf(
        $msg,
        get_class($exception),
        $exception->getMessage(),
        $exception->getFile(),
        $exception->getLine(),
        //implode("\n", $result),
        $exception->getFile(),
        $exception->getLine()
    	);
	//$output['error'] = $msg;
	//print json_encode($output);
	$msg .= '\n';
	file_put_contents($base."cache/apiErrorLog.txt",$msg,FILE_APPEND);
	}

