作業中のメモ

よく「計算機」を使って作業をする.知らなかったことを中心にまとめるつもり.

音声認識による赤外線機器の操作 その 1【準備編】

どうも,筆者です.

先日,新たに Raspberry Pi を購入した.今回から,この Raspberry Pi を用いて音声認識を用いた赤外線操作の実行方法について述べる.

まずは,音声認識ツールの導入方法と簡単な動作確認方法について解説する.導入方法は,こちらのサイトを参考にさせていただいた.

http://www.feijoa.jp/laboratory/raspberrypi/julius442/

準備

環境構築と構築状況の確認のために必要なものを以下に示す.

  • Raspberry Pi 3 Model B
  • Micro SD
  • 5V/2.5A の電源
  • 赤外線が送受信可能なモジュール
  • マイク

今回,上の 3 つは以下のセットを利用した.

ABOX Raspberry Pi3 Model B ボード&専用ケースセット ラズベリーパイ 3 モデル B 32GB(class 10) 豊富な付属品 日本語説明書 ブラックケース【本体+コンプリートスターターキット】

Amazon.co.jp: ABOX Raspberry Pi3 Model B ボード&専用ケースセット ラズベリーパイ 3 モデル B 32GB(class 10) 豊富な付属品 日本語説明書 ブラックケース【本体+コンプリートスターターキット】 : パソコン・周辺機器

4 つ目の赤外線モジュールに関して,多くのサイトでは赤外線受信モジュールと赤外線 LED を利用している.筆者も以前作成したときに同様のものを利用したが,赤外線 LED の指向性が強くうまく動作しなかった. 今回は,音声認識専用の Raspberry Pi にする予定であり,赤外線周りで不具合を出したくないため,以下のものを購入した.

ADRSIR ラズベリー・パイ専用 学習リモコン基板

https://www.amazon.co.jp/Bit-Trade-One-ADRSIR-%E3%83%A9%E3%82%BA%E3%83%99%E3%83%AA%E3%83%BC%E3%83%BB%E3%83%91%E3%82%A4%E5%B0%82%E7%94%A8-%E5%AD%A6%E7%BF%92%E3%83%AA%E3%83%A2%E3%82%B3%E3%83%B3%E5%9F%BA%E6%9D%BF/dp/B077ZNDW79/ref=sr_1_1?s=computers&ie=UTF8&qid=1527394279&sr=1-1&keywords=adrsir

ネットだと 5,000 円程度するが,大須では 4,000 円程度で購入できた.

また,マイクは,以前購入した以下のものを利用した.

iBUFFALO マイクロフォン USB音源 ブラック BSHSM05BK

https://www.amazon.co.jp/gp/product/B001GNZROE/ref=oh_aui_detailpage_o07_s00?ie=UTF8&psc=1

以上をまとめると,

  • Raspberry Pi 3 Model B,Micro SD → スターターキット
  • 赤外線モジュール → ADRSIR
  • マイク → BSHSM05BK

となる.ここまでで,準備は終了です.次からセットアップを行っていく.

セットアップ

Raspberry Pi の初期設定

まずは,Raspberry Pi のセットアップからはじめる.ただ,これは過去の記事を参考にすれば問題なく進められるはずであるため割愛する.

参考

workspacememory.hatenablog.com

workspacememory.hatenablog.com

ただ,この時と比較し,OS のバージョンが変わっている.そのため,今回の OS のバージョンを以下に示す.

$ lsb_release -a
No LSB modules are available.
Distributor ID: Raspbian
Description:    Raspbian GNU/Linux 9.4 (stretch)
Release:    9.4
Codename:   stretch

音声認識ツールのセットアップ

次に,音声認識ツールの導入方法について説明する.音声認識ツールは様々なものがあるが,今回はフリーで利用できる「Julius」という音声認識ツールを利用する.

julius.osdn.jp

まずは,本体をダウンロードする.筆者は git の使い方を覚えてきたので,以下のコマンドで GitHub から本体をダウンロードする.

# Julius 本体のダウンロード
~ $ git clone https://github.com/julius-speech/julius.git

この後コンパイルをかけることになるが,筆者の環境では,動作後,マイクデバイスをうまく認識してくれなかったので,コンパイル時に alsa という指定をする.その際に必要になるライブラリをインストール後,コンパイルを行う.

Raspberry Piで音声認識 - Qiita

# sudo コマンドを利用して,ライブラリをインストール
~ $ sudo apt-get install libasound2-dev libesd0-dev libsndfile1-dev
# julius 本体のディレクトリに移動しコンパイル
pushd julius
~/julius $ ./configure --with-mictype=alsa # ここでライブラリが不足しているとエラーメッセージが表示される
~/julius $ make # コンパイル
~/julius $ sudo make install
popd

追加モジュールのインストール

上記の設定で Julius 本体のインストールが完了した.次に,Julius の実行キット(ディクテーションキットと記述文法音声認識実行キット)をインストールする.これは,既にコンパイル済みのものが配布されているため,それをダウンロードすることになる.これの追加モジュールをひとつのディレクトリ以下に格納する.

~ $ wget "https://ja.osdn.net/frs/redir.php?m=ymu&f=julius%2F60416%2Fdictation-kit-v4.3.1-linux.tgz" # かなりサイズがあるはず(うろ覚え)
~ $ tar zxvf dictation-kit-v4.3.1-linux.tgz
~ $ git clone https://github.com/julius-speech/grammar-kit.git
~ $ mkdir juliusKit
~ $ mv dictation-kit-v4.3.1-linux juliusKit/dictationKit_v4.3.1
~ $ mv grammar-kit juliusKit/grammarKit

この時のディレクトリ構造は以下のようになる.

~/juliusKit
   |--dictationKit_v4.3.1
   |--grammarKit

これで,必要な準備はできた.マイクのセットアップ後,音声認識が実行できるかを確認する.

マイクのセットアップ

実は,先程の設定後,実際に以下のコマンドで動作をさせたが,エラーが出力されうまく動作しなかった.

~/juliusKit $ cd dictationKit_v4.3.1
~/juliusKit/dictationKit_v4.3.1 $ julius -C main.jconf -C am-gmm.jconf -demo 
# === 以下のエラーが表示される ===
# ### read waveform input
# Stat: adin_oss: device name = /dev/dsp (application default)
# Error: adin_oss: failed to open /dev/dsp # もしかすると,「--with-mictype=alsa」を指定する前の実行ファイルを用いた結果かもしれない
# failed to begin input stream
# ==============================

そのため,マイクデバイスの設定を行う.以下のコマンドで優先度を確認する.

~ $ cat /proc/asound/modules ### 優先順調査
# 出力結果
 0 snd_bcm2835
 1 snd_usb_audio ### こっちを優先したい

優先度が低いことが分かる.設定ファイルを修正し,各種設定後再起動する.

# 【1】  /etc/modprobe.d/alsa-base.conf を修正する.ない場合は,新規作成する.
~ $ sudo vim /etc/modprobe.d/alsa-base.conf # エディタは自分が使いやすいものを使う
# 【2】 以下のように変更し,保存する
# === ここから ===
options snd slots=snd_usb_audio,snd_bcm2835
options snd_usb_audio index=0
options snd_bcm2835 index=1
# === ここまで ===
# 【3】 必要なコマンド,ライブラリをインストールする.その後再起動する.
~ $ sudo apt-get install alsa-utils sox libsox-fmt-all
~ $ sudo sh -c "echo snd-pcm >> /etc/modules"
~ $ sudo reboot

再起動後,デバイスのカード番号とデバイス番号を調べる.

~ $ arecord -l # カード番号とデバイス番号を調べる
### 出力例
### カード 0: Device [USB PnP Sound Device], デバイス 0: USB Audio [USB Audio]
###   サブデバイス: 1/1
###   サブデバイス #0: subdevice #0

~ $ amixer sset Mic 70 -c 0 # マイクの音量調節 (-c [カード番号])
~ $ arecord -D plughw:0,0 -d 10 -f cd test.wav # 録音してみる(plughw:[カード番号],[デバイス番号]、-d 10秒)
~ $ aplay -D plughw:1,0 test.wav # 再生してただしく録音できていることを確認する

ここまで実行できていれば,音声認識はできるはずである.次の節で動作確認を行う.

動作確認

以下のディレクトリに移動後,コマンドを実行し動作確認する.

~ $ cd juliusKit/dictationKit_v4.3.1
# 再掲:plughw:[カード番号],[デバイス番号]
~/juliusKit/dictationKit_v4.3.1 $ ALSADEV="plughw:0,0" julius -C main.jconf -C am-gmm.jconf -demo

実行後,しばらくしたら「<<< please speak >>>」という文字列が表示されていれば成功である.後は,マイクから音声を入力し認識出来ていれば良い.

ただ,大量の語彙の中から認識結果を出力するので,はっきり言って精度は悪い.次回は,認識用の辞書を作成し,これをもとにした認識結果を出力する方法を解説する.

Python で GUI を扱う

どうも,筆者です. 前回に引き続き,PythonGUI を利用する方法についてまとめていく. 前回は,Python Script を exe 形式に変換する方法をまとめたが,その時に利用したスクリプトでは,画面を作成していなかった.

今回は,ボタンやテキストを配置し,より GUI よりのものを作成する.

workspacememory.hatenablog.com

雛形の作成

今回も,PythonGUI プログラムを作成する際に tkinter を利用する.tkinter を用いた場合の雛形となるものを以下に示す.

import tkinter as tk

class AppClass(tk.Frame):
    def __init__(self, master=None):
        super().__init__(master)
        self.pack()
        self.createWidgets()

    def createWidgets(self):
         # ここに部品を配置する

def main():
    root = tk.Tk()
    root.title('title')        # タイトルの設定
    root.resizable(0, 0)   # ウィンドウのリサイズ禁止
    # インスタンスの生成
    app = AppClass(master=root)
    # ループ処理
    app.mainloop()

これに,肉付けを行い,画面を作成する.今回は,「ラベルの配置」,「テキストボックスの配置」,「ボタンの配置」を取り扱う.

ラベルの生成

HTML でいう「label」を配置する.tkinter では,以下のようにする.

self.label = tk.Label(self, text='sample')

テキストボックスの生成

HTML でいう一行テキストボックス「input type=text」を配置する.tkinter では,以下のようにする.

self.varEntry = tk.StringVar()
self.entry = tk.Entry(self, textvariable=self.varEntry)

追加したテキストボックスには,テキストボックスに値を設定したり,テキストボックスから値を取り出すことができる.そのやり取りをするための変数を用意する必要がある. 「self.varEntry」がデータの受け渡しに利用する変数である.値の設定・値の取得はそれぞれ以下のようにする.

# 値の設定
self.varEntry.set('hoge')
# 値の取得
data = self.varEntry.get()

ボタンの生成

HTML でいう「button」を配置する.tkinter では,以下のようにする.

self.button = tk.Button(self, text='button')

また,ボタンには押下時に関数を実行するよう,設定ができる.サンプルを以下に示す.

self.button = tk.Button(self, text='button', command=self.sampleFunc)

def sampleFunc(self):
    print('call funciton')

ただ,この記述では関数に引数を渡すことができない.引数を渡して実行するには,以下のようにする.

self.button = tk.Button(self, text='button', command=self.sampleFunc(1))

def sampleFunc(self, val):
    def innerFunc():
        print(val)
    return innerFunc

これでうまく処理できる理由は,以下のサイトにまとまっている.

memopy.hatenadiary.jp

各要素の配置

定義はできたが,このままでは,各要素をどのように配置するか設定されていない状態となる.各要素の配置は,以下のようにする.

# ラベル,テキストボックス,ボタンという順に配置する
self.label.grid(column=0, row=0)
self.entry.grid(column=1. row=0)
self.button.grid(column=2. row=0)

大雑把だが,今回使用する機能の紹介を終える.他にも様々な引数をとるが,完全に把握し切れていないため,分からない部分は都度調べることにする.

Python Script を実行形式(exe 形式)に変換

どうも,筆者です. 今回は,Python Script を Windows で動作する exe 形式に変換する手順を説明する.

動作環境

利用した OS,Python のバージョンを以下に示す.

pyinstaller のインストール

Python Script を exe 形式に変換するため,pyinstaller をインストールする.コマンドプロンプトに以下のコマンドを入力する.

python -m pip install pyinstaller

以下のコマンドを入力し,バージョンを確認しておく.筆者の環境では,3.3.1 となった.

pyinstaller -v

exe 形式に変換

今回は,以下のような Python Script を exe 形式に変換する.この Python Script は,動作確認用に作成したものである.ここでは,test.py という名前で保存した.

#!/usr/bin/env python
# -*- coding: shift-jis -*-

import tkinter
from tkinter import filedialog as dialog
from tkinter import messagebox as msgbox
import os, sys

# ディレクトリ名とファイル名に分割
def splitFilePath(fileName):
    dirName = os.path.dirname(fileName)
    baseName = os.path.basename(fileName)

    return dirName, baseName

# ファイルダイアログを開き、対象のファイルのパスを取得
def getTargetFileName():
    # 現在のディレクトリを取得
    currentDir = os.getcwd()
    # ダイアログを開き、フルパスを取得
    fileName = dialog.askopenfilename(title="Choose file", initialdir=currentDir)

    if fileName is None or fileName == '':
        baseName = ''
    else:
        # ファイル名のみ抽出
        _, baseName = splitFilePath(fileName)

    return baseName

# ファイルの存在確認と拡張子の確認
def chkFilePath(filePath):
    # 以下の 2 点を確認
    # 1.指定されたパスのファイルが存在するか
    # 2.対象とする拡張子か
    targetExtensionList = ['txt', 'csv'] # 対象とする拡張子のリスト

    if filePath == '':
        return False

    # ファイルの存在確認
    if not os.path.exists(filePath):
        # 存在しない場合
        outMsg = '以下のファイルが存在しません\n%s' % filePath
        msgbox.showinfo('Error', outMsg)
        return False
    else:
        # 拡張子の確認
        _, ext = os.path.splitext(filePath)

        if ext[1:] not in targetExtensionList:
            joinExt = ', '.join(map(lambda x: '.%s' % x, targetExtensionList))
            outMsg  = '対象の拡張子ではありません: %s\n対象の拡張子: %s' % (ext, joinExt)
            msgbox.showinfo('Error', outMsg)
            return False
        else:
            return True

def execution(inputFilePath):
    dirName, baseName = splitFilePath(inputFilePath)
    outputFilePath = os.path.join(dirName, 'out_' + baseName)

    with open(outputFilePath, 'w') as fout:
        for line in open(inputFilePath, 'r'):
            data = line.replace('\n', '')
            fout.write(data + '\n')

def main():
    argv = sys.argv
    argc = len(argv)
    useFilePath = ''

    # GUI の初期設定
    rootTk = tkinter.Tk()
    rootTk.withdraw()

    if argc < 2:
        useFilePath = getTargetFileName()
    else:
        useFilePath = argv[1]

    if chkFilePath(useFilePath):
        # 処理を実行
        useFullPath = os.path.abspath(useFilePath)
        execution(useFullPath)

if __name__ == '__main__':
    main()

Python Script を exe 形式に変換するには,コマンドプロンプトに以下のコマンドを入力する.

rem icon をつけない場合
pyinstaller --onefile --clean --noconsole test.py
rem icon をつける場合
rem 同一フォルダに test.ico を配置しておく必要がある.
pyinstaller --onefile --clean --noconsole --add-binary test.ico;. --icon=test.ico test.py

実行すると,以下のようなログが表示される.最後に successfully が表示されていれば変換完了である.なお,exe ファイルは dist フォルダ以下に生成される.

62 INFO: PyInstaller: 3.3.1
62 INFO: Python: 3.6.4
62 INFO: Platform: Windows-7-6.1.7601-SP1
62 INFO: wrote path\test.spec
62 INFO: UPX is not available.
62 INFO: Removing temporary files and cleaning cache in userPath\AppData\Roaming\pyinstaller
93 INFO: Extending PYTHONPATH with paths
['path\\test', 'path\\test']
93 INFO: checking Analysis
93 INFO: Building Analysis because out00-Analysis.toc is non existent
93 INFO: Initializing module dependency graph...
93 INFO: Initializing module graph hooks...
109 INFO: Analyzing base_library.zip ...
2683 INFO: running Analysis out00-Analysis.toc
2683 INFO: Adding Microsoft.Windows.Common-Controls to dependent assemblies of final executable
  required by userPath\AppData\Local\Programs\Python\Python36\python.exe
3229 INFO: Caching module hooks...
3229 INFO: Analyzing path\test.py
3369 INFO: Loading module hooks...
3369 INFO: Loading module hook "hook-encodings.py"...
3447 INFO: Loading module hook "hook-pydoc.py"...
3447 INFO: Loading module hook "hook-xml.py"...
3634 INFO: Loading module hook "hook-_tkinter.py"...
3759 INFO: checking Tree
3759 INFO: Building Tree because out00-Tree.toc is non existent
3759 INFO: Building Tree out00-Tree.toc
3822 INFO: checking Tree
3822 INFO: Building Tree because out01-Tree.toc is non existent
3822 INFO: Building Tree out01-Tree.toc
3853 INFO: Looking for ctypes DLLs
3853 INFO: Analyzing run-time hooks ...
3853 INFO: Including run-time hook 'pyi_rth__tkinter.py'
3853 INFO: Looking for dynamic libraries
4024 INFO: Looking for eggs
4024 INFO: Using Python library userPath\AppData\Local\Programs\Python\Python36\python36.dll
4024 INFO: Found binding redirects: 
[]
4024 INFO: Warnings written to path\build\test\warntest.txt
4056 INFO: Graph cross-reference written to path\build\test\xref-test.html
4087 INFO: checking PYZ
4087 INFO: Building PYZ because out00-PYZ.toc is non existent
4087 INFO: Building PYZ (ZlibArchive) path\build\test\out00-PYZ.pyz
4508 INFO: Building PYZ (ZlibArchive) path\build\test\out00-PYZ.pyz completed successfully.
4508 INFO: checking PKG
4508 INFO: Building PKG because out00-PKG.toc is non existent
4508 INFO: Building PKG (CArchive) out00-PKG.pkg
4804 INFO: Updating manifest in userPath\AppData\Roaming\pyinstaller\bincache00_py36_64bit\python36.dll
4804 INFO: Updating resource type 24 name 2 language 1033
5413 INFO: Updating manifest in userPath\AppData\Roaming\pyinstaller\bincache00_py36_64bit\_ssl.pyd
5413 INFO: Updating resource type 24 name 2 language 1033
5569 INFO: Updating manifest in userPath\AppData\Roaming\pyinstaller\bincache00_py36_64bit\unicodedata.pyd
5569 INFO: Updating resource type 24 name 2 language 1033
5584 INFO: Updating manifest in userPath\AppData\Roaming\pyinstaller\bincache00_py36_64bit\pyexpat.pyd
5584 INFO: Updating resource type 24 name 2 language 1033
5616 INFO: Updating manifest in userPath\AppData\Roaming\pyinstaller\bincache00_py36_64bit\_hashlib.pyd
5616 INFO: Updating resource type 24 name 2 language 1033
5662 INFO: Updating manifest in userPath\AppData\Roaming\pyinstaller\bincache00_py36_64bit\_bz2.pyd
5662 INFO: Updating resource type 24 name 2 language 1033
5678 INFO: Updating manifest in userPath\AppData\Roaming\pyinstaller\bincache00_py36_64bit\_lzma.pyd
5678 INFO: Updating resource type 24 name 2 language 1033
5694 INFO: Updating manifest in userPath\AppData\Roaming\pyinstaller\bincache00_py36_64bit\_socket.pyd
5694 INFO: Updating resource type 24 name 2 language 1033
6037 INFO: Updating manifest in userPath\AppData\Roaming\pyinstaller\bincache00_py36_64bit\select.pyd
6037 INFO: Updating resource type 24 name 2 language 1033
6084 INFO: Updating manifest in userPath\AppData\Roaming\pyinstaller\bincache00_py36_64bit\_tkinter.pyd
6084 INFO: Updating resource type 24 name 2 language 1033
6130 INFO: Updating manifest in userPath\AppData\Roaming\pyinstaller\bincache00_py36_64bit\tk86t.dll
6130 INFO: Updating resource type 24 name 1 language 1033
8814 INFO: Building PKG (CArchive) out00-PKG.pkg completed successfully.
8829 INFO: Bootloader userPath\AppData\Local\Programs\Python\Python36\lib\site-packages\PyInstaller\bootloader\Windows-64bit\runw.exe
8829 INFO: checking EXE
8829 INFO: Building EXE because out00-EXE.toc is non existent
8829 INFO: Building EXE from out00-EXE.toc
8845 INFO: SRCPATH [('test.ico', None)]
8845 INFO: Updating icons from ['test.ico'] to userPath\AppData\Local\Temp\tmp1k39lqf2
8845 INFO: Writing RT_GROUP_ICON 0 resource with 20 bytes
8845 INFO: Writing RT_ICON 1 resource with 68578 bytes
8860 INFO: Appending archive to EXE path\dist\test.exe
8892 INFO: Building EXE from out00-EXE.toc completed successfully.

Power Shell で Excel を操作する(Excel 起動,ファイルオープン,保存,ファイルクローズ)

どうも,筆者です.

たまには,無駄なプログラム(スクリプト)を作成してみようと思う.今回は,「対象とする Excel ファイルを Power Shell から起動し,その後保存して閉じる」というものを作成する.

今回は,以下のような手順で処理する.

  1. Excel アプリケーションを起動する.
  2. Excel ファイルを開く.
  3. Excel ファイルを保存し閉じる.
  4. Excel アプリケーションを終了する.
  5. 後処理

Excel アプリケーションの起動

Power Shell で Excel アプリケーションを起動するには,以下のようにする.

$excel = New-Object -ComObject Excel.Application
$excel.Visible = $false      # 画面上に表示させない
$excel.DisplayAlerts = $true # 警告メッセージは表示する

Excel ファイルを開く

続いて,Excel ファイルを開くには,以下のようにする.

# 現在のディレクトリの絶対パスを取得
$currentPath = (Convert-Path .)
# 対象の Excel ファイル名
$filename = "sample.xlsx"
$book = $excel.Workbooks.Open($currentPath + "/" + $filename)

Excel ファイルを保存し閉じる

# 上書き保存
$book.Save()
# ブックを閉じる
$excel.Workbooks.Close()

Excel アプリケーションを終了する

Excel アプリケーションを終了するには,以下のようにする.

$excel.Quit()

後処理

Excel アプリケーションを終了しただけでは,プロセスが残っている.これは,今回の Power Shell で利用した変数を OS が保持しているためである.

このため,変数を破棄する必要がある.ここでは,$book と $excel を変数として利用したため,以下のようにして破棄する.

[System.Runtime.Interopservices.Marshal]::ReleaseComObject($book)
[System.Runtime.Interopservices.Marshal]::ReleaseComObject($excel)

対象とする Excel ファイルのリストの取得

ある程度自動化したいため,対象とする Excel ファイルをリストにし,テキスト形式で保存する.このテキストファイルを読み込み順に処理する方法を考える.

例として,以下のようなテキストファイルを利用する.これを list.txt として保存する.

sample01.xlsx dummy
sample02.xlsx dummy
sample03.xlsx dummy
sample04.xlsx dummy
sample05.xlsx dummy
sample06.xlsx dummy
sample07.xlsx dummy
sample08.xlsx dummy
sample09.xlsx dummy
sample10.xlsx dummy

list.txt を Power Shell で読み込むには,以下のようにする.

$excelFileList = @(Get-Content -Path ($currentPath + "/list.txt") | %{$_.split(" ")[0]})

「Get-Content」コマンド(Linux の cat コマンド)でファイルの中身を出力する.出力データを 1 行ずつ読み込み,スペースで区切り,最初のデータを配列の要素として取り出す. ここで,「%」は foreach のエイリアスである.また「$_」には,読み込んだ 1 行のデータが格納されている.

作成したスクリプト

作成したスクリプトを以下に示す.

try {
    # 現在のディレクトリの絶対パスを取得
    $currentPath = (Convert-Path .)

    # Excelオブジェクト作成
    $excel = New-Object -ComObject Excel.Application
    $excel.Visible = $false      # 画面上に表示させない
    $excel.DisplayAlerts = $true # 警告メッセージは表示する

    # 対象ファイルの1列目を取り出す
    $excelFileList = @(Get-Content -Path ($currentPath + "/list.txt") | %{$_.split(" ")[0]})

    foreach($filename in $excelFileList) {
        # ファイル名の出力
        Write-Host $filename

        # 対象とするExcelファイル
        $book = $excel.Workbooks.Open($currentPath + "/" + $filename)

        # 上書き保存
        $book.Save()

        # ブックを閉じる
        $excel.Workbooks.Close()

        # 300ms待つ
        Start-Sleep -m 300
    }
    # Excelを閉じる
    $excel.Quit()
}
finally {
    # 変数の破棄
    [System.Runtime.Interopservices.Marshal]::ReleaseComObject($book)
    [System.Runtime.Interopservices.Marshal]::ReleaseComObject($excel)
}

追記1(2018/03/04)

Power Shell を起動する際に,実行権限を変更する必要があるため,以下のような bat ファイルを作成した.

@echo off

echo execute Power Shell
powershell -ExecutionPolicy RemoteSigned -file openClose_Excel.ps1

この bat ファイルを動かす場合は,引数を処理して,作業ディレクトリを指定する処理を加える必要がある.Power Shell で引数を取得する方法を以下に示す.

# 現在のディレクトリで初期化
Param ( $argv1 = "." )

# 引数を必須にする場合は,以下のようにする.
#Param ( [parameter(mandatory = $true)]$argv1 = "." )

try {
    # 現在のディレクトリの絶対パスを取得
    $currentPath = (Convert-Path $argv1)

    # 処理が続く
    ...
}

SoftEther を利用した VPN サーバの構築

どうも,筆者です.今回は,前回セットアップした Raspberry Pi 3 Model B を利用して,VPN サーバを立てた.何度か試してみた結果,ようやくうまくいったので,ここに記録しておく.

VPN に関しては以下のサイトを参考にした.

qiita.com

システムのアップデート

まずは,おなじみのシステムのアップデートを行う.

sudo apt-get update
sudo apt-get upgrade
sudo apt-get dist-upgrade

初期設定

システムのアップデートが完了したら,初期設定を行っていく.まずは,swap 領域を無効にするため,以下のコマンドを入力する.

sudo swapoff --all # swap off にする
sudo apt-get remove dphys-swapfile # swap 機能を無効にする.

次に,必要なエディタのインストールとその設定を行う.

sudo apt-get install -y vim # vim をインストール
sudo update-alternatives --config editor # sudo で使用するエディタの設定をする.ここでは vim を選択
sudo vim /etc/vim/vimrc # vim の設定ファイルを修正する
sudo ln -s /etc/vim/vimrc /root/.vimrc # 設定ファイルを root の .vimrc に反映
ln -s /etc/vim/vimrc ~/.vimrc # 同様に自分のローカルにも反映する

vim の設定ファイルに関しては,以前記事にしたので,そちらを参照のこと.

workspacememory.hatenablog.com

最後に,pi ユーザの変更とパスワードなしでの root 切り替えを無効にする設定を行う.これは,以下のサイトを参考にした.

jyn.jp

sudo useradd -M tmp # 一時的なユーザ tmp の作成
sudo gpasswd -a tmp sudo # sudo 権限を付与
sudo passwd tmp # パスワードの設定
exit # 一旦抜ける

### tmp ユーザでログインする ###
sudo usermod -l admin pi # pi ユーザのユーザ名を admin に変更
sudo usermod -d /home/admin -m admin # pi ユーザのホームディレクトリ名を admin に変更
sudo groupmod -n admin pi # pi ユーザのグループを admin に変更
exit # ログアウト

### admin ユーザでログインする ###
sudo userdel tmp # tmp ユーザを削除
sudo passwd admin # admin ユーザのパスワードを変更
sudo rm /etc/sudoers.d/010_pi-nopasswd # パスワードなしで root 権限を取得で気ないようにするため,指定のファイルを削除

これで,自分の中では,初期設定は完了である.早速,本題の VPN 環境を構築していく.

SoftEther を利用した VPN 環境の構築

ここでは,VPN サーバの環境構築を行う.構築手順は,以下のようになる.

  1. bridge アダプタを利用可能にするため,bridge-utils をインストールする.
  2. softether のデータを Web からダウンロードして,インストールする.
  3. vpnserver をサービスへ登録し,自動起動する.
  4. Windows 側で VPN Server の設定を行う.
  5. ネットワーク環境の設定を行う.ここか一番ハマッた部分である.
  6. ルータ側でアドレス変換の設定を行い,外部から VPN サーバにアクセスできるようにする.

この順に処理を進める.

bridge-utils のインストール

vim のインストールと同様に,インストールするだけである.

sudo apt-get install -y bridge-utils

SoftEther のインストール

以下のサイトから,SoftEther をダウンロードしてくる.

SoftEther ダウンロード センター

それぞれ,選択肢を以下のように設定する.

すべて選択すると,ダウンロード可能なファイルの一覧が表示される.今回は,

SoftEther VPN Server (Ver 4.24, Build 9651, beta) softether-vpnserver-v4.24-9651-beta-2017.10.23-linux-arm_eabi-32bit.tar.gz (5.40 MB)

をダウンロードした.wget コマンドを用いて,tar.gz ファイルをダウンロードする.

wget http://jp.softether-download.com/files/softether/v4.24-9651-beta-2017.10.23-tree/Linux/SoftEther_VPN_Server/32bit_-_ARM_EABI/softether-vpnserver-v4.24-9651-beta-2017.10.23-linux-arm_eabi-32bit.tar.gz

展開し,インストールを行う.

tar zxvf softether-vpnserver-v4.24-9651-beta-2017.10.23-linux-arm_eabi-32bit.tar.gz
pushd vpnserver/
echo -e "1\n1\n1\n" | make
popd
sudo mv vpnserver/ /usr/local/
pushd /usr/local/vpnserver/
sudo chmod 600 *
sudo chmod 700 vpncmd vpnserver
popd

これでインストールは完了である.

サービスへの登録・自動起動の設定

次に,vpnserver をサービスに登録する.好みのエディタで「/etc/systemd/system/vpnserver.service」を新規に開く.そして,以下を記述し,保存する.

sudo vim /etc/systemd/system/vpnserver.service

### 以下を /etc/systemd/system/vpnserver.service に記述し保存する ###
[Unit]
Description=SoftEther VPN Server
After=network.target network-online.target

[Service]
ExecStart=/usr/local/vpnserver/vpnserver start
ExecStop=/usr/local/vpnserver/vpnserver stop
Type=forking
RestartSec=3s

[Install]
WantedBy=multi-user.target

その後,以下のコマンドでサービスを有効にし,自動起動の設定もする.

sudo systemctl daemon-reload
sudo systemctl enable vpnserver.service
sudo systemctl start vpnserver.service

Windows 側での設定

再び,以下のサイトから Windows 用のマネージャファイルをダウンロードし,インストールする.CPU が AMD の場合はよく分からない. SoftEther ダウンロード センター

それぞれ,選択肢を以下のように設定する.

マネージャを開き,順に設定する.この辺りの設定は,以下のサイトに図付で説明があるので,そちらを参考にする.

www.jifu-labo.net

  1. 新しい接続設定から,接続先を設定する.ここでは,DHCP で割り当てられた IP アドレス 192.168.33.31 を設定した.
  2. 管理者パスワードを設定する.
  3. リモートアクセス VPN サーバーを選択する.
  4. 仮想 HUB 名を決める.ここでは,vpn とした.
  5. DDNS 名を決める.重複しないようにする必要があるため,自分が使いたいものが既に設定されている場合,使えない.
  6. L2TP サーバ機能を有効にする」にチェックを入れる.IPsec 事前共有鍵も設定する.
  7. VPN Azure を有効にする」にチェックを入れる.
  8. ユーザーを作成する.ログイン時に使用する.
  9. ローカルブリッジの設定は,ここでは行わない.(ブリッジ接続する~のまま置いておく)

すべて設定すると,VPN サーバ管理画面が表示される.次に,以下の手順で,tap デバイスの設定を行う.

  1. VPN サーバの管理画面において,左下にある「ローカルブリッジ設定」を選択する.
  2. 「仮想 HUB」として,先ほど決めた「vpn」を選択する.
  3. 「新しい tap デバイスとのブリッジ接続」を選択する.
  4. 新しい tap デバイス名に任意の名前を入力する.ここでは,softether とした.
  5. 「ローカルブリッジを追加」を選択する.

これで,Windows 側の設定は完了である.

ネットワーク環境の設定

一番ハマッたネットワークの設定である.最近の Raspberry Pi では,「/etc/dhcpcd.conf」にネットワークインタフェースの設定を記述するらしいが,ここでは,「/etc/network/interfaces」に記述する. 固定 IP を振りたかったが,どうしてもうまくできなかったため,dhcp として処理した.「/etc/network/interfaces」に以下を記述する.

# interfaces(5) file used by ifup(8) and ifdown(8)

# Please note that this file is written to be used with dhcpcd
# For static IP, consult /etc/dhcpcd.conf and 'man dhcpcd.conf'

# Include files from /etc/network/interfaces.d:
source-directory /etc/network/interfaces.d

# The loopback network interface
auto lo
iface lo inet loopback

# The primary network interface
auto eth0
iface eth0 inet manual

auto wlan0
iface wlan0 inet static
address 192.168.33.8
netmask 255.255.255.0
gateway 192.168.33.1
wpa-conf /etc/wpa_supplicant/wpa_supplicant.conf

auto br0
iface br0 inet dhcp  ### 固定 IP でも設定可能なはずであるが,うまく動作しなかったため,DHCP とした. ###
# eth0 を tap_softether に割り当てる.この時,softether の部分は,先程の「tap デバイスの設定」で設定した名前にする.
bridge_ports eth0 tap_softether
bridge_maxwait 10

ここで,ちゃっかり,無線 LAN の設定をしている.「/etc/wpa_supplicant/wpa_supplicant.conf」の設定方法は,以下を参照のこと.

workspacememory.hatenablog.com

設定完了後,reboot をかける.問題がなければ,正常に起動するはずである.この時から,eth0 に割り当てられていた IP アドレスではログインできなくなっていた. そのため,予め設定しておいた無線 LAN 側の IP アドレスで,リモートアクセスする.また,マネージャツールも無線 LAN の IP アドレスを設定することで,管理画面に移行できる.

ssh -X admin@192.168.33.8

リモートログイン後,以下のコマンドで eth0 に tap デバイスが割り当てられているか確認する.

brctl show

bridge name     bridge id               STP enabled     interfaces
br0             xyz.abcdefghijk012345       no          eth0
                                                        tap_softether

ルータの設定

最後に,ルータの設定を行う.筆者は,「WHR-1166DHP3」を使用しているので,型番で調べて設定した.忘れそうなので,やり方を示しておく.ここでのソフトウェアのバージョンは,2.9 である. 「詳細設定」の画面に移動し,「セキュリティー」の「ポート変換」に移動する.以下のように IP アドレスとポートの対応付けを登録する.IP アドレスは,無線 LAN 側に割り当てたものを利用する.

以上で,すべての設定は完了である.疲れた.