- やりたいこと
=================================
会社に Raspberry PI を置いて、iPhone の Bluetooth を使って出退勤管理を Google スプレッドシートに自動入力したい。仕様としては、
- Raspberry PI の Bluetooth デバイスの検知と連動して
- あらかじめ作成しておいた Google スプレッドシートに
- 会社に出社したら出社時刻、
- 退社したら退社時刻を自動入力する。
やりたいのはたったこれだけだが、手入力で管理する手間を考えたら自分の貴重な時間を年間 12〜20時間は削減できるはず!(当社比(笑、1日 3〜5分かかるとして年間 240日出勤と仮定)これは約半日〜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
- Google 認証システムとの連携
=================================
https://console.developers.google.com にアクセスして OAuth 2.0 クライアント ID を作成する。そこからクライアント認証情報(JSON)をダウンロード。ファイル名は(決め打ちで)client_secret.jsonとして Raspberry PI に保存。このクライアント認証情報は以下で作成していくスクリプトと同じディレクトリに置くこと。 - Google 認証システムのフィジビリティスタディ
=================================
Google Sheets > API v4 | Python Quickstart を参考に、まずは Raspberry PI 上で Quickstart をこなしてください。くれぐれも、ファイル名はclient_secret.json(決め打ち)、このクライアント認証情報は Quickstart を実行する以下のスクリプトと同じディレクトリに置くこと!(実行するとホームディレクトリ(/home/piなど)配下に .credentials ディレクトリが自動生成されます。) - 検知する 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
- 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
- スクリプトの作成
=================================
- 以下のスクリプトを参考に、上で取得した 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()
- デーモン化
=================================
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