PySide(3)

Qt Designerで作成した .ui ファイルには、フォームとコントロール全ての情報がxml形式で含まれていて

loader = QUiLoader()
UI = loader.load(ファイルパス名)

のように読み込むと、配置したコントロールのオブジェクトが全て含まれたUIオブジェクトが作成される。たとえばLabelコントロールのオブジェクト名を”label”とした場合

UI.label.setText(‘Foo’)

とすればラベルコントロールのテキストを変更することができる。

それだけ分かっていれば、コードのみでPySideのGUIを作成したプログラムをQt Designer版に対応させるのは簡単に行える。

ボタンオブジェクト名 = QtGui.QPushButton(ボタンテキスト)
ボタンオブジェクト名.clicked.connect(関数名)

UI.ボタンオブジェクト名.clicked.connect(関数名)

のように置き換えてしまえば良い。

Quick2Wireを使ってカラーLEDを指定した色で光らせるPySideプログラムを .ui ファイルに対応させて、更に選択した色名を画面にカラー表示させたり、クリックしたボタンの色を変えたりできるように修正すると、最終的にプログラムは以下のようになった。

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

import sys
import os
sys.path.append('/usr/lib/python3/dist-packages')
from PySide import QtCore, QtGui
from PySide.QtCore import Qt
from PySide.QtUiTools import QUiLoader
from quick2wire.gpio import pins, Out

CURRENT_PATH = os.path.dirname(__file__)
COLOR_TBL = ((Qt.black,  'BLACK'), 
             (Qt.red,    'RED'),
             (Qt.green,  'GREEN'),
             (Qt.yellow, 'YELLOW'),
             (Qt.blue,   'BLUE'),
             (Qt.magenta,'MAGENTA'),
             (Qt.cyan,   'CYAN'),
             (Qt.white,  'WHITE'))
BUTTON_COLOR = (Qt.red, Qt.green, Qt.blue)

class ColorLED:
    led = (pins.pin(0, direction=Out),
           pins.pin(1, direction=Out),
           pins.pin(2, direction=Out))
    def __init__(self):
        for e in self.led:
            e.open();
    def __del__(self):
        for e in self.led:
            e.close();
    def set(self, color):
        self.led[0].value = color & 0x01
        self.led[1].value = color & 0x02
        self.led[2].value = color & 0x04

class Window(QtGui.QMainWindow):
    def __init__(self, parent=None):
        super(Window, self).__init__(parent)
        loader = QUiLoader()
        uiFilePath = os.path.join(CURRENT_PATH, 'QtColorLED.ui')
        self.UI = loader.load(uiFilePath)
        self.setCentralWidget(self.UI)
        self.btnRGB = (self.UI.pushButton_r,
                       self.UI.pushButton_g,
                       self.UI.pushButton_b)
        self.btnRGB[0].clicked.connect(lambda: self.click_btn(0))
        self.btnRGB[1].clicked.connect(lambda: self.click_btn(1))
        self.btnRGB[2].clicked.connect(lambda: self.click_btn(2))
        self.led = ColorLED()
        self.color = 0x01
        self.pal = self.UI.pushButton_r.palette()
        self.click_btn(0)
    def click_btn(self, n):
        self.color = self.color ^ (0x01 << n)
        self.led.set(self.color)
        pal = QtGui.QPalette()
        pal.setColor(QtGui.QPalette.Foreground, COLOR_TBL[self.color][0])
        self.UI.label.setPalette(pal)
        self.UI.label.setText(COLOR_TBL[self.color][1])
        if self.color & (0x01 << n) != 0:
            pal.setColor(self.btnRGB[n].backgroundRole(), BUTTON_COLOR[n])
            self.btnRGB[n].setPalette(pal)
        else:
            self.btnRGB[n].setPalette(self.pal)

if __name__ == '__main__':
    app = QtGui.QApplication(sys.argv)
    win = Window()
    win.show()
    sys.exit(app.exec_())

実行してボタンをクリックすると、ボタンの色が変わってボタンの組み合わせによる色でLEDが光り、同時に色の名前が表示される。

範囲を選択_0202014-11-13 09.33.45

範囲を選択_021

2014-11-13 09.33.58

範囲を選択_022

2014-11-13 09.34.07

範囲を選択_023

2014-11-13 09.34.18

ソースコードの説明は省くが、やっている事は一目瞭然なので必要ないだろう。
Qtを使うのはほとんど始めてなので、冗長な部分やもっとエレガントな使い方があるのだろうと思うが、Raspberry PiでGUIアプリを動かしてハードを制御するという目標は達したので良しとする。

Qtには色々と高度なコントロールが用意されているので、次はそのへんを使って何か作ってみる予定。

PySide

pythonでGUIを構築するためのライブラリ(ツールキット)はTkinter、wxPython、PyQt、PySide、PyGTK、PyGame等色々と存在しているが、種類が多くなると何を使うべきなのか悩むことになるし、ネット上での情報が分散してしまうので選択肢が多いのは良いことばかりとは限らない。

最も手軽に利用できるのは標準でPythonに含まれているTkinterで、何もインストールせずに使えるし単純なGUIならコードも短く書ける。その反面複雑なGUIになると思ったようにデザインするのが難しいという印象。これについては以前使ってみたPyGameでも似たような感じだったので、VisualStudioのようなインタラクティブにコントロールを配置できるリソースエディタが用意されているものという基準で選択すると、wxPythonとQt(PyQt、PySide)が良さそうに思える。サンプルコードの量などもその二つが豊富なようだ。

今回はその中からPySideを使ってみる事にした。同じQtのPythonバインディングであるPyQtではなくPySideを選んだのはライセンスの違いによるものだが、個人的に使うにはあまり気にしなくても良いかもしれない。機能的にはPyQtのほうが先行していて、コミュニティベースのPySideがその後を追うというのが現状らしいが、wxPythonの開発者がPySideの開発に加わったりと目まぐるしく状況が変化しているので、この先どうなるかは分からない。

QtにはQt DesignerというGUIコントロールを配置するためのリソースエディタが存在していて、PySideを使ってみようと思ったのはそれも理由の一つなのだが、最初はそれを使わずにコードだけでGUIアプリを作成してみた。

まず最初にPySideのインストールだが、Pyhton3では

$ sudo apt-get install python3-pyside

で簡単に行える。しかし、virtualenvでPythonの仮想環境を構築してある場合、このインストール方法では仮想環境下に追加されない。apt-getではなくpipでインストールすれば仮想環境下に追加されるはずなのだが、pipでのインストールは上手く行かなかった。ソースからのビルドも試してみたがやはりダメ。

import sys
sys.path.append('/usr/lib/python3/dist-packages')

と記述しておけば元のパッケージを参照できるようになるので、あまりスマートな方法ではないがとりあえずはこれで動かしておく。

Raspberry Piでやるからには何らかのハードを動かさないと面白く無いので、aitendoの100円コーナーで買った極小フルカラーLEDモジュールをGPIOに接続した。GPIOの制御にはQuick2Wireを使う。
画像1: 極小フルカラーLEDモジュール

RGBの三個のLEDが内蔵されていて、3ビットで7色の表示ができる。PWMを使えばもっと微妙な色の表現も可能だが、今回はそこまではやらない。

ウィンドウにボタンを三個表示させて、クリックするとRGBそれぞれをON/OFFできるようにした。接続はGPIO0(ピン番号17)をR、GPIO1(ピン番号18)をG、GPIO2(ピン番号27)をBに割り当てる。プログラムは以下の通り

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

import sys
sys.path.append('/usr/lib/python3/dist-packages')
from PySide import QtCore, QtGui
from quick2wire.gpio import pins, Out

class ColorLED:
    led = [pins.pin(0, direction=Out),
           pins.pin(1, direction=Out),
           pins.pin(2, direction=Out)]
    def __init__(self):
        for e in self.led:
            e.open();
    def __del__(self):
        for e in self.led:
            e.close();
    def set(self, color):
        self.led[0].value = color & 0x01
        self.led[1].value = color & 0x02
        self.led[2].value = color & 0x04

class Window(QtGui.QWidget):
    def __init__(self):
        QtGui.QWidget.__init__(self)    
        self.btn_r = QtGui.QPushButton('RED')
        self.btn_g = QtGui.QPushButton('GREEN')
        self.btn_b = QtGui.QPushButton('BLUE')
        layout = QtGui.QHBoxLayout()
        layout.addWidget(self.btn_r)
        layout.addWidget(self.btn_g)
        layout.addWidget(self.btn_b)
        self.setLayout(layout)
        self.setMinimumSize(400, 100)
        self.btn_r.clicked.connect(lambda: self.click_btn(0x01))
        self.btn_g.clicked.connect(lambda: self.click_btn(0x02))
        self.btn_b.clicked.connect(lambda: self.click_btn(0x04))
        self.led = ColorLED()
        self.color = 0
    def click_btn(self, n):
        self.color = self.color ^ n
        self.led.set(self.color)

if __name__ == '__main__':
    app = QtGui.QApplication(sys.argv)
    win = Window()
    win.show()
    sys.exit(app.exec_())

実行するとこのように表示される。

Raspberry Pi_001

次回はQt Designerを使ってもう少し複雑なGUIを作ってみる予定。

Pressure Sensor

大型の台風18号と19号が連続で発生して、それに合わせたわけでは無いと思うが秋月電子通商で大気圧センサをDIP基板化したモジュールが発売されたので、台風19号の上陸前に入手して試してみた。

インターフェースはI2CとSPIが選択可能で、Raspberry Piでは不要だがI2C使用時はジャンパーでプルアップ抵抗も有効にできる。LEDも載っていて通電すると光るのだが、これは不要な気がする。コマンドからの応答が無い時に、通電してないのか他の要因なのかが識別できる程度のメリットしか無い。まあこのモジュールをそのまま製品に使う事は無いだろうし、ブレッドボードで実験するだけなら消費電力が僅かに増えるのも気にはならないだろう。

使用方法はデータシートに記載されている通り、最初にパワーダウンモードから復帰させてあとは24ビットのデータを3つのレジスタから読み出せば良い。読み出した値を4096で割ったものが気圧(ヘクトパスカル)になる。

プログラムは以下の通り

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

import quick2wire.i2c as i2c
import time
import sys

LPS25H = 0x5c

def i2c_read(addr, reg):
    with i2c.I2CMaster() as bus:
        data = bus.transaction(
                    i2c.writing_bytes(addr, reg),
                    i2c.reading(addr, 1))
        return data[0][0]

def i2c_write(addr, reg, data):
    with i2c.I2CMaster() as bus:
        bus.transaction(i2c.writing_bytes(addr, reg, data))

def get_press(addr):
    xl = i2c_read(addr, 0x28)
    l  = i2c_read(addr, 0x29)
    h  = i2c_read(addr, 0x2A)
    p = h >> 16 | l >> 8 | xl
    return round(p / 4096, 1)

def get_temp(addr):
    l = i2c_read(addr, 0x2B)
    h = i2c_read(addr, 0x2C)
    t = h >> 8 | l
    if t & 0x8000:
        t = -((t - 1) ^ 0xFFFF)
    return round(42.5 + (t / 480.0), 2)

if __name__ == '__main__':
    whoami = i2c_read(LPS25H, 0x0F)
    if whoami != 0xbd:
        print('device incorrect.')
        sys.exit()
    i2c_write(LPS25H, 0x20, 0x90)
    time.sleep(1)
    try:
        while True:
            pha = get_press(LPS25H)
            print(pha, "hPa")
            temp = get_temp(LPS25H)
            print(temp, "C")
            time.sleep(1)
    except KeyboardInterrupt:
        print('\nbreak')            

実際に台風19号が関東上空を通過している時にこのプログラムを動かして見ていると、グングン気圧が下がっていったのが面白かった。気象庁が一時間おきに発表している東京都の計測データと照らし合わせると1hPa程度低い値になっていたが、これは観測地点の差によるものだろう。

ちなみにこのモジュール内には温度センサも内蔵されていて、同時に読み出してみるとADT7410を使用した温度センサモジュールよりも0.5℃ほど低い値を示した。正確な温度計が無いのでどちらが正しいのかは分からないのだが、大気圧センサに内蔵されている温度センサは気圧の補正用途で、あまり精度は高くないらしい。温度センサの値はだいたいの目安程度に考えたほうが良いだろう。

Quick2Wire(3)

Quick2WireでI2C通信を行うのは簡単で、基本的にはI2CMasterオブジェクトのtransactionメソッドを使用するだけで済む。このメソッドの引数としてI2Cデバイスに対する読み書きを記述する。

説明するよりも実際のコードを見たほうがたぶん早い。お馴染みの温度センサーADT7410をRaspberry PiのI2Cポートに接続して、温度を読み出すプログラムは以下のようになる。

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

from quick2wire import i2c
from time import sleep

ADT_ADDR = 0x48

def get_temp(addr):
    with i2c.I2CMaster() as bus:
        data = bus.transaction(
                    i2c.writing_bytes(addr, 0x00),
                    i2c.reading(addr, 2))
        h = data[0][0]
        l = data[0][1]
        t = (h << 8 | l) >> 3
        if t & 0x1000:
            t = -((t - 1) ^ 0x1FFF)
        return round(t / 16.0, 2)

if __name__ == '__main__':
    try:
        while True:
            print("current temperature -> %7.2f" % get_temp(ADT_ADDR))
            sleep(1)
    except IOError as err:
        print('\nIOError: {0}'.format(err))            
    except KeyboardInterrupt:
        print('\nbreak')

with文でI2CMasterオブジェクトを作成して、transactionメソッドのパラメータとしてI2CデバイスへのRead/Writeを記述する。これは複数並べて書くことができるので、戻り値は必ず二次元配列(リスト)になる。アドレスが異なるデバイスへのアクセスを記述することもできる。たとえば

with i2c.I2CMaster() as bus:
    data = bus.transaction(
                i2c.writing_bytes(0x48, 0x00),
                i2c.reading(0x48, 2),
                i2c.writing_bytes(0x5c, 0x28),
                i2c.reading(0x5c, 1),
                i2c.writing_bytes(0x5c, 0x29),
                i2c.reading(0x5c, 1),
                i2c.writing_bytes(0x5c, 0x2A),
                i2c.reading(0x5c, 1))

このように書くと、以下のようなデータが帰ってくる

0x48のレジスタ0x00の値 0x48のレジスタ0x01の値
0x5cのレジスタ0x28の値 無効
0x5cのレジスタ0x29の値 無効
0x5cのレジスタ0x2Aの値 無効

複数のI2Cデバイスがぶら下がっているような場合や、一つのI2Cデバイスでも複数のレジスタに同時にアクセスしなければならないような場合に、まとめて記述する事ができるので便利ではないかと思う。ただし、その場合はエラー処理で注意が必要になるかも知れない。

Quick2Wire(2)

前回インストールしたQuick2WireはGPIOのピンに対する入出力と、I2Cによるデバイスとの通信機能を持つので、まずはGPIOの機能について試してみる。

Pythonでプログラムを組んで入出力を行う事はもちろんできるが、Raspberry Piのコンソールから仮想デバイスに対してリダイレクトするだけでも同じことが行える。GPIOピンヘッダの11番ピンがBCMチップのGPIO17で、Quick2WireではPin0(ゼロ)として扱われているので、ここに適当なLEDと抵抗が外付けされているものとする。
ちなみにQuick2WireのGPIO番号とピンヘッダとBCMチップのGPIO番号は以下のように対応している(Raspberry Piのリビジョンがv2の場合)

GPIO 0 1 2 3 4 5 6 7
Header 11 12 13 15 16 18 22 7
BCM 17 18 27 22 23 24 25 4

この状態でコンソールから以下のコマンドを実行する

$ gpio-admin export 17

これで /sys/devices/virtual/gpio/ にgpio17というデバイスが作成されるので、あとはこのデバイスに対してIn/Outの設定と値の入出力を行えば良い。

$ echo out > /sys/devices/virtual/gpio/gpio17/direction
$ echo 1 > /sys/devices/virtual/gpio/gpio17/value

これで接続されているLEDが光るはずだ。
出力されている値はcatコマンドで確認できる。

$ cat /sys/devices/virtual/gpio/gpio17/value
1

使い終わったら以下のようにunexportしておく

$ gpio-admin unexport 17

この一連の仮想デバイスに対する操作は、実はRaspberry Piの基本機能でも可能である。Quick2Wireを使用しなくても、以下のようにすれば同じことができる。

$ echo 17 > /sys/class/gpio/export
$ echo out > /sys/class/gpio/gpio17/direction
$ echo 1 > /sys/class/gpio/gpio17/value
$ echo 0 > /sys/class/gpio/gpio17/value
$ echo 17 > /sys/class/gpio/unexport

以前のRaspbianでは管理者権限が無いとこの操作は行えなかったのだが、現在のバージョンではgpioグループに所属していれば一般ユーザでもGPIOの操作が可能になっている。デフォルトのユーザであるpiは既にgpioグループに所属しているのでそのままで使用できるが、新たに作成したユーザの場合は以下のコマンドでgpioグループに所属させる必要がある。

$ sudo adduser $USER gpio

ユーザをグループに所属させるには usermod コマンドを使用する方法もあるが、元々所属していたグループ情報が失われてしまう危険性があるので、使用しないほうが無難。

PythonでQuick2Wireを使用してGPIOの操作を行う場合は、quick2wire.gpioモジュールをインポートしてその中のPinsオブジェクトを使用すると簡単に入出力が行える。GPIO0(11番ピン)に接続されたLEDを点滅させるプログラムは、以下のようになる。

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

from quick2wire.gpio import pins, Out
from time import sleep

if __name__ == '__main__':
    try:
        with pins.pin(0, direction=Out) as led:
            while True:
                led.value = 1 - led.value
                sleep(1)
    except KeyboardInterrupt:
        print('\nbreak')

pinオブジェクトの生成時にはIn/Out以外にもプルアップの設定や、信号の変化による割り込みの設定も行える。細かく説明すると長くなるので、入力変化によるイベントのサンプルだけ書いておく。

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

import select
from quick2wire.gpio import pins, In, Out, Rising, Falling, Both

if __name__ == '__main__':
    led = pins.pin(0, direction=Out)
    btn = pins.pin(7, direction=In, interrupt=Both)
    with led,btn:
        epoll = select.epoll()
        epoll.register(btn, select.EPOLLIN|select.EPOLLET)
        try:
            while True:
                events = epoll.poll()
                for fileno,event in events:
                    if fileno == btn.fileno():
                        led.value = btn.value
        except KeyboardInterrupt:
            print('\nbreak')

GPIO0(11番ピン)にLED、GPIO7(7番ピン)にスイッチが接続されているものとして、スイッチのH/L変化によるイベントでLEDの状態を変化させている。
このサンプルのような単純な処理ではあまりメリットは無いが、selectを使うと複数のI/O処理を登録しておいてイベントで一括処理する事が可能になるので、複雑なプログラムになってくると必要とされる場面もあるかと思う。

Quick2WireのI2C機能については次回に。

Quick2Wire

Raspberry PiはLinuxベースのPCでありながら、I/Oポートに接続されたセンサから値を取得したり、モータやサーボを動かしたりというマイコンボード的な事が手軽にできるのが特徴で、Python言語からGPIOやI2Cを操作する手段も何種類か用意されている。

初期の頃から存在しているI2Cデバイス制御用モジュールsmbusは手軽にpythonから使えるのだが、実行時に管理者権限が必要となる。ユーザと管理者が通常は同一なRaspberry Piではあまり問題にはならないと思うが、リモートデスクトップ等の環境でどうしても一般ユーザ権限で動かさなければならないという場合に困る。

調べてみるとQuick2WireモジュールならI2Cの操作に管理者権限が不要という事なので、インストールして試してみる事にした。
apt-getではインストール出来ないので、少しばかり手順が煩雑になる。

Quick2Wireのドキュメントではvirtualenvを使用した仮想環境での使用が推奨されているので、virtualenvがインストールされていない場合は最初にその環境をセットアップ。ここでは仮想環境の名前を「python32env」とした。

$ sudo apt-get install python-pip
$ sudo apt-get install python-virtualenv
$ virtualenv -p python3 python32env

python32envディレクトリに移動してから

$ source bin/activate

で仮想環境を起動。

次にQuick2Wire-Gpio-Adminをインストール

$ git clone https://github.com/quick2wire/quick2wire-gpio-admin.git
$ cd quick2wire-gpio-admin/
$ make
$ sudo make install
$ cd ..

Quick2Wire-Python-APIをインストール

$ git clone https://github.com/quick2wire/quick2wire-python-api.git
$ cd quick2wire-python-api
$ export QUICK2WIRE_API_HOME=`pwd`
$ export PYTHONPATH=$PYTHONPATH:$QUICK2WIRE_API_HOME
$ git checkout
$ python3 setup.py install
$ cd ..

Quick2Wireを使うためにはユーザがgpioとi2cグループに所属している必要があるので、所属していない場合は以下のコマンドを実行してから再起動する。

$ sudo adduser $USER gpio
$ sudo adduser $USER i2c

pythonを起動して、Quick2WireモジュールがインポートできればOK

~/python32env $ python
Python 3.2.3 (default, Mar  1 2013, 11:53:50)
[GCC 4.6.3] on linux2 Type "help", "copyright", "credits" or "license" for more information.
>>> import quick2wire
>>>

Quick2Wireの使用法については次回に。