目标:使用树莓派4B与CANHAT扩展板读取智能插排测量的各项数据(RS485+modbus RTU),获取的数据上传到Hyperledger Fabric框架。
之前学习过了modbus RTU协议,在智能涡轮流量计的实验中应用过一次,这次用这个带485模块的智能插座再复习一次~
实验材料:
树莓派4B/8G:

CANHAT扩展板:

USB-485转换器:

RS485机柜排插:

RJ45水晶头转8PIN端子:

树莓派相关库与例程在上次实验已经安装过了,步骤可参照官网:
RS485 CAN HAT - Waveshare Wiki
还是先使用PC端的串口调试助手测试一下智能插座的通讯。先将设备正确接线:

这次的智能插排RS485模块接线口是水晶头而不是通常的AB端子,所以还需要一个水晶头转端子线,接线如上图的说明书所示。
调试前先看看设备的通信说明书:

可以看到一个寄存器同样也是表示2字节的数据,电量用两个寄存器表示也就是4个字节,其他数据应该都只占用一个寄存器。这次的说明书详细一些,还给出了数据转换公式。
具体各项数据存储的寄存器地址如下:

计算一下需要用到的modbus命令:
01 03 00 48 00 01 04 1C 查询电压值
01 03 00 49 00 01 55 DC 查询电流值
01 03 00 4A 00 01 A5 DC 查询有功功率
01 03 00 4B 00 02 B4 1D 查询有功总电能
01 03 00 4D 00 01 14 1D 查询功率因数
01 03 00 4E 00 02 A4 1C 查询二氧化碳排量
以查询有功总电能为例,串口调试助手发送命令后接收到如下数据:

数据位为“00 00 01 23”,十进制数值为291,根据数据转换公式值=DATA/3200,算得有功总电能为0.09kWh,经验证数据无误:

python文件编写如下:
receive.py:
# -*- coding:utf-8 -*-
import RPi.GPIO as GPIO
import serial
EN_485 = 4
GPIO.setwarnings(False)
GPIO.setmode(GPIO.BCM)
GPIO.setup(EN_485,GPIO.OUT)
GPIO.output(EN_485,GPIO.LOW)
ser = serial.Serial("/dev/ttyAMA0",9600,timeout=1) # open first serial port
while 1:
Str = ser.readall()
if Str:
print (Str)
string=Str.hex()
print(string)
#print(res)
note=open('/home/pi/Desktop/hyperledger/multinodes-pi/data.txt',mode='w')
note.write(string)
note.close()
#break
receive.py与上次实验有所区别,主要因为这次查询的数据较多,且每种数据储存方式不同,如电流电压等都储存在一个寄存器中,也就是说返回的数据位是2个字节,而电能与二氧化碳排量存储在两个寄存器中,所以返回的数据为4个字节,所以需要截取的数据位是不同的。除此以外相比于涡轮流量计查询到的数据,这次实验查询到的不同的值转换公式也不同,如有功功率就是返回的数据位转化成十进制后的值,单位为W;而有功总电能则是返回的数据转化为十进制后再除以3200,单位为kWh。所以我准备在receive接收返码时进行一次判断,因为当返回的数据位有2个字节时,返码总长度为7字节;返回数据位有4个字节时,返码总长度为9字节。所以通过接收到的数组长度就能确定需要截取的数据位的位置了,如果返码总长度为7字节,截取[6:10],总长度为9字节,则截取[6:14]。截取数据位之后将其转为十进制存入data.txt,操作data时我感觉用shell命令处理这么多浮点数的运算写起来比较麻烦,所以在shell脚本调用指定的send.py数据查询1s后,依据查询的数据类型在对应的send文件中将data.txt文件的数据进行换算再重新写入,最后data.txt中存的就是我需要的最终数据。
send.py
# -*- coding:utf-8 -*-
import RPi.GPIO as GPIO
import serial
EN_485 = 4
GPIO.setwarnings(False)
GPIO.setmode(GPIO.BCM)
GPIO.setup(EN_485,GPIO.OUT)
GPIO.output(EN_485,GPIO.HIGH)
t = serial.Serial("/dev/ttyAMA0",9600)
print (t.portstr)
strInput = '01 03 00 00 00 02 C4 0B'
str=bytes.fromhex(strInput)
print(str)
n = t.write(str)
print (n)
send文件通过修改strInput来发送不同的查询命令,将得到的结果存入data.txt并使用脚本读取。在send文件中还需要进行数据转换操作,不同数据转换公式不同,下面是电压voltage与总电能energy的查询文件:
voltage.py:
# -*- coding:utf-8 -*-
import RPi.GPIO as GPIO
import serial
import time
EN_485 = 4
GPIO.setwarnings(False)
GPIO.setmode(GPIO.BCM)
GPIO.setup(EN_485,GPIO.OUT)
GPIO.output(EN_485,GPIO.HIGH)
t = serial.Serial("/dev/ttyAMA0",9600)
#print (t.portstr)
strInput = '01 03 00 48 00 01 04 1C'
string=bytes.fromhex(strInput)
#print(string)
n = t.write(string)
#print (n)
time.sleep(2)
f=open('/home/pi/Desktop/hyperledger/multinodes-pi/data.txt',mode='r')
data=f.readlines()
f.close()
res=int(data[0])
res=float(res)/100
f=open('/home/pi/Desktop/hyperledger/multinodes-pi/data.txt',mode='w')
f.write(str(res))
f.close()
time.sleep(1)
energy.py:
# -*- coding:utf-8 -*-
import RPi.GPIO as GPIO
import serial
import time
EN_485 = 4
GPIO.setwarnings(False)
GPIO.setmode(GPIO.BCM)
GPIO.setup(EN_485,GPIO.OUT)
GPIO.output(EN_485,GPIO.HIGH)
t = serial.Serial("/dev/ttyAMA0",9600)
#print (t.portstr)
strInput = '01 03 00 4B 00 02 B4 1D'
string=bytes.fromhex(strInput)
#print(string)
n = t.write(string)
#print (n)
time.sleep(2)
f=open('/home/pi/Desktop/hyperledger/multinodes-pi/data.txt',mode='r')
data=f.readlines()
f.close()
res=int(data[0])
res=float(res)/3200
f=open('/home/pi/Desktop/hyperledger/multinodes-pi/data.txt',mode='w')
f.write(str(res))
f.close()
time.sleep(1)
相对应地,结合上次实验的涡轮流量计,对hyperledger fabric的链码也做了一些修改,预想的情景下一个树莓派采集一组流量计、智能插排、气量计的数据并将其上传到链上,为了方便区分多组仪表的数据,Key值再加入三位ID来表示这一组仪表的编号。如“2022-8-2 001 003”的Key值表示2022年8月2日这天采集到的ID为001的这组仪表的第3条数据。
修改后链码如下:
/*
SPDX-License-Identifier: Apache-2.0
*/
package main
import (
"encoding/json"
"fmt"
"github.com/hyperledger/fabric-contract-api-go/contractapi"
)
type SmartContract struct {
contractapi.Contract
}
type Data struct {
Flow_now string `json:"flow_now(L/H)"`
Flow_total string `json:"flow_total(L)"`
Voltage string `json:"voltage(V)"`
Current string `json:"current(A)"`
Power string `json:"power(W)"`
Energy string `json:"energy(kWh)"`
Factor string `json:"factor"`
Emissions string `json:"emissions(Kg)"`
Time string `json:"time"`
}
type QueryResult struct {
Key string `json:"Key"`
Record *Data
}
func (s *SmartContract) InitLedger(ctx contractapi.TransactionContextInterface) error {
datas := []Data{
Data{Flow_now: "0", Flow_total: "0", Voltage: "0", Current: "0", Power: "0", Energy: "0", Factor: "0", Emissions: "0", Time: "00:00"},
}
for data := range datas {
dataAsBytes, _ := json.Marshal(data)
err := ctx.GetStub().PutState("2022-07-20 000 000", dataAsBytes)
if err != nil {
return fmt.Errorf("Failed to put to world state. %s", err.Error())
}
}
return nil
}
func (s *SmartContract) AddData(ctx contractapi.TransactionContextInterface, dataNumber string, flow_now string, flow_total string, voltage string, current string, power string, energy string, factor string, emissions string, time string) error {
data := Data{
Flow_now: flow_now,
Flow_total: flow_total,
Voltage: voltage,
Current: current,
Power: power,
Energy: energy,
Factor: factor,
Emissions: emissions,
Time: time,
}
dataAsBytes, _ := json.Marshal(data)
return ctx.GetStub().PutState(dataNumber, dataAsBytes)
}
func (s *SmartContract) QueryData(ctx contractapi.TransactionContextInterface, dataNumber string) (*Data, error) {
dataAsBytes, err := ctx.GetStub().GetState(dataNumber)
if err != nil {
return nil, fmt.Errorf("Failed to read from world state. %s", err.Error())
}
if dataAsBytes == nil {
return nil, fmt.Errorf("%s does not exist", dataNumber)
}
data := new(Data)
_ = json.Unmarshal(dataAsBytes, data)
return data, nil
}
func (s *SmartContract) QueryAllDatas(ctx contractapi.TransactionContextInterface) ([]QueryResult, error) {
startKey := ""
endKey := ""
resultsIterator, err := ctx.GetStub().GetStateByRange(startKey, endKey)
if err != nil {
return nil, err
}
defer resultsIterator.Close()
results := []QueryResult{}
for resultsIterator.HasNext() {
queryResponse, err := resultsIterator.Next()
if err != nil {
return nil, err
}
data := new(Data)
_ = json.Unmarshal(queryResponse.Value, data)
queryResult := QueryResult{Key: queryResponse.Key, Record: data}
results = append(results, queryResult)
}
return results, nil
}
func main() {
chaincode, err := contractapi.NewChaincode(new(SmartContract))
if err != nil {
fmt.Printf("Error create test chaincode: %s", err.Error())
return
}
if err := chaincode.Start(); err != nil {
fmt.Printf("Error starting test chaincode: %s", err.Error())
}
}
shell脚本:
#!/bin/bash
pre=$(date "+%Y-%m-%d")
num="1"
for i in {1..20}
do
now=$(date "+%Y-%m-%d")
if [ $pre != $now ]
then
num="1"
pre=$now
fi
id=$num
len=${#id}
while [ $len -le 2 ]
do
id="0"$id
let len+=1
done
let num+=1
time=$(date "+%H:%M")
res=$now" 001 "$id
sudo python /home/pi/RS485_CAN_HAT_Code/485/python/voltage.py
echo " " >> data.txt
while read rows
do
voltage=$rows
break
done < data.txt
sudo python /home/pi/RS485_CAN_HAT_Code/485/python/current.py
echo " " >> data.txt
while read rows
do
current=$rows
break
done < data.txt
sudo python /home/pi/RS485_CAN_HAT_Code/485/python/power.py
echo " " >> data.txt
while read rows
do
power=$rows
break
done < data.txt
sudo python /home/pi/RS485_CAN_HAT_Code/485/python/energy.py
echo " " >> data.txt
while read rows
do
energy=$rows
break
done < data.txt
sudo python /home/pi/RS485_CAN_HAT_Code/485/python/factor.py
echo " " >> data.txt
while read rows
do
factor=$rows
break
done < data.txt
sudo python /home/pi/RS485_CAN_HAT_Code/485/python/emissions.py
echo " " >> data.txt
while read rows
do
emissions=$rows
break
done < data.txt
echo "这是第"$i"次查询到并添加的数据:"
echo "flow_now(L/H):"$n" flow_total(L):"$t" voltage(V):"$voltage" current(A):"$current" power(W):"$power" energy(kWh):"$energy" factor:"$factor" emissions(Kg):"$emissions" time:"$time
n=0
t=0
cmd="'{\"Args\":[\"AddData\",\"$res\",\"$n\",\"$t\",\"$voltage\",\"$current\",\"$power\",\"$energy\",\"$factor\",\"$emissions\",\"$time\"]}'"
echo "Add命令:"$cmd
echo "#!/bin/bash
peer chaincode invoke -o orderer.example.com:7050 --tls true --cafile /opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem -C mychannel -n test --peerAddresses peer0.org1.example.com:7051 --tlsRootCertFiles /opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/tls/ca.crt --peerAddresses peer0.org2.example.com:7051 --tlsRootCertFiles /opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/peerOrganizations/org2.example.com/peers/peer0.org2.example.com/tls/ca.crt -c "$cmd "
exit"> add.sh
docker cp add.sh cli:/opt/gopath/src/github.com/hyperledger/fabric/peer/
docker exec -it cli bash add.sh
sleep 42
#break
done
脚本运行结果:

Org1查询结果:

~~
尝试通过RVM将RubyGems升级到版本1.8.10并出现此错误:$rvmrubygemslatestRemovingoldRubygemsfiles...Installingrubygems-1.8.10forruby-1.9.2-p180...ERROR:Errorrunning'GEM_PATH="/Users/foo/.rvm/gems/ruby-1.9.2-p180:/Users/foo/.rvm/gems/ruby-1.9.2-p180@global:/Users/foo/.rvm/gems/ruby-1.9.2-p180:/Users/foo/.rvm/gems/rub
我正在使用puppet为ruby程序提供一组常量。我需要提供一组主机名,我的程序将对其进行迭代。在我之前使用的bash脚本中,我只是将它作为一个puppet变量hosts=>"host1,host2"我将其提供给bash脚本作为HOSTS=显然这对ruby不太适用——我需要它的格式hosts=["host1","host2"]自从phosts和putsmy_array.inspect提供输出["host1","host2"]我希望使用其中之一。不幸的是,我终其一生都无法弄清楚如何让它发挥作用。我尝试了以下各项:我发现某处他们指出我需要在函数调用前放置“function_”……这
我正在编写一个gem,我必须在其中fork两个启动两个webrick服务器的进程。我想通过基类的类方法启动这个服务器,因为应该只有这两个服务器在运行,而不是多个。在运行时,我想调用这两个服务器上的一些方法来更改变量。我的问题是,我无法通过基类的类方法访问fork的实例变量。此外,我不能在我的基类中使用线程,因为在幕后我正在使用另一个不是线程安全的库。所以我必须将每个服务器派生到它自己的进程。我用类变量试过了,比如@@server。但是当我试图通过基类访问这个变量时,它是nil。我读到在Ruby中不可能在分支之间共享类变量,对吗?那么,还有其他解决办法吗?我考虑过使用单例,但我不确定这是
我的最终目标是安装当前版本的RubyonRails。我在OSXMountainLion上运行。到目前为止,这是我的过程:已安装的RVM$\curl-Lhttps://get.rvm.io|bash-sstable检查已知(我假设已批准)安装$rvmlistknown我看到当前的稳定版本可用[ruby-]2.0.0[-p247]输入命令安装$rvminstall2.0.0-p247注意:我也试过这些安装命令$rvminstallruby-2.0.0-p247$rvminstallruby=2.0.0-p247我很快就无处可去了。结果:$rvminstall2.0.0-p247Search
我在理解Enumerator.new方法的工作原理时遇到了一些困难。假设文档中的示例:fib=Enumerator.newdo|y|a=b=1loopdoy[1,1,2,3,5,8,13,21,34,55]循环中断条件在哪里,它如何知道循环应该迭代多少次(因为它没有任何明确的中断条件并且看起来像无限循环)? 最佳答案 Enumerator使用Fibers在内部。您的示例等效于:require'fiber'fiber=Fiber.newdoa=b=1loopdoFiber.yieldaa,b=b,a+bendend10.times.m
几个月前,我读了一篇关于rubygem的博客文章,它可以通过阅读代码本身来确定编程语言。对于我的生活,我不记得博客或gem的名称。谷歌搜索“ruby编程语言猜测”及其变体也无济于事。有人碰巧知道相关gem的名称吗? 最佳答案 是这个吗:http://github.com/chrislo/sourceclassifier/tree/master 关于ruby-寻找通过阅读代码确定编程语言的rubygem?,我们在StackOverflow上找到一个类似的问题:
从MB升级到新的MBP后,Apple的迁移助手没有移动我的gem。我这次是通过macports安装rubygems,希望在下次升级时避免这种情况。有什么我应该注意的陷阱吗? 最佳答案 如果你想把你的gems安装在你的主目录中(在传输过程中应该复制过来,作为一个附带的好处,会让你以你自己的身份运行geminstall,而不是root),将gemhome:键设置为您在~/.gemrc中的主目录中的路径. 关于通过MacPorts的RubyGems是个好主意吗?,我们在StackOverf
当我执行>rvminstall1.9.2时一切顺利。然后我做>rvmuse1.9.2也很顺利。但是当涉及到ruby-v时..sam@sjones:~$rvminstall1.9.2/home/sam/.rvm/rubies/ruby-1.9.2-p136,thismaytakeawhiledependingonyourcpu(s)...ruby-1.9.2-p136-#fetchingruby-1.9.2-p136-#downloadingruby-1.9.2-p136,thismaytakeawhiledependingonyourconnection...%Total%Rece
当谈到运行时自省(introspection)和动态代码生成时,我认为ruby没有任何竞争对手,可能除了一些lisp方言。前几天,我正在做一些代码练习来探索ruby的动态功能,我开始想知道如何向现有对象添加方法。以下是我能想到的3种方法:obj=Object.new#addamethoddirectlydefobj.new_method...end#addamethodindirectlywiththesingletonclassclass这只是冰山一角,因为我还没有探索instance_eval、module_eval和define_method的各种组合。是否有在线/离线资
如何检查Ruby文件是否是通过“require”或“load”导入的,而不是简单地从命令行执行的?例如:foo.rb的内容:puts"Hello"bar.rb的内容require'foo'输出:$./foo.rbHello$./bar.rbHello基本上,我想调用bar.rb以不执行puts调用。 最佳答案 将foo.rb改为:if__FILE__==$0puts"Hello"end检查__FILE__-当前ruby文件的名称-与$0-正在运行的脚本的名称。 关于ruby-检查是否