Raspberry PI の Python で、Bluetooth を使って出退勤管理したい。

  1. やりたいこと
    =================================
    会社に Raspberry PI を置いて、iPhone の Bluetooth を使って出退勤管理を Google スプレッドシートに自動入力したい。仕様としては、
  • Raspberry PI の Bluetooth デバイスの検知と連動して
  • あらかじめ作成しておいた Google スプレッドシートに
  • 会社に出社したら出社時刻、
  • 退社したら退社時刻を自動入力する。

やりたいのはたったこれだけだが、手入力で管理する手間を考えたら自分の貴重な時間を年間 12〜20時間は削減できるはず!(当社比(笑、1日 3〜5分かかるとして年間 240日出勤と仮定)これは約半日〜1日に相当する。それでは実際に作ってみましょう。

  1. セットアップ
    =================================
    まずは Raspberry PI をアップデート。
sudo apt-get -y update; sudo apt-get -y upgrade; sudo apt-get -y dist-upgrade; sudo apt-get -y autoremove

以下のライブラリを Raspberry PI にインストール(pip と python-bluez、そしてGoogle APIの Python クライアント)。

sudo apt-get -y install python-pip python-bluez
pip install --upgrade google-api-python-client
  1. Google 認証システムとの連携
    =================================
    https://console.developers.google.com にアクセスして OAuth 2.0 クライアント ID を作成する。そこからクライアント認証情報(JSON)をダウンロード。ファイル名は(決め打ちで)client_secret.jsonとして Raspberry PI に保存。このクライアント認証情報は以下で作成していくスクリプトと同じディレクトリに置くこと。
  2. Google 認証システムのフィジビリティスタディ
    =================================
    Google Sheets > API v4 | Python Quickstart を参考に、まずは Raspberry PI 上で Quickstart をこなしてください。くれぐれも、ファイル名はclient_secret.json(決め打ち)、このクライアント認証情報は Quickstart を実行する以下のスクリプトと同じディレクトリに置くこと!(実行するとホームディレクトリ(/home/piなど)配下に .credentials ディレクトリが自動生成されます。)
  3. 検知する Bluetooth デバイスの ID を調べる
    =================================
    https://github.com/karulis/pybluez/blob/master/examples/simple/inquiry… を Raspberry PI 上にダウンロード、実行して検出したい Bluetooth デバイスの ID を調べる。
wget https://github.com/karulis/pybluez/blob/master/examples/simple/inquiry.py

inquiry.pyの内容は、以下のとおり。

# file: inquiry.py
# auth: Albert Huang <albert@csail.mit.edu>
# desc: performs a simple device inquiry followed by a remote name request of
#       each discovered device
# $Id: inquiry.py 401 2006-05-05 19:07:48Z albert $
#

import bluetooth

print("performing inquiry...")

nearby_devices = bluetooth.discover_devices(
        duration=8, lookup_names=True, flush_cache=True, lookup_class=False)

print("found %d devices" % len(nearby_devices))

for addr, name in nearby_devices:
    try:
        print("  %s - %s" % (addr, name))
    except UnicodeEncodeError:
        print("  %s - %s" % (addr, name.encode('utf-8', 'replace')))

python inquiry.py
  1. Bluetooth デバイスの検出のフィジビリティスタディ
    =================================
    https://raw.githubusercontent.com/paulbarber/raspi-gpio/master/inoutboa… をダウンロードして Raspberry PI から実行してみる。以下、抜粋。
#!/usr/bin/python

import bluetooth
import time

print "In/Out Board"

while True:
    print "Checking " + time.strftime("%a, %d %b %Y %H:%M:%S", time.gmtime())

    result = bluetooth.lookup_name('1C:66:AA:CF:DD:35', timeout=5)
    if (result != None):
        print "John: in"
    else:
        print "John: out"

    time.sleep(60)

bluetooth.lookup_name('1C:66:AA:CF:DD:35', timeout=5)の部分は、上で取得した、検出したい Bluetooth デバイスの ID をコピペする。

python inoutboard.py
  1. スクリプトの作成
    =================================
  • 以下のスクリプトを参考に、上で取得した Bluetooth デバイスの IDをDEVICES=['12:34:56:78:9A:BC']にコピペする。
  • DEVICES=['12:34:56:78:90:AB', 'CD:EF:12:34:56:78' ] のように複数書いても OK(検出する Bluetooth 端末が増えます)。
  • SPREADSHEET_IDは、テンプレートから作成した自身のスプレッドシートの ID(ヒント:スプレッドシートの URL に含まれています)。
  • SCOPES は、https://www.googleapis.com/auth/spreadsheets.readonly(リードオンリー)としてはいけない。
#!/usr/bin/python
# -*- coding: utf-8 -*-

from __future__ import print_function
import httplib2
import os
import time
import bluetooth

from apiclient import discovery
from oauth2client import client
from oauth2client import tools
from oauth2client.file import Storage

SPREADSHEET_ID = '1Yz6R_b_ff7zFUu16Mo4YI6IDBA0U6hteb6pPf9PoYus'
DEVICES=['D8:BB:2C:85:22:17']
SLEEP_INTERVAL=30
SERVICE = '';
FIRST_ROW=3

try:
  import argparse
  flags = argparse.ArgumentParser(parents=[tools.argparser]).parse_args()
except ImportError:
  flags = None

# If modifying these scopes, delete your previously saved credentials
# at ~/.credentials/sheets.googleapis.com-python-quickstart.json
# SCOPES = 'https://www.googleapis.com/auth/spreadsheets.readonly'
SCOPES = 'https://www.googleapis.com/auth/spreadsheets'
CLIENT_SECRET_FILE = 'client_secret.json'
APPLICATION_NAME = 'Google Sheets API Python Inout Client'


def get_credentials():
  """Gets valid user credentials from storage.

  If nothing has been stored, or if the stored credentials are invalid,
  the OAuth2 flow is completed to obtain the new credentials.

  Returns:
      Credentials, the obtained credential.
  """
  home_dir = os.path.expanduser('~')
  credential_dir = os.path.join(home_dir, '.credentials')
  if not os.path.exists(credential_dir):
      os.makedirs(credential_dir)
  credential_path = os.path.join(credential_dir,
                                 'sheets.googleapis.com-python-inout.json')

  store = Storage(credential_path)
  credentials = store.get()
  if not credentials or credentials.invalid:
    flow = client.flow_from_clientsecrets(CLIENT_SECRET_FILE, SCOPES)
    flow.user_agent = APPLICATION_NAME
    if flags:
      credentials = tools.run_flow(flow, store, flags)
    else: # Needed only for compatibility with Python 2.6
      credentials = tools.run(flow, store)
    print('In-Out: Storing credentials to ' + credential_path)
  return credentials

def get_service():

  # Creates a Sheets API service object
  credentials = get_credentials()
  http = credentials.authorize(httplib2.Http())
  try:
    discoveryUrl = ('https://sheets.googleapis.com/$discovery/rest?'
                    'version=v4')
    return discovery.build('sheets', 'v4', http=http,
                            discoveryServiceUrl=discoveryUrl)
  except:
    print('In-Out: Error: Authentiction')
    return None

def run():

  # while True:

    print ('In-Out: Checking ' + time.strftime('%Y/%m/%d (%a) %H:%M %Z', time.localtime()))

    for device in DEVICES:
      result = bluetooth.lookup_name(device, timeout=5)
      if (result != None):
        print('In-Out: In: %s (%s)' % (result, device))
        write_in_out()
      else:
        print('In-Out: No Bluetooth device detected: ' + device)

    # time.sleep(SLEEP_INTERVAL)

def write_in_out():

  service = get_service()
  if service == None:
    return

  values = None
  try:
    rangeName = '%s!A%s:D35' % (time.strftime('%Y-%m', time.localtime()), FIRST_ROW)
    result = service.spreadsheets().values().get(
      spreadsheetId= SPREADSHEET_ID, range=rangeName).execute()
    values = result.get('values', [])

  except:
    print('In-Out: Error: write_in_out: service.spreadsheets().values().get()')

    return 

  if not values:
    print('In-Out: No data found.')
  else:
    # print('=================')
    # print(result)

    sheet_name = time.strftime('%Y-%m', time.localtime())
    body = {'values': [[time.strftime('%H:%M', time.localtime())]]}

    i = FIRST_ROW
    for row in values:
      # print('In-Out: %s | %s ' % (row[0], i))
      if time.strftime('%Y-%m-%d', time.localtime()) == row[0]:

        # Write the time at an 'in' column is empty
        if len(row) < 3:
          rangeName = '%s!C%s' % (sheet_name, i)
          print ('In-Out: Wrote: rangeName: %s | %s | body: %s' % (rangeName, time.strftime('%Y-%m-%d', time.localtime()), body))
          result = service.spreadsheets().values().update(
            spreadsheetId=SPREADSHEET_ID, range=rangeName,
            valueInputOption=u'USER_ENTERED', body=body).execute()

        try:
          # Always write the time at an 'out' column while the bluetooth device is detected
          rangeName = '%s!D%s' % (sheet_name, i)
          print ('In-Out: Wrote: rangeName: %s | %s | body: %s' % (rangeName, time.strftime('%Y-%m-%d', time.localtime()), body))
          result = service.spreadsheets().values().update(
            spreadsheetId=SPREADSHEET_ID, range=rangeName,
            valueInputOption=u'USER_ENTERED', body=body).execute()

        except:
          print('In-Out: Error: write_in_out: service.spreadsheets().values().update()')

      i = i + 1

def main():

  run()

if __name__ == '__main__':
    main()
  1. デーモン化
    =================================
    systemdサービスを設定する。上で作成した Python のスクリプトファイルを/usr/binにコピー、またはシンボリックリンクを作成。
cd /usr/bin
ln -s <上で作成した Python スクリプトファイル名(フルパス)> inout.py
vi /lib/systemd/system/inout.service

正直、[Unit]内のAfter、Requiresこれでいいのかよくわからない(よくないとおもう)。

[Unit]
Description=In-Out Checker
After=hwclock-save.service dhcpcd.service bluetooth.target hciuart.service systemd-hostnamed.service
Requires=hwclock-save.service dhcpcd.service bluetooth.target hciuart.service systemd-hostnamed.service

[Service]
Type=simple
ExecStart=/usr/bin/inout.py
Restart = always

[Install]
WantedBy=multi-user.target

systemdデーモンをリロード

systemctl daemon-reload

inout.serviceのデーモンをスタート

systemctl start inout.service

inout.serviceのデーモンを起動時に有効化

systemctl enable inout.service

広告