スノーシューとGPSロガーで描くモエレの雪上絵ワークショップ、当日。

本日は「スノーシューとGPSロガーで描くモエレの雪上絵」ワークショップ当日でした。

1月早々の大寒波のおかげで札幌でも例年にないくらい冷え込む日が続いていましたが、今日はワークショップ日和の晴れ。
三連休の開始ということもあり、公園内ではクロスカントリーの練習をする方や、モエレ山でそり遊びでにぎわっています。

ワークショップの午前の部はガラスのピラミッドスペース1でGPSロガーづくり。ブレッドボードにArduinoやみちびき対応のGPS、SDカードモジュールなどを取り付け、電源オンですぐロギングが開始されるプログラムを書き込んだロガーを作成していきます。はんだ付けが初めての方もいましたが、みなさん無事動作するロガーが完成。

その後は、昼食をとりながら実際にモエレ沼公園の地図とにらめっこをして描く模様を用紙にかいてもらいます。

描き終わったら、スノーシューをはいていざ出発。大体1時間前後の工程で、モエレ沼公園に地上絵を描いていきます。

我々ディレクター、スタッフも一緒に同行したりしていますが、スノーシューの数が足りないメンバーは冬靴で頑張ってついていきます。

 


スノーシューを使わない場合

が、雪が深すぎて50mくらい進むのでも一苦労、普段の運動不足がたたり、太ももに疲れが蓄積されていきます。


スノーシューの場合

明らかに雪に残る足跡が違いますね。

無事みなさん歩き終わり、結果を確認していきます。

参加者のみなさんには、雪原の上だと雪がないモエレ沼とまた違って目印になるものが雪で隠れてたりなかなか難度が高いことを感じていただけたかと思います。

実際に、作成したロガーでうまく記録できた方や、振動や雪原で転ぶなどで途中までの軌跡が残ってしまっているなど、我々も成層圏気球のspace-moereプロジェクトで試行錯誤してデータを取得することをちょっとでも体験していただけたかと思います。

電子工作体験と、そして屋外活動、体も頭もフルに使った1日、参加者のみなさまお疲れさまでした!

 

少しだけ技術的な話について…

今回、ワークショップで利用したGPSの部品(GPSモジュールなんて言ってたりします)などは、札幌市内ですと梅沢無線さん(狸小路八丁目)で購入することができます。部品の中にはないものもありますのでそういった場合は、電子工作する人はほぼ誰もが知っている「秋月電子」というオンラインショップでも購入することができます。

GPSはいろいろな種類がありますが、space-moereプロジェクトでは、高高度の高さ(上空35kmなど)でも高さを取得できるものを採用していたりします。部品を購入する際に、その部品のデータシート、技術資料などを読み解き、目的に応じた位置情報の精度が取得できるかなどを確認して購入したりしています。

モエレの雪上絵 GPSロガー最終版

1月9日(明後日!)はモエレの雪上絵のワークショップなので、やり残していたコードの追加をしていきます。
SDカードに保存される GPS.LOG というcsvファイルですが、起動するたびに後ろにデータが書き足されていく状態のままになっています。
これだと、使い始める前にカードを初期化しなければならず面倒なので、今回は、起動するたびに新たなファイル名でログを残していく仕様にしたいと思います。

偉そうに書いていますが、実はArduinoはあまり馴染みがないのでちょろっとググったところ、
SD.exists( String ) というのが存在するので、一番安直な方法で書いてみました。
存在しない新しいファイル名を取得する関数。

String getNewFileName(){
  
  String s;
  int fileNum = 0;

  while(1){
    s = "GPS_";
    if (fileNum < 10) {
      s += "00";
    } else if(fileNum < 100) {
      s += "0";
    }
    
    s += fileNum;
    s += ".log";
    
    if(!SD.exists(s)){
      break;
    }
    
    fileNum++;
  }

  return s;
}

あとは、起動時、SDカードを初期化した後で呼んでやって変数に代入しておいて、書き込み時に使うだけです。

fileNameToWrite = getNewFileName();

全体はこんな感じ。

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

#define SERIAL_BAUD 9600

const int GPS_RX = 8;
const int GPS_TX = 9;
const int LED_PIN = 6;  //for LED

const int CHIP_SELECT = 4;  //for SD

TinyGPSPlus gps;
SoftwareSerial gpsSerial(GPS_RX, GPS_TX);
String fileNameToWrite;

void setup() {

  Serial.begin(SERIAL_BAUD);
  while (!Serial) {} // wait
  Serial.println("serial initialized\n");

  gpsSerial.begin(9600);

  setupSD();
  fileNameToWrite = getNewFileName();
  Serial.println( fileNameToWrite );

  pinMode(LED_PIN, OUTPUT);
  
}

void loop() {

  while (gpsSerial.available() > 0) {
    
    char c = gpsSerial.read();
    //Serial.print(c);
    gps.encode(c);

    if (gps.time.isUpdated()) {

      writeData();

      Serial.print("year : "); Serial.println(gps.date.year());
      Serial.print("mon : "); Serial.println(gps.date.month());
      Serial.print("day : "); Serial.println(gps.date.day());

      Serial.print("hour : "); Serial.println(gps.time.hour());
      Serial.print("min : "); Serial.println(gps.time.minute());
      Serial.print("sec : "); Serial.println(gps.time.second());

      Serial.print("lat : "); Serial.println(gps.location.lat(), 6);
      Serial.print("lng : "); Serial.println(gps.location.lng(), 6);
      Serial.print("alt : "); Serial.println(gps.altitude.meters());

    }
  }
}


String getNewFileName(){
  
  String s;
  int fileNum = 0;

  while(1){
    s = "GPS_";
    if (fileNum < 10) {
      s += "00";
    } else if(fileNum < 100) {
      s += "0";
    }
    
    s += fileNum;
    s += ".log";
    
    if(!SD.exists(s)){
      break;
    }
    
    fileNum++;
  }

  return s;
}


void setupSD() {
  
  Serial.print("Init SD.");
  
  if (!SD.begin(CHIP_SELECT)) {
    Serial.println("Card failed, or not present");
    while (1);
  }
  Serial.println("SD initialized.");
}


void writeData() {

  File dataFile = SD.open(fileNameToWrite, FILE_WRITE);

  if (dataFile) {

    digitalWrite(LED_PIN, HIGH);

    String str;
    str = String(gps.date.year());
    str += "/";
    str += String(gps.date.month());
    str += "/";
    str += String(gps.date.day());
    str += ",";
    str += String(gps.time.hour());
    str += ":";
    str += String(gps.time.minute());
    str += ":";
    str += String(gps.time.second());
    str += ",";
    str += String(gps.location.lat(), 6);
    str += ",";
    str += String(gps.location.lng(), 6);
    str += ",";
    str += String(gps.altitude.meters());

    dataFile.println(str);
    dataFile.flush();
    digitalWrite(LED_PIN, LOW);

  } else {
    Serial.println("error opening log");
    Serial.println(fileNameToWrite);
    
    
    digitalWrite(LED_PIN, LOW);
  }

  dataFile.close();

}

GPS_000.LOG
GPS_001.LOG
GPS_002.LOG

といった感じで起動するたびに新しいファイルが保存されていきます。

Gメールの添付画像をGASでGoogle Drive に保存

福井の堆積場にはハイクカムを設置して撮影を始めた雪堆積場のタイムラプスですが、忘備録的にシステムの紹介。
ハイクカムにはSIMを挿すスロットがあり、Hyke Works 経由でメール添付で最新画像が送られてきます。
今回はgmailのアドレスを新しく取得して、ハイクカム側でそのアドレスを指定しています。
APNの設定や、画質の設定、時間間隔の設定などもあります。今回は5分間隔でgmailのアドレスに送られてくるというわけです。
これをどうにかしてローカルにごっそり保存しておいて展示の時に使いたいというのが今回のお題です。

いくつか方法はありそうですが、今回はサーバで動くGoogle Apps Script(GAS) を使ってみました。
Google Drive上に専用のフォルダを準備して、HykeCam からのメールに添付された画像をそのフォルダに保存。
もちろんGASからではローカルには触れることができないので、
展示するときは、Google Drive のデスクトップアプリで同期してしまおうという作戦です。

*FOLDER_ID はgoogleDrive上のフォルダに振られているランダムな(?)文字列です。アクセスすればURLの下にくっついてます。
*SEARCH_TERM は、検索条件ですが、HykeCamからの未読メッセージということにしました。
*フォルダにファイルを作って、最終的には既読にし、削除するとこまでやっています。(容量を気にして)


const FOLDER_ID = ‘xxxxxxxxxxxxxxxxxxxxxxx’;
const SEARCH_TERM = 'from:(HykeCam) is:unread';

function fetchFile(){
  
  const folder = DriveApp.getFolderById(FOLDER_ID);
  const threads = GmailApp.search(SEARCH_TERM, 0, 10);
  const messages = GmailApp.getMessagesForThreads(threads);

  for(const thread of messages){
    for(const message of thread){
      
      const attachments = message.getAttachments();
      for(const attachment of attachments){
        folder.createFile(attachment);
      }
      
      GmailApp.markMessageRead( message );
      GmailApp.moveMessageToTrash( message );
      
    }   
  }
}

というわけでコードは無事に動いていたら、実行するトリガーを1分間隔に設定して準備完了。
明日は月寒の堆積場にカメラ設置に行ってきます。
それが済めば、あとは雪が降って堆積場が稼働するのを待つだけです!