measure.py 19 KB
Newer Older
方开's avatar
方开 committed
1 2 3
import json
import time
from binascii import a2b_hex, b2a_hex
方开's avatar
方开 committed
4

方开's avatar
方开 committed
5 6
import gevent
from gevent import monkey, socket
方开's avatar
方开 committed
7 8 9 10
from cmdUtil import genCmd
from checkUtil.checkWater import checkWater
from cmdUtil.getCmd import GetCmds
from common.crc16.crc16 import crc16s
11
from common.filePath.filePath import logging
方开's avatar
方开 committed
12

13
monkey.patch_all()
方开's avatar
方开 committed
14

15

16 17 18 19 20 21 22 23
# 将十六进制数转成二进制有符号数据负数取补码,正数不变,最终计算其十进制的数值
def hex2Int(data, times=1, bits=16):
    data = (bin(int(data, 16))[2:]).zfill(bits)
    value = int(data[1:], 2) - int(data[0]) * (1 << (bits-1))
    result = value / times
    return result


24
# 根据仓房类型不同对温度、空气温湿度、虫害、气体、通风控制数据作解析,水份类型数据直接全部返回
25
def checkData(data: bytes, houseType: str, measureType: str, pointnum):
方开's avatar
方开 committed
26
    # 收到的所有数据用小写表示便于处理
方开's avatar
方开 committed
27
    data = b2a_hex(data).decode().lower()
方开's avatar
方开 committed
28
    # 不同类型仓房传回来的数据协议不同
29
    if houseType in ["multi", "control"]:
方开's avatar
方开 committed
30 31 32
        # 第七个字节为数据的标志位
        flag = data[12:14]
        # 如果是测温数据的话
33
        if flag == "a1" or measureType == "temp":
方开's avatar
方开 committed
34 35
            tempData = data[14:]
            realTemp = []
36
            # 温度换算,异常坏点数据取 None 值
方开's avatar
方开 committed
37
            for i in range(0, len(tempData), 4):
方开's avatar
方开 committed
38
                if tempData[i] == "8":
39
                    tempValue = int(tempData[i+1:i+4], 16) * 0.0625
方开's avatar
方开 committed
40
                    realTemp.append(tempValue)
41 42
                else:
                    realTemp.append(None)
43 44

            logging.info(f"该通道温度实际数据为{realTemp}")
方开's avatar
方开 committed
45 46
            return realTemp
        # 空气温湿度
47
        elif flag == "a2":
方开's avatar
方开 committed
48 49 50 51 52 53
            tempData = data[14:18]
            humiData = data[18:22]
            # 温湿度换算
            realTempValue = int(tempData, 16) / 10
            realHumiValue = int(humiData, 16) / 10
            realTempHumi = [realTempValue, realHumiValue]
54
            logging.info(f"该通道空气温湿度实际数据为{realTempHumi}")
方开's avatar
方开 committed
55 56 57 58
            return realTempHumi
        # 虫害数据处理
        elif flag == "aa":
            # 虫害数据全部返回,分为多路多组数据
方开's avatar
方开 committed
59
            bugData = data.split("1f00000044")[1:]
方开's avatar
方开 committed
60 61 62 63
            # 针对每组数据分别存放数据
            bugValues = []
            for bug1 in bugData:
                data1 = bug1[4:]
方开's avatar
方开 committed
64
                bugValue = [int(data1[i:i+4], 16)
65
                            for i in range(0, len(data1), 4)]
方开's avatar
方开 committed
66
                bugValues.append(bugValue)
67
            logging.info(f"虫害实际数值为{bugValues}")
方开's avatar
方开 committed
68 69 70
            return bugValues

        # 气体数据 磷化氢、氧气、二氧化碳
71
        elif flag in ["a6", "a7", "a8"]:
方开's avatar
方开 committed
72
            airData = data.split("1f00000044")[1:]
73
            ph2List, o2List, co2List = [], [], []
方开's avatar
方开 committed
74 75 76
            for i in airData:
                if "a6" in i:
                    ph2Data = i[4:8]
77
                    ph2Value = int(ph2Data, 16) * 1
方开's avatar
方开 committed
78
                    ph2List.append(ph2Value)
79
                elif "a7" in i:
方开's avatar
方开 committed
80
                    o2Data = i[4:8]
81
                    o2Value = int(o2Data, 16) * 0.01
方开's avatar
方开 committed
82
                    o2List.append(o2Value)
83
                elif "a8" in i:
方开's avatar
方开 committed
84
                    co2Data = i[4:8]
85
                    co2Value = int(co2Data, 16) * 10
方开's avatar
方开 committed
86 87 88
                    co2List.append(co2Value)
                else:
                    pass
89 90 91
            airList = [ph2List, o2List, co2List]
            logging.info(f"气体实际数值为{airList}")
            return airList
方开's avatar
方开 committed
92 93

        # 水份数据
94
        elif flag == "ac" and measureType == "water":
95
            return [data]
方开's avatar
方开 committed
96 97

        # 通风控制数据
方开's avatar
方开 committed
98
        # 返回数据的最后两个字节代表1-16路的开关状态
99
        elif flag == "a3" and measureType == "wind":
100
            windData = (bin(int(data[-4:], 16))[2:]).zfill(16)
方开's avatar
方开 committed
101
            windData = windData[6:16]
102
            logging.info(f"通风状态数据为{windData}")
方开's avatar
方开 committed
103
            return [windData]
方开's avatar
方开 committed
104
        # 气象站
105
        elif measureType == "weather":
方开's avatar
方开 committed
106 107
            weatherData = data[12:]
            # 分别解析相应的数据
108 109 110 111 112 113 114 115
            airpeedData = hex2Int(weatherData[:4], times=10)
            rainFallData = hex2Int(weatherData[4:8], times=5)
            airPressureData = hex2Int(weatherData[8:12], times=10)
            sumRainfallData = hex2Int(weatherData[12:16], times=5)
            temperatureData = hex2Int(weatherData[16:20], times=10)
            humidityData = hex2Int(weatherData[20:24], times=10)
            snowData = hex2Int(weatherData[24:28], times=1)
            windDirectionData = hex2Int(weatherData[28:32], times=1)
方开's avatar
方开 committed
116

方开's avatar
方开 committed
117
            # 换算成实际数据列表返回
118 119 120
            weatherDatas = [airpeedData, rainFallData, airPressureData, sumRainfallData, temperatureData, humidityData, snowData,
                            windDirectionData]
            logging.info(f"气象站实际数据为{weatherDatas}")
方开's avatar
方开 committed
121
            return weatherDatas
方开's avatar
方开 committed
122
        else:
方开's avatar
方开 committed
123
            return []
方开's avatar
方开 committed
124

125
    # 单一类型采用直接与硬件通信的协议,貌似已经废弃!
126 127
    elif houseType == "single":
        if measureType == "temp":
方开's avatar
方开 committed
128 129 130 131 132 133 134
            # 温度数据的crc位和待校验的数据
            crcValue = data[-4:]
            tempData = data[:-4]
            # 经过crc16校验看数据是否一致
            if crcValue == crc16s(tempData):
                tempData = data[14:-4]
                realTemp = []
135
                # 对温度数据进行解析处理,坏点补上 None 值
方开's avatar
方开 committed
136
                for i in range(0, len(tempData), 4):
方开's avatar
方开 committed
137 138
                    if tempData[i] == "8":
                        tempValue = int(tempData[i+1:i+4], 16) * 0.0625
方开's avatar
方开 committed
139 140 141
                        realTemp.append(tempValue)
                    else:
                        realTemp.append(None)
方开's avatar
方开 committed
142
                return realTemp
方开's avatar
方开 committed
143
            else:
144
                logging.warning(f"crc 检验失败,数据不正确!")
方开's avatar
方开 committed
145 146 147
                return []

        # 空气温湿度数据处理
148
        elif measureType == "temphumi":
方开's avatar
方开 committed
149 150 151 152 153 154 155 156
            # 温度数据的crc位和待校验的数据
            crcValue = data[-4:]
            tempHumiData = data[:-4]
            # 经过crc16校验看数据是否一致
            if crcValue == crc16s(tempHumiData):
                # 分别取出温度和湿度数据
                tempData = data[14:18]
                humiData = data[18:22]
方开's avatar
方开 committed
157 158
                tempValue = int(tempData[1:4], 16) * 0.0625
                humiValue = int(humiData[1:4], 16) / 10
方开's avatar
方开 committed
159 160
                realTempHumi = [tempValue, humiValue]
                return realTempHumi
方开's avatar
方开 committed
161

方开's avatar
方开 committed
162 163 164 165
            else:
                logging.info(f"crc 检验失败,数据不正确!")
                return []
        else:
166
            return []
方开's avatar
方开 committed
167
    else:
168
        return []
方开's avatar
方开 committed
169 170


171 172
# 发起测试的Tcp客户端,并且数据经过处理,此为测试最小单位
def measureClient(sendAddr, cmd, measureType, houseType="multi", pointnum=None, timeout=50):
方开's avatar
方开 committed
173
    try:
174
        # 设置一个带有超时异常的tcp客户端
175
        with gevent.Timeout(timeout, exception=TimeoutError):
方开's avatar
方开 committed
176 177 178 179 180
            tcpSocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
            logging.info(f"正在向{sendAddr}发送指令{cmd}")
            tcpSocket.connect(sendAddr)
            tcpSocket.send(cmd)
            data = tcpSocket.recv(1024)
方开's avatar
方开 committed
181
            # 处理返回的数据
182
            logging.info(f"收到原始数据为:{data}")
183
            datas = checkData(data, houseType, measureType, pointnum)
184 185
            logging.info(f"解析后的数据为{datas}")
            # return datas
方开's avatar
方开 committed
186
    except Exception as e:
187 188
        datas = []
        logging.warn(f"{e},硬件不返回数据")
方开's avatar
方开 committed
189 190
    finally:
        tcpSocket.close()
191
        return datas
方开's avatar
方开 committed
192

方开's avatar
方开 committed
193
# 测量控制器返回处理好的数据
194 195


196
def measureit(houseId, measureType="all", timeout=2):
方开's avatar
方开 committed
197
    try:
198 199 200 201
        # 获取待测试的仓房信息
        gc = GetCmds(houseId)
        houseType = gc.houseType
        subIds = gc.getSubIdByType(measureType)
202
        logging.info(f"正处理测试类型为: {measureType}--{subIds}命令")
203 204 205 206 207
        # 针对不同测试类型设置不同策略
        if measureType == "temp":
            dataList = []
            for subId in subIds:
                # 如果命令列表数据不为空
208
                addr, cmds, pointNums = gc.getCmdsBySubId(subId)
209
                if cmds:
210 211 212
                    for index, cmd in enumerate(cmds):
                        # 根据每个通道的点数去截取数据
                        pointNum = pointNums[index]
213 214 215
                        # 测试客户端
                        data = measureClient(addr, a2b_hex(
                            cmd),  measureType, houseType, pointNum, timeout)
216 217 218 219 220 221 222 223 224 225 226 227 228
                        # 判断返回的温度数据长度是否足够
                        dataLen = len(data)
                        # 如果点数不够的话
                        if dataLen < int(pointNum):
                            needLen = int(pointNum) - dataLen
                            data.extend([None]*needLen)

                        elif dataLen > int(pointNum):
                            data = data[:int(pointNum)]

                        else:
                            pass

229
                        logging.info(f"此通道温度数据为{data}")
230
                        dataList.append(data)
231 232 233

            logging.info(f"所有的温度数据为{dataList}")
            return dataList
234 235 236 237 238 239 240 241 242 243 244 245 246

        elif measureType == "temphumi":
            exterList = []
            interList = []
            for subId in subIds:
                # 如果命令列表数据不为空
                addr, cmds, pointNum = gc.getCmdsBySubId(subId)
                if cmds:
                    # 分别存放外温外湿、内温内湿
                    for cmd in cmds:
                        # 测试客户端
                        data = measureClient(addr, a2b_hex(
                            cmd),  measureType, houseType, pointNum, timeout)
方开's avatar
方开 committed
247 248 249 250 251 252 253 254
                        interList.extend(data)
            # 单独测试一次外温外湿
            exterCmdData = gc.getExterCmd()
            if exterCmdData != None:
                addr = exterCmdData[0]
                exterCmd = exterCmdData[1]
                logging.info("准备测试外温外湿")
                exterData = measureClient(addr, a2b_hex(
方开's avatar
方开 committed
255
                    exterCmd),  measureType, "multi", pointNum, timeout)
方开's avatar
方开 committed
256 257
                logging.info(exterData)
                exterList.extend(exterData)
258
            logging.info(f"内温内湿数据为{interList},外温外湿数据为{exterData}")
259
            return [interList, exterList]
260

方开's avatar
方开 committed
261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279
        elif measureType == "bug":
            subId = subIds[0]
            # 如果命令列表数据不为空
            addr, cmds, pointNum = gc.getCmdsBySubId(subId)
            dataList = []
            # 测试指令和复位指令
            if cmds:
                # 发送测试指令
                cmd = cmds[0]
                data = measureClient(addr, a2b_hex(
                    cmd),  measureType, houseType, pointNum, timeout)
                dataList.extend(data)
                # 发送复位指令
                resetCmd = cmds[1]
                try:
                    tcpSocket = socket.socket(
                        socket.AF_INET, socket.SOCK_STREAM)
                    logging.info(f"正在向虫害分机发送复位指令{resetCmd}")
                    tcpSocket.connect(addr)
方开's avatar
方开 committed
280
                    tcpSocket.send(a2b_hex(resetCmd))
方开's avatar
方开 committed
281 282 283 284 285 286 287
                except Exception as e:
                    logging.info(f"发送复位指令异常:{e}")

            return dataList

        elif measureType in ["air", "weather"]:
            # 气体、气象站只有一个分机
288 289 290 291
            subId = subIds[0]
            # 如果命令列表数据不为空
            addr, cmds, pointNum = gc.getCmdsBySubId(subId)
            dataList = []
方开's avatar
方开 committed
292
            if cmds:
293 294 295 296 297
                # 只有单条命令
                cmd = cmds[0]
                data = measureClient(addr, a2b_hex(
                    cmd),  measureType, houseType, pointNum, timeout)
                dataList.extend(data)
298 299 300

            logging.info(f"测试类型为{measureType}的数据为{dataList}")
            return dataList
301

302 303 304 305 306 307 308 309
        elif measureType == "water":
            subId = subIds[0]
            addr, cmds, pointNum = gc.getCmdsBySubId(subId)
            subInfo = gc.getSubInfo(subId)
            cerealsSpecies = subInfo.get("cerealsSpecies")
            if cmds:
                # 单条指令
                cmd = cmds[0]
310
                # 注意到为了保证measureClient 函数的返回值一致,需要将水分值放在list里,所以使用的时候采用 data[0]
311 312
                data = measureClient(addr, a2b_hex(
                    cmd),  measureType, houseType, pointNum, timeout)
313
                waterList = checkWater(cerealsSpecies, data[0])
314
                dataList = waterList
315 316 317

            logging.info(f"全部水份数据为-{dataList} ")
            return dataList
318 319

        elif measureType == "wind":
方开's avatar
方开 committed
320 321 322
            windList = []
            for subId in subIds:
                # 如果命令列表数据不为空
方开's avatar
方开 committed
323
                addr, cmds, pointNum = gc.getCmdsBySubId(subId, types="wind")
方开's avatar
方开 committed
324 325 326 327 328
                if cmds:
                    cmd = cmds[0]
                    data = measureClient(addr, a2b_hex(
                        cmd),  measureType, houseType, pointNum, timeout)
                    windList.extend(data)
329 330

            logging.info(f"所有的通风数据为{windList}")
方开's avatar
方开 committed
331 332
            return windList

333 334
        else:
            return []
方开's avatar
方开 committed
335
    except Exception as e:
方开's avatar
方开 committed
336
        logging.info(f"发生错误:{e}")
337
        return []
方开's avatar
方开 committed
338 339


方开's avatar
方开 committed
340
# 控制处理
341
def controlit(houseId, measureType="wind", flag="on", timeout=3):
方开's avatar
方开 committed
342
    try:
343
        # 获取该仓库信息
344 345 346
        gc = GetCmds(houseId)
        houseType = gc.houseType
        SubId = gc.getSubIdByType(measureType)
方开's avatar
方开 committed
347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362
        result = []
        for subid in SubId:
            addr, cmds = gc.getCmdsByFlag(subid, flag)
            # 如果获取到指定的命令
            if cmds:
                for cmd in cmds:
                    # 取最后一次结果
                    data = measureClient(addr, a2b_hex(
                        cmd),  measureType, houseType, None, timeout)
                    time.sleep(2)
                result.append(data)
            else:
                logging.warning(f"无通风命令,请检查通风设置参数是否正确")
                result.append(None)

        return result
方开's avatar
方开 committed
363
    except Exception as e:
方开's avatar
方开 committed
364
        logging.info(f"控制通风错误:{e}")
365
        return []
方开's avatar
方开 committed
366 367


368 369 370 371 372 373 374
# 处理接收到json格式的测试命令或控制指令
def handleCommand(command: str):
    try:
        cmd = json.loads(command)
    except Exception as e:
        logging.info(f"解析json格式指令错误:{e}")
    # 分别获得待测试仓房编号、测试指令、控制指令
方开's avatar
方开 committed
375 376 377 378 379 380 381 382 383
    houseId = cmd.get("id")
    measureCmd = cmd.get("measure")
    controlCmd = cmd.get("control")
    if houseId:
        dicts = {}
        dicts["id"] = houseId
        if measureCmd:
            # 对于测试指令中的每种类型进行测试
            alldict = {}
384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404
            try:
                for tp in measureCmd:
                    # 气体虫害测试超时为60秒
                    if tp in ["air", "bug"]:
                        timeout = 50
                    # 水分为20秒
                    elif tp == "water":
                        timeout = 50
                    # 其余类型为3秒
                    else:
                        timeout = 2
                    data = measureit(houseId, tp, timeout)
                    # 对于水分信息,需要获取到其类型
                    if tp == "water":
                        gc = GetCmds(houseId)
                        subid = gc.getSubIdByType("water")
                        cerealsSpecies = gc.getSubInfo(
                            subid[0]).get("cerealsSpecies")
                        alldict["water"] = [
                            {"species": cerealsSpecies, "data": data}]

方开's avatar
方开 committed
405
                    elif tp in ["temp", "temphumi", "bug", "air", "weather", "wind"]:
406
                        alldict[tp] = [{"species": tp, "data": data}]
方开's avatar
方开 committed
407

408 409 410 411 412 413 414 415 416
                    else:
                        pass
                logging.info("已测试完所有指令数据")
                dicts["measured"] = alldict
                sendData = json.dumps(dicts)
                return sendData
            except Exception as f:
                logging.info(f"error:{f}")
                sendData = None
417
        # 如果是控制命令
418 419
        elif controlCmd != None:
            logging.info(controlCmd)
方开's avatar
方开 committed
420
            controlList = []
421 422 423 424 425 426 427 428 429 430
            try:
                for tp in controlCmd:
                    data = controlit(
                        houseId, tp, flag=controlCmd[tp], timeout=3)
                    controlList.append({"species": tp, "data": data})
                dicts["controled"] = controlList
                sendData = json.dumps(dicts)
            except Exception as g:
                logging.info(f"指令error:{g}")
                sendData = None
方开's avatar
方开 committed
431
        else:
方开's avatar
方开 committed
432
            sendData = None
方开's avatar
方开 committed
433
    else:
方开's avatar
方开 committed
434
        sendData = None
方开's avatar
方开 committed
435 436 437
    return sendData


方开's avatar
方开 committed
438 439 440 441
# 接收云平台服务器的心跳信息或者命令信息
def recv(tcpSocket):
    try:
        while True:
442
            # 接收数据解码替换成全小写
方开's avatar
方开 committed
443
            data = tcpSocket.recv(1024)
444
            data = data.decode("utf-8").lower()
方开's avatar
方开 committed
445 446 447 448 449 450 451 452
            # 处理客户端断开造成的失败
            if not data:
                break
            logging.info(f"接收到服务器数据: {data}")
            if "ff" in data:
                logging.info("接收心跳包成功")
            elif "measure" in data or "control" in data:
                # 命令处理后发送给服务器
方开's avatar
方开 committed
453 454
                result = handleCommand(data)
                if result == None:
方开's avatar
方开 committed
455 456 457
                    logging.info("解析指令和返回数据失败")
                    pass
                else:
方开's avatar
方开 committed
458
                    sendData = result + "eeffeeff"
方开's avatar
方开 committed
459 460
                    tcpSocket.send(sendData.encode())
                    logging.info("发送给服务器数据完毕")
方开's avatar
方开 committed
461 462 463 464 465
            else:
                logging.info("不明指令")
                pass
    except Exception as e:
        logging.info(f"接收error:{e}")
466
        returnData = json.dumps({"code": "0"}, ensure_ascii=False) + "eeffeeff"
方开's avatar
方开 committed
467
        tcpSocket.send(returnData.encode())
方开's avatar
方开 committed
468
    finally:
方开's avatar
方开 committed
469
        pass
方开's avatar
方开 committed
470

471

472
# 定时发送心跳信息
方开's avatar
方开 committed
473 474 475 476 477 478 479 480 481 482 483
def heartBeat(tcpSocket):
    sendData = b"ABCffff"
    while True:
        tcpSocket.send(sendData)
        logging.info("发送心跳信息")
        time.sleep(60)


# 建立和云平台的连接
def TCPClient():
    sendAddr = ('49.4.64.207', 6050)
方开's avatar
方开 committed
484
    while True:
方开's avatar
方开 committed
485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500
        try:
            tcpSocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
            tcpSocket.connect(sendAddr)
            gevent.joinall([
                gevent.spawn(recv, tcpSocket),
                gevent.spawn(heartBeat, tcpSocket)
            ])
        except Exception as e:
            logging.info(f"连接error:{e},retrying....")
            time.sleep(30)
            # 30秒重新连接服务器
            continue


def main():
    TCPClient()
方开's avatar
方开 committed
501 502 503


if __name__ == "__main__":
方开's avatar
方开 committed
504
    main()