ITと雑記とド田舎と

ド田舎在住エンジニアがIT備忘録と雑記を書くブログです

Raspberry pi 3で暗視カメラシステムの構築 その3

時間が空きましたが、前回の続きです。今回はカメラから画像を送信する処理と画像を受け取って保存しておき、要求に応じて画像を表示するhtmlを返すwebサーバーを切り分けた部分の紹介になります。ここまでで、下図の設計の左半分が出来ました。こうすることで画像を送信するRaspberr pi3とwebサーバーを別々にします。

 

f:id:kdn-1017-wttd:20181111094136j:plain

システム設計図

 

Raspberry Pi3側画像送信スクリプト

ソースコードは以下になります。

 

import tornado.websocket
import asyncio
import tornado.ioloop
import pickle
import time

from capture_video import CaptureVideo

async def main():
    url = 'ws://192.168.100.50:8888/camera?mode=send_frame'
    conn = await tornado.websocket.websocket_connect(url)

    # 画像データ送信
    await send_frame_loop(conn)

    conn.close()

async def send_frame_loop(conn):
    try:
        while True:
            ret, frame = CaptureVideo.get_frame()
            if ret:
                binary_image = pickle.dumps(frame)
                conn.write_message(binary_image, binary=True)
                await asyncio.sleep(0.1)

    except KeyboardInterrupt:
        conn.close()
        CaptureVideo.capture_start()
        ioloop = tornado.ioloop.IOLoop.current()
        ioloop.stop()
        ioloop.close()


if __name__ == '__main__':
    CaptureVideo.capture_start()
    tornado.ioloop.IOLoop.current().run_sync(main)

 webサーバー側をtornadoで実装しているため、WebSocketクライアントもtornadoを利用しました。WebSocketサーバーに接続するときに、クエリストリングでパラメーター'send_frame'を渡して、画像を送信することをサーバー側に伝えています。あとはsend_frame_loop関数で非同期定期的に画像を送り続けます。前回まででwebサーバー側でカメラから直接画像を取得していた部分を、こちらに置き換えています。画像を取得するクラスCaptureVideoに関しては前回の記事をご覧ください。

 

Webサーバー

WebSocketサーバーの変更

さて、Raspberr Pi3側で画像を送り、別のマシンに配置したWebサーバーで画像を受け取る構成になったため、サーバー側も変更する必要があります。ソースコードが以下になります。

import cv2
import time
import asyncio
import tornado
import tornado.httpserver
import tornado.websocket
import tornado.ioloop
import tornado.web
import pickle

from capture_video import CaptureVideo
from frame_data import FrameData

class HttpHandler(tornado.web.RequestHandler):
    @tornado.web.asynchronous
    def initialize(self):
        pass

    def get(self):
        self.render('./index.html')

class WSHandler(tornado.websocket.WebSocketHandler):

    def open(self, args):
        print(self.request.remote_ip, ": connection opened")
        self.mode = self.get_query_argument('mode')
        print(self.mode)
        if self.mode == 'get_frame':
            self.ioloop = tornado.ioloop.IOLoop.current()
            self.loop()

    def on_close(self):
        print("Session closed")
        self.close()

    def check_origin(self, origin):
        return True

    def on_message(self, message):
        if self.mode == 'send_frame':
            frame = pickle.loads(message)
            frame_data = FrameData.get_instance()
            frame_data.set_frame(frame)

    def loop(self):
        self.ioloop.add_timeout(time.time() + 0.1, self.loop)
        frame_data = FrameData.get_instance()
        encode_param = [int(cv2.IMWRITE_JPEG_QUALITY),90]
        ret, decimg = cv2.imencode('.jpg', frame_data.get_frame(), encode_param)
        if self.ws_connection:
            if ret:
                message = decimg.tobytes()
                self.write_message(message, binary=True)

def main():
    try:
        app = tornado.web.Application([
            (r'/', HttpHandler),
            (r'/camera(.*)', WSHandler),
        ])
        http_server = tornado.httpserver.HTTPServer(app)
        http_server.listen(8888)

        print('server start')
        io_loop = tornado.ioloop.IOLoop.current()
        io_loop.start()

    except KeyboardInterrupt:
        print('server stop')
        ioloop = tornado.ioloop.IOLoop.current()
        ioloop.stop()
        ioloop.close()

if __name__=='__main__':
    main()

 主な変更はWebSocketサーバーの部分です。WebSocketの通信を画像を受け取る場合と、画像をhttpクライアント側に送信する場合で処理を分けました。接続を開始するOpen関数でGetリクエストでクエリストリングを受け取るようにし、受け取ったパラメータが'send_frame'なら画像を受け取って保存、'get_frame'なら今まで通り画像を送信してhtmlで表示するという流れです。

これに合わせてサーバーが返すindex.htmlも少しだけ変更しています。クライアントで実行されるWebSocketに接続するjavascriptの部分で、接続するときにパラメータ'get_frame'を送るようにしました。

var ws = new WebSocket("ws://192.168.100.50:8888/camera?mode=get_frame");
Singletonで画像データの共有

また、カメラから受け取った画像は保存しておいてhttpクライアントに送らないといけないため、画像データを保存しておくためのSingletonクラスFrameDataを作成しました。Singletonはオブジェクト指向プログラミングのデザインパターンの一つで、プログラム内で、「あるクラスのインスタンスを1つしか生成しない」という仕組みを実装するものです。コードは以下です。

from threading import Lock

class FrameData:
    # Singleton

    _unique_instance = None
    _lock = Lock()

    def __new__(cls):
        raise NotImplementedError('Cannot initialize via Constructor')

    def set_frame(self, frame):
        self._unique_instance.frame = frame

    def get_frame(self):
        if self._unique_instance.frame is not None:
            return  self._unique_instance.frame
        else:
            print('Frame is None')
            return None

    @classmethod
    def __internal_new__(cls):
        return super().__new__(cls)

    @classmethod
    def get_instance(cls):
        if not cls._unique_instance:
            with cls._lock:
                if not cls._unique_instance:
                    cls._unique_instance = cls.__internal_new__()
                    cls._unique_instance.frame = None
        return cls._unique_instance

画像を保存するときにget_instanceでインスタンスを取得し、set_frame関数で画像を保存。画像を取り出すときはget_instanceでインスタンスを取得し、get_frame関数で画像を取り出しという流れです。pythonはSingletonの仕組みは標準では実装されていないようでしたので、参考にさせてもらったwebサイトに記載されていたコードを改良して自前で用意しました。Raspberry pi3カメラで接続される場合と、httpクライアントからの接続では別のWebSocketインスタンスが生成されるため、その中で画像を共有するためにSingletonで共通のインスタンスを利用する方法を取りました。

 

基本的な部分は今回までで完成しました。公開サーバーへのデプロイ、画像認識やレイアウトなどを次回以降でやっていこうと思います。

参考

Pythonでシングルトン(Singleton)を実装してみる - [Dd]enzow(ill)? with DB and Python

【ChatDeTornado】TornadoでWebSocketを使ってチャットを作る - Qiita

Raspberry pi 3で暗視カメラシステムの構築 その2

前回の記事の続きになります。今回はRaspberry Pi 3 BにpythonTronadoのWebサーバーを立てて暗視カメラで撮影した映像をWebブラウザから確認するところまでを紹介します。

 

f:id:kdn-1017-wttd:20181111094136j:plain

 

Raspberry Pi上のwebサーバー

TornadoのWebServer

以下がpython3で書いたコードになります。

import cv2
import time
import asyncio
import tornado
import tornado.httpserver
import tornado.websocket
import tornado.ioloop
import tornado.web

from capture_video import CaptureVideo

def capture_frame():
    CaptureVideo.capture_start()


class HttpHandler(tornado.web.RequestHandler):
    @tornado.web.asynchronous
    def initialize(self):
        pass

    def get(self):
        self.render('./index.html')

class WSHandler(tornado.websocket.WebSocketHandler):
    def open(self):
        print(self.request.remote_ip, ": connection opened")
        self.ioloop = tornado.ioloop.IOLoop.current()
        self.loop()

    def on_close(self):
        print("Session closed")
        self.close()

    def check_origin(self, origin):
        return True

    def loop(self):
        self.ioloop.add_timeout(time.time() + 0.1, self.loop)
        frame = CaptureVideo.get_frame()
        encode_param = [int(cv2.IMWRITE_JPEG_QUALITY),90]
        ret, decimg = cv2.imencode('.jpg', frame, encode_param)
        if self.ws_connection:
            if ret:
                message = decimg.tobytes()
                self.write_message(message, binary=True)

def main():
    try:
        app = tornado.web.Application([
            (r'/', HttpHandler),
            (r'/camera', WSHandler),
        ])
        http_server = tornado.httpserver.HTTPServer(app)
        http_server.listen(8888)

        print('server start')
        io_loop = tornado.ioloop.IOLoop.current()
        capture_frame()
        io_loop.start()

    except KeyboardInterrupt:
        print('server stop')
        ioloop = tornado.ioloop.IOLoop.current()
        ioloop.stop()
        ioloop.close()

if __name__=='__main__':
    main()

httpでgetリクエストを待ち受けて、Webブラウザに動画表示用のhtmlファイルを返します。Webブラウザ側はWebSocketで接続して画像を取得するようにするため、WebSocketの通信を待ち受けるHandlerも実装しています。WebSocketの通信が確立されるとloopメソッドが呼び出されて画像をクライアント側に送信し続けるようにようにしました。

画像はOpenCVを利用して処理します。OepnCVの導入については前回の記事をご覧ください。WebSocketでWebブラウザに画像を送信する前にJPEG形式に変換してバイナリデータとして送信するという流れになります。

Cameraから画像キャプチャ

続いて上記のコードで利用しているCaptureVideoクラスについて説明します。このクラスはpythonのasyncioライブラリを利用してWebサーバーとは非同期にカメラから画像を取得するために作成しました。

import asyncio
import cv2

class CaptureVideo:
    is_running = False
    frame = None

    @classmethod
    def capture_start(cls, camera_id=0):
        if not cls.is_running:
            cls.is_running = True
            asyncio.ensure_future(cls.__capture_loop(camera_id))

    @classmethod
    def get_frame(cls):
        if cls.is_running:
            return cls.frame
        else:
            print('capture loop is not running!')

    @classmethod
    def stop_capture(cls):
        if cls.is_running:
            cls.is_running = False

    @classmethod
    async def __capture_loop(cls, camera_id):
        cap = cv2.VideoCapture(camera_id)
        try:
            while cls.is_running:
                ret, cls.frame = cap.read()
                await asyncio.sleep(0.1)
        except:
            loop = asyncio.get_event_loop()
            loop.stop()
            cls.isrunning = False
            cap.release()

 __capture_loopメソッドが非同期にカメラの画像を取得し続けるメソッドです。awaitの部分で処理を中断して制御をWebサーバー側に渡しています。

詳しくはこちら。このクラスを先に記載したWebサーバーの方でimportしてio_loop.startの前にカメラから画像を取得する処理を開始し、WebSocketのループの中で必要に応じてget_frameメソッドにより画像を取得するという仕組みです。

 

最新のTornadoではpython3.5から導入されたこのasyncioライブラリを利用して非同期処理を行うのことを推奨しています。この辺りは日本語で説明されている記事がとても少ない印象でしたので、興味のある方は先ほどリンクを張ったTronadoの公式リファレンスを読まれるのが一番良いと思います。

また、日本語の記事ですとTornadoでWebサーバーを待ち受け開始する際に利用するメソッドであるIOLoop.current()がIOLoop.instance()になっているものが多いのです。こちらは公式で非推奨になっているメソッドで、IOLoop.current()を利用するように記載されています。

 ライブ動画を表示するhtml

最後にWebサーバーから返されるhtmlファイルを説明します。

<html>
  <head>
    <title>livecamera</title>
    <font size="5"><b>livecamera by Raspberry Pi3</b></font><br>
    <img id="liveImg" src="">
    <script type="text/javascript">
      var img = document.getElementById("liveImg");

      var ws = new WebSocket("ws://192.168.11.205:8888/camera");
      ws.onopen = function(){console.log("connection was established")};
      ws.onmessage = function(evt){
        window.URL.revokeObjectURL(img.src)
        img.src = window.URL.createObjectURL(evt.data);
      };

      window.onbeforelaod = function(){
        ws.close(1000);
      };
    </script>
  </head>
</html>

ブラウザでWebサーバーにアクセスするとこちらのhtmlファイルが表示され、Javascriptで書かれたWebSocketに接続する部分が実行されます。あとは受け取った画像をhtml5のimgタグで表示するというものです。こちらは、参考にさせていただいたWebページのものを少し改良して使用させていただきました。

 

f:id:kdn-1017-wttd:20181129222849p:plain

Live映像

 

今回はここまでとなります。最初にシステムの概要図を再掲しましたが、このままだとすべてRaspberry Pi上で完結しています。そこで次回ではWebサーバーを別のPC上にたてて、そこにRaspberry Piから画像データを送信し、クライアントからの要求に対して受け取った画像データを送るというように構成を変更するところまでできればと思っています。

 

参考

 

websocketでRaspberryPiからwindowsPCに画像を送る(Python) - ロボット、電子工作、IoT、AIなどの開発記録

Tornadoを使ってブラウザをPythonの表示器代わりに使うメモ - Qiita

【ChatDeTornado】TornadoでWebSocketを使ってチャットを作る - Qiita

AjaxでバイナリのJPEG画像データを受け取って表示する - Qiita

 

Raspberry pi 3で暗視カメラシステムの構築 その1

実家のハムスターを見張る目的で暗視カメラシステムを構想中です。

ひとまずRaspberry pi 3 Model Bに赤外線カメラモジュールを接続して画像をwebサーバーに送信、webサーバー経由でどこからでも確認できる仕組みを検討しています。

 

システム構成図

思いつくままに描いてみたシステム構成が下図です。想像しながら書いた部分もあるので、問題があれば適宜変更予定。

OpenCVを使って画像認識をおこない、通知機能や録画機能の自動化をできればいいかなと考えています。Herokuの負荷や安全性については全く未調査なので、そちらもおいおい行う予定。Herokuでダメなら安いサーバー1つくらいレンタルしてもいいかな。

f:id:kdn-1017-wttd:20181111094136j:plain

 

暗視カメラ

Amazonで購入。LED赤外線投光器付きの赤外線カメラモジュールにしました。Raspberry Piのカメラ専用端子にリボンケーブルで接続して使うタイプです。

特に問題なく接続できたのですが、ドライバをmodprobeコマンドで毎回手動で読み込まないと認識してくれなかったため「/etc/modules-load.d/moduled.conf」の末尾行に「bcm2835-v4l2」と追記して、起動時にドライバを自動で読み込むようにしました。

OpenCVの準備

python3を使用する予定なのでpythonからOpenCVを使用できるようにします。お手軽なのでpipからインストールする方法にしました。ソースコードからRaspberry pi上でbuildする方法もあります。一応Linux上でbuildしたこともあるのですが、Raspberry Piでbuildすると熱暴走などにも気を付けないといけません。OpenCVに明るい人なら、最新版を必要な機能だけ最適な状態でインストールできるので自前buildもありです。

 まずはOpenCVに必要なモジュールのインストール。

sudo apt install cmake git libgtk2.0-dev pkg-config libavcodec-dev libavformat-dev libswscale-dev python-dev python-numpy libtbb2 libtbb-dev libjpeg-dev libpng-dev libtiff-dev libjasper-dev libdc1394-22-dev

 続いてpythonOpenCVのインストール

pip3 install opencv-python

 この状態でpython側からopencvをインポートするとライブラリがありませんというエラーが何度か表示されるので、さらに追加でライブラリをインストールしていきます。

エラー時に表示されるメッセージをよく読んで検索すれば必要なライブラリが判断できるので、先ほどと同様に「apt」コマンドでインストールしていきましょう。

警告が出たライブラリをメモし忘れていたので以下一例です。ご参考まで。

sudo apt install libcblas-dev libatlas3-base libilmbase12 libqtgui4 libqt4-test

テスト用にカメラから画像を取得して表示する簡単なスクリプトを動かします。

import numpy as np
import cv2

cap = cv2.VideoCapture(0)

while(cap.isOpened()):
    ret, frame = cap.read()
    if ret == True:
        cv2.imshow('frame', frame)
        if cv2.waitKey(1) & 0xFF == ord('q'):
            break
    else:
        break

cap.release()
cv2.destroyAllWindows()

無事に動画が表示されれば成功です。

 次回へ続きます。

参考

Raspberry Pi 3 で専用カメラモジュールを使う

Raspberry Pi3でカメラモジュールをOpenCVで使う - 電気のDIY

PythonでOpenCVを呼び出して、Raspiカメラに写る顔を認識してみた : 試行錯誤な日々

Python3でSeleniumを利用してwebサイトから情報抽出

 映画が好きでちょくちょく映画館に行くのですが、それほど頻度が多くないので見たい映画を見逃すことがありました。

鳥取県在住なので映画館はほぼ候補がなく、1つだけ確認しておけばいいのでどうせなら自動化してやろうと思いスクリプトを自作してみました。

Python3とSeleniumを使ってブラウザを自動操作し、映画館の上映情報を取得するというスクリプトの備忘録兼まとめ記事です。

 

 

Seleniumのインストール

SeleniumはプログラムからWebブラウザを操作するツールです。Webアプリ開発でテストに利用したり、Webから情報を抽出して利用するWebスクレイピングで利用されます。今回はpython3から動かして利用しました。

pipでインストールできますので導入はとても簡単です。以下コマンドでパッケージをインストールしてください。

pip install selenium

 

 WebDriverをインストール

SeleniumWebブラウザを操作するために必要なソフトをインストールします。今回はChromeを利用しました。2018/09/27現在最新のドライバー2.42を利用すればすべてのバージョンのChromeを操作できるようです。リンク先のDownloadsからたどっていけばダウンロードできます。

ChromeDriver - WebDriver for Chrome

f:id:kdn-1017-wttd:20180927194437p:plain

 

インストールしたら任意のディレクトリにドライバーを配置してください。配置したパスはpythonのコードで指定するので覚えておきましょう。

 

Python3のソースコード

以下が作成したPython3のスクリプトになります。

 「Seleniumの設定」のところで"--headless"と指定しているのは、ブラウザをウィンドウで開かないようにしています。この指定をしないとスクリプトを実行する度に、Crhomeが起動します。Seleniumの動作を確認したいときはコメントアウトすると動きが分かりやすいです。

 DRIVER_PATHに先ほどインストールしたChrome用WebDriverのパスを指定し、driver変数を用意してブラウザを操作しています。URLには情報を抽出したいWebサイトのurlを指定します。

実際にブラウザを操作しているのがメソッドfind_elements_by_css_selector()のあたりです。このメソッドはWebページのCSSセレクタを指定することで、任意の要素を取得することができます。最初の呼び出しでは上映スケジュールのボタンを取得してきてクリックすることで、上映映画の一覧を表示する動作をさせています(興味のある方は指定したURLにアクセスして週間上映スケジュールボタンを押してみてください)。CSSセレクタに何を指定するかはWebページを解析して判断する必要があります。ブラウザの開発者ツールなどを利用して解析してみましょう。

for文のところで上映映画のタイトルを取得してmessageに格納し、Line Notityで通知を送っています。Line Notifyについては以前の記事で紹介しているのでよろしければそちらをご覧ください。

 

doinaka-it-zakki.hatenablog.com

 

from selenium import webdriver
import line_notify as ln

DRIVER_PATH = "D:/driver/chromedriver.exe"
URL = "https://www.smt-cinema.com/site/hiezu/index.html"

# LINE notifyによる通知設定
lne = ln.LineNotify('LINEのAPIトークン', 'https://notify-api.line.me/api/notify')

# Seleniumの設定
options = webdriver.chrome.options.Options()
options.add_argument("--headless")

driver = webdriver.Chrome(DRIVER_PATH, chrome_options=options)
driver.get(URL)
elem_schedule_btn = driver.find_elements_by_css_selector("#schedule > ul > li") if len(elem_schedule_btn) > 1: elem_schedule_btn[1].click() movie_list = driver.find_elements_by_css_selector(".weekly > div") message = '' for movie_elem in movie_list: title = movie_elem.find_elements_by_css_selector(".weekly > div > div > div > p.title") message += title[0].text + '\n' # LINEに週間上映映画情報を送信 res = lne.send_message(message)

driver.close()
driver.quit()

 

 無事に映画の上映リストを取得して、LINEに通知を送ることが出来ました。余談ですが私は毎日12:00に通知がくるようにスクリプトの定期実行を設定しています。

 

参考

 PythonとSeleniumを使ったブラウザ自動操作 – 名古屋のWebシステム開発 iNet Solutions

SeleniumからHeadless Chromeを使ってみた

PythonでSeleniumを使ってスクレイピング (基礎)

 

Ubuntu16.04でRuby on Rails5の開発環境構築

Vagrant環境のUbuntu16.04でRuby on Rails5の開発環境を整えた作業の備忘録です。

なお作業は基本的に一般ユーザーの権限で行い、必要に応じてsudoコマンドを使用しています。

環境は以下

  • Ubuntu16.04
  • Ruby2.5.1
  • Rails5.2.1

 

 

Ubuntuデスクトップ環境の日本語化

キーボードをJIS配列に変更

Vagrantではubuntu/xenial64のboxを利用しましたが、日本語化されているないのでまずは日本語化を行いました。導入の方法は前回の記事を参照ください。

以下のコマンドを実行してキーボードをJIS配列に変更します。

$ sudo dpkg-reconfigure keyboard-configuration

下のような画面になるのでJIS配列になるように選択していきます。

f:id:kdn-1017-wttd:20180818152209p:plain

  1. Generic 105-key (Intl) PC
  2. Japanese
  3. Japanese
  4. The default for the keyboard layout
  5. No compose key
  6. NO

次に右上の歯車ボタンからSystem Settingsを選択して、検索からText Entryの設定に入ります。

f:id:kdn-1017-wttd:20180818153125p:plain

 

Input sources to useの欄の左下にある+-ボタンを操作してJapaneseだけになるようにします。下の画像は各種設定終了後のためMozcも追加されていますが無視してください。また、Switch to next source using:のボックスを選択して「半角/全角」ボタンを入力してください。これで、半角/全角ボタンで英字入力と日本語入力を切り替えられるようになります。

f:id:kdn-1017-wttd:20180818153602p:plain

日本語化

デスクトップの日本語化対応を行います。

$ sudo apt-get install language-pack-ja-base language-pack-ja
$ localectl set-locale LANG=ja_JP.UTF-8 LANGUAGE="ja_JP:ja" 
$ source /etc/default/locale 

続いて、先ほどと同じSystem SettingsかたLanguage Supportを選択します。

LanguageのタブのLanguage for menus and windowsで日本語を一番上に持っていきます。ログインし直すか、再起動すればデスクトップが日本語化されます。

日本語入力対応

日本語入力のためのインプットフレームワークパッケージをインストールします。

$ sudo apt-get install fcitx fcitx-mozc

インストールが終わったら先ほどのLanguage Supportの設定からキーボード入力に使うIMシステムをfcitxに変更してください。

f:id:kdn-1017-wttd:20180818160140p:plain

もう一度ログインしなおしてください。

ログインしたら、右上のキーボードのマークをクリックして「現在の入力メソッドの設定」を選択します。設定画面が開いたら、Input Methodから「キーボード-日本語」「Mozc」の二つを+ボタンを押して追加します。以下の画像のようになっていればよいです。

f:id:kdn-1017-wttd:20180818160640p:plain

 

Rails5の導入

Rubyの準備

 まずはRubyのバージョン管理ができるrbenvをインストールします。だたし、aptの標準リポジトリだとrbenvのバージョンが古いためruby2.3までしかインストールできません。そこでgitから直接rbenvを導入します。rbenvとrubyをインストールするためのruby-buildをgitリポジトリからcloneしてきます。

$ git clone https://github.com/rbenv/rbenv.git ~/.rbenv
$ git clone https://github.com/sstephenson/ruby-build.git .rbenv/plugins/ruby-build

続いて、.bashrcに必要な記述を追加します。sourceコマンドで現在のシェルで記述を有効化します。.bashrcはシェルを起動するたびに読み込まれるファイルです。以降はターミナルを開くたびに自動的に読み込まれるのでsourceコマンドは必要ありません。

$ echo 'export PATH="~/.rbenv/bin:$PATH"' >> ~/.bashrc
$ echo 'eval "$(rbenv init -)"' >> ~/.bashrc
$ source ~/.bashrc

これでrubyをインストールしてversion管理する準備が整ったので、必要なversionのインストールを行います。今回はruby 2.5.1です。rbenvでインストール可能なrubyのversionは「rbenv install -l」で確認することができます。

$ rbenv install -l
$ rbenv install 2.5.1
$ rbenv global 2.5.1

今回は2.5.1をインストールしましたが、rbenvを利用しているため必要に応じてversionを切り替えることも可能です。その都度インストールすればよいです。最後のglobalで現在使用するrubyのversionを指定しています。globalをlocalにすることでカレントディレクトリで利用するversionの指定もできるようです。

 

Railsの導入

最初に、rubyのパッケージ(gem)依存性管理を行うbundlerをインストールします。

$ gem install bundler

続いてrailsの開発環境をテストするディレクトリを作成して、初期化します。本開発を行う場合プロジェクト名などをディレクトリ名にすればよいと思います。

$ mkdir ~/rails-test
$ cd ~/rails-test
$ bundle init

Gemfileが作成されるので、ここにrailsを追加します。Gemfileは必要なパッケージを記述しておくことで簡単にパッケージをインストールするためのファイルです。最終行がrailsのパッケージ追加を指定する記述です。

# frozen_string_literal: true

source "https://rubygems.org"

git_source(:github) {|repo_name| "https://github.com/#{repo_name}" }

gem "rails", '~> 5.0'

準備ができたのでプロジェクトのディレクトリにrailをインストールします。Gemfileを読み込んでパッケージをインストールするためにbundleを利用します。プロジェクトにだけパッケージを導入するためににインストール先の指定をvendor/bundleを指定します。この指定を行わない場合、現在使用しているruby環境すべてにパッケージがインストールされてしまいます。1度指定しておけば、同じディレクトリでは次回以降は自動で指定されます。

$ bundle install --path vendor/bundle

 railsの初期化をサブディレクトリを作成して行います。

$ bundle exec rails new --skip-bundle rails-app

サブディレクトリに必要なパッケージをインストールします。

$ cd rails-app
$ bundle install --path vendor/bundle

railsを起動します。

$ bundle exec rails server

起動出来たらwebブラウザからアクセスしてみましょう。アドレスバーに「localhost:3000」と入力します。以下の画面が表示されたら成功です。

f:id:kdn-1017-wttd:20180818180727p:plain

 

以上で終了です。

 

参考

WindowsにVagrantとVirtualBoxを使ったUbuntu GUI開発環境を構築する

Ubuntu 16.04にRuby 2.5.0をインストールするメモ

Ubuntu 16.04 (Xenial) でRoRをはじめる

仮想化ツールを管理するVagrantでUbuntu16.04を導入したお話

今回は仮想化ツールのラッパーであるVagrantを利用して、仮想化ソフトVirtualBoxを用いてUbuntu16.04を導入する方法を備忘録兼ねてまとめたいと思います。

環境は以下です。

 

Vagrantの導入手順

Vagrantについて

VagrantVirtualBox,VMware等の仮想化ソフトを管理して仮想環境の導入時の設定や、開発環境の共有等を簡潔にしてくれる仮想化ソフトのラッパーツールです。以前から存在を知っていたのですが、私はVirtualBox単体で利用していました。先日、プライベートでRuby on Rails5の開発環境が必要になり、Linuxで環境構築を行いたかったため、これを機にVagrantを利用することにしました。

www.vagrantup.com

ViartualBoxとVagrantのダウンロード

まずは公式ページからそれぞれダウンロードしましょう。上記のリンクから下記の画面に移動してください。それぞれ利用しているOSに合わせたものをダウンロードしてインストールしてください。

f:id:kdn-1017-wttd:20180816213917p:plain

 

f:id:kdn-1017-wttd:20180816213936p:plain

 VirtualBoxVagrantのVersionによってはうまく動かないことがあるようですが、私の場合はどちらも最新で問題ありませんでした。 2018/8/15現在でVirtualBox5.2.16,Vagrant2.1.2でした。もしうまくいかない場合はVirtualBoxのVersionを落とすと良いようです。

 

 Vagrantの初期化

 両方のインストールが終了したらコマンドプロンプトで作業を行います。まずはVagrantが正しくインストールされていることを確認しましょう。

vagrant -v
Vagrant 2.1.2

 続いて、仮想マシンを作成するためのディレクトリを作って移動します。

私の場合はVagrant/machineというディレクトリを作成しました。以下、一例です。machineではなく、目的に応じた分かりやすい名称にすれば良いと思います。

mkdir Vagrant
cd Vagrant
mkdir machine
cd machine

移動したら、作業用ディレクトリの初期化を行います。初期化用のコマンドを実行すると下記のように表示され、「Vagrantfile」というVagrantの設定用ファイルが作成されます。

vagrant init A `Vagrantfile` has been placed in this directory. You are now ready to `vagrant up` your first virtual environment! Please read the comments in the Vagrantfile as well as documentation on `vagrantup.com` for more information on using Vagrant.

Ubuntu16.04の追加

Vagrantではboxと呼ばれるOSのイメージをまとめたファイルを追加することで仮想環境の構築を行います。boxはVagrantCloudというwebサイトで公開されているものを確認することができます。

app.vagrantup.com

私はこの中からubuntu/xenial64のboxを追加し利用しました。

f:id:kdn-1017-wttd:20180816221531p:plain

boxを追加するには先ほど作成したVagrantfileを編集を行います。

15行目を以下のように変更します。

config.vm.box = "ubuntu/xenial64"

 ファイルを編集したら以下のコマンドでboxを追加することができます。

vagrant box add ubuntu/xenial64

導入したboxの確認は以下のコマンドで行えます。

vagrant box list
ubuntu/xenial64 (virtualbox, 20180814.0.0)

続いてネットワークの設定を行います。hostマシン(今回はwindows10)から仮想マシンにアクセスする際のIPアドレスなどが決定されます。Vagrantfileの35行目のコメントを解除します。

config.vm.network "private_network", ip: "192.168.33.10"

IPアドレスは変更することもできるようです。

 最後にGUI環境構築のための設定を行います。Vagrantfileの52行目から58行目までのコメントを解除して以下のように設定し直しました。

config.vm.provider "virtualbox" do |vb|
    vb.gui = true
    vb.memory = "4096"
    vb.cpus = 2
    vb.customize [
      "modifyvm", :id,
      "--vram", "256",
      "--clipboard", "bidirectional",
      "--accelerate3d", "on",
      "--hwvirtex", "on",
      "--nestedpaging", "on",
      "--largepages", "on",
      "--ioapic", "on",
      "--pae", "on",
      "--paravirtprovider", "kvm",
    ]
  end

 memoryやcpusの設定はお使いのコンピュータのスペックに合わせて変更しても良いでしょう。

 

Ubuntu16.04の起動

仮想マシンを起動します。

vagrant up

 起動後は仮想マシン側でGUI環境を構築する手順を行います。

VirtualBox仮想マシンのウィンドウが開いているはずなので、ログインしましょう。

ユーザー名、パスワードともにvagrantとなっています。

vagrantssh接続機能でも操作可能です。その場合は以下のコマンドを実行しましょう。

vagrant ssh

 Ubuntu側で以下のコマンドを実行して、デスクトップ環境を構築します。

$ wget -q https://www.ubuntulinux.jp/ubuntu-ja-archive-keyring.gpg -O- | sudo apt-key add -
$ wget -q https://www.ubuntulinux.jp/ubuntu-jp-ppa-keyring.gpg -O- | sudo apt-key add -
$ sudo wget https://www.ubuntulinux.jp/sources.list.d/xenial.list -O /etc/apt/sources.list.d/ubuntu-ja.list

$ sudo apt-get update 
$ sudo apt-get upgrade

$ sudo apt-get install ubuntu-desktop

 パッケージの導入が終了したら、仮想マシンを再起動します。コマンドプロンプト側で以下のコマンドを実行します。

vagrant reload

再起動後Ubuntuのデスクトップ画面が表示されていれば成功です。

f:id:kdn-1017-wttd:20180816225744p:plain

仮想マシンを停止するときはUbuntu側からシャットダウンを行うか、コマンドプロントで以下のコマンドを実行します。

vagrant halt

 

VagrantでUbuntu16.04の導入は以上で終了です。近いうちにRails5の開発環境導入手順もまとめたいと思います。

参考

WindowsにVagrantとVirtualBoxを使ったUbuntu GUI開発環境を構築する

【Vagrant】vagrantを導入しよう

HTML5でBootstrap4を利用する場合のテンプレート

HTML5でBootstrap4を利用する場合のテンプレート用htmlファイルを共有したいと思います。今回はGitHubのGistを利用してみました。

 これまで、Bitbucketを中心に利用していたんですが。公開できるものはGitHubで積極的に公開していきたいと思います。