Thinger.io連携でリアルタイム地図表示する確実な車載GPSトラッカー
必要な部品と材料
| 部品 | 数量 | 備考 |
|---|---|---|
| Arduino Leonardo | 1 | 専用ハードウェアシリアル(Serial1)確保のため |
| SIM900開発ボード | 1 | GSM/GPRS通信用、SIM800互換も可 |
| u-blox NEO-6M GPSモジュール | 1 | |
| GPSアンテナ | 1 | 安物は受信感度が悪いので品質重視 |
| 抵抗 220Ω | 2 | ステータスLED用 |
| LED(汎用) | 2 | 電源・GPSロック表示用 |
| ユニバーサル基板 7×5cm | 1 | LED回路用、3Dプリントケース適合サイズ |
| 12V→5V ステップダウンモジュール | 1 | 車バッテリー/シガーソケットから給電 |
| Adafruit microSDカードリーダー | 1 | 5V対応品必須 |
| microSDカード | 1 | 走行距離保存用 |
必要な工具
- 半田ごて
使用ソフトウェア・サービス
- Thinger.io プラットフォーム(無料IoTダッシュボード)
このプロジェクトについて
「GPSトラッカーを作りたい」というシンプルなアイデアから始まり、学習曲線は急でしたが「多機能GPSトラッカー」に進化しました。初作品なのでフィードバック・改善提案を歓迎します!
車載想定で以下の機能を実装:
- GPS座標を2分ごとにThinger.ioクラウドへ送信、ダッシュボードの地図に表示(HTTP POST)
- SMSコマンド応答:現在地または最終取得位置のGoogle Mapsリンクを返信
- 任意距離(例:4000km)ごとにSMS通知:オイルキャッチタンク清掃リマインダー等に活用可能なソフトウェア「オドメーター」
Arduinoのメモリ制約(フラッシュ・RAM)が厳しく、効率的なコードと軽量ライブラリの選定が必須でした。
主要コンポーネント詳細
- NEO-6M GPS:安価で入手しやすい。ハードウェアシリアル(Serial1)接続
- GPSアンテナ:互換品なら何でも可だが、安物は衛星捕捉数が少なく不安定。品質の良いもの推奨
- SIM900開発ボード:GSM/GPRS用。ソフトウェアシリアル(D8/D9)接続。SIM800系も動作の可能性あり
- Arduino Leonardo:ハードウェアシリアルが2系統必要なため選択(UNOだとソフトウェアシリアル併用でデバッグ困難)
- SDカードリーダー:Adafruit品は5V対応でSPI接続が容易。警告:3.3V専用品を5Vで駆動すると破損します
- ステータスLED:電源表示用とGPSロック表示用
- データ通信付きSIMカード
- 3Dプリントケース:STLファイル同梱、そのままプリント可能
使用ライブラリ
- NeoGPS:GPS解析用。TinyGPS++より軽量・高速。ライブラリマネージャからインストール可。GitHub
- Time:UTCタイムゾーン変換用。GitHub
- PetitFS:超軽量FATファイルシステム。SDカード読み書き用。GitHub
なぜThinger.io公式ライブラリを使わないのか?
公式ライブラリは使いやすいが、Arduino Leonardoのフラッシュメモリの約80%を消費し、他のコードが入りません。そのためHTTP POSTリクエストを自前で実装しています。
SMSコマンド
全て大文字で送信:
- POS:現在地(または最終取得位置)のGoogle Mapsリンク返信
- GETKM:リセット後の累積走行距離返信
- RESETKM:距離カウンタを0にリセット

NeoGPSの設定
インストール後、ライブラリフォルダ内のGPSPort.hを以下で置き換え:
#ifndef GPSport_h
#define GPSport_h
#define gpsPort Serial1
#define GPS_PORT_NAME "Serial1"
#define DEBUG_PORT Serial
#endif
このファイルでGPS接続シリアルポートを定義します。Mega等の場合はSerial2/Serial3に変更。
精度について: GPSは静止時も位置が微小にドリフトするため、距離積算には誤差があります。本プロジェクトでは「15秒間で10m未満の移動は静止とみなす」「10km超のジャンプは異常値として除外」というフィルタを実装。サンプリング間隔は15秒(短くすれば精度向上、処理負荷増)。
PetitFSの設定
超軽量だが制約多し:ファイル作成不可、サイズ拡張不可、追記不可、タイムスタンプ更新不可、同時オープン1ファイルのみ。標準SDライブラリは約12%大きく、全機能を収められないため採用。
設定変更:
pffArduino.hのSD_CS_PINを10に変更(SPIのSSピン)pffconf.hで以下を0に無効化:_USE_DIR,_USE_LSEEK,_FS_FAT12,_FS_FAT16pff.cppの不足戻り値FRESULTを追加(初回コンパイルエラー対策)
SDカード準備
- FAT32でフォーマット(SD Card Formatter推奨)
- 同梱の
dist.txtとsettings.txtをカード直下に配置(ライブラリはフォーマットに極めてシビア) dist.txt:初期距離(例:"0")settings.txt:通知トリガー距離(メートル単位、例:"4000000"で4000km)

SIM900ボードの準備




- 電源: Arduinoから給電不可。外部5V-9V/2A以上の電源をDCジャックへ。電源セレクターを「外部」側に
- シリアル: ソフトウェアシリアル用ジャンパー位置に設定
- ソフトウェア電源制御: R13ジャンパーを半田付けし、Arduino D7から制御可能に(コード内
SIM900power()使用)。手動電源ボタン運用なら不要 - SIMカードPINロック解除: 通常の携帯電話で解除してから使用
- ファームウェアバージョン(重要!): B10以降必須。HTTPヘッダーのContent-Type設定コマンドがB10から対応。
AT+CGMRで確認。古い場合はファームウェア更新が必要
コードのカスタマイズ項目
- APN設定:
connectGPRS()内の"TeleXXX"を契約キャリアのAPNに変更 - Thinger.io URLと認証キー:
postDataThinger()内のURLとauthorizationパラメータを自分のものに置換(HTTPS→HTTPに変更必須。SIM900はSSL非対応) - タイムゾーン:
#define UTC_OFFSET 1を環境に合わせて設定(日本は9) - 通知トリガー距離・メッセージ・宛先電話番号:
DEFAULT_NUMBERとループ内の通知ロジックを編集 - シリアル待機: デバッグ時のみ
while(!Serial);を有効化。本番運用時はコメントアウト必須
Thinger.io連携設定
- アカウント作成後、「Bucket(データベース)」を作成
- 「HTTP Device」を作成(デバイス名は短く。長いと認証キーが256バイトバッファを超える)
- DeviceのCallback設定で作成したBucketを指定
- CallbackのOverviewから「Method URL」と「Authorization Header(Bearer除く)」をメモ
- コード内の
postDataThinger()のURLと認証キーを置換。https→httpに変更必須





送信データ形式:{"longitude":xxx,"latitude":xxx,"date":"DD/MM/YYYY HH:MM:SS"}
成功時シリアル出力:+HTTPACTION: 1,200,0(400エラーならURL/認証キー/ファームウェア確認)
追加データ送信時はsprintfのフォーマット厳守でフィールド追加。
ケース(3Dプリント)








- レイヤー0.2mm、サポートなしでプリント可能
- トップケースのネジ穴はプリント後3mmドリルで開ける設計(サポート不要化)
- 7×5cmユニバーサル基板用マウント付き(ブレッドボード使用時は接着で対応)
- 基板固定用に小さなスペーサー推奨
車両電源への接続




車のACC連動(シガーソケット回路)から給電する方式を採用。エンジンOFFで装置もOFF。常時電源が必要ならバッテリー直結またはモバイルバッテリー併用。ステップダウンモジュール(12V→5V/2USB出力)でArduinoとSIM900に給電。消費電流はGSM通信時約110mAh(電波状況により変動)。
既知の問題
- SMS受信とThinger.io送信が同時刻に発生するとSMSコマンドが取りこぼされる。数秒後に再送で対応可能
- 起動時に圏外だと、その後電波が入っても自動再接続しない(起動時のみ接続処理)。将来的に修正予定
コード
GPS_tracker_Leonardo_v2.ino(全コードは元記事参照。主要部のみ抜粋)
#include <SoftwareSerial.h>
#include <NMEAGPS.h>
#include <GPSport.h>
#include <TimeLib.h>
#include "PF.h"
#include "PetitSerial.h"
#define UTC_OFFSET 9 // 日本時間(UTC+9)
#define TXPin 8 // SIM900 Tx
#define RXPin 9 // SIM900 Rx
#define PWRPin 7 // SIM900 電源制御
#define DEFAULT_NUMBER "+81XXXXXXXXXX" // 通知先電話番号
FATFS fs;
String textMessage, number = DEFAULT_NUMBER;
char sms_msg[160];
float Lat = 0, Long = 0;
boolean valid_location = false;
uint8_t num_sat;
NeoGPS::Location_t prevFix;
NMEAGPS gps;
gps_fix fix;
const char *googlePrefix = "https://maps.google.com/maps?q=";
const char *filename = "DIST.TXT";
const char *settings = "SETTINGS.TXT";
NeoGPS::time_t timeFix;
char datePrint[13], timePrint[10];
float totalDistance = 0, triggerDistance = 4000000;
SoftwareSerial SIM900(TXPin, RXPin);
void setup() {
pinMode(3, OUTPUT); pinMode(4, OUTPUT);
digitalWrite(3, HIGH); // 電源LED点灯
Serial.begin(9600);
// while (!Serial); // デバッグ時のみ有効化
SIM900.begin(9600);
gpsPort.begin(9600);
initializeSD();
SIM900power();
SIM900.println(F("AT")); delay(500);
SIM900.println(F("AT+CMGF=1")); delay(500);
SIM900.println(F("AT+CNMI=1,2,0,0,0")); delay(500);
connectGPRS();
}
void loop() {
while (gps.available(gpsPort)) {
fix = gps.read();
num_sat = fix.satellites;
if (fix.valid.location) {
digitalWrite(4, HIGH); // GPSロックLED
Lat = fix.latitude(); Long = fix.longitude();
if (Lat!=0 && Long!=0 && !valid_location) {
valid_location = true; prevFix = fix.location;
}
}
if (fix.valid.date && fix.valid.time) {
timeFix = fix.dateTime; updateTime();
}
if (valid_location) {
// 15秒ごと距離更新
static const unsigned long REFRESH_INTERVAL_UPD = 15000;
static unsigned long lastRefreshTime = millis();
if (millis() - lastRefreshTime >= REFRESH_INTERVAL_UPD) {
lastRefreshTime += REFRESH_INTERVAL_UPD;
float distanceDelta = prevFix.DistanceKm(fix.location) * 1000;
if (distanceDelta > 10 && distanceDelta < 10000) totalDistance += distanceDelta;
prevFix = fix.location;
}
// 2分ごとSD書き込み+Thinger.io送信
static const unsigned long REFRESH_INTERVAL_WRITE_SD = 120000UL;
static unsigned long lastRefreshTimeWriteSD = millis();
if (millis() - lastRefreshTimeWriteSD >= REFRESH_INTERVAL_WRITE_SD) {
lastRefreshTimeWriteSD += REFRESH_INTERVAL_WRITE_SD;
char buf[9]; dtostrf(totalDistance, 8, 0, buf);
if (PF.open(filename)) Serial.println(F("error open file"));
while (1) {
UINT nr;
if (PF.writeFile(buf, sizeof(buf), &nr)) Serial.println(F("error write file"));
if (nr == sizeof(buf)) { PF.writeFile(0, 0, &nr); break; }
}
// Thinger.ioへHTTP POST
char httpContent[60], tempstrLong[10], tempstrLat[10];
dtostrf(Lat, 2, 6, tempstrLat); dtostrf(Long, 2, 6, tempstrLong);
sprintf(httpContent, "{\"longitude\":%s,\"latitude\":%s,\"date\":\"%s %s\"}", tempstrLong, tempstrLat, datePrint, timePrint);
char httpdataLen[20];
sprintf(httpdataLen, "AT+HTTPDATA=%i,10000", strlen(httpContent));
postDataThinger(httpdataLen, httpContent);
}
}
}
// 走行距離通知
if (totalDistance > triggerDistance) {
char distanceTotalMsg[10];
itoa((totalDistance/1000), distanceTotalMsg, 10);
sprintf(sms_msg, "Empty catchtank! Current distance: %skm", distanceTotalMsg);
textMessage = ""; number = DEFAULT_NUMBER; totalDistance = 0;
sendSMS(sms_msg);
}
updateSerial();
}
// 以下、SIM900制御、SMS送受信、GPRS接続、HTTP POST、SDカード初期化、時刻補正等の関数は元コード参照
3Dプリントパーツ
- トップケース(高さあり版/コンパクト版) - 取り付け穴はプリント後3mmドリルで開孔
- ボトムケース - SIM900・Arduino基板用、コネクタ用切り欠きあり
- SIM900固定具(2タイプ) - マウント穴なしボード対応
dist.txt/settings.txt- SDカードに配置(リネーム必要)
回路図

製造プロセス
- アイロン台の製造:設計、素材、生産プロセス
- サーフボードの構造とデザイン:ポリネシアのルーツから現代の革新まで
- センサーデータをRaspberry Piに統合:実践ガイド
- PSoC 6 Pioneer BoardとIoT Add-On Shieldを極める:開発者向けガイド
- エンジン冷却水不足で走行するリスク:最悪の場合エンジン破損も
- 社内統合システムを活用した精密製造の実現
- Raspberry Pi、自社初MCU「RP2040」と4ドル「Pico」ボードを発表
- 屋外GPS資産追跡:トレンド、課題、新興技術
- Python map()関数をマスター:構文、例、ベストプラクティス
- Java HashMap 完全ガイド:基本から実践まで
- BIOVIA Living MapでCOVID-19薬物再配置を加速