CCCマーケティング データベースマーケティング研究所の Tech Blog

研究所スタッフによる格闘記録やマーケティング界隈についての記事など

ラズベリーパイとWebIOPiを用いた個人用ソファーの使用時間表示装置

初めまして。ソリューション開発チームの木下です。

弊社のofficeには個室のような個人用ソファーがあります。防音性があり、また人目を気にする必要がないので、オンライン会議や電話をする際に使用する方が多いです。

f:id:kinopee0120:20210308170925j:plain
個人用ソファー

使用は60分までと告知しているのですが、その居心地の良さ故に長きに渡って占有してしまう方がいて、他の方が使用できないケースが頻繁に発生していました。

そこで、
①ソファー背面に使用状況と残り使用時間の表示
②ソファー内部に残り使用時間の表示
ができるようにしました。

①は利用を検討している(待っている)人向け、
②は現在使用している人向けのソリューションになります。

今回作成したデバイスはこちらです。

f:id:kinopee0120:20210308172127j:plainf:id:kinopee0120:20210308172040j:plain
①ソファ背面              ②ソファ内部

では、上記デバイスの作成方法について、簡単に解説します。

開発環境

OS: Mac OS Catalina(10.15.7)
エディター:  VScode

必要部材

・タブレット(ブラウジングができればなんでも良い)
RaspberryPi ZERO WH
デジタル時計表示液晶
・充電器

構成図は以下のようになります。

f:id:kinopee0120:20210308180722p:plain
構成図

①RaspberryPi ZERO WHのSSH接続と初期設定

RaspberryPi ZERO WH(以降ラズパイと呼ぶ)はWiFIに接続することができるので、 VNC Serverを用いて、下記画像のようにMac上にラズパイの画面を表示させます。 外部モニターや外部キーボードを準備することなく、ラズパイで開発ができるので、大変便利です。 では、手順を見ていきましょう。

f:id:kinopee0120:20210312172140p:plain
Mac上に表示されたラズパイ のデスクトップ

1,microSDカードにOS(Raspbian)を書き込む

今回は、2020-08-20-raspios-buster-armhf-fullを書き込みました。公式ページなどから無料でダウンロードできます。 この際、SDカードのスロットがあるPCを使用しましょう。(もしくは外付けを購入しましょう。) 私は書き込みのみ、MacBookAirを使用しました。

2.wifi接続用のconfigファイルであるwpa_supplicant.confを作成し、使用する無線LANのSSIDとpasswordを記入

 ※2.4GHz帯域(G)のみ対応

3.空のsshというファイルを作成し、ラズパイ起動時にsshを有効にする

 ※拡張子はつけないこと

4.ラズパイにmicroSDと電源をさして、起動させる。

 ※最初の1分程度は電源ランプが点滅しており、点灯するまで待つこと

5.Macのターミナルからssh接続を行う。

ssh pi@raspberrypi.localで接続できる。
初期パスワード”raspberry”を入力して、接続完了。その後、パスワードを変更する。
sudo passwd piでユーザーpiのパスワードを適宜変更できる。

7.VNCサーバーをインストールする。

Macからラズパイの画面が見れるようにするtightvncserverをインストールするために、下記コマンドを実行する。
 sudo apt-get update
 sudo apt-get upgrade
 sudo apt-get install tightvncserver

8.VNCサーバーを起動させる。

初回起動時のみパスワードを設定する。
tightvncserverと打つことで起動できる。

9.ラズパイのIPを調べる。

ifconfigと打って、"inet"の隣に表示されている
 ※無線LANの場合は、wlan0の項目内のinetを見ること

10.MacからVNCServerでラズパイの画面に入る。

9で取得したIPの後ろに:5901を付加した番号をMacのファインダーのサーバー接続に入力する。Passwordを聞かれるので、8で設定したものを入力して接続完了。

11. pythonのIDLEをインストールする。

python実行をボタンで行うもので、デバッグ時にあると便利です。
sudo apt-get install idle-python3.7

②タイマー作成

ここでは、ソファ内部(手元)に設置する、使用者が残り時間を確認するためのデジタル時計を作成します。 購入したデジタル時計には、サンプルプログラムとpinの説明等が書いてあるエクセルファイルがついてきます。 それをもとに、変更を加えてタイマーを作成していきます。

1.ラズパイとデジタル時計を接続する

購入したデジタル時計デバイスを付属の資料を参考にして適切なGPIOピンにジャンパー線で接続する。

2.サンプルプログラムをラズパイに転送する。

資料に書いてあるURLに飛んで資料を取得し、適切なGPIOピンの情報に変更したものを.pyに変換して、Macからラズパイ内にscpコマンドで転送する。
scp [オプション] (コピー元パス) (保存先パス)
の順に記載して実行しましょう。

3.サンプルプロジェクトを修正して、60分タイマーに変更する。

サンプルプロジェクトは、実行すれば時間が表示されるプログラムになっています。 tm1637libという、プログラムからGPIO信号を操作することでデジタル時計に文字を表示するための独自のクラスが記載されたファイルがあるので、これを用います。

start_clock.py

from tm1637lib import *
import time

GPIO_CLK = 27
GPIO_DIO = 22

Display = TM1637(GPIO_CLK, GPIO_DIO, BRIGHT_TYPICAL)

Display.Clear()
elapsed = 0
limit = 60*60 

start = time.time()


try:
  while True:
    now = time.time()
    sec = limit-(now-start)
    m = sec//60
    m = '{:02}'.format(int(abs(m)))
    
    if sec <= -99*60:
      data = [37,37,37,37]
    elif sec<0:
      data = [38,37,int(m[0]),int(m[1])]
    else:
      data = [38,38,int(m[0]),int(m[1])]
    Display.Show(data)

    time.sleep(10)

except KeyboardInterrupt:
  pass
finally:
  Display.Clear()

limitを決めて(今回は60分)、そこからカウントダウンを行い、60分たったら負の時間を表示、そこから99分たったら「--:--」と表示するようにしています。これをIDLEで実行すると、以下のようにタイマーが始動します。

f:id:kinopee0120:20210309164816p:plain
デジタル時計

③ WebIOPiのインストールとサーバー立ち上げ

WebIOPiとは、Raspberry Piにインストールして実行するとwebサーバを立ててくれて、ブラウザからGPIOの操作や、Raspberry Pi内のPythonプログラムを実行できるようにしてくれるフレームワークです。タブレットをタップしたら、JavaScript上でイベントハンドラーが作動し、webiopiのcallmacro関数内に引数として与えられているpythonプログラム(上記のstart_clock.py等)を動作させる、という流れになります。

1.WebIOPiをインストール

こちらのサイトなどを参考にいたしました。 この作業が終わるとサーバーが立ち上がっているので、localhost:8000に接続すると、WebIOPiのデフォルトの画面に入れるはずです。

2.WebIOPiで実行するpythonスクリプトの作成

まず、プログラムを見てみましょう。

gpio.py

import webiopi
import sys
import os
import subprocess
import datetime
import gc

sd = os.path.dirname(os.path.abspath(__file__))
sys.path.append(sd)
p = subprocess.Popen(["python3", "/home/pi/work/webiopi/static/kill_clock.py"],shell=False)


@webiopi.macro
def clock(data):
    global p
    p.kill()
    if data == "0":
        dt_now = datetime.datetime.now()
        log = 'IN ' + dt_now.strftime('%Y-%m-%d %H:%M:%S') + "\n"
        f = open(sd + "/log.txt","a")
        f.write(log)
        f.close()
        p = subprocess.Popen(["python3", "/home/pi/work/webiopi/static/start_clock.py"],shell=False)
        gc.collect()
        
    else:
        dt_now = datetime.datetime.now()
        log = 'OUT ' + dt_now.strftime('%Y-%m-%d %H:%M:%S') + "\n"
        f = open(sd + "/log.txt","a")
        f.write(log)
        f.close()
        p = subprocess.Popen(["python3", "/home/pi/work/webiopi/static/kill_clock.py"],shell=False)
        gc.collect()

        

@webiopiとデコレータを記載することで、この下の関数がWebIOPiで使用することができるようになります。 clock関数の引数によって、start_clock.pyかkill_clock.pyを切り替えて実行させています。 引数に関しては後述しますが、ブラウザ上のタップの状況です。(on/off) start_clock.pyは上記で書いたプログラムで、デジタルタイマーを表示するもの、 kill_clock.pyはデジタルタイマーの表示を消すものです。 このプログラムをターミナルから実行するために、subprocessを使用しています。 また、どの時間に使用を開始or終了したのかわかるようにlogを出力するようにしてます。

3. ブラウザに表示させるフロント周りのコード作成

最後にフロント部分のコードをまとめて記載します。 main.jsではjQueryを使用いたしました。 cssに関しては割愛いたします。

index.html

<!DOCTYPE html>
<html lang="en">
<head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <meta http-equiv="X-UA-Compatible" content="ie=edge">
        <script type="text/javascript" src="/webiopi.js"></script>
        <link rel="stylesheet" type="text/css" href="./static/css/style.css">
        <title>Document</title>
</head>
<body>
        <p class=msg>盗難ブザーを設置しているため、タブレットを移動しないでください。</p>
        <p class=sign>使用可</p>
        <p class=msg1>残り時間</p>
        <p id ="timer">--:--</p>
        <div class=box>
        <p class=msg2>タップして利用開始</p>
        </div>
        <p class=msg3>60分タイマーがスタートします。</p>
  <script src="./static/js/jquery-3.5.1.min.js"></script>
  <script src="./static/js/main.js"></script>
</body>
</html>

main.js

$(function() {
  webiopi()

  var time = 180;
  var setI;
  var min;
  var sec;

  function startTimer() {
    time -= 1;
    min = Math.floor( time / 60 );
    sec = time % 60;
    min = ('00' + min).slice(-2);
    sec = ('00' + sec).slice(-2);
    $('#timer').html(min + ":" + sec);
    if (time === 0) {
      $('#timer').html("Time&nbspUP!");
      clearInterval(setI);
    }
  }

  function startInterval() {
    setI = setInterval(startTimer,1000);
  }

  $("body").on('click', function() {
    $(this).toggle(1,alertFunc);
  });
  
  function alertFunc(){
  if ($(".sign").text() == "使用可") {
    $(".sign").text("使用中");
    $(".msg").css("color","white");
    $(".msg2").text("タップして使用終了");
    $(".msg3").text("");

    $(this).css("display", "block");
    $(this).css("background-color", "red");
    $('#timer').html("60:00");
    clearInterval(setI);
    time = 3600;
    startInterval();
    webiopi().callMacro("clock", 0);
    // const date = new Date();
    // log = "IN " + date
    // console.log(log)

    
  }else{
    $(".sign").text("使用可");
    $(".msg").css("color","white");
    $(".msg2").text("タップして使用開始");
    $(".msg3").text("60分タイマーがスタートします。");

    $(this).css("display", "block");
    $(this).css("background-color", "white");


    $('#timer').html("--:--");
    clearInterval(setI);
    webiopi().callMacro("clock", 1);
    // const date = new Date();
    // log = "OUT " + date
    // console.log(log)

  }
  };
});

ここで大切なのは、main.jsからもWebIOPiを呼び出していることです。 webiopi().callMacro("呼び出したい関数名","呼び出した関数に与える引数")と記載することで、 gpio.py内のclock関数が呼び出されて、実行されるという仕組みです。
フロントの動作としては、タップするごとに"使用可"、"使用中"がトグル動作のように切り替わる仕様にしました。

また、ソファ内部のデジタル時計では負の時間が表示されるのに対し、 このソファ背面に表示されるタブレットの画面では、60分が経過したら"TimeUp"と表示されるようにしました。 TimeUpなら交代しても文句いわれないよね、という意図があります。

タブレットに表示される画面は以下の2種類になります。

f:id:kinopee0120:20210309183319p:plainf:id:kinopee0120:20210309183344p:plain
タブレット上の画面

以上で、個人用ソファーの使用時間表示装置の完成です。 これで、皆さんがソファを時間を守って使用するようになり、多くの人に使ってもらえるようになることを願っています。 また吐き出されたログを解析して、どの時間帯に多く使われているかなど調べられると、改善につながるかもしれません。