arrow-right hamburger logo-mark social-facebook social-github social-twitter
2017.06.13

ESP8266を使って京急が遅延すると光るガジェットを作ってみた

ポキオ

電子工作レシピ
    このエントリーをはてなブックマークに追加  

こんにちは、京急が大好きなポキオです。

普段はAndroidエンジニアをやってますが、週末は趣味でArduinoやESP8266を触って心を落ち着かせています。先日、「京急が遅延すると光るガジェット」を作ってIoTLTで発表を行ってきました。

今回は、そこで発表したガジェットの作成段階や、技術的な仕組みを紹介します。(発表後に一部パーツ・コーディングを変更しています)

今回作るもの

ESP8266というWi-Fiモジュールを搭載した開発ボード「ESPr® Developer」を使って京急のホームページから運行情報を取得し、遅延していたらLEDを光らせる仕組みを作ります。

筆者の環境

用意するもの

主なパーツ

ESPr® Developer

画像 スイッチサイエンスさんで売られている、ESP8266というWi-Fiモジュールを搭載した開発ボードです。素のESP8266を直接触るのはハードルが高いですが、USB-シリアル変換やレギュレータ、リセットスイッチなど実装済みで、Arduino IDEでも開発できるため便利です。

ただし、筆者の開発環境ではArduino IDEからコードを流し込む際に、一手間必要でした。詳しくはこちら。

参考: ESPr Developer(ESP-WROOM-02開発ボード)で “warning: espcomm_sync failed” と表示される場合の対処

マイコン内蔵RGB LED

画像 秋葉原の秋月電子通商さんで1個40円で売られているものを使用します。通常のLEDは足が2本ですが、こちらは足が4本。これらを制御することで、様々な色でLEDを光らせることができます。ESP8266(Arduino)向けに便利なライブラリが公開されているので、今回はこれを使ってコーディングしていきます。

Adafruit NeoPixel Library

Bトレインショーティー

画像 Bトレインショーティーはバンダイさんから発売されている、自分で組み立てるタイプの鉄道模型です。特徴は、何と言っても可愛さ。実車のディテールを表現しつつ、車両の長さをギュッと縮めてコミカルなルックスになっています。今回は(もちろん京急の)2100形をチョイス。京急の中で好きな車両の一つです。

ガジェットを作ってみる

京急の2100形車両の組み立て

画像 なにはともあれ、京急の車両を組み立てるところから始めます。Bトレインショーティーの京急2100形は、塗装済みで接着剤不要で組み立てができます。

画像 久しぶりのプラモデルにテンションがアガります。

マイコン内蔵RGB LEDを埋め込む

出来上がった京急の車両にLEDを埋め込んでいきます。ここで便利なのがサンハヤトさんのハサミで切れるユニバーサル基板です。 画像

謳い文句の通り、ハサミで自由にカットができて、今回のような小さい車両にも基板を収めることができます。 画像

こんな感じで簡単に、そしてその場で車両ピッタリの基板ができました。 画像

配線

マイコン内蔵RGB LEDは先述の通り足が4本あり、電源(VDD)とグラウンド(GND)に加えて、制御信号の入出力(DIN・DO)があります。DIN・DOは図のように数珠つなぎで配線します。 画像

電源とグラウンドは共通で、それぞれESPr® DeveloperのVOUTとGNDに接続します。大元のDINはPIN4に接続します。

遅延情報の取得ロジック

WebAPI等は使用せずに、10分に一度、京急の運行情報ページにアクセスして情報を取得します。

ESPr® Developerから運行情報の文言をHTTP-GETで取得して、その文言に特定の文字列が含まれるかどうかで運行状態を推測します。具体的には・・・

このような感じです。そして、運転見合わせであれば赤い点滅、ダイヤが少し乱れているときは黄色い点滅をさせるといった感じで、運行状態に応じてマイコン内蔵RGB LEDの光り方を変えることで、運行状態をひと目で把握することができます。

ちなみに、他社線からの振替輸送受託が理由で京急が遅延しているときは、個人的にすこし残念な気持ちになるので、光り方を変えています(笑)

ESPr® Developerのコーディング

かなり無理矢理ですが、こんな感じでコーディングしました。

#include <ESP8266WiFi.h>
#include <WiFiClient.h>
#include <Adafruit_NeoPixel.h>

#define PIN 4 // DINを接続しているPIN
#define NUMLED 4 // マイコン内蔵RGB LEDの個数
#define SSID "(Wi-FiアクセスポイントのSSID)"
#define PASSWORD "(Wi-Fiアクセスポイントのパスワード)"
#define KEIKYU_PAGE "unkou.keikyu.co.jp" // 運行情報のページ
#define INTERVAL_SEC 10 * 60 // ポーリング間隔

Adafruit_NeoPixel pixels = Adafruit_NeoPixel(NUMLED, PIN, NEO_RGB + NEO_KHZ800);

void setup() {
  // Serialの初期化
  Serial.begin(115200);
  Serial.println("");

  // マイコン内蔵RGB LEDの初期化
  pixels.begin();
}

void loop() {
  // Wi-Fi接続開始
  connectWifi();

  // 京急の運行ページから運行情報取得
  String trainInfo = getTrainInfo();

  // Wi-Fi接続終了
  disconnectWifi();

  // 「受託」という文字が含まれていたら、他社からの振替輸送受託で遅延していると判断
  if (trainInfo.indexOf("受託") > 0) {
    Serial.println("振替輸送受託!");
    blinkLikePartyPeople(INTERVAL_SEC);
    return;
  }

  // 「見合わせ」という文字が含まれていたら、運転見合わせが発生していると判断
  if (trainInfo.indexOf("見合わせ") > 0) {
    Serial.println("運転見合わせ!");
    blinkRed(INTERVAL_SEC);
    return;
  }

  // 「乱れ」という文字が含まれていたら、ダイヤが大幅に乱れていると判断
  if (trainInfo.indexOf("乱れ") > 0) {
    Serial.println("大幅に乱れている!");
    blinkYellowAndRed(INTERVAL_SEC);
    return;
  }

  // 「遅れ」「運休」という文字が含まれていたら、ダイヤが少し乱れていると判断
  if (trainInfo.indexOf("遅れ") > 0 || trainInfo.indexOf("運休") > 0) {
    Serial.println("遅延!");
    blinkYellow(INTERVAL_SEC);
    return;
  }

  // 「平常」という文字が含まれていたら、平常運転をしていると判断
  if (trainInfo.indexOf("平常") > 0) {
    Serial.println("たぶん平常通り運転!");
    delay(INTERVAL_SEC * 1000);
    return;
  }

  // 運行情報取得エラーかもしれないので、10秒待ってもう一度取得する
  blinkWhite(10);
}

// Wi-Fi接続
void connectWifi() {
  WiFi.mode(WIFI_STA);
  WiFi.begin(SSID, PASSWORD);

  while (WiFi.status() != WL_CONNECTED) {
    Serial.print(".");
    blinkWhite(3);
  }

  Serial.println("Wi-Fi接続完了");
}

// Wi-Fi切断
void disconnectWifi() {
  WiFi.disconnect();
  Serial.println("Wi-Fi切断完了");
}

// 運行情報の文字列取得
String getTrainInfo() {
  WiFiClient client;

  if ( !client.connect(KEIKYU_PAGE, 80) ) {
    // 接続エラー
    return String("");
  }

  // HTTP-GET
  // レスポンスのLengthが長すぎるとエラーになることがあったのでRangeを指定している
  client.print(String("GET ") + "/" + " HTTP/1.1\r\n" +
               "Host: " + KEIKYU_PAGE + "\r\n" +
               "Range: bytes=8000-9000\r\n" +
               "Connection: close\r\n\r\n");
  client.println();

  delay(1000);

  String body = "";
  String trainInfo = "<!-- ======================== 運行情報 =================================== -->";

  while (client.available()) {
    body += client.readStringUntil('\r');
  }

  // レスポンスから運行情報部分だけを切り抜く
  body = body.substring(body.indexOf(trainInfo) + trainInfo.length());
  body = body.substring(0, body.indexOf(trainInfo));

  return body;
}

// 白い点滅
void blinkWhite(int sec) {
  int count = 0;

  while (count < sec) {
    for (int i = 0; i < 256; i += 5) {
      setColor( i, i, i);
    }

    for (int i = 255; i >= 0; i -= 5) {
      setColor( i, i, i);
    }

    count++;
  }
}

// 黄色い点滅
void blinkYellow(int sec) {
  int count = 0;

  while (count < sec) {
    for (int i = 0; i < 256; i += 5) {
      setColor( i, i, 0);
    }

    for (int i = 255; i >= 0; i -= 5) {
      setColor( i, i, 0);
    }

    count++;
  }
}

// 黄色と赤の点滅
void blinkYellowAndRed(int sec) {
  int count = 0;

  while (count < sec) {
    for (int i = 0; i < 256; i += 5) {
      setColor( i, 0, 0);
    }

    for (int i = 255; i >= 0; i -= 5) {
      setColor( i, 0, 0);
    }

    for (int i = 0; i < 256; i += 5) {
      setColor( i, i, 0);
    }

    for (int i = 255; i >= 0; i -= 5) {
      setColor( i, i, 0);
    }

    count++;
    count++;
  }
}

// 赤い点滅
void blinkRed(int sec) {
  int count = 0;

  while (count < sec) {
    for (int i = 0; i < 256; i += 5) {
      setColor( i, 0, 0);
    }

    for (int i = 255; i >= 0; i -= 5) {
      setColor( i, 0, 0);
    }

    count++;
  }
}

// パリピな輝き
void blinkLikePartyPeople(int sec) {
  int count = 0;

  while (count < sec) {
    for (int i = 0; i < 20; i++) {
      setRandomColor();
      delay(50);
    }

    count++;
  }
}

// LEDを指定した色で光らせる
void setColor(int r, int g, int b) {
  for (int i = 0 ; i < NUMLED; i++) {
    pixels.setPixelColor(i, pixels.Color(r, g, b));
    pixels.show();
  }
  delay(10);
}

// LEDをランダムな色で光らせる
void setRandomColor() {
  for (int i = 0 ; i < NUMLED; i++) {
    pixels.setPixelColor(i, pixels.Color(64 * random(1, 5) - 1 , 64 * random(1, 5) - 1 , 64 * random(1, 5) - 1 ));
    pixels.show();
  }
  delay(10);
}

実際に光らせてみたのがこちら。

画像 情報取得中に実行される白い点滅。

画像 運転見合わせ時に実行される赤い点滅。

画像 最後は他社線からの振替輸送受託時に実行されるパリピ点滅。画像では少しわかりづらいですね……。

まとめ

今回は、京急が遅延していると光るガジェットをESPr® Developerをつかって作成しました。情報の取得方法はかなり力技でしたが、WEB上の情報を簡単に可視化できたことは良かったです。

実際にこのガジェットは、職場のデスクで稼働していて、いつも京急の運行情報を知らせてくれます。今後は他社線バージョンの作成を検討しています。

    このエントリーをはてなブックマークに追加