Wio LTEで遊んでみる(その6:簡易地震計を作ってみる)


Grove IoT スターターキット for SORACOMで遊んでみるの続き。

今までのを組み合わせて簡易的地震計を作ってみる。

〇構成

3軸デジタル加速度センサー
ボタン
温湿度
GPS

とりあえずこの辺のセンサーを使う予定。
これらのセンサーに加えてLTEでまずはSORACOMのHarvestへ送信。
※最終的にはBEAMで別のどっかに転送もしたい。

イメージ的には
・データ送信契機として以下を想定
 加速度センサーで揺れを検知した場合:本来の目的
 ボタン押下された場合:検証とか
 定期的なタイミング:ヘルスチェック用
・データ送信する際に送るデータ
 揺れに関するデータ:何を送ればいいのかは現状不明
 温湿度:ヘルスチェックの際に定期的に何かみたいといったらこの辺かと
 位置情報:とりあえず

〇試行錯誤

とりあえず深く考えずに今までのスケッチを組み合わせてみる。
が、なんかうまくいかない。
具体的にはGPSの値がうまく表示できない。
前回のコードを読み込ませてみてもうまく取得できない。
なぜだ。。こういう時は一回離れてみる。

とりあえず現状で作りたいシステム的にはGPSはマストではないのでこれに関する部分をすっ飛ばしていろいろと修正。

最初の時にSocketに関する処理のところだけで
SORACOMへの送信処理ができると思っていたのだが、
やはりsetupの中での準備が必要だった。

Wio_LTE_for_Arduino リファレンスマニュアル

  • Wio.PowerSupplyLTE
    Wio LTE上のLTEモジュールの電源供給をオン/オフします。 LTEモジュールは電源供給オンの後に、起動操作(TurnOnOrReset)しないと利用できません。 本関数を実行した後の、LTEモジュールの操作は0.5秒以上待ってください。(LTEモジュールの動作が安定するまで待つ。)

  • Wio.TurnOnOrReset
    Wio LTE上のLTEモジュールを起動操作します。 LTEモジュールが電源オンしていないときは電源オン、電源オンしているときはリセットします。 本関数を実行する前に、LTEモジュールへ電源供給(PowerSupplyLTE)してください。

ただ、いろいろと調べていると以下にあるようにLTEモジュールを常時ONにするよりは必要時にのみONにするほうが電力的にやさしいのかもしれない
Wio LTEを省電力に使おう
※が、この辺は課題ということで。

  • Activate(const char* accessPointName, const char* userName, const char* password)

指定したAPN、ユーザー名、パスワードを使って、データ通信を有効にします。
SORACOMへ送信する際には以下
Wio.Activate("soracom.io", "sora", "sora")

で、とりあえずこんな感じ。

// YuLetter

#include <WioLTEforArduino.h>
#include <ADXL345.h>          // https://github.com/Seeed-Studio/Accelerometer_ADXL345
#include <TinyGPS++.h>
#include <stdio.h>


#define SENSOR_TemperatureAndHumidity_PIN    (WIOLTE_D38)
#define BUTTON_PIN  (WIOLTE_D20)

#define INTERVAL_LOOP (1000)
#define INTERVAL_Accelerometer  (18000)

#define INTERVAL_SEND   (1000)
#define RECEIVE_TIMEOUT (10000)

#define LARGE_LOOP_LIMIT  (500)  //ここの数字が大きいとHelthCheckまでの間隔が長くなる

#define G_AVE_LIMIT  (4) //ここの値でどれくらいの揺れ?を検知するかをチューニングする
#define G_AVE_LOOP_LIMIT  (20)  //何回分の加速度を集計して平均値を算出するか?


//状態に対する色設定
#define COLOR_SETUP 0, 10, 0
#define COLOR_NONE 0, 0, 0
#define COLOR_GPS 255, 51, 255
#define COLOR_TempAndHumi 10, 10, 10
#define COLOR_BUTTON 10, 10, 0
#define COLOR_Accelerometer 10, 0, 0
#define COLOR_Send 0, 0, 10
#define COLOR_ERROR 255, 0, 0

WioLTE Wio;
TinyGPSPlus gps;
ADXL345 Accel;

int large_loop_count;
int before_g_ave;
volatile int mode = 0;

void setup()
{
  delay(500);

  SerialUSB.println("### I/O Initialize.");
  //GPSはいったん棚上げ
//  GpsBegin(&Serial);

  delay(500);
  Wio.Init();
  Wio.LedSetRGB(COLOR_SETUP);

  SerialUSB.println("### Power supply ON.");
  Wio.PowerSupplyGrove(true);
  delay(500);
  Accel.powerOn();
  delay(500);
  pinMode(BUTTON_PIN, INPUT);
  delay(500);
  attachInterrupt(BUTTON_PIN, change_state, RISING);

  TemperatureAndHumidityBegin(SENSOR_TemperatureAndHumidity_PIN);
  delay(500);

  Wio.PowerSupplyLTE(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("### SORACOM Activate ERROR ###");
    return;
  }

  large_loop_count = 0;
  before_g_ave = 0;
  SerialUSB.println("### Setup completed.");

  SerialUSB.print("G_AVE_LOOP_LIMIT : ");
  SerialUSB.println(G_AVE_LOOP_LIMIT);
  SerialUSB.print("G_AVE_LIMIT : ");
  SerialUSB.println(G_AVE_LIMIT);

  Wio.LedSetRGB(COLOR_NONE);
}

void loop()
{
  unsigned long start = micros();

  CheckShake();
  delay(500);

  // TODO ここの判定処理を回数ではなく時間にする
  large_loop_count ++;
// 温度湿度についても一定時間ごとにチェックして送信
  if(large_loop_count >= LARGE_LOOP_LIMIT)
  {
    float temp;
    float humi;
    get_TemperatureAndHumidity(&temp, &humi);

    Wio.LedSetRGB(COLOR_Send);
    SerialUSB.println("### Send !! ");
    SendData(temp , humi);
    delay(500);
    mode = 0;

    //何もしないと平常値に戻るタイミングでもう一回送信してしまうので、ここで値を初期化しておく。
    before_g_ave = 0;
    large_loop_count = 0;
  }

  Wio.LedSetRGB(COLOR_NONE);
  delay(INTERVAL_LOOP);
}

////////////////////////////////////////////////////////////////////////////////////////
//
//データ送信
void SendData(float temp , float humi)
{
  char data[200];
  sprintf(data,"{\"temp\":%.1f,\"humi\":%.1f,\"g_ave\":%d,\"mode\":%d}", temp, humi, before_g_ave, mode);

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

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

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

err:
  delay(INTERVAL_SEND);

}

////////////////////////////////////////////////////////////////////////////////////////
//
//揺れチェック
void CheckShake()
{
  SerialUSB.println("### Accelerometer : CheckShake");
  Wio.LedSetRGB(COLOR_Accelerometer);

  int g_sum = 0;
  for(int cnt=0;cnt<G_AVE_LOOP_LIMIT;cnt++)
  {
    int x;
    int y;
    int z;
    Accel.readXYZ(&x, &y, &z);
    int res = sqrt(x * x + y * y + z * z); 
    g_sum = g_sum + res;
  }
  double currnet_g_ave = g_sum / G_AVE_LOOP_LIMIT;
  SerialUSB.print("before_g_ave : ");
  SerialUSB.println(before_g_ave);
  SerialUSB.print("currnet_g_ave : ");
  SerialUSB.println(currnet_g_ave);

  if(before_g_ave!=0 && abs(before_g_ave-currnet_g_ave) >= G_AVE_LIMIT)
  {
    //揺れてる
    Wio.LedSetRGB(COLOR_ERROR);
    SerialUSB.println(">>> Shake!! <<<");
    SetShakeState();
    mode = 2;
  }

  //差し替える
  before_g_ave = currnet_g_ave;
  Wio.LedSetRGB(COLOR_NONE);
}

////////////////////////////////////////////////////////////////////////////////////////
//
void SetShakeState()
{
  large_loop_count = LARGE_LOOP_LIMIT;
}
////////////////////////////////////////////////////////////////////////////////////////
//
//ボタンによる割込み処理
volatile bool StateChanged = false;
volatile bool State = false;

void change_state()
{
  SerialUSB.println(">>> Push Button <<<");
  Wio.LedSetRGB(COLOR_BUTTON);
  State = !State;
  StateChanged = true;

  mode = 1;
  SetShakeState();
}


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

// 加速度センサー
int Accelerometer()
{

  int x;
  int y;
  int z;
  Accel.readXYZ(&x, &y, &z);
  SerialUSB.print(x);
  SerialUSB.print(' ');
  SerialUSB.print(y);
  SerialUSB.print(' ');
  SerialUSB.println(z);

  int res = sqrt(x * x + y * y + z * z); 
  SerialUSB.println(res);

  return res;
}

////////////////////////////////////////////////////////////////////////////////////////
//
// 温湿度
void get_TemperatureAndHumidity(float* temp, float* humi)
{
  SerialUSB.println("### Temperature And Humidity");
  Wio.LedSetRGB(COLOR_TempAndHumi);

  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");

err:
  delay(1000);
}

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;
}

////////////////////////////////////////////////////////////////////////////////////////
//
// GPS
#define GPS_OVERFLOW_STRING "OVERFLOW"

HardwareSerial* GpsSerial;
char GpsData[100];
char GpsDataLength;

void GpsBegin(HardwareSerial* serial)
{
  GpsSerial = serial;
  GpsSerial->begin(9600);
  GpsDataLength = 0;
}
void displayInfo()
{
    SerialUSB.print(F("Location: ")); 
    if (gps.location.isValid()) {
        SerialUSB.println(gps.location.lat(), 6);
        SerialUSB.println(gps.location.lng(), 6);
    } else {
        SerialUSB.println(F("INVALID"));
    }
}

const char* GpsRead()
{
  SerialUSB.println("### GpsRead Start");
  Wio.LedSetRGB(COLOR_GPS);

  while (GpsSerial->available()) {
    char data = GpsSerial->read();

    if (gps.encode(data)) {
      displayInfo();
    }

    if (data == '\r') continue;
    if (data == '\n') {
      GpsData[GpsDataLength] = '\0';
      GpsDataLength = 0;
      SerialUSB.println("### GpsRead End Data Exist");
      return GpsData;
    }

    if (GpsDataLength > sizeof (GpsData) - 1) { // Overflow
      GpsDataLength = 0;
      SerialUSB.println("### GpsRead End Data Overflow");
      return GPS_OVERFLOW_STRING;
    }
    GpsData[GpsDataLength++] = data;
  }

  SerialUSB.println("### GpsRead End NULL");
  return NULL;
}

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

Harvestへの送信も問題なし。
揺れたかどうかの検知も一応はできているし、ヘルスチェックやボタン契機でも一応OK

課題

  • 肝心の「揺れ検知」の部分のロジックがまったくしっくりきていない。 たぶん取得できた値を単純にこんな感じで値を作っているのがダメなんだろうな。

Accel.readXYZ(&x, &y, &z);
int res = sqrt(x * x + y * y + z * z);

この辺は要改善。

  • GPS関連が棚上げ

  • ヘルスチェック関連をもう少しどうにかしたい
    今は適当にループの回数上限がきたら、送信しているが時間なりでどうにか

  • データシートの見方がよくわからない

  • 全体的にむき出し
    ある程度のものができあがったらケース的なものも検討しようかと思う。