AMG8833のデータを簡易サーモグラフィぽくブラウザで可視化(後編)
この記事について
この記事はAMG8833のデータを簡易サーモグラフィぽくブラウザで可視化(前編)の続きです。AMG8833のデータを使い、WEBブラウザ上でヒートマップ表示にチャレンジしています。
前回まではESP8266を使い、AMG8833からデータを取得するところまで紹介しました。今回はESP8266から受け取ったセンサデータをサーバ側の可視化について説明します。
サーバ側の構成について
サーバ側の基本的な動作はESP8266からセンサデータをHTTPで受け取ります。受け取ったデータはそのままキューに送りつけます。また可視化用のブラウザからのリクエスト受け付けるとデータの可視化の非同期処理を起動します。この非同期処理は先ほどのセンサデータを送っているキューからデータを取り出し、ブラウザにSSE(Server sent event)で非同期にデータを送り続けます。前編で説明した通り、センサデータを逐次送りつけ、ブラウザの表示をリアルタイム表示させたいと思い、バックエンド(センサ受信処理)とフロントエンド(ブラウザの表示)を非同期通信で実装しました。サーバ側の受信スレッドを枯渇させないように、別スレッドとして、SSEの送信処理を実装しています。また、センサデータの受信スレッドとフロントエンドの送信処理をメッセージキューで同期させています。メッセージキューは別にサービスを建てたくなかったので、組みこみActiveMQを利用しています。 ヒートマップなどの表示処理はブラウザ側で実行します。サーバ側のバックエンドの処理はSpringBootで実装します。ブラウザ表示のフロントエンドはHTMLとAngularJSで実装しています。ブラウザは初回表示時にフロントエンドを取得し、その後、サーバからのSSEを受信する都度画面の描画を変更します。
今回実装したサーバ側コードは以下のリポジトリにアップしています。
バックエンド処理の実装
バックエンドはSpringBootで実装しています。
センサデータの受信
センサデータは[30.0,30.0,30.0,・・・・・・]
のJSON形式でPOSTリクエストで送信されます。これを以下のRestControllerで取得します。
@RestController @RequestMapping("/api/v1/thermography") public class Controller { private final JmsTemplate jmsTemplate; private final Queue queue; public Controller(JmsTemplate jmsTemplate, Queue queue) { this.jmsTemplate = jmsTemplate; this.queue = queue; } @PostMapping public void postThermoGraphyTelemetory(@RequestBody List<Double> payload) { jmsTemplate.convertAndSend(queue, payload); //(1) } }
- (1) 受け取ったデータはinmemory.queueと言う名前のキューに入れます。バックエンド側ではDoubleオブジェクトのリストとして表現されています。
ServerSentEvent
ServerSentEventの受信処理はRestContorollerに以下のエンドポイントを追加します。非同期処理のタイムアウト値は1分に設定します。(SSEが1分送信されなければタイムアウトします。)
private final AsyncEmitData asyncEmitData; private final JmsTemplate jmsTemplate; private final Queue queue; public Controller(AsyncEmitData asyncEmitData, JmsTemplate jmsTemplate, Queue queue) { this.asyncEmitData = asyncEmitData; this.jmsTemplate = jmsTemplate; this.queue = queue; } @GetMapping("/sse") public SseEmitter getThermoGraphySSE() throws IOException, InterruptedException { SseEmitter emitter = new SseEmitter(60000L); log.info("do Async"); asyncEmitData.streming(emitter); log.info("close Async"); return emitter; }
実際に非同期処理を実施しているのはAsyncEmitData classで実装しています。
@Component public class AsyncEmitData { private boolean isLock = true; private final JmsTemplate jmsTemplate; public AsyncEmitData(JmsTemplate jmsTemplate) { this.jmsTemplate = jmsTemplate; } @Async public void streming(SseEmitter emitter) throws IOException, InterruptedException { jmsTemplate.setReceiveTimeout(0);// (1) while (true) { List<Double> result = (List<Double>) jmsTemplate.receiveAndConvert("inmemory.queue"); //(2) emitter.send(result); //(3) log.info("send event1"); if (result == null) { break; } } log.info("complete sse session."); emitter.complete(); } }
別途@EnableAsync
アノテーションを指定します。
- (1) JmsTemplateの受信処理はデフォルトで非同期処理のため、タイムアウトを0に指定し同期的に受信待ちするようにします。
- (2) JmsTemplate.receiveAndConvertでinmemoru.queueのデータを待ち受けします。
- (3) キューから取得したデータをそのままSSEとして送信します。またデータがnullであればサーバから切断します。
Embedded ActiveMQの利用
組みこみActiveMQの設定です。
- ActiveMQのコンフィグクラス
@Configuration @EnableJms public class ActiveMqConfig { @Bean public Queue queue() { return new ActiveMQQueue("inmemory.queue"); } }
- application.yml
spring: activemq: in-memory: true pool: enabled: false
- pom.xml
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-activemq</artifactId> </dependency>
ブラウザ側処理
ヒートマップの表示処理
ヒートマップの表示はTableを温度の値ごとにAngularJSで表示させます。
- index.html
<!DOCTYPE html> <html lang="en" ng-app="App"> <head> <meta charset="UTF-8"> <title>Viaualization AMG88xx</title> <style> table { border: none; } td { width: 50px; height: 50px; border-style: none; border: none; } tr { border: none; } </style> <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.6/angular.min.js"></script> <script type=text/javascript src="./js/controller.js"></script> </head> <body ng-controller="ApplicationController"> <center> <h1>Visualization.</h1> <table bgcolor="white" > <tr ng-repeat="line in image"> <td ng-repeat="pixel in line" style="background-color: rgb({{pixel[0]}},{{pixel[1]}},{{pixel[2]}})">1</td> </tr> </table> </center> </body> </html>
- controller.js
ar app = angular.module('App',[]); var es = new EventSource('/api/v1/thermography/sse'); app.controller('ApplicationController', ['$scope',function($scope){ es.addEventListener('message', function (event) { var acceptData = JSON.parse(event.data); var temp = [] for (var i = 0; i <= 56; i += 8) { temp.push(acceptData.slice(i, i + 8)); } $scope.image = []; $scope.image = temp.map(function (line) { return line.map(function (temp) { return translateTempToRGB(temp); }) }); $scope.$apply(); }); data = function () { $scope.image = []; var temp = [[0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0], [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0], [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0], [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0], [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0], [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0], [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0], [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]]; $scope.image = temp.map(function (line) { return line.map(function (temp) { return translateTempToRGB(temp); }) }); } data(); }]);
SSEでデータを取得するたびにAngularJSで表示を切り替えています。温度の値をRGBに変換する処理translateTempTpRGB
ファンクションは以下のような実装です。
var translateTempToRGB = function(temp){ var scale = 1 / 50.0; temp * scale; return colorRGBBar(temp * scale); } var sigmoid = function(x, gain, offset){ var value = (x+offset)*gain; return ((Math.tanh(value*0.5)+1)*0.5); } var colorRGBBar = function(x){ var gain = 10 var offset_x = 0.2 x = (x * 2) - 1 red = sigmoid(x, gain, -1*offset_x) blue = 1-sigmoid(x, gain, offset_x) green = sigmoid(x, gain, 0.6) + (1-sigmoid(x,gain,-1*0.6)) green = green - 1.0 return [Math.floor(red*255),Math.floor(green*255),Math.floor(blue*255)]; }
動作
実際に動作させた動画は以下の通りです。わかりやすく氷をセンサの前にくぐらせています。また、チョキやパーなどもわかる程度にデータが取れています。
まとめ
今回はAMG8833のデータをESP8266で取得し、WEBブラウザで可視化するところまでやりました.AMG8833自体は指定温度での割り込み機能などがあり、センサとしては高度な処理ができそうです。また、補間処理をすることで8x8から画素を広げて表示することにもチャレンジしたいです。 サーバ側はフルスクラッチで実装していますが、クラウドサービスなどを使ってデータの取得部分を置き換えるようにしたいです。