RaspberryPiとTWE-Liteで作る戸締り確認装置の製作記録

 防犯のため、窓や玄関ドアが開かれた時に警報を出す防犯システムは各種あり、セキュリティ会社に取り付けてもらったり、DIYで簡単に取り付けすることもできる。その中でも、Panasonic製のワイヤレスセキュリティシステム「マモリエ(Panasonicの商標)」は、窓のクレセントの開閉を検知する機能を持ち、戸締りの確認を容易にできる防犯システムだ。しかし、この「マモリエ」は2020年3月で生産中止となってしまったので、それに代わる装置をRaspberry Piと超小型無線データ通信モジュールTWE-Liteを使って試作した。

●戸締り確認装置の部品構成と配線図

<親機の部品構成>

<使った部品>
 ・Raspberry Pi 4
 ・TWE-Lite ワイヤレスモジュール(TWE-Lite-DIP-UFL)
 ・TWE-Liteダイポールアンテナ(TWE-AN-P2010-050)
 ・白色OLED(SSD1306):電池電圧低下の警告表示のため
 ・I2Cの8ビットI/OエクスパンダIC(10LEDアレイを点灯させる)
 ・10LEDアレイ(OSX10201-R)

<子機の部品構成>

<使った部品>
 ・TWE-Lite ワイヤレスモジュール(TWE-Lite-DIP-UFL)
 ・TWE-Lite薄型アンテナ(TWE-A-P2010-05)
 ・ホールIC(SK8552 : UNISONIC TECHNOLOGIES製 )

<親機の結線図>

<子機の結線図>

●戸締り確認装置の仕組み

<クレセントの開閉状態を検知するTWE-Lite子機>

クレセント錠の可動部先端に取り付けたネオジウム磁石がホールICに近づいている状態が「閉」状態で、ホールICの出力端子が「High」となる。クレセント錠を開けた時に、磁石がホールICから遠ざかると、ホールICの出力端子が「Low」になり、モニターLEDが点灯、TWE-Lite子機のディジタル入力が「High」から「Low」に変化。
TWE-Lite子機はディジタル入力の状態が変化した時と、変化がなくても1分間隔で、状態をTWE-Lite親機に送信する。

<クレセントの開閉状態を収集するTWE-Lite親機>

TWE-Lite子機から送信されるディジタル入力状態をLEDアレイに点灯/消灯させる。子機の電池電圧が低下した時には、白色OLEDに表示する。

●使用したTWE-Lite

TWE-Lite DIPを使用。2018年に購入したTWE-Lite DIPには埋め込み型のアンテナタイプがあったが、現在はアンテナ別取り付け型しか売られていない。今回、子機2個には旧型のアンテナ埋め込み型を使った。

<TWE-LiteDIPの新旧比較>

TWE-Lite新旧比較

<使用したTWE-Liteアプリと設定>

TWE-Lite-DIPに標準でインストールされている標準アプリ(App_Twelite)をそのまま使用した。標準アプリではディジタル入力が4入力しかないが、今回の場合1入力で足りることと、子機の電源電圧の状態をデータで送信できる。

(1)動作モード設定(ハードウェア設定)

・「子機:間欠10秒」モード設定:M3,M2,M1端子をGNDに接続

(2)インターラクティブモード設定

・論理デバイスID:親機(121)、子機3台(1,2,3)
・子機間欠10秒モードの間隔:60秒

<TWE-Lite子機から送信されるデータ事例>

受信データはstr型
“:01811501C0810DF83F0080590009E41A0101FFFFFFFFFF06”
1文字目はヘッダー”:”で、それ以降のデータの意味は

文字列データの意味

●プログラムの構成

●プログラム例

TWE-Liteデータを受信して処理するメインプログラム:TWE-Lite-Main.py

このプログラムの中で、次の2つのモジュールを使っているが、その説明は、プログラム事例を参照のこと。

(1)I2C_EXPANDA.py:「8ビットI/Oエクスパンダ(MCP23008/MCP23S08)を用いたLEDアレイの点灯プログラム例」を参照のこと

(2)oledSSD1306.py:「I2C制御の有機ELディスプレイモジュール(制御ICSSD1306)文字表示プログラム例」を参照のこと

import serial
import threading
from collections import deque
import struct, binascii
import I2C_EXPANDA
import oledSSD1306

class TWE_Lite:
	def __init__(self):
		self.thStat = False
		self.data = deque()		# 読み込みデータをQueにスタック
		self.serialPort = serial.Serial('/dev/ttyS0', 115200, timeout=0)	# timeout=0:Non-blocking
		threadRead = threading.Thread(target=self.serialRead, daemon = True)
		threadRead.start()
		
	def serialRead(self):
		while True:
			self.inData = self.serialPort.readline()
			self.inData = self.inData.strip()		# 前後の空白削除
			if len(self.inData) > 0:
				self.inData = self.inData.decode('utf-8')	# Byte型をstr型に変換
				self.data.append(self.inData)

	def checkSum(self, checkData):
		checkData = checkData[1:]		# ":"を削除
		s24B = struct.Struct("24B")	# 24bytes
		checkData = binascii.a2b_hex(checkData)	# 16進数表記の文字列をバイナリデータに変換
		checkData = list(s24B.unpack(checkData))		# 24個のIntデータに展開、変更可能なlistデータに変換
		checkSumVal = checkData[23]		# チェックサム=24番目最後 1byte
		evalCheckData = checkData[:23]		# チェックサムを除く23個
		# チェックサム計算
		cSum = 0
		for intVal in evalCheckData:
			cSum = cSum + intVal
		cSum = 0x100 - (cSum % 0x100)
		if cSum == checkSumVal:
			return True
		else:
			return False

	def dataRead(self):
		intData = []
		if len(self.data) == 0:		# Queが空の場合
			return intData	
		# self.data Queにデータがある場合
		line = self.data.popleft()		# データの取り出し
		if self.checkSum(line):		# CheckSumがOKの場合
			if line[0] == ":":
				line = line[1:]		# ":"を削除
				sData = struct.Struct(">BBBBBIBHBHBBBBBBBBB")
				line = binascii.a2b_hex(line)	# 16進数表記のASCII文字列をバイナリデータに変換
				intData = list(sData.unpack(line))		# 14個のIntデータに展開, 変更可能なlistデータに変換
				if (intData[7] & 0x8000) == 0x8000:		# 同じタイムスタンプ
					intData[7] &= 0x0FFF
				intData[9] = intData[9] / 1000		# 電圧(V)
		return intData

#/////////////////////////////////////////////////////////
if __name__ == '__main__':
	
	tweLite = TWE_Lite()
	arrayLED = I2C_EXPANDA.MCP23008()
	oled = oledSSD1306.OLED()
	lineStr = ["", "", "", ""]
	arrayLED.ALLOFF()
	BATT_LOW = 2.8
	battLow = [0]*8
	
	stat = True
	try:
		while stat:
			readData = tweLite.dataRead()
			if len(readData) > 0:
				print("送信元ID={:02d}, 通信品質LQI={:3d} Time={:8d}, 電圧={:8.3f}, 入力値={:04X}, Mask={:04X}".format(
							readData[0], readData[4], readData[7], readData[9], readData[11], readData[12]))
				if readData[9] < BATT_LOW:			# 電池電圧低下
					battLow[readData[0] - 1] = 1		# 子機IDの電池電圧低下を"1"に
				else:
					battLow[readData[0] - 1] = 0		# 子機IDの電池電圧低下を"0"にリセット
				lineStr[0] = ""; lineStr[1] = ""
				for idNo, battStat in enumerate(battLow):
					if battStat:
						lineStr[1] += (f'{idNo:02d} ')
				if len(lineStr[1]) > 0:		# 電池低下の番号があれば 
					lineStr[0] = "電池交換"
				oled.dispText(lineStr)			
				if readData[11] == 1:	# マグネットセンサーがONの時
					arrayLED.ON([readData[0] - 1])	# ID=1 to 3  LED=0 to 2
				else:
					arrayLED.OFF([readData[0] - 1])
	except KeyboardInterrupt:
		stat = False
	oled.clearDisp()
	arrayLED.ALLOFF()
	arrayLED.close()

●試験状況

クレセントに取り付けた子機の他に、デモンストレーション用にブレッドボードに配線した子機2個を使って、開閉時の信号変化を試験した。

<試験の姿>

<試験結果>