LoginSignup
12
11

More than 5 years have passed since last update.

ESP8266 (ESP-WROOM-02) でバスの到着を知る「バスどこメーター」を作る(後編)

Last updated at Posted at 2016-02-11

概要

ESP8266 (ESP-WROOM-02) でバスの到着を知ることができる装置を作る

はじめに

ESP8266 (ESP-WROOM-02) でバスの到着を知る「バスどこメーター」を作る(前篇)」の続きです。
実際の回路を作ります。

設計

  • 今回はステッピングモータ(ユニポーラ駆動)を使いました。
    • LED だと常に電流を流さないといけないので。
    • ステッピングモータは、電流OFFでの静止トルクがとても大きいので、スリープと相性が良い。
    • 角度が正確に出せる。
  • ステッピングモータについては「ESP8266 (ESP-WROOM-02) でステッピングモータをいい感じに回す」を参照してください。
    部品点数を少なくするために、今回はシフトレジスタを使わずに、4つのピンを使って直接制御することにしました。
  • 秋月ではステッピングモータは品薄らしい。ユニポーラ駆動はもう無かった。
  • しかし、バイポーラ駆動は難しくて制御できなかったorz。(ドライバ付きならできるかもしれない → 今後の課題)
  • スリープモードのために、IO16 は RST に繋げておきます。

  • 設定は SPIFFS に、モータの位置は EEPROM に保存します。

スケッチ

  • 通信してバス位置情報を取得してモーターを回転させた後、再び DeepSleep モードで眠ります。
  • json の生成と解析に、ArduinoJson ライブラリを使いました。
    使いやすいです。
#include <Esp.h>
#include <ESP8266WiFi.h>
#include <WiFiClient.h>
#include <ESP8266WebServer.h>
#include <FS.h>
#include <EEPROM.h>
#include <ArduinoJson.h>

// アプリ状態
struct APPSTATUS {
  int pos;
};

// モード切り替えピン
const int MODE_PIN = 0; // GPIO0

// Wi-Fi設定保存ファイル
const char* settings = "/wifi_settings.txt";

// サーバモードでのパスワード
const String pass = "thereisnospoon";

// モータ制御ピン
const int PIN[] = {14, 12, 13, 5};

// 2相励磁 (4096ステップで2回転)
const int S = 4;
const int steps[S] = {
  0b1100,
  0b0110,
  0b0011,
  0b1001
};



ESP8266WebServer server(80);

/**
 * アプリ設定
 */
 struct APPCONFIG {
   String ssid; // WiFi SSID
   String pass; // WiFi Password
   String HOST; // WEBAPI
   String PATH; // WEBAPI
   String DSMK; // 乗車バス停
   String ASMK; // 降車バス停
};

void getAppConfig(APPCONFIG& config) {
  File f = SPIFFS.open(settings, "r");
  config.ssid = f.readStringUntil('\n');
  config.pass = f.readStringUntil('\n');
  config.HOST = f.readStringUntil('\n');
  config.PATH = f.readStringUntil('\n');
  config.DSMK = f.readStringUntil('\n');
  config.ASMK = f.readStringUntil('\n');
  f.close();

  config.ssid.trim();
  config.pass.trim();
  config.HOST.trim();
  config.PATH.trim();
  config.DSMK.trim();
  config.ASMK.trim();

  Serial.println("SSID: " + config.ssid);
  Serial.println("PASS: " + config.pass);
  Serial.println("HOST: " + config.HOST);
  Serial.println("PATH: " + config.PATH);
  Serial.println("DSMK: " + config.DSMK);
  Serial.println("ASMK: " + config.ASMK);
}

void handleRootGet() {
  APPCONFIG config;
  getAppConfig(config);

  String html = "";
  html += "<h1>WiFi Settings</h1>";
  html += "<form method='post'>";
  html += " <table>";
  html += "  <tr><td>ssid</td><td><input type='text' name='ssid' placeholder='ssid' value='" + config.ssid + "'></td></tr>";
  html += "  <tr><td>pass</td><td><input type='text' name='pass' placeholder='pass' value='" + config.pass + "'></td></tr>";
  html += "  <tr><td>HOST</td><td><input type='text' name='HOST' placeholder='HOST' value='" + config.HOST + "'></td></tr>";
  html += "  <tr><td>PATH</td><td><input type='text' name='PATH' placeholder='PATH' value='" + config.PATH + "'></td></tr>";
  html += "  <tr><td>DSMK</td><td><input type='text' name='DSMK' placeholder='DSMK' value='" + config.DSMK + "'></td></tr>";
  html += "  <tr><td>ASMK</td><td><input type='text' name='ASMK' placeholder='ASMK' value='" + config.ASMK + "'></td></tr>";
  html += " </table>";
  html += " <input type='submit'>";
  html += "</form>";
  server.send(200, "text/html", html);
}

void handleRootPost() {
  String ssid = server.arg("ssid");
  String pass = server.arg("pass");
  String HOST = server.arg("HOST");
  String PATH = server.arg("PATH");
  String DSMK = server.arg("DSMK");
  String ASMK = server.arg("ASMK");

  File f = SPIFFS.open(settings, "w");
  f.println(ssid);
  f.println(pass);
  f.println(HOST);
  f.println(PATH);
  f.println(DSMK);
  f.println(ASMK);
  f.close();

  String html = "";
  html += "<h1>WiFi Settings</h1>";
  html += ssid + "<br>";
  html += pass + "<br>";
  html += HOST + "<br>";
  html += PATH + "<br>";
  html += DSMK + "<br>";
  html += ASMK + "<br>";
  server.send(200, "text/html", html);
}

// モータ制御
void out(int x) {
  for (int i = 0; i < 4; i++) {
    digitalWrite(PIN[i], (x >> i) & 1 ? HIGH : LOW);
  }
}

// リクエスト送信
String postData(String host, int port, String path, String json) {

  String data = "";
  data += "POST " + path + " HTTP/1.1\r\n";
  data += "Host: " + host + "\r\n";
  data += "Connection: close\r\n";
  data += "Content-Type: application/json;\r\n";
  data += "Content-Length: " + String(json.length()) + "\r\n";
  data += "\r\n";
  data += json;
  Serial.println(data);

  WiFiClient client;
  if (client.connect(host.c_str(), port)) {
    Serial.println("Connection success!!!");
    client.print(data.c_str());
    delay(10);
    String response = client.readString();
    int bodypos =  response.indexOf("\r\n\r\n") + 4;
    return response.substring(bodypos);
  } else {
    Serial.println("Connection failed");
    client.stop();
    return "";
  }
}

const int RANGE = 2048;

/**
 * 保存した値を取得して、新しい値を保存する
 */
int EEPROM_pos_update(int newPos) {
  APPSTATUS buf;
  EEPROM.begin(100);
  EEPROM.get<APPSTATUS>(0, buf);
  int nowPos = buf.pos;

  buf.pos = newPos;
  EEPROM.put<APPSTATUS>(0, buf);
  EEPROM.commit();

  return nowPos;
}

void drive(int P_now, int P_ref) {

  // モーター制御
  while (true) {
    int d = P_ref - P_now;
    if (d == 0) break;
    // P_ref に近づくように ±1 する
    P_now += d / abs(d);
    // P_now にするには、どの方向に回せばよいか
    int i = (P_now + RANGE) % S;
    out(steps[i]);
    delay(20);
  }

  // 電流OFF
  out(0);
}

/**
 * 初期化(クライアントモード)
 */
void setup_client() {

  APPCONFIG config;
  getAppConfig(config);

  WiFi.begin(config.ssid.c_str(), config.pass.c_str());
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }
  Serial.println("");
  Serial.println("WiFi connected");
  Serial.print("IP address: ");
  Serial.println(WiFi.localIP());

  // 情報取得
  StaticJsonBuffer<200> reqBuffer;
  JsonObject& reqRoot = reqBuffer.createObject();
  reqRoot["DSMK"] = config.DSMK;
  reqRoot["ASMK"] = config.ASMK;
  String json;
  reqRoot.printTo(json);
  String buff = postData(config.HOST, 80, config.PATH, json);

  // レスポンスのJSONをパース
  StaticJsonBuffer<1024> resBuffer;
  JsonObject& root = resBuffer.parseObject(buff);
  JsonArray& item = root["item"].asArray();
  int i;
  for (i = 0; i < 6; i++) {
    const char* route = item[i][0]["route"];
    Serial.print(" i : ");
    Serial.print(i);
    Serial.println(route);
    if (route != 0) break;
  }

  // サーバはバスの位置を配列[0]〜[6]で返す
  // 360度を16分割して、[3]の位置を鉛直方向にする
  int P_ref = (i - 3) * 2048 / 16;

  // EEPROMから現在の値を取得して、新しい値を保存する
  int P_now = EEPROM_pos_update(P_ref);

  // モーター制御
  drive(P_now, P_ref);

  // 待ち時間を調整する
  (i + 1) * 20;

  // 10秒間 眠りにつく
  ESP.deepSleep(10 * 1000 * 1000);
}

/**
 * 初期化(サーバモード)
 */
void setup_server() {
  byte mac[6];
  WiFi.macAddress(mac);
  String ssid = "";
  for (int i = 0; i < 6; i++) {
    ssid += String(mac[i], HEX);
  }
  Serial.println("SSID: " + ssid);
  Serial.println("PASS: " + pass);

  /* You can remove the password parameter if you want the AP to be open. */
  WiFi.softAP(ssid.c_str(), pass.c_str());

  server.on("/", HTTP_GET, handleRootGet);
  server.on("/", HTTP_POST, handleRootPost);
  server.begin();
  Serial.println("HTTP server started.");
}

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

  // 1秒以内にMODEを切り替える
  //  0 : Server
  //  1 : Client
  delay(1000);

  // ファイルシステム初期化
  SPIFFS.begin();

  // PIN
  for (int i = 0; i < 4; i++) {
    pinMode(PIN[i], OUTPUT) ;
  }

  pinMode(MODE_PIN, INPUT);
  if (digitalRead(MODE_PIN) == 0) {
    // サーバモード初期化
    setup_server();
  } else {
    // クライアントモード初期化
    setup_client();
  }
}

void loop() {
  server.handleClient();

  // 0補正する
  if (0 < Serial.available()) {
    delay(10);
    String buff = "";
    while (0 < Serial.available()) {
      char ch = Serial.read();
      if (ch == '\n') {

        // EEPROMから現在の値を取得して、新しい値を保存する
        int P_now = EEPROM_pos_update(0);

        int d = atoi(buff.c_str());
        int P_ref = P_now + d;

        Serial.print(" now:");
        Serial.print(P_now);

        // モーター制御
        drive(P_now, P_ref);

        Serial.println(d);
        break;
      } else if ('0' <= ch && ch <= '9' || ch == '-') {
        // ASCII文字コード
        buff += ch;
      }
    }
  }
}

Githubにアップロードしておきます。
https://github.com/exabugs/BusNavi

回路図

だいたい http://qiita.com/exabugs/items/8077885ce0e6f72597fe と同じ。
(upverter でちゃんと書きなおしたらUPします。)

設定

ネットワーク設定

パスワードやバス停IDを、後からブラウザで設定できるように「ESP8266 (ESP-WROOM-02) でWi-Fi使用時のSSID/パスワードをブラウザで設定できるように」で作成したコードを流用します。

  1. サーバモードで起動 (リセット押下後、1秒以内にモードボタン(IO0)を押下)
  2. WROOM02 が アクセスポイントとなるので、そのネットワークにPCで接続する。
  3. ブラウザで 192.168.4.1 に接続
  4. あなたのネットワークのSSID/Passwordと、前編で作成した APIGatewary(CloudFlont) の ホスト/パス、また、東急バスナビの乗車/降車バス停のIDを入力して保存する。
  5. リセットボタンを押下して、クライアントモードで起動。

設定画面

スクリーンショット_2016-02-11_18_59_57.png

ステッピングモータ設定

  • 最初に、モータの初期位置を 0 アジャストしないといけない。(位置がわからないので。)
  • サーバモードで起動している時に、シリアルから数字を入力すると、0 アジャストできるようにしている。
  • 時計で 12時 (鉛直上方向) を 0 とする。そこを矢印が刺すように、100 とか -50 とかシリアルから入力して、0 アジャストする。

動作確認

部品数は意外と少ない。WROOM02 と トランジスタアレイ(シンクドライバ)、モータ。
青い下敷きはモバイルブースターです。(ElasticSearchのセミナーでもらった)
IMG_2449.JPG

バスは、まだ遠くにいる。
IMG_2448.JPG

バスが来たらこんな感じ。今、ひとつ前のバス停。
IMG_2452.JPG

まとめ

  • スリープ 10秒 で情報取得してるが、やりすぎた。バスが遠いなら、2分くらいはスリープしてもいいだろう。
  • しかも、朝だけ、動いていればいい。いやむしろ、人感センサーと繋げるべきか。
  • それでも、やっと、IoT らしくなってきた。
  • 我が家の最寄りのバス停は3つある。物理スイッチで切り替えられるようにしたい。
12
11
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
12
11