Quantcast
Channel: Node.jsタグが付けられた新着記事 - Qiita
Viewing all articles
Browse latest Browse all 8833

RaspberryPi で、GPS Logger & Tracker の製作

$
0
0
概要 車で旅に出る時に軌跡を残しておきたいなと思って、スマホのアプリで軌跡を残していましたが、ちょっとGPSのデータを取得してみたい。実際のGPSのデータってどうなってんの?が今回の作業のきっかけ。 Google MyMapで、GPSロガーのデータをインポートすればKMLに変換してくれて表示してくれるけど、ちょっと自分でやってみよう! メインホスト 今、手元にRaspberryPi4Bが3台あります。 メインホストはこれを選択。 GPSの購入 RasberryPi HAT形式のGPSを考えていましたが、遊ぶにはちょっとお高めなので、USB接続のGPSレシーバーを購入。 Amazon GPSレシーバー GPS受信機 DOCTORADIO GR-7BN アンテナ内蔵 チップ一体型 USB出力 測位 時刻較正用 構成 RaspberryPi4にUSBでGPSレシーバーと繋ぐ USBを介してシリアルでGPSレシーバからNMEAフォーマットのデータを受信する シリアルから1行づつデータを、nodejsで組んだプログラムが読み込む 読み込んだデータをLocalStorageに書き込む(ファイル) 同時にmosquittoのMQTT Pub/SubにPublishする WebBrowserで、MQTT Pub/SubをWebsocketsで受信するためにSubscribeしておく WebBrowserで受信したデータをパースして、10進の緯度経度に変換して、GoogleMapにプロットする 位置データをPolylineで結んで軌跡を表示させる GPSデータの受信と保存 GPSのデータはシリアルポートから受信する。 GPSレシーバーをUSBのここに刺すと、/dev/ttyACM0 で読むことができる。 取得対象はGPRMCのみとする。 下記のreadDataメソッドのdata変数をconsole.logで出力すれば内容を確認できます。 GPSデータのNMEAフォーマットは?に関しては、以下を参照してください。 GPSのNMEAフォーマット GPSデータではGPRMCのみ対象としています。 取得した内容をファイルに書き込んでいます。 gps.js // 日付モジュール const moment = require('moment-timezone'); // ファイル操作モジュール const fs = require('fs'); // パス操作モジュール const path = require('path'); // シリアルからGPSデータ受信 const SerialPort = require('serialport'); const Readline = SerialPort.parsers.Readline; const port = new SerialPort('/dev/ttyACM0'); const parser = new Readline(); port.pipe(parser); parser.on('data', readData); // MQTTモジュール const mqttHost = 'localhost'; const mqtt = require('mqtt'); const mqttClient = mqtt.connect('mqtt://'+mqttHost); mqttClient.on('connect', function() { console.log('mqtt ' + mqttHost + ' connected.'); }); mqttClient.on('error', function(err) { console.log(err); }); /** * GPSデータの読み込み * * @param data String GPSデータ * @return void */ function readData(data) { // GPRMCのみ採用して書き込む if (data.match(/^\$GPRMC/, data) != null) { // ファイルに書き込み writeLogToFile(data); // MQTTでpublish mqttClient.publish('gps', data); } } /** * GPSデータをログに書き込む * * @param data String GPSデータ * @return void */ function writeLogToFile(data) { // 書き込むファイルパスの取得 let filepath = getFilepath(data); // 書き込む行データを作成 let log = getLogLineFromData(data); // 行データを書き込む let dir = path.dirname(filepath); if (!fs.existsSync(dir)) fs.mkdirSync(dir); fs.writeFileSync(filepath, log, {flag:"as"}); } /** * GSPデータから書き込むログファイルパスを取得する * * @param data String GPSデータ * @return String */ function getFilepath(data) { // 日付のファイルを作成して返す let utc = getUtcFromData(data); let date = moment.utc(utc).tz("Asia/Tokyo").format("YYYYMMDD"); let filename = date + ".log"; let dir = './log'; let filepath = dir + '/' + filename; return filepath; } /** * GPSデータからUTC日時を作成する * * @param data String GPSデータ * @return String */ function getUtcFromData(data) { // $GPRMC,144733.00,A,3519.49685,N,13926.21062,E,0.072,,180521,,,A*7C let ary = data.split(','); let hour = ary[1].substr(0, 2); let min = ary[1].substr(2, 2); let sec = ary[1].substr(4, 2); let year = parseInt(ary[9].substr(4, 2)) + 2000; let month = ary[9].substr(2, 2); let day = ary[9].substr(0, 2); let utc = year + '-' + month + '-' + day + ' ' + hour + ':' + min + ':' + sec; return utc; } /** * ログ行をGPSデータから作成する * * @param data String GPSデータ * @return String */ function getLogLineFromData(data) { let utc = getUtcFromData(data); let date = moment.utc(utc).tz("Asia/Tokyo").format(); let log = date + ' - ' + data + "\n"; return log; } $ node ./gps.js でシリアルポートから受信を開始し、./log/YYYYMMDD.log に受信内容が書き込まれます。 こんな感じ YYYYMMDD.log 2021-05-20T00:00:00+09:00 - $GPRMC,150000.00,A,3519.50282,N,13926.21234,E,0.321,,190521,,,A*70 2021-05-20T00:00:01+09:00 - $GPRMC,150001.00,A,3519.50280,N,13926.21236,E,0.185,,190521,,,A*7D 2021-05-20T00:00:02+09:00 - $GPRMC,150002.00,A,3519.50248,N,13926.21231,E,0.405,,190521,,,A*70 2021-05-20T00:00:03+09:00 - $GPRMC,150003.00,A,3519.50233,N,13926.21229,E,0.366,,190521,,,A*76 2021-05-20T00:00:04+09:00 - $GPRMC,150004.00,A,3519.50223,N,13926.21227,E,0.439,,190521,,,A*73 2021-05-20T00:00:05+09:00 - $GPRMC,150005.00,A,3519.50219,N,13926.21227,E,0.095,,190521,,,A*79 2021-05-20T00:00:06+09:00 - $GPRMC,150006.00,A,3519.50216,N,13926.21227,E,0.080,,190521,,,A*71 2021-05-20T00:00:07+09:00 - $GPRMC,150007.00,A,3519.50182,N,13926.21233,E,0.449,,190521,,,A*7A 2021-05-20T00:00:08+09:00 - $GPRMC,150008.00,A,3519.50161,N,13926.21229,E,0.453,,190521,,,A*78 2021-05-20T00:00:09+09:00 - $GPRMC,150009.00,A,3519.50166,N,13926.21224,E,0.125,,190521,,,A*77 2021-05-20T00:00:10+09:00 - $GPRMC,150010.00,A,3519.50176,N,13926.21221,E,0.375,,190521,,,A*7C 2021-05-20T00:00:11+09:00 - $GPRMC,150011.00,A,3519.50165,N,13926.21220,E,0.166,,190521,,,A*7E 2021-05-20T00:00:12+09:00 - $GPRMC,150012.00,A,3519.50145,N,13926.21216,E,0.430,,190521,,,A*7C 地図に表示する リアルタイムに地図に取得した位置を表示させるために、ブラウザからWebsocketsでデータをやりとりします。 Websocketsサーバとしては、いつもIoTで利用している軽量Pub/Sub mosquittoを利用します。 https://mosquitto.org/ Websocketsで接続して処理するjsライブラリはMQTT.jsを利用します。 https://github.com/mqttjs/MQTT.js/ GoogleMapを含む表示画面は、Webscocketsできればどこにあっても構いません。 今回、RaspberryPi4にnodejs ExpressでWebサーバを立てており、そこに表示ページを設置しています。 ページの実装内容は次の通り index.js <!DOCTYPE html> <html> <head> <title>GPS Logger</title> <meta charset="utf-8"> <script src="/javascripts/mqtt.min.js"></script> <link rel='stylesheet' href='/stylesheets/style.css' /> <script> // Map let map; function initMap() { map = new google.maps.Map(document.getElementById('map'), { center: { lat: -34.397, lng: 150.644 }, zoom: 20, }); marker = new google.maps.Marker; marker.setMap(map); polyline = new google.maps.Polyline({ path: [], strokeColor: "#728EF0", strokeOpacity: 0.8, strokeWeight: 3, geodesic: false }); } let marker; let polyline; // MQTT let mqttClient = mqtt.connect('ws://raspberrypi4.local:9090'); mqttClient.on('connect', () => { console.log('mqtt connected.'); mqttClient.subscribe('gps'); }); mqttClient.on('message', (topic, message) => { readData(message.toString()); }); /** * GPSデータの読み込み * * @param json data {lat:val, lng:val} * @return void */ function readData(data) { console.log(data); if (data == null) return ; // 緯度経度をMapの中心に配置 let latlng = getLatLngFromData(data); if (latlng.lat != null && latlng.lng != null) { map.setCenter(latlng); // マーカーを設置 marker.setPosition(latlng); // 軌跡を描く drawLine(latlng); } } /** * GPSデータから10進緯度経度を取得する * * @param strin data GPSデータ * @return json {lat:val, lng:val} */ function getLatLngFromData(data) { let ary = data.split(','); latlng = {lat: null, lng: null}; if (ary[3] && ary[5]) { // 経度 latitude let lats = []; let lat = String(ary[3]); lats[0] = parseInt(lat.substr(0, 2)); // 度 lats[1] = parseFloat(lat.substr(2, lat.length-2))/60; lat = lats[0] + lats[1]; // 緯度 longitude let lng = String(ary[5]); let lngs = []; lngs[0] = parseInt(lng.substr(0, 3)); // 度 lngs[1] = parseFloat(lng.substr(3, lng.length-3))/60; lng = lngs[0] + lngs[1]; latlng = {lat:lat, lng:lng}; console.log(latlng); } return latlng; } /** * 軌跡を描く * * @param json latlng {lat:val, lng:val} * @return void */ function drawLine(latlng) { let path = polyline.getPath(); path.push(new google.maps.LatLng(latlng)); polyline.setPath(path); polyline.setMap(map); } </script> <style type="text/css"> html, body { height: 100%; margin: 0; padding: 0; } #map { height: 100%; } </style> </head> <body> <div id="map"></div> <script src="https://maps.googleapis.com/maps/api/js?key=<YOUR_KEY>&callback=initMap&libraries=&v=weekly" async ></script> </body> </html> GPSレシーバーを受信できる家の窓際においておきます。 そしてWebブラウザで開くとこんな感じ。 10m範囲くらいでデータが動いています。 まとめ と、いった感じでGPSのNMEAフォーマットの緯度経度を含むGPRMCデータを使用して、リアルタイムでの地図表示を行いました。 ちょっとハマったのが、NMEAの60進の度数を10進に変換する時に、どこまでが度なのかわからなかった。 その際こちらが参考になりました。 0183の座標の桁数は可変だった件 次の目標 保存しているデータを、カレンダーから期間を指定してファイル読み込んで地図に描きたい。 以上でーす。

Viewing all articles
Browse latest Browse all 8833

Trending Articles