ころちゃん

木工・電子工作・IoT・作ったもの試したものを記事にして投稿してます

温度・湿度・気圧をBME280/Raspberry Pi/Azureで可視化 その3

time 更新日:  time 公開日:2020/05/11

温度・湿度・気圧をBME280/Raspberry Pi/Azureで可視化 その3

その2ではRaspberry Pi Zero Wにセンサーを取り付け、読み取った値をサーバにアップロードする設定を行いました。本編では、データベースに保存された値を読み取り、JavaScriptでWebブラウザ上にグラフを描画する設定を行います。

  1. 温度・湿度・気圧をBME280/Raspberry Pi/Azureで可視化 その1(AzureにWEBサーバとDBの準備編)
  2. 温度・湿度・気圧をBME280/Raspberry Pi/Azureで可視化 その2(Raspberry Pi Zero WにBME280取り付け・データ送信編)
  3. 温度・湿度・気圧をBME280/Raspberry Pi/Azureで可視化 その3(チャート描画編) ←本稿

グラフ描画に使うライブラリについて

WEBブラウザ上グラフが描けるJavaScriptのライブラリは、いくつかあります。今回はGoogle ChartsとCanvasJSの2つを使ってみました。

作成の参考にさせていただいたサイトです。

サーバサイドでグラフを作り描画するのではなく、サーバからは描画に必要なデータのみを取得し、ブラウザ上のJavaScriptでデータを組み立て描画するところがポイントです。今回の構成では、JavaScriptからPHPを呼び出し、MySQLから必要なデータを取り出しレスポンス、ブラウザ上でデータをもとにグラフを描画するという流れです。

WebAppsのPHPタイムゾーン設定

AzureWebAppsのPHP設定は、デフォルトタイムゾーンがUTCになっており、日本時間と9時間ずれていました。これを正しく直しておかないと、PHPで意図した時間を返してくれないので直します。phpファイル毎に全てタイムゾーンを入れるのも、結構な手間ですからね。

レンタルサーバやオンプレサーバであれば、php.iniで設定を変更できるのですが、WebAppsの場合どうやって変えるの分からず、地味にはまりました。

.user.ini ファイルをルート ディレクトリに追加します。

php.ini ファイルで使用するものと同じ構文を使用して、構成設定を .user.ini ファイルに追加します。

Azure App Service での PHP の構成方法

なるほど…。ルートディレクトリに[.user.ini]ファイルをつくって、同じ構文でタイムゾーンをいれればいいのかな?まあ、書いてる通りやってみよう。

レンタルサーバのphp.iniを参考に、[date.timezone = Asia/Tokyo]と入れて保存>アプリを再起動してみます。

タイムゾーンがUTCからAsia/Tokyoに変更されました。ダブルコーテーション「”」で括ると解説しているサイトもありましたが、入れなくても反映しました。phpinfo()で確認しました。

Google Charts版プログラム

上記参考サイトのコードを参考にコードをカスタマイズしました。

  • 温度データを、1日/1週間/1ヶ月月/3ヵ月とセレクト。
  • 1週間、1ヶ月、3ヵ月は1日の最高・平均・最低の3軸で表示。

chart1.html

<html>
  <head>
    <meta charset="UTF-8">
    <title>センサーデータ</title>
    <!--Load the AJAX API-->
    <script type="text/javascript" src="https://www.gstatic.com/charts/loader.js"></script>
    <script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jquery/1.10.2/jquery.min.js"></script>
    <script type="text/javascript">

    // Load the Visualization API and the piechart package.
    google.charts.load('current', {'packages':['corechart']});

    // Set a callback to run when the Google Visualization API is loaded.
    google.charts.setOnLoadCallback(drawChart);

    function drawChart(showspan = 'Day') {
      var urlWithGetData = "CreateJsonFromMySql1.php?range=" + showspan + "&target=temp";
      var jsonData = $.ajax({
          url: urlWithGetData,
          dataType: "json",
          async: false
          }).responseText;

      // Create our data table out of JSON data loaded from server.
      var data = new google.visualization.DataTable(jsonData);

      // Instantiate and draw our chart, passing in some options.
      var chart = new google.visualization.LineChart(document.getElementById('chart_div'));

      var options = {
                title: 'Temperature report from BME280',
                curveType: 'function',
                height: 500,
                hAxis: {title: 'Time', showTextEvery: 1, slantedText: true, slantedTextAngle: 90},
                vAxis: {title: 'Temperature[℃]'},
                legend: { position: 'bottom' }
              };

      chart.draw(data, options);
    }

    </script>
  </head>

  <body>
    <!--Div that will hold the Line chart-->
    <form>
      <select name="showspan" onchange="drawChart(this.value)">
        <option value="Day" selected="">Day</option>
        <option value="Week">Week</option>
        <option value="Month">Month</option>
        <option value="3Month">3Month</option>
      </select>
    </form>
    <div id="chart_div"></div>
  </body>
</html>

CreateJsonFromMySql1.php

<?php

require_once 'DbManager.php';
require_once 'Encode.php';

$range  = (isset($_GET['range'])) ? $_GET['range']  : 'Day';
$target  = (isset($_GET['target'])) ? $_GET['target']  : 'temp';

$arryCol = [];
$arryRow = [];

switch ($range){
case '3Month':
case 'Month':
case 'Week':
    // Date range for query. Week/Month rangeがWeekの場合7 Month30 3Month90を代入 

    $dayRange = (($range == 'Week') ? 7 : (($range == 'Month') ? 30 : 90));
    $qryDate = strtotime("-{$dayRange} day");

    // Variables to shorten recoring date 西暦と時刻を切り取り日付だけにする
    $substrStart = 5;
    $substrLen = 5;

    // Column definition for GoogleCharts Table 配列のカラムに日付、最小値、平均値、最大値を代入
    array_push($arryCol, MakeColElement('Date', 'string'));
    array_push($arryCol, MakeColElement('min', 'number'));
    array_push($arryCol, MakeColElement('ave', 'number'));
    array_push($arryCol, MakeColElement('max', 'number'));

    break;
case 'Day':
     // rangeがDayの場合 クエリの一日前の日を2020-01-05 10:10:10形式で代入
    $days = date("Y-m-d H:i:s", strtotime("-1 day"));

    // Variables to shorten recoring date 西暦と日付部分を切り取り時刻だけにする
    $substrStart = 11;
    $substrLen = 5;

    // Column definition for GoogleCharts Table
    array_push($arryCol, MakeColElement('Time', 'string'));
    array_push($arryCol, MakeColElement('Value', 'number'));
    break;
default:
    break;
}

try {
    $db = GetDb();

    switch ($range){
    //MonthとWeekの場合
    case '3Month':
    case 'Month':
    case 'Week':
      for ($day=0; $day <$dayRange ; $day++) {
        // prepare query string. min/ave/max will be targeted.
        $days = date("Y-m-d", $qryDate);
        $qry = "select datetime, min($target), avg($target), max($target) from sensordata where datetime like '$days%' ORDER BY id ASC";
        $stt = $db->prepare($qry);

        // call
        $stt->execute();
        $row = $stt->fetch(PDO::FETCH_ASSOC);
        $rsltToJson = [substr($row['datetime'],$substrStart,$substrLen), $row['min('.$target.')'], $row['avg('.$target.')'], $row['max('.$target.')']];
        array_push($arryRow, MakeRowElements($rsltToJson));

        // + 1 day for next day.
        $qryDate = strtotime("+1 day", $qryDate);
      }
      break;

    case 'Day':
      $qry = "select datetime, temp from sensordata where datetime >= '$days' ORDER BY id ASC";
      $stt = $db->prepare($qry);
      $stt->execute();

      // loop till fetch all aquired values and store.
      while($row = $stt->fetch(PDO::FETCH_ASSOC)) {
        $rsltToJson = [substr($row['datetime'],$substrStart,$substrLen), $row[$target]];
        array_push($arryRow, MakeRowElements($rsltToJson));
      }
      break;
    }

    // delete
    $db = NULL;
}
catch (PDOEXception $e) {
    die("connection error:{$e->getMessage()}");
}

// return created Json format.
echo ReturnGoogleChartsTable($arryCol, $arryRow);

function MakeColElement ($label, $type)
{
    return '{"id":"","label":"'.$label.'"'.',"pattern":"","type":"'.$type.'"'.'}';
}

function MakeRowElements ($arry)
{
  $ret = '{"c":[';
  for ($i=0; $i < count($arry); $i++)
  {
    $element = '{"v":"'.$arry[$i].'","f":null}';
    $ret .= ($i != (count($arry)-1)) ? $element.',' : $element ;
  }
  $ret = $ret.']}';
  return $ret;
}

function ReturnGoogleChartsTable ($arryCol, $arryRow)
{
  $ret = '{
          "cols": ['
          .ConnectArry($arryCol)
          .'],'
          .'"rows": ['
          .ConnectArry($arryRow)
          .']
         }';
  return $ret;
}

function ConnectArry($arry)
{
  $ret = '';
  for ($i=0; $i < count($arry); $i++)
  {
    $ret .= ($i != (count($arry)-1)) ? $arry[$i].',' : $arry[$i] ;
  }
  return $ret;
}

?>

保存したPHPプログラムにブラウザでアクセスすると、デフォルトパラメータのtempとdayが選択され、JSON形式のデータが出力されます。このデータをJavaScriptでグラフとして描画しています。

CanvasJS版プログラム

上記参考サイトのコードを参考に、カスタマイズしました。

  • 温度・湿度・気圧を1つのグラフに表示できるように3軸グラフとした。
  • 1日/1週間/1か月/1か月平均の4つセレクト。

実際のデータはこのように見えます(デモ)。

chart2.html

<!DOCTYPE html>
<html lang="ja">
  <head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>センサーデータC</title>
    <script type="text/javascript" src="https://canvasjs.com/assets/script/canvasjs.min.js"></script>
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js"></script>
    <script type="text/javascript">
    function drawChart(selectspan) {
    var temp_data;
     //データをjson形式で取得し、temp_dataという変数に代入する
    $.ajax({
	url: "CreateJsonFromMySql2.php", //温度データ取得のURL
	type: "GET", //GETメソッドで取得
	dataType: 'json', //処理結果はjson形式で受信
	async: false, //非同期通信
	data: {
	       target: 'temp',
	       range: selectspan
	      },
	success: function(json1) { temp_data = json1;} 
    });
     //データをjson形式で取得し、hum_dataという変数に代入する
    var hum_data;
    $.ajax({
	url: "CreateJsonFromMySql2.php",
	dataType: 'json',
	async: false,
	data: {
	       target: 'hum',
	       range: selectspan
	      },
	success: function(json1) { hum_data = json1;}
    });
     //データをjson形式で取得し、press_dataという変数に代入する
    var press_data;
    $.ajax({
	url: "CreateJsonFromMySql2.php",
	dataType: 'json',
	async: false,
	data: {
	       target: 'press',
	       range: selectspan
	      },
	success: function(json1) { press_data = json1;}
    });
    
                var chart = new CanvasJS.Chart(chartContainer01, {
                    title: {text: "BEM280 温度・湿度・気圧データ"},
                    axisX: { labelAngle: -90, labelFontSize: 14, labelFontColor: '#222' },
                    axisY:[{
							title: "Tempreture",
							lineColor: "#369EAD",
							tickColor: "#369EAD",
							labelFontColor: "#369EAD",
							titleFontColor: "#369EAD",
							//includeZero: false,
							suffix: "℃"
						},
						{
							title: "Humidity",
							lineColor: "#C24642",
							tickColor: "#C24642",
							labelFontColor: "#C24642",
							titleFontColor: "#C24642",
							includeZero: false,
							suffix: "%"
						}],
					axisY2: {
							title: "Pressure",
							lineColor: "#7F6084",
							tickColor: "#7F6084",
							labelFontColor: "#7F6084",
							titleFontColor: "#7F6084",
							includeZero: false,
							suffix: "hPa"
						},
               toolTip: {shared: true},
               legend: {cursor: "pointer",itemclick: toggleDataSeries},
               
                    //axisY: { title: "temperature", suffix: '℃',axisYIndex: 0, labelFontSize: 14, labelFontColor: '#222' },
                    theme: "light1",  //デフォルトテーマに設定
                    data: [{
                        type: 'line',  //温度グラフの種類
                        name: "temperature",
                        color: "#369EAD",
                        showInLegend: true,
                        axisYIndex: 0,
                        dataPoints: temp_data //表示するデータ
                    },
                    {
                        type: 'line',  //湿度グラフの種類
                        name: "humidity",
                        color: "#C24642",
                        axisYIndex: 1,
                        showInLegend: true,
                        dataPoints: hum_data //表示するデータ
                    },
                    {
                        type: 'line',  //気圧グラフの種類
                        name: "pressure",
                        color: "#7F6084",
                        axisYType: "secondary",
                        showInLegend: true,
                        dataPoints: press_data //表示するデータ
                    }]
                });
    chart.render();
	//マウスカーソル一つに3つの値を表示する設定
    function toggleDataSeries(e) {
	if (typeof (e.dataSeries.visible) === "undefined" || e.dataSeries.visible) {
		e.dataSeries.visible = false;
	} else {
		e.dataSeries.visible = true;
	}
	e.chart.render();
    }
    }
    </script>
  </head>
  <body onload= drawChart('Day')>
      <form>
      <select name="selectspan" onchange="drawChart(this.value)">
        <option value="Day" selected="">1日</option>
        <option value="Week">1週間</option>
        <option value="Month">1ヵ月</option>
        <option value="Average">1ヵ月平均</option>
      </select>
    </form>
    <div id="chartContainer01" style="height: 450px; width: 100%;"></div><br>
  </body>
</html>

CreateJsonFromMySql2.php

<?php
require_once 'DbManager.php';
require_once 'Encode.php';
//Day,Week,Month,Averageのいずれかで表示期間変動版
//getでtemp,hum,pressのどれかを取得
$target  = isset($_GET['target']) ? $_GET['target'] : 'temp';
$range  = isset($_GET['range']) ? $_GET['range'] : 'Day';
//Day,Week,MonthのいずれかでdayRangeに代入
switch ($range){
case 'Average':
case 'Month':
     $dayRange = 30;
     break;
case 'Week':
     $dayRange = 7;
     break;
case 'Day':
     $dayRange = 1;
     break;
default:
     break;
}
$day = date("Y-m-d H:i:s",strtotime("-$dayRange day"));
$qryDate = strtotime("-{$dayRange} day");
$db = GetDb();
//Averageの場合とそれ以外(Day,Week,Month)で処理を分ける
switch ($range){
case 'Average':
//ここに平均値の処理を書く
for ($day=0; $day <$dayRange ; $day++) {
$days = date("Y-m-d", $qryDate);
$sth = $db->prepare("SELECT datetime,avg($target) FROM sensordata where datetime like '$days%' ORDER BY id ASC");
$sth->execute();
        $row = $sth->fetch(PDO::FETCH_ASSOC);
                $jsonData[] = [
               'label'=>substr($row['datetime'],5,5),
               'y'=>(float)round($row['avg('.$target.')'],2)
               ];
       
        // + 1 day for next day.
        $qryDate = strtotime("+1 day", $qryDate);
     }   
     break;
case 'Month':
case 'Week':
case 'Day':
$sth = $db->prepare("SELECT datetime,$target FROM sensordata where datetime >= '$day' ORDER BY id ASC");
$sth->execute();
        while($row = $sth->fetch(PDO::FETCH_ASSOC)){
                $jsonData[]=[
                'label'=>substr($row['datetime'],5,11),  
                'y'=>(float)$row[$target]
                ];
        }
     break;
}
        //jsonとして出力
        header('Content-type: application/json');
        echo json_encode($jsonData); 
        
?>

JavaScript側からPHPにクエリを渡す際、例えば「気圧」を「1週間分」の時、呼び出すURLはCreateJsonFromMySql2.php?target=press&range=Weekのようになります。クエリストリングの「?」に続けて、対象「target=press」と期間「range=Week」を付与しリクエストします。PHP側ではGETメソッド$_GET[‘target’]、$_GET[‘range’]でクエリの内容を把握します。このグラフは3軸なので、温度、湿度、気圧と3回クエリを送付するようにしました。以下の画像はブラウザの開発者ツール(F12を選択)で表示できます。

保存したPHPプログラムにブラウザでアクセスすると、デフォルトパラメータのtempとdayが選択され、JSON形式のデータが出力されます。このデータをJavaScriptでグラフとして描画しています。

3軸グラフを作るにあたって、公式マニュアルにあったソースコードを参考にしました。またグラフの色、配置、表示方法等マニュアルに細かに解説してあります。英語ですが、根気よく探すと自分の望む表示方法がきっと見つかると思います。

あとがき

コロナの外出自粛を受けて、家でできるテーマを決めて、じっくり取り組んでみようと思いやってみました。プログラミングの知識はワードプレスのカスタマイズでPHPをちょっといじった事がある、素人に毛が生えた程度でしたが、調べながらなんとかここまで完成させることが出来ました。

Python、PHP、JavaScript、どれも参考にさせていただいたサイトに出会わなかったら、全くできなかったと思います。やり方はいろいろあり、これがすべてではないと思いますので、少しでも私の記事が参考になれば幸いです。

sponsored link

down

コメントする




CAPTCHA


管理人

ころちゃん

木工で日用品を作ったり、懐中電灯を改造したりしてます。まだまだ素人ですがどうぞよろしく。



sponsored link

アーカイブ