cloudpackでマーケティング担当している後藤です。re:Inventツアーが終わって一安心したところで、PRブログもはじまったことだし、自分も別のネタを書こうかなと。ひとまず開発者視点で触ってみたかったSORACOMネタでゆるく書いてみたいと思います。

いきなりのピンチ

ゆるく攻めて、なんかできたところでブログでも、って思っていたところいきなりのピンチ…

dq3-command

今行われているSORACOMリリース記念リレーブログにcloudpackは参加していますが、cloudpackではなんと齋藤慎二とCTO鈴木の二人もエントリーしていて、広報担当としては 俺調整力すばらしい! と勝手に思っていたのものつかの間、本日10/26(月)担当のCTO鈴木が先週金曜になって(とってもとっても重要な業務のため)「変わってもらえませんか」と言ってきました!!

ピンチ!

この企画は落としてはならぬと、適当に作っていたアプリを仕上げて一発屋的にブログを書くしかない… 週末の戦いがスタート。

今回のネタ

そもそもSORACOMリリース直後から自分で使ってみたくて、Amazonで購入し、使わなくなったiPhoneをデータ通信端末にしてみたりしてますが、触っているうちに臨機応変に帯域を変更したい欲求に駆られてきました。

ところでSORACOMはAWSを卒業されたメンバーが数多く在籍いていますが、クラウドらしいサービスの在り方を目をつぶっても体現できるわけで、そのほとんどの機能はAPIやSDKによって外部へ公開されてます。

参考: Welcome | SORACOM Developers

というわけで、簡単に帯域変更できるウェブアプリを作ってみようと。

SORACOM Remote(仮称)

そんなわけでつくりました。

以上。

ではさみしいので少し解説。

画面解説

起動してブラウザでアクセスすると以下の画面になります。ここではSORACOM管理コンソールで使用するメールアドレスとパスワードを入力してください。

SORACOM-Remote

ログイン成功すると現在所有しているSIMの一覧が表示されます。

SORACOM-Remote

※「磯部」は「磯辺」の誤りです

機能解説

ここでは以下の機能が利用できます。

  • SIMの一覧表示(Nameタグ、IMSI番号、ステータス、速度タイプ)
  • 利用開始、休止(activate/deactivate)
  • 速度タイプ変更

もちろんSORACOMのコンソールでできますが、スマホの画面でも操作できるくらいにシンプルにして使い安くしてみました。もちろんAPIを利用してなんかしてみたかっただけとも!

あとはデータ通信端末に専用のブックマークを置いてできあがり。

実装について

今回思いつきで簡単に作りたかったのでPythonのFlaskというウェブフレームワークでささっと書いてみました。RubyでいうところのSinatraみたいなシンプルさですね。

ひさびさにコーディングしてみたし、使ったことないものばかりだったので、準備の方が断然時間がかかってしまって後悔ひとしお。純正のSDKがあるRubyでやればよかったか…

# -*- coding: utf-8 -*-
from flask import Flask, render_template, request, redirect, make_response, session, url_for
import httplib2
import json

api_url  = 'https://api.soracom.io/v1'
app = Flask(__name__)
app.config['SECRET_KEY'] = 'SORACOM Remote'

# functions
def _call_api(path, method, params):
    h = httplib2.Http(".cache")
    headers = {
        'X-Soracom-API-Key' : session['apiKey'],
        'X-Soracom-Token'   : session['token'],
        'Content-Type'      : 'application/json'
    }
    resp, content = h.request(api_url + path, method, json.dumps(params), headers=headers)
    error = ''
    if resp.status != 200:
        error = 'Response is bad: ' + str(resp.status) + ' ' + content
        exit

    return error, json.loads(content)

def _is_authorized():
    username = request.form['username']
    password = request.form['password']
    if username is None or password is None:
        return False
    h = httplib2.Http(".cache")
    data = json.dumps({
        "email"    : request.form.get('username'),
        "password" : request.form.get('password')
    })
    headers = {
        'Content-Type' : 'application/json',
        'Accept'       : 'application/json'
    }
    resp, content = h.request(api_url + '/auth',  'POST', body=data, headers=headers)
    if resp.status != 200:
        return False

    resp_json = json.loads(content)
    session['apiKey'] = resp_json['apiKey']
    session['token']  = resp_json['token']
    return True

# routing
@app.before_request
def before_request():
    if session.get('apiKey') is not None:
        return
    if request.path == '/login' or request.path.find('/static') == 0:
        return
    return redirect('/login')

@app.route('/', methods=['GET'])
def index():
    error, sims = _call_api('/subscribers', 'GET', {})
    return render_template('index.html', sims=sims, message='', error=error)

@app.route('/login', methods=['GET', 'POST'])
def login():
    if request.method == 'POST' and _is_authorized():
        return redirect(url_for('index'))
    return render_template('login.html')

@app.route('/logout', methods=['GET'])
def logout():
    session.pop('apiKey', None)
    session.pop('token', None)
    return redirect(url_for('login'))

@app.route('/sim//modify', methods=['GET'])
def modify(imsi):
    new_type = request.args.get('new_type')
    old_type = request.args.get('old_type')
    error, sim = _call_api('/subscribers/' + imsi + '/update_speed_class', 'POST', { 'speedClass': new_type })
    if error == '':
        message = 'SIM {} のタイプを {} から {} に変更しました。'.format(imsi, old_type, new_type)
    error, sims = _call_api('/subscribers', 'GET', {})
    return render_template('index.html', sims=sims, message=unicode(message, 'utf-8'), error=error)

@app.route('/sim//activate', methods=['GET'])
def activate(imsi):
    error, sim = _call_api('/subscribers/' + imsi + '/activate', 'POST', {})
    if error == '':
        message = 'SIM {} を利用可能にしました。'.format(imsi)
    error, sims = _call_api('/subscribers', 'GET', {})
    return render_template('index.html', sims=sims, message=unicode(message, 'utf-8'), error=error)

@app.route('/sim//deactivate', methods=['GET'])
def deactivate(imsi):
    error, sim = _call_api('/subscribers/' + imsi + '/deactivate', 'POST', {})
    if error == '':
        message = 'SIM {} を利用停止(休止状態)しました。'.format(imsi)
    error, sims = _call_api('/subscribers', 'GET', {})
    return render_template('index.html', sims=sims, message=unicode(message, 'utf-8'), error=error)

# main
if __name__ == '__main__':
    app.run(debug=True,host='0.0.0.0',port=80)

デモサイト

※パスワードなどの情報は一切サーバー上に保存してません。心配な方はご自分でソースコードダウンロードし環境構築されることをオススメします。ちなみにMacでローカル稼働もします。

まとめ

  • SORACOMのAPIはわかりやすいので本当に簡単にアプリが実装できちゃう
    →特にAPI リファレンスがすばらしい。自分のIDでログインして実際のレスポンスを確認することができるようになっている
  • スマホでいつでもどこでも変更できるのは便利だし、ともかく気持ちが楽
    →既存のMVNOデータ通信サービス事業者のサービス変更手続きなどに比べると何でも自由ってのがいいですね
  • Flask便利!(SORACOM関係ないけど)

こぼれ話

ちなみに、SORACOMのおかげで個人でカジュアルにSIMを複数扱うようになってしまって、困っているのがどのSIMが何用なのか不明になること。まとめて床とかに落としたら、どれがどれだかわからなくなる恐れが… 1人で複数SIMをもっていて無くしてしまった場合にどうやってSIMを識別するんだろう?自分は怖くて付箋を貼っている…

my-sims

あと、玉川さんのコメントにあるように全JS実装の方がパスワード管理上は安心感あるかも。もちろん構築もシンプルになるし。時間があったらやってみたい。