httpを用いたデバイス制御 の履歴(No.3)

更新


電気回路/zynq

概要

ブラウザや Web API を使って http または https 経由でデバイスを制御したい。

  • ブラウザから Web Form でレジスタをいじる
  • Lab View などから Web API を叩いてレジスタをいじる

目次

Web アプリのフレームワークに Sinatra を使う

いろいろ考えられるけれど、ここでは ruby 製の Sinatra を使ってみる。

理由は自分が ruby とか Rails とかに慣れていることと、Rails ほど重厚なフレームワークは必要ないこと。

ruby のセットアップ

zynq の Ubuntu 18.04LTS に rvm を入れて、そこから ruby や各種 gem を準備する。

https://www.digitalocean.com/community/tutorials/how-to-install-ruby-on-rails-with-rvm-on-ubuntu-18-04

LANG:console
$ # install rvm
$ sudo apt-get install -y gnupg2
$ gpg2 --keyserver hkp://keys.gnupg.net --recv-keys 409B6B1796C275462A1703113804BB82D39DC0E3 7D2BAF1CF37B13E2069D6956105BD0E739499BDB
$ cd /tmp
$ curl -sSL https://get.rvm.io -o rvm.sh
$ cat rvm.sh | bash -s stable
$ source ~/.rvm/scripts/rvm 
$ rvm list known
 # MRI Rubies
 [ruby-]1.8.6[-p420]
 [ruby-]1.8.7[-head] # security released on head
 [ruby-]1.9.1[-p431]
 [ruby-]1.9.2[-p330]
 [ruby-]1.9.3[-p551]
 [ruby-]2.0.0[-p648]
 [ruby-]2.1[.10]
 [ruby-]2.2[.10]
 [ruby-]2.3[.8]
 [ruby-]2.4[.5]
 [ruby-]2.5[.3]
 [ruby-]2.6[.0]
 ruby-head
 ...
$ rvm install 2.6 # ruby 2.6 のインストール → かなり時間がかかる
$ which ruby
 /home/takeuchi/.rvm/rubies/ruby-2.6.0/bin/ruby
$ ruby --version
 ruby 2.6.0p0 (2018-12-25 revision 66547) [armv7l-linux-eabihf]

Sinatra を使う

LANG:console
$ mkdir ~/sinatra_app
$ cd ~/sinatra_app
$ git init

$ # rvm の自動切り替え設定
$ echo 2.6 > .ruby-version
$ echo sinatra_app > .ruby-gemset
$ cd .
 ruby-2.6.0 - #gemset created /home/takeuchi/.rvm/gems/ruby-2.6.0@sinatra_app
 ruby-2.6.0 - #generating sinatra_app wrappers.........

$ # bundler を使って Gemfile で gem を管理する
$ gem install bundler
$ bundle init
$ cat Gemfile
 # frozen_string_literal: true
 
 source "https://rubygems.org"
 
 git_source(:github) {|repo_name| "https://github.com/#{repo_name}" }
 
 # gem "rails"
$ git add .
$ git config --global user.email "takeuchi@bk.tsukuba.ac.jp"
$ git config --global user.name "Osamu Takeuchi"
$ git commit -m "start development"

$ # Sinatra のインストール
$ cat << EOT >> Gemfile

 gem "sinatra"
 gem "sinatra-contrib"
 EOT
$ bundle install

$ # Hello, world! アプリの作成
$ cat << EOT > app.rb
 require "sinatra"
 require "sinatra/reloader"

 get "/" do
   "Hello, world!\n"
 end
 EOT
$ bundle exec ruby app.rb &
 [2019-03-28 08:29:29] INFO  WEBrick 1.4.2
 [2019-03-28 08:29:29] INFO  ruby 2.6.0 (2018-12-25) [armv7l-linux-eabihf]
 == Sinatra (v2.0.5) has taken the stage on 4567 for development with backup from WEBrick
 [2019-03-28 08:29:29] INFO  WEBrick::HTTPServer#start: pid=14700 port=4567
$ curl http://localhost:4567/
 Hello, world!
$ fg
^C
$ git add .
$ git commit -m "hello world!"

外部へ公開する

どうやら動いているみたいだけれど、 ホストPCのブラウザから zynq 機器の IP アドレスを指定して http://xxx.xxx.xxx.xxx:4567/ へアクセスしても、 「正常に接続できませんでした」と言われた。

はじめ、ファイアーウォールのせいかと勘違いしたのだけれど、 実は今の手順でビルドした Kernel には iptable の機能が 備わっておらず、ファイアーウォールなんて影も形もない状態だった。

で気づいたのは、zynq のコンソールからでも 127.0.0.1 以外の IP アドレスを指定すると

LANG:console
$ curl http://192.168.2.2:4567/ # 自分のアドレス
 curl: (7) Failed to connect to 192.168.2.2 port 4567: Connection refused

のように弾かれるのだった。これは、127.0.0.1 しか listen していない感じ。

LANG:console
$ ruby app.rb --help
 Usage: app [options]
     -p port                          set the port (default is 4567)
     -o addr                          set the host (default is localhost)
     -e env                           set the environment (default is development)
     -s server                        specify rack server/handler (default is thin)
     -q                               turn on quiet mode (default is off)
     -x                               turn on the mutex lock (default is off)
$ ruby app.rb -o 0.0.0.0 &
$ curl http://192.168.2.2:4567/
 Hello, world!

これで外部からも繋がるようになった。

ただし、ポートを 80 番にしようとすると Permission Denied と言われる。

Well known port にサーバーを建てるには sudo が必要。 加えて環境変数の読み込みも必要。

LANG:console
$ ruby app.rb -p 80
 /home/takeuchi/.rvm/rubies/ruby-2.6.0/lib/ruby/2.6.0/socket.rb:201:in `bind': Permission denied - bind(2) for 127.0.0.1:80 (Errno::EACCES)
$ sudo bash -c "source /home/takeuchi/.rvm/scripts/rvm;ruby app.rb -o 0.0.0.0 -p 80"
 (うまくいく)

開発が終わり実用段階へ到達したら -e production なども付けたいところ。

erb テンプレートを使う

app.rb

LANG:rb
require "sinatra"
require "sinatra/reloader"

get "/" do
  erb :index
end

views/index.erb

LANG:erb
Hello, world from erb! 

として、

LANG:console
$ curl http://192.168.2.2/
 Hello, world from erb!
$ git add .
$ git commit -m "hello from erb"

layout を使う

  • 個々に指定した出力が layout.erb の yield の部分にはめ込まれる。
  • @title のような変数を view 側と共有できる

app.rb

LANG:rb
require "sinatra"
require "sinatra/reloader"

get "/" do
  @title = "Device Editor"
  erb :index
end

views/layout.erb

LANG:erb
<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="utf-8" />
  <title><%= @title %></title>
</head>
<body>
  <h1><%= @title %></h1>
  <%= yield %>
</body>
</html>
LANG:console
$ curl http://192.168.2.2/
 <!DOCTYPE html>
 <html lang="ja">
 <head>
   <meta charset="utf-8" />
   <title>Device Editor</title>
 </head>
 <body>
   <h1>Device Editor</h1>
   Hello, world from erb!
 </body>
 </html>
$ git add .
$ git commit -m "layout introduced"

フォームを作成

erb から uio コマンドを呼び出し、得られたレジスタ値をフォームの input に表示する。

app.rb

LANG:ruby
require "sinatra"
require "sinatra/reloader"

get "/" do
  @title = "Device Editor"
  @regs = [
    # 表示名   name値   値の読取コマンド
    ["Reg 01", "reg1", "uio 0  0 "],
    ["Reg 02", "reg2", "uio 0  4 "],
    ["Reg 03", "reg3", "uio 0  8 "],
    ["Reg 04", "reg4", "uio 0 12 "],
  ]
  erb :index
end

views/index.erb

LANG:erb
<form method="post">
  <% @regs.each do |reg| %>
  <div class="field">
    <label class="label"><%= reg[0] %>
      <div class="control">
        <input class="input" type="text" placeholder="<%= reg[0]%>" 
               name="<%= reg[1]%>" value="<%= `#{reg[2]}` %>">
      </div>
    </label>
  </div>
  <% end %>

  <div class="field">
    <div class="control">
      <button class="button is-primary">Submit</button>
    </div>
  </div>
</form>

ブラウザから読みに行けば、フォームにレジスタの現在値が表示される。

default-form.png

LANG:console
$ git add .
$ git commit -m "device editor form created"

スタイルシートを適用する

Bulma ( https://bulma.io/ ) という css フレームワークを使ってみる。

layout に

  • viewport 指定
  • css への link
  • fontausome の script 読み込み
  • yield を section, container で囲む
  • h1 に .title を付ける

の変更を行った。

views/layout.erb

LANG:erb
<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="utf-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title><%= @title %></title>
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/bulma/0.7.4/css/bulma.min.css">
  <script defer src="https://use.fontawesome.com/releases/v5.3.1/js/all.js"></script>
</head>
<body>
  <section class="section">
    <div class="container">
      <h1 class="title">
        <%= @title %>
      </h1>
      <%= yield %>
    </div>
  </section>
</body>
</html>

フォームの html は何も変更していないが、ちゃんと今風の表示になった。

form-styled.png

LANG:console
$ git add .
$ git commit -m "style sheed applied"

フォームによる値の変更を反映する

上記のフォームで Submit ボタンを押すと、

Try this: 

post '/' do
  "Hello World"
end

が表示される。

フォームの書き換えでは "/" に対して POST が生じるのだ。

そこで app.rb を、

LANG:rb
require "sinatra"
require "sinatra/reloader"

REGS = [
  ["Reg 01", "reg1", "uio 0  0 "],
  ["Reg 02", "reg2", "uio 0  4 "],
  ["Reg 03", "reg3", "uio 0  8 "],
  ["Reg 04", "reg4", "uio 0 12 "],
]

get "/" do
  @title = "Device Editor"
  @regs = REGS.clone
  erb :index
end

post "/" do
  @title = "Device Editor"
  @regs = REGS.clone
  @regs.each do |reg|
    if params.has_key? reg[1]
      begin
        value = Integer(params[reg[1]])
        system reg[2] + value.to_s # 値を書き込む
      rescue
        reg[3] = "error"
      end        
    end
  end
  erb :index
end

とすることで、レジスタ値を変更することが可能になった。

LANG:console
$ git add app.rb
$ git commit -m "post requiest is handled"

SSL 化する?

Lab View からのアクセスを考えると、むやみに SSL 化しない方が使いやすいかも。

どちらにしても LAN 内にしか公開しないし。

認証

こちらはすべきかなあ。

https://qiita.com/hiroki_y/items/06f5780543bec988d8b7#basic%E8%AA%8D%E8%A8%BC

まあ、しばらくはなしでも。

自動起動

とにかく起動するだけだけれど、以下のようにして行えた。

Webrick をデバッグモードで外向きに建てるとか 本来はあり得ないのでしょうけれど、 とりあえず開発用ということで、 しばらくはこのままいく。

https://blog.freedom-man.com/start-stop-daemon/

/boot/fpga_init.d/99_sinatra_app

LANG:sh
#!/bin/bash

. /lib/lsb/init-functions

PIDFILE=/home/takeuchi/sinatra_app/app.pid

do_start () {
  cd /home/takeuchi/sinatra_app

  . /home/takeuchi/.rvm/scripts/rvm
  DAEMON=`which ruby`
  DAEMON_ARGS="/home/takeuchi/sinatra_app/app.rb -o 0.0.0.0 -p 80"

  echo -n "starting sinatra_app..."
  start-stop-daemon --start --background --exec $DAEMON --make-pidfile --pidfile $PIDFILE -- $DAEMON_ARGS

  result=$?
  if [ $result != "0" ]; then
    pid=`cat $PIDFILE`
    echo "daemon is already running. (pid=${pid})"
    exit 1
  else
    echo
  fi
}

do_stop () {
  echo -n "stopping sinatra_app..."
  start-stop-daemon --stop --pidfile $PIDFILE
  result=$?
  if [ $result != "0" ]; then
    pid=`cat $PIDFILE`
    echo "daemon is not running. (check $PIDFILE)"
    exit 1
  else
    echo
  fi
  rm -f $PIDFILE
}

case $1 in
  start)
    do_start
    ;;
  stop)
    do_stop
    ;;
  *)
    echo "Usage: $0 {start|stop}"
    exit 2
    ;;
esac

コメント・質問





Counter: 2884 (from 2010/06/03), today: 6, yesterday: 1