Google App Engine スタンダード環境で Kotlin + Spring Boot を動かす

概要

2017.6.28 に Google App Engine スタンダード環境が Java8 をサポートしたと告知されました。 告知記事を読んでいると Kotlin + Spring Boot で動かすサンプルが目にとまったので、動かしてみました。

確認環境

参考情報

解説

Google Cloud Platform

  1. プロジェクトを作成

(設定手順をメモしていませんでした…。)

Google Cloud SDK

手順は以下の通り。

  1. ダウンロード
  2. アーカイブの展開
  3. 環境の初期化
  4. アカウントの初期化

ダウンロード後、ファイルの SHA256 チェックサムを検証してからファイルを展開。 install.sh を実行する前に好みのディレクトリに配置する。 筆者はユーザーのホームディレクトリに置いている (/Users/user_name/google-cloud-sdk) 。

install.sh を実行することで、インストール済みコンポーネントを確認でき、コンポーネントのインストール/アンインストール方法を知れる。 また、環境変数 PATH の自動設定を行える。

$ echo "6572e086d94ab2a6b3242a966b688a382a80a417101315d59d38ac804b5a366e  google-cloud-sdk-158.0.0-darwin-x86_64.tar.gz" | shasum -a 256 -c 
google-cloud-sdk-158.0.0-darwin-x86_64.tar.gz: OK
$ tar xvfz google-cloud-sdk-158.0.0-darwin-x86_64.tar.gz 
$ cd google-cloud-sdk
$ ./install.sh
...
Do you want to help improve the Google Cloud SDK (Y/n)? 
...
Modify profile to update your $PATH and enable shell command 
completion?

Do you want to continue (Y/n)? 
...
[/Users/user_name/.bash_profile]:  
...

途中の Y/n は全て初期設定 (Enter) を選択。~/.bash_profile にて PATH の設定が追加される。

Terminal を起ち上げ直して、以下を続行。 Java Extension コンポーネントをインストールして、SDK をアップデートする。 その後にアカウントを初期化する。

$ gcloud components install app-engine-java
$ gcloud components update
$ gcloud init
...
You must log in to continue. Would you like to log in (Y/n)? 
...
Please enter numeric choice or text value (must exactly match list 
item):  google_cloud_platform_project_id
...
API [compute-component.googleapis.com] not enabled on project 
[xxxxxxxxxxx]. Would you like to enable and retry?  (Y/n)? 
...

途中の Y/n は初期設定 (Enter) を選択。ブラウザが起動して認証を求められる。

SDK の設定諸々は以下のコマンドで確認できる。

$ gcloud info
...
Installation Root: [/Users/user_name/google-cloud-sdk]
...

IntelliJ IDEA でプロジェクト作成

  1. Gradle Project を作成
  2. appengine-web.xml 作成
    • Java8 スタンダード環境を指定
  3. build.gradle 作成
  4. Application.kt 作成
    • JsonObjectMapper の設定
    • TemplateEngine の設定
  5. JsonObjectMapper 作成
  6. Controller, Template などを作成 (省略)
  7. appengineRun タスクで実行
    • ローカル環境で動かす
  8. appengineDeploy タスクで配置
    • Google の環境で動かす

appengine-web.xml, build.gradle, Application.kt, JsonObjectMapper は以下の Gist を参照のこと。

Google App Engine Standard Environment in Java8 wi …

Serializer/Deserializer クラスは ObjectMapper の addSerializer/addDesirializer メソッドに型を合わせるために用意している。

IntelliJ IDEA の Terminal にて、以下を実行すればローカル環境で試せる。

$ ./gradlew appengineRun
...
 04, 2017 4:56:51 午前 com.google.appengine.tools.development.AbstractModule startup
情報: Module instance default is running at http://localhost:8080/
7 04, 2017 4:56:51 午前 com.google.appengine.tools.development.AbstractModule startup
情報: The admin console is running at http://localhost:8080/_ah/admin
7 04, 2017 1:56:51 午後 com.google.appengine.tools.development.DevAppServerImpl doStart
情報: Dev App Server is now running

http://localhost:8080/ をダブルクリックで選択し、コンテキストメニューOpen as URL を選択するとブラウザで開ける。 (もっと楽に起動できる方法は?)

同じく、以下を実行すれば Google Cloud Platform 環境に配置される。

$ ./gradlew appengineDeploy
...

配置後、gcloud app browser とすることでデフォルトブラウザが開くが、デフォルトブラウザの設定が不明。 OS のデフォルトは Chrome に設定されているのに、Firefox が開く。 (どこに設定が?)

他の設定もしていた build.gradle から余計な設定を削除したため、もしかしたら削除し過ぎていて問題が起こるかも。 IntelliJ IDEA に Google Cloud Tools plugin をインストールしているが、無くてもよいはずなので記載していない。 Google Cloud Tools を使うことで App Engine に配置できるはずが機能しない。 (何か設定しないとだめ?)

IntelliJ IDEA で Emacs 風キーマップを作る

概要

IntelliJ IDEA の keymap 設定を Emacs にしても、使いたい機能のショートカットが複雑で覚えられません。 そこで、Emacs 風のショートカットに設定を変更します。

筆者は最初に覚えたエディタが Emacs でしたが、その後は Emacs のライトユーザーです。 ヘビーユーザーからしたら Emacs 風ではないと思われるかもしれません。

確認環境

参考情報

解説

Emacs と言えば、ESC キーと Ctrl キーから連なるショートカットが特徴的かと思います。 IntelliJ IDEA ではショートカットキーに Second stroke を設定することにより、 Emacs 風のショートカットキーを設定できます。

私は、Ctrl-[ESC キーとして使用しています(Emacs 標準のはず)。 そのため、Ctrl-[, Ctrl-x, Ctrl-c, Ctrl-h には Second stroke を設定しています。 Ctrl-xCtrl-c で使い分けがあったかもしれませんが無視することにして設定しています。 Ctrl-h はヘルプ系のアクションを割り当てています。 また、Emacs とは異なりますが、Navigation 系のアクションを Ctrl-j (Jump のつもり) に割り当てています。 基本的には、アクションの意味を表すようにキーを選択しているつもりです(Project ならば p, Intention なら i とか)。 使用頻度の低いと思われるアクションは Ctrl-[ x で Find Action を起動して実行する方針です。

設定した内容を整理したスプレッドシートを公開しています。 設定を変更していないアクションについては記載していません。

docs.google.com

アクションの一覧は intellJ IDEA Community のソースコードから取得しています。

intellij-community/ActionsBundle.properties at master · JetBrains/intellij-community · GitHub

IntellJ IDEA の便利機能をまだまだ知らないので、こんな便利機能があるよ!という話があれば伺いたいです。 また、Emacs の標準的なショートカットキーで、このキー設定は鉄板!という話があれば伺いたいです。

libGDX で iOS アプリを起動できない

概要

libGDX で Multi-OS Engine を使用した iOS アプリを起動するとエラーが発生しました。 libGDX が初期設定で指定している Multi-OS Engine は 1.2.3 ですが 1.2.5 に変更する必要があります。

確認環境

  • AndroidStudio 2.2.3
  • libGDX 1.9.5
  • Multi-OS Engine 1.2.3
  • Xcode 8.2.1

参考情報

解説

Intel Multi-OS Engine 1.2.3 で実行すると以下のエラーが表示される。

Terminating app due to uncaught exception 'NSInvalidArgumentException'

build.gradle で moe-gradle のバージョンを 1.2.5 にすると解決する。

classpath 'org.multi-os-engine:moe-gradle:1.2.5'

libGDX 2D UI Library - Widgets

概要

libGDX の 2D UI Library のうち、Button 系以外の Widget についてまとめます。

目次

確認環境

  • OS X El Capitan (10.11.6)
  • Xcode 8.0
  • Android Studio 2.2.2
    • Multi-OS Engine Plugin 1.2.1
    • Kotlin 1.0.4
    • buildToolVersion 25.0.0
    • compileSdkVersion 24
  • libGDX 1.9.5-SNAPSHOT

参考情報

解説

Image

Image (libgdx API)

設定項目 説明
drawable Drawable 描画対象
Skin, String
NinePatch
Texture
TextureRegion
scaling Scaling Scaling.none, Scaling.fit, Scaling.fill, Scaling.stretch (default)。(fill, stretch には X, Y のみがある。)
align Int Align.topLeft, Align.top, Align.topRight, Align.right, Align.bottomRight, Align.bottom, Align.bottomLeft, Align.left, Align.center (default)。

サイズ

min pref max
width/height 0 drawable のサイズ 0

Scaling (align = Align.center の場合)

Scaling.none Scaling.fit
Scaling.fill Scaling.stretch

サンプルコード

    private val actor by lazy {
        Image(Texture("badlogic.jpg")).apply {
            width = this@MyGdxGame.stage.width
            height = this@MyGdxGame.stage.height
            setScaling(Scaling.none)
            //setScaling(Scaling.fit)
            //setScaling(Scaling.fill)
            //setScaling(Scaling.stretch)
            setAlign(Align.center)
        }
    }

    override fun create() {
        ...
        stage.addActor(actor)
    }

Label

Label (libgdx API)

設定項目 説明
text CharSequence 描画対象。改行文字 ('\n') で改行される。
style Label.LabelStyle background, font, fontColor
Skin
labelAlign Int Label 全体の配置。Align.topLeft, Align.top, Align.topRight, Align.right, Align.bottomRight, Align.bottom, Align.bottomLeft, Align.left (default), Align.center。
lineAlign Int 各行の配置。Align.left (default), Align.center, Align.right。
ellipsis Boolean true ならば width を超えないように末尾を "..." に置き換える。(default: false)
String null 以外ならば width を超えないように末尾を指定した String に置き換える。
fontScaleX Float X 方向の拡大率。
fontScaleY Float Y 方向の拡大率。
wrap Boolean true ならば width を超える場合に改行する。ellipsis が優先される。(default: false)

サイズ

min pref max
width/height prefと同じ text が収まるサイズを算出 0

text = "Hello World\nHello Beautiful World!!"、fontScaleX = 5、fontScaleY = 10 の場合の表示例。

labelAlign = topLeft, lineAlign = center labelAlign = center, lineAlign = center labelAlign = bottomRight, lineAlign = center
labelAlign = center, lineAlign = left labelAlign = center, lineAlign = center labelAlign = center, lineAlign = right

サンプルコード

    private val actor by lazy {
        Label("Hello World\nHello Beautiful World!!",
                Label.LabelStyle(BitmapFont(), Color.WHITE)).apply {
            width = this@MyGdxGame.stage.width
            height = this@MyGdxGame.stage.height
            setFontScale(5f, 10f)
            setAlignment(Align.center, Align.center)
        }
    }

    override fun create() {
        ...
        stage.addActor(actor)
    }

TextField

TextField (libgdx API)

設定項目 説明
text String 入力欄に設定される文字列。改行文字 ('\n') は除去される。
style TextField.TextFieldStyle BitmapFont (font, messageFont), Color (fontColor, messageFontColor, disabledFontColor, focusedFontColor), Drawable (background, disabledBackground, focusedBackground, cursor, selection)
Skin
alignment Int Align.left, Align.center, Align.right。
maxLength Int 入力可能文字数。
messageText String text 未入力時に表示する文字列。入力を削除しても再表示されない。
onlyFontChars Boolean true (default) ならば、text に set する際に BitmapFont に含まれない文字を除去する。false ならば、含まれない文字を空白に置き換える。
passwordMode Boolean true ならば、入力文字列を passwordCharacter で表示する。false (default) ならば、入力文字列をそのまま表示する。
passwordCharacter Char passwordMode が true のときに使用する文字。設定しないと passwordMode = true でもマスクされない。

サイズ

min pref max
width/height prefと同じ ? 0

サンプルコード

    private val actor by lazy {
        TextField("", TextField.TextFieldStyle().apply {
            font = BitmapFont().apply {
                data.scale(10f)
            }
            messageFont = font
            fontColor = Color.WHITE
            messageFontColor = Color.RED
        }).apply {
            width = this@MyGdxGame.stage.width
            height = this@MyGdxGame.stage.height
            maxLength = 10
            messageText = "message"
            isPasswordMode = false
            setPasswordCharacter('x')
            setAlignment(Align.right)
            setOnlyFontChars(false)
            //text = "aとbとc"
        }
    }

    override fun create() {
        ...
        stage.addActor(actor)
    }

TextArea

TextArea (libgdx API)

TextField と同様。 異なる点は text に改行文字を含むことができ、width を超える場合には改行して表示される。

List

List (libgdx API)

設定項目 説明
items Array リストの項目。文字列として表示される。
style List.ListStyle BitmapFont (font), Color (fontColorSelected, fontColorUnselected), Drawable (background, selection)
Skin

サイズ

min pref max
width/height prefと同じ ? 0

実行例

サンプルコード

    private val actor by lazy {
        List<String>(List.ListStyle().apply {
            font = BitmapFont().apply {
                data.scale(10f)
            }
            fontColorUnselected = Color.WHITE
            fontColorSelected = Color.RED
            selection = SpriteDrawable(Sprite(Texture("badlogic.jpg")))
        }).apply {
            width = this@MyGdxGame.stage.width
            height = this@MyGdxGame.stage.height
            setItems("pen", "apple", "pineapple")
        }
    }

    override fun create() {
        ...
        stage.addActor(actor)
    }

SelectBox

SelectBox (libgdx API)

設定項目 説明
items Array リストの項目。文字列として表示される。
style SelectBox.SelectBoxStyle BitmapFont (font), Color (fontColor, disabledFontColor), Drawable (background, backgroundOpen, backgroundOver, backgroundDisabled), List.ListStyle (BitmapFont (font), Color (fontColorSelected, fontColorUnselected), Drawable (background, selection)), ScrollPane.ScrollPaneStyle (Drawable (background, corner, hScroll, hScrollKnob, vScroll, vScrollKnob))
Skin
maxListCount Int ドロップダウン時に表示する項目数。(default: items の要素数)
scrollingDisabled Boolean true ならば、ドロップダウン時のスクロールを無効にする (maxListCount の個数しか表示されない)。false (default) ならば、有効にする。

サイズ

min pref max
width/height prefと同じ ? 0

実行例

default maxListCount = 3 maxListCount = 3, scrollingDisabled = true

サンプルコード

    private val actor by lazy {
        val commonFont = BitmapFont().apply {
            data.scale(10f)
        }
        SelectBox<String>(SelectBox.SelectBoxStyle().apply {
            font = commonFont
            fontColor = Color.WHITE
            background = SpriteDrawable(Sprite(Texture("badlogic.jpg")).apply {
                color = Color.YELLOW
            })
            backgroundOpen = SpriteDrawable(Sprite(Texture("badlogic.jpg")).apply {
                color = Color.GRAY
            })
            listStyle = List.ListStyle().apply {
                font = commonFont
                fontColor = Color.GRAY
                fontColorUnselected = Color.GREEN
                fontColorSelected = Color.RED
                selection = SpriteDrawable(Sprite(Texture("badlogic.jpg")).apply {
                    color = Color.RED
                })
            }
            scrollStyle = ScrollPane.ScrollPaneStyle().apply {
                vScroll = SpriteDrawable(Sprite(Texture("badlogic.jpg")).apply {
                    color = Color.BLUE
                })
                vScrollKnob = SpriteDrawable(Sprite(Texture("badlogic.jpg")).apply {
                    color = Color.MAGENTA
                })
            }
        }).apply {
            val stageW = this@MyGdxGame.stage.width
            val stageH = this@MyGdxGame.stage.height
            setPosition(stageW/2 - width/2, stageH - height)
            setItems("1", "2", "3", "4", "5", "6", "7", "8", "9", "0")
            //maxListCount = 3
            //setScrollingDisabled(true)
        }
    }

    override fun create() {
        ...
        stage.addActor(actor)
    }

ProgressBar

ProgressBar (libgdx API)

設定項目 説明
min Float 最小値。
max Float 最大値。
stepSize Float 最小値から最大値を分割したときの一区画のサイズ。value は近くの区画に丸められる。
vertical Boolean true ならば、縦方向に表示。false ならば、横方向に表示。
style ProgressBar.ProgressBarStyle Drawable (background, knob, knobBefore, knobAfter, disabledBackground, disabledKnob, disabledKnobBefore, disabledKnobAfter)
Skin
visualInterpolation Interpolation value に set して、表示が更新される際のアニメーション表示方法。
animateDuration Float アニメーション時間。単位は秒。
animateInterpolation Interpolation setAnimationDuration を実行した際に行われるアニメーションの表示方法。

サイズ

min pref max
width/height prefと同じ ? 0

実行例

vertical = false vertical = true

サンプルコード

    private val actor by lazy {
        ProgressBar(0f, 100f, 25f, false,
                ProgressBar.ProgressBarStyle().apply {
                    background = SpriteDrawable(Sprite(Texture("badlogic.jpg")).apply {
                        color = Color.WHITE
                    })
                    knob = SpriteDrawable(Sprite(Texture("badlogic.jpg")).apply {
                        color = Color.BLUE
                    })
                    knobBefore = SpriteDrawable(Sprite(Texture("badlogic.jpg")).apply {
                        color = Color.RED
                    })
                    knobAfter = SpriteDrawable(Sprite(Texture("badlogic.jpg")).apply {
                        color = Color.GREEN
                    })
                }
        ).apply {
            width = this@MyGdxGame.stage.width
            height = this@MyGdxGame.stage.height
            setVisualInterpolation(Interpolation.fade)
            setAnimateDuration(10f)
            value = 60f
            setAnimateInterpolation(Interpolation.bounce)
            setAnimateDuration(20f)
        }
    }

    override fun create() {
        ...
        stage.addActor(actor)
    }

Slider

ProgressBar のサブクラス。

Slider (libgdx API)

設定項目 説明
min Float 最小値。
max Float 最大値。
stepSize Float 最小値から最大値を分割したときの一区画のサイズ。value は近くの区画に丸められる。
vertical Boolean true ならば、縦方向に表示。false ならば、横方向に表示。
style Slider.SliderStyle Drawable (background, knob, knobBefore, knobAfter, knobDown, knobOver, disabledBackground, disabledKnob, disabledKnobBefore, disabledKnobAfter)
Skin
visualInterpolationInverse Interpolation value に現在の value よりも小さい値を set して、表示が更新される際のアニメーション表示方法。

サイズ

min pref max
width/height prefと同じ ? 0

サンプルコード

    private val actor by lazy {
        Slider(0f, 100f, 25f, false,
                Slider.SliderStyle().apply {
                    background = SpriteDrawable(Sprite(Texture("badlogic.jpg")).apply {
                        color = Color.WHITE
                    })
                    knob = SpriteDrawable(Sprite(Texture("badlogic.jpg")).apply {
                        color = Color.BLUE
                    })
                    knobBefore = SpriteDrawable(Sprite(Texture("badlogic.jpg")).apply {
                        color = Color.RED
                    })
                    knobAfter = SpriteDrawable(Sprite(Texture("badlogic.jpg")).apply {
                        color = Color.GREEN
                    })
                    knobDown = SpriteDrawable(Sprite(Texture("badlogic.jpg")).apply {
                        color = Color.MAGENTA
                    })
                }
        ).apply {
            width = this@MyGdxGame.stage.width
            height = this@MyGdxGame.stage.height
            value = 60f
            setVisualInterpolationInverse(Interpolation.smooth)
            setAnimateDuration(3f)
            value = 10f
        }
    }

    override fun create() {
        ...
        stage.addActor(actor)
    }

GPIO 操作するプログラムを macOS で開発する

概要

macOS 環境で RPi.GPIO モジュールを使用します。 macOS 環境では GPIO が存在しないため、エミュレーターを使用します。 なお、エミュレーターは自作です。

確認環境

参考情報

前回の記事

nosix.hatenablog.com

解説

Raspberry Pi には RPi.GPIO モジュールがインストールされているが、macOS 環境にはインストールされていない。 例えば、以下のプログラムを macOS 環境の PyCharm で作成すると No module named RPi とのエラーメッセージが表示される。

hello_gpio.py

from time import sleep

import RPi.GPIO as GPIO

GPIO.setmode(GPIO.BCM)
GPIO.setup(25, GPIO.OUT)

while True:
    GPIO.output(25, GPIO.HIGH)
    sleep(0.5)
    GPIO.output(25, GPIO.LOW)
    sleep(0.5)

macOS 環境に RPi モジュールをインストールする代わりに RPi モジュールのエミュレーター(もしくは、シミュレーター)を探した。 いくつか見つかったが、モジュール名を変更しなければいけないなど、ソースコード(上記の例では hello_gpio.py)に全く手を入れずに使えるものではなかった。そこで、RPi モジュールのエミュレーターを自作した。

github.com

RPi.GPIO エミュレーターをインストールする

GitHub にある Python パッケージを pip を使って仮想環境にインストール。

mac:~ nosix$ source ~/pyenv/python3/bin/activate
(python3) mac:~ nosix$ 
(python3) mac:~ nosix$ pip install git+https://github.com/nosix/raspberry-gpio-emulator/
Collecting git+https://github.com/nosix/raspberry-gpio-emulator/
  Cloning https://github.com/nosix/raspberry-gpio-emulator/ to /private/var/folders/kz/wv_kdss90634rlck2k9d852m0000gn/T/pip-6akufwi3-build
Installing collected packages: RPi
  Running setup.py install for RPi ... done
Successfully installed RPi-0.1.0

インストールが完了するとエラーは消える。

macOS 環境、Raspberry Pi 環境のいずれでも変更なしで実行できる。

macOS 環境で実行した場合には、エミュレーターが起動されてピンの状態を表示。

Raspberry Pi 環境で実行した場合には、GPIO が操作される。

なお、エミュレーターの UI が気に入らない場合には、自作 UI に差し替えられる設計になっている。 また、各 GPIO の状態変化を監視するプラグインを追加できる様になっている。

macOS で Python プログラム開発環境を構築して Raspberry Pi で実行する

概要

macOSPython プログラムの開発環境を構築します。 macOS で作成したプログラムを Raspberry Pi で実行できる様にします。 前回の記事の続きです。

確認環境

参考情報

前回の記事

nosix.hatenablog.com

解説

Python3 環境を構築する

HomeBrew が導入されていなければ導入する。 HomeBrew は macOS においてソフトウェアパッケージを管理するツール。 ソフトウェアパッケージのインストール、アンインストール、アップグレードなどを行うことができる。

mac:~ nosix$ /usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"

brew コマンドを使用して Python3, virtualenv をインストール。 pip は Python パッケージを管理するツール。 Python プログラムで使用できる便利な機能 (ライブラリ) をインストールするために使用する。 virtualenv は仮想環境(実験室)を作成するツール。 Python バージョンやライブラリの組み合わせを切り替えることが容易になる。

mac:~ nosix$ brew install python3
mac:~ nosix$ pip3 install virtualenv

pip を使用した際に新しいバージョンの通知がある場合は、指示に従ってアップグレードする。

You are using pip version 8.1.2, however version 9.0.1 is available.
You should consider upgrading via the 'pip install --upgrade pip' command.
mac:~ nosix$ pip3 install --upgrade pip

Python3 用の virtualenv (仮想環境) を作成する。 作成後に --version オプションを指定して python コマンドを実行し、バージョンを確認する。

mac:~ nosix$ mkdir ~/pyenv
mac:~ nosix$ virtualenv -p python3 ~/pyenv/python3
mac:~ nosix$ source ~/pyenv/python3/bin/activate
(python3) mac:~ nosix$ 
(python3) mac:~ nosix$ python --version
Python 3.5.2

PyCharm を導入する

PyCharm Community をダウンロードし、インストール。 もしくは、IntelliJ IDEA など JetBarins 製 IDE を使用しているならば、Python Community Edition プラグインをインストール。 以降の画面例は IntelliJ IDEA に Python Community Edition プラグインをインストールしたもの。 なお、有償版を使うと他のコンピューターで動作している Python プログラムをデバッガで監視できるらしい。

以下はプラグインのインストール。Preferences... から

Python プログラムを作る

PyCharm (もしくは、IntelliJ IDEA + Plugin) を使用して Python プログラムを作る。

仮想環境を指定してプロジェクトを作成する。仮想環境を指定するにあたって、PyCharm に作成済みの仮想環境を登録する。

PyCharm に作成済みの仮想環境を登録。~/pyenv/python3/bin/python を選択することで登録される。

登録した仮想環境を選択。

プロジェクト名はお好みで。

Python プログラムを作成して、実行する。

hello.py

from time import sleep

while True:
    print("Hello")
    sleep(1)

Raspberry Pi で実行する

前回、pidep コマンドを作成して Raspberry Pi にログインせずに macOS から実行できる環境を作った。 pidep コマンドを PyCharm で実行できる様にする。

Preferences... を開き Tools > External Tools と移動。

pidep コマンドを使って hello.py を Raspberry Pi で実行する。

再ログインした際に pidep コマンドを実行すると以下のエラーとなることがある。

Permission denied, please try again.
Permission denied, please try again.
Permission denied (publickey,password).
lost connection

このような場合には Terminal で ssh-add コマンドを実行して認証を済ませておく。

Raspberry Pi の Python 実行環境を整える

概要

USB ケーブル 1 本で Raspberry Pi Zero と接続できる様になったので、Python プログラムを Raspberry Pi Zero で動かしてみます。 しかし、今後の試行錯誤を考え、(自分にとっての)プログラムを作りやすい環境を整えてみました。 具体的には以下を行なっています。

  • ssh でのログインをパスワードなしにする
  • Python 2.x 系と 3.x 系を切り替えられる様にする
  • Mac でプログラムを作成し、Raspberry Pi Zero では実行のみ行う様にする

確認環境

参考情報

前回の記事

nosix.hatenablog.com

解説

ssh コマンドでパスワードを入力せずにログインする

前回、ssh コマンドでログインできる様になった。 しかし、ssh コマンドでログインする度にパスワードを入力するのは面倒だ。 そこで、公開鍵認証を使用してパスワード入力せずにログインできる様にする。

Mac で鍵ペアを生成して、公開鍵のみを Raspberry Pi の ~/.ssh/authorized_keys に登録する。 秘密鍵は絶対に外部に漏らしてはいけない。 ssh-keygen コマンドは鍵ペアを生成する。 鍵ペアは ~/.ssh ディレクトリに id_rsa (秘密鍵), id_rsa.pub (公開鍵) として生成される。 安全のためにパスフレーズ (passphrase) は入力した方が良い。 id_rsa.pub を Raspberry Pi の ~/.ssh/authorized_keys に登録する。

mac:~ nosix$ ssh-keygen 
Generating public/private rsa key pair.
Enter file in which to save the key (/Users/nosix/.ssh/id_rsa): 
Created directory '/Users/nosix/.ssh'.
Enter passphrase (empty for no passphrase): 
Enter same passphrase again: 
Your identification has been saved in /Users/nosix/.ssh/id_rsa.
Your public key has been saved in /Users/nosix/.ssh/id_rsa.pub.
...省略...
mac:~ nosix$ scp ~/.ssh/id_rsa.pub pi@raspberrypi.local:~
id_rsa.pub                                    100%  404     0.4KB/s   00:00
mac:~ nosix$ ssh pi@raspberrypi.local
...省略...
pi@raspberrypi:~ $ mkdir .ssh
pi@raspberrypi:~ $ cat id_rsa.pub >> .ssh/authorized_keys
pi@raspberrypi:~ $ rm id_rsa.pub
pi@raspberrypi:~ $ exit

authorized_keys は存在していないため cat コマンドではなく mv コマンドでも構わないが、 既に authorized_keys が存在する場合には追記する必要がある。 cat src_file >> dst_file で追記できることを知っておくと便利かと思う。

以上で設定は完了したので、ssh コマンドでログインしてみる。 途中で接続を続けるかと尋ねられるので yes とする。 Raspberry Pi を識別するための情報(ECDSA key fingerpring)が Mac の ~/.ssh/known_hosts に登録される。 次回からは途中で尋ねられることなくログインできる。

mac:~ nosix$ ssh pi@raspberrypi.local
The authenticity of host 'raspberrypi.local (<省略>)' can't be established.
ECDSA key fingerprint is SHA256:<省略>.
Are you sure you want to continue connecting (yes/no)? yes
Warning: Permanently added 'raspberrypi.local,<省略>' (ECDSA) to the list of known hosts.

The programs included with the Debian GNU/Linux system are free software;
the exact distribution terms for each program are described in the
individual files in /usr/share/doc/*/copyright.

Debian GNU/Linux comes with ABSOLUTELY NO WARRANTY, to the extent
permitted by applicable law.
Last login: Fri Dec 16 04:36:43 2016 from <省略>
pi@raspberrypi:~ $ 

ログインできれば成功。 Raspberry Pi (の authorized_keys) に登録した Mac からはパスワードなしでログインできる。 Mac 側では ssh-keygen で入力したパスフレーズを 1 度だけ入力することがある。 パスフレーズで認証しておくことで、ssh でログインする際にはパスワードが不要になる。 パスワードは passwd コマンドで複雑なパスワードに変更する。

~/.bash_profile にコマンドのエイリアス(別名)を登録しておくと便利。 以下の 1 行を追加すると pish コマンドで Raspberry Pissh でログインできる。

alias pish='ssh pi@raspberrypi.local'

PC に再ログインした場合には、ssh-add コマンドを実行して認証する。 認証が完了していない場合には、ssh の実行の際にパスフレーズの入力が求められる。

mac:~ nosix$ pish
Enter passphrase for key '/Users/nosix/.ssh/id_rsa':

Python3 環境を Raspberry Pi に作る

Python には 2.x 系と 3.x 系が存在する。 2.x 系と 3.x 系で異なる部分があり、2.x 系で動くからと言って 3.x 系では動かないという場合がある。 もちろん、反対もあり得る。 Raspberry Pi に標準でインストールされている Python は 2.x 系。 3.x 系の Python も導入し、2.x 系と 3.x 系を切り替えられる様にしておく。

Raspberry Pi に python3, pip (Python のパッケージ管理ツール), virtualenv (実行環境を切り替えるツール) をインストールする。 pip を使うことで、多くの人が作ってくれた Python の便利道具(パッケージ)を簡単に導入できる。 パッケージにはバージョンがあり、バージョンの組み合わせによっては動作しないこともある。 様々な実験を行うために個別の実験室(仮想環境; virtualenv)を用意しておくと便利。(実験室は比喩。) パッケージを導入する際には実験室を指定して導入し、他の実験室には影響が及ばない様にする。 また、実験室毎に Python2 と Python3 のいずれを使うかを決めておくことができる。

まず、Raspberry Pi からインターネットに接続できる様にする。 Mac でインターネット共有を設定する。 RNDIS/Ethernet Gadget にチェックを入れる。

次に、python3, python3-rpi.gpio, pip, virtualenv をインストールする。

pi@raspberrypi:~ $ sudo apt-get install python3
...省略...
pi@raspberrypi:~ $ sudo apt-get install python3-rpi.gpio
...省略...
pi@raspberrypi:~ $ sudo apt-get install python-pip
...省略...
pi@raspberrypi:~ $ sudo pip install virtualenv
...省略...

実験室 (仮想環境; virtualenv) を作成する。 例では、~/pyenv/python2, ~/pyenv/python3 を作成している。 apt-get で install した python3-rpi.gpio を使用するために、--system-site-packages を指定している。 (ディレクトリはお好みで。)

pi@raspberrypi:~ $ mkdir ~/pyenv
pi@raspberrypi:~ $ virtualenv -p python2 ~/pyenv/python2
Running virtualenv with interpreter /usr/bin/python2
New python executable in /home/pi/pyenv/python2/bin/python2
Also creating executable in /home/pi/pyenv/python2/bin/python
Installing setuptools, pip, wheel...done.
pi@raspberrypi:~ $ virtualenv --system-site-packages -p python3 ~/pyenv/python3
Running virtualenv with interpreter /usr/bin/python3
Using base prefix '/usr'
New python executable in /home/pi/pyenv/python3/bin/python3
Also creating executable in /home/pi/pyenv/python3/bin/python
Installing setuptools, pip, wheel...done.

試しに実験室に入る。activate を source コマンドで読み込むと仮想環境が有効になる。

pi@raspberrypi:~ $ source ~/pyenv/python3/bin/activate
(python3) pi@raspberrypi:~ $ 
(python3) pi@raspberrypi:~ $ python --version
Python 3.4.2

実験室から出る(仮想環境を無効にする)には、deactivate コマンドを使う。

(python3) pi@raspberrypi:~ $ deactivate 
pi@raspberrypi:~ $ 

以降、pip でパッケージをインストールする際には、実験室に入ってから行う。 全ての実験室で共通して使いたいパッケージの場合には、実験室に入らずに pip でインストールする。

Mac 環境でソースコードを作成して、Raspberry Pi で動かす

Raspberry Pi で IDLE を使用してソースコードを編集しても良いが、Mac 環境の方がツールも多く便利である。 Mac 環境でソースコードを作成して、Raspberry Pi にコピーして Raspberry Pi で実行する仕組みを用意する。 (この節の内容は試行中のものです。)

~/bin/pidep コマンドを自作する。(dep は Deploy の意。) Mac から Raspberry Pi にファイルを scp でコピーして、Mac から ssh で実行するスクリプトになっている。

コマンドを実行できる様にする。

mac:~ nosix$ chmod +x ~/bin/pidep

Raspberry Pi に ~/pyhome ディレクトリと仮想環境 ~/pyenv/python3 を用意しておく。

pi@raspberrypi:~ $ mkdir ~/pyhome

pidep の PI_HOME と PI_ENV で指定している。 PI_HOME はコピー先のルート、PI_ENV は実行時に使用する仮想環境の指定。

使い方

まずは、Python3 プログラム(hello.py)を用意する。1 秒毎に Hello と表示される。

from time import sleep

while True:
    print("Hello")
    sleep(1)

このプログラムを pidep で Raspberry Pi にコピーして実行する。 -s はコピー元、-r は実行するプログラム(スクリプト)を指定する。

mac:tmp nosix$ pidep -s hello.py -r hello.py
hello.py                                      100%   68     0.1KB/s   00:00    
Hello
Hello
Hello
Hello
^CTraceback (most recent call last):
  File "hello.py", line 5, in <module>
    sleep(1)
KeyboardInterrupt
Connection to raspberrypi.local closed.

-d でコピー先を指定できる。 -r を省略するとコピーのみ、-s を省略すると実行のみ行われる。

mac:tmp nosix$ pidep -s hello.py -d loop.py -r loop.py
mac:tmp nosix$ pidep -r hello.py
mac:tmp nosix$ pidep -s hello.py