arrow-righthamburgerlogo-marksocial-facebooksocial-githubsocial-twitter
2017.06.13

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

ポキオ

電子工作レシピ
 
   

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

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

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

  1. 今回作るもの
    • 筆者の環境
    • 用意するもの
  2. 主なパーツ
    • ESPr® Developer
    • マイコン内蔵RGB LED
    • Bトレインショーティー
  3. ガジェットを作ってみる
    • 京急の2100形車両の組み立て
    • マイコン内蔵RGB LEDを埋め込む
    • 配線
    • 遅延情報の取得ロジック
    • ESPr® Developerのコーディング
  4. まとめ

今回作るもの

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のコーディング

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
#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上の情報を簡単に可視化できたことは良かったです。

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