2017年10月24日火曜日

vSphereのREST APIを使ってみる

今回はvSphereに6.5からRESTful APIが実装されていたようなので使ってみようと思います。Postmanという試験用のツールから使ってみて使い勝手を把握したら、Pythonから呼び出してみます。RESTなので、別にほかの言語でも構わないですが今更Perlという感じでもないので・・・。
なお、Postmanについては、下記のドキュメントを参考にさせていただきました。あくまで試験用のツールでパスワードなども平文で表示されてしまいますので、試験用途以外では使えないと思います。
Getting Started with the vSphere Automation SDK for REST

今回、使用した環境は下記のとおりです。

  • vCenter Server Appliance 6.5
  • Postman v5.3.1
  • Python 3.5

1 vSphereにユーザを追加します。

vSphereに管理者権限でログインします。
[メニュー]から[管理]を選択し、[ユーザおよびグループ]を選択します。
[+Add]アイコンをクリックし[ユーザ名]、[パスワード]を入力し[OK]をクリックします。

[グローバル権限]をクリックし、[+]アイコンをクリックします。
ユーザに先ほど作成した"test_user"を入力し、"システム管理者"の権限を割り当て[子へ伝達]のチェックを入れ[OK]をクリックします。
# 運用環境では必要なアクセス権に制限した方がいいと思います。


2 Postmanをインストールし起動します。

アカウントを作るように求められますので、アカウントを作成します。
Googleのアカウントも使うことができますので、好きな方を選択します。

3 証明書のエラーチェックを外します。

vCenterは自己署名証明書ですので証明書のチェックをしないように設定します。証明書を買っている場合や組織で用意したものを使用している場合は、下記の手順は不要です。
[File]から[Settings]を選択します。
[General]タブで"SSL certificate verification"を"OFF"に変更します。


4 gitからサンプルをcloneします。

git clone https://github.com/vmware/vsphere-automation-sdk-rest


5 cloneしたサンプルをPostmanにインポートします。

PostmanのGUIから[Import]をクリックし、[Choose files]をクリックします。

.\vsphere-automation-sdk-rest\samples\postman
上記のフォルダから下記4項目を選択し、[開く]をクリックします。
  • vSphere-Automation-Rest-API-Appliance-Resources.postman.json
  • vSphere-Automation-Rest-API-Resources.postman.json
  • vSphere-Automation-Rest-API-Samples.postman.json
  • vSphere-Automation-REST-resources-for-Content-Library.postman_collection.json


6 環境変数を定義します。

右上のアイコンから、[設定]アイコンをクリックし[Manage Environments]を選択します。
[Add]をクリックし、下記の内容を入力し[Add]をクリックし"X"で閉じます。
名前 test_env
vc vCenterのIPもしくはFQDN
user test_user@vsphere.local
password 試験ですので適当に
userをlocalに作成した場合は、@vsphere.localをつけ忘れないようにしてください。

右上のNo Enviromentをクリックして、"test_env"を選択します。


7 ログイン

先程インポートしたPostmanの[Collection]から[vSphere Automation REST Resources]を選択し
[Authentication] - [Login]を選択します。
TYPEに"Basic Auth"を選択し[Send]ボタンをクリックします。
値としてvalueが返ってくればOKです。

{
    "value": "6c83725d0b690a737d97805ce1d05d80"
}


8 既存のVMの一覧を取得

[VM]から"List"を選択し、[Send]をクリックします。
既存で存在する、VMの一覧が表示されるはずです。

9 VMの作成

[VM]から"Create with defaults"を選択し、[Send]をクリックします。
下記のエラーが発生し、VMは作成されません。

"default_message": "Resource pool with identifier 'resgroup-9' does not exist.",
"id": "com.vmware.api.vcenter.vm.resourcepool_not_found"

テンプレートに記載されている"resgroup-9"は実環境にないとエラーが表示されているようです。[BODY]タブの下記の項目を実環境に合わせて設定することで、エラーが発生しなくなります。

"datastore": "datastore-12",
"folder": "group-v7",
"resource_pool": "resgroup-9"

datastoreは[Collection]から[Datastores]にある[List]を実行することで取得可能です。
folderは[Collection]から[Folders]にある[List]を実行することで取得可能です。
resource_poolは[Collection]から[Resource Pool]にある[List]を実行することで取得可能です。

試験環境ではdatastore-25、group-v49、resgroup-10でしたので、この値を使うことにします。
[VM]の"Create with defaults"に戻り、Bodyの部分の下記の通り書き換えます。
{
    "spec": {
        "guest_OS": "RHEL_7_64",
        "placement" : {
            "datastore": "datastore-25",
            "folder": "group-v49",
            "resource_pool": "resgroup-10"
        }
    }
}

書き換えが完了したら、[Send]をクリックします。
下記のように、作成されたVMの番号が返ってきます。
{
    "value": "vm-73"
}


10.Pythonからの呼び出し

さて、Postmanで実行できることがわかったので、Pythonから適当に呼び出してみます。
nm0からnm9と名前の付いた10個の仮想マシンを作成します。
取り敢えずRESTの試験のため適当に作りましたので、実環境に適用する際はパスワードを暗号化して別に持つなど、コードを適切に書き換えてください。

import requests


def connect(vc, user, passwd):
    """
    vCenterに接続してSessionオブジェクトを返します。
    :param vc:      vCenterのFQDNかIPアドレス
    :param user:    ユーザ
    :param passwd:  パスワード
    :return:        Sessionオブジェクト
    """
    url = "https://{0}/rest/com/vmware/cis/session".format(vc)
    sess = requests.Session()
    sess.auth = (user, passwd)
    sess.verify = False
    res = sess.post(url)
    if res.status_code != 200:
        raise Exception(res.content)
    return sess


class VmCreator:
    """
    vm作ります!
    """
    def __init__(self, vc, session, datastore, folder, resource_pool):
        """
        :param vc:              vCenterのFQDNかIPアドレス
        :param session:         Sessionオブジェクト
        :param datastore:       Datastore
        :param folder:          Floder
        :param resource_pool:   Resource Pool
        """
        self._server = vc
        self._url = "https://{0}/rest/vcenter/vm".format(self._server)
        self._session = session
        placement = {
            "datastore": datastore,
            "resource_pool": resource_pool,
            "folder": folder
        }
        self._data = {"placement": placement}

    def set_name(self, value):
        self._data["name"] = value

    def set_guest_os(self, value):
        self._data["guest_OS"] = value

    def set_memory(self, size, hot_add=False):
        memory = {
            "size_MiB": size,
            "hot_add_enabled": hot_add
        }
        self._data["memory"] = memory

    def get_data(self):
        return self._data

    def set_cpu(self, socket, core, hotadd=False, hotremove=False):
        cpu = {
            "count": socket,
            "cores_per_socket": core,
            "hot_add_enabled": hotadd,
            "hot_remove_enabled": hotremove
        }
        self._data["cpu"] = cpu

    def add_disk(self, capacity):
        disk = {"new_vmdk": {"capacity": capacity}}
        if "disks" not in self._data:
            self._data["disks"] = []
        self._data["disks"].append(disk)

    def add_cdrom(self, path):
        cdrom = {
            "type": "IDE",
            "backing": {
                "iso_file": path,
                "type": "ISO_FILE"
            }
        }
        if "cdroms" not in self._data:
            self._data["cdroms"] = []
        self._data["cdroms"].append(cdrom)

    def create(self):
        param = {"spec": self._data}
        res = self._session.post(self._url, json=param)
        if res.status_code != 200:
            raise Exception(res.content)
        return res.content


vcs = "xxx.xxx.xxx.xxx"                         # vCenterServerのIPかFQDN
us = "xxxxxxxxx@vsphere.local"                  # user
ps = "XXXXXXXXXXXXXXXX"                         # password

s = connect(vcs, us, ps)
v = VmCreator(vcs, s, "datastore-XX", "group-vXX", "resgroup-XX")
v.set_guest_os("RHEL_7_64")
v.set_cpu(2, 2)                                 # Socket, Core
v.set_memory(4096, True)                        # 4096MByte
v.add_disk(10*1024*1024*1024)                   # 10GByte
v.add_disk(1024*1024*1024)                      # 1GByte
v.add_cdrom("[datastore1-2] image/CentOS-7-x86_64.iso")

# nm0からnm9までの仮想マシンを作成します。
for i in range(0, 10):
    v.set_name("nm{0}".format(i))               # vm name
    v.create()



無事10個の仮想マシンを作成できることが確認できました。
どんなパラメータ投げてるかは、postのparamをjson.dumpsしてみればわかると思います。

vSphereは元々APIが豊富でしたが、利用にはPerlを入れたりライブラリを入れたりでいろいろと準備が必要でした。RESTで書けるようになることでAPIの利用の敷居が低くなり、色々はかどりそうな気がします。

2017年10月15日日曜日

測距センサー(HC-SR04)とRaspberry Piをつないでみる

最近週末は安物センサーと Raspberry pi で遊んでいます。

今回は距離を測る HC-SR04 というセンサーを Raspberry pi から使ってみます。このセンサーは一時期センサー自体が固まる不良ロッドが広く流通した影響かとても安く出回っています。きちんと動作するものが欲しければ、当時リコール対応をした秋月電子やスイッチサイエンスなど信頼のおけるところから入手してください。

ちなみに私はAmazonの怪しい販売店から150円以下で買いました。

センサーの仕様と外観

見た目は下記のような感じです。

表面


裏面



仕様は下記のような感じで、分解能が0.3cmと割とおおざっぱです・・・。
動作電圧 DC5V
待機電流 2mA以下
有効角度 15°以下
測距距離 2cm~450cm
分解能 0.3cm


配線

ピンは下記のような感じで配線します。GPIOは余っている場所ならどこでもいいです。今回はGPIO26とGPIO19を使いました。
HC-SR04 Raspberry Pi 3
Trig(Output) 37 pin (GPIO26)
Echo(Input) 35 pin (GPIO19)
Ground 06 pin (GND)


ブレッドボード

下図のように配線しました。センサーのEchoからは5Vで入力がありますが、Raspberry pi側の入力は最大3.3Vですので、抵抗は入れなくても動くと記載されているブログもありましたが念のため抵抗を入れてあります。


測距方法の概要

概要としては下記のような手順です。
  1. Trigに10u秒以上をHIGHにすると、測距が開始されます。
  2. EchoがLOWであることを確認します。
  3. 音波が対象物に当たって跳ね返ってくるまでの時間、EchoがHIGHになり、その後EchoがLOWになります。
  4. HIGHになっていた時間と音速を掛けて、往復の距離が出ますので2で割ることで片道の距離を求めます。
下記のような図をイメージしていただければよろしいかと思います。

実装

1. はTrigをHIGHにして10usecの間SleepしたあとLOWにすれば計測が始まります。

2. と 3. の部分については、ArduinoにpulseInというC言語の関数がありますので、このソースを参考にPythonに移植してみます。

pulseIn(pin, value, timeout)

となっていますので、引数もこれを模倣することにします。

4. の音速についてはWikipediaに近似式が「1気圧の乾燥空気では 331.5 + 0.61t」と書いてありましたので、これを使うこととします。ちなみにtは温度です。

温度は固定で入れるか、温度センサーがついていれば、その値を入れてみてもいいかもしれません。
# そもそも分解能が0.3cmと結構おおざっぱなので固定15℃で十分かも

コード

pulse_inの泥臭さ・・・。まぁ、サイクル単位でコード書けるArduinoと比較してしまうとこんなもんかなという感じで、PythonだとArduinoのようなリアルタイムな感じのコードは難しいですね。
hc_sr04_test.py
import RPi.GPIO as GPIO
import time


def pulse_in(pin, value=GPIO.HIGH, timeout=1.0):
    """
    ピンに入力されるパルスを検出します。
    valueをHIGHに指定した場合、pulse_in関数は入力がHIGHに変わると同時に時間の計測を始め、
    またLOWに戻るまでの時間(つまりパルスの長さ)をマイクロ秒単位(*1)で返します。
    タイムアウトを指定した場合は、その時間を超えた時点で0を返します。
    *1 pythonの場合はtimeパッケージの仕様により実装依存ですが、概ねnanosecで返ると思います。
    :param pin: ピン番号、またはGPIO 番号(GPIO.setmodeに依存。)
    :param value: パルスの種類(GPIO.HIGH か GPIO.LOW。default:GPIO.HIGH)
    :param timeout: タイムアウト(default:1sec)
    :return: パルスの長さ(秒)タイムアウト時は0
    """
    start_time = time.time()
    not_value = (not value)

    # 前のパルスが終了するのを待つ
    while GPIO.input(pin) == value:
        if time.time() - start_time > timeout:
            return 0

    # パルスが始まるのを待つ
    while GPIO.input(pin) == not_value:
        if time.time() - start_time > timeout:
            return 0

    # パルス開始時刻を記録
    start = time.time()

    # パルスが終了するのを待つ
    while GPIO.input(pin) == value:
        if time.time() - start_time > timeout:
            return 0

    # パルス終了時刻を記録
    end = time.time()

    return end - start


def init_sensors(trig, echo, mode=GPIO.BCM):
    """
    初期化します
    :param trig: Trigger用ピン番号、またはGPIO 番号
    :param echo: Echo用ピン番号、またはGPIO 番号
    :param mode: GPIO.BCM、または GPIO.BOARD (default:GPIO.BCM)
    :return: なし
    """
    GPIO.cleanup()
    GPIO.setmode(mode)
    GPIO.setup(trig, GPIO.OUT)
    GPIO.setup(echo, GPIO.IN)


def get_distance(trig, echo, temp=15):
    """
    距離を取得します。取得に失敗した場合は0を返します。
    :param trig: Trigger用ピン番号、またはGPIO 番号(GPIO.setmodeに依存。)(GPIO.OUT)
    :param echo: Echo用ピン番号、またはGPIO 番号(GPIO.setmodeに依存。)(GPIO.IN)
    :param temp: 取得可能であれば温度(default:15℃)
    :return: 距離(cm)タイムアウト時は 0
    """

    # 出力を初期化
    GPIO.output(trig, GPIO.LOW)
    time.sleep(0.3)
    # 出力(10us以上待つ)
    GPIO.output(trig, GPIO.HIGH)
    time.sleep(0.000011)
    # 出力停止
    GPIO.output(trig, GPIO.LOW)

    # echo からパルスを取得
    dur = pulse_in(echo, GPIO.HIGH, 1.0)

    # ( パルス時間 x 331.50 + 0.61 * 温度 ) x (単位をcmに変換) x 往復
    # return dur * (331.50 + 0.61 * temp) * 100 / 2
    return dur * (331.50 + 0.61 * temp) * 50


if __name__ == "__main__":

    GPIO_TRIG = 26
    GPIO_ECHO = 19

    init_sensors(GPIO_TRIG, GPIO_ECHO)
    while True:
        print("距離:{0} cm".format(get_distance(GPIO_TRIG, GPIO_ECHO)))
        time.sleep(2)

実測!

最後に実測してみます。
実行は、sudoした上で実行してください。
sudo python3 hc_sr04_test.py

終了はCtrl + Cで。
15cmくらいのところに障害物を置いて距離を測ってみたところ下記のような感じでした。値をそのまま使ってしまうと結構ばらつきますね。もともと分解能が0.3cmと記載されていましたが、それ以上にひどい結果です。もう少し正確な値が必要であれば、前回との差分や平均をとるようなことも考えないといけないかもしれません。
距離:13.887083530426025 cm
距離:15.341055393218994 cm
距離:15.258443355560303 cm
距離:15.250182151794434 cm
距離:15.179961919784546 cm
距離:15.22952914237976 cm
距離:15.390622615814209 cm
距離:15.324532985687256 cm
距離:15.353447198867798 cm
距離:15.266704559326172 cm
距離:15.279096364974976 cm
距離:14.936256408691406 cm


1mの場所に物を置いた場合。
距離:101.06756687164307 cm
距離:99.9605655670166 cm
距離:100.59667825698853 cm
距離:101.3938844203949 cm
距離:90.7245397567749 cm
距離:100.15883445739746 cm
距離:100.46449899673462 cm
距離:100.47689080238342 cm
距離:100.50580501556396 cm
距離:100.8569061756134 cm
距離:100.62146186828613 cm

2017年10月10日火曜日

人感センサー(HC-SR501)とRaspberry Piをつないでみる



人感センサーのHC-SR501がAmazonで150円以下になっており、とても安いのでRaspberry Pi 3との接続を試してみました。なお、Amazonで1つ注文したのですが、なぜか2つ送付されてきました。1個75円!と思って喜んでいたのですが、一方はまともに動作しませんでしたので、もしかしたら歩留まりが悪いのかもしれません。

仕様

なんか、仕様が記載されているドキュメントによって、まちまちなので大体こんなもんだくらいに思っていたほうが良いのかもしれません。
動作電圧範囲 DC4.5V ~ 20V
ドレイン電流 65uA (50uA)
レベル出力 ハイレベル:3.3V、ローレベル出力:0 V
トリガモード Lノンリピートモード/ Hリピートモード(デフォルト)
遅延時間 0.5-18秒
ブロッキング時間 2.5秒
外形寸法 32mm x 24mm
検出範囲 100°(120°)
作業温度 -15° ~ 70°
誘導レンズサイズ 直径:23mm
一応このサイトに記載されている情報が公式の情報に当たると思うのですが、販売店が書いてる情報とはちょっと違いますね・・・。何が正しいんだろう・・・w

外観

HC-SR501を裏から見ると下記の図のような感じです。



検知出力保持時間

検出した後、検出した状態を保持する時間を調整するための半固定抵抗がついてますので、プラスドライバーで調整できます。やってみないとわからに様な気がしますので、必要に応じて変更してください。0.5秒~8秒のようです。(ドキュメントによって異なるようですが)

感度

こちらも、半固定抵抗がついていますのでドライバー等で調整します。最大で7mの範囲まで検知できるようです。

モード

ここは、固定されているものと固定されていないものがあるようです。
リピートに設定しておくと、検知し終わってから先ほどの保持時間の間は3.5vの入力がある状態となります。リピートを選択しておけば間違いないと思います。ノンリピートを選択した場合は検知し始めてから保持時間の間3.5vからの入力があると思われます。(試験してません)

配線

配線は右からPower、Output、Groundとなりますので、下記の通り配線します。
HC-SR501 Raspberry Pi 3
Power 02 pin (5V)
Output 29 pin (GPIO5)
Ground 06 pin (GND)

下図のような感じで配線すればOKです。(*上から見ています)



*配線中はRaspberry Piは通電していない状態でやってください。

あとは、下記のようなプログラムで検知したかどうかを判定します。
人感センサーが動きを検出すると、「動いた」と表示されるだけのシンプルなプログラムですw

hc_sr501_test.py
import sys
import time
import RPi.GPIO as GPIO

SLEEP_TIME = 1
SENSOR_GPIO = 5

GPIO.cleanup()
GPIO.setmode(GPIO.BCM)
GPIO.setup(SENSOR_GPIO, GPIO.IN)

while True:
  if GPIO.input(SENSOR_GPIO) == GPIO.HIGH:
    sys.stdout.write("\r動いた  ")
  else:
    sys.stdout.write("\r動いてない")

  time.sleep(SLEEP_TIME)

実行は、sudoした上で実行してください。
sudo python3 hc_sr501_test.py

終了はCtrl + Cで。

実行してみた感じだと、それほど検出精度が高いようには感じられないですw
まぁ、使えなくはないかなといった感じ。

このセンサーは、動きを検知しているようですので、止まっていると検知できません。

2017年10月4日水曜日

Raspberry Pi 3にUSBスピーカーをつなぐ

今回の環境は下記のとおりです。
  • Raspberry Pi 3
  • Raspbian GNU/Linux 9.1 (stretch)
また、今回登場するスピーカーは、サンワサプライのMM-SPU8BKというスピーカーです。amazonで900円でした。(安い!)
値段の割には見た目が良く評価もそこそこなので、例え繋がらなくてもまぁいいかと思って、ダメもとで買ってみました。



デフォルトだと、HDMIに接続されたテレビから音が出てしまうので、
何とかして、USB接続のスピーカーから音を出してみます。

まずは、aplay -lでデバイスの状態を確認します。

カード 0: ALSA [bcm2835 ALSA], デバイス 0: bcm2835 ALSA [bcm2835 ALSA]
  サブデバイス: 8/8
  サブデバイス #0: subdevice #0
  サブデバイス #1: subdevice #1
  サブデバイス #2: subdevice #2
  サブデバイス #3: subdevice #3
  サブデバイス #4: subdevice #4
  サブデバイス #5: subdevice #5
  サブデバイス #6: subdevice #6
  サブデバイス #7: subdevice #7
カード 0: ALSA [bcm2835 ALSA], デバイス 1: bcm2835 ALSA [bcm2835 IEC958/HDMI]
  サブデバイス: 1/1
  サブデバイス #0: subdevice #0

次に、USBスピーカーを接続してからaplay -lを実行してみます。

**** ハードウェアデバイス PLAYBACK のリスト ****
カード 0: ALSA [bcm2835 ALSA], デバイス 0: bcm2835 ALSA [bcm2835 ALSA]
  サブデバイス: 8/8
  サブデバイス #0: subdevice #0
  サブデバイス #1: subdevice #1
  サブデバイス #2: subdevice #2
  サブデバイス #3: subdevice #3
  サブデバイス #4: subdevice #4
  サブデバイス #5: subdevice #5
  サブデバイス #6: subdevice #6
  サブデバイス #7: subdevice #7
カード 0: ALSA [bcm2835 ALSA], デバイス 1: bcm2835 ALSA [bcm2835 IEC958/HDMI]
  サブデバイス: 1/1
  サブデバイス #0: subdevice #0
カード 1: MicroII [Audio Advantage MicroII], デバイス 0: USB Audio [USB Audio]
  サブデバイス: 1/1
  サブデバイス #0: subdevice #0

無事認識しているようで、新たに増えた部分が今回接続したスピーカーということになります。

カード番号が1
デバイス番号が0

なので、aplayの引数に-Dでカードとデバイスを指定してあげれば問題ない・・・と思ったんだけど・・・。

aplay -D hw:1,0 test.wav
再生中 WAVE 'test.wav' : Signed 16 bit Little Endian, レート 48000 Hz, モノラル
aplay: set_params:1305: チャネル数が使用不可能


はいエラー。しかもチャンネル数ってなによ。

この問題を解決するのには、結構時間がかかってしまったけど、下記のようにplugを付ければモノラル音声をステレオに変換して再生してくれるということらしい。要するに明示的に指定しないと、モノラル(1ch)からステレオ(2ch)には、変更してくれないということみたい。

aplay -D plughw:1,0 test.wav

これで、「aplay test.wav」 で HDMI から音声が出力され、「aplay -D plughw:1,0 test.wav」でUSBスピーカーから音声が出力される。

でも、やりたいことはこれじゃない。
デフォルトをスピーカー、指定した時だけHDMI側から再生したいのです。

色々調べると、まず出てくるのは、「/usr/share/alsa/alsa.conf」を直接書き換えるやり方。
大体下記の変更を行って再起動すれば反映されます!のようなドキュメント。
確かにこれをやればできる・・・。できるんだけど、asoundrcを無効にしてしまうのは、ちょっと違うんじゃないかなぁと・・・。

#         "~/.asoundrc"

defaults.ctl.card 1 # 録音するデバイスを指定
defaults.pcm.card 1 # 再生するデバイスを指定


本来は、再起動なんて必要なくてasoundrcを自動的に読み込んでデバイス選択してくれるものなんじゃないのか??

で、色々試したところ下記の内容をasoundrcに書くことで、希望通りデフォルトをスピーカー側にすることに成功しました。

pcm.!default {
    type plug
    slave.pcm "dmixer"
}

ctl.!default {
    type hw
    card 0
    device 0
}

pcm.dmixer {
    ipc_key 1024
    type dmix
    slave {
        pcm "hw:1,0"
    }
}

結構時間掛かっちゃったなー。
ということで一応メモ。

今回、参考にさせていただいたサイト。
この文章は、後で少し校正させていただきます。

2017年10月3日火曜日

OpenJTalkでLinuxに話してもらう@CentOS7

普通はRaspberry piなどでやるのかもしれませんが、たまたま手元にそれらしい環境がなかったので、なぜかVirtualBox上のCentOSから話してもらうテストです。意味があるかというとあまりないような気もしますが・・・

パッケージは用意されていないように思いますので、ソースからのインストールです。

今回利用した環境は下記の通り。
  • CentOS 7.4
  • Open JTalk 1.10
  • hts_engine API 1.10
まずは、yumで入るものを入れてしまいます。
yum install alsa-utils unzip wget gcc gcc-c++

hts_engineのインストール

hts_engineは、HMMベースの音声合成システムによって訓練されたHMMから音声波形を合成するソフトウェアだそうです。これがないとOpenJTalkをconfigureする際にエラーになります。
mkdir ~/download/
mkdir ~/src/
cd ~/download
wget https://jaist.dl.sourceforge.net/project/hts-engine/hts_engine%20API/hts_engine_API-1.10/hts_engine_API-1.10.tar.gz
cd ~/src/
tar xvzf ~/download/hts_engine_API-1.10.tar.gz
cd hts_engine_API-1.10/
./configure
make
make install


OpenJTalkのインストール

続いてOpenJTalkをインストールします。./configureの際にutf-8のオプションをつけ忘れないようにしてください。
cd ~/download
wget https://jaist.dl.sourceforge.net/project/open-jtalk/Open%20JTalk/open_jtalk-1.10/open_jtalk-1.10.tar.gz
cd ~/src
tar xzvf ~/download/open_jtalk-1.10.tar.gz
cd open_jtalk-1.10/
./configure --with-charset=UTF-8
make
make install

音響モデルの配置

htsvoiceは声の特徴を音素を基本単位としてモデル化した音響ファイルです。ただのファイルなので、どこに置いても良いのですが、/usr/local/shareに配置することが多いようです。
cd ~/download
wget https://jaist.dl.sourceforge.net/project/open-jtalk/HTS%20voice/hts_voice_nitech_jp_atr503_m001-1.05/hts_voice_nitech_jp_atr503_m001-1.05.tar.gz
cd ~/src
tar xzfv ~/download/hts_voice_nitech_jp_atr503_m001-1.05.tar.gz
mv hts_voice_nitech_jp_atr503_m001-1.05 /usr/local/share/hts_voice

辞書の配置

文字を音に変換するので分かち書きと辞書が必要になります。辞書はMeCabで使っていた辞書とほぼ同等のフォーマットになりますが、読み上げに特化した情報(アクセントなど)が付記された辞書になります。これも、どうせ実行時にファイルパスを指定することになりますので、どこに置いても良いです。
cd ~/download
wget http://downloads.sourceforge.net/open-jtalk/open_jtalk_dic_utf_8-1.10.tar.gz
cd ~/src
tar xzfv ~/download/open_jtalk_dic_utf_8-1.10.tar.gz
mv open_jtalk_dic_utf_8-1.10 /usr/local/share/open_jtalk_dic
ここまできたら、とりあえず試験的にしゃべらせてみます。
echo "これはテストです。"| open_jtalk -m /usr/local/share/hts_voice/nitech_jp_atr503_m001.htsvoice -ow output.wav -x /usr/local/share/open_jtalk_dic; aplay output.wav;rm -f output.wav
ちょっと太い男の声で「これはテストです」と話したはずです。
ネット上に音響ファイルはたくさん落ちていますので、気に入ったものを見つけてくるといいと思います。

ほかのサイトでもやっていますが、MMDAgentというソフトウェアの中にmeiという女の人の声が入っていますのでダウンロードして同じディレクトリに配置してみます。
cd ~/download
wget https://jaist.dl.sourceforge.net/project/mmdagent/MMDAgent_Example/MMDAgent_Example-1.7/MMDAgent_Example-1.7.zip
cd ~/src
unzip ../download/MMDAgent_Example-1.7.zip
mv MMDAgent_Example-1.7/Voice/mei/*.htsvoice /usr/local/share/hts_voice/.


配置したらテスト。先ほどから少しパラメータを変更してみました。パラメーターによって声の高さや読み上げるスピードなどを変更することができます。
echo "これはテストです。"| open_jtalk -m /usr/local/share/hts_voice/mei_happy.htsvoice -ow output.wav -x /usr/local/share/open_jtalk_dic/ -a 0.5 -fm 1.0 -jf 0.5 ; aplay output.wav;rm -f output.wav
なお音声自体が個人情報だからなのか、各音響モデルで利用条件がありますので、読み上げた音声を利用する場合は、条件をよく読んで利用したほうが良いと思います。

2017年10月1日日曜日

Elasticsearch + Kibana で解析基盤を作る - その1



何番煎じかわかりませんが、個人的にも会社でもデータ解析用の基盤として利用しているEFKの構成方法を記載してみます。
結構負荷がかかるので、CPUもメモリも良いものを積んだ方がいいと思います。

今回の概要構成は下記の通り。
  • CentOS7.4
  • Elasticsearch 5.6.2
  • Kibana-5.6.2

Elasticsearchのインストール


基本的に公式サイトからダウンロードしてインストールするだけです。
yum install java
yum install https://artifacts.elastic.co/downloads/elasticsearch/elasticsearch-5.6.1.rpm
日本語の検索を行うために、形態素解析のkuromojiを入れます。 プロクシがある環境では、コマンド実行前に「export ES_JAVA_OPTS="-Dhttp.proxyHost=[proxy_ip] -Dhttp.proxyPort=[proxy_port] -Dhttps.proxyHost=[proxy_ip] -Dhttps.proxyPort=[proxy_port]"」を実行して下さい
/usr/share/elasticsearch/bin/elasticsearch-plugin install analysis-kuromoji

つぎに設定ファイルを開き編集します。基本的に設定ファイルの最後に追記すればOKです。
ファイル: /etc/elasticsearch/elasticsearch.yml
network.host: [els_ipaddress]
http.port: 9200
http.cors.enabled: true
http.cors.allow-origin: "*"
http.cors.* の設定は後から入れるElasticsearch-headで cluster health: not connectedと表示され接続できないのを防ぐためです。[els_ipaddress]は実際のElasticsearchをインストールするサーバのIPと置き換えてください。
編集したらElasticsearchを起動します。
systemctl start elasticsearch
問題なく起動したかは下記のファイルにログが記録されますので、エラーなどが発生していないか確認しておいてください。
/var/log/elasticsearch/elasticsearch.log
続けて、firewalldを開きます。ポートはデフォルトでは9200番を使用するようです。
送信元はのちにインストールするKibanaのサーバと管理用の端末など直接アクセスするホストのIPアドレスを含めるようにしてください。
firewall-cmd --permanent --add-rich-rule='rule family="ipv4" source address="[kibana_ipaddress]/[mask]" port port="9200" protocol="tcp" log prefix="elasticsearch" level="info" accept'
firewall-cmd --reload

Kibanaのインストール

Kibanaも公式サイトからダウンロードしてインストールするだけです。
yum install https://artifacts.elastic.co/downloads/kibana/kibana-5.6.1-x86_64.rpm
設定ファイルも先ほどと同様、最後に下記の内容を追記してください。
/etc/kibana/kibana.yml
server.port: 5601
server.host: [kibana_ipaddress]
elasticsearch.url: "http://[els_ipaddress]:9200"
[els_ipaddress]は実際のElasticsearchをインストールするサーバのIPと置き換えてください。
[kibana_ipaddress]はKibanaをインストールしたサーバのIPと置き換えてください。

設定が完了したら、Daemonを起動しておきます。
systemctl start kibana

こちらもFWを開きます。ポートは一般的に5601番を使用するようです。こちらは、Apcheなどと連携しない場合はユーザが直接利用することになりますので、利用者向けに広く開ける必要があります。
firewall-cmd --permanent --add-rich-rule='rule family="ipv4" source address="[client]/[mask]" port port="5601" protocol="tcp" log prefix="kibana" level="info" accept'
firewall-cmd --reload

実際にブラウザからアクセスしてみます。
http://[kibana_ipaddress]:5601/app/kibana
この時点では下記のように「Configure an index pattern」という画面が出てくれば成功です。

x-pack

x-packがあると、Elasticsearchのパフォーマンスを監視することができます。x-packを導入するには無料とはいえライセンス登録が必要になるため、導入するかしないかは、この時点で決めておいた方がいいです。後から入れようとするといろいろ面倒なので・・・。
有償のライセンス費用を払えば、ユーザの権限管理もできますが、今回は無償のライセンスを発行して使います。

まず、Daemonが起動している場合は止めておきます。
systemctl stop elasticsearch
systemctl stop kibana
インストールはelasticsearch-pluginから行います。
/usr/share/elasticsearch/bin/elasticsearch-plugin install x-pack
インストールが終わったら、Elasticsearchのサービスを起動します。
systemctl start elasticsearch
インストールが無事終了したかは下記のコマンドで確認できます。
curl -XGET -u elastic:changeme 'http://[els_ipaddress]:9200/_xpack/license'
{
  "license" : {
    "status" : "active",
    "uid" : "XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX",
    "type" : "trial",
    "issue_date" : "2017-10-01T02:58:46.917Z",
    "issue_date_in_millis" : 1506826726917,
    "expiry_date" : "2017-10-31T02:58:46.917Z",
    "expiry_date_in_millis" : 1509418726917,
    "max_nodes" : 1000,
    "issued_to" : "elasticsearch",
    "issuer" : "elasticsearch",
    "start_date_in_millis" : -1
  }
}


次にkibanaにもx-packのプラグインを入れます。なお、インストールは下記の1行でインストール可能ですが、kibana-pluginは現時点ではProxyサーバに対応していませんので、Proxyのある環境の場合は、ダウンロードしたうえでインストールする必要があります。
/usr/share/kibana/bin/kibana-plugin install x-pack
Proxy環境下でインストールする場合は下記のような感じで、wgetした後にファイルを指定してインストールしてください。パスはフルパス指定じゃないと通らないようです。
wget https://artifacts.elastic.co/downloads/kibana-plugins/x-pack/x-pack-5.6.2.zip
/usr/share/kibana/bin/kibana-plugin install file:////local/path/x-pack-5.6.2.zip


インストールが終わったら、kibanaも起動します。
systemctl start kibana
Daemonが自動起動するように設定しておきます。
systemctl enable elasticsearch
systemctl enable kibana

実際にブラウザからアクセスしてみます。
http://[kibana_ipaddress]:5601/app/kibana
この時点では下記のようにログイン画面が出てくれば成功です。ユーザ名は「elastic」パスワードは「changeme」でログインできます。


ログインしたら、左のメニューから「Monitoring」を選択し「Your Trial license will expire on」に続く日付をクリックしてください。



今回は、1年ごとに更新が必要で機能が限定されているBasicLicenseを取得しますので「Get Basic」をクリックしてください。

メールアドレス宛にライセンスキーへのリンクが届きますので、ユーザ名、メールアドレス等を入力してください。


ライセンスキーを取得したら、名前を付けてサーバ上に保存して下さい。ここでは、「~/x-pack-license.txt」として保存をしたものとして進めます。
ライセンスキーの登録は下記のコマンドで実行できます。
curl -XPUT -u elastic:changeme 'http://[els_ipaddress]:9200/_xpack/license?acknowledge=true' -H "Content-Type: application/json" -d @x-pack-lisence.txt
無事登録できたかは下記のコマンドで確認できますので、確認しておいてください。
curl -XGET 'http://[els_ipaddress]:9200/_xpack/license'
下記のような内容が応答として帰ってくれば、完了です。
{
  "license" : {
    "status" : "active",
    "uid" : "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
    "type" : "basic",
    "issue_date" : "2017-10-02T00:00:00.000Z",
    "issue_date_in_millis" : 1506902400000,
    "expiry_date" : "2018-10-02T23:59:59.999Z",
    "expiry_date_in_millis" : 1538524799999,
    "max_nodes" : 100,
    "issued_to" : "先ほど登録した名前 (会社名)",
    "issuer" : "Web Form",
    "start_date_in_millis" : 1506902400000
  }
}