3
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

LohacoダッシュボタンをESP8266を使いフルスクラッチで製作してみた

Last updated at Posted at 2017-04-02

できたもの

  • ボタンを押すと設定済みのECサイトで商品を発注(Lohaco)
  • ESP-WROOM-02 + 発注サービスAPI(ECサイト操縦用)にて構成

きっかけ

Webサイトのスクレイピング技術およびESP-WROOM-02の基本動作を学ぶため,ボタンを押すと事前に設定したECサイト(Lohaco)のページにアクセス・購入を行うものを製作してみました.

無題のプレゼンテーション (1).png

環境

1.ハードウェア部分

ボタンを押すと指定のアドレスに購入したい商品の情報をjsonで送信するものとしました.ほぼこちらのページを参考に取り組みました.他にも同様のページが沢山あるので,困ることは少ないと思います.
1.jpg

つまったところ(1) ※解決済み

電源投入後にブートシークエンスを延々繰り返し,立ち上りが完了しない(Arduino IDE のシリアルモニタにだらだらとメッセージが流れ続ける).

理由:電流不足
電源をUSBシリアル変換アダプタからの供給から,USB経由での供給に変更することで解決.

つまったところ(2) ※一応解決したが..

ESP-WROOM-02 上のAPIをキックするコードについてははじめは以下の構成でした(ボタンの押し込み→割り込み→割り込み関数内でHTTPアクセス).しかし手元の環境ではどうにも正常なアクセスができず.Stackoverflow などを見る限りこの構成だとクラッシュするよう(?)

HttpClient.ino(HTTPアクセスがクラッシュし正常動作せず)
#include <Arduino.h>
#include <ESP8266WiFi.h>
#include <ESP8266HTTPClient.h>

const int BTN_REQ = 12;

void setup() {
  // ボタンがIO-12に接続されており,押し込み時に割り込み関数(btn_isr)実行
  pinMode(BTN_REQ, INPUT_PULLUP);
  attachInterrupt(digitalPinToInterrupt(BTN_REQ), btn_isr, FALLING);
  ...
}

void btn_isr(){
  detachInterrupt(BTN_REQ);
  // APIのキック部分
  HTTPClient tHttp;
  tHttp.begin("http://[server_address]:8080/lohaco");
  ...
  attachInterrupt(digitalPinToInterrupt(BTN_REQ), btn_isr, FALLING);
}

void loop(){}

なので「割り込み時はフラグのみ制御」「処理実態はloop内」とすることで一応の解決はみました.これでよかったのかどうか...

HttpClient.ino(正常動作)
...
const int BTN_REQ = 12;
volatile bool mOrderTrigger;

void setup() {
  // ボタンがIO-12に接続されており,押し込み時に割り込み関数(btn_isr)実行
  pinMode(BTN_REQ, INPUT_PULLUP);
  attachInterrupt(digitalPinToInterrupt(BTN_REQ), btn_isr, FALLING);
  ...
}

void btn_isr(){
  detachInterrupt(BTN_REQ);
  mOrderTrigger = true;  // フラグ変更
  attachInterrupt(digitalPinToInterrupt(BTN_REQ), btn_isr, FALLING);    
}

void loop(){
  if (mOrderTrigger){
    // APIのキック部分
    HTTPClient tHttp;
    tHttp.begin("http://[server_address]:8080/lohaco");
    ...
    mOrderTrigger = false;
  }
}

2.ソフトウェア部分

APIを通じてリクエストを受け取るとこれに応じて発注を行うサービスを,Python + Selenium + Bottle で構築しました.Lohacoの商品は

http://lohaco.jp/product/XXXXXXX

のように最後のIDで区別されたアドレスが割り当てられているようでした(ちなみにAmazoneだと https://www.amazon.co.jp/dp/XXXXXXXXXX/).そこでAPIのリクエストフォーマットとして「Lohacoの商品ID」「数量」を以下のようなjson形式で受け取るものとしました.

[
  {"id":XXXXXXX , "unit": 2},
  {"id":YYYYYYY , "unit": 1}
]

あとはPython + Selenium でごり押しです.ログインが必要になるのでclass引数で受け取ることにしました.定期便のある商品とない商品があり,その場合には制御が異なるので地道にトライ.同様の構成で AmazoneOrderer, RakutenOrderer, YodobashiOrderer などを作成しAPI側で切り替えれば,どのサイトにも発注できます.

LohacoOrderer.py

class LohacoOrderer():

  def __init__(self, aId, aPasswd):
    self.id = aId
    self.passwd = aPasswd

  def login(self):
    # ログイン処理
    ....
	
  def order(self, aOrder):
    try:
      tProductUrl = "https://lohaco.jp/product/" + str(aOrder["id"])
      self.browser.get(tProductUrl)
      time.sleep(8)

      # 通常発注を選択
      tNormalOrderTab = self.browser.find_elements_by_class_name("blcTabNormal.sys-blcTabNormal.unselected")
      if len(tNormalOrderTab) > 0:
        tNormalOrderTab[0].click()
        time.sleep(4)

      # 数量入力
      self.browser.find_element_by_id('main-pur-num').clear()
      self.browser.find_element_by_id('main-pur-num').send_keys(aOrder['unit'])
      time.sleep(4)

      # カートにいれる
      tAddCartBtn = next((x for x in self.browser.find_elements_by_class_name("toCart2-center") if x.is_displayed()), None)
      tAddCartBtn.click()
      time.sleep(4)

      # 決済
      ....

      return True

    except:
      # 売り切れなどの場合,上記のどこかで指定のボタンが押せずエラーになる
      return False

  def close(self):
    # 終了処理			
    ....
Server.py
...
LOHACO_ORDERER = LohacoOrderer("lohaco_id", "lohaco_passwd")
...

@route('/lohaco', method=['POST'])
def root():
  tOrders = request.json
  LOHACO_ORDERER.login()	
  tResults = []
  for order in tOrders:
    # 各商品ごとに,発注成功ならtrue, 売り切れなどで発注失敗の場合はfalseをかえす
    tResult = LOHACO_ORDERER.order(order)
    tResults.append({"id":order["id"], "result": tResult})
  tBody = json.dumps(tResults)
  return __buildResponse(200, tBody)
		
if __name__ == "__main__":
  run(host='0.0.0.0', port=8080, debug=True, reloader=False)

起動したサーバーに対してリクエストを投げればブラウザを操縦して発注してくれるという寸法です.物理ボタンの代わりにcurlでも発注する場合は以下.

% curl -v -H "Content-type: application/json" -X POST -d '[{"id":XXXXXXX, "unit":2}' http://server_host:8080/

改善

省電力化

ボタンの使われ方を考えるとほぼすべての時間は停止しているはず.よって,
ボタンを押すことでスリープを解除→HTTPアクセス→再度スリープ,であれば大幅な省電力化を達成できます(むしろそうあるべき).関連記事はこちら

パラメータ設定

購入個数や商品についてはハードコーディングしてしまいましたが,DIPスイッチで変更できたりESP-WROOM-02のAP機能(Webサーバーを立ち上げられる)で設定できるとよさそうです.関連記事はこちら

他の実装法

システムを実現するだけであれば

の組み合わせで,ほぼノンコーディング(?)ただしMESHと通信するためのスマホ・タブレットが必要であったり,myThings Developers を動作させるサーバーがIDCFクラウド限定,叩けるのはLohaco限定であるなどの条件は付きます.

また,物理ボタンを使わず発注サービスAPIを利用する別案といては,Google Spreadsheetに書いておくと週毎/月毎の締め切り日に発注する,というシステムも簡単に制作できそうです.

やってみて

"考えながらの製作" というより "仕様に合ったコードをひたすらググり成型してコピペ" というレベルでした.Web上で拾える情報の豊富さに驚きです.コピペで大抵のことができてしまうなら(そして多くの場合何をやるにしてもWeb上に情報がある),何かを製作するという行為は一体なんなのでしょう.

まとめ

ESP-WROOM-02 とスクレイピング技術を使い,ワンボタンでECサイト(Lohaco)の商品を発注できるシステムを構築しました.

3
5
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
3
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?