見出し画像

ラズベリーパイ専用アドオンボードSense HATで遊ぼう

IoTといえばセンサーですよね。といわけで今回は、ラズパイにセンサーやLEDを簡単に付けて遊べるSense HATで遊んでいきます。

Sense HATとは?

公式サイトの説明(自動翻訳)を紹介します。

Sense HATはRaspberry Pi用のアドオンボードで、特にAstro Piのミッションのために作られました- それは2015年12月に国際宇宙ステーションに発売されました - そして現在購入可能です。Sense HATには8×8 RGB LEDマトリックス、5ボタンジョイスティックがあり、以下のセンサーが含まれています。
・ジャイロスコープ
・加速度計
・磁力計
・温度
・大気圧
・湿度

このボードをラズパイに装着すると、ジョイスティックとLEDだけでなく、温度、湿度など各種センサーもついてくる超便利ボードということです。ラズパイにセンサー付けたいけど配線が分からない、という私にはうれしいボードです。

Sense HATを買おう

Sense HATを買いました。ボードの他に、スペーサーも、ちゃんと付いてました。(スペーサー:ボードとラズパイ本体の間に取り付け、ボードを安定させる支えのこと。写真中央の黒いコマゴマしてるやつ)

画像1

白い小さな四角がLEDです。8x8の64個のLEDがついてます。ジョイスティックは、右下にあります。上、下、右、左それとボタンのように押すことができます。

画像2

LEDとジョイスティックの場所

セットアップしよう

ラズパイのOS『ラズビアン』にはSense HATを認識するドライバがすでに入っています。そのため、セットアップはカンタン。Sense HAT をラズパイにつけて、起動すればそれだけで使えます。

画像3

Sense HATをラズパイに装着したらこんな感じ

一応、Sense HATのドライバを最新にしておきます。

$ sudo apt-get install sense-hat

今回使うラズパイについて

今回使うラズパイはRaspberry Pi 3 Model Bで、OSのバージョンは↓です。

$ cat /etc/debian_version
9.8
$ cat /etc/os-release
PRETTY_NAME="Raspbian GNU/Linux 9 (stretch)"
NAME="Raspbian GNU/Linux"
VERSION_ID="9"
VERSION="9 (stretch)"
ID=raspbian
ID_LIKE=debian
HOME_URL="http://www.raspbian.org/"
SUPPORT_URL="http://www.raspbian.org/RaspbianForums"
BUG_REPORT_URL="http://www.raspbian.org/RaspbianBugs"

以上で、前置きは終わりです。Sense HATで遊んでいきます。

ーーー

LEDを光らせてみよう

ラズパイ上に、以下のPythonスクリプトを書いたLightsUpLED.pyファイルを作ります。

#LightsUpLED.py

from sense_hat import SenseHat
from time import sleep
sense = SenseHat()
sense.set_pixel(1, 2, [0, 0, 255])
sleep(2)
sense.clear()

実行します。

# python 2.x で実行する場合
$ python LightsUpLED.py

# python 3.x で実行する場合
$ python3 LightsUpLED.py

左から2番目で、上から3番目のLEDが青く点灯して2秒で消えたら成功です。

ジョイスティックでLEDを操作してみよう(1)

こんどは、LEDが光る場所をジョイスティックで操作してみます。以下のPythonスクリプトを書いたjoystick.pyファイルを作り、実行します。

from sense_hat import SenseHat

sense = SenseHat()
sense.clear()
x = 3
y = 3
sense.set_pixel(x,y,100,100,100)

VMAX = 7
VMIN = 0

colNo = 0
cols = [
   [200,0,0]
   ,[0,200,0]
   ,[0,0,200]
]

chFlg = 0
while True :
   for event in sense.stick.get_events():
       print( "Debug01 {0} {1}".format( event.direction, event.action) )
       if event.direction == "down" and event.action == "pressed":
           if y < VMAX:
               y += 1
       elif event.direction == "up" and event.action == "pressed":
           if y > VMIN:
               y -= 1
       elif event.direction == "right" and event.action == "pressed":
           if x < VMAX:
               x += 1
       elif event.direction == "left" and event.action == "pressed":
           if x > VMIN:
               x -= 1
       elif event.direction == "middle" and event.action == "pressed":
           break
       if chFlg == 0:
           sense.clear()
           chFlg = 1
       elif chFlg == 1:
           sense.clear()
           sense.set_pixel(x,y,cols[colNo][0],cols[colNo][1],cols[colNo][2])
       print( "Debug02 x={0}, y={1}, chFlg={2}".format( x, y, chFlg) )

このように、ジョイスティックでLEDを操作できれば成功です。スクリプトは、ジョイスティックを押し込むと停止します。

ジョイスティックでLEDを操作してみよう(2)

今度は、LEDで文字を表示します。ジョイスティックの操作で表示する文字が変わります。スクリプトは、ジョイスティックを押し込むと停止します。

from sense_hat import SenseHat

sense = SenseHat()
sense.clear()

r = [255,0,0]
b = [0,0,255]
i = [75,0,130]
v = [159,0,255]
e = [0,0,0]
w = [255,255,255]

imageOff = [
    e,e,e,e,e,e,e,e,
    e,e,e,e,e,e,e,e,
    e,e,e,e,e,e,e,e,
    e,e,e,e,e,e,e,e,
    e,e,e,e,e,e,e,e,
    e,e,e,e,e,e,e,e,
    e,e,e,e,e,e,e,e,
    e,e,e,e,e,e,e,e
]

imageUp = [
    e,e,e,w,e,e,e,e,
    e,e,e,w,e,e,e,e,
    e,e,e,w,e,e,e,e,
    e,e,e,w,w,w,w,e,
    e,e,e,w,e,e,e,e,
    e,e,e,w,e,e,e,e,
    e,e,e,w,e,e,e,e,
    w,w,w,w,w,w,w,w
]

imageDown = [
    r,r,r,r,r,r,r,r,
    e,e,e,r,e,e,e,e,
    e,e,e,r,e,e,e,e,
    e,e,e,r,r,r,r,e,
    e,e,e,r,e,e,e,e,
    e,e,e,r,e,e,e,e,
    e,e,e,r,e,e,e,e,
    e,e,e,r,e,e,e,e
]

imageRight = [
    e,e,e,e,i,e,e,e,
    e,e,e,i,e,e,e,e,
    i,i,i,i,i,i,i,i,
    e,e,i,e,e,e,e,e,
    e,i,e,i,i,i,i,i,
    i,e,e,i,e,e,e,i,
    e,e,e,i,e,e,e,i,
    e,e,e,i,i,i,i,i
]

imageLeft = [
    e,e,e,e,v,e,e,e,
    e,e,e,v,e,e,e,e,
    v,v,v,v,v,v,v,v,
    e,e,v,e,e,e,e,e,
    e,v,e,v,v,v,v,v,
    v,e,e,e,e,v,e,e,
    e,e,e,e,e,v,e,e,
    e,e,e,v,v,v,v,v
]

imageMiddle = [
    e,b,e,b,b,b,b,b,
    b,b,b,b,e,b,e,b,
    e,b,e,b,b,b,b,b,
    e,b,e,b,e,b,e,b,
    e,b,e,b,b,b,b,b,
    e,b,b,e,e,b,e,e,
    b,b,e,e,e,b,e,e,
    e,b,e,e,e,b,e,e
]

chFlg = 0
image = imageOff
while chFlg >= 0:
    for event in sense.stick.get_events():
        print(event.direction, event.action)
        if event.direction == "down":
            image = imageDown
            chFlg = 1
        if event.direction == "up":
            image = imageUp
            chFlg = 1
        if event.direction == "right":
            image = imageRight
            chFlg = 1
        if event.direction == "left":
            image = imageLeft
            chFlg = 1
        if event.direction == "middle":
            chFlg = -1
        if chFlg != 0:
            sense.clear()
        if chFlg == 1:
            sense.set_pixels(image)
            chFlg = 0

センサーで計測しよう

湿度、温度、気圧を計測します。

from sense_hat import SenseHat
sense = SenseHat()

print( "Humidity: ", round( sense.get_humidity(),2 ) )
print( "Temperature: ", round( sense.get_temperature(),2 ) )
print( "Air pressure: ", round( sense.get_pressure(),2 ) )

実行するとこのような結果になりました。

$ python sense.py
('Humidity: ', 33.46)
('Temperature: ', 31.03)
('Air pressure: ', 1019.54)

湿度(Humidity)33.46%、温度(Temperature)31.03℃、気圧(Air pressure)1019.54 hPaでした。計測時、穏やかな天気なので気圧は安定しているようで、ほぼ標準の大気圧(1013.25 hPa)でした。温度は、ラズパイの熱が影響しているらしく実際より高い温度になりました。湿度の正確性はよくわかりません (;^_^

ARで表示しよう

以前、紹介した省スペースARモニターを使って、センサー情報を表示します。

省スペースARモニターの作り方は、こちらにあります。

上のブログ記事に書いてる、省スペースARモニターを作りar.htmlとgetInfoOutToJson.pyを以下に書き換えるれば完成です。

ar.html

<html>
    <head>
    <meta charset="utf-8">
    <meta name="viewport" content="initial-scale=1.0,user-scalable=no,maximum-scale=1,width=device-width">
    <title>aframe</title>
    <script src="https://aframe.io/releases/0.6.1/aframe.min.js"></script>
    <script src="https://unpkg.com/aframe-html-shader@0.2.0/dist/aframe-html-shader.min.js"></script>
    <script src="https://jeromeetienne.github.io/AR.js/aframe/build/aframe-ar.js"></script>

    <style type="text/css">
        #target {
        width: 128px;
        height: 128px;
        position: absolute;
        background: rgba(200,200,200,1.0);
        }
        #htmlTarget {
        position: absolute;
        z-index: 1;
        height: 100%;
        width: 100%;
        overflow: hidden;
        position: fixed;
        top: 0;
        }
    </style>

    <script>
        AFRAME.registerComponent('update-html', {
            init: function () {
            this.lettersEntity = document.getElementById('lettersEntity');
            this.lettersIndex = 0;
            this.lettersUpdateDur = 1500 / this.lettersEntity.getAttribute('material').fps;
            this.lettersTime = 0;
            this.pre = document.getElementById('pre');
            },
            tick: function (t, dt) {
            this.lettersTime += dt;
            if (this.lettersTime >= this.lettersUpdateDur) {
                var nxtSrc = "";
                var tmp = getStr();
                document.getElementById('pre').innerHTML = tmp;
                this.lettersTime = 0;
            }
            }
        });

        function getStr() {
            var wStr = "";
            var req = new XMLHttpRequest();
            req.onreadystatechange = function() {
                if(req.readyState == 4 && req.status == 200){
                    var data = JSON.parse(req.responseText);
                    var len = data.length;
                    for(var i=0; i<len; i++) {
                    switch( data[i].name ){
                        case '時刻': wStr += data[i].name + ":" + data[i].value; break;
                        default : wStr += "<br />" + data[i].name + ":" + data[i].value; break;
                    }
                    }
                }
            };
            req.open("GET", "sample.json", false);
            req.send(null);
            return( wStr );
        }
    </script>
    </head>
    <body>
    <a-scene update-html arjs="debugUIEnabled:false;">
        <a-marker preset="hiro">
        <a-entity
            id="lettersEntity"
            geometry="primitive: box;
                        width: 1.0;
                        height: 1.0;
                        depth: 0.01;"
            rotation="-90 0 0"
            position="0 0 0"
            material="
                shader: html;
                target: #target;
                transparent: true;
                ratio: width;
                fps: 1.5"
        ></a-entity>
        <a-entity position=" 0.6 0.0  0.1" rotation="0 0 0" geometry="primitive:box;width:0.2;height:0.1;depth:1.2;" material="color: #0000FF;roughness:0.1;metalness:0.5;"></a-entity>
        <a-entity position="-0.6 0.0 -0.1" rotation="0 0 0" geometry="primitive:box;width:0.2;height:0.1;depth:1.2;" material="color: #00FF00;roughness:0.1;metalness:0.5;"></a-entity>
        <a-entity position=" 0.1 0.0 -0.6" rotation="0 90 0" geometry="primitive:box;width:0.2;height:0.1;depth:1.2;" material="color: #FF0000;roughness:0.1;metalness:0.5;"></a-entity>
        <a-entity position="-0.1 0.0  0.6" rotation="0 90 0" geometry="primitive:box;width:0.2;height:0.1;depth:1.2;" material="color: #FF00FF;roughness:0.1;metalness:0.5;"></a-entity>
        </a-marker>
        <a-entity camera></a-entity>
    </a-scene>
    <!-- HTML to render as a material. -->
    <div id="htmlTarget" style="width:0px;height:0px;">
        <center>
        <div id="target">
            <div  ID="pre" style ="font-size: 10pt"></div>
        </div>
        </center>
    </div>
    </body>
</html>

getInfoOutToJson.py

# coding: UTF-8
import subprocess
import sys
import datetime
import time
from sense_hat import SenseHat
sense = SenseHat()

r = [255,0,0]
o = [255,127,0]
y = [255,255,0]
g = [0,255,0]
b = [0,0,255]
i = [75,0,130]
v = [159,0,255]
e = [0,0,0]
w = [255,255,255]

imageOff = [
    e,e,e,e,e,e,e,e,
    e,e,e,e,e,e,e,e,
    e,e,e,e,e,e,e,e,
    e,e,e,e,e,e,e,e,
    e,e,e,e,e,e,e,e,
    e,e,e,e,e,e,e,e,
    e,e,e,e,e,e,e,e,
    e,e,e,e,e,e,e,e
]

imageUp = [
    e,e,e,w,e,e,e,e,
    e,e,e,w,e,e,e,e,
    e,e,e,w,e,e,e,e,
    e,e,e,w,w,w,w,e,
    e,e,e,w,e,e,e,e,
    e,e,e,w,e,e,e,e,
    e,e,e,w,e,e,e,e,
    w,w,w,w,w,w,w,w
]

imageDown = [
    r,r,r,r,r,r,r,r,
    e,e,e,r,e,e,e,e,
    e,e,e,r,e,e,e,e,
    e,e,e,r,r,r,r,e,
    e,e,e,r,e,e,e,e,
    e,e,e,r,e,e,e,e,
    e,e,e,r,e,e,e,e,
    e,e,e,r,e,e,e,e
]

imageRight = [
    e,e,e,e,i,e,e,e,
    e,e,e,i,e,e,e,e,
    i,i,i,i,i,i,i,i,
    e,e,i,e,e,e,e,e,
    e,i,e,i,i,i,i,i,
    i,e,e,i,e,e,e,i,
    e,e,e,i,e,e,e,i,
    e,e,e,i,i,i,i,i
]

imageLeft = [
    e,e,e,e,v,e,e,e,
    e,e,e,v,e,e,e,e,
    v,v,v,v,v,v,v,v,
    e,e,v,e,e,e,e,e,
    e,v,e,v,v,v,v,v,
    v,e,e,e,e,v,e,e,
    e,e,e,e,e,v,e,e,
    e,e,e,v,v,v,v,v
]

imageMiddle = [
    e,b,e,b,b,b,b,b,
    b,b,b,b,e,b,e,b,
    e,b,e,b,b,b,b,b,
    e,b,e,b,e,b,e,b,
    e,b,e,b,b,b,b,b,
    e,b,b,e,e,b,e,e,
    b,b,e,e,e,b,e,e,
    e,b,e,e,e,b,e,e
]

def OutInfo():
    now = datetime.datetime.now()
    clock = str(now.hour) + ':' + str(now.minute) + ':' + str(now.second)
    tempera = round( sense.get_temperature(),1 )
    humidit = round( sense.get_humidity(),1 )
    path = 'sample.json'

    acceleration = sense.get_accelerometer_raw()
    x = str( round( acceleration['x'],1 ) )
    y = str( round( acceleration['y'],1 ) )
    z = str( round( acceleration['z'],1 ) )
    acc = "("+ x + "," + y + "," + z + "," + ")"

    joy = "ー"
    image = imageOff
    for event in sense.stick.get_events():
        if event.direction == "down":
            joy = "下"
            image = imageDown
        if event.direction == "up":
            joy = "上"
            image = imageUp
        if event.direction == "right":
            joy = "右"
            image = imageRight
        if event.direction == "left":
            joy = "左"
            image = imageLeft
        if event.direction == "middle":
            joy = "押"
            image = imageMiddle
    sense.set_pixels(image)

    with open( path, mode='w' ) as f:
        outStr = ''
        outStr += '['
        outStr += '{"name":"時刻","value":"' + str(clock) + '"}'
        outStr += ',{"name":"温度","value":"' + str(tempera) + '[度]"}'
        outStr += ',{"name":"湿度","value":"' + str(humidit) + '[%]"}'
        outStr += ',{"name":"操作","value":"' + joy + '"}'
        outStr += ',{"name":"加速度","value":"' + acc + '"}'
        outStr += ']'
        f.write( outStr )

for i in range(10000):
    OutInfo()
    print("Running")
    time.sleep( 0.7 )

トラブルシュート:Pythonスクリプトが動かない

文字コードの影響で、(日本語を含む)Pythonスクリプトがエラーになることがあるようなので、検証してみます。

#test.py
print("テスト")

実行するとエラーになりました。
test.pyファイルの文字コードがUTF8の場合のエラーメッセージはこちら。

$ python test.py
  File "test.py", line 2
SyntaxError: Non-ASCII character '\xe3' in file test.py on line 2, but no encoding declared; see http://python.org/dev/peps/pep-0263/ for details

メッセージに、http://python.org/dev/peps/pep-0263/を見るようにとあるので、見てみるとスクリプトを書いたファイルの文字コードを指定してねとのこと。文字コードの指定は、『#coding: {{文字コード}}』とするようです。UTF8の場合は、『#coding: UTF-8』のように書くようです。test.pyファイルの先頭に『#coding: UTF-8』と追記して、文字コードUTF-8で保存します。

#test.py
#coding: UTF-8
print("テスト")

実行してみると、確かにエラーが発生しなくなりました。

$ python test.py
テスト

おわりに

今回は、ラズベリーパイ専用のアドオンボード Sense HATで遊びました。セットアップが簡単で気に入りました。今回紹介したこと以外にも使い道ありそうなので、また何か紹介すると思います。

こんな弱小ブログでもサポートしてくれる人がいることに感謝です。