RasberryPiでEthereum(ERC20)トークン対応自動販売機をプロトタイピング
Ethereumのアカウントを見張って、ERC20トークンの入金があったら何か物理的に動作させるという自動販売機的なデバイスを作成する場合の参考のため、プロトタイピングの様子をご紹介します。
当社ではERC20の社内通貨FABコインを発行&運用して、コミュニティ通貨の運用ノウハウを蓄積し、またそれがコミュニティに与える影響について調査しています。FABコインについて詳しくはこちらをご覧ください。
簡単に紹介すると、FABコインはビジネスチャット上でボットにコマンドを指定することで簡単にやり取りができる社内コミュニティ通貨です。チャット上で「ありがとう」など感謝の言葉をかけると、感謝された人に少額のコインが発行されプレゼントされるという「ピアボーナス」の機能を持っています。貯めたコインは何に使うのか?というと、社内の不用品フリーマーケットでものを売買したり、ちょっとした頼みごと(例:コンビニ行くならおにぎり買ってきて)をお願いするときにお礼として渡すなどです。
このコインの使い所をもう少し増やしたいと思っていて、昨年より社内販売のドリンクを買えるように画策してきました。社内販売は担当者が利用者のリクエストに応じて、ネットの格安通販などで安く仕入れて、外部で買うよりもお得にドリンクを購入できるという福利厚生の一種です。このドリンクの一部をFABコインの販促予算の一部で買い上げ、希望者に社内通貨で販売するというスキームで社内通貨による販売を実現します。
社内販売ドリンクは販売所に置かれた貯金箱にお金を入れるという無人販売所の形式で運営されています。これと同じことをトークンで運用するためには、何らかの電子的な貯金箱を用意する必要があります。FABコインはビジネスチャット上で送ることができる通貨なので、社内販売の貯金箱に該当するアカウントを作り、そこに送金することでこれを実現すると良いでしょう。しかし、ただ「送金しました」というだけでは、面白みに欠けます。そこで、電磁的に記録された価値を物理的に手に取れるもの変換することを誇張して表現するため、FABコインで動作する自動販売機を作ることにしました。
とはいえ、自動販売機の本物を用意するのは大変です。では、もう少し象徴的な自動販売機=券売機はどうでしょうか。券売機であれば蕎麦屋などで使ったことがある人も多く、コンセプトの表現に説明が不要です。コインをチャットで券売機に送ると、券売機がドリンクチケットを発行する。それをドリンクと交換する。こういう流れです。一回紙を挟む必要があるところも、なかなか日本的な趣がありますね。
券売機について機能のレベルで考えてみましょう。券売機はEthereumノードであって、とあるアカウントへの送金を見張り、入金があればチケットを発行する。そんな装置です。シンプルで、プログラム的にも難しいところはありません。Ethereumのノードプログラムにはライトモード(ウォレット向きの軽量動作モード)がありますので、ただアカウントを見張るだけなら、非力なコンピュータでも動作が可能です。
そこで今回はこの様な用途に向いた小型で安い学習用コンピュータ「RaspberryPi」と安価なレシートプリンター(サーマルプリンタ)でこれをプロトタイピングしてみました。
プロトタイプの概要
スケッチとしては次の様な形になります。
詳細
難しくはないと書きましたが、今回のプロトタイピングをするには3つ考えなくてはならないことがあります。上記のスケッチで大体の雰囲気は掴んでいただけると思いますので、以下ではこれらの各課題部分の詳細を紹介します。
- RaspberryPiでEthereumノードを動かす
- RaspberryPiでサーマルプリンターを動かす
- 券売機のプログラムを作る
おっとその前に、セットアップについてご案内しておきます。
調達およびセットアップ
RaspberryPi(RaspberryPi3 Model B+)はキットで11,000円程度、サーマルプリンタは5,000円程度で、Amazon.comで手に入りました。今回はキットを購入しましたので、プリインストールされているNOOBSをつかっても良いですが、念のため一度全てをフォーマットしてRaspbian Liteをインストールしました。インストールは難しいところはありません。次の様なページが参考になります。
https://raspida.com/install-rasbian-balenaetcher
アプリケーションはjavascript(Node.js)で書きます。そのためNode.jsを設定します。インストールについて難しいところはありませんね。次の様なページが参考になります。
https://qiita.com/interceptor128/items/29abb0466ca994bc0e8d
RaspberryPiでEthereumノード(geth)を動かす
これは難しいところがありません。
https://geth.ethereum.org/downloads/
こちらから使用しているコンピュータのアーキテクチャにあったものをDLして解凍して、適当な場所に配置するだけです。
wget https://gethstore.blob.core.windows.net/builds/geth-linux-arm7-1.9.9-01744997.tar.gz
FABコインはテストネットのRinkeby上にデプロイされています。ネットワークをRinkebyに、Syncmodeをlightにして起動させます。今回、Ethereumの適当なアドレスへの送金を見張るだけになりますので特定のERC20のTransferイベントを監視するだけで良いです。そのためEthereumノードはアプリケーションに対してwebsocketのAPIだけ提供すれば良いでしょう。その様な設定でgethを起動します。
geth --rinkeby --syncmode "light" --ws
動いています。Ethereumノードはずっと動いていて良いので、supervisorで常時起動に設定しておきます。今回の設定では、CPU使用率は35%ぐらいです。
RaspberryPiでサーマルプリンターを動かす
「これが難しいのではないか…」と当初個人的には思っていました。が、意外にも難しくありませんでした。Amazonで手に入る安価なサーマルプリンタは大体ESC/POSというエプソンのコマンドセットで動作する様です。NPMで探してみたらESCPOSというライブラリがありましたので、これを使うことにしました。
https://www.npmjs.com/package/escpos
適当なプログラムを作りプリンタをRaspberryPiのUSBにつないで、”Hello World”の印刷を試みるとデバイスを取得するところでERRORが出ており、udevデバイスを見てみるとおそらくこれがプリンタだというものパーミッションにユーザの書き込み権限がありませんでしたのでsudo chmod 666
して試すと印刷できました。
const escpos = require('escpos');
const device = new escpos.USB(0x0416,0x5011);
const printer = new escpos.Printer(device);
device.open((err)=>{
if(!err) console.log(err);
else printer.text("Hello world").close();
});
このライブラリ自体は割と癖があり調べるのが億劫でした。まずは「動けばOK」の精神であまり深追いせずに使っていきましょう。デバイスは今後USBを差し替えても大丈夫な様にudevルールを設定しておきます。
sudo nano /etc/udev/rules.d/99-usbprint.rules
[99-usbprint.rules]
SUBSYSTEM=="usb", ATTR{idVendor}=="0416" , ATTR{idProduct}=="5011" , MODE="0666"
sudo udevadm control --reload-rules
券売機のプログラムを作る
準備が整いました、プログラムを書いていきます。やっぱりあまり難しいところはありません。諸設定をした後、fabcoin.events.Transferでイベントハンドラを登録して、イベントごとに内容をプリントするいうだけです。次の様な感じです。
const Web3 = require("web3");
const cw = require("./chatworkmessage.js");
const config = require("config");
const room_id = config.get("Chatwork.BotRoomID");
const ABI = require("./abi.json");
const address = "0xc3f2F8E6968124dD4e9381CE4fAd6E4faBF21CaD";
const scan = "https://rinkeby.etherscan.io/tx/";
const escpos = require("escpos");
const EOA = config.get("Ethereum.DrinkBotEOA");
(async () => {
const web3 = await new Web3(new Web3.providers.WebsocketProvider("ws://localhost:8546"));
const fabcoin = await new web3.eth.Contract(ABI, address);
const device = await new escpos.USB(0x0416, 0x5011);
const printer = await new escpos.Printer(device);
cw.post(room_id, "Okay, I'm ready to work.");
fabcoin.events.Transfer(
{
fromBlock: "latest",
filter: { to: EOA }
},
(err, event) => {
if (err) {
console.log(err);
cw.post(room_id,err).catch(console.log);
} else {
let scan_url = scan + event.transactionHash;
let fab = Math.round(event.returnValues.value / 10000000000000000);
fab = fab / 100.0;
console.log(JSON.stringify(event));
let tx = `[info][title]I confirmed your remittance.[/title]blockNumber:${event.blockNumber}\nfrom:${event.returnValues.from}\namount:${event.returnValues.value}\nfab:${fab}[hr]${scan + event.transactionHash}[/info]`;
if (fab >= 0.1) {
cw.post(room_id, tx).catch(console.log);
let txt = `\n=== Drink Ticket ===\n\nblockNumber:${event.blockNumber}\ntx#:${event.transactionHash.slice(0,10)}...\nfrom:${event.returnValues.from.slice(0,10)}...\nto:${event.returnValues.to.slice(0, 10)}...\namount:${event.returnValues.value}\nfab:${fab}\nyen:${fab * 100}\n\n\n-----------\n\n`;
device.open(function(err) {
if (err) {
console.log(err);
cw.post(room_id, err).catch(console.log);
} else printer.text(txt).close();
});
} else {
cw.post(
room_id,
"Thank you. But I can't print a ticket below 0.1 FAB."
).catch(console.log);
}
}
}
);
})();
動作の様子は次の通りです。
まとめ
もうすこし色々あるかと思いましたが、すんなりとプロトタイプを作ることができました。
今回はアウトプットがプリンターですが、オートロックに繋いで入室を管理したり、リレーで通電をコントロールすることで様々なデバイスでトークン支払い対応できます。こんなものはどう?というアイデアがありましたら是非教えてください。またプロトタイピングのため、解説やコードはかなり荒いです。技術的なところで、詳しく知りたいところなどありましたら質問お寄せください。
ファブリカコミュニケーションズで働いてみませんか?
あったらいいな、をカタチに。人々を幸せにする革新的なサービスを、私たちと一緒に創っていくメンバーを募集しています。
ファブリカコミュニケーションズの社員は「全員がクリエイター」。アイデアの発信に社歴や部署の垣根はありません。
“自分から発信できる人に、どんどんチャンスが与えられる“そんな環境で活躍してみませんか?ご興味のある方は、以下の採用ページをご覧ください。