快速部署一个简易的环温监测网络(BLE+MQTT+HTTP)

      

         本文不包含公司机密技术和信息。

        来新公司几个月了,发现有一个困扰的点是,货架上的机器对环境温度还比较敏感,然后机器本身散热也挺严重,导致货架上部分机器受到高温干扰无法正常运行。

       因为我在秒测时主要就是做的测温这一块儿,所以提了个主意,想要在货架上部署温度传感器,然后通过物联网网关集成数据,我们就可以随时监测当前的机器处在什么温度下。领导雷厉风行,觉得对研发有一定的帮助,直接拍板了。

       想法有了,就去找了一下,发现了有很多专用的物联网服务器,比如mosquitto之类的。再加上蓝牙网关,初步想法基本上就形成了。

       最终的技术框架:货架上根据需求密度,部署好BLE温度传感器,该传感器与具体的位置一一绑定,然后不断的广播该区域的温度数据。然后根据场地地形以及实际测试,部署BLE网关设备,网关会定期扫描周边的BLE广播数据,集成后上传到后端的物联网服务器。该服务器本身也提供一个简单的http服务,用于展示数据和提供查询服务。   

       大概这么个结构(BLE + MQTT +HTTP):

479284eb708c42489188532eeaac5610.png

        这个网络的一个好处是,无论哪一环,各种设备,实现方式等,都可以灵活替换。

        传感器毫无疑问使用BLE无线温度计,超低功耗且传输距离适合室内部署。这里我直接选了老东家秒秒测的产品,秒测的产品做的很棒,然后因为自己参与打造的,很信任也很熟悉~。秒测几乎所有的带温度测量功能的产品都有BLE,所以理论上选哪个都行。这里综合使用场景考虑,无线冷链标签以及米家蓝牙温湿度计2。

        然后BLE网关,我自己找了一个比较好的功能很强大的网关,但是秒测同事帮推荐了四月兄弟的产品,简陋一些但是便宜很多,出于信任便选了后者。收到网关后发现,四月兄弟提供了一个测试用的mqtt服务器,这下好了,前期不用自己部署服务器了。

        收到网关的时候,恰逢疫情居家办公,就自己在家里做测试,我手头还保留了一些当时发的家用温湿度计,还有几个研发用的冷链标签(属于离职未归还的公司财产,罪过…)。按照说明配置好网关后,直接就可以在工具中看到网关扫描到的广播数据了,很棒。

        然后开始研究如何部署自己的服务器,毕竟四月兄弟的测试服务器不受控制。网络上有很多企业级的,腾讯和阿里云都有提供此类服务,比较有名的,EMQX,功能非常强大。还有一个是轻量级的mosquitto,可以部署在低功耗的单板计算机上。

        我这里的话,是在一个专用网络环境中,并发量不会太大。考虑到我可能得自己准备机器部署,刚好手头有一个正在吃灰的树莓派,就选择了mosquitto。

        这里可以直接安装eclipse-mosquitto,也可以自己下载源码编译,两个我都尝试过了,都比较easy。配置好端口号启动服务后,修改网关的上报地址和topic等信息,就可以在mosquitto的log中看到有源源不断的数据上来了。

        接下来需要提供一个简单的http服务,用来展示数据以及提供查询接口。

        由于传感器要和货架一一对应,这里要预先准备好对应关系。某个传感器,其放置在哪个位置,对应该位置的机器的ip段是多少,这样后续查询的时候就可以通过绑定关系知道某个ip对应的货架温度是多少。

        这里我使用一个json文件来描述这个关系,比如A22这个货架,对应的ip分段是 10.77.22,此货架上部署了两个传感器。

[
	{
		"position":"A22",
		"ip_start":"10.77.22.0",
		"ip_end":"10.77.22.254",		
		"sensor":["FE9738011038","FE9738011096"]		
	},
	{
		"position":"A41",
		"ip_start":"10.77.41.0",
		"ip_end":"10.77.41.254",		
		"sensor":["FE9738011025","A4C138F18B7F"]		
	}

]

代码如下,这里由于并发数量很低,所以写的代码也偷懒,需要什么数据,直接暴力遍历了(我下次一定从良23333)。然后这里两种传感器都做了解析,只要有广播上来会先识别是哪个设备,再按照对应的格式解析数据。然后实在不熟悉python,找了个web.py模块提供简单的http服务。

#! /usr/bin/env python3
# -- coding:utf-8--

import os
import sys

sys.path.append('/home/min.hu/.local/lib/python3.9')
sys.path.append('/home/min.hu/.local/lib/python3.9/site-packages')
sys.path.append(os.path.abspath(os.path.dirname(__file__) + '/' + '..'))
sys.path.append("..")

import time
import paho.mqtt.client as mqtt
import msgpack
import json
import web

#HOST= "mqtt.bconimg.com"
HOST= "xx.xx.xx.xx"
PORT=1884
TOPIC ="hm/test"
USER ="hm"
PWD="hm"

NIOT_NAME = "MOT-U202"      #冷链标签
MINI_NAME = "LYWSD03MMC"    #米家蓝牙温湿度计2

niot_list=set()
mini_list=set()
maps={}
lists=[]

#@json file:labs_more_2.json

def read_lists(folder):
    filelist =os.listdir(folder)
    for i in range(0,len(filelist)):
        path = os.path.join(folder,filelist[i])
        print("file:{}".format(filelist[i]))
        if os.path.isfile(path) and path.endswith('.json'):
            with open(path,'r') as jfile:
                mlist = json.load(jfile)  
                lists.extend(mlist)
                for k in range(0,len(mlist)):
                    print(mlist[k])
        
def get_mac_info_by_band_ip(band_ip):
    for key,value in maps.items():
        iplist=value['ip']
        for ip in iplist:
            if(band_ip == ip):
                return key              
    return None
def get_map_info_by_ip(band_ip):
    ip_section= band_ip.rsplit('.',1)  
    for item in lists:
        if item['ip_start'].startswith(ip_section[0]):
            #直接返回
            return item
    return None    
    
def get_map_info_by_mac(band_mac):
    for item in lists:        
        if band_mac in item['sensor']:
            return item
    return None    


def print_map_info(map):
    print(map) 

#niot    
# 02 01 06 03 02 09 18 17 16 CF CF FE 97 38 01 10 38 D4 0C 21 05 00 00 00 00  01  C4   66 0B    00 0A
#                                 |-------mac-------|-max-|-min-|--counter--|sta|batt|cur_temp|itv(0.5min)|
#mini
# 02 01 06 11 16 95 FE 30 58 5B 05 0C 7F 8B F1 38 C1 A4 28 01 00 05 38 01 F6 01 64 ...
#                                    |------mac--------|           |temp |-hum-|batt|      

      
def read_temp(mac,advdata,device):
    cur_temp =float(0)
    if device ==NIOT_NAME:
        cur_temp = float(advdata[27]|(advdata[28]<<8))/100
    elif device ==MINI_NAME:          
        cur_temp = float(advdata[22]|(advdata[23]<<8))/10    

    for item in lists:        
        if mac in item['sensor']:
            index =item['sensor'].index(mac)
            if not 'temp' in item:
                item['temp']=['' for i in range(len(item['sensor']))]
            item['temp'][index]=cur_temp
            print(item)
            break

def check_name_from_ltv_data(mac,advdata):
    i=0
    while(i < len(advdata)):
        length =advdata[i]
        sec_type=advdata[i+1]        
        if sec_type ==9:#通过设备名称筛选
            name = ''.join(chr(advdata[i+2+x]) for x in range(length-1))
            if(MINI_NAME == name):
                mini_list.add(mac)
            elif(NIOT_NAME == name):
                niot_list.add(mac)
            else:
                None        
        i+=(length+1)

def check_temp_from_lvt_data(mac,advdata):
    if mac in niot_list:
        read_temp(mac,advdata,NIOT_NAME)    
    elif mac in mini_list:
        read_temp(mac,advdata,MINI_NAME) 

def print_hex(bytes):
    # l=[hex(int(i)) for i in bytes]
    l=["%02X"%(int(i)) for i in bytes] 
    print(" ".join(l))

def on_subscribe(mosq, obj, mid, granted_qos):
    print("Subscribed: " + str(mid))

def on_message(mosq, obj, msg):
    for d in msgpack.unpackb(msg.payload, raw = True)[b'devices']:
        #parse iBeacon data        
        advData = d[8:]
        mac = "{:02X}{:02X}{:02X}{:02X}{:02X}{:02X}".format((d[1]), (d[2]), (d[3]), (d[4]), (d[5]), (d[6]))
        # print("mac:%s"%mac)
        map=get_map_info_by_mac(mac)
        if map!=None:
            if(d[0]==4):            
                check_name_from_ltv_data(mac,advData)
            else:    
                check_temp_from_lvt_data(mac,advData)             


def on_connect(mosq, obj,flags, rc):
    mqttTopic = TOPIC
    print("Connected with result code "+str(rc))
    mqttc.subscribe(mqttTopic, 0)
    print("Connected")

read_lists("./")
mqttHost    = HOST
mqttPort    = PORT
mqttc = mqtt.Client()
mqttc.on_connect = on_connect
mqttc.on_subscribe = on_subscribe
mqttc.on_message = on_message
mqttc.username_pw_set(USER,PWD)
mqttc.connect(mqttHost, mqttPort, 60)
# mqttc.loop_forever()
mqttc.loop_start()

#query page.

class sensor:
    def GET(self,ip):
        if not ip:            
            return temp_page(lists)  
        else:
            map =get_map_info_by_ip(ip)
            if(map):
                print(map)  
                return map

    
urls = ('/(.*)', 'sensor')
app = web.application(urls, globals())
temp_page =web.template.frender('templates/temp.html')

if __name__ == "__main__":
    print("app.run...")
    app.run()
    

        发现可以使用模板文件:

$def with(lists)

<!DOCTYPE html>
<html>
    <head>        
        <meta charset="utf-8">
        <meta http-equiv="X-UA-Compatible" content="IE=edge">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>温度详情</title>           

    </head>
    <body>
        <h1>设备温度列表:</h1>
<table border ='1' >        
        
$for item in lists:
    $if 'temp' in item:
        <tr>
            <td>货架:</td>
            <td>$item['position']</td>
            <td>Mac:</td>
            <td>$item['sensor']</td>
            <td>温度:</td>
            <td>$item['temp']</td>
        </tr>        
</table> 
</body>
</html>

        效果(这里是在服务器上跑,然后本地访问的,localhost是vscode自动转发的端口):

11f14c02272643379e3109491efb7d0d.png

         假如在访问地址后添加一个ip参数,则会直接返回这样一组数据:

{'position': 'A22', 'ip_start': '10.77.22.0', 'ip_end': '10.77.22.254', 'sensor': ['FE9738011038', 'FE9738011096'], 'temp': [31.27, 30.02]}

        更进一步的扩展能力,比如需要查看某个货架的历史温度,有两种实现方式。一种是,服务器端集成数据仓库服务,持久化接收到的温度数据,物联网环境推荐使用同样小巧轻便的sqlite3;另外传感器本身是有存储历史数据的,可以通过直接读取某个传感器的数据来展示,这中方式需要网关支持,然后BLE的数据吞吐量限制,传输数据需要一定的时间。

然后几个主要的问题点。

        树莓派的优点是,轻巧的很,部署也很方便,哪里需要就往哪里搬。如果没有专用服务器的话,一个树莓派几百元就能搞定。使用树莓派存在的一个问题是,只能在局域网中访问。如果需要在外部网络访问,需要花生壳之类的内网穿透软件。一套下来,成本可能比直接部署在云上高…

        由于公司本身有研发专用的服务器,我想了想就省点钱,把mosquitto转移到研发服务器上。本身是个linux小白,又没有root权限,折腾起来有点麻烦,最后是在docker上安装的。

        网上有一大堆如何在docker上部署mosquitto的文章,基本上都说明白了。有一个细节的地方是,mosquitto.conf文件的配置,各种路径要对应docker中的环境而不是本机。mosquitto.conf文件配置(docker):

persistence true
persistence_location /mosquitto/data
allow_anonymous false
password_file /mosquitto/config/pwfile.conf
log_dest file /mosquitto/log/mqtt.log
log_type error
log_type warning
log_type debug
listener 1884
#注意在docker中生成的文件不会同步到当前主机中,比如pwfile.conf文件,这里是手动复制过来的

Docker启动脚本:

docker run -itd \
 --name=mosquitto\
 --privileged=true \
 -p 1884:1884 -p 9001:9001 \
 -v /home/min.hu/mqtt/config/mosquitto.conf:/mosquitto/config/mosquitto.conf \
 -v /home/min.hu/mqtt/config/pwfile.conf:/mosquitto/config/pwfile.conf \
 -v /home/min.hu/mqtt/data:/mosquitto/data \
 -v /home/min.hu/mqtt/log:/mosquitto/log \
  eclipse-mosquitto 

另一个比较麻烦的问题是,对于一些专用的vpn网络,部署起来相对麻烦。我们这边,要找运维开端口开防火墙,然后坑爹的网络环境跑起来还各种丢数据,ssh都登录不上,要反复的测试。

实际部署

        一般基于BLE5.0以上的网关,其传输距离在50米左右,也就是说,一台网关可以覆盖方圆100米的范围。然后实际部署的时候,要考虑到墙壁以及各种物体阻碍信号传输,具体要部署多少个网关可以实际测试下。

        然后传感器的部署数量,这里可以分为中高低三种精度。中等精度,每个货架放两台,一边一个;对于一个高精度的监测网络而言,可以每个货架每层放两台,或者更高精度每台机器旁边都放置一个传感器;低精度的话,每个货架一个就可以。

        MQTT服务器的架构本身决定了,可以有多个sub client 和pub client。所以扩充起来非常方便,适合一步一步将整个网络从小到大逐渐扩展。

        然后部署成本上,不计算其他任务的情况下,整体实际研发和部署时间:1-2天;

附,成本清单:

        四月兄弟IOT网关(不带电源):265元/台 +电源12元/台;

        米家蓝牙温湿度计2:29元/台。寿命一年,可换电池。
    优点:带液晶显示屏,带湿度数据,本机可存储三个月数据;
    缺点:0℃以下可能无法正常工作;此产品为米家定制,未绑定米家账号的时候,不广播温湿度数据,可行性需要与秒测验证;
        秒秒测独角兽:69元/台:

    寿命:一年半以上,若调整广播频率和数据记录间隔,估计最低可达两年以上。
    优点:超长寿命可免于维护,设备本身可连续记录10万组温度数据,对于后续拓展有一定的想象空间;
    特点:防水塑封包装,适用于长期监测稳定运行,带塑封的情况下温度反应较缓慢,当前场景可拆掉塑封。   

  • 3
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值