blockchain上で自動支払を行うスマートコンセントを作ってみる

More than 1 year has passed since last update.


自動支払をするスマートコンセント

Ethereumのスマートコントラクトを書いてみたくて、スマートコンセントを練習として作ってみました。

物理的にはひとまずこのような形状をしています。筐体を3Dプリントしようとしましたが、一旦力尽きました。。

Screen Shot 2017-12-28 at 18.17.55.png

概要は下記のような感じです。

Screen Shot 2017-12-28 at 18.17.55.png

このコンセプトは「C2Cで一般ユーザが別のユーザに電気を販売することができる」モデルですが、自分でサーバは立てておらず、2つのデバイスがBlockchainを経由してtrasnsactionを行います。uberやairbnbとは違い中央集権的なサーバが不要となっているため、運営が不在の状況でもサービスを提供することができるかもしれません。1

使っているフレームワークなどは以下のとおりです。

Screen Shot 2017-12-28 at 18.18.02.png

それぞれのモジュールの間の通信はこのようになっています。

Screen Shot 2017-12-28 at 18.18.07.png

似たようなプロジェクトが幾つか発表されています。



  • Nayuta


    • コンセント側は同じようなコンセプトで、支払いをスマホで行っているところは少し違うようです。今回はスマホレスにしてみたかったため、プラグにコンピュータを組み込みました

    • プロダクトデザインカッコいいな。




  • BlockCharge


    • 電気スタンドと電気自動車の間で自動で支払いを行うかたちです。

    • ドローンともつながってワイヤレス給電する未来いいな




  • Toyota Open Road Project


    • blockchainを使っているかはわからないですが、似たコンセプトです。

    • プロジェクト更新止まっちゃってるのかな、、



githubこちらですが、まだバグバグなので頑張ります、、

https://github.com/kopanitsa/blockchain-smart-outlet


スマートコントラクトを作成する

Ethereum上で動作するスマートコントラクトをsolidityで書いています。

シーケンス図の3で、コンセントからhashが送られてくると、コントラクトはhashをmapに保存します。

mapping(bytes32 => address) reserved;

function sendOutletHash(bytes32 _hash) public {
reserved[_hash] = msg.sender;
}

その後、シーケンス図8で、keyをコントラクトに送信します。ここで、keyとhashの照合が行われ、支払い(coinの移動)が実施されます。正常に取引が完了すると、eventがコンセントに送信されます(シーケンス図の10)。

mapping(address => uint256) public coinBalance; // remaining coin of each devices

event log(string);
event startSupply(address);

function payFromPlug(string _key, uint256 _coin) public {
bytes32 encrypted = keccak256(_key);
address targetOutlet = reserved[encrypted];
if (targetOutlet == 0) {
log("ERROR: Cannot find ready outlet");
return;
}

if ((int256)(coinBalance[msg.sender] - _coin) < 0) {
log("ERROR: You dont have enough coin to supply!");
return;
}
coinBalance[msg.sender] -= _coin;
coinBalance[targetOutlet] += _coin;
remainingTime[encrypted] = now + 60 * 1 minutes;

startSupply(targetOutlet);
}


ハードウェアを作成する

ハードウェアはざっくり以下のような感じです。赤線が電源、青線がGPIOです。

プラグ側

Screen Shot 2017-12-28 at 19.42.47.png

コンセント側

Screen Shot 2017-12-28 at 19.42.50.png

なお、コンセント側を家の電源(ブレーカーから壁の中を伸びてるやつ)に直接(コンセント等を経由せず)繋げる場合、電気工事士の資格が必要なので注意しましょう。


プラグ側のコードを作成する

プラグの情報処理は、コンセントに刺されて起動するところから始まります2

全体の流れ、こんな感じです。


plug/wifi.js

    setup() {

this.disconnect().then(()=> {
return this.connectOutlet();
}).then(()=> {
return this.getKeyFromOutlet();
}).then(()=> {
return this.disconnect();
}).then(()=> {
return this.connectMain();
}).then(()=> {
return this.sendEvent();
}).catch((err) => {
log.warn(err);
});
}

シーケンス図6で、wifiの接続先を通常のアクセスポイントからコンセントのローカルAPに変更するのですが、wireless-toolsというモジュールを使って以下のように実現しました。wpa_supplicant.enableでWi-Fiの接続先を変更できます。


plug/wifi.js

    connectOutlet() {

const options = {
interface: 'wlan0',
ssid: 'SmartOutlet',
driver: 'nl80211'
};
return new Promise((resolve, reject) => {
wpa_supplicant.enable(options, (err) => {
if (err) {
reject("connectOutlet:" + err);
}
resolve();
});
});
}

コンセントのローカルAPに接続できたら、コンセントに立っているhttpサーバからkeyを取得します。


plug/wifi.js

    getKeyFromOutlet() {

let _this = this;
return new Promise((resolve, reject) => {
http.get(outlet_server_url, (res) => {
let body = '';
res.setEncoding('utf8');
res.on('data', (chunk) => {
body += chunk;
});
res.on('end', (res) => {
_this.key = body;
resolve();
});
}).on('error', (e) => {
reject("http error:" + e);
});
});
}

最後に、接続先を通常のアクセスポイントに戻し、keyをコントラクトに送信します。


plug/coin.js

// すでにblockchain上にスマートコントラクトをデプロイしており、アドレスを知っているとします

const address_contract = "0x71A2897b2B222225855240c10114FdE25865b3fB";

const Web3 = require('web3');
const net = require('net')
// 事前に、gethはローカル(raspberry pi上)で動作し、IPCで接続できるようにしておきます。
let web3 = new Web3('/home/pi/.ethereum/rinkeby/geth.ipc', net);
let plugContract = new web3.eth.Contract(abi, address_contract);

...

execPayFromPlug(key, coin) {
plugContract.methods.payFromPlug(key, coin).send(
{from: this.address, gasPrice: 2000000000, gas: 3000000},
(error, result)=> {
log.debug("payFromPlug result:" + result);
}).on('transactionHash', (hash) => {
log.debug("transactionHash: " + hash);
})
}


あとは、blockchainのtransactionが完了し、コンセント側で承認完了されるのを待つだけです。


コンセント側のコードを作成する

コンセント側は2つのアプリが動いています。1つはコンセントの電気の流れを制御したり、スマートコントラクトとやりとりするメインのアプリ。もう一つは小さなHTTPサーバで、シーケンス図7でプラグにkeyを渡すのに使用されます。

今回、コンセント側のRaspberry Piは2つのwifiモジュールを持つ構成としました3。一つはRaspberry Pi 3に組みおまれているもので、上記メインのアプリ用にstation modeで動作します。もう一つはUSB wifiモジュールで上記HTTPサーバにアクセスするためのAPとして動作します。

まずはWebサーバ側。非常にシンプルです。keyはメインアプリで生成されたあと、一時的に/home/pi/key.txtに記録されるとしています。


outletserver/app.js

const http = require('http');

const fs = require('fs');
const ip = '192.168.1.1';
const key = '/home/pi/key.txt';

http.createServer(function (req, res) {
const key = fs.readFileSync(key, 'utf8');
res.writeHead(200, {'Content-Type': 'text/plain'});
res.end(key);
}).listen(8000, ip);


次にメインのアプリ側です。

メインのアプリはプラグが接続され、スイッチが押されたことをGPIOで検知するところから始まります(シーケンス図1)。


outlet/gpio.js

    checkSwitch() {

let value = this.readGpio(SW_IN, 'utf8');
if (parseInt(value) == 1) {
this.writeGpio(SSR, 1); // コンセントから電気が流れるようにする
this.emit('switch', true);

this.initialPowerTimer = setTimeout(() => {
this.writeGpio(SSR, 0); // 一定時間後(処理が行われない場合)コンセントからの電気をOFFにする
this.emit('switch', false);
}, 60 * 1000);
}
}


続いて、ワンタイムkeyとhashを生成します。hashはランダムな16文字として生成したkeyをkeccak256でハッシュ化したものです。


outlet/outlet.js

    this.gpio.on('switch', (enabled) => {

if (enabled) {
const hashpair = _this.generateKeyAndHash(); // keyとhashを生成する
_this.coin.sendOutletHash('0x' + hashpair.datahash); // hashをblockchainに送付
fs.writeFile(key, hashpair.datakey); // keyをサーバに渡すため、ファイルとして保存
}
});

....

generateKeyAndHash() {
const l = 16;
const c = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
const cl = c.length;
let key = "";
for(let i=0; i<l; i++){
key += c[Math.floor(Math.random()*cl)];
}
let hash = createKeccakHash('keccak256').update(key).digest('hex');

return {datakey: key, datahash: hash};
}



outlet/coin.js

    // hashを送付

sendOutletHash(hash) {
web3.eth.personal.unlockAccount(this.address, this.password, 60)
.then((response) => {
this.execSendOutletHash(hash)
}).catch((error) => {
console.log(error);
});
}

execSendOutletHash(hash) {
plugContract.methods.sendOutletHash(hash).send(
{from: this.address,
gasPrice: 20000000000,
gas: 3000000},
(error, result)=> {
}).on('transactionHash', (hash) => {
log.debug("transactionHash: " + hash);
});
}


その後、コントラクト上でkeyhashの照合が行われ、startSupply(address)というeventがbroadcastされるので、それをsubscribeし、コンセントの電源を切らないようにすれば完成です。


outlet/outlet.js

    startPaymentWatching() {

let subscription = web3.eth.subscribe('logs', {
address: address_contract,
topics: [web3.utils.sha3('startSupply(address)')]
}, (error, result) => {
if (error)
log.error(error);
this.emit('contract', true);
});
}


今後

まだまだバグや動きが不審な点があるので修正しつつ、以下のようなあたりを改善していきたいと思います。


  • コントラクトのテストを書く

  • 本体のテストも書く

  • 取引を見える化する


    • ここで結局Webサーバが必要になるのか、、



  • ハードウェアをもう少しちゃんとつくる


    • Raspberry Pi(Linux)じゃなくてRTOSとかで動けば、プラグデバイスの起動時間も短く、価格も安くなりそうなのですが、geth的なものをRTOSまたはOSなしで動かすことってできるのかな、、



  • 取引用コインの入出金の方法を検討する

  • テストネットではなく本線ネットにつなげてみる??


参考URLなど





  1. じゃあだれが責任をもって運用をおこなうのかとか、もうからないのに誰がつくるのかなどは別のお話です 



  2. 今回は、プラグが刺されてスイッチが押された際、短時間のみ電源供給をするようにしました 



  3. 今後wifiモジュール一つで動作するように修正したいと考えています