利用modbus协议从云端服务器读取温湿度数据

1 Modbus协议介绍及解析

1.1 Modbus简介

Modbus是一种广泛应用于工业控制领域串行通信协议,以其开放性、高可靠性、高效简单性、免费等优点,成为了工业领域通信协议的业界标准,是工业现场电子设备之间常用的连接方式。Modbus按其格式可分为Modbus-RTU,Modbus-ASCII,Modbus-TCP,其中前两者适用于串行通信控制网络中,例如RS485,RS232等,而Modbus-TCP主要应用于基于以太网TCP/IP通信的控制网络中。通过此协议,控制器相互之间、或控制器和其它设备之间进行通信。Modbus协议使用的是主从通讯技术,即由主设备主动查询和操作从设备。一般将主控设备方所使用的协议称为Modbus Master,从设备方使用的协议称为Modbus Slave。典型的主设备包括工控机和工业控制器等;典型的从设备如PLC可编程控制器等。Modbus通讯物理接口可以选用串口(包括RS232和RS485),也可以选择以太网口。

1.2 报文类型及格式

1.2.1 协议描述

Modbus协议定义了一个与基础通信层无关的简单协议数据单元(PDU)。特定总线或网络上的 Modbus协议映射能够在应用数据单元(ADU)上引入一些附加域,启动Modbus事务处理的客户机创建Modbus 应用数据单元。如图1所示。Modbus-RTU方式的通讯数据帧格式如图2所示。

在这里插入图片描述

1.2.2 通讯信息传输过程

当命令由主机发送至从机时,符合相应地址码的从机处理命令,根据功能码作相应任务。如果CRC校验无误,则执行相应的任务,然后把数据返送给主机。如果CRC校验出错就不返回任何信息,主机应当有相应的超时处理。如果接收正确,但不能处理,返回异常报文。对字型数据发送顺序为先高字节后低字节;对浮点数按照正常的顺序发送;信文总长度(包括地址码和CRC校验码)不超过256字节。

1.2.3 应用数据单元

应用数据单元(ADU)由地址码、功能码、数据区、错误校验码构成。

  1. 地址码
    地址码是通讯信息帧的第一字节,从0到255。每个从机有唯一的地址码,并且只有符合地址码的从机才能响应回送信息。0xFF为广播地址。

  2. 功能码
    功能码向服务器指示将执行哪种操作。Modbus协议建立了客户机启动的请求格式,用一个字节编码 Modbus数据单元的功能码域,当从客户机向服务器设备发送报文时,功能码域通知服务器执行哪种操作。

  3. 数据区
    数据区可以是数据(如:开关量输入/输出、模拟量输入/输出、寄存器等等)、参考地址等。均为二进制数。各种数据参考地址在综合控制装置中均从1开始,在通讯过程中则从0开始,所以读写地址N时使用的地址数据为N-1。

  4. 错误校验码(CRC校验)
    由于电子噪声或一些其它干扰,信息在传输过程中有时会发生错误,CRC校验可以检验主机或从机在通讯数据传送过程中的信息是否有误,错误的数据可以放弃(无论是发送还是接收),这样增加了系统的安全和效率。Modbus通讯协议的CRC(冗余循环码)包含2个字节,即16位二进制数。CRC码由发送设备(主机)计算,放置于发送信息帧的尾部。接收信息的设备(从机)再重新计算接收到信息的CRC,比较计算得到的CRC是否与接收到的相符,如果两者不相符,则表明出错。在进行CRC计算时只用8个数据位,起始位及停止位,如有奇偶校验位也包括奇偶校验位,都不参与CRC计算。

1.3 主要功能码说明

1.3.1 读取内部线圈(接点)

功能码:0x01
描述:读从机多个内部线圈数据。不支持广播命令。(地址0XXXX)
查询
查询信息指定要读取的线圈开始地址和线圈数量。线圈地址从0开始。
以读取地址为17的从机中第7~15号内部线圈的值为例:

在这里插入图片描述

响应
响应报文格式:

在这里插入图片描述

计算机发送命令:[设备地址] [功能码01] [起始寄存器地址高8位] [低8位] [读取的寄存器数高8位] [低8位] [CRC校验的低8位] [CRC校验的高8位]
设备响应:[设备地址] [功能码01] [返回的字节个数][数据1][数据2]…[数据n][CRC校验的低8位] [CRC校验的高8位]

1.3.2 读取开关量输入

功能码:0x02
描述:读从机多个开入。不支持广播命令。(地址1XXXX,只读)
查询和响应同0x01功能码

1.3.3 写数字量(线圈状态)

功能码:0x05
例:[11][05][00][AC][FF][00][CRC低][CRC高]
1.功能码:写数字量的命令号固定为05。
2.需下置的寄存器地址高8位,低8位:表明了需要下置的开关的地址。
3.下置的数据高8位,低8位:表明需要下置的开关量的状态。例子中为把该开关闭合。注意,此处只可以是[FF][00]表示闭合[00][00]表示断开,其他数值非法。
4.此命令一条只能下置一个开关量的状态。
计算机发送命令:[设备地址] [功能码05] [需下置的寄存器地址高8位] [低8位] [下置的数据高8位] [低8位] [CRC校验的低8位] [CRC校验的高8位]
设备响应:如果成功把计算机发送的命令原样返回,否则不响应。

1.3.4 取多个保持寄存器(数据寄存器)

功能码:0x03
描述:读从机多个保持寄存器二进制数据。不支持广播命令。(地址4XXXX)
查询
查询信息指定要读取的寄存器开始地址和寄存器数量。寄存器地址从0开始。
以读取地址为17的从机中第108~第110号寄存器为例:
查询报文格式

在这里插入图片描述

响应
响应报文格式:

在这里插入图片描述

计算机发送命令:[设备地址] [功能码03] [起始寄存器地址高8位] [低8位] [读取的寄存器数高8位] [低8位] [CRC校验的低8位] [CRC校验的高8位]
设备响应:[设备地址] [功能码03] [返回的字节个数][数据1][数据2]…[数据n][CRC校验的低8位] [CRC校验的高8位]

1.3.5 读取多个输入寄存器(模拟输入寄存器)

功能码:0x04
描述:读从机多个模拟输入寄存器二进制数据。不支持广播命令。(地址3XXXX,只读)
同0x03功能码

1.3.6 写单个寄存器

功能码:0x06
描述:写从机单个寄存器。(地址4XXXX)
命令: 命令指定要写的寄存器地址和写入的值。地址从900开始。
以将数据1写入地址为1的从机中第900号寄存器为例:
命令报文格式:

在这里插入图片描述

响应
响应报文格式:与命令格式相同。
计算机发送命令:[设备地址] [功能码06] [需下置的寄存器地址高8位] [低8位] [下置的数据高8位] [低8位] [CRC校验的低8位] [CRC校验的高8位]

1.3.7 写多个保持寄存器

功能码:0x10
描述:写从机多个保持寄存器。(地址4XXXX)
命令: 命令指定要写的寄存器地址和写入的值。地址从900开始。
以将数据1写入地址为1的从机中第900、901号寄存器为例:
命令报文格式:

在这里插入图片描述
响应
响应报文格式:

在这里插入图片描述

计算机发送命令:[设备地址] [功能码10] [需下置的寄存器起始地址高8位] [低8位] [要写的寄存器数量的高字节] [低字节] [要写的字节数(等于寄存器数目*2)] [下置的数据高8位] [低8位] [CRC校验的低8位] [CRC校验的高8位]
设备响应:如果成功则响应:设备地址,功能码[0x10],寄存器起始地址高字节,低字节,要写的寄存器数量的高字节,低字节,CRC校验低字节,高字节

2 使用python完成Modbus协议从云端获取信息

云端信息请求分为两大类,一是通过TCP协议进行请求,二是通过UDP协议进行请求,下面主要分析使用python进行TCP协议进行请求,UDP协议除了需要每次请求数据时都要与进行连接以外,其它数据请求格式和解析接收的数据与TCP一致。

2.1 TCP方式请求数据

  1. 建立tcp连接
tcp = socket.socket(socket.AF_INET,
                        socket.SOCK_STREAM,
                        socket.IPPROTO_TCP)
    tcp.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    tcp.settimeout(5)
    try:
        tcp.connect(('demo-monitor.igong.com', 8002))
        print("TCP连接成功")
    except:
        print("连接TCP失败")
        sys.exit(1)
  1. 生成crc16校验位
def crc16(string):
    # data = bytes.fromhex(string)
    data = string
    crc = 0xFFFF
    for pos in data:
        crc ^= pos
        for i in range(8):
            if ((crc & 1) != 0):
                crc >>= 1
                crc ^= 0xA001
            else:
                crc >>= 1
    return hex(((crc & 0xff) << 8) + (crc >> 8))
  1. 指令发送和接收
def getStain(cmd, num, time):
    # print(cmd)
    # print(num)
    cmd = bytes.fromhex(cmd)
    crc = crc16(cmd)
    crc = bytes.fromhex(crc[2:])
    cmd = cmd + crc
    # print(cmd)
    # 发送对应的指令
    tcp.send(cmd)
    try:
        data = tcp.recv(8192)
    except socket.timeout:
        print("超时")
        sys.exit(1)
    crc = data[-2:]
    crc1 = crc16(data[:-2])
    crc1 = crc1[2:]
    if len(crc1) == 3:
        crc1 = '0' + crc1
    crc1 = bytes.fromhex(crc1)
    if crc != crc1:
        print("CRC16校验失败!")
        sys.exit(2)
    yb, wd = struct.unpack('>ii', data[4:12])
    yb = yb / 100.0
    wd = wd / 100.0
    print("应变:", yb, "温度:", wd)
    print(time)
    yb = str(yb)
    wd = str(wd)
    AddData(num, yb, wd, time)
  1. 数据存储到mysql
# 连接数据
def MySQLConnect():
    connection = pymysql.connect(
        host='localhost',  # IP,MySQL数据库服务器IP地址
        port=3306,  # 端口,默认3306,可以不输入
        user='你的名字',  # 数据库用户名
        password='你的密码',  # 数据库登录密码
        database='sensor',  # 要连接的数据库
        charset='utf8'  # 字符集,注意不是'utf-8'
    )
    return connection


# 插入数据到数据库
def AddData(num, yb, wd, time):
    # 连接数据库
    conn = MySQLConnect()
    # 使用cursor()方法创建一个游标对象cursor
    cursor = conn.cursor()
    # 插入数据库
    sql = "INSERT INTO strain_sensor(id ,mic, strain_temp, time) VALUES (%s,%s,%s,%s); "
    cursor.execute(sql, [num, yb, wd, time])
    # 提交事务
    conn.commit()
    # 关闭游标
    cursor.close()
    # 关闭数据库连接
    conn.close()

将数据存入mysql数据库中,Modbus —TCP云端数据采集就完成了,下面是完整Modbus—TCP采集代码:

import socket
import sys
import struct
import time
import tcp
import threading
import _thread
import pymysql


# 本程序是应变传感器采集,可以通过发送ALL指令进行全部传感器的采集


def crc16(string):
    # data = bytes.fromhex(string)
    data = string
    crc = 0xFFFF
    for pos in data:
        crc ^= pos
        for i in range(8):
            if ((crc & 1) != 0):
                crc >>= 1
                crc ^= 0xA001
            else:
                crc >>= 1
    return hex(((crc & 0xff) << 8) + (crc >> 8))


# 连接数据
def MySQLConnect():
    connection = pymysql.connect(
        host='localhost',  # IP,MySQL数据库服务器IP地址
        port=3306,  # 端口,默认3306,可以不输入
        user='你的用户名',  # 数据库用户名
        password='你的密码',  # 数据库登录密码
        database='sensor',  # 要连接的数据库
        charset='utf8'  # 字符集,注意不是'utf-8'
    )
    return connection


# 插入数据到数据库
def AddData(num, yb, wd, time):
    # 连接数据库
    conn = MySQLConnect()
    # 使用cursor()方法创建一个游标对象cursor
    cursor = conn.cursor()
    # 插入数据库
    sql = "INSERT INTO strain_sensor(id ,mic, strain_temp, time) VALUES (%s,%s,%s,%s); "
    cursor.execute(sql, [num, yb, wd, time])
    # 提交事务
    conn.commit()
    # 关闭游标
    cursor.close()
    # 关闭数据库连接
    conn.close()


# 获取一次数据
def getStain(cmd, num, time):
    # print(cmd)
    # print(num)
    cmd = bytes.fromhex(cmd)
    crc = crc16(cmd)
    crc = bytes.fromhex(crc[2:])
    cmd = cmd + crc
    # print(cmd)
    # 发送对应的指令
    tcp.send(cmd)
    try:
        data = tcp.recv(8192)
    except socket.timeout:
        print("超时")
        sys.exit(1)
    crc = data[-2:]
    crc1 = crc16(data[:-2])
    crc1 = crc1[2:]
    if len(crc1) == 3:
        crc1 = '0' + crc1
    crc1 = bytes.fromhex(crc1)
    if crc != crc1:
        print("CRC16校验失败!")
        sys.exit(2)
    yb, wd = struct.unpack('>ii', data[4:12])
    yb = yb / 100.0
    wd = wd / 100.0
    print("应变:", yb, "温度:", wd)
    print(time)
    yb = str(yb)
    wd = str(wd)
    AddData(num, yb, wd, time)


def setCircleData(cmd, num):
    count = 0
    flag = 0
    last = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())
    last1 = time.time()
    while True:
        now = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())
        now1 = time.time()
        # print(now)
        if (flag == 0):
            count = count + 1
            flag = 1
            getStain(cmd, num, last)
            last = now
            last1 = now1
        if now1 - last1 > 5:
            if count >= 5:
                str = input("请选择是否继续采集(y表示继续,n表示退出):")
                if str == 'y':
                    count = 0
                    continue
                else:
                    break
            count = count + 1
            getStain(cmd, num, now)
            last = now
            last1 = now1


# 同时采集全部应变传感器
def setCircleAll(cmd):
    flag = 0
    count = 0
    last = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())
    last1 = time.time()
    while True:
        now = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())
        now1 = time.time()
        # count=0
        # print(now)
        if (flag == 0):
            flag = 1
            count = count + 1
            for cmd1 in cmd:
                id = '00' + cmd1[0:2]
                # print(id)
                # print(cmd1)
                getStain(cmd1, id, last)
            last = now
            last1 = now1
        if now1 - last1 > 5:
            if count >= 5:
                str = input("请选择是否继续采集(y表示继续,n表示退出):")
                if str == 'y':
                    count = 0
                    continue
                else:
                    break
            count = count + 1
            for cmd1 in cmd:
                id = '00' + cmd1[0:2]
                getStain(cmd1, id, now)
            last = now
            last1 = now1


if __name__ == '__main__':
    tcp = socket.socket(socket.AF_INET,
                        socket.SOCK_STREAM,
                        socket.IPPROTO_TCP)
    tcp.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    tcp.settimeout(5)
    try:
        tcp.connect(('demo-monitor.igong.com', 8002))
        print("TCP连接成功")
    except:
        print("连接TCP失败")
        sys.exit(1)
    flag = 0
    while True:
        print("具体指令给格式为000+传感器编号(1,2,3,4,5)")
        num = input("请输入采集传感器的编号(All表示采集全部传感器,0表示退出采集):")
        if num == '0001':
            cmd = '010300010002'
            setCircleData(cmd, num)

        elif num == '0002':
            cmd = '020300010002'
            setCircleData(cmd, num)

        elif num == '0003':
            cmd = '030300010002'
            setCircleData(cmd, num)
        elif num == '0004':
            cmd = '040300010002'
            setCircleData(cmd, num)
        elif num == '0005':
            cmd = '050300010002'
            setCircleData(cmd, num)
        elif num == 'All':
            cmd = {
    
    '010300010002', '020300010002', '030300010002', '040300010002', '050300010002'}
            setCircleAll(cmd)
        elif num == '0':
            break
        else:
            print("输入信息不合法,请重新输入")

2.2 UDP方式请求数据

UDP连接

udp = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP)
    udp.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    udp.settimeout(5)

完整代码如下:

import socket
import struct
import sys
import time

import pymysql


#实现采集温度传感器和静力水准仪

def crc16(string):
    #data = bytes.fromhex(string)
    data=string
    crc = 0xFFFF
    for pos in data:
        crc ^= pos
        for i in range(8):
            if ((crc & 1) != 0):
                crc >>= 1
                crc ^= 0xA001
            else:
                crc >>= 1
    return hex(((crc & 0xff) << 8) + (crc >> 8))

#连接数据库
def MySQLConnect():
    connection = pymysql.connect(
        host='localhost',  # IP,MySQL数据库服务器IP地址
        port=3306,  # 端口,默认3306,可以不输入
        user='你的用户名',  # 数据库用户名
        password='你的密码',  # 数据库登录密码
        database='sensor',  # 要连接的数据库
        charset='utf8'  # 字符集,注意不是'utf-8'
    )
    return connection

#插入温湿度采集到数据库
def AddData1(wd,sd,time):
    # 连接数据库
    conn = MySQLConnect()
    # 使用cursor()方法创建一个游标对象cursor
    cursor = conn.cursor()
    # 插入数据库
    sql = "INSERT INTO temp_hum_sensor(temp, hum, time) VALUES (%s,%s,%s); "
    cursor.execute(sql, [wd, sd, time])
    # 提交事务
    conn.commit()
    # 关闭游标
    cursor.close()
    # 关闭数据库连接
    conn.close()

#插入静力水准仪采集到数据库
def AddData2(id,water_level,time):
    # 连接数据库
    conn = MySQLConnect()
    # 使用cursor()方法创建一个游标对象cursor
    cursor = conn.cursor()
    # 插入数据库
    sql = "INSERT INTO static_level(id, water_level, time) VALUES (%s,%s,%s); "
    cursor.execute(sql, [id, water_level, time])
    # 提交事务
    conn.commit()
    # 关闭游标
    cursor.close()
    # 关闭数据库连接
    conn.close()

#采集温度传感器的数据
def getDataTemp(cmd):
    #flag标志采集的次数
    flag=0
    last = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())
    print(last)
    last1 = time.time()
    cmd = bytes.fromhex(cmd)
    #print(cmd)
    crc = crc16(cmd)
    crc = bytes.fromhex(crc[2:])
    #得到发送的指令(modbus协议定义内容+校验)
    cmd = cmd + crc
    udp.sendto(cmd, ('demo-monitor.igong.com', 8001))
    try:
        data, addr = udp.recvfrom(8192)
    except socket.timeout:
        print("超时")
        sys.exit(1)
    crc = data[-2:]
    crc1 = crc16(data[:-2])
    crc1 = crc1[2:]
    if (len(crc1) == 3):
        crc1 = '0' + crc1
    crc1 = bytes.fromhex(crc1)

    # print(crc1)
    if crc != crc1:
        print("CRC16校验失败!")
        sys.exit(2)
    # 解析数据
    wd, sd = struct.unpack('>ii', data[4:12])
    wd = wd / 100.
    print("温度:", wd, "湿度:", sd)
    AddData1(wd, sd, last)
    flag=flag+1
    while True:
        now= time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())
        #print(s)
        now1=time.time()
        #每隔5s获取一次数据
        if(now1-last1>5):
            udp.sendto(cmd, ('demo-monitor.igong.com', 8001))
            try:
                data, addr = udp.recvfrom(8192)
            except socket.timeout:
                print("超时")
                sys.exit(1)
            crc = data[-2:]
            crc2=bytes.hex(crc)
            #print(crc2)
            crc1 = crc16(data[:-2])
            crc1=crc1[2:]
            if(len(crc1)==3):
                crc1='0'+crc1
            #print(crc1)
            crc1=bytes.fromhex(crc1)
            #print(crc1)
            if crc != crc1:
                print("CRC16校验失败!")
                sys.exit(2)
            #解析数据
            wd, sd = struct.unpack('>ii', data[4:12])
            wd = wd / 100.0
            #当前时间
            print(now)
            #获取得到的数据
            print("温度:", wd, "湿度:", sd)
            last=now
            last1=now1
            wd=str(wd)
            sd=str(sd)
            AddData1(wd,sd,now)
            flag = flag + 1
            if flag >= 5:
                str1 = input("请选择是否继续采集(y表示继续,n表示退出):")
                if str1 == 'y':
                    flag = 0
                    continue
                else:
                    break



def getDataStaticLevel(cmd):
    id=cmd[0:2]
    #print(id)
    if id=='02':
        #print("2号")
        id='00'+id
        getData(id,cmd)
    elif id=='03':
        #print("3号")
        id = '00' + id
        getData(id,cmd)
    elif id=='04':
        #print("4号")
        id = '00' + id
        getData(id,cmd)
    elif id=='05':
        #print("5号")
        id = '00' + id
        getData(id,cmd)

def getData(id,cmd):
    flag=0
    last = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())
    print(last)
    last1 = time.time()
    cmd = bytes.fromhex(cmd)
    crc = crc16(cmd)
    crc = crc[2:]
    if (len(crc) == 3):
        crc = '0' + crc
    crc = bytes.fromhex(crc)
    cmd = cmd + crc
    udp.sendto(cmd, ('demo-monitor.igong.com', 8001))
    print("发送数据成功")
    try:
        data, address = udp.recvfrom(8192)
        #print(data)
    except socket.timeout:
        print("超时")
        sys.exit(1)
    #print(len(data))
    crc = data[-2:]
    #print(data[:-2])
    crc1 = crc16(data[:-2])
    #print(crc1)
    crc1=crc1[2:]
    if len(crc1) == 3:
        crc1 = '0' + crc1
    #print(crc1)
    crc1 = bytes.fromhex(crc1)
    if crc != crc1:
        print("CRC16校验失败!")
        sys.exit(2)
    #print(data[4:8])
    nd = struct.unpack('>i', data[4:8])
    #print(nd)
    nd1 = nd[0]*10.0
    nd1=str(nd1)
    #print(last)
    print("挠度:"+nd1)
    AddData2(id,nd1,last)
    flag=flag+1

    while True:

        now = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())
        # print(s)
        now1 = time.time()
        # 每隔5s获取一次数据
        if (now1 - last1 > 5):
            udp.sendto(cmd, ('demo-monitor.igong.com', 8001))
            try:
                data, addr = udp.recvfrom(8192)
            except socket.timeout:
                print("超时")
                sys.exit(1)
            crc = data[-2:]
            crc2 = bytes.hex(crc)
            # print(crc2)
            crc1 = crc16(data[:-2])
            crc1 = crc1[2:]
            if (len(crc1) == 3):
                crc1 = '0' + crc1
            # print(crc1)
            crc1 = bytes.fromhex(crc1)
            # print(crc1)
            if crc != crc1:
                print("CRC16校验失败!")
                sys.exit(2)
            # 解析数据
            nd = struct.unpack('>i', data[4:8])
            nd = nd[0] * 10.0
            nd1=str(nd)
            print(now)
            print("挠度:" + nd1)
            nd=str(nd)
            AddData2(id, nd1, now)
            last=now
            last1=now1
            flag = flag + 1
            if flag >= 5:
                str1 = input("请选择是否继续采集(y表示继续,n表示退出):")
                if str1 == 'y':
                    flag = 0
                    continue
                else:
                    break

if __name__ == '__main__':
    print("开始程序")
    print("程序相关说明:")
    print("本程序采用UDP协议,其中当输入指令为0就退出整个程序。")
    print("命令格式类似于地址(01,02,03,04,05)+03+传感器地址(0001)+传感器个数(0001,0002)")
    print("例如:010300010002(温度传感器),020300010001(静力水准仪1)")
    print("如果出现不合法指令就输出提示信息,并重新输入指令。")
    udp = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP)
    udp.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    udp.settimeout(5)

    while True:
        cmd = input("请输入相关命令:")
        #print(len(cmd))
        num = cmd[8:]
        #print(num)
        if num == '0001' and cmd[0:2] == '01':
            print("此处不实现只对温度采集!!!")
        elif num == '0001':
            print("一个传感器")
            getDataStaticLevel(cmd)
        elif num == '0002':
            print("两个传感器")
            getDataTemp(cmd)
        elif cmd == '0':
            break
        else:
            print("指令不合法!!!")

2.3 运行结果

TCP传输程序运行结果如下:

在这里插入图片描述

数据库存储效果如下:

在这里插入图片描述

3 使用c语言完成modbus协议从云端服务器读取信息

下文介绍使用tcp方式请求数据的方法

3.1 新建项目

本文使用小熊猫ide
下载链接

新建一个TCP客户端应用

在这里插入图片描述

3.2 修改代码

  1. 初始化socket dll
    通过ip连接服务器对应端口,以便于获取数据
WORD winsock_version = MAKEWORD(2,2);
	WSADATA wsa_data;
	if (WSAStartup(winsock_version, &wsa_data) != 0) {
    
    
		printf("Failed to init socket!\n");
		return 1;
	}
	
	SOCKET client_socket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
	if (client_socket == INVALID_SOCKET) {
    
    
		printf("Failed to create server socket!\n");
		return 2;
	}
	
	struct sockaddr_in server_addr;
	server_addr.sin_family = AF_INET;
	server_addr.sin_port = htons(PORT);
	server_addr.sin_addr.S_un.S_addr = inet_addr(SERVER_IP);
	if (connect(client_socket, (LPSOCKADDR)&server_addr, sizeof(server_addr)) == SOCKET_ERROR) {
    
    
		printf("Failed to connect server: %ld !\n", GetLastError());
		return 3;
	}
  1. 生成crc16校验码
uint16_t CRC_16(uint8_t *temp)
{
    
    
	uint8_t i,j;
	uint16_t CRC_1 = 0xFFFF;          //声明CRC寄存区,也就是步骤1
	for(i = 0;i < 6;i++)       //这里的for循环说的是步骤6中的重复步骤 2 到步骤 5
	{
    
    
		CRC_1 ^= temp[i]; //这里就是步骤2,进行异或运算
		for(j = 0;j < 8;j++)         //用来将异或后的低八位全部移出的for循环
		{
    
    
			if(CRC_1 & 0x01)         //判断低八位的最后一位是否为1,为1时执行下列语句,也就是步骤3说的移位判断与步骤5说的右移8次
			{
    
    
				/*一定要先移位,再异或*/
				CRC_1 >>=1;          //移位后再异或,就是步骤4
				CRC_1 ^= 0xA001;     //0xA001为0x8005的逆序
			}
			else                    //若不为1,则直接移位。
			{
    
    
				CRC_1 >>=1;
			}
		}
	}
	
	//	CRC_1 = (((CRC_1 & 0xFF)<<8) + (CRC_1>>8));
	//	printf("%04x\r\n",CRC_1);     //用于打印检测CRC校验码
	return(CRC_1);
}
  1. 分析数据并取出
int ret = recv(client_socket, recv_data, BUFFER_SIZE, 0);
		if (ret < 0) {
    
    
			printf("Failed to receive data!\n");
			break;
		}
		recv_data[ret]=0; // correctly ends received string
		char yb[4],wd[4];
		for(int i=0;i<4;i++){
    
    
			//TODO
			yb[i] = recv_data[4+i];
			wd[i] = recv_data[8+i];
			
		}
		float mic = hexToDec(yb)/100.0;
		float strain_temp = hexToDec(wd)/100.0;
		printf("应变:%f\r\n",mic);
		printf("温度:%f\r\n",strain_temp);

完整代码如下:

#include <stdio.h>
#include <string.h>
#include <stdbool.h>
#include <winsock2.h>  
#include <math.h>
#include "stdint.h"
#define length_8 8    //定义一个宏,为传入8位16进制数的个数
#define PORT 8002
#define SERVER_IP "123.56.90.74"
#define BUFFER_SIZE 4196

const char* kExitFlag = "exit";


/* 返回ch字符在sign数组中的序号 */
int getIndexOfSigns(char ch)
{
    
    
	if(ch >= '0' && ch <= '9')
	{
    
    
		return ch - '0';
	}
	if(ch >= 'A' && ch <='F') 
	{
    
    
		return ch - 'A' + 10;
	}
	if(ch >= 'a' && ch <= 'f')
	{
    
    
		return ch - 'a' + 10;
	}
	return -1;
}
/* 十六进制数转换为十进制数 */
int hexToDec(char *source)
{
    
    
	int sum = 0;
	int t = 1;
	int i, len=4;
	char low,high;
	for(int i=0,j=7;i<4;i++){
    
    
		//TODO
		high = (source[i] & 0xf0)>>4;
		low = source[i] & 0x0f;
		sum += high*pow(16,j--)+low*pow(16,j--); 
	}
	return sum;
}



const unsigned char *fromhex(const char *str)
{
    
    
	static unsigned char buf[512];
	size_t len = strlen(str) / 2;
	if (len > 512) len = 512;
	for (size_t i = 0; i < len; i++) {
    
    
		unsigned char c = 0;
		if (str[i * 2] >= '0' && str[i*2] <= '9') 
			c += (str[i * 2] - '0') << 4;
		if ((str[i * 2] & ~0x20) >= 'A' && (str[i*2] & ~0x20) <= 'F') 
			c += (10 + (str[i * 2] & ~0x20) - 'A') << 4;
		if (str[i * 2 + 1] >= '0' && str[i * 2 + 1] <= '9') 
			c += (str[i * 2 + 1] - '0');
		if ((str[i * 2 + 1] & ~0x20) >= 'A' && (str[i * 2 + 1] & ~0x20) <= 'F')
			c += (10 + (str[i * 2 + 1] & ~0x20) - 'A');
		buf[i] = c;
	}
	return buf;
}

uint16_t CRC_16(uint8_t *temp)
{
    
    
	uint8_t i,j;
	uint16_t CRC_1 = 0xFFFF;          //声明CRC寄存区,也就是步骤1
	for(i = 0;i < 6;i++)       //这里的for循环说的是步骤6中的重复步骤 2 到步骤 5
	{
    
    
		CRC_1 ^= temp[i]; //这里就是步骤2,进行异或运算
		for(j = 0;j < 8;j++)         //用来将异或后的低八位全部移出的for循环
		{
    
    
			if(CRC_1 & 0x01)         //判断低八位的最后一位是否为1,为1时执行下列语句,也就是步骤3说的移位判断与步骤5说的右移8次
			{
    
    
				/*一定要先移位,再异或*/
				CRC_1 >>=1;          //移位后再异或,就是步骤4
				CRC_1 ^= 0xA001;     //0xA001为0x8005的逆序
			}
			else                    //若不为1,则直接移位。
			{
    
    
				CRC_1 >>=1;
			}
		}
	}
	
	//	CRC_1 = (((CRC_1 & 0xFF)<<8) + (CRC_1>>8));
	//	printf("%04x\r\n",CRC_1);     //用于打印检测CRC校验码
	return(CRC_1);
}

int main() {
    
    
	// 初始化socket dll。
	WORD winsock_version = MAKEWORD(2,2);
	WSADATA wsa_data;
	if (WSAStartup(winsock_version, &wsa_data) != 0) {
    
    
		printf("Failed to init socket!\n");
		return 1;
	}
	
	SOCKET client_socket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
	if (client_socket == INVALID_SOCKET) {
    
    
		printf("Failed to create server socket!\n");
		return 2;
	}
	
	struct sockaddr_in server_addr;
	server_addr.sin_family = AF_INET;
	server_addr.sin_port = htons(PORT);
	server_addr.sin_addr.S_un.S_addr = inet_addr(SERVER_IP);
	if (connect(client_socket, (LPSOCKADDR)&server_addr, sizeof(server_addr)) == SOCKET_ERROR) {
    
    
		printf("Failed to connect server: %ld !\n", GetLastError());
		return 3;
	}
	
	char recv_data[BUFFER_SIZE+1];
	while (true) {
    
    
		uint8_t data[length_8];
		printf("0+传感器编号(1,2,3,4,5)0300010002\r\n");
		scanf("%s",data);
		uint16_t crc;
		unsigned char * cmd;
		char crc1[8];
		cmd = fromhex(data);
		crc = CRC_16(cmd);
		uint8_t a = 0xFF;
		for(int i=0;i<6;i++){
    
    
			//TODO
			crc1[i] = cmd[i];
		}
		crc1[6] = a & crc;
		crc1[7] = (crc >> 8) & a;
		
		if (send(client_socket, crc1, 8, 0) < 0) {
    
    
			printf("Failed to send data!\n");
			break;
		}
		
		int ret = recv(client_socket, recv_data, BUFFER_SIZE, 0);
		if (ret < 0) {
    
    
			printf("Failed to receive data!\n");
			break;
		}
		recv_data[ret]=0; // correctly ends received string
		char yb[4],wd[4];
		for(int i=0;i<4;i++){
    
    
			//TODO
			yb[i] = recv_data[4+i];
			wd[i] = recv_data[8+i];
			
		}
		float mic = hexToDec(yb)/100.0;
		float strain_temp = hexToDec(wd)/100.0;
		printf("应变:%f\r\n",mic);
		printf("温度:%f\r\n",strain_temp);
		
		
		//		printf("Receive data from server: \"%x\"\n",recv_data);
		if (strcmp(data,kExitFlag)==0) {
    
    
			printf("Exit!\n");
			break;
		}
	}
	
	closesocket(client_socket);
	WSACleanup();
	
	return 0;
}

3.3 实现效果

在这里插入图片描述

总结

通过本次实验,了解了如何使用Modbus协议进行通讯。Modbus是一种串行通信协议,其免费、简单,并且方便修改。但Modbus是主从方式通信,也就是说,不能同步进行通信,总线上每次只有一个数据进行传输,即主机发送,从机应答,主机不发送,总线上就没有数据通信。


参考:
https://blog.csdn.net/qinkaiword/article/details/119419055
https://blog.csdn.net/panda5_csdn/article/details/94332166

猜你喜欢

转载自blog.csdn.net/apple_52030329/article/details/128433115