M5STICK-CとSIGFOXを繋いでみた

最近、沖縄がSIGFOXのエリアになったらしいので、SIGFOXを使ってみようと!
今、東京に来ているので、マルツ 秋葉原本店で、Sigfoxブレークアウトボード BRKWS01 RC3【BRKWS01】を購入(税込み5,162円)して、M5STICK-CとGPS受信機キット 1PPS出力付き 「みちびき」3機受信対応を繋いで、SIGFOX経由で位置情報をサーバに送信してみました。

出張中なので、手持ちの部材が少なく、配線の色合わせていないのはごめんなさい。

ハードウェアの詳細

BRKWS01 は、1年間の回線利用料も含まれています。
回線登録の仕方は、こちらを参照してください。
ブレイクアウト基板には、ピンヘッダ4本を半田付けしました。
3.3V,GND,TX,RXの4本だけを使います。電源が3.3Vなので、M5STICK-Cの汎用ポートと接続しています。TXをM5STICK-Cの36、RXをM5STICK-Cの26に繋いでいます。
GROVE端子の電源は5Vなので、こっちにつなぐと壊れるかも!

GPSに関しては、5Vでも駆動するので、GROVE端子と接続しています。

SIGFOX ConsoleのCALLBACK設定

SIGFOXのゲートウェイから、自前のサーバにデータを送信するために、CALLBACKという設定を行います。

SIGFOXは最大12バイトしか送信できないので、位置情報(緯度、経度)は、バイナリで送信するしかないので、Custom payload configを設定する必要があります。HEXの文字列のままでもいいと言う人は、設定しなくてもOKです。
経度(4バイト)緯度(4バイト)で送るので、
lng::float:32 lat::float:32
を設定しています。
Content typeは、JSONで送りたいので、application/jsonにして、BODY部で、フォーマットを規定します。
{“device” : “{device}”,”data” : “{data}”,”time” : {time},”lng”:{customData#lng},”lat”:{customData#lat}}
ここで、customData#っていうのが、Custom payload configで設定した変数になります。

※customDataが付かない、lngやlatだけの変数も用意されていますが、これは、SIGFOXで取得した位置情報を入れることが出来ますが、今月から利用できなくなったようです。実際取得すると、整数部しか入ってこないので,使い物になりません(笑)

Arduinoのプログラム

// Sigfoxブレークアウトボード BRKWS01 RC3とGPSをM5STICK-Cに繋いで、位置情報を送信する
// Programed by Kazuyuki Eguchi

#include <M5StickC.h>
#include <TinyGPS++.h>

HardwareSerial MODEM_SERIAL(1);
HardwareSerial GPS_SERIAL(2);

TinyGPSPlus gps;

char replybuffer[255];

int count = 0;

void setup() {
  M5.begin();
  M5.Axp.ScreenBreath(8); // バックライトの明るさ(7-15)
  M5.Lcd.setRotation(1);
  M5.Lcd.fillScreen(TFT_BLACK);
  M5.Lcd.setTextSize(2);
  M5.Lcd.setTextColor(TFT_YELLOW);
  M5.Lcd.setCursor(0, 0);

  MODEM_SERIAL.begin(9600, SERIAL_8N1, 36, 26);
  GPS_SERIAL.begin(9600,SERIAL_8N1, 33, 32);

  while (!Serial) {
    ; // wait for serial port to connect. Needed for native USB port only
  }

  readline(1000);
  readline(1000);
  readline(1000);

  Serial.println("Connect to the Sigfox Breakout board...");
  Serial.print("Device ID : ");

  MODEM_SERIAL.print("AT$I=10\r");

  if(readline(1000)){
    Serial.print(replybuffer);
  }
}

void loop() {
   char hex[32];
   char data[8];
   
   if (GPS_SERIAL.available()) {
    int inByte = GPS_SERIAL.read();
    
    if(gps.encode(inByte)){
      if (gps.location.isValid())
      {
        float lng = gps.location.lng();
        float lat = gps.location.lat();
        count++;

        if(count == 1)
        {
          int lng_i = *( ( int* )&lng );
          int lat_i = *( ( int* )&lat );

          data[0] = (lng_i >> 24) & 0xff;
          data[1] = (lng_i >> 16) & 0xff;
          data[2] = (lng_i >> 8) & 0xff;
          data[3] = lng_i & 0xff;
        
          data[4] = (lat_i >> 24) & 0xff;
          data[5] = (lat_i >> 16) & 0xff;
          data[6] = (lat_i >> 8) & 0xff;
          data[7] = lat_i & 0xff;

          sprintf(hex,"AT$SF=%02x%02x%02x%02x%02x%02x%02x%02x\r",data[0],data[1],data[2],data[3],data[4],data[5],data[6],data[7]);
          Serial.println(hex);
          MODEM_SERIAL.print(hex);
          
          if(readline(30000))
          {
            Serial.print(replybuffer);
          }else
          {
            Serial.println("readline Timeout");
          }
        }

        if(count > 5000)
        {
          count = 0;
        }
      }
      
      displayInfo();
    }
  }
}

uint8_t readline(uint16_t timeout) {
  uint16_t replyidx = 0;

  while (timeout--) {
    if (replyidx >= 254) {
      break;
    }

    while(MODEM_SERIAL.available()) {
      char c =  MODEM_SERIAL.read();
      if (c == '\r') continue;
      if (c == 0xA) {
        if (replyidx == 0)
          continue;
      }
      replybuffer[replyidx] = c;
      replyidx++;
    }

    if (timeout == 0) {
      break;
    }
    delay(1);
  }
  replybuffer[replyidx] = 0;
  return replyidx;
}

void displayInfo()
{
  M5.Lcd.fillScreen(TFT_BLACK);
  M5.Lcd.setCursor(0, 0);
  
  M5.Lcd.print(F("Location: "));
   
  if (gps.location.isValid())
  {
    M5.Lcd.print(gps.location.lat(), 6);
    M5.Lcd.print(F(","));
    M5.Lcd.print(gps.location.lng(), 6);
  }
  else
  {
    M5.Lcd.print(F("INVALID"));
  }

  M5.Lcd.print(F("  Date/Time: "));
  
  if (gps.date.isValid())
  {
    M5.Lcd.print(gps.date.month());
    M5.Lcd.print(F("/"));
    M5.Lcd.print(gps.date.day());
    M5.Lcd.print(F("/"));
    M5.Lcd.print(gps.date.year());
  }
  else
  {
    M5.Lcd.print(F("INVALID"));
  }

  M5.Lcd.print(F(" "));
  
  if (gps.time.isValid())
  {
    if (gps.time.hour() < 10) {
      M5.Lcd.print(F("0"));
    }
    
    M5.Lcd.print(gps.time.hour());
    M5.Lcd.print(F(":"));
    
    if (gps.time.minute() < 10) {
      M5.Lcd.print(F("0"));
    }
    
    M5.Lcd.print(gps.time.minute());
    M5.Lcd.print(F(":"));
    
    if (gps.time.second() < 10) {
      M5.Lcd.print(F("0"));
    }
    
    M5.Lcd.print(gps.time.second());
    M5.Lcd.print(F("."));
    
    if (gps.time.centisecond() < 10) {
      M5.Lcd.print(F("0"));
    }
    
    M5.Lcd.print(gps.time.centisecond());
  }
  else
  {
    M5.Lcd.print(F("INVALID"));
  }
}

サーバでデータを受信してみたら

受信側のPHPのプログラムはこんな感じ

<?php
$body = file_get_contents('php://input');
error_log(date("Y/m/d H:i:s") . "," . $body . print_r(apache_request_headers(),true) . "\n",3,"./debug.log");

echo "O";
?>

注意:位置情報は、改ざんしています。

2019/06/22 13:40:34,{“device” : “7390CA”,”data” : “430bxxxx420exxxx”,”time” : 1561178432,”lng”:139.80000,”lat”:35.700000}
Array
(
[Accept-Charset] => UTF-8;q=0.9,*;q=0.7
[User-Agent] => SIGFOX
[Content-Type] => application/json
[Accept-Language] => fr
[Content-Length] => 101
[Connection] => close
[X-File-Type] => normal
[X-Ua-Device] => pc
[X-Failure-Cache-Time] => 60
[X-Accel-Expires] => 60
[X-Wp-Access] => 0
[X-Server-Address] => 183.90.243.23
[X-Real-Ip] => 185.110.97.11
[X-Forwarded-Ssl] => on
[X-Forwarded-Port] => 443
[X-Forwarded-Host] => eguchi.jp
[X-Forwarded-Proto] => https
[X-Forwarded-For] => 185.110.97.11
[Host] => eguchi.jp
)

以上、ご参考までに