#やったこと
ブロックチェーンはシェアリングエコノミーを低コストで実現できる可能性があります。そこで以下の様なシステムをプロトタイピングしてみました。
プラットフォームはイーサリアムを使用しています。
パソコンと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
#参考文献
以下の本がすごく参考になりました。お勧めです!!
堅牢なスマートコントラクト開発のためのブロックチェーン[技術]入門