親子で楽しむ SORACOM Harvest

[Crystal Dew World](https://crystalmark.info/)のひよひよです。普段は、ストレージ関連のソフトウェア（CrystalDiskMark、CrystalDiskInfo）を開発しておりますが、今年からIoTにチャレンジしています。

# センサーボックス（温度、湿度、電波強度、位置情報）

Grove IoTスターターキット for SORACOM を使ってセンサーボックス（温度、湿度、電波強度、位置情報）を開発しました。子供たちと家中の温度や電波強度の測定をしたのですが、場所や外的要因によって大きく変化するグラフを眺めながらとても喜んでおりました。モバイルバッテリーさえあれば、どこにでも持ち運べるので旅行のお供にも使えそうです。

Grove接続のセンサーは、半田ごてやブレッドボードでの複雑な配線が不要でお手軽にIoTにチャレンジできるのが良いですね。

## 実装内容

### ベーススケッチ
[soracom-harvest](https://github.com/SeeedJP/WioLTEforArduino/blob/master/examples/soracom/soracom-harvest/soracom-harvest.ino)

### 追加機能
- 電波強度（RSSI）取得

``` c:一部抜粋
  int rssi;
  rssi = Wio.GetReceivedSignalStrength();
```

- 位置情報（GPS）取得

※[TinyGPS++](http://arduiniana.org/libraries/tinygpsplus/)を使用

``` c:一部抜粋
  while(GpsSerial->available()){
    if(gps.encode(GpsSerial->read())){
      if(gps.location.isUpdated()){
        sat = gps.satellites.value();
        lat = gps.location.lat();
        lng = gps.location.lng();
        SerialUSB.println("");
        SerialUSB.print("SAT= "); SerialUSB.println(gps.satellites.value());
        SerialUSB.print("LAT= "); SerialUSB.println(gps.location.lat(), 6);
        SerialUSB.print("LON= "); SerialUSB.println(gps.location.lng(), 6);
      }
    }
  }
```

※soracom-harvestスケッチをカスタマイズして作った今回のスケッチ全体は最後に掲載しています。

### 製作物
##### 写真1. 子供たちと楽しく遊びました！
<img width="320" alt="soracom_grove.jpg" src="https://qiita-image-store.s3.amazonaws.com/0/319837/b4ec6201-71de-14ce-40a1-b40cbed5e076.jpeg">
ドライヤーで温めると一気に温度が上がり、緩やかに温度が下がって、すぐにグラフに反映される。こういったわかりやすい単純な動きが子供の興味を引くのかもしれません。
##### 写真2. センサーボックスで収集したデータをSORACOM Harvestでグラフ化
<img width="320" alt="soracom_rssi.jpg" src="https://qiita-image-store.s3.amazonaws.com/0/319837/d05860f5-a353-82cf-2e4e-cbc391ff1b72.jpeg"> 
##### 写真3. センサーボックスで収集したデータをSORACOM Harvestで地図上にプロット
<img width="320" alt="soracom_maps-1.jpg" src="https://qiita-image-store.s3.amazonaws.com/0/319837/a60afed9-9b3b-b8de-e087-94dce381dc69.jpeg">
##### 写真4. 専用ケースに格納
<img width="320" alt="soracom_gps.jpg" src="https://qiita-image-store.s3.amazonaws.com/0/319837/9db24d80-b6ff-0c4a-05cf-502702f6eb1e.jpeg"> 

### 工夫したポイント

- GPSがロックしたらLEDの色が変わる！

GPSのロック状況が視覚的にわかるので、センサーボックスだけを持ち出して測定する際にとても便利でした。まぁ、当たり前の工夫ですけど。

``` c:一部抜粋
  if(sat >= 4){ // GPS衛星を4つ以上捕捉
    Wio.LedSetRGB(0, 0, 255); // WioLTEのLEDを青に変更
  }else{
    Wio.LedSetRGB(255, 0, 0); // WioLTEのLEDを赤に変更
  }
```
#### 写真5. GPS衛星捕捉中（LED：赤色）
<img width="320" alt="gps_ng_red.jpg" src="https://qiita-image-store.s3.amazonaws.com/0/319837/d7ea6063-2d8d-3711-2b67-ef6a96baca0c.jpeg">
#### 写真6. GPS衛星捕捉完了（LED：青色）
<img width="320" alt="gps_ok_blue.jpg" src="https://qiita-image-store.s3.amazonaws.com/0/319837/156f4118-e492-4cb5-5232-75d7f27341b6.jpeg">

## まとめ
Grove IoT スターターキット for SORACOM を使って、センサーボックス（温度、湿度、電波強度、位置情報）を開発しました。SORACOM Harvestを使えば、収集したセンサー情報を簡単に可視化できるのでとても楽しいです。子供たちも大喜び間違いなし！？

### 参考サイト
- [ソラコム](https://soracom.jp/)
- [初めての「技術ブログ」書き方のご紹介](https://blog.soracom.jp/blog/2018/04/18/howto-techblog/)
- [WioLTE で Harvest の位置情報データを “お手軽に” 試してみた](https://qiita.com/j3tm0t0/items/35a0f34396e94ac240c0)
- [Wio LTEでGrove GPS出力を解析](https://qiita.com/jksoft/items/a922c6fb201d360cf69c)
- [Wio LTEでGPS情報を取得した情報をSORACOM Harvestの地図プレビューで見る](https://qiita.com/cactanaka/items/95172b2c9fa9065cd1f6)
- [SORACOM Harvest で位置情報データをサポートしました](https://blog.soracom.jp/blog/2018/04/26/harvest-map-support/)

## スケッチ全体
``` c:soracom-harvest-sensor-box.ino
#include <WioLTEforArduino.h>
#include <stdio.h>
#include <TinyGPS++.h>

#define INTERVAL        (60000)
#define RECEIVE_TIMEOUT (10000)
#define SENSOR_PIN    (WIOLTE_D38)

TinyGPSPlus gps;
WioLTE Wio;

// for GPS module
HardwareSerial* GpsSerial;
char GpsDataLength;

void setup() {
  delay(200);

  SerialUSB.println("");
  SerialUSB.println("--- START ---------------------------------------------------");

  SerialUSB.println("### I/O Initialize.");
  Wio.Init();

  // for GPS module
  GpsBegin(&Serial);

  SerialUSB.println("### Power supply ON.");
  Wio.PowerSupplyLTE(true);
  delay(500);
  Wio.PowerSupplyGrove(true);
  delay(500);

  SerialUSB.println("### Turn on or reset.");
  if (!Wio.TurnOnOrReset()) {
    SerialUSB.println("### ERROR! ###");
    return;
  }

  SerialUSB.println("### Connecting to \"soracom.io\".");
  if (!Wio.Activate("soracom.io", "sora", "sora")) {
    SerialUSB.println("### ERROR! ###");
    return;
  }

  TemperatureAndHumidityBegin(SENSOR_PIN);

  SerialUSB.println("### Setup completed.");
}

void loop() {
  char data[100];
  float temp;
  float humi;
  int rssi;
  int sat;
  double lat, lng;

  if (!TemperatureAndHumidityRead(&temp, &humi)) {
    SerialUSB.println("ERROR!");
    goto err;
  }

  SerialUSB.print("Current humidity = ");
  SerialUSB.print(humi);
  SerialUSB.print("%  ");
  SerialUSB.print("temperature = ");
  SerialUSB.print(temp);
  SerialUSB.println("C");

  rssi = Wio.GetReceivedSignalStrength();
  while(GpsSerial->available()){
    if(gps.encode(GpsSerial->read())){
      if(gps.location.isUpdated()){
        sat = gps.satellites.value();
        lat = gps.location.lat();
        lng = gps.location.lng();
        SerialUSB.println("");
        SerialUSB.print("SAT= "); SerialUSB.println(gps.satellites.value());
        SerialUSB.print("LAT= "); SerialUSB.println(gps.location.lat(), 6);
        SerialUSB.print("LON= "); SerialUSB.println(gps.location.lng(), 6);
      }
    }
  }
  if(sat >= 4){
    Wio.LedSetRGB(0, 0, 255);
  }else{
    Wio.LedSetRGB(255, 0, 0);
  }

  sprintf(data,"{\"temp\":%.1f,\"humi\":%.1f, \"rssi\":%d, \"lat\":%f, \"lng\":%f}", temp, humi, rssi, lat, lng);

  SerialUSB.println("### Open.");
  int connectId;
  connectId = Wio.SocketOpen("harvest.soracom.io", 8514, WIOLTE_UDP);
  if (connectId < 0) {
    SerialUSB.println("### ERROR! ###");
    goto err;
  }

  SerialUSB.println("### Send.");
  SerialUSB.print("Send:");
  SerialUSB.print(data);
  SerialUSB.println("");
  if (!Wio.SocketSend(connectId, data)) {
    SerialUSB.println("### ERROR! ###");
    goto err_close;
  }

  SerialUSB.println("### Receive.");
  int length;
  length = Wio.SocketReceive(connectId, data, sizeof (data), RECEIVE_TIMEOUT);
  if (length < 0) {
    SerialUSB.println("### ERROR! ###");
    goto err_close;
  }
  if (length == 0) {
    SerialUSB.println("### RECEIVE TIMEOUT! ###");
    goto err_close;
  }
  SerialUSB.print("Receive:");
  SerialUSB.print(data);
  SerialUSB.println("");

err_close:
  SerialUSB.println("### Close.");
  if (!Wio.SocketClose(connectId)) {
    SerialUSB.println("### ERROR! ###");
    goto err;
  }

err:
  delay(INTERVAL);
}

////////////////////////////////////////////////////////////////////////////////////////
//

int TemperatureAndHumidityPin;

void TemperatureAndHumidityBegin(int pin)
{
  TemperatureAndHumidityPin = pin;
  DHT11Init(TemperatureAndHumidityPin);
}

bool TemperatureAndHumidityRead(float* temperature, float* humidity)
{
  byte data[5];

  DHT11Start(TemperatureAndHumidityPin);
  for (int i = 0; i < 5; i++) data[i] = DHT11ReadByte(TemperatureAndHumidityPin);
  DHT11Finish(TemperatureAndHumidityPin);

  if(!DHT11Check(data, sizeof (data))) return false;
  if (data[1] >= 10) return false;
  if (data[3] >= 10) return false;

  *humidity = (float)data[0] + (float)data[1] / 10.0f;
  *temperature = (float)data[2] + (float)data[3] / 10.0f;

  return true;
}

void DHT11Init(int pin)
{
  digitalWrite(pin, HIGH);
  pinMode(pin, OUTPUT);
}

void DHT11Start(int pin)
{
  // Host the start of signal
  digitalWrite(pin, LOW);
  delay(18);

  // Pulled up to wait for
  pinMode(pin, INPUT);
  while (!digitalRead(pin)) ;

  // Response signal
  while (digitalRead(pin)) ;

  // Pulled ready to output
  while (!digitalRead(pin)) ;
}

byte DHT11ReadByte(int pin)
{
  byte data = 0;

  for (int i = 0; i < 8; i++) {
    while (digitalRead(pin)) ;

    while (!digitalRead(pin)) ;
    unsigned long start = micros();

    while (digitalRead(pin)) ;
    unsigned long finish = micros();

    if ((unsigned long)(finish - start) > 50) data |= 1 << (7 - i);
  }

  return data;
}

void DHT11Finish(int pin)
{
  // Releases the bus
  while (!digitalRead(pin)) ;
  digitalWrite(pin, HIGH);
  pinMode(pin, OUTPUT);
}

bool DHT11Check(const byte* data, int dataSize)
{
  if (dataSize != 5) return false;

  byte sum = 0;
  for (int i = 0; i < dataSize - 1; i++) {
    sum += data[i];
  }

  return data[dataSize - 1] == sum;
}

////////////////////////////////////////////////////////////////////////////////////////

void GpsBegin(HardwareSerial* serial)
{
  GpsSerial = serial;
  GpsSerial->begin(9600);
  GpsDataLength = 0;
}
```