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

目标:使用树莓派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 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查询结果:

 ~~

 

  • 5
    点赞
  • 30
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 4
    评论
### 回答1: 智能插排项目使用了Freertos和Lwip技术,分别实现了实时操作系统和网络协议栈的功能。该项目设计了一个智能插排,可以通过互联网远程控制它的开关,实现智能化家居生活。 通过Freertos固件,智能插排可以实现实时任务调度和多任务处理,确保开关操作的实时性和稳定性。Lwip技术提供了底层网络通信功能,使得智能插排可以连接到远程服务器,实现远程控制。 智能插排工作原理为:智能插排通过无线网络连接到远程服务器,接收来自服务器的信号后,通过驱动控制继电器的开关,实现远程控制。同时,智能插排还具备本地控制功能,通过物理按钮可以进行手动开关。 该智能插排还可以通过手机APP进行控制,使得使用者可以实现远程控制和定时开关的功能。同时,还可以通过获取智能插排的工作状态,实现低功耗管理和智能节能。 总之,通过使用Freertos和Lwip技术,智能插排项目实现了远程控制和本地控制的功能,为家居生活带来更便利的体验。 ### 回答2: 智能插排项目是一项利用现代智能技术设计的智能家居产品,旨在提高家庭生活中的舒适度和便利程度。在该项目中,采用了两种关键的技术模块,分别是FreeRTOS和lwIP。这两种技术模块可以说是智能插排项目的核心组成部分,下面将分别介绍。 FreeRTOS是一个开放源代码的实时操作系统内核,用于嵌入式系统中。在智能插排项目中,它的作用是负责管理和分配系统资源,以实现实时性和并行性。它的特性包括:多任务处理、支持时间片、内存保护和任意抢占等。 lwIP是一个轻量级的TCP/IP协议栈,用于嵌入式系统中。在智能插排项目中,它的作用是处理网络通信任务,如网络协议处理、数据传输和连接管理。相对于其他TCP/IP协议栈,lwIP具有更小的内存占用和更好的运行效率,并且支持各种网络协议,如TCP、UDP、IPv4和IPv6等。 综上所述,智能插排项目的FreeRTOS和lwIP两个技术模块各自承担着不同的职责,但都是实现该项目的关键所在。FreeRTOS的实时性和多任务处理能力可以保证系统高效运行,而lwIP的网络协议处理和连接管理能力可以保证系统网络通信流畅无阻。两个模块的应用使得智能插排项目在智能家居市场中具有一定的竞争优势。 ### 回答3: 智能插排项目是一种智能家居设备,它可以实现远程控制、计时开关等功能,方便人们的生活。为了实现这些功能,该项目采用了两个开源软件:FreeRTOS和lwIP。 FreeRTOS是一款实时操作系统。它提供了一些基本的函数,如任务管理、时间管理、内存管理等,这些函数可以帮助我们快速开发实时系统。在智能插排项目中,FreeRTOS可以用来实现多任务管理,例如,我们可以为定时开关任务、远程控制任务等编写单独的任务,这样可以提高系统可靠性和安全性。 lwIP是一款轻量级的TCP/IP协议栈,它可以在嵌入式系统中运行。lwIP的主要特点是轻量级、高效、可移植性强。在智能插排项目中,lwIP可以用来实现TCP/IP通讯功能,例如,远程控制信号的传输,以及动态获取IP地址等。通过lwIP,我们可以实现智能插排的网络通信功能。 综上所述,智能插排项目可以利用FreeRTOS和lwIP提供的基础功能,实现多任务管理、远程控制、定时开关等功能。这些功能可以帮助人们更方便地控制家居设备,提高生活的品质和便利性。同时,利用开源软件可以降低项目的开发成本和风险,提高项目的可靠性和可扩展性。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

lv(●—●)

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值