Never Ride Alone:サイクリストのためのスマート安全システム
必要な部品と材料
| 部品 | 数量 |
|---|---|
| 5mm LED:赤 | 2 |
| u-blox Ublox neo 6m GPS | 1 |
| Arduino MKR Fox 1200 | 1 |
| バッテリー、3.7V | 1 |
| ブザー | 1 |
| SparkFun プッシュボタンスイッチ 12mm | 1 |
| スライドスイッチ | 1 |
| 抵抗 1kオーム | 1 |
| Seeed Grove - 振動センサー (SW-420) | 1 |
必要なツールと機械
- 3Dプリンター(汎用)
プロジェクト概要
こんにちは、私はヘスス・ソリアーノ、電子工学の学生、起業家……そしてサイクリストです!

12歳から自転車に乗っていて、通常は一人でトレーニングしています。これまでにたくさんの転倒、事故、その他のトラブルを経験しました。ある日、激しい事故で意識を失ったら、私は死ぬかもしれないと気づきました。山の中で一人きりだったのです!
少し動画で想像してみてください。自転車に乗って景色を楽しんでいると、突然車が現れて轢かれる。斜面を転落し、運転手は逃走。あなたは意識を失い、閉じ込められ、世界から見えない存在になります。
2〜3時間後、家族が心配し始める。救急隊があなたを特定しようとするが、4〜5時間後にようやく発見。その時点ではおそらく手遅れです。
そこで調査を始め、いくつかの興味深い選択肢を見つけました:まず、Strava Summitというアプリ。事故時にGPS位置を友人に送信できます。SpecializedのAngiも選択肢でした。ヘルメットに統合され、衝突時に助けを呼べます。しかし問題がありました。これらすべてのシステムは**モバイル電話の電波**が必要です。バッテリー、GPS、モバイル電波のすべてが必要だったのです。
それが「Never Ride Alone(NRA)」誕生のきっかけです。データ送信のソリューションとしてSigfoxを見つけ、ArduinoにSigfoxモジュールがあることを知ったとき、これは素晴らしいと思いました!

NRAは「逆アラーム」のように動作することにしました。サイクリングに出かけるとき、ボタンを押してNRAを起動。LEDが点滅し、視認性も向上します。30秒間動きが検出されない場合、緊急モードを発動。ブザーが鳴り、誰も止めない場合(危険ではなく、単にオフし忘れただけなら)は怪我をしていると判断。自動的にGPSモジュールが作動し、Sigfoxネットワーク経由でGPS位置情報をすべての友人/家族に送信します。

多くのサイクリストにヒアリングし、ミニマルなデザインにたどり着きました。サイクリストは使いやすく、バッテリー持ちの良いガジェットを求めています。NRAなら、ボタンを押してペダルを漕ぐだけ。トレーニング後は止めて充電するだけです。
NRAのケースはABSプラスチック製。バレンシア工科大学提供の3Dプリンターで作成しました。

NRAの設計ファイルはここで編集/ダウンロードできます:
https://www.tinkercad.com/things/fmkE685dNfP-nra-v01
このビルドでは、Arduino MKR1200、Neo 6-m GPS、ボタン、2つのLED、1kオーム抵抗、ブザー、振動センサー、3.7V 150mAhバッテリーを使用します。

ArduinoとSigfoxの設定を開始しました。これがSigfoxバックエンドページでの私の設定です:

Arduino MKR1200がデータを送信すると、このサーバーが生データを情報に変換します。私の場合、経度、緯度、高度を使用。この情報で、設定したすべての人(友人、家族など)にメールを送信し、Google Mapsのリンクをクリックすれば負傷したサイクリストの位置がわかります。

すべてのコンポーネントがケースに収まる様子:

まずGPSモジュール。プラス、マイナス、データ転送線のみ必要。アンテナはフレームに接着。Arduinoがその上に配置されます。

バッテリーをArduinoに接続し、スイッチも追加。これでオン/オフ可能です。

バッテリーコネクターもあり、これでNRAを充電できます。充電時はオフにしておく必要があります。

このプロジェクトではドローン用バッテリー充電器でNRAを充電しました。完璧に動作しました。

配線後、アンテナをArduinoの上に配置。すべてのコンポーネントをこのシェルに収めるのは少し複雑ですが、注意深くやれば可能です。

このビルドでは振動センサーは使用しませんでした。この場合、NRAに電源が入ると10分ごとにGPS位置を送信し続けます。結果は良好でした。
道路沿いでもNRAをテストしました。非常に良い経験でした。
本当に使いやすく、手元に信頼できるガジェットがあるという安心感があります。

これがNever Ride Aloneの歴史です。常にあなたを見守るガジェット。いつライドしても、事故の際には必ず位置が特定されるので安心です。

Never Ride Aloneチームは地球環境の改善にも取り組んできました。すべてのライドテスト中、山で見かけたゴミをすべて拾いました。持続可能な開発目標(SDGs)に従い、ライド中に見つけたすべてのゴミを除去しようとしました。

拾ったゴミを見て、山のゴミの大半がサイクリスト由来だと気づきました!エナジーバーの包装、ジェル、タイヤなどを大量に回収。今、すべてのサイクリストへの啓発活動を行っています。レース中に「クリーンポイント」を設置し、タイムをロスせずにゴミを捨てられるようにしています。このエリアは区切られており、レース終了後にすべて清掃されます。

お読みいただきありがとうございました。安全にライドしてください :)
コード
NEVER RIDE ALONE V.0.1 Arduino
NRAコード。センサーは未使用。10分ごとにすべてのGPSデータを送信。
///// NEVER RIDE ALONE V.0.1 /////
//.............................//
// ////AUTOR: JESUS SORIANO ADAM////
// ////////FECHA: 05/05/2019 ///////
/////////////////////////////////
#include <SigFox.h>
#include <ArduinoLowPower.h>
#include <TinyGPS.h> // TinyGPSをインクルード
#define WAITING_TIME 10 // メッセージごとの待機時間(分)
#define GPS_PIN 2 // (トランジスタピン、省電力用。現在は未使用)
#define GPS_INFO_BUFFER_SIZE 128
bool debug = true; ////////DEBUG/////
const int backlights = 4;
const int pulsador = 5;
const int audio = 3;
TinyGPS gps; // GPSオブジェクト
// GPSデータ変数
int year;
byte month, day, hour, minute, second, hundredths;
unsigned long chars;
unsigned short sentences, failed_checksum;
char GPS_info_char;
char GPS_info_buffer[GPS_INFO_BUFFER_SIZE];
unsigned int received_char;
bool message_started = false;
int i = 0;
// GPS座標構造体、32ビットプラットフォームで12バイト
struct gpscoord {
float a_latitude; // 4バイト
float a_longitude; // 4バイト
float a_altitude; // 4バイト
};
float latitude = 0.0f;
float longitude = 0.0f;
float altitud = 0;
// 待機関数
void Wait(int m, bool s) {
// m分待機
// s: 遅いLEDパルス
if (debug) {
Serial.print("Waiting: ");
Serial.print(m);
Serial.println(" min.");
}
digitalWrite(LED_BUILTIN, LOW);
if (s) {
int seg = m * 30;
for (int i = 0; i < seg; i++) {
digitalWrite(backlights, HIGH);
delay(500);
digitalWrite(backlights, LOW);
delay(500);
}
} else {
int seg = m * 15;
for (int i = 0; i < seg; i++) {
digitalWrite(backlights, HIGH);
delay(500);
digitalWrite(backlights, LOW);
delay(500);
}
}
}
// Sigfoxデータ送信関数
void SendSigfox(String data) {
if (debug) {
Serial.print("Sending: ");
Serial.println(data);
if (data.length() > 12) {
Serial.println("Message too long, only first 12 bytes will be sent");
}
}
// EOL削除
// data.trim();
// モジュール開始
SigFox.begin();
// 初回設定後少なくとも30ms待機(前回は100ms)
delay(100);
// すべての保留割り込みをクリア
SigFox.status();
delay(1);
if (debug) SigFox.debug();
delay(100);
SigFox.beginPacket();
SigFox.print(data);
if (debug) {
int ret = SigFox.endPacket(true); // バッファをSIGFOXネットワークに送信し、レスポンスを待機
if (ret > 0) {
Serial.println("No transmission");
} else {
Serial.println("Transmission ok");
}
Serial.println(SigFox.status(SIGFOX));
Serial.println(SigFox.status(ATMEL));
if (SigFox.parsePacket()) {
Serial.println("Response from server:");
while (SigFox.available()) {
Serial.print("0x");
Serial.println(SigFox.read(), HEX);
}
} else {
Serial.println("Could not get any response from the server");
Serial.println("Check the SigFox coverage in your area");
Serial.println("If you are indoor, check the 20dB coverage or move near a window");
}
Serial.println();
} else {
SigFox.endPacket();
}
SigFox.end();
}
// GPSデータ変換関数
/* GPS浮動小数点データを文字データに変換 */
String ConvertGPSdata(const void* data, uint8_t len) {
uint8_t* bytes = (uint8_t*)data;
String cadena;
if (debug) {
Serial.print("Length: ");
Serial.println(len);
}
for (uint8_t i = len - 1; i < len; --i) {
if (bytes[i] < 12) {
cadena.concat(byte(0)); // 未テスト
}
cadena.concat(char(bytes[i]));
if (debug) Serial.print(bytes[i], HEX);
}
if (debug) {
Serial.println("");
Serial.print("String to send: ");
Serial.println(cadena);
}
return cadena;
}
// GPS位置取得関数
String GetGPSpositon() {
int messages_count = 0;
String pos;
if (debug) Serial.println("GPS ON");
digitalWrite(GPS_PIN, HIGH); // GPSオン
Wait(1, false);
while (messages_count < 5000) {
while (Serial1.available()) {
int GPS_info_char = Serial1.read();
if (GPS_info_char == '$') messages_count++; // メッセージ開始。カウント。
if (debug) {
if (GPS_info_char == '$') {
// メッセージ開始
message_started = true;
received_char = 0;
} else if (GPS_info_char == '*') {
// メッセージ終了
for (i = 0; i < received_char; i++) {
Serial.write(GPS_info_buffer[i]); // 完全受信後、PCにメッセージ出力
}
Serial.println();
message_started = false; // 次のメッセージ準備
} else if (message_started == true) {
// メッセージ開始済みで新しい文字を受信
if (received_char <= GPS_INFO_BUFFER_SIZE) {
// バッファオーバーフロー防止
GPS_info_buffer[received_char] = GPS_info_char;
received_char++;
} else {
// リセット(オーバーフロー発生)
message_started = false;
received_char = 0;
}
}
}
if (gps.encode(GPS_info_char)) {
gps.f_get_position(&latitude, &longitude);
altitud = gps.altitude() / 100;
// 専用構造体に座標格納
gpscoord coords = {altitud, longitude, latitude};
gps.crack_datetime(&year, &month, &day, &hour, &minute, &second, &hundredths);
if (debug) {
Serial.println();
Serial.println();
Serial.print("Latitud/Longitud: ");
Serial.print(latitude, 5);
Serial.print(", ");
Serial.println(longitude, 5);
Serial.println();
Serial.print("Fecha: ");
Serial.print(day, DEC);
Serial.print("/");
Serial.print(month, DEC);
Serial.print("/");
Serial.print(year);
Serial.print(" Hora: ");
Serial.print(hour, DEC);
Serial.print(":");
Serial.print(minute, DEC);
Serial.print(":");
Serial.print(second, DEC);
Serial.print(".");
Serial.println(hundredths, DEC);
Serial.print("Altitud (metros): ");
Serial.println(gps.f_altitude());
Serial.print("Rumbo (grados): ");
println(gps.f_course());
Serial.print("Velocidad(kmph): ");
Serial.println(gps.f_speed_kmph());
Serial.print("Satelites: ");
Serial.println(gps.satellites());
Serial.println();
digitalWrite(backlights, HIGH);
delay(500);
digitalWrite(backlights, LOW);
delay(500);
}
gps.stats(&chars, &sentences, &failed_checksum);
if (debug) Serial.println("GPS turned off");
digitalWrite(GPS_PIN, LOW); // GPSオフ
pos = ConvertGPSdata(&coords, sizeof(gpscoord)); // データ送信
return pos;
}
}
}
pos = "No Signal";
}
// セットアップ
void setup() {
pinMode(4, OUTPUT);
pinMode(backlights, OUTPUT);
pinMode(pulsador, INPUT);
pinMode(audio, OUTPUT);
if (debug) {
Serial.begin(9600);
while (!Serial) {} // シリアルポート接続待ち。ネイティブUSBポートのみ必要
Serial.println("Serial Connected");
}
// Serial1ピン13-14で3.3V接続GPS用
Serial1.begin(9600);
while (!Serial1) {}
if (debug) {
Serial.println("GPS Connected");
}
pinMode(GPS_PIN, OUTPUT); // GPSスイッチピン
if (!SigFox.begin()) {
Serial.println("Shield error or not present!");
return;
}
// デバッグLED有効化、自動ディープスリープ無効化
if (debug) {
SigFox.debug();
} else {
SigFox.end(); // モジュールを最深スリープへ
}
}
// ループ
void loop() {
String position_data;
position_data = GetGPSpositon();
SendSigfox(position_data);
Wait(WAITING_TIME, false);
}
カスタムパーツとエンクロージャ
MKR1200、バッテリー、ボタン、LED、GPS、ブザーが収まります。MKR1200、バッテリー、ボタン、LED、GPS、ブザーが収まります。nra_v_0_1_qCsc7I6QvO.zip
回路図
Arduinoの写真はMKR1200ではありません(注意)
製造プロセス
- 集積回路(IC):現代エレクトロニクスを支える微小強力チップ
- Arduinoデジタルダイス: LCD表示の乱数ジェネレーター自作
- スマートホーム連携向けマルチポジション温度センサーシステム
- DIY Arduino USBゲームコントローラー - 高性能ゲームパッドを自作
- 自作16×8 LEDマトリックスでテトリスゲーム:Arduino Nano実装
- MotionSense: ArduinoとESP8266で作るスマート侵入検知システム
- Arduino MKRバンドルで作るセキュアIoTパズルボックス:ステップバイステップガイド
- MOSMusicプロジェクトキット: MOSFET、Arduinoで音楽同期照明制御
- ボール盤の主要構成部品と機能:ベースからモーターまで完全解説
- 内燃機関の主要コンポーネント:構造と機能
- C# switch 文をマスターする:構文、例、ベストプラクティス