やったこと
ブロックチェーンはシェアリングエコノミーを低コストで実現できる可能性があります。そこで以下の様なシステムをプロトタイピングしてみました。
プラットフォームはイーサリアムを使用しています。
パソコンとUSB機器があれば、無料で試すことができます。
OSはWindows10を使用しました。
1.入金されると変数がTrueになるコントラクトを作成します。
2.pythonから変数の状態を監視します。変数がTrueの場合は、自動的にIoTの電源をONにします。
3.ユーザーは一定時間IoTを使用することが出来ます。
4.一定時間後にIoTの電源を自動的にOFFにします。同時に、変数の値をFalseに変更します。この処理もpythonから行います。
スマートコントラクトの実行環境の構築
まずはスマートコントラクトを実行する環境を構築しましょう。
GethとMistWalletのダウンロード
1.以下をダウンロードして下さい。下記は少し古いバージョンです。最新版での動作確認はしていません。
Geth1.6.5 Installer
https://geth.ethereum.org/downloads/
Ethereum-Wallet-win64-0-8-10.zip
https://github.com/ethereum/mist/releases
2.Geth1.6.5をインストールします。
Destination Folderは以下にして下さい。
C:\Geth
3.Ethereum-Wallet-win64-0-8-10.zipを解凍して以下に保存して下さい。
C:\Geth
プライベートネットの作成
1.以下のディレクトリを作成し、genesis.jsonを保存します。
C:\Geth\private_net\genesis.json
{
"config": {
"chainId": 15,
"homesteadBlock": 0,
"eip155Block": 0,
"eip158Block": 0
},
"nonce": "0x0000000000000042",
"mixhash": "0x0000000000000000000000000000000000000000000000000000000000000000",
"difficulty": "0x00",
"alloc": {},
"coinbase": "0x0000000000000000000000000000000000000000",
"timestamp": "0x00",
"parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
"extraData": "0x00",
"gasLimit": "0x1312d00"
}
2.コマンドプロンプトから以下のコマンドを実行し、プライベートネットを初期化します。
geth --datadir C:\Geth\private_net init C:\Geth\private_net\genesis.json
3.コマンドプロンプトで以下のコマンドを実行し、Gethを起動します。
geth --networkid "10" --nodiscover --datadir "C:\Geth\private_net" --rpc --rpcaddr "localhost" --rpcport "8545" --rpccorsdomain "*" --rpcapi "eth,net,web3,personal" --targetgaslimit "20000000" console 2>> C:\Geth\private_net\geth_err.log
4.以下のコマンドを2回実行し、2つのアカウントを作成します。
1つ目のアカウントはIoTのオーナーです。もう1つはIoTの利用者です。
personal.newAccount("password")
各アカウントのアドレスはランダムに決まります。私の場合は以下の様なアドレスが与えられました。
アカウント0:IoTとコントラクトのオーナー
> personal.newAccount("password")
"0x75481e0328a78ee0352e6a91605758386ae85676"
アカウント1:IoTの利用者
> personal.newAccount("password")
"0x30d25e40ebd92dcec587eee48ba91cb0db555782"
5.以下のコマンドを実行してマイニングを開始します。
マイニングしていないと、送金やコントラクトが実行されません。
miner.start(4)
(4)はマイニングに使用するCPUのコア数です。
アカウント0がマイニングを行うことになります。
6.以下のコマンドを実行して、アカウント0の残金を確認します。
web3.fromWei(eth.getBalance(eth.accounts[0]), "ether")
アカウント0はマイニング報酬を得ていることが分かります。
試しにアカウント1の残金も確認してみてください。ゼロになっているはずです。
ここで以下のコードを実行して、gethを終了して下さい。
exit
7.パスワードの入力を省略します。
通常は、送金やコントラクトの実行時にパスワードの入力を求められますが、今回はプライベートネット上のテストなのでパスワードの入力を省略できる様にします。
以下のディレクトリにpassword.txtを保存します。
C:\Geth\private_net
password
password
8.コマンドプロンプトで以下のコマンドを実行し、Gethを起動して下さい。
--unlock以下のアドレスは環境によって異なりますので、変更して下さい。
geth --networkid "10" --nodiscover --datadir "C:\Geth\private_net" --rpc --rpcaddr "localhost" --rpcport "8545" --rpccorsdomain "*" --rpcapi "eth,net,web3,personal" --targetgaslimit "20000000" --unlock 0x75481e0328a78ee0352e6a91605758386ae85676,0x30d25e40ebd92dcec587eee48ba91cb0db555782 --password "C:\Geth\private_net\password.txt" console 2>> C:\Geth\private_net\geth_err.log
9.以下のコマンドを実行して、アカウント0からアカウント1へ送金します。
eth.sendTransaction({from: eth.accounts[0], to:eth.accounts[1], value: web3.toWei(100, "ether")})
以下のコマンドでアカウント0とアカウント1の残金を確認します。
> web3.fromWei(eth.getBalance(eth.accounts[0]), "ether")
710
> web3.fromWei(eth.getBalance(eth.accounts[1]), "ether")
0
今はアカウント1の残金は0です。
以下のコマンドでマイニングをスタートした後に、アカウント0とアカウント1の残金を再度確認してみます。
> miner.start(4)
null
> web3.fromWei(eth.getBalance(eth.accounts[0]), "ether")
650
> web3.fromWei(eth.getBalance(eth.accounts[1]), "ether")
100
送金が完了していることが分かります。
コントラクトのデプロイと動作確認
1.Gethを起動した状態で、Ethereum Wallet.exeを実行して下さい。
C:\Geth\win-unpacked\Ethereum Wallet.exe
2.以下の操作を行い、ShareringIOT.solをデプロイします。
pragma solidity ^0.4.11;
contract ShareringIOT {
address public owner; // サービスオーナーのアドレス
address public user; // ユーザーのアドレス
uint public endtime; // 利用終了時刻(UnixTime)
bool public status; // trueの場合は利用可能
// サービスオーナーの権限チェック
modifier onlyOwner() {
require(msg.sender == owner);
_;
}
// コンストラクタ
function ShareringIOT() {
owner = msg.sender;
}
// 支払い時に呼ばれる関数
function payToSwitch() public payable {
// 利用者がいない場合のみ処理を実行する
require(status == false);
// 送金がある場合のみ処理を実行する
require(msg.value > 0);
user = msg.sender;
endtime = now + 60; // 入金すると60秒間使用できる
status = true;
}
// statusを初期化する関数
// 利用終了時刻になったら呼び出される
function updateStatus() public {
// 利用終了時刻に達していなければ処理を終了する
require(now > endtime);
// statusを初期化する
status = false;
user = 0x0;
endtime = 0;
}
// 支払われたetherを引き出すための関数
function withdrawFunds() public onlyOwner {
if (!owner.send(this.balance))
throw;
}
// コントラクトを破棄するための関数
function kill() public onlyOwner {
selfdestruct(owner);
}
}
CONTRACTSタブを選択し、DEPLOY NEW CONTRACTをクリックします。
SOLIDITY CONTRACT SOURCE CODEにShareringIOT.solをコピペします。
Pick a contractでSharering IOTを選択します。
DEPLOYをクリックします。
パスワード("password")を入力して、SEND TRANSACTIONをクリックします。
※Gethはパスワードの入力を省略する設定で起動していますが、MistWalletを使用する場合は、パスワードの入力を求められます。
WALLETSタブを見ると、ShareringIOT.solがブロックに取り込まれて行くことが分かります。
3.以下の操作を行い、コントラクトの動作を確認します。
CONTRACTSタブを選択し、SHARERINGIOTをクリックします。
Select functionでPay To Switchを選択します。
Execute fromではPay To Switchを実行するアカウント、つまりIoTを使用するユーザーを選択します。
※私の環境ではなぜかアカウント1がMistWalletでアカウント5になっていました。
Send ETHERでIoTの使用料金を指定します。とりあえず、1としましょう。
EXECUTEをクリックし、コントラクトをデプロイした時と同様にSEND TRANSACTIONします。
ちょっと待つと、StatusがYESになりました。つまりIoTのスイッチがONになりました。
またコントラクトに1ETHER入金されています。
1分後にUpdate Statusを実行して下さい。この処理でIoTのスイッチがOFFになります。
Update Statusは入金を伴わないので、Send ETHERは0にします。0以外だとSEND TRANSACTIONできません。
※このコントラクトは入金額に依らず1分間だけIoTが使えるようになっています。
pythonからコントラクトにアクセスする
pythonからコントラクトへのアクセスはWeb3.pyを使用します。
Web3.pyのインストール
Web3.pyのインストールにはMicrosoft Build Toolsが必要です。以下からダウンロード、インストールして下さい。
Microsoft Build Tools 2015
https://www.microsoft.com/ja-JP/download/details.aspx?id=48159
次に以下のコマンドを実行して、Web3.pyをインストールします。
pip3 install web3
Web3.pyを使ってコントラクトの情報を取得する
以下のコードを実行するとIoTのスイッチの状態がpythonで取得できます。
from web3 import Web3,HTTPProvider
from web3.contract import ConciseContract
web3 = Web3(HTTPProvider('http://localhost:8545'))
# コントラクトのインターフェース
abi = [ { "constant": True, "inputs": [], "name": "status", "outputs": [ { "name": "", "type": "bool", "value": True } ], "payable": False, "type": "function" }, { "constant": False, "inputs": [], "name": "withdrawFunds", "outputs": [], "payable": False, "type": "function" }, { "constant": True, "inputs": [], "name": "endtime", "outputs": [ { "name": "", "type": "uint256", "value": "1524623990" } ], "payable": False, "type": "function" }, { "constant": False, "inputs": [], "name": "kill", "outputs": [], "payable": False, "type": "function" }, { "constant": True, "inputs": [], "name": "user", "outputs": [ { "name": "", "type": "address", "value": "0xcc2c45a9a59bdc022ae26303b1fe67f38980632f" } ], "payable": False, "type": "function" }, { "constant": False, "inputs": [], "name": "updateStatus", "outputs": [], "payable": False, "type": "function" }, { "constant": False, "inputs": [], "name": "payToSwitch", "outputs": [], "payable": True, "type": "function" }, { "constant": True, "inputs": [], "name": "owner", "outputs": [ { "name": "", "type": "address", "value": "0x1787ddf19df506ac302b4ce7fc554ffacbbd7fd3" } ], "payable": False, "type": "function" }, { "inputs": [], "payable": False, "type": "constructor" } ]
# コントラクトのアドレス
contract_address = "0xf043da01c282B7F2e5BEa826Cb0bC2E5d68A9cB6"
# Contract instance in concise mode
contract = web3.eth.contract(abi=abi, address=contract_address, ContractFactoryClass=ConciseContract)
print(contract.status()) # IoTのスイッチの状態を取得する
>> False or True
abiとcontract_addressは環境に応じて異なりますので、以下の方法で取得し書き換えて下さい。
1.abiの取得方法
右中央のShow Interfaceをクリックします。
Contract JSON Interfaceの中身をコピーして、abiに代入して下さい。
trueとfalseはTrueとFalseに書き直して下さい。trueとfalseのままだとエラーになります。
2.contract_addressの取得方法
コントラクトのアドレスは上図の0x3c0F217...の部分になります。
コントラクトの情報を取得してIoTを制御する
USB機器とechonet lite対応のエアコンのON/OFF切り替えを実装しました。
USB機器はデバイスマネージャーで有効/無効の切り替えができるものなら何でもOKです。
echonet liteはwifiに接続している家電機器を制御する通信プロトコルです。ご興味のある方は以下をご参照下さい。
https://echonet.jp/about/
USB機器のON/OFFを切り替える
1.USB機器のON/OFFにはdevcon.exeを使用します。
devcon.exeはコマンドラインからPCに接続されている機器の有効/無効を切り替えるソフトです。
devcon.exeはWindows Driver Kitに含まれています。以下からWindows Driver Kitをダウンロードとインストールして下さい。
https://docs.microsoft.com/ja-jp/windows-hardware/drivers/download-the-wdk
2.以下の4つのバッチファイルを作成します。
USB ON
"C:\Program Files (x86)\Windows Kits\10\Tools\x64\devcon.exe" enable "USB\VID_14CD&PID_1212*"
powershell start-process usb_enable.bat -verb runas
USB OFF
"C:\Program Files (x86)\Windows Kits\10\Tools\x64\devcon.exe" disable "USB\VID_14CD&PID_1212*"
powershell start-process usb_disable.bat -verb runas
usb_enabel.batとusb_disable.batをわざわざpowershellから呼び出している理由は、usb_enabel.batとusb_disable.batを管理者権限で実行するためです。
usb_enabel.batとusb_disable.batの以下の部分はハードウェアIDになります。制御したいデバイスに応じて書き換えます。
USB\VID_14CD&PID_1212*
ハードウェアIDはデバイスマネージャーから確認できます。
以下はコードになります。
バッチファイルと同じディレクトリで実行して下さい。
from time import sleep
from web3 import Web3,HTTPProvider
from web3.contract import ConciseContract
import subprocess
web3 = Web3(HTTPProvider('http://localhost:8545'))
# コントラクトのインターフェース
abi = [ { "constant": True, "inputs": [], "name": "status", "outputs": [ { "name": "", "type": "bool", "value": False } ], "payable": False, "type": "function" }, { "constant": False, "inputs": [], "name": "withdrawFunds", "outputs": [], "payable": False, "type": "function" }, { "constant": True, "inputs": [], "name": "endtime", "outputs": [ { "name": "", "type": "uint256", "value": "0" } ], "payable": False, "type": "function" }, { "constant": False, "inputs": [], "name": "kill", "outputs": [], "payable": False, "type": "function" }, { "constant": True, "inputs": [], "name": "user", "outputs": [ { "name": "", "type": "address", "value": "0x0000000000000000000000000000000000000000" } ], "payable": False, "type": "function" }, { "constant": False, "inputs": [], "name": "updateStatus", "outputs": [], "payable": False, "type": "function" }, { "constant": False, "inputs": [], "name": "payToSwitch", "outputs": [], "payable": True, "type": "function" }, { "constant": True, "inputs": [], "name": "owner", "outputs": [ { "name": "", "type": "address", "value": "0x75481e0328a78ee0352e6a91605758386ae85676" } ], "payable": False, "type": "function" }, { "inputs": [], "payable": False, "type": "constructor" } ]
# コントラクトのアドレス
contract_address = "0x3c0F217091d0D753De586cB2B3b5551793555E57"
# Contract instance in concise mode
contract = web3.eth.contract(abi=abi, address=contract_address, ContractFactoryClass=ConciseContract)
while(True):
if(contract.status()): # status = Trueの場合はUSB機器を起動する
subprocess.run("power_shell_enable.bat") # usbを起動する
print("USB ON.")
sleep(60) # 60秒間使用できる
contract.updateStatus(transact={"from":web3.eth.accounts[0]}) # 60秒後にstatusを初期化する
subprocess.run("power_shell_disable.bat") # usbを停止する
print("USB OFF.")
sleep(10) # 10秒に1回statusをチェック
スマート家電を制御するコード
echonet lite対応家電を持っていないので動作確認できていませんが、コードを載せます。
import socket
import codecs
from time import sleep
from web3 import Web3,HTTPProvider
from web3.contract import ConciseContract
web3 = Web3(HTTPProvider('http://localhost:8545'))
# コントラクトのインターフェース
abi = [ { "constant": True, "inputs": [], "name": "status", "outputs": [ { "name": "", "type": "bool", "value": False } ], "payable": False, "type": "function" }, { "constant": False, "inputs": [], "name": "withdrawFunds", "outputs": [], "payable": False, "type": "function" }, { "constant": True, "inputs": [], "name": "endtime", "outputs": [ { "name": "", "type": "uint256", "value": "0" } ], "payable": False, "type": "function" }, { "constant": False, "inputs": [], "name": "kill", "outputs": [], "payable": False, "type": "function" }, { "constant": True, "inputs": [], "name": "user", "outputs": [ { "name": "", "type": "address", "value": "0x0000000000000000000000000000000000000000" } ], "payable": False, "type": "function" }, { "constant": False, "inputs": [], "name": "updateStatus", "outputs": [], "payable": False, "type": "function" }, { "constant": False, "inputs": [], "name": "payToSwitch", "outputs": [], "payable": True, "type": "function" }, { "constant": True, "inputs": [], "name": "owner", "outputs": [ { "name": "", "type": "address", "value": "0x75481e0328a78ee0352e6a91605758386ae85676" } ], "payable": False, "type": "function" }, { "inputs": [], "payable": False, "type": "constructor" } ]
# コントラクトのアドレス
contract_address = "0x3c0F217091d0D753De586cB2B3b5551793555E57"
# Contract instance in concise mode
contract = web3.eth.contract(abi=abi, address=contract_address, ContractFactoryClass=ConciseContract)
ip = "169.254.166.226" # エアコンのipアドレス
ECHONETport = 3610
while(True):
# 参考
# https://qiita.com/miyazawa_shi/items/725bc5eb6590be72970d
# https://qiita.com/miyazawa_shi/items/9384f0d2cd1ee255b1c1
if(contract.status()): # status = Trueの場合はエアコンを起動する.
message = b"1081000005FF010130016101800130" # エアコンを起動する指令値
msg = codecs.encode(message, 'hex_codec')
# エアコンに起動指令を送る
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sock.sendto(msg, (ip, ECHONETport))
print("Air conditioner ON.")
sleep(60) # 60秒間使用できる
contract.updateStatus(transact={"from":web3.eth.accounts[0]}) # statusを初期化する
message = b"1081000005FF010130016101800131" # エアコンを停止する指令値
msg = codecs.encode(message, 'hex_codec')
# エアコンに停止指令を送る
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sock.sendto(msg, (ip, ECHONETport))
print("Air conditioner OFF.")
sleep(10) # 10秒に1回statusをチェック
終わりに
環境構築が少し煩雑でしたが、短いコードで実装することができました。近い将来、スマートコントラクトを利用したシェアリングサービスが広がるのではないでしょうか??
何より、作って動かしてみると楽しいですね!
内容について至らぬ点が多々あると思いますが、ご意見頂けますと幸いですm(__)m
参考文献
以下の本がすごく参考になりました。お勧めです!!
堅牢なスマートコントラクト開発のためのブロックチェーン[技術]入門