Raspberry Pi + RubyでLチカ

 前回でRaspberry Piの初期設定がだいたい終わったので、引き続きIoTエンジニア養成読本のハンズオンの内容をベースにLチカ(LED点滅)をやってみました。

gihyo.jp

 書籍の例ではPythonが使われていますが、そのままやっても面白くないのでRubyで挑戦しました。mrubyでやりたかったのですが、mrubyでGPIOを操作するためのライブラリのmruby-WiringPiとmruby-raspberryを試してみたもののビルドがうまくいかなかったので、ひとまずCRubyでやってみます。

まずは常時点灯

 まずは特に制御はせずに、常時LEDが点灯するように接続してみます。下記の図のように接続するとLEDが点灯します。書籍で使われている抵抗は330Ωですが、購入した抵抗セットの中には含まれてなかったので、1kΩのものを使っています。また、回路図は Fritzing を使って作成しました。

Fritzing
http://fritzing.org/home/

f:id:akanuma-hiroaki:20170504133715p:plain:w300:left

f:id:akanuma-hiroaki:20170504135253j:plain:w300

赤:1番ピン
黒:6番ピン(GND)

コマンドラインからLED点灯

 次にコマンドラインから直接GPIIOを操作してみます。GPIOへの入出力はOSの擬似ファイルとして提供されているようなので、それを操作します。

 まず使用するGPIOのピンをexportファイルに対して設定します。今回はGPIO22のピンを使います。

f:id:akanuma-hiroaki:20170504134511p:plain:w300:left

黒:15番ピン(GPIO22)
赤:6番ピン(GND)


pi@raspberrypi:~ $ ls -l /sys/class/gpio/export 
-rwxrwx--- 1 root gpio 4096 May  1 05:36 /sys/class/gpio/export
pi@raspberrypi:~ $ 
pi@raspberrypi:~ $ file /sys/class/gpio/export                                                                                                                                                                                                
/sys/class/gpio/export: ERROR: cannot read `/sys/class/gpio/export' (Input/output error)
pi@raspberrypi:~ $ 
pi@raspberrypi:~ $ cat /sys/class/gpio/export                                                                                                                                                                                                 
cat: /sys/class/gpio/export: Input/output error
pi@raspberrypi:~ $ 
pi@raspberrypi:~ $ echo 22 > /sys/class/gpio/export                                                                                                                                                                                           
pi@raspberrypi:~ $ 

 するとそのGPIOピンを操作するためのディレクトリが作られるので、まずは入出力の向きを指定します。今回はLEDの点灯のための出力なので、outを設定します。

pi@raspberrypi:~ $ ls -l /sys/class/gpio/gpio22/direction 
-rwxrwx--- 1 root gpio 4096 May  1 06:16 /sys/class/gpio/gpio22/direction
pi@raspberrypi:~ $ 
pi@raspberrypi:~ $ file /sys/class/gpio/gpio22/direction                                                                                                                                                                                      
/sys/class/gpio/gpio22/direction: ASCII text
pi@raspberrypi:~ $ 
pi@raspberrypi:~ $ cat /sys/class/gpio/gpio22/direction                                                                                                                                                                                       
in
pi@raspberrypi:~ $ 
pi@raspberrypi:~ $ echo out > /sys/class/gpio/gpio22/direction                                                                                                                                                                                
pi@raspberrypi:~ $ 

 そしてそのピンの値を1にすると点灯状態になり、0にすると消灯状態に戻ります。

pi@raspberrypi:~ $ ls -l /sys/class/gpio/gpio22/value 
-rwxrwx--- 1 root gpio 4096 May  1 06:16 /sys/class/gpio/gpio22/value
pi@raspberrypi:~ $ 
pi@raspberrypi:~ $ file /sys/class/gpio/gpio22/value 
/sys/class/gpio/gpio22/value: ASCII text
pi@raspberrypi:~ $ 
pi@raspberrypi:~ $ cat /sys/class/gpio/gpio22/value 
0
pi@raspberrypi:~ $ 
pi@raspberrypi:~ $ echo 1 > /sys/class/gpio/gpio22/value 
pi@raspberrypi:~ $ 
pi@raspberrypi:~ $ echo 0 > /sys/class/gpio/gpio22/value 
pi@raspberrypi:~ $ 

RubyスクリプトでLチカ

 ではRubyスクリプトでLチカをやってみたいと思います。まずはRaspberry Pi上にRuby環境を作るために、gitとrbenvと、その他必要なライブラリをインストールします。

pi@raspberrypi:~ $ sudo apt-get install git
pi@raspberrypi:~ $ sudo apt-get install rbenv
pi@raspberrypi:~ $ echo 'export PATH="$HOME/.rbenv/bin:$PATH"' >> .bashrc
pi@raspberrypi:~ $ echo 'eval "$(rbenv init -)"' >> .bashrc
pi@raspberrypi:~ $ 
pi@raspberrypi:~ $ tail .bashrc
# sources /etc/bash.bashrc).
if ! shopt -oq posix; then
  if [ -f /usr/share/bash-completion/bash_completion ]; then
    . /usr/share/bash-completion/bash_completion
  elif [ -f /etc/bash_completion ]; then
    . /etc/bash_completion
  fi
fi
export PATH="$HOME/.rbenv/bin:$PATH"
eval "$(rbenv init -)"
pi@raspberrypi:~ $ 
pi@raspberrypi:~ $ git clone https://github.com/rbenv/ruby-build.git ~/.rbenv/plugins/ruby-build
Cloning into '/home/pi/.rbenv/plugins/ruby-build'...
remote: Counting objects: 7534, done.
remote: Compressing objects: 100% (48/48), done.
remote: Total 7534 (delta 36), reused 0 (delta 0), pack-reused 7483
Receiving objects: 100% (7534/7534), 1.54 MiB | 850.00 KiB/s, done.
Resolving deltas: 100% (4567/4567), done.
Checking connectivity... done.
pi@raspberrypi:~ $ 
pi@raspberrypi:~ $ sudo apt-get install -y libssl-dev libreadline-dev

 そしてruby2.4.1をインストール。

pi@raspberrypi:~ $ rbenv install 2.4.1                                                                                                                                                                                                        
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
        LANGUAGE = (unset),
        LC_ALL = (unset),
        LC_CTYPE = "UTF-8",
        LANG = "en_GB.UTF-8"
    are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_GB.UTF-8").
Downloading ruby-2.4.1.tar.bz2...
-> https://cache.ruby-lang.org/pub/ruby/2.4/ruby-2.4.1.tar.bz2
Installing ruby-2.4.1...
Installed ruby-2.4.1 to /home/pi/.rbenv/versions/2.4.1
pi@raspberrypi:~ $ 
pi@raspberrypi:~ $ rbenv global 2.4.1
pi@raspberrypi:~ $ 
pi@raspberrypi:~ $ rbenv version
2.4.1 (set by /home/pi/.rbenv/version)
pi@raspberrypi:~ $ 
pi@raspberrypi:~ $ ruby -v
ruby 2.4.1p111 (2017-03-22 revision 58053) [armv7l-linux-eabihf]

 gemの管理にはbundlerを使います。

pi@raspberrypi:~ $ gem install bundler
Fetching: bundler-1.14.6.gem (100%)
Successfully installed bundler-1.14.6
Parsing documentation for bundler-1.14.6
Installing ri documentation for bundler-1.14.6
Done installing documentation for bundler after 27 seconds
1 gem installed
pi@raspberrypi:~ $ 
pi@raspberrypi:~ $ bundle init
Writing new Gemfile to /home/pi/Gemfile
pi@raspberrypi:~ $ 

 GPIOの操作にはroot権限が必要なので、sudoでbundlerが使えるように、visudoでrbenvのパスを追加しておきます。

pi@raspberrypi:~ $ sudo visudo

secure_path に /home/pi/.rbenv/shims を追加

 そして今回はRubyからGPIOを操作するために、Pi Piper を使用してみます。

github.com

 Gemfileに pi_piper の記述を追加してbundle installします。

pi@raspberrypi:~ $ vi Gemfile 
pi@raspberrypi:~ $ 
pi@raspberrypi:~ $ cat Gemfile
# frozen_string_literal: true
source "https://rubygems.org"

gem 'pi_piper'
pi@raspberrypi:~ $ 
pi@raspberrypi:~ $ bundle install --path vendor/bundle
Fetching gem metadata from https://rubygems.org/...........
Fetching version metadata from https://rubygems.org/.
Resolving dependencies...
Installing eventmachine 1.0.9 with native extensions
Installing ffi 1.9.18 with native extensions
Using bundler 1.14.6
Installing pi_piper 2.0.0
Bundle complete! 1 Gemfile dependency, 4 gems now installed.
Bundled gems are installed into ./vendor/bundle.
pi@raspberrypi:~ $ 

 ここまででスクリプトを実行する準備はできたので、スクリプトを用意します。内容は下記の通り。

require 'bundler/setup'
require 'pi_piper'

pin = PiPiper::Pin.new(pin: 22, direction: :out)

loop do
  pin.on
  sleep 1
  pin.off
  sleep 1
end

 GPIOナンバーと入出力の方向を指定してPinオブジェクトを生成し、onメソッドで点灯、offメソッドで消灯しています。実行にはroot権限が必要なので、sudoでbundler経由でスクリプトを実行します。

pi@raspberrypi:~ $ sudo bundle exec ruby led-flash.rb

youtu.be

スイッチの入力検出

 では次にタクトスイッチを追加して、その入力を検出してみたいと思います。

f:id:akanuma-hiroaki:20170504135109p:plain:w300:left

f:id:akanuma-hiroaki:20170504135454j:plain:w300

赤:15番ピン(GPIO22)
黒:6番ピン(GND)
黄:16番ピン(GPIO23)


require 'bundler/setup'                                    
require 'pi_piper'                                         
                                                           
pin = PiPiper::Pin.new(pin: 23, direction: :in, pull: :up) 
                                                           
loop do                                                    
  pin.read                                                 
  if pin.value == 0                                        
    puts 'The switch has been pushed.'                     
  end                                                      
  sleep 1                                                  
end                                                        

 GPIOナンバーは23で、入出力の向きはinを指定します。プルアップ・プルダウン抵抗はプルアップを指定します。readメソッドで入力値を読み取って、ボタンが押されたら(valueが0だったら)コンソールにメッセージを出力します。

スイッチ押下中だけLED点灯

 次にスイッチの入力とLEDへの出力を組み合わせて、スイッチを押している間だけLEDを点灯させます。

require 'bundler/setup'                                            
require 'pi_piper'                                                 
                                                                   
led_pin = PiPiper::Pin.new(pin: 22, direction: :out)               
switch_pin = PiPiper::Pin.new(pin: 23, direction: :in, pull: :up)  
                                                                   
loop do                                                            
  switch_pin.read                                                  
  if switch_pin.off?                                               
    led_pin.on                                                     
  else                                                             
    led_pin.off                                                    
  end                                                              
  sleep(0.5)                                                       
end                                                                

 スイッチの入力値の関係がまだよくわかっていないのですが、押された時はvalueが0になり、off?メソッドがtrueになるので、その場合はLEDを点灯しています。

スイッチ押下でLEDのON/OFF切り替え

 そして最後にスイッチを押すことでLEDのON/OFFを切り替えるようにしてみます。

require 'bundler/setup'                                                                       
require 'pi_piper'                                                                            
                                                                                              
led_pin = PiPiper::Pin.new(pin: 22, direction: :out)                                          
led_pin.off                                                                                   
                                                                                              
switch_pin = PiPiper::Pin.new(pin: 23, direction: :in, pull: :up)                             
                                                                                              
loop do                                                                                       
  switch_pin.read                                                                             
  if switch_pin.value == 0                                                                    
    puts "Turn %s the LED since the switch has been pushed." % (led_pin.off? ? 'ON' : 'OFF')  
    led_pin.off? ? led_pin.on : led_pin.off                                                   
    led_pin.read                                                                              
  end                                                                                         
  sleep(0.5)                                                                                  
end                                                                                           

 スイッチが押されたらLEDの点灯状態を反転させています。ON/OFF切り替え後にreadメソッドを実行しないと現在の状態が認識されないようだったので、実行しています。

 Pi Piperではループを回して待ち受けるのではなく、watch や after メソッドを使ってイベントドリブンな形で実装することもできるようなのですが、ちょっと試した限りではうまくいかなかったので、いずれそちらの形で実装できるようにしてみたいと思います。