树莓派4B与智能插排通过RS485(modbus RTU协议)通信

目标:使用树莓派4B与CANHAT扩展板读取智能插排测量的各项数据(RS485+modbus RTU),获取的数据上传到Hyperledger Fabric框架。

之前学习过了modbus RTU协议,在智能涡轮流量计的实验中应用过一次,这次用这个带485模块的智能插座再复习一次~

实验材料:

树莓派4B/8G:

树莓派4B与智能插排通过RS485(modbus RTU协议)通信_第1张图片

 CANHAT扩展板:

树莓派4B与智能插排通过RS485(modbus RTU协议)通信_第2张图片

 USB-485转换器:

树莓派4B与智能插排通过RS485(modbus RTU协议)通信_第3张图片

 RS485机柜排插:

树莓派4B与智能插排通过RS485(modbus RTU协议)通信_第4张图片

 RJ45水晶头转8PIN端子:

树莓派4B与智能插排通过RS485(modbus RTU协议)通信_第5张图片

 树莓派相关库与例程在上次实验已经安装过了,步骤可参照官网:

RS485 CAN HAT - Waveshare Wiki

一、PC端串口测试

还是先使用PC端的串口调试助手测试一下智能插座的通讯。先将设备正确接线:

树莓派4B与智能插排通过RS485(modbus RTU协议)通信_第6张图片

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

调试前先看看设备的通信说明书:

树莓派4B与智能插排通过RS485(modbus RTU协议)通信_第7张图片

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

具体各项数据存储的寄存器地址如下:

树莓派4B与智能插排通过RS485(modbus RTU协议)通信_第8张图片

 计算一下需要用到的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    查询二氧化碳排量

以查询有功总电能为例,串口调试助手发送命令后接收到如下数据:

树莓派4B与智能插排通过RS485(modbus RTU协议)通信_第9张图片

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

树莓派4B与智能插排通过RS485(modbus RTU协议)通信_第10张图片

 二、树莓派与智能插排通信

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

脚本运行结果:

树莓派4B与智能插排通过RS485(modbus RTU协议)通信_第11张图片

Org1查询结果:

 ~~

 

你可能感兴趣的:(树莓派,区块链,python,fabric,linux)