バスロケ等のプログラム例

ble信号を取得し、ファイルに書き出すプログラム例

定期的に実行し、信号が得られなかったときは情報なしで記録

#!/usr/bin/python
# coding: UTF-8
'''
ble信号をスキャンしてデータをcsvファイルに書き出す
 - scan関数は、引数で指定する時間、信号を待ち受け、得られた結果を返す
 - 信号がなかったら空のデータを書き出す
 - 信号あった時は、その中からaddrTypeがrandomなものを拾い出し、それを書き出す
 - sleepは必要ないかもしれない
 - Ctrl-Cを押すとファイルを閉じ終了
 - ble検出モジュールbluepyのマニュアルURL:
      bluepy manual page: http://ianharvey.github.io/bluepy-doc/
'''
import time
import numpy as np
from datetime import datetime, timedelta

import sys
sys.path.append('/usr/local/lib/python2.7/dist-packages/bluepy')
from bluepy.btle import Scanner, DefaultDelegate

# initial
try:
    scanner = Scanner().withDelegate(DefaultDelegate())
    post_interval = 0.5  # interval of scanning
except :
    print "no device"
    sys.exit()

# file for output
outfile = open("bleTest.csv","w")

while True:
    try:
        devices = scanner.scan(post_interval)
        if len(devices)==0:
            print ",,,%s" %(datetime.now().strftime("%H:%M:%S") )
        else:
            for dev in devices:
                if dev.addrType=="random":
                    dateNow = datetime.now()
                    timeStr = dateNow.strftime("%H:%M:%S")
                    outfile.write("%s (%s), %d, %s\n" % (dev.addr, dev.addrType, dev.rssi, dateNow)) # file output
                    print "Device %s (%s), RSSI=%d dB, %s" % (dev.addr, dev.addrType, dev.rssi, timeStr)
        time.sleep(post_interval)
    except KeyboardInterrupt:
        outfile.close()
    except :
        print "no device"
        sys.exit()
        break

gpsとbleデータを取得し、サーバーに送信するプログラム例

#!/usr/bin/python
# coding:utf-8
'''
gpsデータとbleデータを取得し、サーバに送信する
 - 送信先URLはpostUrlで指定
 - gpsデータ取得モジュールはpythonに標準で入っている
   - gpsデータを取得するOS上のモジュールgpsdはインストールしておくこと
 - bleデータ取得にはbluepyモジュールを使う
   - bluepyモジュールをインストールした場所をpathに加える
 - それぞれのモジュールは並行して動かせていない、順々におこなうので、その分、時間はずれる
'''
import sys
sys.path.append('/usr/local/lib/python2.7/dist-packages/bluepy')
import requests
import json
import struct
import uuid
from gps import *
from time import *
import time
import threading
from datetime import datetime
from pytz import timezone
from bluepy.btle import Scanner, DefaultDelegate

postUrl = "http://8tops.yamanashi.ac.jp/~toyoki/busloc/post_test.php"
gpsd = None #seting the global variable
def isNaN(num):
    return num != num

class GpsPoller(threading.Thread):
    '''
    GPSデータ取得のためのクラス
    '''
    def __init__(self):
        threading.Thread.__init__(self)
        global gpsd #bring it in scope
        gpsd = gps(mode=WATCH_ENABLE) #starting the stream of info
        self.current_value = None
        self.running = True #setting the thread running to true

    def run(self):
        global gpsd
        while gpsp.running:
            gpsd.next() #this will continue to loop and grab EACH set of gpsd info to clear the buffer

if __name__ == '__main__':
    # instance for BLE
    scanner = Scanner().withDelegate(DefaultDelegate())
    ble_get_interval = 0.5  # interval of scanning BLEs
    try:
        devices = scanner.scan(ble_get_interval)
        haveBleDevice = True
    except :
        haveBleDevice = False
    # macアドレスの16進表示
    mac_address = ":".join( [hex( fragment )[2:].zfill( 2 ) for fragment in struct.unpack( "BBBBBB", struct.pack( "!Q", uuid.getnode() )[2:] )] )
    # 送信用基本データ ("name"は今のところダミー)
    dt = {
        "name": "RaspberryPI 1",
        "mac": mac_address,
    }
    # create the instance for gps device
    gpsp = GpsPoller() # create the thread
    try:
        gpsp.start() # gpsデータ検索窓口
        while True:
            # GPSデータ取得
            if gpsd.utc != ""  and  (not isNaN(gpsd.fix.latitude)):
                dt["lat"] = gpsd.fix.latitude
                dt["lng"] = gpsd.fix.longitude
                t_utc = timezone('UTC').localize(datetime.strptime(gpsd.utc,"%Y-%m-%dT%H:%M:%S.%fZ"))
                now = datetime.strftime(t_utc.astimezone(timezone('Asia/Tokyo')), "%Y-%m-%d %H:%M:%S")
                dt['time'] = now
            else:
                dt["time"] = datetime.now().strftime("%H:%M:%S")
            # BLEデータ取得
            if haveBleDevice :
                bleDevices = scanner.scan(ble_get_interval)
                if len(bleDevices) != 0:
                    for dev in bleDevices:
                        if dev.addrType=="random":
                            dt["bleAddr"] = dev.addr
                            dt["bleRssi"] = dev.rssi
                            print dev.addr
            # データ送信
            res = requests.post(
                postUrl,
                data=json.dumps(dt))
            time.sleep(5) #set to whatever
    except (KeyboardInterrupt, SystemExit): #when you press ctrl+c
        # print "\nKilling Thread..."
        gpsp.running = False
        gpsp.join() # wait for the thread to finish what it's doing
        #print "Done.\nExiting."

gpsデータとbleデータは別々に記録、送信する

(Googleドライブ上の文書に書いた。) https://docs.google.com/document/d/1rQnKdOOaYGrYmnsv2qv9Os72lCca2pc-YrkzYSDOj9o

位置情報csvファイルデータをGoogle Maps APIで描画する方法

(1) csv形式のデータ読み込みjson形式で送出するphpプログラム

csvが"属性=値"となっているcsvファイルの場合

<?php
header("Content-type: application/json; charset=utf-8");

$ymd = $_GET['file'];

if(isset($_GET['file'])) {
    $FP = fopen($_GET['file'], "r");
} else {
    $FP = fopen("data/postedData" .  $ymd . ".csv", "r"); // デフォルト値
}
if($FP == false) exit();

$dataArray = [];
while( ($row = fgetcsv($FP)) !== false) {
    $singleData = [];
    foreach($row as $col) {            // 各項を=で分解して前半はキー、後半は値とする
	$value = explode("=",$col);
	if(count($value)==2) $singleData[$value[0]] = $value[1];
    }
    $dataArray[] = $singleData;
}

$jsonTree =  json_encode($dataArray);
if(isset($_GET['callback'])) $jsonTree =  $_GET['callback'] . "(". $jsonTree . ")";
echo $jsonTree;

?>

単純な形式のcsvファイルの場合

データは各行、「日時, 緯度, 経度」の3カラムからなっている場合。

<?php
header("Content-type: application/json; charset=utf-8");

$ymd = $_GET['file'];

if(isset($_GET['file'])) {
    $FP = fopen($_GET['file'], "r");
} else {
    $FP = fopen("data/postedData" .  $ymd . ".csv", "r");
}
if($FP == false) exit();

$dataArray = [];
while( ($row = fgetcsv($FP)) !== false) {
    $dataArray[] = array("time"=>$row[0],
			 "lat"=>$row[1],
			 "lng"=>$row[2]);
}

$jsonTree =  json_encode($dataArray);
if(isset($_GET['callback'])) $jsonTree =  $_GET['callback'] . "(". $jsonTree . ")";
echo $jsonTree;

?>

上記2つを統合し1つのスクリプトで両方に対応できるようにしたもの

1行を読み込んでみてイコールを含むかどうか判断して2通りの処理を行う。

<?php
header("Content-type: application/json; charset=utf-8");

$ymd = $_GET['file'];

if(isset($_GET['file'])) {
    $FP = fopen($_GET['file'], "r");
} else {
    $FP = fopen("data/postedData" .  $ymd . ".csv", "r");
}
if($FP == false) exit();

// ファイル内容の形式を検出する
$row = fgetcsv($FP);
if(strpos($row[1],"=")) {
    $withIndex = true;
} else {
    $withIndex = false;
}
rewind($FP);

$dataArray = [];

if($withIndex) {   // インデックス付きcsvファイルの場合
    while( ($row = fgetcsv($FP)) !== false) {
	$singleData = [];
	foreach($row as $col) {            // 各項を=で分解して前半はキー、後半は値とする
	    $value = explode("=",$col);
	    if(count($value)==2) $singleData[$value[0]] = $value[1];
	}
	if($singleData["lat"]!="") 	$dataArray[] = $singleData;
    }
} else {  // 単純なcsvファイルの場合
    while( ($row = fgetcsv($FP)) !== false) {
	if($row[1] == "") continue;
	$dataArray[] = array("time"=>$row[0],
			     "lat"=>$row[1],
			     "lng"=>$row[2]);
    }
}

$jsonTree =  json_encode($dataArray);
if(isset($_GET['callback'])) $jsonTree =  $_GET['callback'] . "(". $jsonTree . ")";
echo $jsonTree;

?>

(2) 表示ページの作成

基本的なページの記述(サーバサイドスクリプト)はhtml, phpで書き、jsonデータをよみこんで描画する部分はJavaScriptで記述する。

(2-1) サーバサイドスクリプト

実行例: http://8tops.yamanashi.ac.jp/~toyoki/busloc/showBusLocations.php

松浦くんのデータ表示: http://8tops.yamanashi.ac.jp/~toyoki/busloc/showBusLocations4matsu.php

(json形式データ処理には上記3番目のものを使った。したがってファイル形式の違いを主導で選択しなくても良い。)


<!DOCTYPE HTML PUBLIC "-//w3c//dtd html 4.01 traditional//en">
<head>
    
    
    
    
    
    


    

ファイル名一覧

ファイルの種別

(2-2) Google Maps APIを利用するJavaScript

phpを含む上記htmlでshowBuslocations.jsという名前で読み込むJavaScript。

サーバ側にファイルとしてjson形式のデータを置いてテストしてみるのもよい。(もちろん、json形式ではなく、csv形式のデータを読み込んで処理することも可能である。jQueryにはcsvを便利に扱う関数もある。)

// global variables
var tinyIcon = "http://8tops.yamanashi.ac.jp/~toyoki/icons/blue.png";
var normalIcon = "http://maps.google.co.jp/mapfiles/ms/icons/blue-dot.png";
var baseUrl = 'http://8tops.yamanashi.ac.jp/~toyoki/busloc/';
var posMarker = [];
var busPosition = [];
var map;
var maxDataNumber= 2000; // 地図表示に時間がかかるのでデータ数の上限を決めておく


// show bus positions on Google Map
function showBusLocations() {
    var urlStr = baseUrl + $("#scriptName").val() + "?callback=?" + "&file=" + $("#filename").val();
    $.getJSON(urlStr, function(data) {
	      if(data==null) alert("データがみつかりません");

        var clat=0.0;
        var clng=0.0;
        var dataCount=0;
        busPosition = [];
	      for(var idx  in data) {
	          if(data[idx].time=="" || data[idx].lat=="") continue;
            dataCount++;
	          /* $("#show_results").append(data[idx].time +","
				       + data[idx].lat + ","
				       + data[idx].lng + "
"); */ clat += parseFloat(data[idx].lat); clng += parseFloat(data[idx].lng); busPosition.push({"time":data[idx].time, "lat": parseFloat(data[idx].lat), "lng": parseFloat(data[idx].lng)}); if(dataCount>maxDataNumber) { alert("データ数が" + maxDataNumber + "を超えましたので、それ以上のデータを読み込みませんでした。"); break; } } // 描画 if(busPosition.length==0) { alert($("#filename").val() + "からは位置情報がひとつも得られませんでした。"); } clat /= dataCount; clng /= dataCount; if(map == null) { var mapOptions = { zoom:16, center: new google.maps.LatLng(clat,clng) } map = new google.maps.Map(document.getElementById("map-canvas"),mapOptions); } if(posMarker.length !=0) { for(var i in posMarker) posMarker[i].setMap(null); posMarker = []; } map.setCenter(new google.maps.LatLng(clat,clng)); var iconObj = {url: tinyIcon, anchor: new google.maps.Point(4,4)} for( var idx in busPosition) { posMarker.push(new google.maps.Marker({ position: new google.maps.LatLng(busPosition[idx].lat, busPosition[idx].lng), icon: iconObj, map: map, title: busPosition[idx].time })); } }); }