Sensor History PHP Example

Moderator: Telldus

Post Reply
atchoo
Posts: 18
Joined: Thu Jan 18, 2018 11:08 am
Location: Oslo, Norway
Contact:

Sensor History PHP Example

Post by atchoo » Mon Jul 22, 2019 2:20 pm

Hello,

I've had quite a few requests about how to display sensor history with a graph, so I decided to just post an example here.

I had to make some compromises regarding design/CSS, to be able to squash it all into only 2 PHP files. The important parts are not affected by that anyway though, and I've done my best to keep it short and safe/secure.

Screenshot:
Skjermbilde.png
Skjermbilde.png (156.86 KiB) Viewed 253 times
Requirements:
PHP web server,
At least 1 temperature sensor with history logging enabled

Included:
Bootstrap 4, Chartist JS, JQuery

sensorhistory.php:

Code: Select all

<?php
if(!isset($_SESSION)) {
session_start();
}

if(isset($_POST['oauth_consumerKey']) && isset($_POST['oauth_privateKey']) && isset($_POST['oauth_token']) && isset($_POST['oauth_tokenSecret'])) {
$_SESSION['oauth_consumerKey'] = $_POST['oauth_consumerKey'];
$_SESSION['oauth_privateKey'] = $_POST['oauth_privateKey'];
$_SESSION['oauth_token'] = $_POST['oauth_token'];
$_SESSION['oauth_tokenSecret'] = $_POST['oauth_tokenSecret'];
} 
$formAction = "https://" . $_SERVER['HTTP_HOST'] . $_SERVER['PHP_SELF'];
if(!isset($_SESSION['oauth_consumerKey']) && !isset($_SESSION['oauth_privateKey'])) {
echo "Please visit <a href='http://api.telldus.com/keys/generatePrivate' target='_blank'>this page</a>, and fill out this form accordingly:<br /><br />";
echo "<form action='" . $formAction . "' method='post'>
    <label for 'oauth_consumerKey'>Public Key:</label>
    <input type='text' name='oauth_consumerKey'><br /><br />
    <label for 'oauth_privateKey'>Private Key:</label>
    <input type='text' name='oauth_privateKey'><br /><br />
    <label for 'oauth_token'>Token:</label>
    <input type='text' name='oauth_token'><br /><br />
    <label for 'oauth_tokenSecret'>Token Secret:</label>
    <input type='text' name='oauth_tokenSecret'><br /><br />
    <input type='submit' value='Send'>
</form>";
    
} else {

class ZNetSensors
{
    const	API_URL	= "https://pa-api.telldus.com/json";
    
    const	REQUEST_TIMEOUT		= 5,
            REQUEST_USER_AGENT  = 'Sensor History',
            REQUEST_RETRIES		= 3,
            REQUEST_RETRY_SLEEP	= 3;

    public function __construct()
    {
        echo "This class is not intended to be instanciated.";
    }

    protected static function request($pathquery, $retry=self::REQUEST_RETRIES)
    {
        $oauth_consumerKey = $_SESSION['oauth_consumerKey'];
        $oauth_privateKey = $_SESSION['oauth_privateKey'] ;
        $oauth_token = $_SESSION['oauth_token'];
        $oauth_tokenSecret = $_SESSION['oauth_tokenSecret'];
        $oauth_signature = ($oauth_privateKey . "%26" . $oauth_tokenSecret);
        $oauth_nonce = md5(uniqid(mt_rand(), true));
        $oauth_timestamp = $_SERVER['REQUEST_TIME'];
        $host=parse_url(self::API_URL, PHP_URL_HOST);

        $finalurl=sprintf("%s%s", self::API_URL, $pathquery);

        $options=stream_context_create(
            array('http'=>
                array(
                    'timeout' => self::REQUEST_TIMEOUT,
                    'method'=> 'GET',

                    'header'=>
                        "User-Agent: ".self::REQUEST_USER_AGENT."\r\n".
                        "Authorization: OAuth oauth_consumer_key=" . $oauth_consumerKey . ", oauth_nonce=" . $oauth_nonce . ", oauth_signature=" . $oauth_signature .", oauth_signature_method=\"PLAINTEXT\", oauth_timestamp=" . $oauth_timestamp . ", oauth_token=" . $oauth_token . ", oauth_version=\"1.0\"",
                )
            )
        );
        // VERY IMPORTANT: Keep queries per load to a minimum!!
        // Uncomment the next line to reality check:
        // echo "$finalurl\n";
        
        $json=@file_get_contents($finalurl, false, $options);
        if ($http_response_header[0]!='HTTP/1.1 200 OK' and $http_response_header[0]!='HTTP/1.0 200 OK') {
            print_r('Request failed with \"'.$http_response_header[0]."\", retry:$retry, url:".addslashes($finalurl));

            if ($retry<self::REQUEST_RETRIES) {
                sleep(self::REQUEST_RETRY_SLEEP);
                return self::request($pathquery, ++$retry);
            }
        }

        $response=json_decode($json, true);
        if (json_last_error()!=JSON_ERROR_NONE) {
            print_r('JSON invalid') . print_r($json);
        }
        return $response;
    }

    public static function sensorsList($includeIgnored, $includeValues, $includeUnit, $includeScale)
    {
        $query=array();
        $query['includeIgnored']=$includeIgnored;
        $query['includeValues']=$includeValues;
        $query['includeUnit']=$includeUnit;
        $query['includeScale']=$includeScale;
        return self::request("/sensors/list?".http_build_query($query));
    }
    
    public static function sensorInfo($sensorID, $includeUnit)
    {
        $query=array();
        $query['id']=$sensorID;
        $query['includeUnit']=$includeUnit;
        return self::request("/sensor/info?".http_build_query($query));
    }

    public static function sensorHistory($sensorID, $from, $to, $includeUnit)
    { //$from = timestamp in seconds, $to = timestamp in seconds
        $query=array();
        $query['id']=$sensorID;
        $query['from']=$from;
        $query['to']=$to;
        $query['includeUnit']=$includeUnit;
        return self::request("/sensor/history?".http_build_query($query));
    }
}
?>
<!doctype html>
<html lang="en"><head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
    <meta name="description" content="Sensor History Example in PHP">
    <meta name="author" content="©2019 Atchoo Development">
    <link rel="icon" href="favicon.ico">
    <title>Sensor History</title>
    <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" crossorigin="anonymous">
    <link rel="stylesheet" href="//cdn.jsdelivr.net/chartist.js/latest/chartist.min.css">
    <link href="css/chartist-plugin-tooltip.css" rel="stylesheet">
	<link href="https://fonts.googleapis.com/css?family=Asap:400,700&amp;subset=latin-ext" rel="stylesheet">
	<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-select@1.13.9/dist/css/bootstrap-select.min.css">
    <script src="https://code.jquery.com/jquery-3.4.1.min.js" integrity="sha256-CSXorXvZcTkaix6Yvo6HppcZGetbYMGWSFlBw8HfCJo=" crossorigin="anonymous"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.7/umd/popper.min.js" integrity="sha384-UO2eT0CpHqdSJQ6hJty5KVphtPhzWj9WO1clHTMGa3JDZwrnQq4sF86dIHNDz0W1" crossorigin="anonymous"></script>
    <script src="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js" integrity="sha384-JjSmVgyd0p3pXB1rRibZUAYoIIy6OrQ6VrjIEaFf/nJGzIxFDsf4x0xIM+B07jRM" crossorigin="anonymous"></script>
	<script src="https://cdn.jsdelivr.net/npm/bootstrap-select@1.13.9/dist/js/bootstrap-select.min.js"></script>
    <script src="//cdn.jsdelivr.net/chartist.js/latest/chartist.min.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/chartist-plugin-threshold@0.0.2/dist/chartist-plugin-threshold.min.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/chartist-plugin-axistitle@0.0.4/dist/chartist-plugin-axistitle.min.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/chartist-plugin-tooltips@0.0.17/dist/chartist-plugin-tooltip.min.js"></script> 

<script>
$(function () {
  $('[data-toggle="tooltip"]').tooltip()
})
</script>
<style>
.ct-line.ct-threshold-above, .ct-point.ct-threshold-above, .ct-bar.ct-threshold-above {
  stroke: #fe6813;
}

.ct-line.ct-threshold-below, .ct-point.ct-threshold-below, .ct-bar.ct-threshold-below {
  stroke: #097e9f;
}

.ct-area.ct-threshold-above {
  fill: #ff0b03;
}

.ct-area.ct-threshold-below {
  fill: #151ea9;
}
</style>
</head>
<body class="bg-light">
<div class="container-fluid">
<div class="row">
<div class="col-md-12">
<?php
$sensors = ZNetSensors::sensorsList(0,1,1,1);
$sensor = $sensors['sensor'];
$i = "-1";
foreach($sensor as $key=>$sens) {
$sensorName = $sens['name'];
$sensorID = $sens['id'];
$sensorInfo = ZNetSensors::sensorInfo($sensorID,'1');
$sensorName = $sensorInfo['name'];
$sensorData = $sensorInfo['data'];
$sensorProtocol = $sensorInfo['protocol'];
if(!isset($_SESSION['hp'])) {
$_SESSION['hp'] = '-12';
}
$_SESSION['historyPeriod'] = ($_SESSION['hp'] . " hours");
$fromTime = strtotime($_SESSION['historyPeriod']);
$toTime = strtotime("now");
$sensorHistory = ZNetSensors::sensorHistory($sensorID, $fromTime, $toTime, 1);
$history = $sensorHistory['history'];
$keepHistory = $sens['keepHistory'];
$i='-1';
if($keepHistory == '1') {
$logValues = array ();	
$logTimes = array ();
foreach($history as $key => $hist) {
	$i++;
	$logTS = $hist['ts'];
	$logTimes[] = date('H:i', $logTS);
	$logValues[] = $hist['data'][0]['value'];
}
// print_r($logValues);
// print_r($logTimes);
// $logTimes = Array of human readable Clock times of all log entries;
//	i.e. "$logTimes[2]" for the time of the 3rd entry, "$logTimes[3]" for the next.
// $logValues = Array of sensor values, corresponding with $logTimes
// Note: The reason for 2 separate arrays is just conveniency due to the following hacky combination of PHP & Javascript
?>
<div class="card text-white bg-dark rounded" style="border-width: 2px; border-color: black; padding: 12px;">
	<h2 class="card-header text-white rounded text-center" style="font-family: Asap; font-weight: 700; text-transform: uppercase; font-size: 24px; padding: 30px;">Sensor History<br /><span class="text-warning"><?php echo $sensorName?></span></h2> 
<div class="card-body rounded bg-light">
<div class="ct-chart-line" style="height: 360px;" id="chart"></div>
<script>
var wholelabels = [<?php foreach($logTimes as $key => $t) { echo "'".$t."', "; } ?>];
var halflabels = wholelabels.filter(function(value, index, Arr) {
    return index % 10 == 0;
});
var wholeseries = [<?php foreach($logValues as $key => $v) { echo $v.", "; } ?>];
var halfseries = wholeseries.filter(function(value, index, Arr) {
    return index % 10 == 0;
});
	
var data = {
  labels: halflabels, 
  series: [
  {meta: "Temperatur", value: halfseries}
  ]
};

new Chartist.Line('.ct-chart-line', data, {
  axisY: {
  onlyInteger: true
  },
  fullwidth: true,
  showArea: true,
  chartPadding: {
    top: 20,
    right: 0,
    bottom: 22,
    left: 20
  },
  plugins: [
    Chartist.plugins.ctAxisTitle({
      axisX: {
        axisTitle: 'Time',
        axisClass: 'ct-axis-title',
        offset: {
          x: 0,
          y: 50
        },
        textAnchor: 'middle'
      },
      axisY: {
        axisTitle: '\u00b0C',
        axisClass: 'ct-axis-title',
        offset: {
          x: 0,
          y: -20
        },
        textAnchor: 'middle',
        flipTitle: false
      }
    }),
	  Chartist.plugins.tooltip({
	  	  
	}),
  ]
});
</script>
</div>
<div class="card-footer text-light bg-dark rounded text-center">
<select class="selectpicker show-tick" id="historyInterval">
  <option value="-24" <?php if($_SESSION['hp'] == '-24') { echo 'selected'; }?>>Last 24 Hours</option>
  <option value="-12" <?php if($_SESSION['hp'] == '-12') { echo 'selected'; }?>>Last 12 Hours</option>
  <option value="-8" <?php if($_SESSION['hp'] == '-8') { echo 'selected'; }?>>Last 8 Hours</option>
  <option value="-6" <?php if($_SESSION['hp'] == '-6') { echo 'selected'; }?>>Last 6 Hours</option>
  <option value="-3" <?php if($_SESSION['hp'] == '-3') { echo 'selected'; }?>>Last 3 Hours</option>
</select>
<script>
		$('#historyInterval').change(function() {
		// console.log(this.value);
			$.ajax({
  				type: "POST",
  				url: "historyOptions.php",
  				data: { historyPeriod: this.value },
  				success: function() {
        		location.reload()
				}
		
		});		
	});	
</script>
</div>
</div>
<?php 
}}}
?>
</div> <!-- End Full Width col -->
</div> <!-- End Row -->
</div> <!-- End Container -->
</body>
</html>
historyOptions.php:

Code: Select all

<?php
if(!isset($_SESSION)) {
session_start();
}
if(isset($_POST['historyPeriod'])) {
$_SESSION['hp'] = $_POST['historyPeriod'];	
}
?>
Happy coding!


Andreas

Post Reply