Sensor History PHP Example

Moderator: Telldus

Post Reply
atchoo
Posts: 21
Joined: Fri Mar 17, 2023 9:45 am
Location: Oslo, Norway
Contact:

Sensor History PHP Example

Post by atchoo »

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 11486 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
bergetun
Posts: 35
Joined: Fri Mar 17, 2023 9:45 am

Re: Sensor History PHP Example

Post by bergetun »

Thank you very much for the example.

Stupid questions bellow

I am trying to build a VERY simple PHP site that shows my different Telldus sensors, but my PHP and Webserver skills is very limited.

Will this code work on its own if the web server does support PHP. Can i use any web hosts out there ?

When looking on the api.telldus.com it seems that you need some server plugins to get it working (PEAR HTTPauth and LightOpenID) Do i need to get the hosting company to install these on a server.


Do you have a example that only shows a sensor with value on a website without the visitors needing to enter any login details?
Post Reply