LoRaベースのスマートシティ大気質監視システム
部品と材料
| 部品 | 数量 |
|---|---|
| Arduino UNO | 1 |
| Seeed Base Shield V2 | 1 |
| NodeMCU ESP8266 ブレイクアウトボード | 1 |
| Seeed ラウドネスセンサー | 1 |
| Seeed Grove – 大気質センサー v1.3 | 1 |
| Seeed Grove – 温湿度センサー (DHT11) | 1 |
| Reyax LoRa モジュール (RYLR998) | 1 |
| Seeed 小型ソーラーパネル 80×100mm 1W | 1 |
| Seeed LiPo Rider Pro | 1 |
| 充電式リチウムイオン電池 3.7V | 1 |
必要なツールと機器
- 3Dプリンター (汎用)
ソフトウェアとオンラインサービス
- Arduino IDE
- Grandeur (IoTバックエンドプラットフォーム)
- Grandeur Canvas (ダッシュボードビルダー)
プロジェクト概要
スマートシティと大気質
世界銀行によると、スマートシティはテクノロジーを活用して効率的な公共サービスと持続可能な都市環境を提供します。このビジョンの中心はリアルタイム環境監視であり、汚染源を特定し、交通流の最適化や住民への安全な経路案内など、データに基づいた政策決定を可能にします。
課題と解決策
都市の大気汚染は深刻な健康リスクをもたらし、タイムリーなデータ取得が対策に不可欠です。低コストセンサーノードのIoTネットワークにより、大気質と気象パラメータを継続的に取得できます。信頼性の高い低電力通信層と組み合わせることで、自治体は汚染ホットスポットを検知し、プロアクティブに対応できます。
プロジェクト計画
本システムは騒音、温度、湿度、大気質の主要環境指標を監視し、データをクラウドプラットフォームへ無線送信します。ノード全体は1Wソーラーパネルとリチウムイオン電池バックアップで駆動し、自律運用を実現。クラウドダッシュボードでデータをリアルタイム可視化し、即時の洞察と長期トレンド分析を可能にします。
各センサーを直接クラウドに接続すると電力消費とコストが増大するため、複数の近接ノードからデータを集約し、単一のLoRaゲートウェイ経由で送信します。LoRaの長距離・低電力特性は密集した都市展開に最適です。

バックエンドプラットフォームにはGrandeurを採用しました。Arduino、NodeMCU、Raspberry Pi向けの使いやすいSDKと、ドラッグアンドドロップで構築できるダッシュボードビルダーを提供しているためです。
ソーラー電源はリモートノードに持続可能でメンテナンスフリーなエネルギーを供給し、リチウムイオン電池は日照不足時の運用を保証します。
センサーノードの構築
ノードにはGrove大気質センサー、Groveラウドネスセンサー、DHT11温湿度センサーを統合。これらはSeeed Base Shieldにはんだ付け不要で接続でき、組み立てとメンテナンスを簡素化します。

電源は1WソーラーパネルとSeeed LiPo Rider Pro充電器で供給。5Vブーストコンバーターを内蔵し、Arduinoの動作電圧範囲を維持します。
無線通信にはReyax RYLR998 LoRaトランシーバーを使用。3.6Vロジックで動作するため、Arduinoの5V TXラインを保護する簡易電圧分割回路を追加しています。


LoRaモジュール接続時は、Base Shieldの電源スイッチを3.3Vに設定してください。

組み立て後、ArduinoからLoRaモジュールへATコマンドを送信し、通信を確認します。

アップロードとテスト
付属のArduinoスケッチをアップロードし、シリアルモニターでセンサー値が正しく送信されているか確認します。

エンクロージャーへの組み込み
添付のSTLファイルを3Dプリントし、フィット確認後、両面テープまたはホットグルーで部品を固定。ソーラーパネルを上面に取り付け、電源ケーブルを指定の穴から通します。



Grandeurの設定
Grandeurはデバイスとユーザーを紐付け、リアルタイムダッシュボードを提供するIoTバックエンドです。以下の手順でプロジェクトをセットアップします:
- grandeur.devで新規プロジェクトを作成
- ユーザーアカウントを登録(後でデバイスとペアリング)
- デバイスモデルを定義し、JSONスキーマを設定:
{"temp":0, "humid":0, "air":0, "noise":0} - デバイスをインスタンス化し、固有IDとアクセストークンを取得してArduinoファームウェアに保存
- ダッシュボードまたはAPI(
pairDevice())でデバイスとユーザーをペアリング - Canvasダッシュボードを構築:温度、湿度、騒音レベル、大気質の4つのグラフウィジェットを追加し、対応する変数にバインド
Canvas認証後、ユーザーはログインしてライブデータを閲覧可能。APIキーとデバイストークンはゲートウェイファームウェアで必要です。
IoT LoRaゲートウェイの準備
NodeMCUをLoRaゲートウェイとして動作させ、センサーノードからUARTでデータを受信し、Wi-Fi経由でGrandeurへ転送します。Raspberry Piも選択肢ですが、NodeMCUはコンパクトで低コスト、本プロジェクトの帯域幅とレイテンシ要件を満たします。

ゲートウェイファームウェア
ファームウェアはLoRaモジュールから受信したカンマ区切りペイロードを解析、各値をfloatに変換し、提供SDKでGrandeurへプッシュします。以下はデータ抽出と送信のコアロジックです。
void process_received_data(){
start_pos = inputString.indexOf(start_val);
end_pos = inputString.indexOf(end_val);
String data_string = inputString.substring(start_pos+1, end_pos-1);
int firstCommaIndex = data_string.indexOf(',');
int secondCommaIndex = data_string.indexOf(',', firstCommaIndex+1);
int thirdCommaIndex = data_string.indexOf(',', secondCommaIndex+1);
String temperature = data_string.substring(0, firstCommaIndex);
String humidity = data_string.substring(firstCommaIndex+1, secondCommaIndex);
String noise_level = data_string.substring(secondCommaIndex+1, thirdCommaIndex);
String air_quality = data_string.substring(thirdCommaIndex+1);
temp = temperature.toFloat();
humid = humidity.toFloat();
noise = noise_level.toFloat();
air = air_quality.toFloat();
inputString = "";
stringComplete = false;
}
ダッシュボード表示


ファームウェアコード
センサーノード (Arduino)
#include "AirQuality.h"
#include "Arduino.h"
#include "DHT.h"
#define DHTPIN 4
#define DHTTYPE DHT11
DHT dht(DHTPIN, DHTTYPE);
AirQuality airqualitysensor;
int current_quality =-1;
const int sampleWindow = 50;
unsigned int sample;
float noise;
float temp, humid;
int airQuality;
void setup() {
Serial.begin(115200);
airqualitysensor.init(14);
dht.begin();
}
void loop() {
noise = calculate_sound_in_db();
airQuality = calculate_air_quality();
calculate_temp_humid();
String values = String(temp)+","+String(humid)+","+String(noise)+","+String(airQuality);
String cmd = "AT+SEND=0,"+String(values.length())+","+values;
Serial.println(cmd);
delay(15000);
}
float calculate_sound_in_db(){
unsigned long startMillis= millis();
float peakToPeak = 0;
unsigned int signalMax = 0;
unsigned int signalMin = 1024;
while (millis() - startMillis < sampleWindow){
sample = analogRead(A1);
if (sample < 1024){
if (sample > signalMax) signalMax = sample;
else if (sample < signalMin) signalMin = sample;
}
}
peakToPeak = signalMax - signalMin;
float db = map(peakToPeak,0,1000,48,120);
return db;
}
int calculate_air_quality(){
current_quality=airqualitysensor.slope();
return current_quality;
}
void calculate_temp_humid(){
float h = dht.readHumidity();
float t = dht.readTemperature();
if (isnan(h) || isnan(t)) return;
temp = t;
humid = h;
}
ISR(TIMER1_OVF_vect){
if(airqualitysensor.counter==61){
airqualitysensor.last_vol=airqualitysensor.first_vol;
airqualitysensor.first_vol=analogRead(A0);
airqualitysensor.counter=0;
airqualitysensor.timer_index=1;
PORTB=PORTB^0x20;
}else{
airqualitysensor.counter++;
}
}
ゲートウェイ (NodeMCU/Arduino)
#include <Grandeur.h>
#include <ESP8266WiFi.h>
#include <SoftwareSerial.h>
SoftwareSerial LORA(D7, D8);
String deviceID = "your device id";
String apiKey = "your api key";
String token = "your device token";
String ssid = "your wifi ssid";
String password = "your wifi password";
Grandeur::Project project;
Grandeur::Project::Device device;
int connected = 0;
uint32_t lastUpdate = 0;
String inputString = "";
boolean stringComplete = false;
char start_val = ':';
char end_val = '%';
int start_pos = 0;
int end_pos = 0;
float temp = 0;
float humid = 0;
float air = 0;
float noise = 0;
void onConnection(bool status) {
switch(status) {
case CONNECTED:
Serial.println("Device is connected to the cloud.");
connected = 1;
lastUpdate = millis();
return;
case DISCONNECTED:
Serial.println("Device is disconnected from the cloud.");
connected = 0;
return;
}
}
void connectWiFi() {
WiFi.mode(WIFI_STA);
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
Serial.println("");
Serial.println("WiFi connected");
Serial.println(WiFi.localIP());
}
void dataCallback(const char* code, Var res) {
if(code == "DEVICE-DATA-UPDATED") {
double temp_data = (double) res["update"]["temp"];
double humid_data = (double) res["update"]["humid"];
double noise_data = (double) res["update"]["noise"];
double air_data = (double) res["update"]["air"];
Serial.printf("Temperature: %f Humidity: %f Noise Level: %f Air Quality: %f\n", temp_data, humid_data, noise_data, air_data);
return;
}
Serial.println("Failed to Update DATA");
return;
}
void sendUpdate() {
Var data;
data["temp"] = temp;
data["humid"] = humid;
data["noise"] = noise;
data["air"] = air;
device.data().set("temp", temp, dataCallback);
device.data().set("humid", humid, dataCallback);
device.data().set("noise", noise, dataCallback);
device.data().set("air", air, dataCallback);
}
void setup() {
Serial.begin(9600);
LORA.begin(115200);
inputString.reserve(200);
connectWiFi();
project = grandeur.init(apiKey, token);
device = project.device(deviceID);
project.onConnection(onConnection);
}
void loop() {
serialEvent();
project.loop(WiFi.status() == WL_CONNECTED);
if (stringComplete) {
process_received_data();
if (connected) sendUpdate();
}
}
void serialEvent() {
while (LORA.available()) {
char inChar = (char)LORA.read();
if (inChar == '\n') {
stringComplete = true;
} else inputString += inChar;
}
}
製造プロセス
- OpenSensorsで空気質を測定: ArduinoとPythonによるガイド
- DIY空気質モニター:コスト効率の良いEPA準拠検知器
- Heliumベースのソーラー駆動空気質センサー:設計と導入ガイド
- スマートプラントIoT:ArduinoとAzureで作る接続型グリーンハウス
- Bosch Sensortec、AI搭載BME688空気質センサを発表、ウイルス感染リスク低減へ
- ルネサス、組み込みAIで室内空気質センサープラットフォームを強化、臭気検出を向上
- 微小PM2.5・VOCセンサーがリアルタイム個人空気質モニタリングを実現
- 先進カーボンベースNO₂センサーが室内空気質モニタリングを強化
- IoTセンサーが大気汚染モニタリングと公衆衛生に革命をもたらす
- IoT対応環境空気質監視:安全性・生産性・持続可能性の向上
- 産業用IoT大気質監視:生産性向上とコスト削減