LoginSignup
14
11

More than 5 years have passed since last update.

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

Last updated at Posted at 2018-04-27

やったこと

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

image.png

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

image.png

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

C:\Geth

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

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

C:\Geth\private_net\genesis.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"
}

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

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

image.png

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")

image.png

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

アカウント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

image.png

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

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

C:\Geth\private_net
password.txt
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

image.png

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

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

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

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

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

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

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

image.png

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

image.png

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

image.png

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

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

image.png

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

image.png

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

image.png

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

image.png

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

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

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

1.abiの取得方法

image.png

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

image.png

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

2.contract_addressの取得方法

image.png

コントラクトのアドレスは上図の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

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

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

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

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

参考文献

以下の本がすごく参考になりました。お勧めです!!
堅牢なスマートコントラクト開発のためのブロックチェーン[技術]入門

14
11
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
14
11