Use modbus protocol to read temperature and humidity data from cloud server

1 Introduction and Analysis of Modbus Protocol

1.1 Introduction to Modbus

Modbus is a serial communication protocol widely used in the field of industrial control. With its advantages of openness, high reliability, high efficiency, simplicity, and free, it has become the industry standard for communication protocols in the industrial field. It is commonly used between electronic devices in industrial fields. connection method. Modbus can be divided into two types according to its format Modbus-RTU,Modbus-ASCII,Modbus-TCP, among which the first two are suitable for serial communication control network, such as RS485, RS232, etc., and are Modbus-TCPmainly used in control network based on Ethernet TCP/IP communication. Through this protocol, controllers communicate with each other, or between controllers and other devices. The Modbus protocol uses master-slave communication technology, that is, the master device actively queries and operates the slave device. Generally, the protocol used by the master device side is referred Modbus Masterto as the protocol used by the slave device side Modbus Slave. Typical master devices include industrial computers and industrial controllers, etc.; typical slave devices such as PLC programmable controllers, etc. The physical interface of Modbus communication can be a serial port (including RS232 and RS485), or an Ethernet port.

1.2 Message type and format

1.2.1 Protocol description

The Modbus protocol defines a simple protocol data unit (PDU) that is independent of the underlying communication layer. Modbus protocol mapping on a particular bus or network can introduce some additional fields on the Application Data Unit (ADU), which is created by a client that initiates a Modbus transaction. As shown in Figure 1. The communication data frame format of Modbus-RTU mode is shown in Figure 2.

insert image description here

1.2.2 Communication information transmission process

When the command is sent from the master to the slave, the slave that matches the corresponding address code will process the command and perform the corresponding task according to the function code. If the CRC check is correct, execute the corresponding task, and then return the data to the host. If there is an error in the CRC check, no information will be returned, and the host should have corresponding timeout processing. If it is received correctly but cannot be processed, an exception message will be returned. For font data, the sending sequence is high byte first and then low byte; for floating point numbers, send in normal order; the total length of the message (including address code and CRC check code) does not exceed 256 bytes.

1.2.3 Application Data Unit

Application data unit (ADU) is composed of address code, function code, data area, and error check code.

  1. Address code
    The address code is the first byte of the communication information frame, from 0 to 255. Each slave has a unique address code, and only the slave that matches the address code can respond to the feedback message. 0xFF is broadcast address.

  2. Function Codes
    Function codes indicate to the server which operation to perform. The Modbus protocol establishes the request format initiated by the client, and uses a byte to encode the function code field of the Modbus data unit. When sending a message from the client to the server device, the function code field informs the server which operation to perform.

  3. Data area
    The data area can be data (such as: digital input/output, analog input/output, register, etc.), reference address, etc. Both are binary numbers. Various data reference addresses start from 1 in the integrated control device, and start from 0 in the communication process, so the address data used when reading and writing address N is N-1.

  4. Error check code (CRC check)
    Due to electronic noise or some other interference, information sometimes has errors during transmission. CRC check can check whether the information of the master or slave is wrong during the communication data transmission process. Data can be discarded (whether sent or received), which increases the security and efficiency of the system. The CRC (Redundant Cyclic Code) of the Modbus communication protocol consists of 2 bytes, that is, 16-bit binary numbers. The CRC code is calculated by the sending device (host) and placed at the end of the sending information frame. The device (slave) that receives the information recalculates the CRC of the received information, and compares whether the calculated CRC is consistent with the received one. If the two do not match, it indicates an error. When performing CRC calculation, only 8 data bits, start bit and stop bit are used. If there is a parity bit, the parity bit is not involved in the CRC calculation.

1.3 Description of main function codes

1.3.1 Reading Internal Coils (Contacts)

Function code: 0x01
Description: Read multiple internal coil data of the slave. Broadcast commands are not supported. (Address 0XXXX)
Query
The query information specifies the start address and number of coils to be read. Coil addresses start from 0.
Take the reading of the values ​​of the 7th to 15th internal coils in the slave with the address of 17 as an example:

insert image description here

Response
Response message format:

insert image description here

Computer sends command: [device address] [function code 01] [start register address high 8 bits] [low 8 bits] [read register number high 8 bits] [low 8 bits] [CRC check low 8 bits ] [High 8 bits of CRC check]
Device response: [Device address] [Function code 01] [Number of returned bytes] [Data 1] [Data 2]...[Data n] [Low 8 bits of CRC check bit] [high 8 bits of CRC check]

1.3.2 Read digital input

Function code: 0x02
Description: Read multiple binary inputs of the slave. Broadcast commands are not supported. (Address 1XXXX, read-only)
Query and response are the same as function code 0x01

1.3.3 Write digital quantity (coil status)

Function code: 0x05
Example: [11][05][00][AC][FF][00][CRC low][CRC high] 1. Function code
: The command number for writing digital quantity is fixed at 05.
2. Higher 8 bits and lower 8 bits of the address of the register to be lowered: it indicates the address of the switch that needs to be lowered.
3. Higher 8 bits and lower 8 bits of the lower data: it indicates the status of the lower switching value. In the example, close the switch. Note that here only [FF][00] means closed [00][00] means open, other values ​​are illegal.
4. This command can only set the status of one switch.
The computer sends the command: [device address] [function code 05] [higher 8 bits of the register address to be lowered] [lower 8 bits] [lower 8 bits of data] [lower 8 bits] [lower 8 bits of the CRC check bit] [High 8 bits of CRC check]
Device response: If the command sent by the computer is successful, it will return as it is, otherwise it will not respond.

1.3.4 Fetch multiple holding registers (data registers)

Function code: 0x03
Description: Read the binary data of multiple holding registers of the slave. Broadcast commands are not supported. (Address 4XXXX)
Query
Query information specifies the start address and number of registers to be read. Register addresses start from 0.
Take the 108th to 110th registers in the slave with the address of 17 as an example:
query message format

insert image description here

Response
Response message format:

insert image description here

Computer sends command: [device address] [function code 03] [high 8 bits of starting register address] [low 8 bits] [high 8 bits of read register number] [low 8 bits] [low 8 bits of CRC check ] [High 8 bits of CRC check]
Device response: [Device address] [Function code 03] [Number of returned bytes] [Data 1] [Data 2]...[Data n] [Low 8 bits of CRC check bit] [high 8 bits of CRC check]

1.3.5 Reading Multiple Input Registers (Analog Input Registers)

Function code: 0x04
Description: Read the binary data of multiple analog input registers of the slave. Broadcast commands are not supported. (address 3XXXX, read-only)
same as 0x03 function code

1.3.6 Writing a single register

Function code: 0x06
Description: Write a single register of the slave. (Address 4XXXX)
command: The command specifies the address of the register to be written and the value to be written. Addresses start at 900.
Take the example of writing data 1 into the No. 900 register in the slave with address 1:
command message format:

insert image description here

Response
Response message format: the same as the command format.
The computer sends the command: [device address] [function code 06] [higher 8 bits of the register address to be lowered] [lower 8 bits] [lower 8 bits of data] [lower 8 bits] [lower 8 bits of the CRC check bit] [high 8 bits of CRC check]

1.3.7 Writing Multiple Holding Registers

Function code: 0x10
Description: Write multiple holding registers of the slave. (Address 4XXXX)
command: The command specifies the address of the register to be written and the value to be written. Addresses start at 900.
Take writing data 1 into registers 900 and 901 of the slave with address 1 as an example:
command message format:

insert image description here
Response
Response message format:

insert image description here

The computer sends the command: [device address] [function code 10] [high 8 bits of the start address of the register to be set down] [low 8 bits] [high byte of the number of registers to be written] [low byte] [to be written number of bytes (equal to the number of registers*2)] [lower 8 bits of data] [low 8 bits] [low 8 bits of CRC check] [high 8 bits of CRC check] device response: if
successful Response: device address, function code [0x10], register start address high byte, low byte, high byte and low byte of the number of registers to be written, CRC check low byte, high byte

2 Use python to complete the Modbus protocol to obtain information from the cloud

Cloud information requests are divided into two categories, one is through TCP协议the request, and the other is through UDP协议the request. The following mainly analyzes the use of python to make TCP协议the request. UDP协议In addition to the need to connect with each time the data is requested, other data request formats and analysis Received data is consistent with TCP.

2.1 Request data in TCP mode

  1. establish tcp connection
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. Generate crc16 check digit
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. command sending and receiving
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. data storage to 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()

Store the data in the mysql database, and the Modbus-TCP cloud data collection is completed. The following is the complete Modbus-TCP collection code:

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 request data

UDP connection

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

The complete code is as follows:

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 Running results

The results of the TCP transmission program are as follows:

insert image description here

The database storage effect is as follows:

insert image description here

3 Use c language to complete the modbus protocol to read information from the cloud server

The following describes the method of requesting data using tcp

3.1 New project

This article uses the little panda ide
download link

Create a new TCP client application

insert image description here

3.2 Modify the code

  1. Initialize the socket dll
    to connect to the corresponding port of the server through ip, so as to obtain data
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. Generate crc16 check code
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. Analyze data and extract
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);

The complete code is as follows:

#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 Realize the effect

insert image description here

Summarize

Through this experiment, we learned how to use the Modbus protocol for communication. Modbus is a serial communication protocol that is free, simple, and easily modifiable. But Modbus is master-slave communication, that is to say, it cannot communicate synchronously. Only one data is transmitted on the bus at a time, that is, the master sends and the slave responds. If the master does not send, there is no data communication on the bus.


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

Guess you like

Origin blog.csdn.net/apple_52030329/article/details/128433115