ブロックチェーンとpythonでIoTを動かそう

#やったこと
ブロックチェーンはシェアリングエコノミーを低コストで実現できる可能性があります。そこで以下の様なシステムをプロトタイピングしてみました。
プラットフォームはイーサリアムを使用しています。
パソコンとUSB機器があれば、無料で試すことができます。
OSはWindows10を使用しました。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/158180/f388257c-9a99-9e54-1115-23c41ac28606.png)

１．入金されると変数がTrueになるコントラクトを作成します。
２．pythonから変数の状態を監視します。変数がTrueの場合は、自動的にIoTの電源をONにします。
３．ユーザーは一定時間IoTを使用することが出来ます。
４．一定時間後にIoTの電源を自動的にOFFにします。同時に、変数の値をFalseに変更します。この処理もpythonから行います。

#スマートコントラクトの実行環境の構築
まずはスマートコントラクトを実行する環境を構築しましょう。
##GethとMistWalletのダウンロード
１．以下をダウンロードして下さい。下記は少し古いバージョンです。最新版での動作確認はしていません。

Geth1.6.5 Installer
https://geth.ethereum.org/downloads/
Ethereum-Wallet-win64-0-8-10.zip
https://github.com/ethereum/mist/releases

２．Geth1.6.5をインストールします。
Destination Folderは以下にして下さい。

```
C:\Geth
```

![image.png](https://qiita-image-store.s3.amazonaws.com/0/158180/b560c4eb-e23e-bf89-cfcd-8cb7ca731f16.png)

３．Ethereum-Wallet-win64-0-8-10.zipを解凍して以下に保存して下さい。

```
C:\Geth
```

##プライベートネットの作成

１．以下のディレクトリを作成し、genesis.jsonを保存します。

```
C:\Geth\private_net\genesis.json
```

```json: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"
}
```

２．コマンドプロンプトから以下のコマンドを実行し、プライベートネットを初期化します。

```
geth --datadir C:\Geth\private_net init C:\Geth\private_net\genesis.json
```

![image.png](https://qiita-image-store.s3.amazonaws.com/0/158180/443b390f-498b-6d4e-4056-c040844aa484.png)

３．コマンドプロンプトで以下のコマンドを実行し、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
```

４．以下のコマンドを2回実行し、２つのアカウントを作成します。
１つ目のアカウントはIoTのオーナーです。もう１つはIoTの利用者です。

```
personal.newAccount("password")
```

![image.png](https://qiita-image-store.s3.amazonaws.com/0/158180/c388d3b8-ff2c-6afe-b348-e525a52702f3.png)

各アカウントのアドレスはランダムに決まります。私の場合は以下の様なアドレスが与えられました。

```
アカウント０：IoTとコントラクトのオーナー
> personal.newAccount("password")
"0x75481e0328a78ee0352e6a91605758386ae85676"
アカウント１：IoTの利用者
> personal.newAccount("password")
"0x30d25e40ebd92dcec587eee48ba91cb0db555782"
```

５．以下のコマンドを実行してマイニングを開始します。
マイニングしていないと、送金やコントラクトが実行されません。

```
miner.start(4)
```

(4)はマイニングに使用するCPUのコア数です。
アカウント０がマイニングを行うことになります。

６．以下のコマンドを実行して、アカウント０の残金を確認します。

```
web3.fromWei(eth.getBalance(eth.accounts[0]), "ether")
```

アカウント０はマイニング報酬を得ていることが分かります。
試しにアカウント１の残金も確認してみてください。ゼロになっているはずです。
ここで以下のコードを実行して、gethを終了して下さい。

```
exit
```

![image.png](https://qiita-image-store.s3.amazonaws.com/0/158180/e0c1337c-f32c-71ff-355f-747808eeeb51.png)

７．パスワードの入力を省略します。
通常は、送金やコントラクトの実行時にパスワードの入力を求められますが、今回はプライベートネット上のテストなのでパスワードの入力を省略できる様にします。

以下のディレクトリにpassword.txtを保存します。

```
C:\Geth\private_net
```

```txt:password.txt
password
password
```

８．コマンドプロンプトで以下のコマンドを実行し、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
```

９．以下のコマンドを実行して、アカウント０からアカウント１へ送金します。

```
eth.sendTransaction({from: eth.accounts[0], to:eth.accounts[1], value: web3.toWei(100, "ether")})
```

以下のコマンドでアカウント０とアカウント１の残金を確認します。

```
> web3.fromWei(eth.getBalance(eth.accounts[0]), "ether")
710
> web3.fromWei(eth.getBalance(eth.accounts[1]), "ether")
0
```

今はアカウント１の残金は０です。

以下のコマンドでマイニングをスタートした後に、アカウント０とアカウント１の残金を再度確認してみます。

```
> miner.start(4)
null
> web3.fromWei(eth.getBalance(eth.accounts[0]), "ether")
650
> web3.fromWei(eth.getBalance(eth.accounts[1]), "ether")
100
```

![image.png](https://qiita-image-store.s3.amazonaws.com/0/158180/b652af64-3fd9-bc22-76f5-2beeed9d2a0e.png)

送金が完了していることが分かります。

##コントラクトのデプロイと動作確認

１．Gethを起動した状態で、Ethereum Wallet.exeを実行して下さい。

```
C:\Geth\win-unpacked\Ethereum Wallet.exe
```

２．以下の操作を行い、ShareringIOT.solをデプロイします。

```java: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);
	}
}
```

![image.png](https://qiita-image-store.s3.amazonaws.com/0/158180/34a79eb5-baaf-4b0f-61c4-36d613544ce1.png)

CONTRACTSタブを選択し、DEPLOY NEW CONTRACTをクリックします。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/158180/adf39fb3-c083-69d3-60b1-a77ca578fe8f.png)

SOLIDITY CONTRACT SOURCE CODEにShareringIOT.solをコピペします。
Pick a contractでSharering IOTを選択します。
DEPLOYをクリックします。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/158180/7320751f-defa-3656-8e39-3ffb92a28b24.png)

パスワード（"password"）を入力して、SEND TRANSACTIONをクリックします。
※Gethはパスワードの入力を省略する設定で起動していますが、MistWalletを使用する場合は、パスワードの入力を求められます。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/158180/df33dbb3-a93e-12f6-c457-cc039ba9440f.png)

WALLETSタブを見ると、ShareringIOT.solがブロックに取り込まれて行くことが分かります。

３．以下の操作を行い、コントラクトの動作を確認します。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/158180/c5a199b4-c9e7-312d-6a0d-503d1e9560e8.png)

CONTRACTSタブを選択し、SHARERINGIOTをクリックします。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/158180/157b9269-2051-682d-57d4-98fbb896615b.png)

Select functionでPay To Switchを選択します。
Execute fromではPay To Switchを実行するアカウント、つまりIoTを使用するユーザーを選択します。
　※私の環境ではなぜかアカウント１がMistWalletでアカウント５になっていました。
Send ETHERでIoTの使用料金を指定します。とりあえず、１としましょう。
EXECUTEをクリックし、コントラクトをデプロイした時と同様にSEND TRANSACTIONします。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/158180/1dd46b1d-0191-fab2-788a-ab2b90d51623.png)

ちょっと待つと、StatusがYESになりました。つまりIoTのスイッチがONになりました。
またコントラクトに１ETHER入金されています。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/158180/1a01c269-ca47-98c9-b284-29216da1a991.png)

１分後にUpdate Statusを実行して下さい。この処理でIoTのスイッチがOFFになります。
Update Statusは入金を伴わないので、Send ETHERは０にします。０以外だとSEND TRANSACTIONできません。
※このコントラクトは入金額に依らず１分間だけIoTが使えるようになっています。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/158180/29af35be-6cc2-4d45-e9f3-63144dc140f8.png)

#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で取得できます。

```python:Web3Test.py

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は環境に応じて異なりますので、以下の方法で取得し書き換えて下さい。

１．abiの取得方法

![image.png](https://qiita-image-store.s3.amazonaws.com/0/158180/919402a4-bf02-29e6-1929-96ca88d5fcd8.png)

右中央のShow Interfaceをクリックします。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/158180/560f9b9a-c2a6-e024-081c-dcbb3e1feb34.png)

Contract JSON Interfaceの中身をコピーして、abiに代入して下さい。
trueとfalseはTrueとFalseに書き直して下さい。trueとfalseのままだとエラーになります。

２．contract_addressの取得方法

![image.png](https://qiita-image-store.s3.amazonaws.com/0/158180/b8723bb3-7a30-30df-3858-557a912ddae8.png)

コントラクトのアドレスは上図の0x3c0F217...の部分になります。

#コントラクトの情報を取得してIoTを制御する
USB機器とechonet lite対応のエアコンのON/OFF切り替えを実装しました。
USB機器はデバイスマネージャーで有効/無効の切り替えができるものなら何でもOKです。
echonet liteはwifiに接続している家電機器を制御する通信プロトコルです。ご興味のある方は以下をご参照下さい。
https://echonet.jp/about/

##USB機器のON/OFFを切り替える

１．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

２．以下の４つのバッチファイルを作成します。
USB ON

```usb_enable.bat
"C:\Program Files (x86)\Windows Kits\10\Tools\x64\devcon.exe" enable "USB\VID_14CD&PID_1212*"
```
```power_shell_enable.bat
powershell start-process usb_enable.bat -verb runas
```
USB OFF

```usb_disable.bat
"C:\Program Files (x86)\Windows Kits\10\Tools\x64\devcon.exe" disable "USB\VID_14CD&PID_1212*"
```
```power_shell_disable.bat
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はデバイスマネージャーから確認できます。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/158180/0f37c08c-6079-3efd-3a59-1844e866ddb9.png)

以下はコードになります。
バッチファイルと同じディレクトリで実行して下さい。

```python:SmartContractUSB.py

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対応家電を持っていないので動作確認できていませんが、コードを載せます。

```python:SmartContractEchoNetLite.py

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


#参考文献
以下の本がすごく参考になりました。お勧めです！！
[堅牢なスマートコントラクト開発のためのブロックチェーン[技術]入門](https://www.amazon.co.jp/%E5%A0%85%E7%89%A2%E3%81%AA%E3%82%B9%E3%83%9E%E3%83%BC%E3%83%88%E3%82%B3%E3%83%B3%E3%83%88%E3%83%A9%E3%82%AF%E3%83%88%E9%96%8B%E7%99%BA%E3%81%AE%E3%81%9F%E3%82%81%E3%81%AE%E3%83%96%E3%83%AD%E3%83%83%E3%82%AF%E3%83%81%E3%82%A7%E3%83%BC%E3%83%B3-%E6%8A%80%E8%A1%93-%E5%85%A5%E9%96%80-%E7%94%B0%E7%AF%AD-%E7%85%A7%E5%8D%9A/dp/4774193534/ref=sr_1_2?ie=UTF8&qid=1524808342&sr=8-2&keywords=%E3%83%96%E3%83%AD%E3%83%83%E3%82%AF%E3%83%81%E3%82%A7%E3%83%BC%E3%83%B3)