みつきんのメモ

組み込みエンジニアです。Interface誌で「Yocto Projectではじめる 組み込みLinux開発入門」連載中

NuttXをSTM32F103 minimum(BluePill)で動かす

はじめに

STM32F103 Minimum BoardでNuttXというRTOSを動かしてみる。 このボードはAmazonで買えた

2個で990円。もっと安いものもあるみたいだが、中国から直送で1ヶ月くらい輸送にかかりそうなのでプライムで短期間で届きそうなものを買った。

このボードにはSTM32F103C8T6が載っている。

メモリ サイズ
フラッシュ 64KiB
RAM 20KiB

NuttXを動かすためには大まかに次のような作業が必要になる。

  1. kconfig-frontendsのインストール
  2. stlink toolsのインストール
  3. ARMツールチェーンのインストール
  4. NuttXの作成
  5. ボードへの書き込み

kconfig-frontendsのインストール

nuttxのビルドにはKconfigを使用するが、その際にkconfig-frontendsが必要となる。

ubuntu 18.04では、自分でビルドする必要がある。

必要パッケージのインストール

次のパッケージをインストールする。

$ sudo apt install build-essential
$ sudo apt install make gperf flex bison libncurses-dev

ビルドとインストール

次のようにしてkconfig-frontendを作成、インストールする。

$ git clone https://bitbucket.org/nuttx/tools
$ cd tools/kconfig-frontends
$ ./configure
$ make -j 4
$ sudo make install

stlink toolsのインストール

nuttxの書き込みにstlink toolsを使用する場合はインストールする。 ubuntu 18.04では自分でビルドする必要がある。

必要パッケージのインストール

次のパッケージをインストールする。

$ sudo apt install cmake libusb-1.0

ビルドとインストール

$ wget https://github.com/texane/stlink/archive/v1.5.1.tar.gz
$ tar xvfz ./v1.5.1.tar.gz
$ cd stlink-1.5.1
$ make -j4
$ cd build/Release
$ sudo make install
$ sudo ldconfig

udevルールの更新

root権限無しでstlinkにアクセスできるようにudevルールを更新する。

$ sudo udevadm control --reload-rules
$ sudo udevadm trigger

ARMツールチェーンのインストール

$ wget https://armkeil.blob.core.windows.net/developer/Files/downloads/gnu-rm/8-2018q4/gcc-arm-none-eabi-8-2018-q4-major-linux.tar.bz2
$ tar xvf gcc-arm-none-eabi-8-2018-q4-major-linux.tar.bz2
$ mv gcc-arm-none-eabi-8-2018-q4-major ~/bin

次のようにPATHに追加する。

PATH="/home/mickey/bin/gcc-arm-none-eabi-8-2018-q4-major/bin:${PATH}";

nuttxの作成

ソースの取得

$ git clone https://bitbucket.org/nuttx/nuttx.git
$ git clone https://bitbucket.org/nuttx/apps.git

コンフィグレーション

$ cd nuttx
$ tools/configure.sh -l configs/stm32f103-minimum/usbnsh

ビルド

$ make -j4

イメージの書き込み

ST-Link V2とボードの接続

ST-Link V2のピン配置とファンクションをマニュアルから抜粋する

f:id:mickey_happygolucky:20190127210206p:plain:w400
ピン配置

f:id:mickey_happygolucky:20190127210426p:plain:w600
ピンファンクション

ST-Link V2 Pin ターゲット
GND 20 GND
TCK 9 CLK
TMS 7 IO
TVCC 1 3.3V

st-flashコマンドによる書き込み

st-flashコマンドでnuttx.binを書き込む。

$ st-flash write nuttx.bin 0x8000000
st-flash 1.5.1
2019-01-27T15:55:48 INFO usb.c: -- exit_dfu_mode
2019-01-27T15:55:48 INFO common.c: Loading device parameters....
2019-01-27T15:55:48 INFO common.c: Device connected is: F1 Medium-density device, id 0x20036410
2019-01-27T15:55:48 INFO common.c: SRAM size: 0x5000 bytes (20 KiB), Flash: 0x10000 bytes (64 KiB) in pages of 1024 bytes
2019-01-27T15:55:48 INFO common.c: Attempting to write 50224 (0xc430) bytes to stm32 address: 134217728 (0x8000000)
Flash page at addr: 0x0800c400 erased
2019-01-27T15:55:50 INFO common.c: Finished erasing 50 pages of 1024 (0x400) bytes
2019-01-27T15:55:50 INFO common.c: Starting Flash write for VL/F0/F3/F1_XL core id
2019-01-27T15:55:50 INFO flash_loader.c: Successfully loaded flash loader in sram
 50/50 pages written
2019-01-27T15:55:53 INFO common.c: Starting verification of write complete
2019-01-27T15:55:54 INFO common.c: Flash written and verified! jolly good!

jolly good!が表示されていれば、書き込みは成功している。

ターゲットの起動

USB NSH環境のNuttXを書き込んだボードをUbuntuのPCに接続すると/dev/ttyACM0として見える。 minicomでNSHに接続する。

何度かEnterキーを押してみて、プロンプトが表示されればOK。

nsh>
nsh> help
help usage:  help [-v] [<cmd>]

  [        cp       exit     kill     mw       sleep    usleep
  ?        dd       false    ls       pwd      test     xd
  cat      echo     help     mb       set      true
  cd       exec     hexdump  mh       sh       unset

Builtin Apps:
nsh>

f:id:mickey_happygolucky:20190127220255j:plain:w600
BluePill

yoctoでkodiをつくる

メディアセンターのディストリビューションをつくってみる。

環境構築

ラズベリーパイ3向けの環境。いつものやつ。

sumo(2.5)をベースにすると後述のCMAのエラーが発生するため、thud(2.6)をベースにする。

ソース取得

$ mkdir -p rpi-kodi/layers
$ cd rpi-kodi/layers
$ git clone git://git.yoctoproject.org/poky.git -b thud
$ git clone git://git.yoctoproject.org/meta-raspberrypi -b thud
$ git clone git://git.openembedded.org/meta-openembedded -b thud

kodiのレイヤをダウンロード

meta-kodiはthudブランチがないため、sumoブランチを使用する。

LAYERSERIES_COMPAT_meta-kodi = "sumo thud"となっているため互換性は問題ない。

$ git clone https://github.com/koenkooi/meta-kodi.git -b sumo
$ cd ../

環境変数設定

$ source layers/poky/oe-init-build-env build

自動的にビルドディレクトリに移動される。 これで、bitbake関連のツールが使用可能になる。

レイヤ追加

$ bitbake-layers add-layer ../layers/meta-openembedded/meta-oe
$ bitbake-layers add-layer ../layers/meta-openembedded/meta-python
$ bitbake-layers add-layer ../layers/meta-openembedded/meta-multimedia
$ bitbake-layers add-layer ../layers/meta-openembedded/meta-networking
$ bitbake-layers add-layer ../layers/meta-raspberrypi
$ bitbake-layers add-layer ../layers/meta-kodi

local.confの修正

MACHINEの行を修正する。

MACHINE = "raspberrypi3"
DL_DIR ?= "${TOPDIR}/../downloads"

# enable uart
ENABLE_UART = "1"

# systemd
DISTRO_FEATURES_append = " systemd pam"
VIRTUAL-RUNTIME_init_manager = "systemd"
DISTRO_FEATURES_BACKFILL_CONSIDERED = "sysvinit"
VIRTUAL-RUNTIME_initscripts = ""

# IMAGE_INSTALL_append = " connman connman-client"

IMAGE_ROOTFS_SIZE = "1782579"

# kodi
IMAGE_INSTALL_appen = " \
            kodi \
            kodi-addon-inputstream-adaptive \
            kodi-addon-pvr-hts \
            libkodiplatform \
"

kodiのconfigureでエラー

下記のエラーが発生してdo_configureで失敗する。

Could NOT find GBM (missing: GBM_LIBRARY GBM_INCLUDE_DIR)

例えば下記のようにlocal.confを修正して、GBMを無効にしてみる。

EXTRA_OECMAKE_append_pn-kodi = " -DENABLE_GBM=OFF"

すると、次のようなメッセージがログに追加される。

| CMake Warning at CMakeLists.txt:111 (message):
|   Your request to disable the dependency GBM required on platform gbm was
|   ignored.  Please choose another platform or add "-DENABLE_GBM=ON" to your
|   CMake command line to resolve this warning.

つまり、GBMを有効化できないなら他のプラットフォームを使えと。

そげな。。。

いろいろ調べると、libgbmはmesaの持ち物らしい。 meta-raspberrypiのプラットフォームでmesaを有効にするにはlocal.confに次の行を記述する。

MACHINE_FEATURES += "vc4graphics"

CMAの確保に失敗する

sumoをベースにkodiを構築した場合、起動してしばらくすると次のエラーが発生しkodiが制御不能になる。

カーネル自体は動作しているので、シリアルコンソールなどでコマンドは実行できる。

[  364.142061] vc4-drm soc:gpu: failed to allocate buffer with size 16777216
[  364.170268] vc4-drm soc:gpu: failed to allocate buffer with size 16777216
[  364.197362] vc4-drm soc:gpu: failed to allocate buffer with size 16777216
[  364.204449] [drm:vc4_bo_create [vc4]] *ERROR* Failed to allocate from CMA:
[  364.211492] [drm]                         kernel:   8100kb BOs (1)
[  364.217836] [drm]                            V3D:  47104kb BOs (85)
[  364.224269] [drm]                     V3D shader:     44kb BOs (11)
[  364.230654] vc4_v3d 3fc00000.v3d: Failed to allocate memory for tile binning: -12. You may need to enable CMA or give it more memory.

CMAのアロケーションに失敗しているが、cat /proc/meminfo | grep -i cmaなどして、CMAFreeのサイズを確認しても、十分にサイズは余っている。

カーネルのバージョンに依存する不具合のようで、thudブランチに切り替えたところ、現時点ではこのエラーは発生していない。

Z11 mini(nx529j)にMoKee(Android 7.1.2ベース)をインストールする

MoKeeをインストールする動機

アマゾンでZ11 mini(nx529j)を買った。結構古いモデルでAndroidバージョンは5.1とか。 今時さすがにそれでは使う気にならなかったので、Android7か8を入れる方法探した。

MoKeeがAndroid7.1.2をベースにしたバージョンを公開しており、 意外と情報が多かったので、これをインストールすることにした。

端末の開発者オプションを有効化する。

いつもの連打で開発オプションをメニューに出し、開発者オプションで以下の項目を有効化する

udevルールの登録

$ sudo vi /etc/udev/rules.d/51-z11-mini.rules

次の内容で作成する

SUBSYSTEM=="usb", ATTR{idVendor}=="19d2", MODE="0666", GROUP="plugdev"

その後、次のコマンドを実行し、adbで認識できるようにする。

$ sudo adb kill-server
$ sudo adb start-server
$ adb devices
List of devices attached
NX529J  unauthorized

ここでunauthorizedが表示される場合は、端末側でPCを信頼するかアラートが出ているので許可するをタップ。

認識できた場合は次のようにdeviceになる。

$ adb devices
List of devices attached
NX529J  device

fastbootをインストール

次にコマンドを実行し、fastbootをインストールする。

$ sudo apt install android-tools-fastboot

TWRPのイメージをダウンロード

ここDownloadをクリックし、z11_mini_twrp+root.rarをダウンロードする。

その後適当な場所に展開し、z11_twrp.imgを抽出する。

端末のパーティションをバックアップ

TWRPのイメージで起動

まずはTWRPのイメージを書き込まずにTWRPで起動してみる。

fastbootモードで端末を再起動する。

$ adb reboot bootloader

z11_twrp.imgを保存した場所に移動し、次のコマンドで起動する。

$ fastboot boot z11_twrp.img

TWRPの画面でBackupを選択する。

次の項目をバックアップする。

  • System
  • Data(excl. storage)
  • Recover
  • Boot
  • EFS
  • Firmware
  • System Image

Backup Complete Successfulが表示されたら、Reboot Systemをタップする。

バックアップしたファイルをPCに保存する

バックアップしたファイルを次のコマンドでPCに保存する。

$ mkdir z11_mini_backup
$ cd z11_mini_backup
$ adb pull /storage/sdcard0/TWRP/BACKUPS/Nubia_Z11_mini/2018-12-14--03-48-57_NX529J_ENCommon_V123

この時点で10GBくらいあるので、端末側からはバックアップデータを削除する。

カスタムROMのイメージをダウンロード

ここからMK71.2-nx529j-181128-RELEASE.zipをダウンロード

次のコマンドで、zipを端末にコピー

$ adb push MK71.2-nx529j-181128-RELEASE.zip /storage/sdcard0/

gappsイメージをダウンロード

ここからopen_gapps-arm64-7.1-pico-20181017.zipをダウンロードする。

gappsにはnanoやpicoなどのパッケージがあるが、それらの差分はここを参照。

とりあえず、最小限のpicoをインストールする。

$ adb push open_gapps-arm64-7.1-pico-20181017.zip /storage/sdcard0/

カスタムROMとgappsのインストール

TWRPの書き込み

z11_twrp.imgを保存した場所に移動し、次のコマンドでTWRPを書き込む

$ adb reboot bootloader
$ fastboot flash recovery z11_twrp.img

次に端末側でreboot to recovery modeを選択する。

インストール済みのOSを削除

TWRPでWipe->Advanced Wipeの順でタップする。

次に以下の項目をチェックしてSwipe to Wipeを行なう。

  • Dalvik ART Cache
  • System
  • Data
  • Cache

次にTWRPのメインメニューに戻りInstallをタップする。

MK71.2-nx529j-181128-RELEASE.zipを選択したあと、Add more Zipsをタップし、open_gapps-arm64-7.1-pico-20181017.zipを選択する。

次にSwipe to confirm flashを実行するためにスワイプする。

すべて書き込みが成功しdoneが表示されたら、Reboot Systemをタップし再起動する。

次回起動時にMOKEEの設定画面が表示されれば成功。

注意点

TWRPとしてTWRP_3.2.1-5_nx529j.imgを使用すると、カスタムROMのインストール時にエラーが発生するので、z11_twrp.imgの方を使用すること。

間違ってTWRP_3.2.1-5_nx529j.imgを書き込んだ場合でも、fastbootモードで起動すればTWRPのイメージを書き直せるので、致命的な失敗になることはないと思われる。

参考

udevルール TWRP及びカスタムロムの書き込み []

LeafletでGeocoding

簡単に住所検索

Control.Geocoder.jsを使用すると、簡単に住所検索機能が追加できる。

これはどうやらLeafletの開発者本人が作ってるものっぽい。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Leaflet Geocoder Test</title>

    <link rel="stylesheet" href="https://unpkg.com/leaflet@1.3.4/dist/leaflet.css" />
    <script src="https://unpkg.com/leaflet@1.3.4/dist/leaflet.js"></script>

    <link rel="stylesheet" href="https://unpkg.com/leaflet-control-geocoder/dist/Control.Geocoder.css" />
    <script src="https://unpkg.com/leaflet-control-geocoder/dist/Control.Geocoder.js"></script>

    <script src="https://code.jquery.com/jquery-3.3.1.min.js"></script>
</head>

<body>
<div id="map" style="width: 700px; height: 400px"></div>
<script>
    let map = L.map('map').setView([35.731059, 139.739748], 18);
    $(function () {
        L.tileLayer(
            'http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png',
            {
                attribution: 'Map data &copy; <a href="http://openstreetmap.org">OpenStreetMap</a>',
                maxZoom: 18
            }
        ).addTo(map);
        let options = {
            geocoder: new L.Control.Geocoder.Nominatim()
        };
        L.Control.geocoder(options).addTo(map);
    });
</script>

</body>
</html>

これスゴいのがoptionsに渡すgeocoderを切り替えることで、10種類くらいのGeocoderを簡単に使用することができる。 中には、というか大半は予めAPI Keyを取得しておく必要があるが。。。

簡単に住所検索機能が追加できるのはスゴい。。。

obnizとGPSモジュールで地図(その4)

概要

前回の続き、というか番外編。

Google Map以外の地図

Google MapのJavaScript APIのキーを取得するには、どうやら課金情報(クレジットカードなど)の登録も必要になるらしい。 実際にはしなくても使用できるっぽいことをネットで見かけているけど。。。

JavaScriptで使用できるオープンソースの地図ライブラリを探したところ、Leafletを見つけた

こちらは制限なく地図データを使用できる。

任意の場所を地図で表示

こちらも表示したい緯度と軽度がわかれば、次のようにするだけで地図は表示することができる。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Leaflet Test</title>
    <link rel="stylesheet" href="https://unpkg.com/leaflet@1.3.4/dist/leaflet.css" />
    <script src="https://unpkg.com/leaflet@1.3.4/dist/leaflet.js"></script>
    <script src="https://code.jquery.com/jquery-3.3.1.min.js"></script>
</head>

<body>
<div id="map" style="width: 700px; height: 400px"></div>
<script>
    let map = L.map('map').setView([35.69496, 139.76746000000003], 18);
    $(function () {
        L.tileLayer(
            'http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png',
            {
                attribution: 'Map data &copy; <a href="http://openstreetmap.org">OpenStreetMap</a>',
                maxZoom: 18
            }
        ).addTo(map);
    });
</script>

</body>
</html>

こちらもかなり手軽に地図を表示することができる。

地図データはL.tileLayerに設定するURLによって切り替えることができる。 今回はOpenStreetMapを使用している。

f:id:mickey_happygolucky:20181210232145p:plain
Leaflet + OpenStreetMap

obniz + Leafletのサンプル

obniz + GPSモジュール + 地図のサンプルをLeafletを使用するように変更する。

obnizとGPSモジュールの接続についてはその1を参照。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Obniz GPS Leaflet Sample</title>
    <link rel="stylesheet" href="https://unpkg.com/leaflet@1.3.4/dist/leaflet.css" />
    <script src="https://unpkg.com/leaflet@1.3.4/dist/leaflet.js"></script>
    <script src="https://code.jquery.com/jquery-3.3.1.min.js"></script>
    <script src="https://unpkg.com/obniz/obniz.js"></script>
    <script src="https://cdn.jsdelivr.net/gh/infusion/GPS.js@v0.4.7/gps.min.js"></script>
</head>
<body>

<div id="obniz-debug"></div>
<div id="map" style="width: 700px; height: 400px"></div>

<script>
    let gps = new GPS;
    let obniz = new Obniz('OBNIZ_ID_HERE');
    let buffer = '';
    let counter = 0;
    let map = null;

    obniz.onconnect = async function () {
        obniz.io0.output(1); //VCC
        obniz.io3.output(0); //GND

        obniz.display.print('waiting GPS...');
        const uart = obniz.getFreeUart();
        uart.start({tx: 1, rx: 2, baud: 9600});

        uart.onreceive = function (data, text) {
            buffer += text;
            if (++counter % 3 === 0) {
                const lines = buffer.split(/\n/);
                for (const s of lines) {
                    if (s.indexOf('$GPGGA') !== -1) {
                        gps.update(s);
                    }
                }
                buffer = '';
            }
        };
    };

    gps.on('data', function(parsed) {
        const lat = parsed.lat;
        const lon = parsed.lon;
        if (lat != null && lon != null) {
            update_map(lat, lon);
        }
    });

    function update_map(lat, lon) {
        if (map == null) {
            map = L.map('map').setView([lat, lon], 18);
            L.tileLayer(
                'http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png',
                {
                    attribution: 'Map data &copy; <a href="http://openstreetmap.org">OpenStreetMap</a>',
                    maxZoom: 18
                }
            ).addTo(map);
            obniz.display.clear();
            obniz.display.print('GPS ok');
        } else {
            map.setView([lat, lon]);
        }
    }

</script>

</body>
</html>

前回のプログラムとは地図の部分を変更しただけなので、それ以外の部分は全く同じ。

前回と同様の理由で実行画面は載せない。

obnizとGPSモジュールで地図(その3)

概要

前回の続き

Google Mapの表示

自分のページにGoogle Mapを埋め込むにはAPI Keyを取得し、使用時に設定する必要がある。 しかし、API Keyを使用しなくても実験用途には使用できる。

その際、次のように明らかに開発用途だとわかるようになっている。

f:id:mickey_happygolucky:20181209184140p:plain
API KeyなしでのGoogle Map

この時、「このページではGoogleマップが正しく読み込まれませんでした。」というダイアログが表示されるがOKをクリックする。

任意の場所を地図で表示

表示したい緯度と軽度がわかれば、次のようにするだけで地図は表示することができる。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Map Test</title>
    <script src="https://code.jquery.com/jquery-3.3.1.min.js"></script>
    <script src="https://maps.googleapis.com/maps/api/js"></script>
</head>

<body>
<div id="sample" style="width: 700px; height: 400px"></div>
<script>
    var map;
    $(function () {
        map = new google.maps.Map($('#sample')[0], {
            center: {
                lat: 35.69496, // 緯度
                lng: 139.76746000000003 // 経度
            },
            zoom: 19
        });
    })
</script>

</body>
</html>

ぜんぶくっつけてみる

その1からここまで作ってきたサンプルを組み合わせて、 obnizに接続したGPSモジュールから取得したNMEAをGPS.jsでパースし、Google Mapで表示するプログラムを作ってみる。

obnizとGPSモジュールの接続についてはその1を参照。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Obniz GPS Google Map Sample</title>

    <script src="https://code.jquery.com/jquery-3.3.1.min.js"></script>
    <script src="https://unpkg.com/obniz/obniz.js"></script>
    <script src="https://cdn.jsdelivr.net/gh/infusion/GPS.js@v0.4.7/gps.min.js"></script>
    <script src="https://maps.googleapis.com/maps/api/js"></script>
</head>
<body>

<div id="obniz-debug"></div>
<div id="sample" style="width: 700px; height: 400px"></div>

<script>
    let gps = new GPS;
    let obniz = new Obniz('OBNIZ_ID_HERE');
    let buffer = '';
    let counter = 0;
    let map = null;

    obniz.onconnect = async function () {
        obniz.io0.output(1); //VCC
        obniz.io3.output(0); //GND

        obniz.display.print('waiting GPS...');
        const uart = obniz.getFreeUart();
        uart.start({tx: 1, rx: 2, baud: 9600});

        uart.onreceive = function (data, text) {
            buffer += text;
            if (++counter % 3 === 0) {
                const lines = buffer.split(/\n/);
                for (const s of lines) {
                    if (s.indexOf('$GPGGA') !== -1) {
                        gps.update(s);
                    }
                }
                buffer = '';
            }
        };
    };

    gps.on('data', function(parsed) {
        const lat = parsed.lat;
        const lon = parsed.lon;
        if (lat != null && lon != null) {
            update_map(lat, lon);
        }
    });

    function update_map(lat, lon) {
        if (map == null) {
            map = new google.maps.Map($('#sample')[0], {
                center: {
                    lat: lat,
                    lng: lon
                },
                zoom: 19
            });
            obniz.display.clear();
            obniz.display.print('GPS ok');
        } else {
            map.setCenter({lat: lat, lng: lon});
        }
    }

</script>

</body>
</html>

UARTで受信したデータは、必ずしも切りの良いところまで受信できているわけではないため、 3回に1回だけパースを行なうようにしている。

実行画面を載せると、住所がモロバレするので今回は載せない。

GPSモジュールが衛星に捕捉され緯度経度が取れるようになるまで数分かかるので、 実行してから気長に待つ必要がある。

obnizとGPSモジュールで地図(その2)

概要

前回の続き

取得したNMEAをパース

GPSモジュールから取得できるのはNMEAというデータのかたまり。

こんな感じ。

$GPTXT,01,01,02,LLC FFFFFFFF-FFFFFFED-FFFFFFFF-FFFFFFFF-FFFFFFF9*50
$GPRMC,,V,,,,,,,,,,N*53
$GPVTG,,,,,,,,,N*30
$GPGGA,,,,,,0,00,99.99,,,,,,*48
$GPGSA,A,1,,,,,,,,,,,,,99.99,99.99,99.99*30
$GPGLL,,,,,,V,N*64
$GPTXT,01,01,02,ANTSTATUS=INIT*25
$GPRMC,,V,,,,,,,,,,N*53

GPSモジュールが衛星に捕捉されると、ここに緯度経度の情報が入ってくるのだが、 これを自前で解析するのは面倒。

そこで、GPS.jsというライブラリを使用させてもらう。

GPS.jsは基本的には、Node.jsで使用することを想定しているようだが、次の通りブラウザからも使用できるようになっている。

Using GPS.js with the browser
The use cases should be rare to parse NMEA directly inside the browser, but it works too.

<script src="gps.js"></script>
<script>
   var gps = new GPS;
   gps.update('...');
</script>

ただ、レアケースだと言っていることと、やはりダウンロードして使うことが前提となっている様子。

Webサーバを構築せずに手軽にやりたいので、これをCDNでアクセスできないか検討する。

jsDeliver

いろいろなJavaScriptのライブラリのCDNを提供しているjsDeliverというありがたいサイトがある。

ここによると、GithubにおいてあるJavaScriptCDNでアクセスできるようになるらしい。

// load any GitHub release, commit, or branch
// note: we recommend using npm for projects that support it
https://cdn.jsdelivr.net/gh/user/repo@version/file

バージョン情報が必要らしい。masterだと実行するタイミングで意図通りに動作するかわからないので、 リリースタイミングのタグかブランチを探してみる。

GPS.jsの人は、リリースごとにきちんとタグを打ってくれる人のようで、最新版は「v0.4.7」というタグが打たれていた。

jsDeliverのGithubルールにGPS.jsのURLを当てはめていくと次のようになる。

<script src="https://cdn.jsdelivr.net/gh/infusion/GPS.js@v0.4.7/gps.min.js"></script>

NMEAから緯度経度を取得してみる

GPS.jsは、GPSクラスを持っており、gps.on()にfunctionを登録しておくとデータが更新されたタイミングでコールバックしてくれる。 データの更新はgps.update()にNMEAを渡して呼び出すタイミングで行われる。 これら踏まえてサンプルを作ってみる。

NMEAの中で緯度経度の情報の取得に必要なのはGPGGAというデータらしい。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>GPS parse</title>
    <script src="https://cdn.jsdelivr.net/gh/infusion/GPS.js@v0.4.7/gps.min.js"></script>
    <script src="https://code.jquery.com/jquery-3.3.1.min.js"></script>
</head>
<body>

<div id="obniz-debug"></div>
<textarea id="text" style="width:100%; height:200px; resize:none;"></textarea>

<script>
   var gps = new GPS;

   // Add an event listener on all protocols
   gps.on('data', function(parsed) {
       var text = 'lat :' + parsed.lat + ', lon :' + parsed.lon;
       var textarea = $('#text');
       textarea.text(textarea.text() + text);
       textarea.scrollTop(textarea[0].scrollHeight);
   });

   gps.update('$GPGGA,224900.000,4832.3762,N,00903.5393,E,1,04,7.8,498.6,M,48.0,M,,0000*5E');
</script>

</body>
</html>

今回は適当なデータを直に埋め込んでいる。

実行画面

f:id:mickey_happygolucky:20181209093303p:plain
実行画面

緯度と経度がそれぞれ取得できた。