使用Python整合物理设备与IOTA缠结 – 价格和地址

使用Python整合物理设备与IOTA缠结 - 价格和地址

本文是该系列教程的第四部分,目的是将物理设备与IOTA协议集成起来。在本教程中我们将为IOTA支付系统添加一些新功能,即如何跟随不断波动的IOTA市场价格来管理(调整)冰箱的服务价格以及如何处理IOTA一次性签名的问题。

建议您先行阅读该系列教程的前几部分,来对项目的目的,构建过程有个大致了解:

注意!

与本系列教程的前几部分相比,本教程将更加注重软件方面的内容,因此本文不会再添加任何新硬件。

用例

第三部分文章中,我们为项目添加了一个LCD显示屏来充当用户界面,并且也简短的介绍到了IOTA地址重用的话题,那么现在就从这个话题开始。

地址重用

大家事先要搞明白,当讲到“IOTA一次性签名问题”时,并不是指IOTA存在问题或者bug。IOTA一次性签名方案本身是一种添加到IOTA协议中的保护机制,它赋予了IOTA抗量子特性。尽管如此,在IOTA协议之上构建应用程序时,仍然需要注意和处理这个问题。第三部分文章中描述到“只要酒店老板不从冰箱的收款地址支出资金,它就是完全安全的,但是一旦从其中一个地址支出了任何资金,就必须更换一个新的IOTA收款地址”,这实际上意味着,每次你从一个IOTA地址上支出资金后,它的一部分私钥就会被公开,这使得坏人有可能通过伪造签名来盗取地址上的资金。那么,该如何处理这个“问题”呢?假设酒店冰箱的显示屏上公布的的收款地址上积攒了一些IOTA,而酒店老板计划用这些IOTA来购买酒店用具(从这个地址上支出),一种选择是,只要他从这个收款地址上进行了支出,就在显示屏上显示另外一个新的收款地址,另一个更好的选择是,每收一次款,冰箱的显示屏上就立刻更换一个新地址,这样就可以完全忽略所有的地址重用问题。现在我们已经有了新的LCD用户界面,也有了实现这个解决方案的实际方法。

服务价格

本教程中讨论的第二个问题是,如何使冰箱的服务价格能够跟随波动的IOTA市场价格进行调整。对于教程示例的冰箱服务来说,可以设定一个静态的IOTA价格,比如使用1小时需支付多少IOTA,但这却不是一个人性化的商业模式。对于酒店老板来说,一个更可持续的商业模式是将他的冰箱服务价格以美元定价,比如1小时服务收费1美元,然后在每笔交易开始时将美元的价格转换为MIOTA价格。那么如何实现这个特性呢?此时,新液晶显示屏又派上用场了。通过调用coinmarketcap.com网站的API,可以查询到IOTA当前的美元价格,然后将冰箱服务价格转换为IOTA价格,最后在LCD上显示出来。需要考虑的另一个问题是,以美元计价的价格也可能因电力等外部因素而不时变化。所以必须确保有一个简单的方法来更新美元价格,同时确保客人能够享受到他购买的服务时长。这应该可以通过使用一些基本的数学来实现。

注意!

新推出的Trinity钱包已经内置了将法币转换为IOTA的功能。从这个意义上说,用法币显示冰箱的服务价格就足够了,让Trinity负责法币到IOTA的转换。

付款流程

接下来看看一个基于生成新地址的新支付过程可能是什么样的。

使用Python整合物理设备与IOTA缠结 - 价格和地址

1、生成新的付款地址

首先使用PyOTA中的get_new_address()函数生成一个新的支付地址。注意,现在必须在创建api对象时提供一个有效的SEED,生成地址时,函数会用到这个SEED。还要注意get_new_address()是一个确定性函数,这意味着需要每次为函数提供一个唯一索引,以确保不会生成两次相同的地址。可能有不同的方法为函数提供唯一索引,比如时间戳等。在本例中,只是使用了一个计数器,每次付款增加1。

2、生成并显示新的二维码

接下来,将新地址发送到二维码生成器。请注意,在进行IOTA支付时必须在支付地址中加入校验和。因此,在将地址发送到二维码生成器之前,须使用with_valid_checksum()函数将校验和添加到地址中。

3、检查地址上的交易

接下来开始查询地址上的收入情况。一旦检测到地址上有新的交易,就能知道即将收到一笔款项(此时交易不一定是确认了的)。

4、隐藏二维码

接下来,在GUI中隐藏掉前面步骤中已经有收入的地址的二维码,以防止在这个地址上还有交易在等待确认时,再收到新的支付交易。本示例中将用一条消息来替换二维码,该消息表明已检测到了新的付款,并且正在等待付款交易在网络上得到确认。

5、获取地址余额

接下来,开始检查地址上是否有正数余额,因为每个新地址默认余额是0,所以如果检测到大于0的正数余额则意味着在第3步中查询到的交易(付款)已经得到确认。

6、增加冰箱服务时长

检测到正数余额后,再将这个IOTA余额按照当前的“时长/美元”价格比率转换为服务时长,最后将计算出的“服务时长”加入到当前的冰箱服务时长(灯光余额)中,这个过程会重复进行。

组件

前一个教程相比,不需要其他组件。

必需的软件和库

前一个教程相比,不需要其他软件或库。

Python代码

现在来看一下需要添加到Python代码中的一些新内容。

SEED

最重要的变化当然是,收款地址不再固定,要为每笔付款生成一个新地址。出于上述原因,现在必须为IOTA地址生成器提供一个SEED。因此,请确保将Python代码中的SEED替换为你自己的SEED。请参阅此处来了解创建IOTA SEED的更多信息。

索引计数器

增加了一个新的读/写索引计数器函数,以确保能够跟踪在电源故障等情况下使用的最新地址索引。索引计数器函数使用Python configparser库将数据存储为.ini格式的文本文件。确保下载“let_there_be_light.ini”文件并将它放在与Python程序相同的文件夹中,否则会出错。可以从这里下载这个文件。

等待图标

为了防止用户多次向同一个IOTA地址付款,一旦发现新付款后,会立即用一个沙漏图标来替换掉支付二维码。请注意,pyQRcode库在向GUI呈现二维码时使用TkInter BitmapImage函数,该函数使用的是一种称为XBM的特殊图形文件格式,这意味着我们不能使用传统的图片文件格式,如bmp,jpg,png等。然而,如果想创建自己的图标,可以使用一些能够将传统图片格式转换为XBM的程序和网络服务。为了避免在使用Python示例代码时出现任何错误,请下载“hourglass.xbm”文件并将它放在与Python程序相同的文件夹中。可以从这里下载到此文件。

价格

如前所述,我们希望在适当的时候能够灵活地更改服务价格。出于这个原因,我创建了一个在Python程序启动时读取的lightprice_USD变量。当将美元服务价格转换为IOTA的价格时,也会用到lightprice_USD变量。要更改服务的价格,只需将lightprice_USD变量更新为与服务价格匹配的任何值。注意,lightprice_USD是一个浮动的数字,价格为每分钟服务的USD价格。

# Import the requests library to send http request to coinbase.com
import requests

# Import json library for reading json data returned by the http request
import json

# Import the configparser library used for reading and writing to let_there_be_light.ini
import configparser

# Import some funtions from TkInter
from tkinter import *
import tkinter.font
import tkinter.ttk

# Import some functions from Pillow
from PIL import ImageTk, Image

# Import the PyQRCode library
import pyqrcode

# Imports some Python Date/Time functions
import time
import datetime

# Imports the PyOTA library
from iota import Iota
from iota import Address

# Define the Exit function
def exitGUI():

root.destroy()

# Seed used for generating addresses and collecting funds.
# IMPORTANT!!! Replace with your own seed
MySeed = b"WRITE9YOUR9OWN9SEED9HERE9....................."

# URL to IOTA fullnode used when checking balance
iotaNode = "https://field.carriota.com:443"

# Create an IOTA object
api = Iota(iotaNode, MySeed)

# Define main form as root
root = Tk()

# Set the form background to white
root.config(background="white")

# TkInter font to be used in GUI
myFont = tkinter.font.Font(family = 'Helvetica', size = 12, weight = "bold")

# Set main form to full screen
root.overrideredirect(True)
root.geometry("{0}x{1}+0+0".format(root.winfo_screenwidth(), root.winfo_screenheight()))
root.focus_set() # <-- move focus to this widget
root.bind("<Escape>", lambda e: e.widget.quit())

# Define mainFrame
mainFrame = tkinter.Frame(root)
mainFrame.config(background="white")
mainFrame.place(relx=0.5, rely=0.5, anchor=CENTER)

# Create and render the QR code
qrframe = tkinter.Frame(mainFrame)
qrframe.grid(row=0,column=0,rowspan=3)
code = pyqrcode.create('')
code_xbm = code.xbm(scale=3)
code_bmp = tkinter.BitmapImage(data=code_xbm)
code_bmp.config(background="white")
qrcode = tkinter.Label(qrframe, image=code_bmp, borderwidth = 0)
qrcode.grid(row=0, column=0)

# Create and render logo
# Make sure you download and place the "iota_logo75.jpg" file in the same folder as your python file.
# The logofile can be dowloaded from: http://imagebucket.net/6jrt31fbb4js/iota_logo75.jpg
path = "iota_logo75.jpg"
img = ImageTk.PhotoImage(Image.open(path))
iotalogo = tkinter.Label(mainFrame, image = img, borderwidth = 0)
iotalogo.grid(row=0,column=1)

# Create and render timer
timeText = tkinter.Label(mainFrame, text="", font=("Helvetica", 50))
timeText.config(background='white')
timeText.grid(row=1,column=1)

# Create and render Exit button
exitButton = Button(mainFrame, text='Exit', font=myFont, command=exitGUI, bg='white', height=1, width=10)
exitButton.grid(row=2,column=1)

# Create and render price text
priceTextFrame = tkinter.Frame(mainFrame)
priceTextFrame.grid(row=3,column=0,columnspan=2)
priceText = tkinter.Label(priceTextFrame, text="Here comes price", font=("Helvetica", 12))
priceText.config(background='white')
priceText.grid(row=3,column=0)

# Create and render progress bar
progFrame = tkinter.Frame(mainFrame)
progFrame.grid(row=4,column=0,columnspan=2)
mpb = tkinter.ttk.Progressbar(progFrame,orient ="horizontal",length = 435, mode ="determinate")
mpb.grid(row=4,column=0)
mpb["maximum"] = 30
mpb["value"] = 0

# Create and render payment status text
paymentStatusFrame = tkinter.Frame(mainFrame)
paymentStatusFrame.grid(row=5,column=0,columnspan=2)
paymentStatusText = tkinter.Label(paymentStatusFrame, text="Waiting for new transactions", font=("Helvetica", 9))
paymentStatusText.config(background='white')
paymentStatusText.grid(row=5,column=0)

# Create and render light status text
statusTextFrame = tkinter.Frame(mainFrame)
statusTextFrame.grid(row=6,column=0,columnspan=2)
statusText = tkinter.Label(statusTextFrame, text="Light is OFF", font=("Helvetica", 9))
statusText.config(background='white')
statusText.grid(row=6,column=0)

# Define function to update QR code based on new address
def updateQRcode(QRaddr):
code = pyqrcode.create(QRaddr)
code_xbm = code.xbm(scale=3)
code_bmp = tkinter.BitmapImage(data=code_xbm)
code_bmp.config(background="white")
qrcode.configure(image=code_bmp)
qrcode.image = code_bmp

# Define function to replace QR code with hourglass icon
# Make sure you download and place the "hourglass.xbm" file in the same folder as your python file.
# The hourglass icon file can be dowloaded from: https://gist.github.com/huggre/c126863786991b49c2965d42b12f6b3d
def showXBM():
xbm_img = tkinter.BitmapImage(file="hourglass.xbm")
xbm_img.config(background="white")
qrcode.configure(image=xbm_img)
qrcode.image = xbm_img

# Define function to generate new IOTA address
def generateNewAddress(addrIndexCount):
result = api.get_new_addresses(index=addrIndexCount, count=1, security_level=2)
addresses = result['addresses']
QRaddr=str(addresses[0].with_valid_checksum())
updateQRcode(QRaddr)
address = [addresses[0]]
return(address)

# Define function for checking address balance on the IOTA tangle.
def checkbalance(addr):
gb_result = api.get_balances(addr)
balance = gb_result['balances']
return (balance[0])

# Define Function to displays payment status in GUI
def updatePaymentStatus(paymentStatus):
paymentText="Payment Status: " + paymentStatus
paymentStatusText.configure(text=paymentText)

# Define function to return light price in IOTA's based on market value on marketcap.com
def getLightPriceIOTA():
r = requests.get('https://api.coinmarketcap.com/v1/ticker/iota/')
for coin in r.json():
fprice = float(coin["price_usd"])
lightprice_IOTA = fprice * lightprice_USD
return (lightprice_IOTA)

# Define function to display price in GUI
def displayprice():
sprice = "PRICE: " + str(lightprice_USD) + " USD / " + str(round(getLightPriceIOTA(),3)) + " MIOTA pr. minute"
priceText.configure(text=sprice)

# Define function to check for existing address transactions
def getTransExist(addr):
result = api.find_transactions(addresses=addr)
myhashes = result['hashes']
if len(myhashes) > 0:
transFound = True
else:
transFound = False
return(transFound)

# Define function for reading and storing address indexes
# Make sure you download and place the "let_there_be_light.ini" file in the same folder as your python file.
# The let_there_be_light.ini file can be dowloaded from: https://gist.github.com/huggre/c5185df916ca00d2e1d12943a9d9d03a
def getNewIndex():
config = configparser.ConfigParser()
config.read('let_there_be_light.ini')
oldIndex = config.getint('IndexCounter', 'addrIndexCount')
newIndex = oldIndex +1
config.set('IndexCounter', 'addrIndexCount', str(newIndex))
with open('let_there_be_light.ini', 'w') as configfile:
config.write(configfile)
return(newIndex)

# Define some variables
lightbalance = 0
balcheckcount = 0
lightstatus = False
addrfound = False
transFound = False

# The price of the service in USD pr. minute. Change at will
lightprice_USD = 0.01

# Function that returns next index used by the generateNewAddress function
addrIndex = getNewIndex()

# Generate new payment address
addr = generateNewAddress(addrIndex)

# Display price in GUI
displayprice()

# Display payment status in GUI
updatePaymentStatus("Waiting for new transactions")

# Main loop that executes every 1 second
def maintask(balcheckcount, lightbalance, lightstatus, transFound, addr, addrIndex):

# Check for new funds and add to lightbalance when found.
if balcheckcount == 30:

# Check if address has any transactions
if transFound == False:
transFound = getTransExist(addr)
if transFound == True:
showXBM()
updatePaymentStatus("New transaction found, please wait while transaction is confirmed")

# If new transactions has been found, check for positive balance and add to lightbalance
if transFound == True:
balance = checkbalance(addr)
if int(balance) > 0:
lightbalance = lightbalance + int(((balance/1000000) * 60) / (getLightPriceIOTA()))
addrIndex = getNewIndex()
addr = generateNewAddress(addrIndex)
updatePaymentStatus("Waiting for new transactions")
transFound = False

balcheckcount = 0

# Manage light balance and light ON/OFF
if lightbalance > 0:
if lightstatus == False:
statusText.config(text="Light is ON")
lightstatus=True
lightbalance = lightbalance -1
else:
if lightstatus == True:
statusText.config(text="Light is OFF")
lightstatus=False

# Print remaining light balance
strlightbalance = datetime.timedelta(seconds=lightbalance)
timeText.config(text=strlightbalance)

# Increase balance check counter
balcheckcount = balcheckcount +1

# Update progress bar
mpb["value"] = balcheckcount

# Run maintask function after 1 sec.
root.after(1000, maintask, balcheckcount, lightbalance, lightstatus, transFound, addr, addrIndex)

# Run maintask function after 1 sec.
root.after(1000, maintask, balcheckcount, lightbalance, lightstatus, transFound, addr, addrIndex)

root.mainloop()

可从此处下载到这段Python代码。

运行项目

要运行该项目,首先需要将上面的代码保存为Raspberry PI上的文本文件。

请注意,Python程序文件使用.py扩展名,因此我们将文件保存为let_there_be_light_addr.py。

要执行该程序,只需启动一个新的终端窗口,进入到let_there_be_light_addr.py的保存位置并输入:

python let_there_be_light_addr.py

现在应该能看到GUI出现在LCD显示屏上,并显示一个自动生成的IOTA支付地址的二维码。一旦确认了一笔付款,就会在GUI中生成并显示一个新的地址。要退出Python程序,只需按下exit按钮。

注意!

在执行程序之前,请下载“iota_logo75.jpg”文件并将它放在与python文件相同的文件夹中,否则会出错。LOGO文件可以从这里下载。

支付灯光点亮费

要打开LED灯(或者将Light设置为ON状态),只需登录IOTA钱包(Trinity),扫描GUI中显示的支付二维码,然后支付一些IOTA。交易被IOTA缠结确认后,LED就能保持长亮状态直到剩余时长耗尽,这取决于你转移的IOTA数量。

捐助作者

如果你喜欢本教程并希望作者能够继续创作,请随时向作者的IOTA地址捐赠一小笔捐款。IOTA捐助地址:IKNETWIYVUSILFMIBYEBUMAJOGHU9EEEQLFAVCZLPJWJGYOTWBKKIAFBEUMFYMNFUOQVBJJFFWFISMLGZZTJMHEGFY

 

原文链接:https://medium.com/coinmonks/integrating-physical-devices-with-iota-price-and-addresses-4f352e321cbb

inhuman

专栏作者:inhuman

个人简介:我共发表了 163 篇文章,总计被阅读了90,630 次,共获得了 1,349 个赞。

作者邮箱 作者主页 Ta的文章

发表评论

电子邮件地址不会被公开。 必填项已用*标注