知的好奇心 for IoT

IoT関連の知的好奇心を探求するブログです

シェアハウスで洗濯機や乾燥機の終了を通知するサービスを稼働させた(ウェブアプリ編)

HEROKU

Laundry Reminderのクラウドアプリケーションプラットフォームとして利用しているのがHerokuです。Herokuは2007年創業の企業で、2010年にセールスフォース・ドットコムに買収されたPaaSの草分け的存在です。

Herokuを選んだ理由はやはり無料で使えること(アプリの総合月間利用時間が1,000時間まではクレジットカードを登録すれば無料)が一番大きかったのですが、データベースにPostgreSQLが使えること、使ったことは無かったもののDjangoというPythonで実装されたWebアプリケーションフレームワークにすごく魅力を感じたことが決め手となりました。

また、PostgreSQL以外にも無料枠が設定されているadd-onがたくさんあり、Laundry ReminderではSendGridというメールサービスも利用しています。

f:id:IntellectualCuriosity:20171101123106p:plain
Herokuのダッシュボード画面(laundryroomというアプリ名にしています)

 

Herokuの使い方を覚える

クラウドアプリケーションプラットフォームをHerokuに決めたので、Herokuの使い方を覚えなくてはいけなくなりました。HerokuドキュメントサイトのHeroku Dev Centerに行って、最初のお約束「Getting Started 」を選ぶと言語に応じたステップバイステップガイドを選択する画面になります。

f:id:IntellectualCuriosity:20171106170308p:plain
Getting Startedで選択できる言語 2017/11/06現在

ぼくはPythonを使うと決めていたので迷わずへびを選びました。すると15項目からなるメニューと最初の項目「Introduction」の内容が表示されます。

Introductionには、簡単なDjangoアプリをデプロイするチュートリアルを”数分”で行うこと、”もう数分”を使ってHerokuの動作の仕組みを学ぶとか書いてあるのですが、これが結構な無理ゲーでネイティブでもそんな短い時間じゃできないだろうと思える感じです。少なくても”数分”ではなく”数十分”は覚悟しましょう。

また、Introductionにはチュートリアルを行う上での前提が書かれています。

  • 無料のHerokuアカウント
  • Python 3.6がインストール済み
  • Pipenvがインストール済み
  • Postgresがインストール済み

ぼくはセンサーアプリをラズパイで開発していたので、そのままウェブアプリもラズパイで開発を行うことにして、上の前提をラズパイで整えてチュートリアルを進めました。

チュートリアルの最後の項目「Next steps」に書いてあった「How Heroku Works」と「Deploying Python and Django Apps on Heroku」もやって、本命のDjangoアプリの作り方を学ぶ準備を整えました。

 

Djangoの使い方を覚える

Djangoの使い方は、Django Software FoundationのDjango projectサイトにもやっぱりある最初のお約束「Getting started」を行って覚えました。ぼくが覚えた時には英語のコンテンツしかなかったのですが、右下にLanguageを選択できるインターフェースができていて、なんと日本語が選べるようになっています!日本語を選択すると「Getting started」が「さぁ始めましょう。」に変わって、萎みかけていたやる気を復活させてくれると思います。

f:id:IntellectualCuriosity:20171107142337p:plain

さぁ始めましょう。」の最初のコンテンツ「Djangoの概要」をざっと見てみたら、概要よりもちょっと突っ込んだ内容になっているという印象を受けました。まったくDjangoを知らない人は、先にWikipediaDjangoページを読んだ方がどういうものか理解できると思います。

Djangoの概要が(なんとなく)理解出来たら、ちゃっちゃとチュートリアルを始めてみましょう。この世界、習うより慣れろです!チュートリアルでは簡単な投票アプリを作るので、まずそれをローカル環境で動かせるようにして、その後Herokuの使い方で行った内容を参考にしてHerokuで動かしてみましょう。

 

Laundry Reminderのウェブアプリを作る

こうして、HerokuとDjangoの使い方を覚えてLaundry Reminderのウェブアプリを作りました。

ここではDjangoのキモとなるmodels.pyとurls.py、views.pyを簡略化した形で紹介します。

models.py

Laundry Reminderのメイン機能はMachineモデルだけで実現しています。

センサーアプリのクラウドへのリクエストに対応するのがbuilding,floor,machine,powerフィールドです。machine_typeとlayoutは画面表示の識別用フィールドで、emailはfacebook連携で得られたメールアドレス用のフィールドです。

unique_togetherはこのモデルがユニークになるフィールドの組み合わせを定義しています。

ここでは説明を簡略化するために、Machineモデル以外のモデル、Adminサイト用の定義、フィールドの値の定義を省いたmodels.pyを載せています。

from __future__ import unicode_literals
from django.db import models

# Create your models here.

class Machine(models.Model):
    building = models.CharField(max_length=2)
    floor = models.CharField(max_length=2)
    machine = models.CharField(max_length=2)
    machine_type = models.CharField(max_length=1)
    power = models.CharField(max_length=1, default='0')
    layout = models.IntegerField()
    email = models.EmailField(blank=True)

    unique_together=("building","floor","machine")

urls.py

説明を簡略化するために、ウェブアプリの画面表示用とセンサーアプリのリクエスト用のURLとビューの関連付けだけにしています。

URLは正規表現を使用して有効なURLのパターンを記述します。<>で囲まれた文字列はリクエストの文字列が入った変数として関連付けされたビュー内で利用することができます。

from django.conf.urls import url

from . import views

app_name = 'アプリ名'
urlpatterns = [
    # ウェブアプリの画面表示用
    url(r'^(?P<r_building>[A-Z][A-Z])/(?P<r_floor>[A-Z][A-Z])/$', views.detail, name='detail'),
    # センサーアプリのリクエスト用
    url(r'^(?P<r_building>[A-Z][A-Z])/(?P<r_floor>[A-Z][A-Z])/(?P<r_machine>[A-Z][A-Z])/(?P<r_power>[0|1])/$', views.receive, name='receive'),
]

views.py

説明を簡略化するために、ウェブアプリの画面表示用とセンサーアプリのリクエスト用のビューだけにしています。

どちらのビューも始めにステータスコード404を返す場合の処理を行っていて、リクエストが条件に合わない場合をはじいています。

detailビューはスマホからのリクエストパラメータと該当するMachineモデルのデータなどをdetail.htmlテンプレートと共にレンダリングして、スマホLaundry Reminderの画面情報を返しています。Djangoの仕組みでは取得できない情報があったため、SQL文を直書きしている部分があります。

receiveビューはセンサーアプリからのマシンの電源ステータス(r_power)で該当するMachineモデルのデータを更新し、電源ステータスがOFFの場合はメールアドレスがあるとメールを送信します。

from django.shortcuts import render, get_list_or_404, get_object_or_404
from django.http import HttpResponse
from django.core.mail import send_mail

from .models import Machine

# Create your views here.

def detail(request, r_building, r_floor):
    machines = get_list_or_404(
        Machine.objects.order_by('layout'), building=r_building, floor=r_floor
    )

    floors = Machine.objects.raw(
        'SELECT floor, min(id) as id FROM アプリ名_machine ' + 
        'WHERE building=\'' + r_building + '\' ' + 
        'GROUP BY floor ORDER BY floor'
    )
    
    context = {
        'building': machines[0].get_building_display(),
        'r_building': r_building,
        'floors': floors,
        'r_floor': r_floor,
        'machines': machines,
    }
    return render(request, 'アプリ名/detail.html', context)

def receive(request, r_building, r_floor, r_machine, r_power):
    machine = get_object_or_404(
        Machine,
        building=r_building,
        floor=r_floor,
        machine=r_machine
    )
    machine.power = r_power
    if r_power == '0':
        if machine.email <> '':
            send_mail(
                # subject
                'Your laundry finished.',
                # message
                machine.get_machine_display() + ' of the ' + machine.get_floor_display() + ' has finished.',
                # from address
                machine.email,
                # to address
                [machine.email, ],
                fail_silently=True,
            )
        machine.email = ''
    machine.save()
    return HttpResponse("OK")

 

Laundry Reminderを導入してみたい!興味がある!という方は、プロフィール欄のメールアドレスにお問い合わせください。

 

終わり