エンジニアらしき人のひとりごと

100人中100人は興味を示さなくても、100万人居たら1人くらいは面白いと思ってくれそうな、重箱の隅をほじくってみるブログ。

黒電話をスマホの子機(Bluetoothヘッドセット)に!⑤

黒電話をスマホの子機にしよう!という話の、第5回。

過去記事は、以下にあります。
黒電話をスマホの子機(Bluetoothヘッドセット)に!① - エンジニアらしき人のひとりごと
黒電話をスマホの子機(Bluetoothヘッドセット)に!② - エンジニアらしき人のひとりごと
黒電話をスマホの子機(Bluetoothヘッドセット)に!③ - エンジニアらしき人のひとりごと
黒電話をスマホの子機(Bluetoothヘッドセット)に!④ - エンジニアらしき人のひとりごと

前回までのあらすじ

ラズパイ(Raspberry Pi)を使って、黒電話をスマホの子機(Bluetoothヘッドセット)にしようと画策中。
スマホ⇔ラズパイ間をBluetoothで接続し、ラズパイからスマホの発着信操作ができた(②と③)。
黒電話のダイヤルは、ON/OFFを繰り返すスイッチのようなもので、なんとかラズパイに繋いでON/OFFが分かるようになった。

で、今回は

そろそろプログラムを書いて、何番がダイヤルされたかを検出できるようにしよう。

使う道具を決める

プログラムからGPIOを操作する方法がいくつかあるので、まずはどれを使うか選定する。

  • /sys/class/gpio/ へのファイル操作(④で使用した方法)
  • WiringPiライブラリ
  • RPi.GPIOライブラリ
  • pigpioライブラリ

http://abyz.co.uk/rpi/pigpio/

いろいろあるが、今回ではpigpioを使うことにした。pigpio本体はLinuxのデーモンとして動いていて、高速&高精度のGPIO操作が可能。そして、Pythonのアプリから操作するためのインタフェースとしてのライブラリが提供されている。
これを選んだ最大の理由は、特徴に以下の項目があったから。
>callbacks on GPIO 0-31 level change (time accurate to a few μs)

これの何が良いかというと、
何番がダイヤルされたかをカウントするために必要な、パルスの立ち上がり(0→1になる)タイミングを教えてくれる、ということ。

もしこの機能がなかったら、立ち上がりの検出処理を自分で実装する必要が出てくるので、ちょっと面倒だし、なんだかスマートな方法じゃない。
どういう処理かというと、定期的にGPIOポートの値をチェックして「0, 0, 0, 0, 0, 0, 0, 0, 1, あ!いま1になった!」というのを見つける、いわゆるポーリングの処理。

これをライブラリ側でやってくれるというのは、すごくスマート。プログラムが簡単になる。
しかも、数マイクロ秒の誤差で、ライブラリからアプリに通知してくれる。
おぉすごい!

ということで、Python + pigpio でプログラムを書いてみた。

簡単なコード

#!/usr/bin/python3

import pigpio
import time
from enum import Enum

HIGH = 1
LOW = 0

# D3ケーブルの接続ポート
PORT_DIALING = 22

# D1ケーブルの接続ポート
PORT_DIAL_PULSE = 17

# H1ケーブルの接続ポート
PORT_HOOK = 24

class KurodenGpio:
	Status = Enum("Status", "IDLE WAIT_PULSE PULSE_HIGH PULSE_LOW")

	gpio = None

	status = Status.IDLE

	# パルスの立ち上がりをカウントする
	dial_count = 0

	def __init__(self):

		# pigpioを生成
		gpio = pigpio.pi()
		self.gpio = gpio

		# 各ポートをINPUTに
		gpio.set_mode(PORT_DIAL_PULSE, pigpio.INPUT)
		gpio.set_mode(PORT_DIALING, pigpio.INPUT)
		gpio.set_mode(PORT_HOOK, pigpio.INPUT)

		# ノイズ対策
		gpio.set_glitch_filter(PORT_DIAL_PULSE, 1000)
		gpio.set_glitch_filter(PORT_DIALING, 1000)
		gpio.set_glitch_filter(PORT_HOOK, 1000)

		# パルスの立ち上がり/立ち下りを契機に、関数を呼び出してもらう
		gpio.callback(PORT_DIAL_PULSE, pigpio.EITHER_EDGE, self.callback_gpio_d1)
		gpio.callback(PORT_DIALING,    pigpio.EITHER_EDGE, self.callback_gpio_d3)
		gpio.callback(PORT_HOOK,       pigpio.EITHER_EDGE, self.callback_gpio_hook)

	def __del__(self):
		self.gpio.stop()

	def callback_gpio_hook(self, gpio, level, tick):

		if level == 0:
			print("受話器取った")
		else:
			print("受話器置いた")

	def callback_gpio_d3(self, gpio, level, tick):
		if level == 0:
			print("ダイヤル開始")
			if self.status == self.Status.IDLE:
				self.status = self.Status.WAIT_PULSE
				self.dial_count = 0
		else:
			print("ダイヤル終了")
			if self.status == self.Status.PULSE_LOW or self.status == self.Status.WAIT_PULSE:
				self.status = self.Status.IDLE
				if self.dial_count != 0:
					if self.dial_count == 10:
						self.dial_count = 0
					print("** Dial:", self.dial_count)

	def callback_gpio_d1(self, gpio, level, tick):
		if level == 1 and (self.status == self.Status.WAIT_PULSE or self.status == self.Status.PULSE_LOW):
			self.status = self.Status.PULSE_HIGH
		elif level == 0 and self.status == self.Status.PULSE_HIGH:
			self.status = self.Status.PULSE_LOW
			self.dial_count += 1

#####

if __name__ == '__main__':
	print("** START **")
	gpio = KurodenGpio()
	while True:
		time.sleep(1)

状態管理はいらんかな?とも思ったが、つけてみた。

このコードをgpiotest.pyとして保存し、以下のように実行している。

$ python3 gpiotest.py

何番がダイヤルされたかと、受話器の上げ下げが分かるようになった!

次回予告

そろそろ本格的にアプリを作ってみることにする。


ちなみに、本シリーズの記事一覧はこちらからどうぞ!
tokieng.hatenablog.com