Node.js(5) three.js

前回作ったNode.jsベースのWebアプリで、Raspberry PiをWebサーバにしてデバイスの情報をブラウザに表示する目的はほぼ実現できた。
JavaScriptにはブラウザで各種データをビジュアルに表示するライブラリが豊富にあり、手軽に使えることもわかったので仕上げとしてそのあたりを試してみる。

使用したのは3Dライブラリのthree.js。WebGLをJavaScriptで手軽に使うことができる。

とりあえずテクスチャを貼った立方体を表示させて、Raspberry Piから送られてくる加速度センサの値でそれを動かすようにしてみた。
スクリーンショット 2015-05-30 14.23.50
ブラウザ側でマウスによってカメラを動かせるようにしたので、複数のクライアントから接続した場合、立方体の動きは全く同一になるが、表示される角度や大きさはクライアント側で個別のものとなる。

ソースコード一式はこちら

gitでソースをクローンしたら、以下のようにnpmでモジュールをインストールして動かすことができる。

$ git clone https://github.com/tokoya/node_three.git  
$ cd node_three  
$ npm install  
$ npm start  

ブラウザからは前回と同じように「Raspberry PiのIPアドレス:3000/users」へアクセス。

three.jsは実行時にサイトからダウンロードする事も可能なようだが、ローカルなネットワーク環境下でも動かせるように/public/javascripts/にthree.min.jsとTrackballControls.jsを予め置いてある。

データの3D処理は完全にブラウザ側で行われるので、Raspberry Piの処理能力とは無関係にリッチな表現ができるのは面白い。かなり高度な表現ができるライブラリも各種存在しているので、Raspberry Piとの組み合わせは表現の幅が広がるのではないかと思う。

Node.js(4) I2C

Webサーバとクライアント(ブラウザ)間でコネクションを維持したまま通信を行うsocket.ioライブラリを、Node.jsで使用してWebアプリを作成するのは思ったよりも簡単に出来た。socket.ioを使用するとブラウザ側からリクエストを送出する事無く、サーバ側から自動的に配信を行うPUSH型のアプリを作成できるので、今回はそれを試してみる。

Raspberry PiのGPIOポートにスイッチを付けてその状態を配信するだけでも良いのだが、シンプル過ぎて面白く無いので以前Pythonで使ってみた加速度センサモジュールを今回も使用した。加速度センサモジュールについてはここを参照。

前回と同じようにexpressフレームワークが生成したコードのpackage.jsonに、必要なライブラリの定義を追加。Raspberry Pi用のI2Cパッケージはいくつか存在するようだが、今回はこれを使用した。

{
  "name": "i2c_acc",
  "version": "0.0.0",
  "private": true,
  "scripts": {
    "start": "node ./bin/www"
  },
  "dependencies": {
    "body-parser": "~1.12.0",
    "cookie-parser": "~1.3.4",
    "debug": "~2.1.1",
    "ejs": "~2.3.1",
    "express": "~4.12.2",
    "morgan": "~1.5.1",
    "serve-favicon": "~2.2.0",
    "socket.io": "^1.3.5",
    "i2c": "~0.2.1"
  }
}

加速度センサからのデータ読み出しと、socket.ioによる通信はアプリケーションのエントリポイント”/bin/www”で、サーバオブジェクト生成後に呼び出されるapp_i2c.jsに記述。

var express = require('express');
var router = express.Router();
var i2c = require('i2c');
var addr = 0x1d;
var wire = new i2c(addr, {device: '/dev/i2c-1'});

function signed(n) {
  return (n < 128) ? n : n - 256; 
}

function app_i2c(server) {
  var io = require('socket.io')(server);
  wire.writeBytes(0x16, [0x05], function(err, res){});
  wire.writeBytes(0x10, [0,0,0,0,0,0], function(err, res){});
  setInterval(function() {
    wire.readBytes(0x06, 3, function(err, res){
      io.sockets.emit('event', {
        x: signed(res[0]),
        y: signed(res[1]),
        z: signed(res[2]),
      });
    });
  }, 100);
}

module.exports = app_i2c;

加速度センサを初期化した後、100msec周期でx,y,z値を読み出し、接続されているすべてのクライアントに対してその値を送信している。
続きを読む »

Node.js(3) socket.io

前回作ったNode.jsのWebアプリは、クライアント(ブラウザ)からのリクエストを受けて結果を返す昔ながらの静的なWebスタイルなので、Raspberry Piをサーバとしてリアルタイムにセンサの値を表示させたいといった用途にはあまり向いていない。
JavaScriptを使ってブラウザから定期的にhttpリクエストを送信する事で、リアルタイム性のあるWebアプリを実現する方法は既に広く使われているが、最近ではWebサーバとブラウザ間で直接コネクションして通信を行うWebSocketの規格が登場し、Node.jsでも簡単に使えるようなので試してみた。

Webアプリの機能としては前回と同じくブラウザからの操作でRaspberry PiのLEDをOn/Offするだけだが、リクエストはsocket.ioで送信してレスポンスもsocket.ioのイベントとして受け取る。これによって複数のブラウザから同時にRaspberry Piに接続した場合、どれかがLEDの状態を変えた時に全てのクライアントがその変化をリアルタイムに知ることができる。

プロジェクトの全コードは前回と同じくGitHubに置いた

順番に見ていこう。まずexpressフレームワークで自動生成したプロジェクトのpackage.jsonに、GPIO操作とscoket.ioのパッケージ定義を追加した。

{
  "name": "led_io",
  "version": "0.0.0",
  "private": true,
  "scripts": {
    "start": "node ./bin/www"
  },
  "dependencies": {
    "body-parser": "~1.12.0",
    "cookie-parser": "~1.3.4",
    "debug": "~2.1.1",
    "ejs": "~2.3.1",
    "express": "~4.12.2",
    "morgan": "~1.5.1",
    "serve-favicon": "~2.2.0",
    "onoff": "~1.0.2",
    "socket.io": "^1.3.5"
  }
}

アプリケーションのエントリポイント”/bin/www”で、サーバオブジェクト生成後にsocket.io用のオブジェクト作成と呼び出しを追加。ここは分けずにwwwに直接コードを追加しても良かったのだが、見通しを良くするためにこうした。

var server = http.createServer(app);
var sock = require('../sock'); // 追加
sock(server); // 追加

socket.io用オブジェクトの処理は、クライアントから接続されてイベントを受信した時、パラメータに従ってLEDをOn/Offしてから、その状態を全てのクライアントに対して送信する。

var io = require('socket.io');  
var gpio = require('onoff').Gpio;
var led = new gpio(17, 'out');

function sock(server) {
  var sock = io.listen(server);
  sock.sockets.on('connection', function(socket) {
    socket.on('event', function(data) {   // クライアントからのイベント受信
      console.log('socket id: [' + socket.id + '] data: ' + data.onoff);
      if (data.onoff == 'on') {
        led.writeSync(1);
        sock.sockets.emit('event', { onoff: 'on' });  // 全てのクライアントへ送信
        // 送信元にだけ送信する場合は
        // socket.emit を使う
        // 送信元以外の全てに送信する場合は
        // socket.broadcast.emit を使う
      }
      if (data.onoff == 'off') {
        led.writeSync(0);
        sock.sockets.emit('event', { onoff: 'off' }); // 全てのクライアントへ送信
      }
    });
    socket.on('disconnect', function() {  // 切断
      console.log('disconnect id: [' + socket.id + ']');
    });
  });
}

module.exports = sock;

コメントにもあるように、サーバからクライアントへの送信は、全てのクライアント、イベントを送信したクライアント、イベントを送信したクライアントを除く全て。に対して行うことができる。
今回は使用しなかったが、クライアントをルームに分けて、同じルームに入っているクライアントにだけ送信する事もできる。
サーバ側のコードとしてはこれで終わり。次はクライアント側を見ていこう。
続きを読む »

Git

Blogにソースコードを載せる時に、短いコードならそのまま書いても問題は無いが、プロジェクトに複数のファイルが存在してディレクトリ構造が意味を持つような場合はそう簡単には行かなくなる。以前はGoogle Driveにzip圧縮したものを置いた事もあるが、その手の用途にはGitHubを使用するのが最近の王道らしいので、試してみた。

GitHubはソースコード管理ツールGit(ジット)のリモートリポジトリサービスで、プロジェクトを公開する場合は無料で使えるから確かにその手の用途には向いている。

Raspberry PiでGitを使うには

$ sudo apt-get install git

でインストールすれば良い。

$ git --version
git version 1.7.10.4

バージョンを確認すると1.7.10.4で最新版よりはかなり古いが、GitHubのリポジトリからクローンするだけならたぶん問題ない。最新版が必要な場合は

$ sudo apt-get install libcurl4-gnutls-dev libexpat1-dev gettext libz-dev libssl-dev
$ sudo apt-get autoconf
$ git clone https://github.com/git/git
$ cd git
$ make configure
$ ./configure --with-expat --with-curl=/usr/local
$ make && sudo make install

でソースからビルドしてインストールできるようだ。
ビルドにはかなり時間がかかるので注意。ビルド後にRaspberry Piを再起動すると、バージョンは2.4.1.168になった。

$ git --version
git version 2.4.1.168.g1ea28e1

Node.jsでGPIOに接続したLEDをon/offするWebアプリのコード一式をとりあえずGitHubに置いたので、Node.jsとGitがインストール済であれば以下のようにしてインストールできる。

$ git clone https://github.com/tokoya/led.git
$ cd led
$ npm install
$ npm start

Node.jsのサーバ起動後にPCのブラウザで「Raspberry PiのIPアドレス:3000/users」にアクセスして、Webアプリの起動が確認できればOK。

Node.js(2) GPIO

Raspberry PiにNode.jsをインストールして、ExpressフレームワークでWebアプリを簡単に作成して動かすことができたが、何もデバイスを制御しない状態ではRaspberry Piを使う意味が無いので、まずはGPIOを制御するモジュールを追加して簡単な制御を行ってみる。

Node.jsのRaspberry Pi用GPIOパッケージは何種類も存在していて、ざっと調べてみた限りでは以下の三種類が良く使われているようだ。

  1. pi-gpio
  2. rpi-gpio
  3. onoff

今回はその三種類の中からonoffを試してみたが、最初に目についたというだけで特に理由はない。他のパッケージでも恐らく同様に使えるはず。

インストールはExpressでプロジェクトのひな型(プロジェクト名はledとする)を作ってからそのディレクトリに移動して

$ express -e led
$ cd led
$ npm install
$ npm install onoff

を実行するか、あるいは生成されたpackage.jsonにパッケージ名”onoff”を追加してから npm install を行えば良い。

{
  "name": "led",
  "version": "0.0.0",
  "private": true,
  "scripts": {
    "start": "node ./bin/www"
  },
  "dependencies": {
    "body-parser": "~1.12.0",
    "cookie-parser": "~1.3.4",
    "debug": "~2.1.1",
    "ejs": "~2.3.1",
    "express": "~4.12.2",
    "morgan": "~1.5.1",
    "serve-favicon": "~2.2.0",
    "onoff": "~1.0.2"
  }
}

このパッケージを使用してGPIO操作を行う場合、ユーザがgpioグループに属していればルート権限が不要なのでデフォルトの「pi」ユーザならそのままで使えるが、新たに作成したユーザでログインしている場合はsudoを付けてNode.jsを起動するか、あるいはそのユーザをgpioグループに属させる必要がある。

$ sudo gpasswd -a ユーザID gpio

GPIO操作のサンプルとして、まずは定番のLED点滅を行ってみる。
GPIOの適当なポートにLEDと電流制限抵抗をつなぎ、ポートに”1″を出力すると点灯、”0″を出力すると消灯する状態になっている場合、ブラウザからの操作でLEDをON/OFFするには/routes/users.jsファイルに以下のようなコードを書く。LEDはGPIO17(ピン番号11)に接続した。

var express = require('express');
var router = express.Router();

var GPIO = require('onoff').Gpio;
var led = new GPIO(17, 'out');

/* GET users listing. */
router.get('/', function(req, res, next) {
  console.log(req.query);
  var onoff = req.query.onoff;
  var msg = '';
  if (onoff == 'on') {
    led.writeSync(1);
    msg = 'LED ON';
  }
  if (onoff == 'off') {
    led.writeSync(0);
    msg = 'LED OFF';
  }
  res.send(msg);
});

module.exports = router;

ブラウザからHTTPのGETでパラメータが送られてきた時、”onoff”の値が”on”ならばLEDを点灯、”off”ならば消灯という動きをするので、本来ならばブラウザで表示するHTMLファイルを作成しなければならないが、動作を試すだけならばGETのパラメータを直接URLに書いて確認できる。

プロジェクトのディレクトリに移動して

$ npm start

でWebアプリを起動した後、サーバが起動して待受け状態になるまで少し待ってから、PCのブラウザで以下のようにアクセスする。

Raspberry PiのIPアドレス:3000/users?onoff=on

スクリーンショット 2015-05-12 10.38.35
全て正しければこれでRaspberry PiのGPIOに接続したLEDが点灯するはず。URLの最後を”onoff=off”に変えれば消灯する。
続きを読む »

Node.js

例年のことだが12月から3月にかけては非常に忙しく(年度末のせい)、Raspberry Piに電源を入れている暇もなかったのだが、GW前にはどうにか落ち着いてきたのでblogを再開。

今まではほとんどPythonを使ってRaspberry Piに接続したセンサからデータを取得したりしていたが、最近は制御系のシステムでもブラウザをUIに使えないか?という話が仕事の案件として割と多く、その関係でJavaScriptをRaspberry Piで試したりしている。

サーバーサイドはPHPでクライアントがJavaScriptというパターンが一般的には多いようだが、異なる言語でシステムを組むのはどうも抵抗があったので、サーバーサイドもJavaScriptで作成できる「Node.js」をRaspberry Piに導入して動かしてみたところ、非常に簡単にWebアプリを構築する事ができた。

Node.jsはapt-getでインストールすると古いバージョンが入ってしまうので、raspbianでは以下のようにDebパッケージからインストールする。

$ wget http://node-arm.herokuapp.com/node_latest_armhf.deb 
$ sudo dpkg -i node_latest_armhf.deb

インストール後にバージョンを確認するとv0.12.1だった

$ node -v
v0.12.1

この状態でもサーバーサイドJavaScriptを動作させる事は可能だが、Webアプリのひな形を自動生成してくれるフレームワーク「express」が非常に便利なので、これもインストールしておく。インストールにはNode.jsに含まれるパッケージ管理ツール「npm」を使用。

$ sudo npm install -g express-generator

npmでパッケージをインストールする時に「-g」オプションを付けると、すべてのプロジェクトで共通のモジュールとしてインストールされる。付けないとプロジェクト毎のフォルダにインストールされるので、同じモジュールでもプロジェクト毎にバージョンが異なるものを使うといった事ができるようだ。

expressでWebアプリのひな形を作成し、起動するには以下のように行う

$ express -e MyWebApp
$ cd MyWebApp
$ npm install
$ npm start

「express -e MyWebApp」でMyWebAppプロジェクトのフォルダが作成され、その中にひな形となるファイル一式が自動生成される。オプションの「-e」はHTMLテンプレートエンジンにEJSを使用するという指定で、何も付けないデフォルトではJADEを使用したひな型になるのだが、JADEは簡潔に書ける代わりに表記法を覚える必要があるので、最初はEJSで始める事をお勧めする。

「npm install」を実行すると生成されたpackage.jsonに記載されたパッケージがインストールされる。
「npm start」はpackage.jsonの「scripts:start」に記載された内容を実行するので、実際には「node ./bin/www」が実行される事になる。
「./bin/www」はサーバオブジェクトを生成して待ち受け状態に入り、ブラウザからアクセスされると「app.js」の内容に従って其々の処理を行うという動きをする。Node.jsのサーバはデフォルトではポート3000を使用するので、ブラウザでRaspberry Piのポート3000にアクセスすれば良い。IPが「192.168.1.12」だとしたら「192.168.1.12:3000」にアクセスする。

Raspberry Piのコンソールに

> node ./bin/www

GET / 200 574.327 ms - 207
GET /stylesheets/style.css 200 222.119 ms - 110
GET /favicon.ico 404 203.292 ms - 1998

のように表示されて、ブラウザに
express
と応答が表示されていればOK。Raspberry Piではサーバの起動に少し時間がかかるので、すぐにアクセスするとエラーになる場合がある。

expressはルートの下に「/users」というページを自動生成しているので、アプリが起動した状態で「192.168.1.12:3000/users」へアクセスすると

respond with a resource

と一行だけ表示されるはず。これは「./routes/users.js」ファイルの

router.get('/', function(req, res, next) {
  res.send('respond with a resource');
});

によって表示されているもので、app.jsファイルの

var users = require('./routes/users');
app.use('/users', users);

という二行の定義から呼び出されている。つまり同じようにクラスを作ってapp.jsファイルに追加すれば、Webアプリのページを増やすことができる。

Raspberry PiでWebアプリを動かす場合は、何かデバイスを制御したりセンサの値を取得したりといったコードをこのひな型に追加していく事になるのだが、長くなるのでそれは次回。

ネットワークカメラ

Raspberry Piで画像を取り込みたいと思った時に、それを実現する方法は色々とある。専用のカメラモジュールが用意されているし、以前に書いたようにUSBカメラを接続してキャプチャする事もできる。

監視用によく使われているAxisのネットワークカメラがたまたま手元に来たので、Raspberry Piでできるだけ簡単にネットワークカメラの画像を取り込む方法について試してみた。

AxisのネットワークカメラはWebサーバを内蔵しているので、ブラウザでアクセスするだけで映像をモニタする事ができるし、設定によってモニタ画面のボタンをマウスでクリックした時にその画像を保存する事ができる。と言うことはRaspberry PiでネットワークカメラのWebサーバにアクセスして、ボタンをクリックした時のCGIを実行できれば同じことができるはず。

まずはその前に、モニタ画面でボタンをクリックした時にRaspberry Piへ画像を送れるようにしてみる。FTPを使用するので、Raspberry PiにFTPサーバがインストールされていない場合は

$ sudo apt-get install vsftpd

で最初にインストール。
デフォルトではanonymousログインのみで書き込み禁止になっているので、/etc/vsftpd.conf ファイルを編集してanonymousを禁止、ローカルユーザによるログインと書込みを可にする。

anonymous_enable=NO
local_enable=YES
write_enable=YES

設定ファイルを編集したらサービスを再起動

$ sudo service vsftpd restart

カメラ画像の転送先フォルダも作成しておく。ここでは「camera」とした。

Axisネットワークカメラのイベント設定はアクションルールとレシピの二つに分かれていて、前者は何をイベントとして受け付けるのか、後者はイベントを受け付けた時の動作を設定する。従ってブラウザのモニタ画面でボタンを押された時に画像をFTPサーバへアップロードする場合は、アクションルールに「マニュアルトリガ」、レシピに「FTP転送」を設定する事になる。

スクリーンショット 2014-12-08 11.56.24

スクリーンショット 2014-12-08 11.43.31

この設定をネットワークカメラに保存して、モニタ画面のトリガボタンをON/OFFするとRaspberry Piに画像が転送される事を確認。これが上手く行ったら、あとはトリガボタンがON/OFFされた時に実行されるCGIを、Raspberry Piから直接叩けば良い。

トリガONの時は

カメラのIPアドレス/axis-cgi/io/virtualinput.cgi?action=6:/

が実行されて、OFFの時は

カメラのIPアドレス/axis-cgi/io/virtualinput.cgi?action=6:\

が実行される。

pythonでCGIを実行するのはurllibモジュールを使用すれば良いのだが、認証が必要になるのでそこがちょっとだけ面倒。予め認証用のオブジェクトを作っておき、それをurllibモジュールに登録しておく必要がある。

ネットワークカメラの画像をFTP転送させるプログラムは以下の通り。IPアドレスやカメラのユーザ名、パスワードは適宜変更する。

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

from urllib import request
from urllib import parse

class camera():
    def __init__(self, topurl, cgiurl, camuser, campass):
        passwd_mgr = request.HTTPPasswordMgrWithDefaultRealm( )
        passwd_mgr.add_password(None, topurl, camuser, campass)
        handler = request.HTTPDigestAuthHandler(passwd_mgr)
        opener = request.build_opener(handler)
        request.install_opener(opener)
        self.cgi = topurl + cgiurl
    def save(self):
        trig_on  = parse.urlencode({'action' : '6:/'})
        trig_off = parse.urlencode({'action' : '6:\\'})
        request.urlopen(self.cgi + trig_on)
        request.urlopen(self.cgi + trig_off)

if __name__ == '__main__':
    netcam = camera('http://192.168.1.90/',
                    'axis-cgi/io/virtualinput.cgi?',
                    'root',
                    'xxxxxxxx')
    netcam.save()

実際のところpythonが動けばRaspberry Pi以外でもこのプログラムは動作するので、画像キャプチャだけならわざわざRaspberry Piを使う必要は無いし、カメラ単体に一定周期や動体検知で画像保存する機能もあるから、それだけで事足りる場合も多いだろう。

何かのセンサー類を使って、特定の条件で画像を取り込みたい。というネットワークカメラ単体では対応が難しいような要件であれば、役に立つかもしれない。

RemminaとAvahi

Linuxでリモート接続で書いたように、以前はWindowsからRaspberry Piにリモート接続して使ってたのを、今はWinodws上の仮想PCでLinuxを動かして、そこからリモート接続するようにしている。

Windowsの時はputtyやWinSCPを使用していて、Linuxではデフォルトのターミナルエミュレータやファイルマネージャがそれと同じように使えるのだが、WindowsではリモートデスクトップにRDPを使っていたので、Linuxでも同じプロトコルを使おうと思うと標準のツールでは対応できない。

UbuntuソフトウェアセンターでRDPを検索すると、Linux用のRDPクライアントが何種類か出てくる。とりあえずその中で最も評価数の多かった「Remmina」を試してみる事にした。インストールはUbuntuソフトウェアセンターから一発で行える。

実際使ってみるとリモートデスクトップ以外にもSSHシェルやSFTPによるファイル転送の機能もあり、思ったよりもかなり便利だった。Raspberry Piをリモートで使う場合はこれだけでほとんど間に合ってしまう。もっと早く使ってみれば良かった。

Remminaを起動すると以下のようなウィンドウが表示されるので、最初にツールバーの左から二番目にある「新規リモートデスクトップの作成」をクリックしてRaspberry Piを登録する。

Remmina リモートデスクトップクライアント_032

リモートデスクトップの設定_033

サーバーに接続先PCのIPアドレスを入力するのだが、Raspberry Piに「Avahi」をインストールしてあるので、IPアドレスではなく「ホスト名.local」を設定している。Avahiは一種のサーバアプリで、IPアドレスの分からないPCを即座に探し出せるようにするためのものだ。元々はZeroconfというプロトコルで、それをフリー実装したのがAvahi。

以前にRaspberry Piが固定IP設定では無く、DHCPからアドレスを割り当てられる環境の時にIPアドレスをメールで通知する方法(IPアドレスの通知)を書いたが、これはRaspberry Piからメールを送信する実験という意味もあったので、IPアドレスを知りたいだけならAvahiをインストールしておくほうが簡単。

インストールはRaspberry Piのコンソール(ターミナル)から

$ sudo apt-get update
$ sudo apt-get install avahi-daemon

で行う。
Avahiをインストールすると、その後は同じネットワーク内でZeroconfに対応したPCからは「ホスト名.local」でアクセスする事ができる。Raspberry Piのホスト名をデフォルトから変更していなければ「raspberrypi.local」になる。

$ ping raspberrypi.local
PING raspberrypi.local (192.168.1.3) 56(84) bytes of data.
64 bytes from 192.168.1.3: icmp_seq=1 ttl=64 time=3.11 ms
64 bytes from 192.168.1.3: icmp_seq=2 ttl=64 time=1.88 ms

LinuxとMacはZeroconfの機能が組み込まれているので、何もしなくてもAvahiをインストールしたRaspberry Piを検出できるはずだが、Windowsにはその機能がない。appleのiTunesをインストールすると、Zeroconfの元になったBonjourが組み込まれて使用可能になるようだ。

話をRemminaに戻す。
RemminaにはRDP以外にもSSHやSFTPで接続する機能があるので、それも一緒に登録しておくと便利。

リモートデスクトップの設定_034

リモートデスクトップの設定_035

すべて登録すると以下のように三種類の接続先が表示される。

Remmina リモートデスクトップクライアント_036

あとは必要とする接続先をダブルクリックするだけなのだが、リモートデスクトップの接続時はウィンドウサイズが正しく認識されない場合があった。いちど全画面にしてから「ウィンドウサイズをリモートの解像度に合わせる」を選択すれば正しく合う場合もあったが、どんどんずれて行く事もあって原因は良くわからない。その場合はマニュアルでサイズを調整する必要がある。
ホストがVirtualBoxによる仮想PCである事に関係しているのかも知れない。

1_024

それ以外は今のところ問題なく使用できている。WindowsのRDPクライアントを使った時と同じくMinecraft Piは動作しないが、これはビデオアクセラレータの機能を使用しているのでしかたがないだろう。

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(2)

PySide(Qt)を使用してGUIアプリを作成する時に、コントロールの数が少ない単純デザインであればすべてコードで記述する事は可能だが、複雑なレイアウトになると現実的では無くなる。頑張って書いたとしても、メンテナンス性が悪くなるので調整や追加を行うのはかなり大変だ。

多くのツールキットにはGUIデザイン用のツールが存在していて、QtにはQt Designer、wxPythonにはwxGladeというリソースエディタがある。wxGladeはまだ使った事が無いが、そちらはwxPythonを試してみる時まで置いておくとして、今回も引き続きツールキットとしてPySideを使用するのでツールは必然的にQt Designerとなる。

Qt Designerはかなり重量級のアプリなので、Raspberry Piでも動かせない事はないのだが実際に試してみると動作が遅くて実用的では無かった。なのでQt Designerは別なPCで動かし、デザインしたファイルだけをRaspberry Piに持って行って動かす事にする。

具体的にはWindows7の仮想PCとしてインストールしたUbuntu(Xubuntu)にQt Designerをインストールした。Ubuntuソフトウェアセンターで「Qtデザイナー」を検索すれば出てくるので、そこでインストールすれば良い。

Ubuntuソフトウェアセンター_002

Windowsの場合はQt Projectのダウンロードページから、Qt Online Installer for Windowsをダウンロードして実行すれば必要なツールはすべてインストールされる。Windows版ではQt DesignerはQt Creatorの一部になっているため、Qt Designerだけを使用したい場合でもすべてインストールしなくてはいけないようだ。

Qt Creatorに含まれるQt Designerを使用する場合、最初にプロジェクトを作成してからでないとデザインが行えないようなので、PySideのためだけにQt Designerを使いたい場合にはWindowsではなくLinuxで行うほうが今のところはやりやすい。Windows環境下でもVirtualBox等の仮想PCツールで手軽にLinux環境を構築可能なので、そちらをお勧めする。

XubuntuではインストールしたQt Designerはメニューの開発カテゴリの中に「Qt4デザイナ」という名前で追加されるので、それを選択すれば起動するのだが、起動する前にまずはどんなGUIを作るのかを考えておく。
Qtのレイアウトはコントロールが多層構造になるので、デザインツールを使用する場合でもGUIの構造は予め考えておいたほうが良い。大雑把に紙に書くのも有効。

前回作ったGUIと同じものでは面白く無いので、光らせたカラーLEDの色と同じ名前を同じ色で表示させる事にした。ついでに押したボタンの色も変えて分かりやすくする。

Form - [プレビュー]_018

プレビューだと分からないが、フォーム全体のレイアウトをVertical Layoutにしておき、そこにLabelとHorizontal Layoutを並べて、Horizontal Layoutの中にボタンを三個並べた構造になっている。これをQt Designerで作ってみる。

最初にQt Designerを起動すると、新しいフォームの作成ダイアログが出るのでWidgetを選択。

Qt Designer_003

フォームが表示されたら、左側のウィジェットボックスからLabelとHorizontal Layoutをドラッグしてフォームの上に貼り付ける。

Qt Designer_007

メニューの「フォーム→垂直に並べる」を選択してフォーム全体のレイアウトを指定すると、LabelとHorizontal Layoutが縦に並ぶ。

Qt Designer_008

この状態で左側のウィジェットボックスからHorizontal Layoutの中にPush Buttonをドラッグして三個貼り付ける。ボタンは自動的に横に並ぶ。

Qt Designer_009

右側のオブジェクトインスペクタでボタンを選択して、オブジェクト名の部分をダブルクリックしてボタンに名前を付ける。ここでは pushButton_r、pushButton_g、pushButton_b とした。ボタンのtextプロパティでそれぞれに「RED」「GREEN」「BLUE」のボタンテキストも設定する。

Qt Designer_010

オブジェクトインスペクタでFormを選択して、プロパティのminimumSizeを400×100に設定。フォームをリサイズして指定したサイズよりも小さくならない事を確認する。

Qt Designer_011

オブジェクトインスペクタでLabelを選択して、プロパティのfontを適当なサイズに変更。ここではフォントサイズを28に設定した。

Qt Designer_012

最後に三個のボタン全ての autoFillBackground プロパティをON(チェックマークを付ける)にして完了。適当なファイル名を付けて保存する。ここでは QtColorLED.ui という名前で保存した。

保存したファイルは以下のような内容になっている

<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
 <class>Form</class>
 <widget class="QWidget" name="Form">
  <property name="geometry">
   <rect>
    <x>0</x>
    <y>0</y>
    <width>400</width>
    <height>100</height>
   </rect>
  </property>
  <property name="minimumSize">
   <size>
    <width>400</width>
    <height>100</height>
   </size>
  </property>
  <property name="windowTitle">
   <string>Form</string>
  </property>
  <layout class="QVBoxLayout" name="verticalLayout">
   <item>
    <widget class="QLabel" name="label">
     <property name="font">
      <font>
       <pointsize>36</pointsize>
      </font>
     </property>
     <property name="text">
      <string>TextLabel</string>
     </property>
     <property name="alignment">
      <set>Qt::AlignCenter</set>
     </property>
    </widget>
   </item>
   <item>
    <layout class="QHBoxLayout" name="horizontalLayout">
     <item>
      <widget class="QPushButton" name="pushButton_r">
       <property name="autoFillBackground">
        <bool>true</bool>
       </property>
       <property name="text">
        <string>RED</string>
       </property>
      </widget>
     </item>
     <item>
      <widget class="QPushButton" name="pushButton_g">
       <property name="autoFillBackground">
        <bool>true</bool>
       </property>
       <property name="text">
        <string>GREEN</string>
       </property>
      </widget>
     </item>
     <item>
      <widget class="QPushButton" name="pushButton_b">
       <property name="autoFillBackground">
        <bool>true</bool>
       </property>
       <property name="text">
        <string>BLUE</string>
       </property>
      </widget>
     </item>
    </layout>
   </item>
  </layout>
 </widget>
 <resources/>
 <connections/>
</ui>

このuiファイルをRaspberry Piに転送して、とりあえず「表示するだけ」のコードを書いて表示を確認。

Raspberry Pi_019

表示するだけのコードは以下の通り

#!/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.QtUiTools import QUiLoader

CURRENT_PATH = os.path.dirname(__file__)

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)

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

もちろんボタンをクリックしても何も動作しないが、ウィンドウをドラッグしたり、サイズを変えたりすることはできる。次回はこのコードに肉付けして、実際に動作するアプリにしていく予定。