上一篇博客配置好了樹莓派端的串口通信,這次在加入渦輪流量計之前也先用PC端模擬樹莓派測試一下該儀表是否能正常工作。
一、測試流量計通信
流量計說明書如下:
?并且在設(shè)備上電時以(9600,8n1格式)自動發(fā)送四個字節(jié):
返碼格式:站地址(1字節(jié))+波特率(2字節(jié))+格式(1字節(jié))
8n1格式指8個數(shù)據(jù)位,無校驗,1個停止位,8e1與8o1分別對應(yīng)偶校驗和奇校驗。
將流量計與USB轉(zhuǎn)485轉(zhuǎn)換器及24V電源正確接線,打開PC端串口,開啟電源,觀察接收窗口信息(注意接收區(qū)和發(fā)送區(qū)均調(diào)成十六進(jìn)制顯示):
?接收到返碼為“01 25 80 00”,其意義為:設(shè)備地址1,波特率9600,格式8n1
依據(jù)說明書的樣例,在PC端發(fā)送以下命令:
?這一命令用于查詢瞬時流量值,依據(jù)上篇學(xué)過的modbus-RTU協(xié)議,可以對該命令作如下解析:
發(fā)送:
01 03 00 00 00 02 C4 0B
01-設(shè)備地址為1
03-代表查詢功能
00 00-查詢的寄存器起始地址,由此得知儲存瞬時流量值這一數(shù)據(jù)的寄存器起始地址為0
00 02-查詢的寄存器數(shù)量,由此得知瞬時流量這一數(shù)據(jù)由2個寄存器儲存
C4 0B-CRC校驗碼
接收:
01 03 04 00 00 00 00 FA 33
01-設(shè)備地址為1
03-查詢功能
04-表示后面有4個字節(jié)的數(shù)據(jù),因為查詢了2個寄存器,所以返回2*2個字節(jié)數(shù)據(jù)
00 00 00 00-查詢所得的數(shù)據(jù)
FA 33-CRC校驗碼
確認(rèn)了流量計通訊正常后,將樹莓派與流量計正確接線,在通訊實驗前我事先利用CRC校驗算法算好了所有將要用到的命令的校驗位值,依據(jù)說明書記錄的各項數(shù)據(jù)類型可以推算該數(shù)據(jù)占用的寄存器個數(shù),例如32位無符號占用4字節(jié),即2個寄存器,因此查詢時需要從起始地址往后查兩個寄存器的數(shù)據(jù),各個數(shù)據(jù)的起始地址在說明書中均已給出。
二、流量計實驗前準(zhǔn)備工作
所有擬用到的完整命令如下:
01 03 00 00 00 02 C4 0B //查詢瞬時流量值(L/H)
01 03 00 02 00 02 65 CB //查詢累計流量(L)
01 03 00 08 00 02 45 C9 //查詢累計脈沖數(shù)
01 03 17 71 00 02 91 A4 //查詢儀器系數(shù)
01 06 00 06 00 01 A8 0B //累計流量清零
01 06 00 07 00 01 F9 CB //累計脈沖數(shù)清零
解釋一下脈沖數(shù)和儀器系數(shù),流量計工作時,內(nèi)部的水流會推動里面的渦輪葉片轉(zhuǎn)動,每當(dāng)葉片經(jīng)過磁鐵時會產(chǎn)生感應(yīng)信號,再經(jīng)過放大器之類的將脈沖送到計數(shù)器里,而儀器系數(shù)就是表明多少個脈沖來表示一升水的流量,比如儀器系數(shù)1000就表示1000個脈沖1升水。
首先查詢一下儀器系數(shù)(左邊發(fā)送,右邊接收):
可以看到返回的數(shù)據(jù)位是“00 00 03 11”,將十六進(jìn)制311轉(zhuǎn)成十進(jìn)制為785,說明此流量計儀器系數(shù)為785,785個脈沖一升水。
清零累計流量與累計脈沖數(shù):
清零之后我先往流量計里倒了一瓶自來水做實驗,對比其脈沖數(shù)和流量值的關(guān)系,最后確認(rèn)了瞬時流量值和累計流量值的數(shù)據(jù)位返回的均是保留兩位小數(shù)的十六進(jìn)制形式,比如累計流量返碼的數(shù)據(jù)位是“00 00 01 00”即十六進(jìn)制的100,轉(zhuǎn)化為十進(jìn)制256,表示的水量是2.56升。
到這準(zhǔn)備工作都已經(jīng)完成,想要采集流量數(shù)據(jù)只要把流量計裝到工作環(huán)境里開始工作就完事了,隨時可以通過receive和send兩個python文件發(fā)送計算好的命令來查詢和修改流量計采集的各項數(shù)據(jù)。接下來我就夾帶點私貨了~
三、數(shù)據(jù)處理預(yù)備工作
以上兩步基本確認(rèn)了樹莓派與流量計的通訊沒有問題,不出意外的話實驗可以順利進(jìn)行的。那我在這里先準(zhǔn)備一下實驗數(shù)據(jù)的處理(這一節(jié)其實和上篇寫的實驗?zāi)繕?biāo)沒啥關(guān)系hhh)。我最終是想將查詢流量計所得的瞬時流量、累計流量兩項數(shù)據(jù)上傳到搭建好的Hyperledger Fabric環(huán)境中,此前對照官方示例fabcar寫了個test鏈碼:
/*
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 {
Now string `json:"now(L/H)"`
Total string `json:"total(L)"`
}
type QueryResult struct {
Key string `json:"Key"`
Record *Data
}
func (s *SmartContract) InitLedger(ctx contractapi.TransactionContextInterface) error {
datas := []Data{
Data{Now:"0", Total: "0"},
}
for data := range datas {
dataAsBytes, _ := json.Marshal(data)
err := ctx.GetStub().PutState("2022-07-20 00:00", 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, now string, total string) error {
data := Data{
Now: now,
Total: total,
}
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())
}
}
上傳的數(shù)據(jù)叫做Data,Record包含“total”(累計流量,單位L)和“now”(瞬時流量,單位L/H);Key是查詢的時間,形如“2022-07-20 19:00”,將格式化的定長時間字符串作為Key可以避免由字典序排列引起的查詢結(jié)果亂序問題。
稍微修改一下上篇博客里的收發(fā)python文件,主要是調(diào)試時出現(xiàn)過參數(shù)類型的問題,修改完之后receive.py在接收數(shù)據(jù)后截取出數(shù)據(jù)位,轉(zhuǎn)化成十進(jìn)制,再轉(zhuǎn)化為浮點數(shù)除以100,然后以字符串形式存入data.txt。
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()
data=string[6:14]
print(data)
res=int(data,16)
#print(res)
result=float(res)/100
#print(result)
note=open('/home/pi/Desktop/hyperledger/multinodes-pi/data.txt',mode='w')
note.write(str(result))
note.close()
#break
?為了方便shell腳本的調(diào)用,我把原先的send.py分成了兩個,一個query_now用來查詢瞬時流量,一個query_total用來查詢累計流量。
query_now.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)
query_total.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 02 00 02 65 CB'
str=bytes.fromhex(strInput)
print(str)
n = t.write(str)
print (n)
然后就是shell腳本的編寫,我預(yù)想的邏輯是首先開啟receive.py保持接收數(shù)據(jù),然后3min一次循環(huán)調(diào)用腳本。每次循環(huán)先是調(diào)用一次query_now.py,此時data.txt會通過receive.py存入瞬時流量值的數(shù)據(jù),用shell命令讀取出數(shù)據(jù)存入變量n;再調(diào)用一次query_total.py,相同的方法讀出累計流量值數(shù)據(jù)存入變量t。n和t再和當(dāng)前時間time組成一條命令“AddData”寫入add.sh這一腳本,最后在cli容器里調(diào)用add腳本完成數(shù)據(jù)上傳的操作。為了預(yù)留足夠的時間給流量計通訊以及python的數(shù)據(jù)寫入等工作,我在運行pyhton文件之后會等待3s的時間,確保數(shù)據(jù)寫入的操作已經(jīng)完成。
但是很不幸,一個莫名其妙的bug卡了我整整一個下午。。按照上述的邏輯進(jìn)行到shell命令從data.txt中讀命令這一步時,它讀出來的永遠(yuǎn)是空串?在確認(rèn)了邏輯無誤后我在網(wǎng)上查了很久也查不到和我遇到的相同的bug,相對來說比較可能的說法是腳本讀取文件時光標(biāo)位置出錯,由于文件沒有關(guān)閉,讀取操作結(jié)束后光標(biāo)會一直停留在文件末尾,所以下次再讀時是從文件末尾開始讀,讀出來的就是空串。這樣看來是我shell命令讀完之后文件沒有正確關(guān)閉嗎?那起碼第一次得能讀出數(shù)據(jù)吧,我連一次都沒讀出來過。。另一次文件操作也就只有receive.py里寫入數(shù)據(jù)的過程了,但是我也的確寫了關(guān)閉文件這個操作,我唯一能猜測的可能性就是樹莓派系統(tǒng)的shell命令行對文件是否關(guān)閉的判斷與python有點沖突,因為我用shell腳本讀其他未被python操作過的文件都是能讀出數(shù)據(jù)的,唯獨這個被python改寫過的data.txt不行。后來我就想怎么讓shell讀取時光標(biāo)再重新回到文件起始位置?我用shell也寫點東西進(jìn)去,寫入完畢之后應(yīng)該會關(guān)上文件吧?于是我就用shell語句寫了一個空格添加在data.txt的末尾,然后再用“while read rows”讀取,就這樣還真解決了這個bug。。。當(dāng)然以上的原因都是我的猜想,如果有大佬明白真正原因的也請評論區(qū)告訴我一下~感謝!
最后能成功運行的test.sh是這樣的:
#!/bin/bash
for i in {1..20}
do
sudo python /home/pi/RS485_CAN_HAT_Code/485/python/query_now.py
time=$(date "+%Y-%m-%d %H:%M")
sleep 3
echo " " >> data.txt
while read rows
do
n=$rows
break
done < data.txt
sudo python /home/pi/RS485_CAN_HAT_Code/485/python/query_total.py
sleep 3
echo " " >> data.txt
while read rows
do
t=$rows
break
done < data.txt
echo "這是第"$i"次查詢到并添加的數(shù)據(jù):"
echo "Now(L/H):"$n" Total(L):"$t" time:"$time
cmd="'{\"Args\":[\"AddData\",\"$time\",\"$n\",\"$t\"]}'"
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 174
#break
done
差不多準(zhǔn)備就緒了,在家里的洗手間搭了一下實驗環(huán)境:
?四、運行結(jié)果
?test.sh啟動,掛了大概一個小時:
?Org1查詢的結(jié)果,按照時間順序排序:
文章來源:http://www.zghlxwxcb.cn/news/detail-420263.html
?搞定!文章來源地址http://www.zghlxwxcb.cn/news/detail-420263.html
到了這里,關(guān)于樹莓派4B與智能渦輪流量計通過RS485(modbus RTU協(xié)議)收發(fā)數(shù)據(jù)(二)(Hyperledger Fabric環(huán)境中上傳數(shù)據(jù))的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!