Raspberry PiのGPIO端子にスイッチ(ボタン等)を取り付けて、デバイスを制御することがあるかと思います。
ここではスイッチを取り付けるのが面倒なので物理的なスイッチの代わりに、Wi-Fiや有線LANで接続したスマートフォンやPCにスイッチを表示して、Raspberry PiにON/OFF信号を伝える方法を考えたいと思います。
Raspberry Piと制御用のスマートフォンの間でリアルタイムにデータのやり取りが行えるように、WebSocketを使った方法の一例を記録しておこうと思います。
目次
Raspberry PiにWebSocketでソフトウェア的にスイッチON/OFFを伝える方法の例
使用するOS
OSはRaspbianを使用させて頂きました。今回はGPIOは使用しませんが、使用する環境を想定しています。
Raspbianの初期設定は、宜しければこちらの記事を御覧ください。
使用したRaspbianのイメージは「2018-10-09-raspbian-stretch.zip」になります。
Express.jsプロジェクト作成
スイッチ画面と信号のやり取りはNode.js+Express.jsを使用させて頂きました。
インストールとプロジェクトの作成は下記の手順になります。
- node.jsをインストールします。
sudo apt-get install nodejs npm
- express-generatorをインストールします。
sudo npm install -g express-generator
- expressコマンドでwsswitchプロジェクトを作成します。
express --view=ejs wsswitch
- 動作確認のためデバッグ実行します。
cd wsswitch npm install DEBUG=wsswitch:* npm start
- 他のPCからWebブラウザで次のURLにアクセスできるか確認しました。
http://<Raspberry PiのIPアドレス>:3000
wsインストール
WebSocketサーバはwsを使用させて頂きました。
ファイルの変更・作成
app.js変更
- app.jsを変更します。
vi app.js
10行目付近の「var app = express();」の下に次の内容を追加します。
var app = express(); // -------------------------------------------------- // WebSocketのインスタンス生成 var WebSocket = require('ws').Server; var wss = new WebSocket({port:3001}); // Raspberry Piで何かイベント処理をさせるWsEventHandlerインスタンス生成 // ws-event-handler.jsファイルにその処理を書きましょう var WsEventHandler = require('./ws-event-handler'); var ev = new WsEventHandler; // WebSocketの送受信処理 wss.on('connection', function connection(ws) { console.log('WebSocket:接続しました') // デバイスの初期化など ev.connected(wss); ws.on('message', function incoming(data){ console.log("WebSocket:受信しました: " + data); // 何かイベントに応じたデバイスの処理実行 ev.handleEvent(wss, JSON.parse(data)); // 受け取ったイベントをWebSocketクライアントにログとして表示 wss.sendAllClients(data); }); ws.on('close',function close(){ // デバイスの終了処理など ev.closed(wss); console.log('WebSocket:切断しました'); }); }); // 全クライアントにデータを送信するメソッドを追加しておきます wss.sendAllClients = function SendAllClients(data) { this.clients.forEach(function each(client){ // 全クライアントにデータ送信 client.send(data); }); } // --------------------------------------------------
views/index.ejs変更
- views/index.ejsを変更します。
vi views/index.ejs
下記の内容にそっくり入れ替えます。
<!DOCTYPE html> <html lang="ja"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Raspberry Pi 仮想スイッチ</title> <link rel='stylesheet' href='/stylesheets/style.css' /> </head> <body> <h1>Raspberry Pi 仮想スイッチ(カチカチ押すほうの)</h1> <div id="app"> <template v-for="item in buttons"> <button type="button" v-on:mousedown="sendEventToWs(item, $event)" v-on:mouseup="sendEventToWs(item, $event)"> {{ item.caption }}</button> </template> <ul v-for="subitem in logs"> <li>{{ subitem }}</li> </ul> </div> <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script> <script> var socket; var app1 = new Vue({ el: '#app', data: { buttons: [ { caption:'スイッチ1', name: 'switch1'}, { caption:'スイッチ2', name: 'switch2'}, { caption:'スイッチ3', name: 'switch3'}, { caption:'スイッチ4', name: 'switch4'}, { caption:'スイッチ5', name: 'switch5'} ], logs: [] }, methods: { sendEventToWs: function(item, event) { var data = { 'name' : item.name, 'event': event.type } socket.send(JSON.stringify(data)) } }, mounted: function() { socket = new WebSocket('ws://' + window.location.hostname + ':3001/'); var _this = this; socket.addEventListener('open',function(event){ _this.$data.logs.push('WebSocketに接続しました'); }); socket.addEventListener('message',function(event){ _this.$data.logs.push('Raspiから受信:' + event.data); }); } }) </script> </body> </html>
ボタンの表示画面と、ボタンを押したときのWebSocketへの通信、そしてWebSocketから受信したデータを表示する内容になります。
ws-event-handler.js作成
- 最後にws-event-handler.jsファイルを作成します。
vi ws-event-handler.js
下記の内容になります。
var WsEventHandler = function() { this.client_cnt = 0; } WsEventHandler.prototype.connected = function(wss) { this.client_cnt ++; if(this.client_cnt > 1) return; // 以下接続開始時の処理 //WebSocketサーバから全クライアントへメッセージ送信 wss.sendAllClients('デバイスを初期化しました'); } WsEventHandler.prototype.handleEvent = function(wss, data) { if(! data) return; //イベントに応じた処理実行 wss.sendAllClients(data.name + 'の' + data.event +'を認識しました'); } WsEventHandler.prototype.closed = function(wss) { this.client_cnt --; if(this.client_cnt > 0) return; // 以下接続終了時の処理 } module.exports = WsEventHandler
WebScoketでスイッチが押された時に、Raspberry Piに何をさせるか、をここに記載するかたちになります。
以上でソフトウェア的なスイッチが完成しました。
プロジェクトをデバッグ実行します。
DEBUG=wsswitch:* npm start
Webブラウザでアクセスしてみますと。
http://<Raspberry PiのIPアドレス>:3000
ボタン(ソフトウェア的なスイッチ)が5つ表示されます。ボタンを押すと、スマートフォン/PCからRaspberryPiへ、押されたボタンの名前(switch1)とイベント(mousedown)がデータとして送信されます。ボタンを離すと同様にmouseupが送信されます。
Raspberry Pi側は、9番で作成したws-event-handler.jsファイルのWsEventHandler.prototype.handleEvent関数でイベントを受け取ることができます。
ボタンON/OFFに対応するRaspberry Piに行わせたい処理を、この関数に書き足して使用するかたちになります。
Node.js(JavaScript)からPythonを呼び出すことも可能のようです。こちらの記事でも同様に、Express.jsからPythonのプログラムを実行しています。
本格的にNode.jsプロジェクトを編集する場合、Raspberry Pi本体で行うのではなく、WindowsやMac、LinuxのPCでVisual Studio Codeを使用する方法が効率的かと思います。
Node.jsのデバッグ実行につきましては、よろしければこちらの記事を御覧ください。