目标:使用树莓派4B与CANHAT扩展板读取智能插排测量的各项数据(RS485+modbus RTU),获取的数据上传到Hyperledger Fabric框架。
之前学习过了modbus RTU协议,在智能涡轮流量计的实验中应用过一次,这次用这个带485模块的智能插座再复习一次~
实验材料:
树莓派4B/8G:
CANHAT扩展板:
USB-485转换器:
RS485机柜排插:
RJ45水晶头转8PIN端子:
树莓派相关库与例程在上次实验已经安装过了,步骤可参照官网:
RS485 CAN HAT – Waveshare Wiki
一、PC端串口测试
还是先使用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 GPIOimport serial EN_485 =4GPIO.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 portwhile 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 GPIOimport serialEN_485 =4GPIO.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 GPIOimport serialimport timeEN_485 =4GPIO.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)/100f=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 GPIOimport serialimport timeEN_485 =4GPIO.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)/3200f=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 mainimport ("encoding/json""fmt""github.com/hyperledger/fabric-contract-api-go/contractapi")type SmartContract struct {contractapi.Contract}type Data struct {Flow_nowstring `json:"flow_now(L/H)"`Flow_total string `json:"flow_total(L)"`Voltagestring `json:"voltage(V)"`Currentstring `json:"current(A)"`Powerstring `json:"power(W)"`Energystring `json:"energy(kWh)"`Factorstring `json:"factor"`Emissionsstring `json:"emissions(Kg)"`Timestring `json:"time"`}type QueryResult struct {Keystring `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/bashpre=$(date "+%Y-%m-%d")num="1"for i in {1..20}donow=$(date "+%Y-%m-%d")if [ $pre != $now ]thennum="1"pre=$nowfiid=$numlen=${#id}while [ $len -le 2 ]doid="0"$idlet len+=1donelet num+=1time=$(date "+%H:%M")res=$now" 001 "$idsudo python /home/pi/RS485_CAN_HAT_Code/485/python/voltage.pyecho " " >> data.txtwhile read rowsdovoltage=$rowsbreakdone > data.txtwhile read rowsdocurrent=$rowsbreakdone > data.txtwhile read rowsdopower=$rowsbreakdone > data.txtwhile read rowsdoenergy=$rowsbreakdone > data.txtwhile read rowsdofactor=$rowsbreakdone > data.txtwhile read rowsdoemissions=$rowsbreakdone add.shdocker cp add.sh cli:/opt/gopath/src/github.com/hyperledger/fabric/peer/ docker exec -it cli bash add.shsleep 42#breakdone
脚本运行结果:
Org1查询结果:
~~