斐讯M1的报文
家里有個斐訊M1的空氣檢測儀,原來可以發(fā)送數(shù)據(jù)到服務(wù)器,再從手機(jī)app查看的。后來廠家倒閉了,服務(wù)器也關(guān)了,,,因?yàn)槁?lián)系不到服務(wù)器,wifi信號燈就會一直一閃一閃的。這幾天嘗試模擬搭建服務(wù)器,獲得成功,記錄一下。
一、首先原服務(wù)器的域名是aircat.phicomm.com ,這個是改不了的,但是可以通過在路由器的dns上做設(shè)置,把它解析為自己局域網(wǎng)內(nèi)的服務(wù)器IP,修改完ping一下域名,能成功的解析為本地地址就對了。
二、服務(wù)器通信的端口是tcp 9000 。
最簡單是在模擬的服務(wù)器上執(zhí)行
netcat -l -p 9000 ,
防火墻也要開放9000端口
sudo ufw allow 9000
此時M1能訪問到aircat.phicomm.com:9000 , 信號燈就不會閃了。而且可以看到netcat不斷收到M1發(fā)送的數(shù)據(jù)。
三、如果要進(jìn)一步,拿到發(fā)送的數(shù)據(jù),就需要再做些開發(fā)了。嘗試用python做了個簡單實(shí)現(xiàn)
# coding: utf-8 import select import socket import queue from time import sleepresponseMsg = b'\x00\x18\x00\x00\x02{"type":5,"status":1}\xff#END#'# Create a TCP/IP server = socket.socket(socket.AF_INET, socket.SOCK_STREAM) server.setblocking(False)# Bind the socket to the port server_address = ('192.168.88.17', 9000) print ('starting up on %s port %s' % server_address) server.bind(server_address)# Listen for incoming connections server.listen(5)# Sockets from which we expect to read inputs = [server]# Sockets to which we expect to write # 處理要發(fā)送的消息 outputs = []# Outgoing message queues (socket: Queue) message_queues = {}while inputs:# Wait for at least one of the sockets to be ready for processingprint ('waiting for the next event')# 開始select 監(jiān)聽, 對input_list 中的服務(wù)器端server 進(jìn)行監(jiān)聽# 一旦調(diào)用socket的send, recv函數(shù),將會再次調(diào)用此模塊readable, writable, exceptional = select.select(inputs, outputs, inputs)# Handle inputs# 循環(huán)判斷是否有客戶端連接進(jìn)來, 當(dāng)有客戶端連接進(jìn)來時select 將觸發(fā)for s in readable:# 判斷當(dāng)前觸發(fā)的是不是服務(wù)端對象, 當(dāng)觸發(fā)的對象是服務(wù)端對象時,說明有新客戶端連接進(jìn)來了# 表示有新用戶來連接if s is server:# A "readable" socket is ready to accept a connectionconnection, client_address = s.accept()print ('connection from', client_address)# this is connection not serverconnection.setblocking(0)# 將客戶端對象也加入到監(jiān)聽的列表中, 當(dāng)客戶端發(fā)送消息時 select 將觸發(fā)inputs.append(connection)# Give the connection a queue for data we want to send# 為連接的客戶端單獨(dú)創(chuàng)建一個消息隊(duì)列,用來保存客戶端發(fā)送的消息message_queues[connection] = queue.Queue()else:# 有老用戶發(fā)消息, 處理接受# 由于客戶端連接進(jìn)來時服務(wù)端接收客戶端連接請求,將客戶端加入到了監(jiān)聽列表中(input_list), 客戶端發(fā)送消息將觸發(fā)# 所以判斷是否是客戶端對象觸發(fā)data = s.recv(1024)# 客戶端未斷開if len(data)>0:# A readable client socket has dataprint ('received [%s %s] from %s' % (data[:23].hex(), data[23:], s.getpeername()))# 將收到的消息放入到相對應(yīng)的socket客戶端的消息隊(duì)列中responsedata=data[:23] + responseMsgmessage_queues[s].put(responsedata)# Add output channel for response# 將需要進(jìn)行回復(fù)操作socket放到output 列表中, 讓select監(jiān)聽if s not in outputs:outputs.append(s)else:# 客戶端斷開了連接, 將客戶端的監(jiān)聽從input列表中移除# Interpret empty result as closed connectionprint ('closing', client_address)# Stop listening for input on the connectionif s in outputs:outputs.remove(s)inputs.remove(s)s.close()# Remove message queue# 移除對應(yīng)socket客戶端對象的消息隊(duì)列del message_queues[s]# Handle outputs# 如果現(xiàn)在沒有客戶端請求, 也沒有客戶端發(fā)送消息時, 開始對發(fā)送消息列表進(jìn)行處理, 是否需要發(fā)送消息# 存儲哪個客戶端發(fā)送過消息for s in writable:try:# 如果消息隊(duì)列中有消息,從消息隊(duì)列中獲取要發(fā)送的消息message_queue = message_queues.get(s)send_data = ''if message_queue:send_data = message_queue.get_nowait()else:# 客戶端連接斷開了print ("has closed")except queue.Empty:# 客戶端連接斷開了errinfo=s.getpeername()print ( errinfo )outputs.remove(s)else:if message_queue :if len(send_data)>0:print ("send %s " % send_data)s.send(send_data)else:print ("has closed ")# writable.remove(s)# print "Client %s disconnected" % (client_address)# # Handle "exceptional conditions"# 處理異常的情況for s in exceptional:print ('exception condition on', s.getpeername())# Stop listening for input on the connectioninputs.remove(s)if s in outputs:outputs.remove(s)s.close()# Remove message queuedel message_queues[s]sleep(1)#start #[aa0f01350742398f0b0000000000000000b0f89324705300 b'\x00\x00\x04{ "humidity": "47.29", "temperature": "23.66", "value": "1", "hcho": "10" }\xff#END#'] from ('192.168.130.40', 23741)#[aa0f01350742398f0b0000000000000000b0f89324705300 b'\x00\x00\x01\xff#END#'] #[aa0f01350742398f0b0000000000000000b0f8932470 b'\x00\x03\x00\x00\x01\xff#END#'代碼tcp通信的框架基本復(fù)制自網(wǎng)上,原作可能是在python2開發(fā)的,自己微調(diào)了一下,在python3可以跑。這里面關(guān)鍵是通信的報文解析,經(jīng)過觀察是這樣的:
?1、M1往服務(wù)器送的報文
aa 0f 01 35 07 42 39 8f 0b 00 00 00 00 00 00 00 00 b0 f8 93 24 70 53?b'\x00\x00\x00\x00\x04{ "humidity": "47.29", "temperature": "23.66", "value": "1", "hcho": "10" }\xff#END#
(1)前23個字節(jié)(紅色部分,十六進(jìn)制表示)一般是固定的,把它分為四組
| aa 0f 01 | 35 07 42 39 8f 0b | 00 00 00 00 00 00 00 00 | b0 f8 93 24 70 53 | |
| 第一組 | 第二組 | 第三組 | 第四組 |
第一組3個字節(jié)aa 0f 01是固定的,可以看為是報文標(biāo)志符,可以用它來幫助判斷是否M1的報文;
第三組固定是8個全00字節(jié);第四組是mac地址 ;
第二組經(jīng)過觀察,其實(shí)是第四組(mac地址)的鏡像,可能是為了校驗(yàn)或者迷惑?
(2)隨后是5個字節(jié)?+ Json數(shù)據(jù)?
5個字節(jié)定義不明,但目前只看到b'\x00\x00\x00\x00\x04?' ,這個不影響數(shù)據(jù)解析
Json數(shù)據(jù)就是發(fā)送的溫度、濕度、pm2.5等數(shù)據(jù),稍作解析就可以利用。
(3)最后是結(jié)束標(biāo)志:? ?b'\xff#END#?'? ,
跟開頭的報文標(biāo)志聯(lián)合起來可以應(yīng)該可以用正則提取報文了。
2、服務(wù)器往M1發(fā)的報文
服務(wù)器往M1發(fā)的報文有兩種
(1)心跳報文
aa 0f 01 35 07 42 39 8f 0b 00 00 00 00 00 00 00 00 b0 f8 93 24 70 53?b'\x00\x18\x00\x00\x02{"type":5,"status":1}\xff#END#'
這個報文紅色部分跟M1發(fā)過來的前23個字節(jié)是一樣的,可以從M1送來的報文截取后原封不動發(fā)回去。(調(diào)試時試過如果不一樣,會導(dǎo)致M1主動關(guān)閉連接,估計是m1判斷送來的報文mac地址跟自己mac地址不一樣,認(rèn)為不是自己的數(shù)據(jù)。)后面的定義不明,不過也是固定的。
這個報文的作用好像是會激發(fā)M1上報數(shù)據(jù),如果不發(fā)的話,M1在連接成功最初,會密集報送一定量的數(shù)據(jù),之后如果沒有服務(wù)器反饋會明顯減慢發(fā)送頻率。在代碼里看見有人定義它為“心跳報文”。
(2)設(shè)置亮度
aa 0f 01 35 07 42 39 8f 0b 00 00 00 00 00 00 00 00 b0 f8 93 24 70 53?b'\x00\x18\x00\x00\x02{"brightness":"50.0","type":2}\xff#END#'
這個報文是設(shè)置屏幕亮度,brightness后面的數(shù)字一般是0、 25、 50、 75、 100,亮度逐漸增強(qiáng)
( 觀察發(fā)現(xiàn)發(fā)送了設(shè)置屏幕亮度的報文后,M1會暫停上報數(shù)據(jù)了,轉(zhuǎn)而發(fā)送
[aa0f01350742398f0b0000000000000000b0f893247053 b'\x00\x03\x00\x00\x01\xff#END#']
可能是對于設(shè)置亮度成功的一種應(yīng)答。
此時需要服務(wù)器發(fā)一條心跳報文給M1,就會開始繼續(xù)上報數(shù)據(jù))
總結(jié)
- 上一篇: aden -接球游戏3.0
- 下一篇: 记录一次微信小程序+阿里云oss的配置步