嘿!您似乎在 United States,您想使用我们的 English 网站吗?
Switch to English site
Skip to main content

RS物联网区块链演示器第5部分:主机软件

Main26_80184f3ab8eccb4bed07828542c381117531fa8d.jpg

使用Python集成传感器和输出并与区块链交互。

这一系列文章着眼于为两年一度的Electronica交易会和会议设计和构建一组示范者,展示了区块链技术如何用于创建安全,分散的数据平台以及更多物联网。

在之前的帖子中,我们已经介绍了机械电子构建,然后创建了一个私有区块链网络,并为此部署了智能合约,这将支持我们的四个特定用例。 在这篇文章中,我们现在看一下驱动LED,读取按钮和传感器的Python应用程序,最后与我们的以太坊智能合约进行交互。

请注意,我们不是完整地覆盖每个Python脚本,而是查看显示应用程序关键部分如何工作的片段。

组态

application:
    role: carcrash
    impact_trigger: 10000
    leds_port: 5558

buttons:
    leds_port: 5557

mqtt:
    broker: miner

blockchain:
    boot_node: "enode://3d8007b5099e2ee9ae384aac17ff508d6827a1aec956131344df\
                6eded933656bac0a9f51768fce908580b7b293ff0d6737633e794bc7f29a\
                8683371709f98ec5@123.1.1.2:30531"
    network_id: 555
    account: "0x2BC19750cdf3991D0A27d45304276Cd4D71F6975"
    contract: "0x6636bbD9B3C364d96B8a6CCFc9e6DAcc76c316CC"
    leds_port: 5556

决定对配置文件使用YAML标记,因为这比简单的INI文件提供了更多的结构,但人类比JSON更容易解析。 上面我们可以看到汽车崩溃演示器的 /etc/iotbc/config.yml 文件。

使用配置文件允许在不同的Python脚本之间共享公共参数,并在开发期间快速更新这些参数。 但是,应该注意的是,优化使用的参数集还有空间,这可能值得投入时间,扩展节点的数量或者在生产能力中使用的节点数量。

以太坊节点

# geth APIs to expose

APIS = "admin,db,eth,debug,miner,net,shh,txpool,personal,web"

# Build the geth command

## Base parameters

gethcmd = (['/usr/local/bin/geth',
           '--datadir', '/data/bc',
           '--networkid', str(cfg['blockchain']['network_id']),
           '--bootnodes', cfg['blockchain']['boot_node'],
           '--unlock', '0',
           '--password', '/dev/null',
           '--nat', 'none',
           '--rpc',
           '--rpccorsdomain', '"*"',
           '--rpcapi', APIS])

## Append params for miner and set text to identify new block

if cfg['application']['role'] == 'miner':
    miner = True
    gethcmd.extend(('--gasprice', str(cfg['blockchain']['gasprice'])))
    gethcmd.extend(('--targetgaslimit', str(cfg['blockchain']['targetgaslimit'])))
    gethcmd.append('--mine')
    newblock = 'Successfully sealed new block'
else:
    miner = False
    newblock = 'Imported new chain segment'

# Command to tee the output from geth to a non-blocking FIFO

teecmd = (['/usr/local/bin/ftee', '/tmp/geth.out'])

上面我们可以看到geth以太坊节点软件的参数是如何在运行这个, eth-node.的Python脚本中构建的。 网络ID和引导节点地址取自上述配置文件。 role参数用于决定是否需要将节点配置为挖掘器,还要定义我们将在geth的输出中查找的字符串,该字符串将指示何时挖掘或导入新块。

The ftee实用程序是UNIX / Linux标准命令tee的非阻塞版本。 我们将输出从geth传输到此处,并从那里首先将其导向FIFO,我们可以使用例如 cat,用于观察geth节点活动,第二个目标是我们的Python脚本。

    ethnode = subprocess.Popen(gethcmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)

    fifofeed = subprocess.Popen(teecmd, stdin=ethnode.stderr, stdout=subprocess.PIPE)

    for line in fifofeed.stdout:
        out = line.decode('utf-8')

        if miner == True and 'txs=' in out:
            txs = out.split('txs=')[1].split()[0]
            if int(txs) > 0:
                leds.send_string('red,{0},{1}'.format(led_period, txs))

        elif newblock in out:
            leds.send_string('green,{0},1'.format(led_period))

上面我们可以看到Python的子进程模块如何用于执行geth和ftee,后者从前者获取输入 - 然后解析其输出以检查新块并在这些事务中,同时也将其发送到/TMP/ geth.out。

名为leds的函数使用Zero MQ与另一个进程通信,以便在开发/导入新块时指示它使绿色以太坊LED闪烁。 YAML配置中的blockchain> leds_port参数指定要连接的端口号。

Mining_716f8c29d1053f68211b4da4c781f16cddec704c.jpg

通过这个,我们可以通过确认绿色LED每5秒闪烁一次,快速确定矿工或其中一个演示单元的网络运行情况。 通过读取FIFO,我们可以获得更详细的信息,在矿工单元上,我们有一个简单的脚本,可以将此输出重定向到控制台,以便我们可以密切观察挖掘过程。

按钮和LEDs

def confpins():
    if device == 'miner':
        with open('/sys/class/gpio/export', 'w') as f:
            for pin in pins:
                if os.path.isdir('/sys/class/gpio/gpio{0}'.format(pin)) is False:
                    print('Exporting pin: {0}'.format(pin))
                    f.write(str(pin))
                    f.flush()

        for pin in pins:
            with open('/sys/class/gpio/gpio{0}/direction'.format(pin), 'w') as f:
                f.write("out")
                f.flush()

    else:
        GPIO.setmode(GPIO.BCM)
        for pin in pins:
            GPIO.setup(pin, GPIO.OUT)

eth-node 脚本一样,需要在每个单元上运行通用脚本 eth-ledsbuttons ,并通过YAML文件为硬件平台配置其行为。 使用Raspberry Pi节点,我们可以使用Python RPi.GPIO  库来驱动GPIO。

但是,据我所知,英特尔NUC没有这样的库。虽然这不是一个主要的问题,因为在Linux下我们可以通过sysfs切换和读取GPIO。

上面我们可以看到为了设置LED的GPIO而调用的函数;如果在矿工上运行,则通过写入sysfs来完成,而在Raspberry Pi上则由RPi.GPIO库处理。类似的功能负责将引脚设置为读取按钮状态的输入。

如前所述,通过ZMQ发送到eth-leds的命令用于在指定的时间内闪烁LED一定次数,否则只需打开或关闭它。

按钮脚本非常相似,当然它只读取I / O引脚状态而不是设置它。这样做的目的是提供一种快速简便的方法来重新启动节点,并在需要时将区块链数据库重置为已知状态。因为你想要在交易会上做的最后一件事就是必须连接键盘和显示器并开始输入Linux命令!

def sysrestart():
    cmd = ['/sbin/shutdown', '-r', 'now']
    restarter = subprocess.Popen(cmd, stdout=subprocess.PIPE)
    out = restarter.communicate()[0]
    print(out)

通过Intel NUC,我们可以使用硬件复位引脚连接重启按钮。 但是,Raspberry Pi没有这个,因此当在Pi上运行时,按钮Python脚本将读取引脚状态并在相应引脚被拉低时启动重启。 上面可以看到处理这个问题的功能。

def blockchainreset():
    print('BCR!')

    leds.send_string('red,0.1,20')

    print('Stop Ethereum node')
    subprocess.run(['/bin/systemctl', 'stop', 'eth-node'],
                   stdout=subprocess.DEVNULL,
                   stderr=subprocess.DEVNULL)

按下BCR按钮时触发blockchainreset()函数,上面显示的代码片段首先快速连续闪烁红色LED以提供反馈,然后停止以太坊节点软件。 以下:

  1. 未安装R / W数据分区
  2. 从干净备份还原数据分区
  3. 重新安装数据分区
  4. 如果需要,有3分钟的延迟允许所有单元重置为相同的状态
  5. 以太坊节点软件重新启动

这个功能再一次是为了方便,适合我们的演示场景。 在生产区块链网络中,更有可能是完全自动化和/或采用其他策略来避免数据损坏。

外围集成

DesignSpark_Pmod_Library1_a4c132e84a37716d0b0eceb993b4975c00e913aa.jpg

所有四个用例演示器单元都是基于Raspberry Pi的,其中三个使用DesignSpark Pmod HAT进行物理接口。 回顾一下使用的Pmods是:

  1. PmodOLEDrgb(机器故障+温度警报)
  2. PmodAD1(机器故障)
  3. PmodTC1(温度警报)
  4. PmodLVLSHFT(LeakKiller)

(LeakKiller)前三个都受DesignSpark.Pmod库的支持,这极大地简化了集成。 第四个是电压电平转换器,用于连接LeakKiller设备上的Adafruit Dotstar可寻址LED,它们通过DotStar Pi模块驱动。

Car Crash装置使用Click屏蔽,以及Accel Click和8x8 Click。 前者使用MikroElektronika的示例代码驱动,而后者使用优秀的luma.led_matrix库

应用

from web3 import Web3, HTTPProvider
from web3.contract import ConciseContract
from web3.middleware import geth_poa_middleware

所以现在我们终于开始实际的用例应用程序和区块链集成了! 我们之前看到geth被配置为公开许多API,其中一个被命名为web。 这是一个基于HTTP / JSON的API,我们可以使用各种不同的Python库直接进行交互,我们手动构建和解析有效负载。 但是,有一个web3 Python库使得与以太坊智能合约的交互变得更加容易。

由于我们使用的是权威证明,我们还需要注入一个中间件层来添加对此的支持,否则我们将得到一个错误,因为工作证明是当前的默认值。

w3 = Web3(HTTPProvider('http://127.0.0.1:8545'))
w3.middleware_stack.inject(geth_poa_middleware, layer=0)

CarCrash = w3.eth.contract(
    contract, abi=abi, ContractFactoryClass=ConciseContract)

def IoTBCwrite(data):
    CarCrash.setImpact(int(data), transact={'from': account})

上面我们可以看到我们连接到端口8545上的geth用于API。 在此之后,注入上述中间件层。 接下来,我们设置智能合约,并指定其地址。 它存储在变量contract中,该变量已通过YAML配置文件设置。 我们还以JSON格式传递ABI定义,该格式已存储在abi中。

此时,我们现在可以创建一个函数,当传递一个整数时,它将导致在我们的智能合约中调用一个函数,一个变量将被更新并持久化到区块链。 一旦基础设施到位,我们就有了非常简单的节点,我们有节点参与私有网络,在它们上配置的帐户,以及Ether,部署的智能合约,以及我们可以通过API与之交互的机制。

我们如何阅读该变量的内容? 再一次,很简单。

def IoTBCread():
    data = CarCrash.getImpact(transact={'from': account})
    return(data)

只有这次它需要从矿工或至少一个配置了帐户的节点执行,因为在我们的智能合约中我们声明只有这个帐户可以调用getImpact()。

在其他演示器单元上运行的应用程序非常相似。通过这些,我们存储机器故障,温度警报或最后泄漏的时间,并且使用UNIX时间,即自1970年1月1日00:00:00时代以来的秒数。

潜在的改进

如果我们想在此基础上提供更高级的演示或用于生产,那么可以进行改进的领域包括:

  • 配置文件格式/结构

  • 区块链数据管理,例如:

    • 使用R / W文件系统更不受不干净关机和电源循环的影响。

    • 从文件系统损坏中完全自动恢复。

    • 使用“轻”同步模式,从而将最小的区块链数据存储在设备上,并设置特殊同步节点以与完整数据库一起操作。

  • 更复杂的智能合约

关于最后一点,为了清楚起见,我们故意保持智能合约非常简单。 然而,在实践中,它们几乎肯定会更加复杂,并且涉及服务提供的不同阶段的众多不同利益相关者。

本系列的前几篇文章

在总共五个部分的课程中涵盖了示范者的设计和建造:

Andrew Back

点击了解我们的连接支架是如何构建的

 

Open source (hardware and software!) advocate, Treasurer and Director of the Free and Open Source Silicon Foundation, organiser of Wuthering Bytes technology festival and founder of the Open Source Hardware User Group.