2018年09月15日

夏の工作 Raspberry Pi (Twitterのメンションに反応して動作する)

Twitterで部屋の環境をつぶやくbotと,留守番猫カメラができたので,これらを組み合わせて,Twitter経由でカメラの制御ができるようにする。

Twitterでbot宛のメンションを監視して,「カメラ」「留守番」等のコマンドを検出したら,特定の動作を行うようにしたい。

メンションにリプライを返すbotはいろいろとネットに情報があって例えば,

参考サイト:TweepyとStreaming APIで自動リプライbotをつくる

などを参考にやってみたのだが,どうも動いたり動かなかったりする。変だな変だなと思っていたら,実はTwitterのAPIの仕様が2018年8月で変更になって,上記で使われているStreaming APIが廃止になってしまったらしい。

代わりにTweepy(pythonのライブラリ)で,api.mentions_timelineを使うことで,自分宛のメンションのタイムラインを取得できる。これをcronで定期的に実行するようにして,自分宛の新しいメンションがあったら,その内容をチェックして,「カメラ」,「留守番」等のキーワードが含まれていたら,所定の動作(留守番カメラの起動,写真撮影など)をするスクリプトを書いた。

参考サイト:[(元)情報学科大学生のブログ] Tweepyを使ってTwitterのbotを作る

cronの実行周期を1分にしているので,botに対して@付きでつぶやいてからレスポンスがあるまで最長1分くらい時間がかかるけれど,瞬時に応答が必要な機能でもないので,特に問題は感じない。




注意点としては,自分宛のメンションのうち,特定のユーザID(自分)からのメンションにだけ反応するようにしておくこと。そうしないと知らない人が留守番猫カメラのON/OFFを制御できたりしてしまう。
タグ:Raspberry_Pi
posted by ソウヘイ at 19:24| Comment(0) | パソコン・インターネット

2018年09月11日

夏の工作 Raspberry Pi (欧文/和文モールス音源生成スクリプト)

留守番猫カメラからの続き。

せっかくのラズパイを何か無線関係に使うことはできないだろうか。D-starのノード局にして使う,というのは聞いたことがあるけれど,D-starが使えるリグもないし,そもそもphoneはあまり興味がない。

せっかくならCW関係の何か...といってもすぐ思いつくエレキー/メモリキーヤーなんかはありきたりだし,すでに電池駆動で使えるメモリキーヤーを持っているので,別に必要性もない。

そこで,ちょうど和文や欧文の聞き取り練習をしているところでもあるので,日本語の普通文をCWの音声ファイルに変換するスクリプトはどうだろう。

Windows用にA1A Breakerというすばらしいフリーソフトがあるけれど、確か日本語の平文(漢字仮名交じり文)を直接変換することはできなかったのでは。

というような思考を経て、次のような仕様を考えた。
1 欧文と和文を自動判定
2 和文の場合、漢字仮名交じり文をカナに変換
3 CWの音声ファイルを生成

1はまあ含まれている文字から簡単に判別できそう。
3も文字に対応した符号を生成して、sin波の入切で音声データを作るのはなんとかなりそう。

やっかいなのは2で、漢字仮名交じり文の読み仮名を解析してカナに変換する方法を考えなくてはいけない。

自力でそんなコードを書くのはほとんど無理だけど、世の中には親切な人がいるもので、MeCabというオープンソースの形態素解析エンジンがある。これは日本語の文章を単語に区切り、品詞を同定する処理をしてくれるもので、そのついでに読み仮名も出してくれる。

で、これを使って1、2、3それぞれの機能ごとに少しずつコードを書いていった。




この辺からPythonの日本語処理でハマることが多くなる。まずstrとunicodeと文字コードがごちゃごちゃになってよくわからない。出てくるエラーを見ながらググってはエラーを出し,print文を仕込んでordやtypeやencode文を使って確認しつつ,辻褄を合わせるデバッグ作業が続いた。

文字コード(ASCIIコード,UTF-8コード)に対応したモールス符号の生成(半角"ア" -> UTF-8"FF67 -> モールス符号"11011")は以下のサイトを参考にした。欧文は普通にASCIIコードで変換できる(ord("A")=65)んだけど,半角カナはord("ア")=177とはならず,ord("ア")=65393(FF71)で,これはUTF-8コード。この辺も最初はよくわからずハマった。

参考サイト:JH7UBCブログ ラズパイ Python モールス符号練習機

MeCabの読み変換はもちろん完璧ではなく,「大喜び」を「だいよろこび」と変換するなど,細かいミスはあるものの,一般的な文章なら相当精度良く変換できる。これは本当に素晴らしいなぁ。

和文符号だと濁点や半濁点を分離しないといけない(「ザ」は「サ゛」にする)必要があり,このために全角カナを半角カナにさらに変換。半角カナには濁点・半濁点付きの文字がないので,強制的に分離される。




sin波をつなげて音声を作っていくのは,以下のサイトを参考にした。信号ありをレベル1,無音部分を0として,ドットを基準の長さにして,ダッシュは3倍の長さ,というようにつなげていく。

参考サイト:[python] sin波の音をWAV形式で出力する

サンプリング周波数は最初サンプルと同じくCD音質の44100Hzとしていたけれど,これだと2分を超えるような符号列だと変換途中にメモリーエラーで落ちてしまうことが判明。よく考えたらCWのトーン周波数は700Hzなので,標本化定理に従えば1400Hzで充分再現できるはず。一応さらに倍の2800Hzとした。これで15分以上の長い文章でも一気に変換できるようになったと思う。

まだまだおかしいところはあると思うけれど,現状のコードを載せておく。いろいろなライブラリを読み込んでいるので,importに書かれているライブラリを事前にインストールしておく必要がある。

速度は変数[sec]に短点の秒数で指定。現状だと総通試験の和文の速度(1分間75字程度)になっていると思う。適宜調整してほしい。総通の試験で出ないと思われる符号はバッサリ削除している。音源はそのままでは完成度が低くて使えないかもしれないので,カナ変換した時点で生成されるout.txtをA1A Breakerなりに読み込ませて使うのも良いと思う。

実行方法は下記。引数に変換元のテキストファイルを入れておく。
python txt2cw.py momotaro.txt


ソースコード txt2cw.py
※「動きません」とか質問されても,当方もラズパイ,Pythonビギナーなのでお答えできない可能性が高いです。アドバイスは歓迎いたします。

#!/usr/bin/env python
# coding: utf-8

import numpy as np
import wave
import struct
import pydub
import sys
import MeCab
import mojimoji
import subprocess
import re
import codecs
import jaconv
import textwrap
import gc

#文字コードーモールス符号の変換テーブル
Morse_Code =[63,62,60,56,48,32,33,35,39,47,1,1,1,1,1,1,1,6,17,21,9,2,20,
11,16,4,30,13,18,7,5,15,22,27,10,8,3,12,24,14,25,29,19]
Wabun_Code =[30,59,6,12,61,34,14,57,7,22,54,59,6,12,61,34,18,37,24,29,31, #ヲ〜コ
53,43,55,46,23,5,20,22,58,36,10,21,16,27,28,17,51,19,2,9, #サ〜ホ
25,52,3,49,41,14,57,7,8,11,45,15,26,13,42,4,44, #マ〜ン
109,82,121,40,74,106] # ()ホレ ラタ 段落 区切り点

m = MeCab.Tagger('-Oyomi')

# 原文の読込み(第1引数にファイル名を指定)
f = codecs.open(sys.argv[1], 'r', 'utf-8')
lines = f.readlines()
f.close
bunsho = '\n'.join(lines)
print('*** 原文 ***')
print(bunsho)

# 試験で出ない符号は削除,またはスペースに置換する
bunsho = re.sub('[&%#+*/@$=],', '', bunsho)
bunsho = re.sub('[-\'\",.()]', ' ', bunsho)

#複数の連続するスペースは一つにする
bunsho = re.sub(r"\s+", " ", bunsho)

yomi = m.parse(bunsho.encode('utf-8'))
print('*** MeCab 読み仮名取得***')
print(yomi)
#一般名詞のひらがなで残ったものをカタカナにする
yomi2 = jaconv.hira2hkata(unicode(yomi,'utf-8'))
print('*** jaconv ひらがなtoカタカナ***')
print(yomi2)

#A1A Breaker用にテキストファイルを出力
with open('./out.txt', mode='w') as f:
s_wrap_list = textwrap.wrap(yomi2,20)
f.write('\n'.join(s_wrap_list))

han = mojimoji.zen_to_han(yomi2.decode('utf-8'))
han = han[:len(han)-1]
print('*** mojimoji 全角to半角 ***')
print(han)

message=han
message_len=len(message)

#不要な変数を削除してメモリ解放
del lines
del bunsho
del yomi
del yomi2
del han
gc.collect()

A = 1 # 振幅
fs = 2800 #サンプリング周波数
f0 = 700 #基本周波数 CW信号のトーン
sec = 0.055 #秒 短点の基準

cw_tone = []
# アルファベットは全て大文字に変換
message = message.upper()

# 欧文/和文の判定
#regexp = re.compile(r'[^\x20-\x7E]')
regexp = re.compile(r'(?:\xEF\xBD[\xA1-\xBF]|\xEF\xBE[\x80-\x9F])')
result = regexp.search(message.encode('utf-8'))
if result != None:
wabun = 1 # 和文のときは1 欧文は0
else:
wabun = 0

print(wabun)

if wabun ==1:
message = u'<' + message + u'>' # 和文の場合はホレ,ラタを追加

#和文中のアルファベットは削除(総通の問題には出ないので)
message = re.sub(r'[a-zA-Z]+', r'', message)
message_len=len(message)

#和文中の試験に出ない符号は削除する
message = re.sub('[?!()]', '', message)
message_len=len(message)
print(message)

#sin波
def create_wave(A,f0,fs,t): #A:振幅,f0:基本周波数,fs:サンプリング周波数,再生時間[s]
#nポイント
point = np.arange(0,fs*t)
sin_wave =A* np.sin(2*np.pi*f0*point/fs)

sin_wave = [int(x * 32767.0) for x in sin_wave] #16bit符号付き整数に変換

return sin_wave

def create_cwfile(data): # wavファイルの作成と書き出し
#バイナリ化
binwave = struct.pack("h" * len(data), *data)

#サイン波をwavファイルとして書き出し
w = wave.Wave_write("cw.wav")
p = (1, 2, fs, len(binwave), 'NONE', 'not compressed')
w.setparams(p)
w.writeframes(binwave)
w.close()
print(len(data))

#mp3に変換
sound = pydub.AudioSegment.from_wav("cw.wav")
sound.export("cw.mp3", format="mp3")

def dot(): #短点
cw_tone.extend(create_wave(A,f0,fs,sec))

def dash(): #長点
cw_tone.extend(create_wave(A,f0,fs,3*sec))

def space(): #文字間隔
cw_tone.extend(create_wave(0,f0,fs,2*sec))

#文字コードを対応するモールス符号に変換
try:
while True:
for i in range(message_len):
char_code = ord(message[i])
print(char_code)
if char_code == 0x20: #space code
space()
elif wabun == 0: # 欧文
Mcode = Morse_Code[char_code - 48]
while Mcode != 1:
mark = Mcode & 1
if mark == 0:
dot()
else:
dash()
cw_tone.extend(create_wave(0,f0,fs,sec)) # 符号間のスペース(1点)
Mcode >>=1
cw_tone.extend(create_wave(0,f0,fs,2*sec)) # 文字間のスペース(3点分)
elif wabun == 1: # 和文
if char_code >= 48 and char_code <= 57: # 数字
Mcode = Morse_Code[char_code - 48]
elif char_code >= 65 and char_code <= 90: # 和文中のアルファベット
Mcode = Morse_Code[char_code - 48]
elif char_code == 60: #ホレ
Mcode =121
elif char_code == 62: #ラタ
Mcode =40
elif char_code == 40 or char_code == 65378: #下向きカッコ
Mcode =109
elif char_code == 41 or char_code == 65379: #上向きカッコ
Mcode =82
elif char_code == 65377: #段落(。)
Mcode =74
elif char_code == 10 or char_code == 13: #単なる改行は無視
Mcode =1
elif char_code == 65380 or char_code == 65381: #区切り点,中黒
Mcode =106
else:
Mcode = Wabun_Code[char_code - 65382]
while Mcode != 1:
mark = Mcode & 1
if mark == 0:
dot()
else:
dash()
cw_tone.extend(create_wave(0,f0,fs,sec)) # 符号間のスペース(1点)
Mcode >>=1
cw_tone.extend(create_wave(0,f0,fs,2*sec)) # 文字間のスペース(3点分)
create_cwfile(cw_tone)
break

except KeyboardInterrupt:
pass


出力されたmp3をffmpegで動画にしたもの。
原文はももたろうの前半部分。
posted by ソウヘイ at 20:34| Comment(0) | パソコン・インターネット

2018年09月07日

夏の工作 Raspberry Pi (ラズパイ3買い増しと留守番猫カメラ)

LEGOでラズパイケースを作るの続き

私がラズパイで楽しそうに遊んでいたら、かんなさんも気になったみたい。で、カメラモジュールを調べたりして、OSインストール済みのマイクロSDカードやケースがセットになったラズパイ3をポチっていた。

というわけで2台目のラズパイがやってきた。

まずやったことは、私のラズパイ2のホスト名の変更。デフォルトのままかんなさんのラズパイ3を接続したらホスト名がバッテイングしてしまう。

かんなさんはカメラモジュールを使って、留守番猫カメラを作りたいという。これは猫がくつろぎそうな場所にカメラを向けておいて、定期的に撮影し、留守中にスマホから猫たちの様子を確認できるというもの。

以前MacのFacetimeを自動応答にして同じことができたんだけど、OSがHigh Sierraになってから自動応答ができなくなってしまった。今のところ対策方法がわからない。

かんなさんもカメラモジュールのチュートリアルで写真を撮るところまではできたけれど、3日後に迫った新潟帰省にはちょっと間に合わない感じ。ということで私が出しゃばって突貫工事で留守番猫カメラを構築することになった。

FTPでファイルをレンタルサーバにアップロードするのは気圧測定のところでやったので、写真を撮ってアップロードするスクリプトをcronで定期的に実行するのはそんなに難しくなさそう。

ただ、写真を撮るタイミングにちょうどその場所に猫がいるかは運しだいになってしまう。画像ファイルをどんどん更新する方式だと、たまたま猫がいるときに写真を撮って、それがアップロードされたタイミングで外からスマホで見るようにしないと、次の更新で猫は画角からはずれてしまうかもしれない。

こういう場合、動体検知という技術を使う。検索したらラズパイでそういう便利なことができるmotionというライブラリをちゃんと親切な人が作っている。

これならカメラの画像に変化があった(猫が映った)ときにその写真を保存して、アップロードするということができる。

というようなことを調べて最低限のコードで動くようにした。デバッグで苦労したのはホームディレクトリからコマンドで動かす場合はファイルの場所を相対パスで指定しておけば良いんだけど、cronから実行する場合は絶対パスで指定する必要があること。考えてみれば当たり前なんだけど、慌てているとこんなことでつまずく。

どうにかこうにか動くようにして、新潟帰省中に動作を確認。思ったより猫が動かないのか、動体検知の閾値が高いのか、更新はめったにされなかったけれど、たまにカメラを横切る猫を捉えることができた。




あとは外出先からツイッター経由で指示して写真を撮らせたり、留守番猫カメラのオンオフができるともっと便利かも。さらに応用としてはカメラを可動にして動画をストリーミングするとか、赤外線発光LEDを使って外からエアコンの温度設定を変えたりとか。だいぶIoTらしくなってきた。



posted by ソウヘイ at 22:01| Comment(0) | パソコン・インターネット

2018年09月02日

夏の工作 Raspberry Pi (LEGOでケースを作る)

ツイッターのbotをつくるからの続き

ラズパイ本体だけなら市販のケースが使えるけれど,GPIOを使ってセンサをくっつけたりするとケースが合わなくなる。

検索するとLEGOでかわいいケースを作っている方がいて,これならプラスチックや金属を加工する必要もないし,センサを増やしたりして形が変わっても柔軟に対応できる。とかなんとか自分に言い訳をして,買ってしまった。




ラズパイ本体はタダみたいな値段で譲っていただいたけれど,なんか沼にハマっているような気がしないでもない。LEGOは子供の頃好きで,でもけっこうお高いのでそんなに大きなものを買うこともできず,そんなことを思い出しながら触るのは楽しい。ただ黄色いボックスにはフィギュア類が入っていなかった。これはちょっと寂しい。あのフィギュアが好きなのに。




たくさんパーツの入っている黄色BOXだけど,広い面積をカバーする平面パーツが少ない。なので市販のラズパイケースのような単純な直方体状の形を作るのは意外と大変。

最初に温湿度・気圧センサをラズパイ本体の上部に配置するような形で作ったら,温度計測値が本体の熱(けっこう熱くなる)の影響を受けてしまった。そこでこのような本体とセンサを分離するような形状としている。




posted by ソウヘイ at 18:19| Comment(0) | パソコン・インターネット

2018年08月26日

夏の工作 Raspberry Pi (ツイッターのbotをつくる)

温度湿度と気圧を測るからの続き。

ツイッターに自動で投稿するロボット(bot)は単なるソフトウェアなので、何もラズパイでつくる必要はない。ただラズパイと連携することで周辺のセンシング情報を取り込んでツイートしたり、外からのメンションに反応して機器を動かすとか、そういう使い方が考えられる。

まずはbot用にツイッターアカウントを用意する必要があるんだけど、これが結構大変だった。自分がアカウントを作った当時は認証もザルで、一人で複数アカウントを持つのも簡単だったように思う。それが今ではスマホの電話番号が必須になっていて、非常に面倒くさい。なんとか電話番号の入力なしでアカウントを作りたかったけれど、ちょっとやそっとでは無理そうだったので、仕方なく番号を入力した。そのうち既存ユーザーも番号の入力が必須になるかもしれない。そうなったら潮時かな。




キットに付いてきた温湿度センサと気圧センサでだいたい感じは掴めたので,東京出張の帰りに秋葉原に寄って,ひとつで温湿度気圧が測れるモジュール(BME280センサ使用)を買ってきた。






I2Cで取り込んだセンサの計測値をMySQLのデータベースに格納し,Rでグラフ化したものをFTPでレンタルサーバにアップ。そして最新の計測データと画像のURLをつぶやくツイッターのbotを作る。

ツイッターのbotはtwythonというライブラリを使わせてもらい,サンプルコードをちょこちょこと改造して完成。cronというコマンドを定期的に実行するLinuxの機能を利用して,30分ごととか1時間ごとにbotを実行するようにした。




これで外出中でも猫が留守番している部屋の環境情報がツイッター経由で取得できるようになった。


posted by ソウヘイ at 18:59| Comment(0) | パソコン・インターネット